平台
PX30 + Android 9.0 + AndroidStudio 4.1.3
概述
在Android 平台上实现AMR-WB的编解码, 要求不高, JAVA也行, C/CPP也行, 可惜相关的资料很少. 在完成本文前, 走了一段相当坎坷的路, GOOGLE/BAIDU能给的帮助都相当的有限, 特此记录.
关于 AMR WB
“AMR-WB”全称为“Adaptive Multi-rate - Wideband”,即“自适应多速率宽带编码”,采样频率为16kHz,是一种同时被国际标准化组织ITU-T和3GPP采用的宽带语音编码标准,也称 为G722.2标准。AMR-WB提供语音带宽范围达到50~7000Hz,用户可主观感受到话音比以前更加自然、舒适和易于分辨。
与之作比较,现在GSM用的EFR(Enhenced Full Rate,增强型全速率编码)采样频率为8kHz,语音带宽为200~3400Hz。
AMR-WB应用于窄带GSM(全速信道16k,GMSK)的优势在于其可采用从6.6kb/s, 8.85kb/s和12.65kb/s三种编码,当网络繁忙时C/I恶化,编码器可以自动调整编码模式,从而增强QoS。在这种应用中,AMR-WB抗扰度优于AMR-NB。
AMR-WB应用于EDGE、3G可充分体现其优势。足够的传输带宽保证AMR-WB可采用从6.6kb/s到23.85kb/s共九种编码,语音质量超越PSTN固定电话
更多说明:
AMR nb and wb
amr nb和wb的帧结构 百锐科技
(PS: 上面两篇文章大同小异, 只是同样翻阅了好多次, 当然某度文档还有同样的收费内容…)
解码篇
测试用的文件和工具
文件 : AMR-WB file samples
工具: VLC (有搞过多媒体相关的, 都知道它)
解码资料
基于 AMR-WB编解码器的移动网络话音传输抗丢包算法
opencore-amr-android(不支持AMR-WB)
amr-wb-enc-android(AMR-WB编码, 非解码)
android amr编解码(一些知识, 与标题还是偏得有点远)
MediaCodec之Decoder(了解MediaCodec并使用它)
解决方案
在着手前, 有几个关键问题需谨慎确认
1.音频帧数据准确: 建议下载对应的文件格式进行测试, 数据源导致不可判定问题根源, 如每帧数据的准确性
(PS, 刚开始未确认数据源, 一直以56字节/帧去解码, 浪费了大量时间, 实际应该是61字节/帧)
2.音频数据清晰可分辨: 如果拿到的AMR音频数据底噪或杂音较大, 会影响对解码效果的判断
(PS, 数据源码夹杂了一些杂音, 导致一直以为解码器或解码步骤有问题)
3.音频参数正确
4.解码器
方案1: MediaCodec
import android.annotation.SuppressLint; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaExtractor; import android.media.MediaFormat; import android.util.Log; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.LinkedBlockingDeque; /** 参考自: 原文链接:https://blog.csdn.net/TinsanMr/article/details/51049179 */ public class AmrMediaCodec extends Thread { private static final String TAG = "AmrMediaCodec"; private ByteBuffer[] inputBuffers; private ByteBuffer[] outputBuffers; private MediaCodec.BufferInfo info; private final long TIME_US = 1000 * 1000; private int streamType, mode; private int channel = AudioFormat.CHANNEL_OUT_MONO; private int mSampleRate = 16 * 1000; private int bit = AudioFormat.ENCODING_PCM_16BIT; private String type = MediaFormat.MIMETYPE_AUDIO_AMR_WB;//"audio/amr-wb"; private MediaCodec mDecoder; private final LinkedBlockingQueue<byte[]> queue = new LinkedBlockingQueue<>(); private AudioTrack audioTrack; private boolean running = true; public AmrMediaCodec(int streamType, int sampleRate, int channel, int format, int mode){ this.streamType = streamType; this.mSampleRate = sampleRate; this.channel = channel; this.bit = format; this.mode = mode; } @SuppressLint("NewApi") public AmrMediaCodec(){ //https://www.jianshu.com/p/f5a1c9318524 MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);//REGULAR_CODECS参考api说明 MediaCodecInfo[] codecs = list.getCodecInfos(); d("Decoders: "); for (MediaCodecInfo codec : codecs) { if (codec.isEncoder()) continue; d(codec.getName()); } d("Encoders: "); for (MediaCodecInfo codec : codecs) { if (codec.isEncoder()) d(codec.getName()); } streamType = AudioManager.STREAM_MUSIC; mSampleRate = 16000; channel = AudioFormat.CHANNEL_OUT_MONO; bit = AudioFormat.ENCODING_PCM_16BIT; mode = AudioTrack.MODE_STREAM; } /** * 初始化解码器 */ private void initMediaCodec() { d("initMediaDecode"); try { mDecoder = MediaCodec.createDecoderByType(type); MediaFormat encodeFormat = MediaFormat.createAudioFormat(type, mSampleRate, channel); //设置比特率,AMR一共有8中比特率 //public static final int MR795 = 7950; /* 7.95 kbps */ //encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 16000); //设置nputBuffer的大小 //encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024); //Logger.d(TAG, encodeFormat.toString()); mDecoder.configure(encodeFormat, null, null, 0); } catch (IOException e) { e.printStackTrace(); } mDecoder.start();//启动MediaCodec ,等待传入数据 inputBuffers = mDecoder.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据 outputBuffers = mDecoder.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据 info = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息 d("buffers:" + inputBuffers.length); int minBufferSize = AudioTrack.getMinBufferSize(mSampleRate, channel, bit); audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, mSampleRate, channel, bit, minBufferSize, mode); audioTrack.setVolume(1f); audioTrack.play(); } long startMs; @Override public void run() { initMediaCodec(); startMs = System.currentTimeMillis(); decode(); release(); } //喂AMR数据, 加入队列. @SuppressLint("SdCardPath") public void feed(byte[] frame){ //frame的长度应该是61 byte queue.add(frame); } //读取队列中的AMR数据, 并塞入队列进行解码. private void queueCodec(){ byte[] frame = queue.poll(); if(frame != null) { d("decode frame.size=" + frame.length); //for (int i = 0; i < inputBuffers.length - 1; i++) { int inputIndex = mDecoder.dequeueInputBuffer(TIME_US);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 if (inputIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到inputBuffer inputBuffer.clear();//清空之前传入inputBuffer内的数据 inputBuffer.put(frame); mDecoder.queueInputBuffer(inputIndex, 0, frame.length, 0, 0);//通知MediaDecode解码刚刚传入的数据 } } } private void dequeueCodec(){ //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒 //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待 /*int outputIndex = mDecoder.dequeueOutputBuffer(info, TIME_US); if (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 ByteBuffer outputBuffer = outputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer byte[] chunkPCM = new byte[info.size];//BufferInfo内定义了此数据块的大小 outputBuffer.get(chunkPCM);//将Buffer内的数据取出到字节数组中 outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 //putPCMData(chunkPCM);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码 mDecoder.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 //outputIndex = mediaDecode.dequeueOutputBuffer(bufferInfo, 10000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 }*/ int outIndex = mDecoder.dequeueOutputBuffer(info, TIME_US); if(outIndex >= 0){ d("dequeue outIndex=" + outIndex); ByteBuffer buffer = outputBuffers[outIndex]; //if (DEBUG_AUDIO) // Log.v(TAG, "We can't use this buffer but render it due to the API limit, " + buffer); final byte[] chunk = new byte[info.size]; buffer.get(chunk); //clear buffer,otherwise get the same buffer which is the last buffer buffer.clear(); // We use a very simple clock to keep the video FPS, or the // audio playback will be too fast while (info.presentationTimeUs / 1000 > System.currentTimeMillis() - startMs) { try { sleep(10); } catch (InterruptedException e) { e.printStackTrace(); break; } } d("send data to AudioTrack " + info.size); // AudioTrack write data audioTrack.write(chunk, info.offset, info.offset + info.size); mDecoder.releaseOutputBuffer(outIndex, false); } // All decoded frames have been rendered, we can stop playing now if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { w("OutputBuffer BUFFER_FLAG_END_OF_STREAM"); //break; } } private void decode() { while (running) { queueCodec(); dequeueCodec(); } } /** * 释放资源 */ public void release() { Logger.d(TAG, "release"); if(!running)return; interrupt(); running = false; if (mDecoder != null) { mDecoder.stop(); mDecoder.release(); mDecoder = null; } if(audioTrack != null){ audioTrack.stop(); audioTrack.release(); audioTrack = null; } } }
方案2:libstagefright_amrwbdec
苦苦搜索无果, 最后在系统源码中, 找到已包含了相关的解码功能:
frameworks/av/media/libstagefright/codecs/amrwb/Android.mk
LOCAL_MODULE := libstagefright_amrwbdec
自带测试程序
include $(CLEAR_VARS) LOCAL_SRC_FILES := \ test/amrwbdec_test.cpp LOCAL_C_INCLUDES := \ $(LOCAL_PATH)/src \ $(LOCAL_PATH)/include \ $(call include-path-for, audio-utils) LOCAL_STATIC_LIBRARIES := \ libstagefright_amrwbdec libsndfile LOCAL_SHARED_LIBRARIES := \ libaudioutils LOCAL_CLANG := true LOCAL_SANITIZE := signed-integer-overflow LOCAL_MODULE := libstagefright_amrwbdec_test LOCAL_MODULE_TAGS := tests include $(BUILD_EXECUTABLE)
frameworks/av/media/libstagefright/codecs/amrwb/test/amrwbdec_test.cpp
/* * Copyright (C) 2014 The Android Open Source Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <assert.h> #include <stdlib.h> #include "pvamrwbdecoder.h" #include <audio_utils/sndfile.h> // Constants for AMR-WB. enum { kInputBufferSize = 64, kSamplesPerFrame = 320, kBitsPerSample = 16, kOutputBufferSize = kSamplesPerFrame * kBitsPerSample/8, kSampleRate = 16000, kChannels = 1, kFileHeaderSize = 9, kMaxSourceDataUnitSize = 477 * sizeof(int16_t) }; const uint32_t kFrameSizes[] = { 17, 23, 32, 36, 40, 46, 50, 58, 60 }; int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr, "Usage %s <input file> <output file>\n", argv[0]); return EXIT_FAILURE; } // Open the input file. FILE* fpInput = fopen(argv[1], "rb"); if (fpInput == NULL) { fprintf(stderr, "Could not open %s\n", argv[1]); return EXIT_FAILURE; } // Validate the input AMR file. char header[kFileHeaderSize]; int bytesRead = fread(header, 1, kFileHeaderSize, fpInput); if ((bytesRead != kFileHeaderSize) || (memcmp(header, "#!AMR-WB\n", kFileHeaderSize) != 0)) { fprintf(stderr, "Invalid AMR-WB file\n"); fclose(fpInput); return EXIT_FAILURE; } // Open the output file. SF_INFO sfInfo; memset(&sfInfo, 0, sizeof(SF_INFO)); sfInfo.channels = kChannels; sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; sfInfo.samplerate = kSampleRate; SNDFILE *handle = sf_open(argv[2], SFM_WRITE, &sfInfo); if (handle == NULL) { fprintf(stderr, "Could not create %s\n", argv[2]); fclose(fpInput); return EXIT_FAILURE; } // Allocate the decoder memory. uint32_t memRequirements = pvDecoder_AmrWbMemRequirements(); void *decoderBuf = malloc(memRequirements); assert(decoderBuf != NULL); // Create AMR-WB decoder instance. void *amrHandle; int16_t *decoderCookie; pvDecoder_AmrWb_Init(&amrHandle, decoderBuf, &decoderCookie); // Allocate input buffer. uint8_t *inputBuf = (uint8_t*) malloc(kInputBufferSize); assert(inputBuf != NULL); // Allocate input sample buffer. int16_t *inputSampleBuf = (int16_t*) malloc(kMaxSourceDataUnitSize); assert(inputSampleBuf != NULL); // Allocate output buffer. int16_t *outputBuf = (int16_t*) malloc(kOutputBufferSize); assert(outputBuf != NULL); // Decode loop. int retVal = EXIT_SUCCESS; while (1) { // Read mode. uint8_t modeByte; bytesRead = fread(&modeByte, 1, 1, fpInput); if (bytesRead != 1) break; int16 mode = ((modeByte >> 3) & 0x0f); // AMR-WB file format cannot have mode 10, 11, 12 and 13. if (mode >= 10 && mode <= 13) { fprintf(stderr, "Encountered illegal frame type %d\n", mode); retVal = EXIT_FAILURE; break; } if (mode >= 9) { // Produce silence for comfort noise, speech lost and no data. memset(outputBuf, 0, kOutputBufferSize); } else /* if (mode < 9) */ { // Read rest of the frame. int32_t frameSize = kFrameSizes[mode]; bytesRead = fread(inputBuf, 1, frameSize, fpInput); if (bytesRead != frameSize) break; int16 frameType, frameMode; RX_State_wb rx_state; frameMode = mode; mime_unsorting( (uint8_t *)inputBuf, inputSampleBuf, &frameType, &frameMode, 1, &rx_state); int16_t numSamplesOutput; pvDecoder_AmrWb( frameMode, inputSampleBuf, outputBuf, &numSamplesOutput, decoderBuf, frameType, decoderCookie); if (numSamplesOutput != kSamplesPerFrame) { fprintf(stderr, "Decoder encountered error\n"); retVal = EXIT_FAILURE; break; } for (int i = 0; i < kSamplesPerFrame; ++i) { outputBuf[i] &= 0xfffC; } } // Write output to wav. sf_writef_short(handle, outputBuf, kSamplesPerFrame / kChannels); } // Close input and output file. fclose(fpInput); sf_close(handle); // Free allocated memory. free(inputBuf); free(inputSampleBuf); free(outputBuf); return retVal; }
"Usage %s <input file> <output file>\n"
使用: libstagefright_amrwbdec_test audio.amr audio.pcm
接下来只需要抽出代码封装JNI给上层调用即可.
package com.ansondroider.acore.media; public class AmrwbDecoder{ static { System.loadLibrary("amrwb_decoder"); } public AmrwbDecoder(){ initDecoder(); } private native int initDecoder(); //decode 与 amrToPcm 实际是一样的, 只是返回和调用的方式不同. public native int decode(byte[] amr, short[] pcmOut320); public native short[] amrToPcm(byte[] amr); public native void release(); }
源码涉及商业信息, 暂不附上, SO库请自行下载
libamrwb_decoder
编码篇
在有过解码的痛苦经历后, 编码的过程相对简单了许多, 可以考虑使用前面发的github上的项目, 也可以自行从源码封装JNI, 也可以使用MediaCodec.
MediaCodec 编译AMR-WB
然而出师未捷, 突然来了这么一段崩溃信息:
2021-04-07 15:45:36.689 3815-3850/com.ansondroider.amrencoder E/ACodec: [OMX.google.amrwb.encoder] configureCodec returning error -38 2021-04-07 15:45:36.689 3815-3850/com.ansondroider.amrencoder E/ACodec: signalError(omxError 0x80001001, internalError -2147483648) 2021-04-07 15:45:36.689 3815-3850/com.ansondroider.amrencoder E/MediaCodec: Codec reported err 0x80001001, actionCode 0, while in state 3 2021-04-07 15:45:36.692 3815-3849/com.ansondroider.amrencoder E/MediaCodec: configure failed with err 0x80001001, resetting... 2021-04-07 15:45:36.699 3815-3850/com.ansondroider.amrencoder I/OMXClient: Treble IOmx obtained 2021-04-07 15:45:36.704 3815-3849/com.ansondroider.amrencoder E/AndroidRuntime: FATAL EXCEPTION: Thread-2 Process: com.ansondroider.amrencoder, PID: 3815 android.media.MediaCodec$CodecException: Error 0x80001001 at android.media.MediaCodec.native_configure(Native Method) at android.media.MediaCodec.configure(MediaCodec.java:1943) at android.media.MediaCodec.configure(MediaCodec.java:1872) at com.ansondroider.amrencoder.utils.AmrEncoder.initCodec(AmrEncoder.java:50) at com.ansondroider.amrencoder.utils.AmrEncoder.run(AmrEncoder.java:65)
void initCodec() throws IOException { codec = MediaCodec.createEncoderByType(type); MediaFormat format = MediaFormat.createAudioFormat(type, 8000, 1); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); //... }
查询的结果大同小异, 加上下面代码后并没有解决问题
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
configuring a MediaCodec
Illegal State Exception when calling MediaCodec.configure()
Mediacodec jelly-bean
同时, 如何设置AMR-WB格式为23.85 kbit/s仍然是团谜雾, 刚好在查询的结果中看到:
mfAACEncoder.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);//AAC-HE 64kbps
加上并解决:
format.setInteger(MediaFormat.KEY_BIT_RATE, 23850);
从编码结果可以看出, 设置KEY_BIT_RATE, 与前面的AMR-WB格式说明完全一致:
尝试把编码后的数据保存到文件, 并为文件头加上 “#!AMR-WB\n”; 放到VLC可以正常播放.
最后附上 AmrEncoder 源码.
import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaRecorder; import android.os.Environment; import com.ansondroider.acore.Logger; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; public class AmrEncoder extends Thread{ final String TAG = "AmrEncoder"; final boolean saveToFile = false; int audioSource = MediaRecorder.AudioSource.MIC; int sampleRateInHz = 44100; int channelConfig = AudioFormat.CHANNEL_IN_MONO; int audioFormat = AudioFormat.ENCODING_PCM_16BIT; String type = MediaFormat.MIMETYPE_AUDIO_AMR_WB; boolean running = true; MediaCodec codec; AudioRecord recorder; private ByteBuffer[] decodeInputBuffers; private ByteBuffer[] decodeOutputBuffers; private MediaCodec.BufferInfo decodeBufferInfo; FileOutputStream writeToFile = null; public AmrEncoder(OnAmrDecoding cb) { int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat); recorder = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); amrCallback = cb; } void initCodec() throws IOException { codec = MediaCodec.createEncoderByType(type); MediaFormat format = MediaFormat.createAudioFormat(type, 8000, 1); //MediaFormat format = new MediaFormat(); format.setString(MediaFormat.KEY_MIME, type); //format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); //format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRateInHz); //format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); //android.media.MediaCodec$CodecException: Error 0x80001001 format.setInteger(MediaFormat.KEY_BIT_RATE, 23850); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); decodeInputBuffers = codec.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据 decodeOutputBuffers = codec.getOutputBuffers();//MediaCodec将解码后的数据放到此ByteBuffer[]中 我们可以直接在这里面得到PCM数据 decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息 recorder.startRecording(); if(saveToFile) { writeToFile = new FileOutputStream(Environment.getExternalStorageDirectory() + "/amrencode.amr"); writeToFile.write("#!AMR-WB\n".getBytes()); writeToFile.flush(); } } @Override public void run() { Logger.d(TAG, "run.start"); try { initCodec(); encode(); release(); } catch (IOException e) { e.printStackTrace(); } Logger.d(TAG, "run.end"); } void release() throws IOException { Logger.d(TAG, "release"); if (codec != null) { codec.stop(); codec.release(); codec = null; } if(saveToFile && writeToFile != null){ writeToFile.close(); } } void encode() throws IOException { while(running){ //input buffer byte[] buffer = new byte[640]; int srcLength = recorder.read(buffer, 0, buffer.length); // reads 640 bytes int inputIndex = codec.dequeueInputBuffer(10000);//获取可用的inputBuffer -1代表一直等待,0表示不等待 建议-1,避免丢帧 if (inputIndex >= 0) { //拿到inputBuffer ByteBuffer inputBuffer = decodeInputBuffers[inputIndex]; //清空之前传入inputBuffer内的数据 inputBuffer.clear(); inputBuffer.put(buffer); //通知MediaDecode解码刚刚传入的数据 codec.queueInputBuffer(inputIndex, 0, srcLength, 0, 0); } //output buffer. int outputIndex = codec.dequeueOutputBuffer(decodeBufferInfo, 10000); while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 ByteBuffer outputBuffer = decodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer byte[] amr = new byte[decodeBufferInfo.size];//BufferInfo内定义了此数据块的大小 outputBuffer.get(amr);//将Buffer内的数据取出到字节数组中 outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 if(amrCallback != null){ amrCallback.onFrame(amr); if(saveToFile) { writeToFile.write(amr); writeToFile.flush(); } } if(App.D_VOICE)Logger.d(TAG, "got amr frame " + amr.length); //此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 codec.releaseOutputBuffer(outputIndex, false); //再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 outputIndex = codec.dequeueOutputBuffer(decodeBufferInfo, 10000); } } } @Override public void interrupt() { super.interrupt(); running = false; } OnAmrDecoding amrCallback; public interface OnAmrDecoding{ void onFrame(byte[] amrFrame); } }
结语
对多媒体知识了解粗浅, 若有不当之处, 请不吝指正, 感谢!