Android MediaCodec AAC encoder

12,439

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

Share:
12,439

Related videos on Youtube

fadden
Author by

fadden

I'm a self-employed software engineer. In the past I worked for Google, on the Android platform (2005-2014). I spent five years working on Dalvik (2006-2011), and two on system-level graphics (2012-2014). I've written a few documents for Android developers that appear on the official site, including the original versions of JNI Tips, SMP Primer for Android, and Android System-Level Graphics Architecture. I also wrote Grafika and run bigflake.com.

Updated on September 17, 2020

Comments

  • fadden
    fadden almost 4 years

    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();
    
    • Sergey Benner
      Sergey Benner over 11 years
      post the full method where you do it. with start() stop().. thank you.
    • Sergey Benner
      Sergey Benner over 11 years
      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.
    • Admin
      Admin over 11 years
      @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.
    • muetzenflo
      muetzenflo about 8 years
      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.
    • I'm SuperMan
      I'm SuperMan almost 8 years
      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.
    • Saman
      Saman over 6 years
      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.
  • muetzenflo
    muetzenflo about 8 years
    OP was asking for API 16, MediaMuxer was introduced with API 18