zoukankan      html  css  js  c++  java
  • (原创)speex与wav格式音频文件的互相转换(二)

    之前写过了如何将speex与wav格式的音频互相转换,如果没有看过的请看一下连接

    http://www.cnblogs.com/dongweiq/p/4515186.html

    虽然自己实现了相关的压缩算法,但是发现还是与gauss的压缩比例差了一些,一部分是参数设置的问题,另外一部分是没有使用ogg的问题。

    本来想研究一下gauss的ogg算法,然后将他录制的音频转为wav格式,再继续进行后面的频谱绘制之类的。

    在后续的研究gauss的解码过程,他是先解了ogg的格式,然后分段,然后去掉speex的头,然后把一段段的speex数据再解码成pcm的原数据,最后使用audiotrack一段段播放出来了。audiotrack播放的时候是阻塞的,所以播了多久就解了多久。

    既然他得到了一段段pcm的原数据,那我就可以去将这一段段的原数据拼起来,最后得到解码完成的整个的pcm数据,最后加上wav的头不就可以直接转换成wav格式的音频了么???

    前天的时候想到这里,立马就去改了。

    SpeexDecoder是gauss的demo里主要的解码类,我们复制一份,改名为SpeexFileDecoder

    去掉里面跟播放相关的audiotrack变量,因为我们要得到的是解码数据,跟播放无关。

    修改后代码如下

      1 package com.sixin.speex;
      2 
      3 import java.io.File;
      4 import java.io.FileOutputStream;
      5 import java.io.IOException;
      6 import java.io.RandomAccessFile;
      7 import java.util.ArrayList;
      8 import java.util.List;
      9 
     10 import android.media.AudioFormat;
     11 import android.media.AudioManager;
     12 import android.media.AudioTrack;
     13 import android.os.RecoverySystem.ProgressListener;
     14 import android.util.Log;
     15 
     16 /**
     17  * 采用Jspeex方案,首先解包,从ogg里面接出来,然后使用speex decode将speex转为wav数据并进行播放
     18  * 
     19  * @author Honghe
     20  */
     21 public class SpeexFileDecoder {
     22 
     23     protected Speex speexDecoder;
     24     private String errmsg = null;
     25     private List<ProgressListener> listenerList = new ArrayList<ProgressListener>();
     26     private File srcPath;
     27     private File dstPath;
     28 
     29     public SpeexFileDecoder(File srcPath, File dstPath) throws Exception {
     30         this.srcPath = srcPath;
     31         this.dstPath = dstPath;
     32     }
     33 
     34     private void initializeAndroidAudio(int sampleRate) throws Exception {
     35         int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
     36 
     37         if (minBufferSize < 0) {
     38             throw new Exception("Failed to get minimum buffer size: " + Integer.toString(minBufferSize));
     39         }
     40     }
     41 
     42     public void addOnMetadataListener(ProgressListener l) {
     43         listenerList.add(l);
     44     }
     45 
     46     public String getErrmsg() {
     47         return errmsg;
     48     }
     49 
     50     public void decode() throws Exception {
     51         errmsg = null;
     52         byte[] header = new byte[2048];
     53         byte[] payload = new byte[65536];
     54         final int OGG_HEADERSIZE = 27;
     55         final int OGG_SEGOFFSET = 26;
     56         final String OGGID = "OggS";
     57         int segments = 0;
     58         int curseg = 0;
     59         int bodybytes = 0;
     60         int decsize = 0;
     61         int packetNo = 0;
     62         // construct a new decoder
     63         speexDecoder = new Speex();
     64         speexDecoder.init();
     65         // open the input stream
     66         RandomAccessFile dis = new RandomAccessFile(srcPath, "r");
     67         FileOutputStream fos = new FileOutputStream(dstPath);
     68 
     69         int origchksum;
     70         int chksum;
     71         try {
     72 
     73             // read until we get to EOF
     74             while (true) {
     75                 if (Thread.interrupted()) {
     76                     dis.close();
     77                     return;
     78                 }
     79 
     80                 // read the OGG header
     81                 dis.readFully(header, 0, OGG_HEADERSIZE);
     82                 origchksum = readInt(header, 22);
     83                 readLong(header, 6);
     84                 header[22] = 0;
     85                 header[23] = 0;
     86                 header[24] = 0;
     87                 header[25] = 0;
     88                 chksum = OggCrc.checksum(0, header, 0, OGG_HEADERSIZE);
     89 
     90                 // make sure its a OGG header
     91                 if (!OGGID.equals(new String(header, 0, 4))) {
     92                     System.err.println("missing ogg id!");
     93                     errmsg = "missing ogg id!";
     94                     return;
     95                 }
     96 
     97                 /* how many segments are there? */
     98                 segments = header[OGG_SEGOFFSET] & 0xFF;
     99                 dis.readFully(header, OGG_HEADERSIZE, segments);
    100                 chksum = OggCrc.checksum(chksum, header, OGG_HEADERSIZE, segments);
    101 
    102                 /* decode each segment, writing output to wav */
    103                 for (curseg = 0; curseg < segments; curseg++) {
    104 
    105                     if (Thread.interrupted()) {
    106                         dis.close();
    107                         return;
    108                     }
    109 
    110                     /* get the number of bytes in the segment */
    111                     bodybytes = header[OGG_HEADERSIZE + curseg] & 0xFF;
    112                     if (bodybytes == 255) {
    113                         System.err.println("sorry, don't handle 255 sizes!");
    114                         return;
    115                     }
    116                     dis.readFully(payload, 0, bodybytes);
    117                     chksum = OggCrc.checksum(chksum, payload, 0, bodybytes);
    118 
    119                     /* decode the segment */
    120                     /* if first packet, read the Speex header */
    121                     if (packetNo == 0) {
    122                         if (readSpeexHeader(payload, 0, bodybytes, true)) {
    123 
    124                             packetNo++;
    125                         } else {
    126                             packetNo = 0;
    127                         }
    128                     } else if (packetNo == 1) { // Ogg Comment packet
    129                         packetNo++;
    130                     } else {
    131 
    132                         /* get the amount of decoded data */
    133                         short[] decoded = new short[160];
    134                         if ((decsize = speexDecoder.decode(payload, decoded, 160)) > 0) {
    135                             //把边解边播改为写文件
    136                             fos.write(ShortAndByte.shortArray2ByteArray(decoded), 0, decsize * 2);
    137                         }
    138                         packetNo++;
    139                     }
    140                 }
    141                 if (chksum != origchksum)
    142                     throw new IOException("Ogg CheckSums do not match");
    143             }
    144         } catch (Exception e) {
    145             e.printStackTrace();
    146         }
    147         fos.close();
    148         dis.close();
    149     }
    150 
    151     /**
    152      * Reads the header packet.
    153      * 
    154      * <pre>
    155      *  0 -  7: speex_string: "Speex   "
    156      *  8 - 27: speex_version: "speex-1.0"
    157      * 28 - 31: speex_version_id: 1
    158      * 32 - 35: header_size: 80
    159      * 36 - 39: rate
    160      * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb
    161      * 44 - 47: mode_bitstream_version: 4
    162      * 48 - 51: nb_channels
    163      * 52 - 55: bitrate: -1
    164      * 56 - 59: frame_size: 160
    165      * 60 - 63: vbr
    166      * 64 - 67: frames_per_packet
    167      * 68 - 71: extra_headers: 0
    168      * 72 - 75: reserved1
    169      * 76 - 79: reserved2
    170      * </pre>
    171      * 
    172      * @param packet
    173      * @param offset
    174      * @param bytes
    175      * @return
    176      * @throws Exception
    177      */
    178     private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throws Exception {
    179         if (bytes != 80) {
    180             return false;
    181         }
    182         if (!"Speex   ".equals(new String(packet, offset, 8))) {
    183             return false;
    184         }
    185         //        int mode = packet[40 + offset] & 0xFF;
    186         int sampleRate = readInt(packet, offset + 36);
    187         //        int channels = readInt(packet, offset + 48);
    188         //        int nframes = readInt(packet, offset + 64);
    189         //        int frameSize = readInt(packet, offset + 56);
    190         //        RongXinLog.SystemOut("mode=" + mode + " sampleRate==" + sampleRate + " channels=" + channels
    191         //                + "nframes=" + nframes + "framesize=" + frameSize);
    192         initializeAndroidAudio(sampleRate);
    193 
    194         if (init) {
    195             // return speexDecoder.init(mode, sampleRate, channels, enhanced);
    196             return true;
    197         } else {
    198             return true;
    199         }
    200     }
    201 
    202     protected static int readInt(final byte[] data, final int offset) {
    203         /*
    204          * no 0xff on the last one to keep the sign
    205          */
    206         return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | (data[offset + 3] << 24);
    207     }
    208 
    209     protected static long readLong(final byte[] data, final int offset) {
    210         /*
    211          * no 0xff on the last one to keep the sign
    212          */
    213         return (data[offset] & 0xff) | ((data[offset + 1] & 0xff) << 8) | ((data[offset + 2] & 0xff) << 16) | ((data[offset + 3] & 0xff) << 24) | ((data[offset + 4] & 0xff) << 32)
    214                 | ((data[offset + 5] & 0xff) << 40) | ((data[offset + 6] & 0xff) << 48) | (data[offset + 7] << 56);
    215     }
    216 
    217     protected static int readShort(final byte[] data, final int offset) {
    218         /*
    219          * no 0xff on the last one to keep the sign
    220          */
    221         return (data[offset] & 0xff) | (data[offset + 1] << 8);
    222     }
    223 
    224 }

    注意上面因为speex解码出来的是160个short类型的数组,而java写文件要求写入的是byte数组,所以我们还是用到了short转byte数组的方法shortArray2ByteArray,我封装了一个类。也贴在下边

     1 package com.sixin.speex;
     2 
     3 public class ShortAndByte {
     4     /** 
     5     * @功能 短整型与字节的转换 
     6     * @param 短整型 
     7     * @return 两位的字节数组 
     8     */
     9     public static byte[] shortToByte(short number) {
    10         int temp = number;
    11         byte[] b = new byte[2];
    12         for (int i = 0; i < b.length; i++) {
    13             b[i] = new Integer(temp & 0xff).byteValue();// 将最低位保存在最低位  
    14             temp = temp >> 8; // 向右移8位  
    15         }
    16         return b;
    17     }
    18 
    19     /** 
    20      * @功能 字节的转换与短整型 
    21      * @param 两位的字节数组 
    22      * @return 短整型 
    23      */
    24     public static short byteToShort(byte[] b) {
    25         short s = 0;
    26         short s0 = (short) (b[0] & 0xff);// 最低位  
    27         short s1 = (short) (b[1] & 0xff);
    28         s1 <<= 8;
    29         s = (short) (s0 | s1);
    30         return s;
    31     }
    32 
    33     /** 
    34      * @说明 主要是为解析静态数据包,将一个字节数组转换为short数组 
    35      * @param b 
    36      */
    37     public static short[] byteArray2ShortArray(byte[] b) {
    38         int len = b.length / 2;
    39         int index = 0;
    40         short[] re = new short[len];
    41         byte[] buf = new byte[2];
    42         for (int i = 0; i < b.length;) {
    43             buf[0] = b[i];
    44             buf[1] = b[i + 1];
    45             short st = byteToShort(buf);
    46             re[index] = st;
    47             index++;
    48             i += 2;
    49         }
    50         return re;
    51     }
    52 
    53     /** 
    54      * @说明 主要是为解析静态数据包,将一个short数组反转为字节数组 
    55      * @param b 
    56      */
    57     public static byte[] shortArray2ByteArray(short[] b) {
    58         byte[] rebt = new byte[b.length * 2];
    59         int index = 0;
    60         for (int i = 0; i < b.length; i++) {
    61             short st = b[i];
    62             byte[] bt = shortToByte(st);
    63             rebt[index] = bt[0];
    64             rebt[index + 1] = bt[1];
    65             index += 2;
    66         }
    67         return rebt;
    68     }
    69 }

    读出来的原数据我们放入到了dstPath的文件,再来看看是怎么操作的呢?其中我还是修改了gauss的speexplayer方法。

    我们复制speexPlayer方法,改名为SpeexFileDecoderHelper,按照如下方法修改

      1 /**
      2  * 
      3  */
      4 package com.sixin.speex;
      5 
      6 import java.io.File;
      7 
      8 import android.os.Handler;
      9 
     10 /**
     11  * @author honghe
     12  * 
     13  */
     14 public class SpeexFileDecoderHelper {
     15     private String srcName = null;
     16     private String dstName = null;
     17     private SpeexFileDecoder speexdec = null;
     18     private OnSpeexFileCompletionListener speexListener = null;
     19     private static final int speexdecode_completion = 1001;
     20     private static final int speexdecode_error = 1002;
     21 
     22     public Handler handler = new Handler() {
     23         public void handleMessage(android.os.Message msg) {
     24             int what = msg.what;
     25             switch (what) {
     26             case speexdecode_completion:
     27                 if (speexListener != null) {
     28                     speexListener.onCompletion(speexdec);
     29                 } else {
     30                     System.out.println("司信---------null===speexListener");
     31                 }
     32                 break;
     33             case speexdecode_error:
     34                 if (speexListener != null) {
     35                     File file = new File(SpeexFileDecoderHelper.this.srcName);
     36                     if (null != file && file.exists()) {
     37                         file.delete();
     38                     }
     39                     speexListener.onError(null);
     40                 }
     41                 break;
     42             default:
     43                 break;
     44             }
     45         };
     46     };
     47 
     48     public SpeexFileDecoderHelper(String fileName,String dstName, OnSpeexFileCompletionListener splistener) {
     49         this.speexListener = splistener;
     50         this.srcName = fileName;
     51         this.dstName = dstName;
     52         try {
     53             speexdec = new SpeexFileDecoder(new File(this.srcName),new File(this.dstName));
     54         } catch (Exception e) {
     55             e.printStackTrace();
     56             File file = new File(SpeexFileDecoderHelper.this.srcName);
     57             if (null != file && file.exists()) {
     58                 file.delete();
     59             }
     60         }
     61     }
     62 
     63     public void startDecode() {
     64         RecordDecodeThread rpt = new RecordDecodeThread();
     65         Thread th = new Thread(rpt);
     66         th.start();
     67     }
     68 
     69     public boolean isDecoding = false;
     70 
     71     class RecordDecodeThread extends Thread {
     72 
     73         public void run() {
     74             try {
     75                 if (speexdec != null) {
     76                     isDecoding = true;
     77                     speexdec.decode();
     78                     if (null != speexdec.getErrmsg()) {
     79                         throw new Exception(speexdec.getErrmsg());
     80                     }
     81                 }
     82                 System.out.println("RecordPlayThread   文件转换完成");
     83                 if (isDecoding) {
     84                     handler.sendEmptyMessage(speexdecode_completion);
     85                 } 
     86                 isDecoding = false;
     87             } catch (Exception t) {
     88                 t.printStackTrace();
     89                 System.out.println("RecordPlayThread   文件转换出错");
     90                 handler.sendEmptyMessage(speexdecode_error);
     91                 isDecoding = false;
     92             }
     93         }
     94     }
     95 
     96     /**
     97      * 结束播放
     98      */
     99     public void stopDecode() {
    100         isDecoding = false;
    101     }
    102 
    103     public String getSpxFileName() {
    104         return this.srcName;
    105     };
    106 }

    这个方法是开启了一个现成去解码,然后解码完成后会发送handler,调用回调方法,通知解码失败还是成功。OnSpeexFileCompletionListener这个很简单,我需要贴吗?还是贴上吧,省的被骂娘。

     1 package com.sixin.speex;
     2 
     3 /**
     4  * Speex音频解码完成监听
     5  * @author honghe
     6  *
     7  */
     8 public interface OnSpeexFileCompletionListener {
     9     void onCompletion(SpeexFileDecoder speexdecoder);
    10     void onError(Exception ex);
    11 }

    到此代码都贴出来了。什么?!还不会用?哦,我还没写怎么加wav头呢,那再写个方法吧

     1 /**
     2      * 语音转换
     3      * 
     4      * @param name
     5      * @param srcFileName spx文件名
     6      * @param dstFileName 转换后得到文件的文件名
     7      */
     8     public static void decodeSpx(Context context, String srcFileName, final String dstFileName) {
     9         final String temppath = AudioFileFunc.getFilePathByName("temp.raw");
    10         try {
    11             // 如果是speex录音
    12             if (srcFileName != null && srcFileName.endsWith(".spx")) {
    13                 if (mSpeexFileDecoderHelper != null && mSpeexFileDecoderHelper.isDecoding) {
    14                     stopMusic(context);
    15                 } else {
    16                     muteAudioFocus(context, true);
    17                     mSpeexFileDecoderHelper = new SpeexFileDecoderHelper(srcFileName, temppath, new OnSpeexFileCompletionListener() {
    18 
    19                         @Override
    20                         public void onError(Exception ex) {
    21                             System.out.println("转换错误");
    22                         }
    23 
    24                         @Override
    25                         public void onCompletion(SpeexFileDecoder speexdecoder) {
    26                             System.out.println("转换完成");
    27                             WaveJoin.copyWaveFile(temppath, dstFileName);
    28                         }
    29                     });
    30                     mSpeexFileDecoderHelper.startDecode();
    31                 }
    32             } else {
    33                 System.out.println("音频文件格式不正确");
    34             }
    35 
    36         } catch (Exception e) {
    37             e.printStackTrace();
    38         }
    39     }


    copyWaveFile这个方法在哪里?去看开头的那个链接吧,上面有。不过在加wav头的时候要注意,加的头要和你录制音频的时候设置的参数一致,比如samplerate,声道数,framesize这些,我就设置错了一次,gauss录制音频的时候使用的是单声道,我加入的wav头的channel设置成了2,结果放出来的声音老搞笑了,就跟快放一样。有兴趣你可以试试。

    代码已更新

    代码链接如下:

    https://github.com/dongweiq/study/tree/master/Record

    我的github地址:https://github.com/dongweiq/study

    欢迎关注,欢迎star o(∩_∩)o 。有什么问题请邮箱联系 dongweiqmail@gmail.com qq714094450

  • 相关阅读:
    命名空间“Aspose”中不存在类型或命名空间名称“Slides”。
    项目平台不同修改方法
    手机端自动跳转
    KindEditor得不到textarea值的解决方法
    正则表达式常用
    Log4Net不生成日志文件
    python笔记03-----文件操作
    python笔记04-----字典、元组、集合操作
    python笔记02-----字符串操作
    python笔记01-----列表操作
  • 原文地址:https://www.cnblogs.com/dongweiq/p/4521655.html
Copyright © 2011-2022 走看看