java wav转pcm_WAV格式转换成MP3

Java (4) 2024-08-13 17:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
java wav转pcm_WAV格式转换成MP3,希望能够帮助你!!!。

之前写过了如何将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 packagecom.sixin.speex;2

3 importjava.io.File;4 importjava.io.FileOutputStream;5 importjava.io.IOException;6 importjava.io.RandomAccessFile;7 importjava.util.ArrayList;8 importjava.util.List;9

10 importandroid.media.AudioFormat;11 importandroid.media.AudioManager;12 importandroid.media.AudioTrack;13 importandroid.os.RecoverySystem.ProgressListener;14 importandroid.util.Log;15

16 /**

17 * 采用Jspeex方案,首先解包,从ogg里面接出来,然后使用speex decode将speex转为wav数据并进行播放18 *19 *@authorHonghe20 */

21 public classSpeexFileDecoder {22

23 protectedSpeex speexDecoder;24 private String errmsg = null;25 private List listenerList = new ArrayList();26 privateFile srcPath;27 privateFile dstPath;28

29 public SpeexFileDecoder(File srcPath, File dstPath) throwsException {30 this.srcPath =srcPath;31 this.dstPath =dstPath;32 }33

34 private void initializeAndroidAudio(int sampleRate) throwsException {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 voidaddOnMetadataListener(ProgressListener l) {43 listenerList.add(l);44 }45

46 publicString getErrmsg() {47 returnerrmsg;48 }49

50 public void decode() throwsException {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 = newSpeex();64 speexDecoder.init();65 //open the input stream

66 RandomAccessFile dis = new RandomAccessFile(srcPath, "r");67 FileOutputStream fos = newFileOutputStream(dstPath);68

69 intorigchksum;70 intchksum;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 *

155 * 0 - 7: speex_string: "Speex "156 * 8 - 27: speex_version: "speex-1.0"157 * 28 - 31: speex_version_id: 1158 * 32 - 35: header_size: 80159 * 36 - 39: rate160 * 40 - 43: mode: 0=narrowband, 1=wb, 2=uwb161 * 44 - 47: mode_bitstream_version: 4162 * 48 - 51: nb_channels163 * 52 - 55: bitrate: -1164 * 56 - 59: frame_size:  * 60 - 63: vbr166 * 64 - 67: frames_per_packet167 * 68 - 71: extra_headers: 0168 * 72 - 75: reserved1169 * 76 - 79: reserved2170 * 

171 *172 *@parampacket173 *@paramoffset174 *@parambytes175 *@return

176 *@throwsException177 */

178 private boolean readSpeexHeader(final byte[] packet, final int offset, final int bytes, boolean init) throwsException {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=" + channels191 //+ "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 intoffset) {203 /*

204 * no 0xff on the last one to keep the sign205 */

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 intoffset) {210 /*

211 * no 0xff on the last one to keep the sign212 */

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 intoffset) {218 /*

219 * no 0xff on the last one to keep the sign220 */

221 return (data[offset] & 0xff) | (data[offset + 1] << 8);222 }223

224 }

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

1 packagecom.sixin.speex;2

3 public classShortAndByte {4 /**

5 * @功能 短整型与字节的转换6 *@param短整型7 *@return两位的字节数组8 */

9 public static byte[] shortToByte(shortnumber) {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 returnb;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 returns;31 }32

33 /**

34 * @说明 主要是为解析静态数据包,将一个字节数组转换为short数组35 *@paramb36 */

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

53 /**

54 * @说明 主要是为解析静态数据包,将一个short数组反转为字节数组55 *@paramb56 */

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 returnrebt;68 }69 }

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

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

1 /**

2 *3 */

4 packagecom.sixin.speex;5

6 importjava.io.File;7

8 importandroid.os.Handler;9

10 /**

11 *@authorhonghe12 *13 */

14 public classSpeexFileDecoderHelper {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 = newHandler() {23 public voidhandleMessage(android.os.Message msg) {24 int what =msg.what;25 switch(what) {26 casespeexdecode_completion:27 if (speexListener != null) {28 speexListener.onCompletion(speexdec);29 } else{30 System.out.println("司信---------null===speexListener");31 }32 break;33 casespeexdecode_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 publicSpeexFileDecoderHelper(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 voidstartDecode() {64 RecordDecodeThread rpt = newRecordDecodeThread();65 Thread th = newThread(rpt);66 th.start();67 }68

69 public boolean isDecoding = false;70

71 class RecordDecodeThread extendsThread {72

73 public voidrun() {74 try{75 if (speexdec != null) {76 isDecoding = true;77 speexdec.decode();78 if (null !=speexdec.getErrmsg()) {79 throw newException(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 voidstopDecode() {100 isDecoding = false;101 }102

103 publicString getSpxFileName() {104 return this.srcName;105 };106 }

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

1 packagecom.sixin.speex;2

3 /**

4 * Speex音频解码完成监听5 *@authorhonghe6 *7 */

8 public interfaceOnSpeexFileCompletionListener {9 voidonCompletion(SpeexFileDecoder speexdecoder);10 voidonError(Exception ex);11 }

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

1 /**

2 * 语音转换3 *4 *@paramname5 *@paramsrcFileName spx文件名6 *@paramdstFileName 转换后得到文件的文件名7 */

8 public static void decodeSpx(Context context, String srcFileName, finalString 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, newOnSpeexFileCompletionListener() {18

19 @Override20 public voidonError(Exception ex) {21 System.out.println("转换错误");22 }23

24 @Override25 public voidonCompletion(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

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

发表回复