Android MediaCodec AAC encoder
Asked Answered
I

2

24

I use the MediaCodec class provided by the Android SDK since API level 16 with the OMX.SEC.aac.enc encoder to encode audio to a file. I get the audio input from the AudioRecord class. My instance of the AudioRecord class is configured like this:

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);

I am able to play the raw data from the AudioRecord instance, so the problem does not reside there.

I write the output from the AudioRecord instance to a ByteBuffer instance and pass it to an available input buffer from the encoder. The output from the encoder is written to a file on the SD-card.

These are the configuration parameters for my MediaCodec instance:

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 tells me that there are no streams in my aac file. The command FFMPEG -i @filename@ gives me the following error: Invalid data found when processing input. None of the mediaplayers I tested are able to play my file.

Why am I unable to play my file? I receive no OpenMAX errors in LogCat and the application does not crash when encoding. I wrote a video encoder that works on the same principle and it works.

This is the code to read the data from the AudioRecord instance to a buffer:

    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();

The function add from my encoder copies the content of one buffer to another:

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");
    }
}

The following code is used to occupy the input buffer of the encoder:

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();

I write the output from the encoder to a local file like this:

    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();
Inflame answered 7/3, 2013 at 15:55 Comment(6)
post the full method where you do it. with start() stop().. thank you.Nympho
Also, take a look at this thread - code.google.com/p/spydroid-ipcamera/issues/detail?id=43 they seem to have a working piece of code for recording AAC there.Nympho
@Sergey Benner, They do not use the MediaCodec class. The MediaRecorder class is used in Spydroid. That class will be my second choice if the MediaCodec class does not work.Inflame
could you ever solve this issue? I am facing a similar problem, while encoding an exisiting wav file. The encoding process finished without errors, the created file has a reasonable file size, but no player is able to play it.Bondstone
I met this problem too, the aac file KEY_BIT_RATE is not 64kps,but is 128kps. Maybe this is the reason that the aac file can not be played. I can't make it right yet.Synaesthesia
I know this is too late, but in case in the future someone else came to this problem, mediacodec return data with no header and file signature, when you save returned data by mediacodec to a file you still need to write file header data and it depends on the file extension you want to use, so as @Mimmo-Grottoli mentioned using Mediamauxer write it for you, but if you have some limitation to use Mediamauxer you have to write header file yourself and it's gonna be very hard depends on chosen media format.Dirndl
C
2

I guess you missed the MediaMuxer class. You need it if you want to write something got from MediaCodec to a file for example.

Crossness answered 23/6, 2015 at 14:34 Comment(1)
OP was asking for API 16, MediaMuxer was introduced with API 18Bondstone
K
0

I had to solve the is use case as well. It is possible, but there are some tricky things to get this working. I've upload my proof of concept app to GitHub. It has a working example of how to do this.

https://github.com/dburckh/EncodeRawAudio

Konopka answered 11/9, 2022 at 22:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.