peliken
peliken

Reputation: 305

MediaCodec hasn't any available input buffer

I am using MediaCodec API for encoding video and audio into mp4 file. Data encoded in separate threads. Sometimes on some devices audio encoder stops to return any available input buffer and as result MediaMuxer crashes when trying to stop it. Here is my code:

configuring media codec:

public static final String MIME_TYPE_AUDIO = "audio/mp4a-latm";
public static final int SAMPLE_RATE = 44100;
public static final int CHANNEL_COUNT = 1;
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
public static final int BIT_RATE_AUDIO = 128000;
public static final int SAMPLES_PER_FRAME = 1024 * 2;
public static final int FRAMES_PER_BUFFER = 24;
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
public static final int MAX_INPUT_SIZE = 16384 * 4;
public static final int MAX_SAMPLE_SIZE = 256 * 1024;

private AudioRecord audioRecord;
private ByteBuffer[] inputBuffers;
private ByteBuffer inputBuffer;
private MediaExtractor mediaExtractor;

private boolean audioSended = false;
private boolean completed = false;
private int sampleCount;
private int iBufferSize;

public AudioEncoderCore(MovieMuxer muxer) throws IOException {
    this.muxer = muxer;
    bufferInfo = new MediaCodec.BufferInfo();

    MediaFormat mediaFormat = null;

        mediaFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, CHANNEL_COUNT);
        mediaFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, MAX_INPUT_SIZE);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE_AUDIO);

        encoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
        encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        encoder.start();
        iBufferSize = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;

        // Ensure buffer is adequately sized for the AudioRecord
        // object to initialize
        int iMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
        if (iBufferSize < iMinBufferSize)
            iBufferSize = ((iMinBufferSize / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;

        audioRecord = new AudioRecord(AUDIO_SOURCE, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, iBufferSize);
        audioRecord.startRecording();

}

sending data to encoder:

public void sendDataFromMic(boolean endOfStream) {
    if (endOfStream)
        Log.d(TAG, "sendDataFromMic end of stream");

    long audioPresentationTimeNs;

    byte[] mTempBuffer = new byte[SAMPLES_PER_FRAME];
    audioPresentationTimeNs = System.nanoTime();

    int iReadResult = audioRecord.read(mTempBuffer, 0, mTempBuffer.length);

    if (iReadResult == AudioRecord.ERROR_BAD_VALUE || iReadResult == AudioRecord.ERROR_INVALID_OPERATION || iReadResult == 0) {
        Log.e(TAG, "audio buffer read error");
    } else {
        // send current frame data to encoder
        try {
            if (inputBuffers == null)
                inputBuffers = encoder.getInputBuffers();

            //Sometimes can't get any available input buffer
            int inputBufferIndex = encoder.dequeueInputBuffer(100000);
            Log.d(TAG, "inputBufferIndex = " + inputBufferIndex);
            if (inputBufferIndex >= 0) {
                inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                inputBuffer.put(mTempBuffer, 0, iReadResult);

                Log.d(TAG, "sending frame to audio encoder " + iReadResult + " bytes");
                encoder.queueInputBuffer(inputBufferIndex, 0, iReadResult, audioPresentationTimeNs / 1000,
                        endOfStream ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
            }


        } catch (Throwable t) {
            Log.e(TAG, "sendFrameToAudioEncoder exception");
            t.printStackTrace();
        }
    }
}

drain encoder:

    public void drainEncoder() {
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
    while (true) {
        int encoderStatus = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_NSECS);
        if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
            Log.d(TAG, "no output available, spinning to await EOS");
            break;
        } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
            encoderOutputBuffers = encoder.getOutputBuffers();
        else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat newFormat = encoder.getOutputFormat();
            Log.d(TAG, "encoder format changed: " + newFormat);
            trackIndex = muxer.addTrack(newFormat);
        } else if (muxer.isMuxerStarted()) {
            ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
            if (encodedData == null)
                throw new RuntimeException("encoded buffer " + encoderStatus + " was null");

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
                bufferInfo.size = 0;
            }

            if (bufferInfo.size != 0) {
                encodedData.position(bufferInfo.offset);
                encodedData.limit(bufferInfo.offset + bufferInfo.size);

                muxer.writeSampleData(trackIndex, encodedData, bufferInfo);

                Log.d(TAG, "sent " + bufferInfo.size + " bytes to muxer, ts=" +
                        bufferInfo.presentationTimeUs + " track index=" + trackIndex);
            }

            encoder.releaseOutputBuffer(encoderStatus, false);

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d(TAG, "end of stream reached");
                completed = true;
                break;      // out of while
            }
        }
    }
}

Bug is stable reproduced on HTC One, Galaxy S3, but all works fine on Huawei Honor 3C.

Upvotes: 2

Views: 2453

Answers (1)

peliken
peliken

Reputation: 305

After source code investigations I found solution. When I dequeue output buffer, the muxer may be not started yet, in that case buffer not released. So here's working code for drain encoder:

    public void drainEncoder() {
    ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
    while (true) {
        int encoderStatus = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_NSECS);
        if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
            Log.d(TAG, "no output available, spinning to await EOS");
            break;
        } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
            encoderOutputBuffers = encoder.getOutputBuffers();
        else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            MediaFormat newFormat = encoder.getOutputFormat();
            Log.d(TAG, "encoder format changed: " + newFormat);
            trackIndex = muxer.addTrack(newFormat);
        } else if (muxer.isMuxerStarted()) {
            ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
            if (encodedData == null)
                throw new RuntimeException("encoded buffer " + encoderStatus + " was null");

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG");
                bufferInfo.size = 0;
            }

            if (bufferInfo.size != 0) {
                encodedData.position(bufferInfo.offset);
                encodedData.limit(bufferInfo.offset + bufferInfo.size);

                muxer.writeSampleData(trackIndex, encodedData, bufferInfo);
                Log.d(TAG, "sent " + bufferInfo.size + " bytes to muxer, ts=" +
                        bufferInfo.presentationTimeUs + " track index=" + trackIndex);
            }

            encoder.releaseOutputBuffer(encoderStatus, false);

            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                Log.d(TAG, "end of stream reached");
                completed = true;
                break;      // out of while
            }
        }
        else{
            //Muxer not ready, release buffer
            encoder.releaseOutputBuffer(encoderStatus, false);
            Log.d(TAG, "muxer not ready, skip data");
        }
    }
}

Upvotes: 2

Related Questions