Add unit tests for hardware video codecs.
Bug: webrtc:9594 Change-Id: I4529a5123997e0309bde1b931bb6d99bea8c0dfd Reviewed-on: https://webrtc-review.googlesource.com/92399 Commit-Queue: Sami Kalliomäki <sakal@webrtc.org> Reviewed-by: Magnus Jedvert <magjed@webrtc.org> Reviewed-by: Patrik Höglund <phoglund@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24223}
This commit is contained in:
committed by
Commit Bot
parent
39a44b2134
commit
a381871dbf
@ -52,6 +52,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
// MediaCodec.
|
||||
private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
|
||||
|
||||
private final MediaCodecWrapperFactory mediaCodecWrapperFactory;
|
||||
private final String codecName;
|
||||
private final VideoCodecType codecType;
|
||||
|
||||
@ -123,13 +124,14 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
@Nullable private Callback callback;
|
||||
|
||||
// Valid and immutable while the decoder is running.
|
||||
@Nullable private MediaCodec codec = null;
|
||||
@Nullable private MediaCodecWrapper codec = null;
|
||||
|
||||
HardwareVideoDecoder(
|
||||
String codecName, VideoCodecType codecType, int colorFormat, EglBase.Context sharedContext) {
|
||||
HardwareVideoDecoder(MediaCodecWrapperFactory mediaCodecWrapperFactory, String codecName,
|
||||
VideoCodecType codecType, int colorFormat, EglBase.Context sharedContext) {
|
||||
if (!isSupportedColorFormat(colorFormat)) {
|
||||
throw new IllegalArgumentException("Unsupported color format: " + colorFormat);
|
||||
}
|
||||
this.mediaCodecWrapperFactory = mediaCodecWrapperFactory;
|
||||
this.codecName = codecName;
|
||||
this.codecType = codecType;
|
||||
this.colorFormat = colorFormat;
|
||||
@ -143,7 +145,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
|
||||
this.callback = callback;
|
||||
if (sharedContext != null) {
|
||||
surfaceTextureHelper = SurfaceTextureHelper.create("decoder-texture-thread", sharedContext);
|
||||
surfaceTextureHelper = createSurfaceTextureHelper();
|
||||
surface = new Surface(surfaceTextureHelper.getSurfaceTexture());
|
||||
surfaceTextureHelper.startListening(this);
|
||||
}
|
||||
@ -170,7 +172,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
keyFrameRequired = true;
|
||||
|
||||
try {
|
||||
codec = MediaCodec.createByCodecName(codecName);
|
||||
codec = mediaCodecWrapperFactory.createByCodecName(codecName);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.e(TAG, "Cannot create media decoder " + codecName);
|
||||
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||
@ -304,7 +306,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
Logging.d(TAG, "release");
|
||||
VideoCodecStatus status = releaseInternal();
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
releaseSurface();
|
||||
surface = null;
|
||||
surfaceTextureHelper.stopListening();
|
||||
surfaceTextureHelper.dispose();
|
||||
@ -368,7 +370,8 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
};
|
||||
}
|
||||
|
||||
private void deliverDecodedFrame() {
|
||||
// Visible for testing.
|
||||
protected void deliverDecodedFrame() {
|
||||
outputThreadChecker.checkIsOnValidThread();
|
||||
try {
|
||||
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
|
||||
@ -527,16 +530,16 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
final int vPos = uPos + uvStride * sliceHeight / 2;
|
||||
final int vEnd = vPos + uvStride * chromaHeight;
|
||||
|
||||
VideoFrame.I420Buffer frameBuffer = JavaI420Buffer.allocate(width, height);
|
||||
VideoFrame.I420Buffer frameBuffer = allocateI420Buffer(width, height);
|
||||
|
||||
buffer.limit(yEnd);
|
||||
buffer.position(yPos);
|
||||
YuvHelper.copyPlane(
|
||||
copyPlane(
|
||||
buffer.slice(), stride, frameBuffer.getDataY(), frameBuffer.getStrideY(), width, height);
|
||||
|
||||
buffer.limit(uEnd);
|
||||
buffer.position(uPos);
|
||||
YuvHelper.copyPlane(buffer.slice(), uvStride, frameBuffer.getDataU(), frameBuffer.getStrideU(),
|
||||
copyPlane(buffer.slice(), uvStride, frameBuffer.getDataU(), frameBuffer.getStrideU(),
|
||||
chromaWidth, chromaHeight);
|
||||
if (sliceHeight % 2 == 1) {
|
||||
buffer.position(uPos + uvStride * (chromaHeight - 1)); // Seek to beginning of last full row.
|
||||
@ -548,7 +551,7 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
|
||||
buffer.limit(vEnd);
|
||||
buffer.position(vPos);
|
||||
YuvHelper.copyPlane(buffer.slice(), uvStride, frameBuffer.getDataV(), frameBuffer.getStrideV(),
|
||||
copyPlane(buffer.slice(), uvStride, frameBuffer.getDataV(), frameBuffer.getStrideV(),
|
||||
chromaWidth, chromaHeight);
|
||||
if (sliceHeight % 2 == 1) {
|
||||
buffer.position(vPos + uvStride * (chromaHeight - 1)); // Seek to beginning of last full row.
|
||||
@ -646,4 +649,26 @@ class HardwareVideoDecoder implements VideoDecoder, VideoSink {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected SurfaceTextureHelper createSurfaceTextureHelper() {
|
||||
return SurfaceTextureHelper.create("decoder-texture-thread", sharedContext);
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
// TODO(sakal): Remove once Robolectric commit fa991a0 has been rolled to WebRTC.
|
||||
protected void releaseSurface() {
|
||||
surface.release();
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected VideoFrame.I420Buffer allocateI420Buffer(int width, int height) {
|
||||
return JavaI420Buffer.allocate(width, height);
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected void copyPlane(
|
||||
ByteBuffer src, int srcStride, ByteBuffer dst, int dstStride, int width, int height) {
|
||||
YuvHelper.copyPlane(src, srcStride, dst, dstStride, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
private static final int DEQUEUE_OUTPUT_BUFFER_TIMEOUT_US = 100000;
|
||||
|
||||
// --- Initialized on construction.
|
||||
private final MediaCodecWrapperFactory mediaCodecWrapperFactory;
|
||||
private final String codecName;
|
||||
private final VideoCodecType codecType;
|
||||
private final Integer surfaceColorFormat;
|
||||
@ -82,7 +83,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
private boolean automaticResizeOn;
|
||||
|
||||
// --- Valid and immutable while an encoding session is running.
|
||||
@Nullable private MediaCodec codec;
|
||||
@Nullable private MediaCodecWrapper codec;
|
||||
// Thread that delivers encoded frames to the user callback.
|
||||
@Nullable private Thread outputThread;
|
||||
|
||||
@ -128,10 +129,11 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
* desired bitrates
|
||||
* @throws IllegalArgumentException if colorFormat is unsupported
|
||||
*/
|
||||
public HardwareVideoEncoder(String codecName, VideoCodecType codecType,
|
||||
Integer surfaceColorFormat, Integer yuvColorFormat, Map<String, String> params,
|
||||
int keyFrameIntervalSec, int forceKeyFrameIntervalMs, BitrateAdjuster bitrateAdjuster,
|
||||
EglBase14.Context sharedContext) {
|
||||
public HardwareVideoEncoder(MediaCodecWrapperFactory mediaCodecWrapperFactory, String codecName,
|
||||
VideoCodecType codecType, Integer surfaceColorFormat, Integer yuvColorFormat,
|
||||
Map<String, String> params, int keyFrameIntervalSec, int forceKeyFrameIntervalMs,
|
||||
BitrateAdjuster bitrateAdjuster, EglBase14.Context sharedContext) {
|
||||
this.mediaCodecWrapperFactory = mediaCodecWrapperFactory;
|
||||
this.codecName = codecName;
|
||||
this.codecType = codecType;
|
||||
this.surfaceColorFormat = surfaceColorFormat;
|
||||
@ -174,7 +176,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
lastKeyFrameNs = -1;
|
||||
|
||||
try {
|
||||
codec = MediaCodec.createByCodecName(codecName);
|
||||
codec = mediaCodecWrapperFactory.createByCodecName(codecName);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.e(TAG, "Cannot create media encoder " + codecName);
|
||||
return VideoCodecStatus.FALLBACK_SOFTWARE;
|
||||
@ -384,7 +386,7 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
Logging.e(TAG, "getInputBuffers failed", e);
|
||||
return VideoCodecStatus.ERROR;
|
||||
}
|
||||
yuvFormat.fillBuffer(buffer, videoFrameBuffer);
|
||||
fillInputBuffer(buffer, videoFrameBuffer);
|
||||
|
||||
try {
|
||||
codec.queueInputBuffer(
|
||||
@ -481,7 +483,8 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
};
|
||||
}
|
||||
|
||||
private void deliverEncodedImage() {
|
||||
// Visible for testing.
|
||||
protected void deliverEncodedImage() {
|
||||
outputThreadChecker.checkIsOnValidThread();
|
||||
try {
|
||||
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
|
||||
@ -576,6 +579,11 @@ class HardwareVideoEncoder implements VideoEncoder {
|
||||
return sharedContext != null && surfaceColorFormat != null;
|
||||
}
|
||||
|
||||
// Visible for testing.
|
||||
protected void fillInputBuffer(ByteBuffer buffer, VideoFrame.Buffer videoFrameBuffer) {
|
||||
yuvFormat.fillBuffer(buffer, videoFrameBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of supported YUV color formats used for MediaCodec's input.
|
||||
*/
|
||||
|
||||
53
sdk/android/src/java/org/webrtc/MediaCodecWrapper.java
Normal file
53
sdk/android/src/java/org/webrtc/MediaCodecWrapper.java
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.view.Surface;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Subset of methods defined in {@link android.media.MediaCodec} needed by
|
||||
* {@link HardwareVideoEncoder} and {@link HardwareVideoDecoder}. This interface
|
||||
* exists to allow mocking and using a fake implementation in tests.
|
||||
*/
|
||||
interface MediaCodecWrapper {
|
||||
void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags);
|
||||
|
||||
void start();
|
||||
|
||||
void flush();
|
||||
|
||||
void stop();
|
||||
|
||||
void release();
|
||||
|
||||
int dequeueInputBuffer(long timeoutUs);
|
||||
|
||||
void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
|
||||
|
||||
int dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs);
|
||||
|
||||
void releaseOutputBuffer(int index, boolean render);
|
||||
|
||||
MediaFormat getOutputFormat();
|
||||
|
||||
ByteBuffer[] getInputBuffers();
|
||||
|
||||
ByteBuffer[] getOutputBuffers();
|
||||
|
||||
Surface createInputSurface();
|
||||
|
||||
void setParameters(Bundle params);
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
interface MediaCodecWrapperFactory {
|
||||
/**
|
||||
* Creates a new {@link MediaCodecWrapper} by codec name.
|
||||
*
|
||||
* <p>For additional information see {@link android.media.MediaCodec#createByCodecName}.
|
||||
*/
|
||||
MediaCodecWrapper createByCodecName(String name) throws IOException;
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright 2018 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
package org.webrtc;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.view.Surface;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Implementation of MediaCodecWrapperFactory that returns MediaCodecInterfaces wrapping
|
||||
* {@link android.media.MediaCodec} objects.
|
||||
*/
|
||||
class MediaCodecWrapperFactoryImpl implements MediaCodecWrapperFactory {
|
||||
private static class MediaCodecWrapperImpl implements MediaCodecWrapper {
|
||||
private final MediaCodec mediaCodec;
|
||||
|
||||
public MediaCodecWrapperImpl(MediaCodec mediaCodec) {
|
||||
this.mediaCodec = mediaCodec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
|
||||
mediaCodec.configure(format, surface, crypto, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
mediaCodec.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
mediaCodec.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
mediaCodec.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
mediaCodec.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueInputBuffer(long timeoutUs) {
|
||||
return mediaCodec.dequeueInputBuffer(timeoutUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInputBuffer(
|
||||
int index, int offset, int size, long presentationTimeUs, int flags) {
|
||||
mediaCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueOutputBuffer(BufferInfo info, long timeoutUs) {
|
||||
return mediaCodec.dequeueOutputBuffer(info, timeoutUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseOutputBuffer(int index, boolean render) {
|
||||
mediaCodec.releaseOutputBuffer(index, render);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getOutputFormat() {
|
||||
return mediaCodec.getOutputFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] getInputBuffers() {
|
||||
return mediaCodec.getInputBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] getOutputBuffers() {
|
||||
return mediaCodec.getOutputBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(18)
|
||||
public Surface createInputSurface() {
|
||||
return mediaCodec.createInputSurface();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TargetApi(19)
|
||||
public void setParameters(Bundle params) {
|
||||
mediaCodec.setParameters(params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaCodecWrapper createByCodecName(String name) throws IOException {
|
||||
return new MediaCodecWrapperImpl(MediaCodec.createByCodecName(name));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user