How to transfer data from MediaCodec to AudioTrack using Xamarin for Android

I am trying to decode an mp3 file and transfer it to AudioTrack. Everything works fine, but causes a lot of GC on the Java side. I should not allocate memory in my play / stream loop and blame ByteBuffer.Get (byte [], int, int) binding to allocate Java temp array. Can anyone confirm and / or show the best way to feed data from MediaCodec to AudioTrack? (I know API 21 introduced AudioTrack.write (ByteBuffer, ...)) Thanks

That's what I'm doing:

byte[] audioBuffer = new byte[...];

...

ByteBuffer codecOutputBuffer = codecOutputBuffers[outputIndex];

// The next line seems to be the source of a lot of GC during playback
codecOutputBuffer.Get(audioBuffer, 0, bufInfo.Size);

audioTrack.Write(audioBuffer, 0, bufInfo.Size);

UPDATE 1: I tried using the Allocation Tracker to validate the distribution site. I found out that the selected objects are arrays of large bytes of size 8 kbytes. Unfortunatelly Allocation Tracker does not show stacktrace placement for them:

1   32      org.apache.harmony.dalvik.ddmc.Chunk    6   org.apache.harmony.dalvik.ddmc.DdmServer    dispatch    
2   16      java.lang.Integer                       6   java.lang.Integer   valueOf 
3   16      byte[]                                  6           
4   8192    byte[]                                  20          
5   8192    byte[]                                  20          
6   8192    byte[]                                  20          

, ByteBuffer.Get(byte [], int, int), ,

  • audioTrack.Write(...) -

  • codecOutputBuffer.Get(audioBuffer, 0, bufInfo.Size) -

Java, , .

2: Java, - .

/ , ByteBuffer.Get(byte [], int, int) Mono Java . , 8kb , AudioBuffer - 4kb.

3: , - ( mp3-), . java // , play(), pause(), #. , , #. ( , ).

Java:

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;    
import java.nio.ByteBuffer;

public class AudioPlayer {

    public void play(Context aContext, final int resourceId){

        final Context context = aContext;

        new Thread()
        {
            @Override
            public void run() {

                try {
                    AssetFileDescriptor fd = context.getResources().openRawResourceFd(resourceId);

                    MediaExtractor extractor = new MediaExtractor();
                    extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
                    extractor.selectTrack(0);

                    MediaFormat trackFormat = extractor.getTrackFormat(0);

                    MediaCodec decoder = MediaCodec.createDecoderByType(trackFormat.getString(MediaFormat.KEY_MIME));
                    decoder.configure(trackFormat, null, null, 0);

                    decoder.start();
                    ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
                    ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();

                    int inputIndex = decoder.dequeueInputBuffer(-1);
                    ByteBuffer inputBuffer = decoderInputBuffers[inputIndex];
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    byte[] audioBuffer = null;
                    AudioTrack audioTrack = null;

                    int read = extractor.readSampleData(inputBuffer, 0);
                    while (read > 0) {
                        decoder.queueInputBuffer(inputIndex, 0, read, extractor.getSampleTime(), 0);

                        extractor.advance();

                        int outputIndex = decoder.dequeueOutputBuffer(bufferInfo, -1);
                        if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

                            trackFormat = decoder.getOutputFormat();

                        } else if (outputIndex >= 0) {

                            if (bufferInfo.size > 0) {

                                ByteBuffer outputBuffer = decoderOutputBuffers[outputIndex];
                                if (audioBuffer == null || audioBuffer.length < bufferInfo.size) {
                                    audioBuffer = new byte[bufferInfo.size];
                                }

                                outputBuffer.rewind();
                                outputBuffer.get(audioBuffer, 0, bufferInfo.size);
                                decoder.releaseOutputBuffer(outputIndex, false);

                                if (audioTrack == null) {
                                    int sampleRateInHz = trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                                    int channelCount = trackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                                    int channelConfig = channelCount == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO;

                                    audioTrack = new AudioTrack(
                                            AudioManager.STREAM_MUSIC,
                                            sampleRateInHz,
                                            channelConfig,
                                            AudioFormat.ENCODING_PCM_16BIT,
                                            AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT) * 2,
                                            AudioTrack.MODE_STREAM);

                                    audioTrack.play();
                                }

                                audioTrack.write(audioBuffer, 0, bufferInfo.size);
                            }
                        }

                        inputIndex = decoder.dequeueInputBuffer(-1);
                        inputBuffer = decoderInputBuffers[inputIndex];

                        read = extractor.readSampleData(inputBuffer, 0);
                    }
                } catch (Exception e) {

                }
            }
        }.start();    
    }    
}

#

[Activity(Label = "AndroidAudioTest", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView(Resource.Layout.Main);

        var play = FindViewById<Button>(Resource.Id.Play);
        play.Click += (s, e) =>
        {
            new AudioPlayer().Play(this, Resource.Raw.PianoInsideMics);
        };
    }
}
+4

All Articles