android amr-wb 编解码

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: android amr-wb 编解码

平台


 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字节/帧)

image.png

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格式说明完全一致:

image.png

尝试把编码后的数据保存到文件, 并为文件头加上 “#!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);
    }
}


结语


 对多媒体知识了解粗浅, 若有不当之处, 请不吝指正, 感谢!


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8月前
|
编解码 Android开发
Android native层实现MediaCodec编码H264/HEVC
Android平台在上层实现mediacodec的编码,资料泛滥,已经不再是难事,今天给大家介绍下,如何在Android native层实现MediaCodec编码H264/HEVC,网上千篇一律的接口说明,这里不再赘述,本文主要介绍下,一些需要注意的点,权当抛砖引玉,相关设计界面如下:
143 0
|
19天前
|
安全 Java Shell
Android11以上 Audio音频调试方法
Android11以上 Audio音频调试方法
58 0
|
编解码 vr&ar Android开发
Android FFmpeg 转换MP3格式
Android FFmpeg 转换MP3格式
205 0
|
Android开发 内存技术
Android录制和播放PCM数据
PCM是android系统中的原生音频数据,那么我们如何录制和播放这个格式的byte[]数据呢?
331 0
|
编解码 算法 数据格式
iOS音视频开发 - 音频编码格式(pcm、wav、mp3、aac、ogg)
我们通常从音乐App(如:网易云音乐)听歌时,会看到一首歌需要的存储空间大概是10M左右,对于手机磁盘来说这是可以接受的。但在网络中实时在线传播的话,这个数据量可能就太大了,所以必须对其进行压缩编码。
|
数据采集 Java Android开发
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(二)
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(二)
209 0
|
数据采集 传感器 编解码
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
212 0
【Android RTMP】音频数据采集编码 ( FAAC 头文件与静态库拷贝到 AS | CMakeList.txt 配置 FAAC | AudioRecord 音频采样 PCM 格式 )(一)
|
编解码 算法 Java
基于ffmpeg 编解码 GIF 【PC】【Android】
基于ffmpeg 编解码 GIF 【PC】【Android】
149 0