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并使用它)
在着手前, 有几个关键问题需谨慎确认
音频帧数据准确: 建议下载对应的文件格式进行测试, 数据源导致不可判定问题根源, 如每帧数据的准确性
(PS, 刚开始未确认数据源, 一直以56字节/帧去解码, 浪费了大量时间, 实际应该是61字节/帧)
音频数据清晰可分辨: 如果拿到的AMR音频数据底噪或杂音较大, 会影响对解码效果的判断
(PS, 数据源码夹杂了一些杂音, 导致一直以为解码器或解码步骤有问题)
音频参数正确
解码器
方案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
苦苦搜索无果, 最后在系统源码中, 找到已包含了相关的解码功能:
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)
/*
* 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
#include
#include
#include
#include
#include "pvamrwbdecoder.h"
#include
// 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 , 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
使用: 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格式说明完全一致:
KEY_BIT RATE | 帧大小 (byte) |
---|---|
23850 | 61 |
18250 | 47 |
尝试把编码后的数据保存到文件, 并为文件头加上 “#!AMR-WB\n”; 放到VLC可以正常播放.
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);
}
}
对多媒体知识了解粗浅, 若有不当之处, 请不吝指正, 感谢!