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:
Sami Kalliomäki
2018-08-08 11:29:23 +02:00
committed by Commit Bot
parent 39a44b2134
commit a381871dbf
13 changed files with 1326 additions and 24 deletions

View File

@ -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);
}
}

View File

@ -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.
*/

View 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);
}

View File

@ -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;
}

View File

@ -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));
}
}