安卓MediaCodec AAC编码器

24

我使用Android SDK提供的MediaCodec类(自API 16起)与OMX.SEC.aac.enc编码器将音频编码到文件中。音频输入来自AudioRecord类。我的AudioRecord类实例配置如下:

bufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_DEFAULT, bufferSize);

我能够播放来自AudioRecord实例的原始数据,因此问题不在那里。

我将AudioRecord实例的输出写入一个ByteBuffer实例,并将其传递给编码器中可用的输入缓冲区。编码器的输出被写入SD卡上的文件。

这些是我MediaCodec实例的配置参数:

codec = MediaCodec.createEncoderByType("audio/mp4a-latm");
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
format.setInteger(MediaFormat.KEY_BIT_RATE, 64 * 1024);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectHE);
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

VLC告诉我我的aac文件中没有流。命令FFMPEG -i @filename@给出以下错误:处理输入时找到无效数据。我测试过的所有媒体播放器都无法播放我的文件。

为什么我无法播放我的文件?我在LogCat中没有收到任何OpenMAX错误,并且该应用程序在编码时不会崩溃。我编写了一个按照相同原理工作的视频编码器,它可以正常运行。

这是从AudioRecord实例读取数据到缓冲区的代码:

    new Thread() {
        public void run() {
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize);
            int read = 0;
            while (isRecording) {
                read = recorder.read(byteBuffer, bufferSize);
                if(AudioRecord.ERROR_INVALID_OPERATION != read){
                    encoder.add(byteBuffer);
                }
            }
            recorder.stop();
        }
    }.start();

我的编码器中的add函数将一个缓冲区的内容复制到另一个缓冲区:

public void add(ByteBuffer input) {
    if (!isRunning)
        return; 

    if (tmpInputBuffer == null)
        tmpInputBuffer = ByteBuffer.allocate(input.capacity());

    if (!tmpBufferClear)
        Log.e("audio encoder", "deadline missed"); //TODO lower bit rate

    synchronized (tmpInputBuffer) {
        tmpInputBuffer.clear();
        tmpInputBuffer.put(input);
        tmpInputBuffer.notifyAll();
        Log.d("audio encoder", "pushed data into tmpInputBuffer");
    }
}
以下代码用于占用编码器的输入缓冲区:
new Thread() {
    public void run() {
        while (isRunning) {
            if (tmpInputBuffer == null)
                continue;
            synchronized (tmpInputBuffer) {
                if (tmpBufferClear) {
                    try {
                        Log.d("audio encoder", "falling asleep");
                        tmpInputBuffer.wait(); //wait when no input is available
                    } 
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                ByteBuffer[] inputBuffers = codec.getInputBuffers();
                int inputBufferIndex;
                do
                    inputBufferIndex = codec.dequeueInputBuffer(-1);
                while (inputBufferIndex < 0);
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                Log.d("input buffer size", String.valueOf(inputBuffer.capacity()));
                Log.d("tmp input buffer size", String.valueOf(tmpInputBuffer.capacity()));
                inputBuffer.put(tmpInputBuffer.array());
                tmpInputBuffer.clear();
                codec.queueInputBuffer(inputBufferIndex, 0, tmpInputBuffer.capacity(), 0, 0);
                tmpBufferClear = true;
                Log.d("audio encoder", "added to input buffer");
            }
        }
    }
}.start();
我将编码器的输出写入本地文件,方法如下:

    new Thread() {
        public void run() {
            while (isRunning) {
                ByteBuffer[] outputBuffers = codec.getOutputBuffers();
                MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, -1);
                while (outputBufferIndex >= 0) {
                    ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                    byte[] outData = new byte[bufferInfo.size];
                    outputBuffer.get(outData);

                    try {
                        fileWriter.write(outData, 0, outData.length);
                    } 
                    catch (IOException e) {
                        e.printStackTrace();
                    }
                    codec.releaseOutputBuffer(outputBufferIndex, false);
                    outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
                    Log.d("audio encoder", "removed from output buffer");
                }
            }
            codec.stop();

            try {
                fileWriter.close();
            } 
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    }.start();
                tmpBufferClear = true;
                Log.d("audio encoder", "added to input buffer");
            }
        }
    }
}.start();

发布执行代码的完整方法,包括start()和stop()。。谢谢。 - Sergey Benner
1
另外,请查看这个线程 - https://code.google.com/p/spydroid-ipcamera/issues/detail?id=43 他们似乎有一个可以录制AAC的可行代码。 - Sergey Benner
@Sergey Benner,他们不使用MediaCodec类。在Spydroid中使用的是MediaRecorder类。如果MediaCodec类无法工作,那么这个类将成为我的第二选择。 - user2144883
你能解决这个问题吗?我遇到了类似的问题,当我对现有的wav文件进行编码时。编码过程没有出现错误,创建的文件大小合理,但是没有任何播放器能够播放它。 - muetzenflo
我也遇到了这个问题,aac文件的KEY_BIT_RATE不是64kps,而是128kps。也许这就是导致无法播放aac文件的原因。我还没有解决它。 - I'm SuperMan
我知道现在已经晚了,但是以防将来有人遇到这个问题,mediacodec返回的数据没有头和文件签名,当你将mediacodec返回的数据保存到文件中时,你仍然需要写入文件头数据,这取决于你想使用的文件扩展名,所以正如@Mimmo-Grottoli所提到的那样,使用Mediamauxer为您编写它,但如果您有一些限制不能使用Mediamauxer,则必须自己编写头文件,这将取决于所选择的媒体格式,可能会非常困难。 - Saman
2个回答

2

我想你可能错过了MediaMuxer类。如果你想将从MediaCodec获得的内容写入文件,就需要它。


OP正在寻找API 16,MediaMuxer是在API 18中引入的。 - muetzenflo

0

我也必须解决这个使用案例。虽然有一些棘手的事情需要处理,但是这是可能的。我已经将我的概念性应用程序上传到GitHub上。它具有如何实现此操作的工作示例。

https://github.com/dburckh/EncodeRawAudio


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