Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(486)

Unified Diff: webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java

Issue 2308843002: Add dynamic bitrate tracker and adjustment for Exynos VP8 HW encoder. (Closed)
Patch Set: Shorten log message Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java
diff --git a/webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java b/webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java
index 72544bd83f0babb186b3ba1f1c1d90102d19433a..923ea91441631f1bba155def2c2acd55394c3d85 100644
--- a/webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java
+++ b/webrtc/api/android/java/src/org/webrtc/MediaCodecVideoEncoder.java
@@ -52,6 +52,12 @@ public class MediaCodecVideoEncoder {
private static final int DEQUEUE_TIMEOUT = 0; // Non-blocking, no wait.
private static final int BITRATE_ADJUSTMENT_FPS = 30;
private static final int MAXIMUM_INITIAL_FPS = 30;
+ private static final double BITRATE_CORRECTION_SEC = 3.0;
+ // Maximum bitrate correction scale - no more than 2 times.
+ private static final double BITRATE_CORRECTION_MAX_SCALE = 2;
+ // Amount of correction steps to reach correction maximum scale.
+ private static final int BITRATE_CORRECTION_STEPS = 10;
+
// Active running encoder instance. Set in initEncode() (called from native code)
// and reset to null in release() call.
private static MediaCodecVideoEncoder runningInstance = null;
@@ -73,6 +79,19 @@ public class MediaCodecVideoEncoder {
private static final String VP9_MIME_TYPE = "video/x-vnd.on2.vp9";
private static final String H264_MIME_TYPE = "video/avc";
+ // Type of bitrate adjustment for video encoder.
+ public enum BitrateAdjustmentType {
+ // No adjustment - video encoder has no known bitrate problem.
+ NO_ADJUSTMENT,
+ // Framerate based bitrate adjustment is required - HW encoder does not use frame
+ // timestamps to calculate frame bitrate budget and instead is relying on initial
+ // fps configuration assuming that all frames are coming at fixed initial frame rate.
+ FRAMERATE_ADJUSTMENT,
+ // Dynamic bitrate adjustment is required - HW encoder used frame timestamps, but actual
+ // bitrate deviates too much from the target value.
+ DYNAMIC_ADJUSTMENT
+ }
+
// Class describing supported media codec properties.
private static class MediaCodecProperties {
public final String codecPrefix;
@@ -81,39 +100,39 @@ public class MediaCodecVideoEncoder {
// Flag if encoder implementation does not use frame timestamps to calculate frame bitrate
// budget and instead is relying on initial fps configuration assuming that all frames are
// coming at fixed initial frame rate. Bitrate adjustment is required for this case.
- public final boolean bitrateAdjustmentRequired;
+ public final BitrateAdjustmentType bitrateAdjustmentType;
MediaCodecProperties(
- String codecPrefix, int minSdk, boolean bitrateAdjustmentRequired) {
+ String codecPrefix, int minSdk, BitrateAdjustmentType bitrateAdjustmentType) {
this.codecPrefix = codecPrefix;
this.minSdk = minSdk;
- this.bitrateAdjustmentRequired = bitrateAdjustmentRequired;
+ this.bitrateAdjustmentType = bitrateAdjustmentType;
}
}
// List of supported HW VP8 encoders.
private static final MediaCodecProperties qcomVp8HwProperties = new MediaCodecProperties(
- "OMX.qcom.", Build.VERSION_CODES.KITKAT, false /* bitrateAdjustmentRequired */);
+ "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
private static final MediaCodecProperties exynosVp8HwProperties = new MediaCodecProperties(
- "OMX.Exynos.", Build.VERSION_CODES.M, false /* bitrateAdjustmentRequired */);
+ "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.DYNAMIC_ADJUSTMENT);
private static final MediaCodecProperties[] vp8HwList = new MediaCodecProperties[] {
qcomVp8HwProperties, exynosVp8HwProperties
};
// List of supported HW VP9 encoders.
private static final MediaCodecProperties qcomVp9HwProperties = new MediaCodecProperties(
- "OMX.qcom.", Build.VERSION_CODES.M, false /* bitrateAdjustmentRequired */);
+ "OMX.qcom.", Build.VERSION_CODES.M, BitrateAdjustmentType.NO_ADJUSTMENT);
private static final MediaCodecProperties exynosVp9HwProperties = new MediaCodecProperties(
- "OMX.Exynos.", Build.VERSION_CODES.M, false /* bitrateAdjustmentRequired */);
+ "OMX.Exynos.", Build.VERSION_CODES.M, BitrateAdjustmentType.NO_ADJUSTMENT);
private static final MediaCodecProperties[] vp9HwList = new MediaCodecProperties[] {
qcomVp9HwProperties, exynosVp9HwProperties
};
// List of supported HW H.264 encoders.
private static final MediaCodecProperties qcomH264HwProperties = new MediaCodecProperties(
- "OMX.qcom.", Build.VERSION_CODES.KITKAT, false /* bitrateAdjustmentRequired */);
+ "OMX.qcom.", Build.VERSION_CODES.KITKAT, BitrateAdjustmentType.NO_ADJUSTMENT);
private static final MediaCodecProperties exynosH264HwProperties = new MediaCodecProperties(
- "OMX.Exynos.", Build.VERSION_CODES.LOLLIPOP, true /* bitrateAdjustmentRequired */);
+ "OMX.Exynos.", Build.VERSION_CODES.LOLLIPOP, BitrateAdjustmentType.FRAMERATE_ADJUSTMENT);
private static final MediaCodecProperties[] h264HwList = new MediaCodecProperties[] {
qcomH264HwProperties, exynosH264HwProperties
};
@@ -146,7 +165,15 @@ public class MediaCodecVideoEncoder {
};
private VideoCodecType type;
private int colorFormat; // Used by native code.
- private boolean bitrateAdjustmentRequired;
+
+ // Variables used for dynamic bitrate adjustment.
+ private BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
+ private double bitrateAccumulator;
+ private double bitrateAccumulatorMax;
+ private double bitrateObservationTimeMs;
+ private int bitrateAdjustmentScaleExp;
+ private int targetBitrateBps;
+ private int targetFps;
// SPS and PPS NALs (Config frame) for H.264.
private ByteBuffer configData = null;
@@ -213,14 +240,15 @@ public class MediaCodecVideoEncoder {
// Helper struct for findHwEncoder() below.
private static class EncoderProperties {
- public EncoderProperties(String codecName, int colorFormat, boolean bitrateAdjustment) {
+ public EncoderProperties(
+ String codecName, int colorFormat, BitrateAdjustmentType bitrateAdjustmentType) {
this.codecName = codecName;
this.colorFormat = colorFormat;
- this.bitrateAdjustment = bitrateAdjustment;
+ this.bitrateAdjustmentType = bitrateAdjustmentType;
}
public final String codecName; // OpenMax component name for HW codec.
public final int colorFormat; // Color format supported by codec.
- public final boolean bitrateAdjustment; // true if bitrate adjustment workaround is required.
+ public final BitrateAdjustmentType bitrateAdjustmentType; // Bitrate adjustment type
}
private static EncoderProperties findHwEncoder(
@@ -264,7 +292,7 @@ public class MediaCodecVideoEncoder {
// Check if this is supported HW encoder.
boolean supportedCodec = false;
- boolean bitrateAdjustmentRequired = false;
+ BitrateAdjustmentType bitrateAdjustmentType = BitrateAdjustmentType.NO_ADJUSTMENT;
for (MediaCodecProperties codecProperties : supportedHwCodecProperties) {
if (name.startsWith(codecProperties.codecPrefix)) {
if (Build.VERSION.SDK_INT < codecProperties.minSdk) {
@@ -272,9 +300,10 @@ public class MediaCodecVideoEncoder {
Build.VERSION.SDK_INT);
continue;
}
- if (codecProperties.bitrateAdjustmentRequired) {
- Logging.w(TAG, "Codec " + name + " does not use frame timestamps.");
- bitrateAdjustmentRequired = true;
+ if (codecProperties.bitrateAdjustmentType != BitrateAdjustmentType.NO_ADJUSTMENT) {
+ bitrateAdjustmentType = codecProperties.bitrateAdjustmentType;
+ Logging.w(TAG, "Codec " + name
+ + " requires bitrate adjustment: " + bitrateAdjustmentType);
}
supportedCodec = true;
break;
@@ -300,9 +329,10 @@ public class MediaCodecVideoEncoder {
for (int codecColorFormat : capabilities.colorFormats) {
if (codecColorFormat == supportedColorFormat) {
// Found supported HW encoder.
- Logging.d(TAG, "Found target encoder for mime " + mime + " : " + name +
- ". Color: 0x" + Integer.toHexString(codecColorFormat));
- return new EncoderProperties(name, codecColorFormat, bitrateAdjustmentRequired);
+ Logging.d(TAG, "Found target encoder for mime " + mime + " : " + name
+ + ". Color: 0x" + Integer.toHexString(codecColorFormat)
+ + ". Bitrate adjustment: " + bitrateAdjustmentType);
+ return new EncoderProperties(name, codecColorFormat, bitrateAdjustmentType);
}
}
}
@@ -375,23 +405,29 @@ public class MediaCodecVideoEncoder {
}
runningInstance = this; // Encoder is now running and can be queried for stack traces.
colorFormat = properties.colorFormat;
- bitrateAdjustmentRequired = properties.bitrateAdjustment;
- if (bitrateAdjustmentRequired) {
+ bitrateAdjustmentType = properties.bitrateAdjustmentType;
+ if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT) {
fps = BITRATE_ADJUSTMENT_FPS;
} else {
fps = Math.min(fps, MAXIMUM_INITIAL_FPS);
}
Logging.d(TAG, "Color format: " + colorFormat +
- ". Bitrate adjustment: " + bitrateAdjustmentRequired +
+ ". Bitrate adjustment: " + bitrateAdjustmentType +
". Initial fps: " + fps);
+ targetBitrateBps = 1000 * kbps;
+ targetFps = fps;
+ bitrateAccumulatorMax = targetBitrateBps / 8.0;
+ bitrateAccumulator = 0;
+ bitrateObservationTimeMs = 0;
+ bitrateAdjustmentScaleExp = 0;
mediaCodecThread = Thread.currentThread();
try {
MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
- format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * kbps);
+ format.setInteger(MediaFormat.KEY_BIT_RATE, targetBitrateBps);
format.setInteger("bitrate-mode", VIDEO_ControlRateConstant);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
- format.setInteger(MediaFormat.KEY_FRAME_RATE, fps);
+ format.setInteger(MediaFormat.KEY_FRAME_RATE, targetFps);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, keyFrameIntervalSec);
Logging.d(TAG, " Format: " + format);
mediaCodec = createByCodecName(properties.codecName);
@@ -529,17 +565,36 @@ public class MediaCodecVideoEncoder {
private boolean setRates(int kbps, int frameRate) {
checkOnMediaCodecThread();
- int codecBitrate = 1000 * kbps;
- if (bitrateAdjustmentRequired && frameRate > 0) {
- codecBitrate = BITRATE_ADJUSTMENT_FPS * codecBitrate / frameRate;
- Logging.v(TAG, "setRates: " + kbps + " -> " + (codecBitrate / 1000)
- + " kbps. Fps: " + frameRate);
+
+ int codecBitrateBps = 1000 * kbps;
+ if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
+ bitrateAccumulatorMax = codecBitrateBps / 8.0;
+ if (targetBitrateBps > 0 && codecBitrateBps < targetBitrateBps) {
+ // Rescale the accumulator level if the accumulator max decreases
+ bitrateAccumulator = bitrateAccumulator * codecBitrateBps / targetBitrateBps;
+ }
+ }
+ targetBitrateBps = codecBitrateBps;
+ targetFps = frameRate;
+
+ // Adjust actual encoder bitrate based on bitrate adjustment type.
+ if (bitrateAdjustmentType == BitrateAdjustmentType.FRAMERATE_ADJUSTMENT && targetFps > 0) {
+ codecBitrateBps = BITRATE_ADJUSTMENT_FPS * targetBitrateBps / targetFps;
+ Logging.v(TAG, "setRates: " + kbps + " -> " + (codecBitrateBps / 1000)
+ + " kbps. Fps: " + targetFps);
+ } else if (bitrateAdjustmentType == BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
+ Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps
+ + ". ExpScale: " + bitrateAdjustmentScaleExp);
+ if (bitrateAdjustmentScaleExp != 0) {
+ codecBitrateBps = (int)(codecBitrateBps * getBitrateScale(bitrateAdjustmentScaleExp));
+ }
} else {
- Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + frameRate);
+ Logging.v(TAG, "setRates: " + kbps + " kbps. Fps: " + targetFps);
}
+
try {
Bundle params = new Bundle();
- params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, codecBitrate);
+ params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, codecBitrateBps);
mediaCodec.setParameters(params);
return true;
} catch (IllegalStateException e) {
@@ -608,6 +663,8 @@ public class MediaCodecVideoEncoder {
ByteBuffer outputBuffer = outputBuffers[result].duplicate();
outputBuffer.position(info.offset);
outputBuffer.limit(info.offset + info.size);
+ reportEncodedFrame(info.size);
+
// Check key frame flag.
boolean isKeyFrame =
(info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0;
@@ -646,6 +703,56 @@ public class MediaCodecVideoEncoder {
}
}
+ private double getBitrateScale(int bitrateAdjustmentScaleExp) {
+ return Math.pow(BITRATE_CORRECTION_MAX_SCALE,
+ (double)bitrateAdjustmentScaleExp / BITRATE_CORRECTION_STEPS);
+ }
+
+ private void reportEncodedFrame(int size) {
+ if (targetFps == 0 || bitrateAdjustmentType != BitrateAdjustmentType.DYNAMIC_ADJUSTMENT) {
+ return;
+ }
+
+ // Accumulate the difference between actial and expected frame sizes.
+ double expectedBytesPerFrame = targetBitrateBps / (8.0 * targetFps);
+ bitrateAccumulator += (size - expectedBytesPerFrame);
+ bitrateObservationTimeMs += 1000.0 / targetFps;
+
+ // Put a cap on the accumulator, i.e., don't let it grow beyond some level to avoid
+ // using too old data for bitrate adjustment.
+ double bitrateAccumulatorCap = BITRATE_CORRECTION_SEC * bitrateAccumulatorMax;
+ bitrateAccumulator = Math.min(bitrateAccumulator, bitrateAccumulatorCap);
+ bitrateAccumulator = Math.max(bitrateAccumulator, -bitrateAccumulatorCap);
+
+ // Do bitrate adjustment every 3 seconds if actual encoder bitrate deviates too much
+ // form the target value.
+ if (bitrateObservationTimeMs > 1000 * BITRATE_CORRECTION_SEC) {
+ Logging.d(TAG, "Acc: " + (int)bitrateAccumulator
+ + ". Max: " + (int)bitrateAccumulatorMax
+ + ". ExpScale: " + bitrateAdjustmentScaleExp);
+ boolean bitrateAdjustmentScaleChanged = false;
+ if (bitrateAccumulator > bitrateAccumulatorMax) {
+ // Encoder generates too high bitrate - need to reduce the scale.
+ bitrateAccumulator = bitrateAccumulatorMax;
+ bitrateAdjustmentScaleExp--;
+ bitrateAdjustmentScaleChanged = true;
+ } else if (bitrateAccumulator < -bitrateAccumulatorMax) {
+ // Encoder generates too low bitrate - need to increase the scale.
+ bitrateAdjustmentScaleExp++;
+ bitrateAccumulator = -bitrateAccumulatorMax;
+ bitrateAdjustmentScaleChanged = true;
+ }
+ if (bitrateAdjustmentScaleChanged) {
+ bitrateAdjustmentScaleExp = Math.min(bitrateAdjustmentScaleExp, BITRATE_CORRECTION_STEPS);
+ bitrateAdjustmentScaleExp = Math.max(bitrateAdjustmentScaleExp, -BITRATE_CORRECTION_STEPS);
+ Logging.d(TAG, "Adjusting bitrate scale to " + bitrateAdjustmentScaleExp
+ + ". Value: " + getBitrateScale(bitrateAdjustmentScaleExp));
+ setRates(targetBitrateBps / 1000, targetFps);
+ }
+ bitrateObservationTimeMs = 0;
+ }
+ }
+
// Release a dequeued output buffer back to the codec for re-use. Return
// false if the codec is no longer operable.
boolean releaseOutputBuffer(int index) {
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698