在Android上使用AudioRecord和MediaCodec对AAC音频进行编码

18

我正试图使用Android的AudioRecord和MediaCodec编码AAC音频。我创建了一个编码器类,与(使用Android MediaCodec从相机编码H.264)非常相似。使用此类,我创建了一个AudioRecord实例并告诉它将其byte []数据读取到AudioEncoder(audioEncoder.offerEncoder(Data))。

 while(isRecording) 
 {
  audioRecord.read(Data, 0, Data.length);
  audioEncoder.offerEncoder(Data);
 }

这是我AudioRecord的设置:

    int audioSource = MediaRecorder.AudioSource.MIC;
    int sampleRateInHz = 44100;
    int channelConfig = AudioFormat.CHANNEL_IN_MONO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

我已经成功收集了一些byte[]数组数据并将其写入本地文件。不幸的是,该文件无法播放。我在网上进行了更多搜索,并找到了一篇相关的帖子(如何使用Android MediaCodec生成AAC ADTS基本流)。所以,其他遇到类似问题的人都表示主要问题是“MediaCodec编码器生成原始AAC流。原始AAC流需要转换为可播放的格式,例如ADTS流”。因此,我尝试添加ADTS头部。然而,在我添加ADTS头部之后(我在下面的代码中进行了注释),我的AudioEncoder甚至不会写入输出音频文件。我是否漏掉了什么?我的设置是正确的吗?
欢迎任何建议、评论和意见,非常感谢!谢谢大家!
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class AudioEncoder {

    private MediaCodec mediaCodec;
    private BufferedOutputStream outputStream;
    private String mediaType = "audio/mp4a-latm";

    public AudioEncoder() {
        File f = new File(Environment.getExternalStorageDirectory(), "Download/audio_encoded.aac");
        touch(f);
        try {
            outputStream = new BufferedOutputStream(new FileOutputStream(f));
            Log.e("AudioEncoder", "outputStream initialized");
        } catch (Exception e){
            e.printStackTrace();
        }

        mediaCodec = MediaCodec.createEncoderByType(mediaType);
        final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
        final int kBitRates[] = { 64000, 128000 };
        MediaFormat mediaFormat  = MediaFormat.createAudioFormat(mediaType,kSampleRates[3],1);
        mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);

        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[1]);
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        mediaCodec.start();
    }

    public void close() {
        try {
            mediaCodec.stop();
            mediaCodec.release();
            outputStream.flush();
            outputStream.close();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    // called AudioRecord's read
    public synchronized void offerEncoder(byte[] input) {
        Log.e("AudioEncoder", input.length + " is coming");

        try {
            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();

                inputBuffer.put(input);


                mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo,0);

////trying to add a ADTS
//            while (outputBufferIndex >= 0) {
//                int outBitsSize   = bufferInfo.size;
//                int outPacketSize = outBitsSize + 7;    // 7 is ADTS size
//                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
//
//                outputBuffer.position(bufferInfo.offset);
//                outputBuffer.limit(bufferInfo.offset + outBitsSize);
//
//                byte[] outData = new byte[outPacketSize];
//                addADTStoPacket(outData, outPacketSize);
//
//                outputBuffer.get(outData, 7, outBitsSize);
//                outputBuffer.position(bufferInfo.offset);
//
////                byte[] outData = new byte[bufferInfo.size];
//                outputStream.write(outData, 0, outData.length);
//                Log.e("AudioEncoder", outData.length + " bytes written");
//
//                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
//                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
//
//            }


//Without ADTS header
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);
                outputStream.write(outData, 0, outData.length);
                Log.e("AudioEncoder", outData.length + " bytes written");

                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

            }
        } catch (Throwable t) {
            t.printStackTrace();
        }

    }

    /**
     *  Add ADTS header at the beginning of each and every AAC packet.
     *  This is needed as MediaCodec encoder generates a packet of raw
     *  AAC data.
     *
     *  Note the packetLen must count in the ADTS header itself.
     **/
    private void addADTStoPacket(byte[] packet, int packetLen) {
        int profile = 2;  //AAC LC
        //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
        int freqIdx = 4;  //44.1KHz
        int chanCfg = 2;  //CPE

        // fill in ADTS data
        packet[0] = (byte)0xFF;
        packet[1] = (byte)0xF9;
        packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
        packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
        packet[4] = (byte)((packetLen&0x7FF) >> 3);
        packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
        packet[6] = (byte)0xFC;
    }

    public void touch(File f)
    {
        try {
            if(!f.exists())
                f.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

最终,我能够使用ADTS技术播放音频。不幸的是,完全没有声音... - xiaowoo
你解决了这个问题吗?显然,除非API >= 18,否则没有编码音频的工作示例可以使用MediaCodec。 - Steve M
2个回答

8
你可以使用Android的MediaMuxer将由MediaCodec创建的原始流打包成.mp4文件。奖励:.mp4中包含的AAC数据包不需要ADTS头。
我在Github上有一个此技术的工作示例

2
感谢您的回复,但是我正在针对API 16(Android 4.1)进行开发。MediaMuxer仅在Android v 4.3或API 18上可用。 - xiaowoo
1
我在配置音频编码器时遇到了问题。使用上面解决方案中提供的完全相同的音频MediaEncoder参数,我收到错误android.media.MediaCodec$CodecException: Error 0x80001001。我是否忽略了一些明显的东西? - JCutting8
然而我需要一个 .aac 扩展名的文件。我该怎么做呢?谢谢! - ch271828n
@ch271828n 如果你需要原始的.aac流,你可能想要使用原始的MediaCodec输出,跳过MediaMuxer(它将流打包成像.mp4这样的容器格式)。 - dbro

5

请查看这里的“testEncoder”方法,了解如何正确使用MediaCodec作为编码器。

在您的代码中,您的输入(音频录制器)配置为单声道,而您的输出(ADTS数据包头)设置为两个声道(chanCfg = 2)。

此外,如果您更改输入采样率(目前为44.1khz),还必须更改ADTS数据包头中的freqIdx标志。请查看此链接以获取有效值。

ADTS数据包头配置文件标志设置为“AAC LC”,您还可以在MediaCodecInfo.CodecProfileLevel下找到它。您已将配置文件设置为2,即MediaCodecInfo.CodecProfileLevel.AACObjectLC。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接