(オリジナル)speexとwav形式オーディオファイルの相互変換(二)
63663 ワード
前にspeexとwav形式のオーディオをどのように変換するかを書いたことがありますが、見たことがない場合は接続を見てください.
http://www.cnblogs.com/dongweiq/p/4515186.html
自分で関連する圧縮アルゴリズムを実現したが,gaussの圧縮割合と若干差があり,一部はパラメータ設定の問題であり,もう一部はoggを使用していない問題であることが分かった.
もともとgaussのoggアルゴリズムを研究して、彼が録画したオーディオをwavフォーマットに変えて、後のスペクトル描画などを続けたいと思っていました.
後続のgaussの復号過程では,oggのフォーマットを先に解いてセグメント化し,speexのヘッダを除去し,セグメントのspeexデータをpcmの元データに復号し,audiotrackセグメントを用いて再生した.audiotrackは放送中はブロックされていたので、どのくらい放送したら解けたのか.
彼が1段のpcmの元のデータを得た以上、私はこの段の元のデータをつづることができて、最后に復号が完成した全体のpcmのデータを得て、最后にwavの头をプラスして直接wavフォーマットのオーディオに変换することができますか??
おとといここを思い出して、すぐに直しました.
SpeexDecoderはgaussのdemoの主要な復号クラスで、私たちは1部コピーして、SpeexFileDecoderと改名します
再生に関連するaudiotrack変数を削除します.再生に関係なく復号データが得られるからです.
修正後のコードは以下の通りです.
上はspeexが160個のshortタイプの配列を復号したため、javaがファイルを書くにはbyte配列を書き込む必要があるので、shortがbyte配列を回転する方法shortArray 2 ByteArrayを使ってクラスをパッケージしました.下にも貼って
読み込んだ元のデータをdstPathのファイルに入れて、どうやって操作したのか見てみましょう.その中でgaussのspeexplayerメソッドを修正しました.
speexPlayerメソッドをコピーし、SpeexFileDecoderHelperと名前を変更し、次のように変更します.
この方法は、既存の復号化をオンにし、復号化が完了するとhandlerを送信し、コールバックメソッドを呼び出し、復号化に失敗したか成功したかを通知する.OnSpeexFileCompletionListenerこれは簡単ですが、貼る必要がありますか?やはり貼っておきましょう.
このコードは全部貼ってあります.なに?!まだ使えないの?ああ、私はまだwavの頭を加える方法を書いていません.じゃ、もう一つ方法を書きましょう.
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を歓迎します.何か問題があったらメールで連絡してください[email protected] qq714094450
http://www.cnblogs.com/dongweiq/p/4515186.html
自分で関連する圧縮アルゴリズムを実現したが,gaussの圧縮割合と若干差があり,一部はパラメータ設定の問題であり,もう一部はoggを使用していない問題であることが分かった.
もともとgaussのoggアルゴリズムを研究して、彼が録画したオーディオをwavフォーマットに変えて、後のスペクトル描画などを続けたいと思っていました.
後続のgaussの復号過程では,oggのフォーマットを先に解いてセグメント化し,speexのヘッダを除去し,セグメントのspeexデータをpcmの元データに復号し,audiotrackセグメントを用いて再生した.audiotrackは放送中はブロックされていたので、どのくらい放送したら解けたのか.
彼が1段のpcmの元のデータを得た以上、私はこの段の元のデータをつづることができて、最后に復号が完成した全体のpcmのデータを得て、最后にwavの头をプラスして直接wavフォーマットのオーディオに変换することができますか??
おとといここを思い出して、すぐに直しました.
SpeexDecoderはgaussのdemoの主要な復号クラスで、私たちは1部コピーして、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配列を回転する方法shortArray 2 ByteArrayを使ってクラスをパッケージしました.下にも貼って
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を歓迎します.何か問題があったらメールで連絡してください[email protected] qq714094450