Search code examples
androidandroid-mediacodecaacaudiotrackmediaextractor

Decoding an AAC audio file into a RAW PCM file


I have an AAC-format audio file that im trying to convert into a raw format PCM file, in order to mix it with another audio file and play it using AudioTrack later on.

After some research I came across this library that decodes my AAC file appropriately. However, it only passes the decoded bytes directly to the AudioTrack. When trying to write the decoded bytes into an output stream instead, the resulting file only contains noise.

this is the code i use to decode the AAC file -

public void AACDecoderAndPlay() {
    ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
    ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();

    BufferInfo info = new BufferInfo();

    // create an audiotrack object
    AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, JamboxAudioTrack.FREQUENCY,
            JamboxAudioTrack.CHANNEL_CONFIGURATION, JamboxAudioTrack.AUDIO_ENCODING,
            JamboxAudioTrack.BUFFER_SIZE, AudioTrack.MODE_STREAM);

    audioTrack.play();


    long bytesWritten = 0;
    while (!eosReceived) {
        int inIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
        if (inIndex >= 0) {
            ByteBuffer buffer = inputBuffers[inIndex];
            int sampleSize = mExtractor.readSampleData(buffer, 0);
            if (sampleSize < 0) {
                // We shouldn't stop the playback at this point, just pass the EOS
                // flag to mDecoder, we will get it again from the
                // dequeueOutputBuffer
                Log.d(LOG_TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);

            } else {
                mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
                mExtractor.advance();
            }

            int outIndex = mDecoder.dequeueOutputBuffer(info, TIMEOUT_US);
            switch (outIndex) {
                case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                    Log.d(LOG_TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
                    outputBuffers = mDecoder.getOutputBuffers();
                    break;

                case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                    MediaFormat format = mDecoder.getOutputFormat();
                        Log.d(LOG_TAG, "New format " + format);
//                        audioTrack.setPlaybackRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));

                        break;

                case MediaCodec.INFO_TRY_AGAIN_LATER:
                    Log.d(LOG_TAG, "dequeueOutputBuffer timed out!");
                    break;

                default:
                    ByteBuffer outBuffer = outputBuffers[outIndex];
                    Log.v(LOG_TAG, "We can't use this buffer but render it due to the API limit, " + outBuffer);

                    final byte[] chunk = new byte[info.size];
                    outBuffer.get(chunk); // Read the buffer all at once
                    outBuffer.clear(); // ** MUST DO!!! OTHERWISE THE NEXT TIME YOU GET THIS SAME BUFFER BAD THINGS WILL HAPPEN

                    audioTrack.write(chunk, info.offset, info.offset + info.size); // AudioTrack write data

                    if (info.offset > 0) {
                        Log.v(LOG_TAG, "" + info.offset);
                    }
                    try {
                        mOutputStream.write(chunk, info.offset, info.offset + info.size);
                        bytesWritten += info.offset + info.size;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    mDecoder.releaseOutputBuffer(outIndex, false);
                    break;
            }

            // All decoded frames have been rendered, we can stop playing now
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d(LOG_TAG, "OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                break;
            }
        }
    }
    Log.v(LOG_TAG, "Bytes written: " + bytesWritten);

    mDecoder.stop();
    mDecoder.release();
    mDecoder = null;

    mExtractor.release();
        mExtractor = null;

        audioTrack.stop();
        audioTrack.release();
        audioTrack = null;
    }

To play the decoded file i use a plain AudioTrack that reads and plays from a buffer -

public void start() {
    new Thread(new Runnable() {
        public void run() {
            try {
                Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
                InputStream inputStream = new FileInputStream(playingFile);
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

                AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC, FREQUENCY,
                        CHANNEL_CONFIGURATION, AUDIO_ENCODING, BUFFER_SIZE, AudioTrack.MODE_STREAM);

                short[] buffer = new short[BUFFER_SIZE / 4];
                long startTime = System.currentTimeMillis();

                track.play();

                while (dataInputStream.available() > 0) {
                    int i = 0;
                    while (dataInputStream.available() > 0 && i < buffer.length

) {
                            buffer[i] = dataInputStream.readShort();
                            i++;
                        }
                        track.write(buffer, 0, buffer.length);
                        if (latency < 0) {
                            latency = System.currentTimeMillis() - startTime;
                        }
                    }
//
//            int i = 0;
//            while((i = dataInputStream.read(buffer, 0, BUFFER_SIZE)) > -1){
//                track.write(buffer, 0, i);
//            }
                    track.stop();
                    track.release();
                    dataInputStream.close();
                    inputStream.close();
                }
                catch (Exception e)
                {
                    e.printStackTrace();
                }
            }
    }).start();
}

What am I missing?


Solution

  • Your problem seems to be in the fact that you write your output as plain bytes (I don't see the setup of mOutputStream in your code though). These plain bytes will be in the native endianness of your platform (in practice, little endian), but read it as shorts using DataInputStream in a platform-independent way (which is specified to be big endian).

    The simplest way to solve it here would be to use a byte array instead of a short array when playing it back; AudioTrack takes both byte and short arrays, and when given a byte array, it interprets it in the right (native) way, which matches the output from MediaCodec. Just make sure that the buffer size is an even number of bytes.

    If you really need to have the values as shorts, you need to use a reader that reads in little endian mode (all current Android ABIs are little endian). There doesn't seem to be any straightforward available API for this, but in practice it's not too hard. See e.g. the readLittleShort method in Java : DataInputStream replacement for endianness for an example on how to do that.