最近做的一個項目,需要給硬件傳輸語音。因為硬件的種種限制問題,要求:
1,音頻原生格式PCM。
2.采樣率8000,單聲道,采樣值大小16Bit。
我的音頻來源是接入了一個第三方的SDK,從中下載下來的音頻是AAC格式的。采樣率是44.1KHZ。雙聲道,16Bit。那么首先我需要把他轉成PCM。我用的是Android原生自帶的MediaCodec。具體可以看API文檔。有翻譯了的中文API(http://www.cnblogs.com/roger-yu/p/5635494.html);
public class AudioDecoder { private static final String TAG = "AudioDecoder "; private static final int TIMEOUT_US = 1000; private MediaExtractor mExtractor; private MediaCodec mDecoder; private String outputpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/zzz.pcm"; private FileOutputStream fos; private boolean eosReceived; private int mSampleRate = 0; int channel = 0; public void startPlay(String path) throws IOException { eosReceived = false; //創建MediaExtractor對象用來解AAC封裝 mExtractor = new MediaExtractor(); //設置需要MediaExtractor解析的文件的路徑 try { mExtractor.setDataSource(path); } catch (Exception e) { Log.e(TAG, "設置文件路徑錯誤" + e.getMessage()); } fos = new FileOutputStream(new File(outputpath)); MediaFormat format = mExtractor.getTrackFormat(0); if (format == null) { Log.e(TAG, "format is null"); return; } // chunkPCMDataContainer = new ArrayList<>(); //判斷當前幀的文件類型是否為audio String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("audio/")) { Log.d(TAG, "format :" + format); //獲取當前音頻的采樣率 mExtractor.selectTrack(0); mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); //獲取當前幀的通道數 channel = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); //音頻文件的長度 long duration = format.getLong(MediaFormat.KEY_DURATION); Log.d(TAG, "length:" + duration / 1000000); } //創建MedioCodec對象 mDecoder = MediaCodec.createDecoderByType(mime); //配置MedioCodec mDecoder.configure(format, null, null, 0); if (mDecoder == null) { Log.e(TAG, "Can't find video info"); return; } //啟動MedioCodec,等待傳入數據 mDecoder.start(); new Thread(AACDecoderAndPlayRunnable).start(); } Runnable AACDecoderAndPlayRunnable = new Runnable() { @Override public void run() { AACDecoderAndPlay(); } }; private void AACDecoderAndPlay() { //MediaCodec在此ByteBuffer[]中獲取輸入數據 ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); //MediaCodec將解碼后的數據放到此ByteBuffer[]中 我們可以直接在這里面得到PCM數 ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); //用於描述解碼得到的byte[]數據的相關信息 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); //啟動AudioTrack,這是個播放器,可以播放PCM格式的數據。如果有需要可以用到。不需要播放的直接刪掉就可以了。 /* int buffsize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_STEREO,AudioFormat.ENCODING_PCM_16BIT); //創建AudioTrack對象 AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, buffsize, AudioTrack.MODE_STREAM); //啟動AudioTrack audioTrack.play();*/ while (!eosReceived) { //獲取可用的inputBuffer 網上很多填-1代表一直等待,0表示不等待 建議-1,避免丟幀。我這里隨便填了個1000,也沒有問題。具體我也不太清楚 int inIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US); if (inIndex >= 0) { ByteBuffer buffer = inputBuffers[inIndex]; //從MediaExtractor中讀取一幀待解的數據 int sampleSize = mExtractor.readSampleData(buffer, 0); //小於0 代表所有數據已讀取完成 if (sampleSize < 0) { Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM"); mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } else { //插入一幀待解碼的數據 mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0); //MediaExtractor移動到下一取樣處 mExtractor.advance(); } //從mediadecoder隊列取出一幀解碼后的數據 參數BufferInfo上面已介紹 10000同樣為等待時間 同上-1代表一直等待,0代表不等待。此處單位為微秒 //此處建議不要填-1 有些時候並沒有數據輸出,那么他就會一直卡在這 等待 int outIndex = mDecoder.dequeueOutputBuffer(info, TIMEOUT_US); switch (outIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = mDecoder.getOutputBuffers(); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // MediaFormat format = mDecoder.getOutputFormat(); // audioTrack.setPlaybackRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE)); break; case MediaCodec.INFO_TRY_AGAIN_LATER: break; default: ByteBuffer outBuffer = outputBuffers[outIndex]; //BufferInfo內定義了此數據塊的大小 final byte[] chunk = new byte[info.size]; // createFileWithByte(chunk); //將Buffer內的數據取出到字節數組中 outBuffer.get(chunk); //數據取出后一定記得清空此Buffer MediaCodec是循環使用這些Buffer的,不清空下次會得到同樣的數據 outBuffer.clear(); // putPCMData(chunk); try { //將解碼出來的PCM數據IO流存入本地文件。 fos.write(chunk); } catch (IOException e) { e.printStackTrace(); } // audioTrack.write(chunk,info.offset,info.offset+info.size); //此操作一定要做,不然MediaCodec用完所有的Buffer后 將不能向外輸出數據 mDecoder.releaseOutputBuffer(outIndex, false); break; } //所有幀都解碼完之后退出循環 if (info.flags != 0) { Log.i("AA", "轉碼成功++++++++++++++"); break; } /* 所有幀都解碼完並播放完之后退出循環 if((info.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){ break; }*/ } } Log.i("AA", "轉碼成功"); //釋放MediaDecoder資源 mDecoder.stop(); mDecoder.release(); mDecoder = null; //釋放MediaExtractor資源 mExtractor.release(); mExtractor = null; /* //釋放AudioTrack資源 audioTrack.stop(); audioTrack.release(); audioTrack = null;*/ //關流 try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } public void stop() { eosReceived = true; } }
在Activityd里調用
private static final String SAMPLE = Environment.getExternalStorageDirectory().getAbsolutePath()+"/需解碼的文件名"; protected static AudioDecoder mAudioDecoder; mAudioDecoder = new AudioDecoder(); play.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { mAudioDecoder.startPlay(SAMPLE); } catch (IOException e) { e.printStackTrace(); } } });
好了,至此解碼部分已經搞定了。打你解出來文件一看。我擦嘞!一個1.6M的AAC文件。解出來有90M。差不多快擴大了80倍。不要問我什么,因為我也不知道。要么就是我解錯了,要么就是解出來就是這么大。
PCM的是解出來了。但是我的解出來的是44.1KHZ采樣率的。我的天,還要我重采樣。你要我這種小菜雞情何以堪。於是我在網上各種找。終於找到一個庫 JSSRC。
這里是github地址:https://github.com/hutm/JSSRC
里面的類也不是所有都需要用到,只需要SSRC,I0Bessel,SplitRadixFft這三個類就可以實現轉換采樣率的功能。於是我就把那三個類搬到我的工程里面來了。
然后直接用下面這個方法就可以把44.1KHZ重采樣成8K的了
public void Resampling(){ File BeforeSampleChangedFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/目標文件"); File SampleChangedFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/目的文件"); try { FileInputStream fis = new FileInputStream(BeforeSampleChangedFile); FileOutputStream fos = new FileOutputStream(SampleChangedFile);
//同樣低采樣率轉高采樣率也是可以的,改下面參數就行。 new SSRC(fis,fos,44100,8000,2,2,1,Integer.MAX_VALUE,0,0,true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
基本就到此結束了。
-----------------Android世界里的一只小菜雞