blob: 79767688a7fbea949d7d882b47a1e9c14856ed09 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.media;
import android.media.MediaCodec;
import android.util.SparseArray;
import org.chromium.base.Log;
import org.chromium.base.annotations.JNINamespace;
import java.nio.ByteBuffer;
/**
* This class extends MediaCodecBridge for encoding processing.
* As to H264, WebRTC requires that each IDR/keyframe should have SPS/PPS at the beginning.
* Unlike other HW/SW H264 codec implementations, MediaCodec will generate a separate config
* frame with SPS/PPS as the first frame and won't include them in the following keyframes.
* So here we append the SPS/PPS NALs at the start of each keyframe.
*/
@JNINamespace("media")
class MediaCodecEncoder extends MediaCodecBridge {
private static final String TAG = "MediaCodecEncoder";
// Output buffers mapping with MediaCodec output buffers for the possible frame-merging.
private SparseArray<ByteBuffer> mOutputBuffers = new SparseArray<>();
// SPS and PPS NALs (Config frame).
private ByteBuffer mConfigData;
protected MediaCodecEncoder(MediaCodec mediaCodec, @BitrateAdjuster.Type int bitrateAdjuster) {
super(mediaCodec, bitrateAdjuster, false);
}
@Override
protected ByteBuffer getOutputBuffer(int index) {
return mOutputBuffers.get(index);
}
@Override
protected void releaseOutputBuffer(int index, boolean render) {
try {
mMediaCodec.releaseOutputBuffer(index, render);
mOutputBuffers.remove(index);
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to release output buffer", e);
}
}
// Save the config frame(SPS/PPS NALs) and append it to each keyframe.
// WrongConstant: MediaCodec#dequeueOutputBuffer returns the index when successful.
@SuppressWarnings({"deprecation", "WrongConstant"})
@Override
protected int dequeueOutputBufferInternal(MediaCodec.BufferInfo info, long timeoutUs) {
int indexOrStatus = -1;
try {
indexOrStatus = mMediaCodec.dequeueOutputBuffer(info, timeoutUs);
ByteBuffer codecOutputBuffer = null;
if (indexOrStatus >= 0) {
boolean isConfigFrame = (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
if (isConfigFrame) {
Log.d(TAG, "Config frame generated. Offset: %d, size: %d", info.offset,
info.size);
codecOutputBuffer = getMediaCodecOutputBuffer(indexOrStatus);
codecOutputBuffer.position(info.offset);
codecOutputBuffer.limit(info.offset + info.size);
mConfigData = ByteBuffer.allocateDirect(info.size);
mConfigData.put(codecOutputBuffer);
// Log few SPS header bytes to check profile and level.
StringBuilder spsData = new StringBuilder();
for (int i = 0; i < (info.size < 8 ? info.size : 8); i++) {
spsData.append(Integer.toHexString(mConfigData.get(i) & 0xff)).append(" ");
}
Log.i(TAG, "spsData: %s", spsData.toString());
// Release buffer back.
mMediaCodec.releaseOutputBuffer(indexOrStatus, false);
// Query next output.
indexOrStatus = mMediaCodec.dequeueOutputBuffer(info, timeoutUs);
}
}
if (indexOrStatus >= 0) {
codecOutputBuffer = getMediaCodecOutputBuffer(indexOrStatus);
codecOutputBuffer.position(info.offset);
codecOutputBuffer.limit(info.offset + info.size);
// Check key frame flag.
boolean isKeyFrame = (info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
if (isKeyFrame) {
Log.d(TAG, "Key frame generated");
}
final ByteBuffer frameBuffer;
if (isKeyFrame && mConfigData != null) {
Log.d(TAG, "Appending config frame of size %d to output buffer with size %d",
mConfigData.capacity(), info.size);
// For encoded key frame append SPS and PPS NALs at the start.
frameBuffer = ByteBuffer.allocateDirect(mConfigData.capacity() + info.size);
mConfigData.rewind();
frameBuffer.put(mConfigData);
frameBuffer.put(codecOutputBuffer);
frameBuffer.rewind();
info.offset = 0;
info.size += mConfigData.capacity();
mOutputBuffers.put(indexOrStatus, frameBuffer);
} else {
mOutputBuffers.put(indexOrStatus, codecOutputBuffer);
}
}
} catch (IllegalStateException e) {
Log.e(TAG, "Failed to dequeue output buffer", e);
}
return indexOrStatus;
}
// Call this function with catching IllegalStateException.
@SuppressWarnings("deprecation")
private ByteBuffer getMediaCodecOutputBuffer(int index) {
ByteBuffer outputBuffer = super.getOutputBuffer(index);
if (outputBuffer == null) {
throw new IllegalStateException("Got null output buffer");
}
return outputBuffer;
}
}