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
64
sdk/android/tests/src/org/webrtc/CodecTestHelper.java
Normal file
64
sdk/android/tests/src/org/webrtc/CodecTestHelper.java
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Random;
|
||||
import org.webrtc.JavaI420Buffer;
|
||||
import org.webrtc.VideoFrame;
|
||||
|
||||
/**
|
||||
* Helper methods for {@link HardwareVideoEncoderTest} and {@link HardwareVideoDecoderTest}.
|
||||
*/
|
||||
class CodecTestHelper {
|
||||
static void assertEqualContents(byte[] expected, ByteBuffer actual, int offset, int size) {
|
||||
assertThat(size).isEqualTo(expected.length);
|
||||
assertThat(actual.capacity()).isAtLeast(offset + size);
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertWithMessage("At index: " + i).that(actual.get(offset + i)).isEqualTo(expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] generateRandomData(int length) {
|
||||
Random random = new Random();
|
||||
byte[] data = new byte[length];
|
||||
random.nextBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
static VideoFrame.I420Buffer wrapI420(int width, int height, byte[] data) {
|
||||
final int posY = 0;
|
||||
final int posU = width * height;
|
||||
final int posV = posU + width * height / 4;
|
||||
final int endV = posV + width * height / 4;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(data.length);
|
||||
buffer.put(data);
|
||||
|
||||
buffer.limit(posU);
|
||||
buffer.position(posY);
|
||||
ByteBuffer dataY = buffer.slice();
|
||||
|
||||
buffer.limit(posV);
|
||||
buffer.position(posU);
|
||||
ByteBuffer dataU = buffer.slice();
|
||||
|
||||
buffer.limit(endV);
|
||||
buffer.position(posV);
|
||||
ByteBuffer dataV = buffer.slice();
|
||||
|
||||
return JavaI420Buffer.wrap(width, height, dataY, width, dataU, width / 2, dataV, width / 2,
|
||||
/* releaseCallback= */ null);
|
||||
}
|
||||
}
|
||||
314
sdk/android/tests/src/org/webrtc/FakeMediaCodecWrapper.java
Normal file
314
sdk/android/tests/src/org/webrtc/FakeMediaCodecWrapper.java
Normal file
@ -0,0 +1,314 @@
|
||||
/*
|
||||
* 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.graphics.SurfaceTexture;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaCrypto;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Bundle;
|
||||
import android.view.Surface;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Fake MediaCodec that implements the basic state machine.
|
||||
*
|
||||
* @note This class is only intended for single-threaded tests and is not thread-safe.
|
||||
*/
|
||||
public class FakeMediaCodecWrapper implements MediaCodecWrapper {
|
||||
private static final int NUM_INPUT_BUFFERS = 10;
|
||||
private static final int NUM_OUTPUT_BUFFERS = 10;
|
||||
private static final int MAX_ENCODED_DATA_SIZE_BYTES = 1_000;
|
||||
|
||||
/**
|
||||
* MediaCodec state as defined by:
|
||||
* https://developer.android.com/reference/android/media/MediaCodec.html
|
||||
*/
|
||||
public enum State {
|
||||
STOPPED_CONFIGURED(Primary.STOPPED),
|
||||
STOPPED_UNINITIALIZED(Primary.STOPPED),
|
||||
STOPPED_ERROR(Primary.STOPPED),
|
||||
EXECUTING_FLUSHED(Primary.EXECUTING),
|
||||
EXECUTING_RUNNING(Primary.EXECUTING),
|
||||
EXECUTING_END_OF_STREAM(Primary.EXECUTING),
|
||||
RELEASED(Primary.RELEASED);
|
||||
|
||||
public enum Primary { STOPPED, EXECUTING, RELEASED }
|
||||
|
||||
private final Primary primary;
|
||||
|
||||
State(Primary primary) {
|
||||
this.primary = primary;
|
||||
}
|
||||
|
||||
public Primary getPrimary() {
|
||||
return primary;
|
||||
}
|
||||
}
|
||||
|
||||
/** Represents an output buffer that will be returned by dequeueOutputBuffer. */
|
||||
public static class QueuedOutputBufferInfo {
|
||||
private int index;
|
||||
private int offset;
|
||||
private int size;
|
||||
private long presentationTimeUs;
|
||||
private int flags;
|
||||
|
||||
private QueuedOutputBufferInfo(
|
||||
int index, int offset, int size, long presentationTimeUs, int flags) {
|
||||
this.index = index;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
this.presentationTimeUs = presentationTimeUs;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public static QueuedOutputBufferInfo create(
|
||||
int index, int offset, int size, long presentationTimeUs, int flags) {
|
||||
return new QueuedOutputBufferInfo(index, offset, size, presentationTimeUs, flags);
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public long getPresentationTimeUs() {
|
||||
return presentationTimeUs;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
private State state = State.STOPPED_UNINITIALIZED;
|
||||
private @Nullable MediaFormat configuredFormat;
|
||||
private int configuredFlags;
|
||||
private final MediaFormat outputFormat;
|
||||
private final ByteBuffer[] inputBuffers = new ByteBuffer[NUM_INPUT_BUFFERS];
|
||||
private final ByteBuffer[] outputBuffers = new ByteBuffer[NUM_OUTPUT_BUFFERS];
|
||||
private final boolean[] inputBufferReserved = new boolean[NUM_INPUT_BUFFERS];
|
||||
private final boolean[] outputBufferReserved = new boolean[NUM_OUTPUT_BUFFERS];
|
||||
private final List<QueuedOutputBufferInfo> queuedOutputBuffers = new ArrayList<>();
|
||||
|
||||
public FakeMediaCodecWrapper(MediaFormat outputFormat) {
|
||||
this.outputFormat = outputFormat;
|
||||
}
|
||||
|
||||
/** Returns the current simulated state of MediaCodec. */
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/** Gets the last configured media format passed to configure. */
|
||||
public @Nullable MediaFormat getConfiguredFormat() {
|
||||
return configuredFormat;
|
||||
}
|
||||
|
||||
/** Returns the last flags passed to configure. */
|
||||
public int getConfiguredFlags() {
|
||||
return configuredFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a texture buffer that will be returned by dequeueOutputBuffer. Returns index of the
|
||||
* buffer.
|
||||
*/
|
||||
public int addOutputTexture(long presentationTimestampUs, int flags) {
|
||||
int index = getFreeOutputBuffer();
|
||||
queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
|
||||
index, /* offset= */ 0, /* size= */ 0, presentationTimestampUs, flags));
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a byte buffer buffer that will be returned by dequeueOutputBuffer. Returns index of the
|
||||
* buffer.
|
||||
*/
|
||||
public int addOutputData(byte[] data, long presentationTimestampUs, int flags) {
|
||||
int index = getFreeOutputBuffer();
|
||||
ByteBuffer outputBuffer = outputBuffers[index];
|
||||
|
||||
outputBuffer.clear();
|
||||
outputBuffer.put(data);
|
||||
outputBuffer.rewind();
|
||||
|
||||
queuedOutputBuffers.add(QueuedOutputBufferInfo.create(
|
||||
index, /* offset= */ 0, data.length, presentationTimestampUs, flags));
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first output buffer that is not reserved and reserves it. It will be stay reserved
|
||||
* until released with releaseOutputBuffer.
|
||||
*/
|
||||
private int getFreeOutputBuffer() {
|
||||
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
|
||||
if (!outputBufferReserved[i]) {
|
||||
outputBufferReserved[i] = true;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("All output buffers reserved!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(MediaFormat format, Surface surface, MediaCrypto crypto, int flags) {
|
||||
if (state != State.STOPPED_UNINITIALIZED) {
|
||||
throw new IllegalStateException("Expected state STOPPED_UNINITIALIZED but was " + state);
|
||||
}
|
||||
state = State.STOPPED_CONFIGURED;
|
||||
configuredFormat = format;
|
||||
configuredFlags = flags;
|
||||
|
||||
final int width = configuredFormat.getInteger(MediaFormat.KEY_WIDTH);
|
||||
final int height = configuredFormat.getInteger(MediaFormat.KEY_HEIGHT);
|
||||
final int yuvSize = width * height * 3 / 2;
|
||||
final int inputBufferSize;
|
||||
final int outputBufferSize;
|
||||
|
||||
if ((flags & MediaCodec.CONFIGURE_FLAG_ENCODE) != 0) {
|
||||
final int colorFormat = configuredFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
|
||||
|
||||
inputBufferSize = colorFormat == CodecCapabilities.COLOR_FormatSurface ? 0 : yuvSize;
|
||||
outputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
|
||||
} else {
|
||||
inputBufferSize = MAX_ENCODED_DATA_SIZE_BYTES;
|
||||
outputBufferSize = surface != null ? 0 : yuvSize;
|
||||
}
|
||||
|
||||
for (int i = 0; i < inputBuffers.length; i++) {
|
||||
inputBuffers[i] = ByteBuffer.allocateDirect(inputBufferSize);
|
||||
}
|
||||
for (int i = 0; i < outputBuffers.length; i++) {
|
||||
outputBuffers[i] = ByteBuffer.allocateDirect(outputBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (state != State.STOPPED_CONFIGURED) {
|
||||
throw new IllegalStateException("Expected state STOPPED_CONFIGURED but was " + state);
|
||||
}
|
||||
state = State.EXECUTING_RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
if (state.getPrimary() != State.Primary.EXECUTING) {
|
||||
throw new IllegalStateException("Expected state EXECUTING but was " + state);
|
||||
}
|
||||
state = State.EXECUTING_FLUSHED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
if (state.getPrimary() != State.Primary.EXECUTING) {
|
||||
throw new IllegalStateException("Expected state EXECUTING but was " + state);
|
||||
}
|
||||
state = State.STOPPED_UNINITIALIZED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
state = State.RELEASED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueInputBuffer(long timeoutUs) {
|
||||
if (state != State.EXECUTING_FLUSHED && state != State.EXECUTING_RUNNING) {
|
||||
throw new IllegalStateException(
|
||||
"Expected state EXECUTING_FLUSHED or EXECUTING_RUNNING but was " + state);
|
||||
}
|
||||
state = State.EXECUTING_RUNNING;
|
||||
|
||||
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
|
||||
if (!inputBufferReserved[i]) {
|
||||
inputBufferReserved[i] = true;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queueInputBuffer(
|
||||
int index, int offset, int size, long presentationTimeUs, int flags) {
|
||||
if (state.getPrimary() != State.Primary.EXECUTING) {
|
||||
throw new IllegalStateException("Expected state EXECUTING but was " + state);
|
||||
}
|
||||
if (flags != 0) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Flags are not implemented in FakeMediaCodecWrapper.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int dequeueOutputBuffer(MediaCodec.BufferInfo info, long timeoutUs) {
|
||||
if (state.getPrimary() != State.Primary.EXECUTING) {
|
||||
throw new IllegalStateException("Expected state EXECUTING but was " + state);
|
||||
}
|
||||
|
||||
if (queuedOutputBuffers.isEmpty()) {
|
||||
return MediaCodec.INFO_TRY_AGAIN_LATER;
|
||||
}
|
||||
QueuedOutputBufferInfo outputBufferInfo = queuedOutputBuffers.remove(/* index= */ 0);
|
||||
info.set(outputBufferInfo.getOffset(), outputBufferInfo.getSize(),
|
||||
outputBufferInfo.getPresentationTimeUs(), outputBufferInfo.getFlags());
|
||||
return outputBufferInfo.getIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseOutputBuffer(int index, boolean render) {
|
||||
if (state.getPrimary() != State.Primary.EXECUTING) {
|
||||
throw new IllegalStateException("Expected state EXECUTING but was " + state);
|
||||
}
|
||||
if (!outputBufferReserved[index]) {
|
||||
throw new RuntimeException("Released output buffer was not in use.");
|
||||
}
|
||||
outputBufferReserved[index] = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] getInputBuffers() {
|
||||
return inputBuffers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer[] getOutputBuffers() {
|
||||
return outputBuffers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaFormat getOutputFormat() {
|
||||
return outputFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Surface createInputSurface() {
|
||||
return new Surface(new SurfaceTexture(/* texName= */ 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParameters(Bundle params) {}
|
||||
}
|
||||
423
sdk/android/tests/src/org/webrtc/HardwareVideoDecoderTest.java
Normal file
423
sdk/android/tests/src/org/webrtc/HardwareVideoDecoderTest.java
Normal file
@ -0,0 +1,423 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.graphics.Matrix;
|
||||
import android.media.MediaCodec.BufferInfo;
|
||||
import android.media.MediaCodecInfo.CodecCapabilities;
|
||||
import android.media.MediaFormat;
|
||||
import android.os.Handler;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.chromium.testing.local.LocalRobolectricTestRunner;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowSystemClock;
|
||||
import org.webrtc.EglBase;
|
||||
import org.webrtc.EncodedImage;
|
||||
import org.webrtc.EncodedImage.FrameType;
|
||||
import org.webrtc.FakeMediaCodecWrapper.State;
|
||||
import org.webrtc.SurfaceTextureHelper;
|
||||
import org.webrtc.TextureBufferImpl;
|
||||
import org.webrtc.VideoCodecStatus;
|
||||
import org.webrtc.VideoDecoder;
|
||||
import org.webrtc.VideoDecoder.DecodeInfo;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoFrame.I420Buffer;
|
||||
import org.webrtc.VideoFrame.TextureBuffer.Type;
|
||||
import org.webrtc.VideoSink;
|
||||
import org.webrtc.YuvConverter;
|
||||
|
||||
@RunWith(LocalRobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class HardwareVideoDecoderTest {
|
||||
private static final VideoDecoder.Settings TEST_DECODER_SETTINGS =
|
||||
new VideoDecoder.Settings(/* numberOfCores= */ 1, /* width= */ 640, /* height= */ 480);
|
||||
private static final int COLOR_FORMAT = CodecCapabilities.COLOR_FormatYUV420Planar;
|
||||
private static final long POLL_DELAY_MS = 10;
|
||||
private static final long DELIVER_DECODED_IMAGE_DELAY_MS = 10;
|
||||
|
||||
private static final byte[] ENCODED_TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
|
||||
|
||||
private class TestDecoder extends HardwareVideoDecoder {
|
||||
private final Object deliverDecodedFrameLock = new Object();
|
||||
private boolean deliverDecodedFrameDone = true;
|
||||
|
||||
public TestDecoder(MediaCodecWrapperFactory mediaCodecFactory, String codecName,
|
||||
VideoCodecType codecType, int colorFormat, EglBase.Context sharedContext) {
|
||||
super(mediaCodecFactory, codecName, codecType, colorFormat, sharedContext);
|
||||
}
|
||||
|
||||
public void waitDeliverDecodedFrame() throws InterruptedException {
|
||||
synchronized (deliverDecodedFrameLock) {
|
||||
deliverDecodedFrameDone = false;
|
||||
deliverDecodedFrameLock.notifyAll();
|
||||
while (!deliverDecodedFrameDone) {
|
||||
deliverDecodedFrameLock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WaitNotInLoop") // This method is called inside a loop.
|
||||
@Override
|
||||
protected void deliverDecodedFrame() {
|
||||
synchronized (deliverDecodedFrameLock) {
|
||||
if (deliverDecodedFrameDone) {
|
||||
try {
|
||||
deliverDecodedFrameLock.wait(DELIVER_DECODED_IMAGE_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (deliverDecodedFrameDone) {
|
||||
return;
|
||||
}
|
||||
super.deliverDecodedFrame();
|
||||
deliverDecodedFrameDone = true;
|
||||
deliverDecodedFrameLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SurfaceTextureHelper createSurfaceTextureHelper() {
|
||||
return mockSurfaceTextureHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseSurface() {}
|
||||
|
||||
@Override
|
||||
protected VideoFrame.I420Buffer allocateI420Buffer(int width, int height) {
|
||||
int chromaHeight = (height + 1) / 2;
|
||||
int strideUV = (width + 1) / 2;
|
||||
int yPos = 0;
|
||||
int uPos = yPos + width * height;
|
||||
int vPos = uPos + strideUV * chromaHeight;
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocateDirect(width * height + 2 * strideUV * chromaHeight);
|
||||
|
||||
buffer.position(yPos);
|
||||
buffer.limit(uPos);
|
||||
ByteBuffer dataY = buffer.slice();
|
||||
|
||||
buffer.position(uPos);
|
||||
buffer.limit(vPos);
|
||||
ByteBuffer dataU = buffer.slice();
|
||||
|
||||
buffer.position(vPos);
|
||||
buffer.limit(vPos + strideUV * chromaHeight);
|
||||
ByteBuffer dataV = buffer.slice();
|
||||
|
||||
return JavaI420Buffer.wrap(width, height, dataY, width, dataU, strideUV, dataV, strideUV,
|
||||
/* releaseCallback= */ null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void copyPlane(
|
||||
ByteBuffer src, int srcStride, ByteBuffer dst, int dstStride, int width, int height) {
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
dst.put(y * dstStride + x, src.get(y * srcStride + x));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDecoderBuilder {
|
||||
private VideoCodecType codecType = VideoCodecType.VP8;
|
||||
private boolean useSurface = true;
|
||||
|
||||
public TestDecoderBuilder setCodecType(VideoCodecType codecType) {
|
||||
this.codecType = codecType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestDecoderBuilder setUseSurface(boolean useSurface) {
|
||||
this.useSurface = useSurface;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestDecoder build() {
|
||||
return new TestDecoder((String name)
|
||||
-> fakeMediaCodecWrapper,
|
||||
/* codecName= */ "org.webrtc.testdecoder", codecType, COLOR_FORMAT,
|
||||
useSurface ? mockEglBaseContext : null);
|
||||
}
|
||||
}
|
||||
|
||||
private EncodedImage createTestEncodedImage() {
|
||||
return EncodedImage.builder()
|
||||
.setBuffer(ByteBuffer.wrap(ENCODED_TEST_DATA))
|
||||
.setFrameType(FrameType.VideoFrameKey)
|
||||
.setCompleteFrame(true)
|
||||
.createEncodedImage();
|
||||
}
|
||||
|
||||
@Mock private EglBase.Context mockEglBaseContext;
|
||||
@Mock private SurfaceTextureHelper mockSurfaceTextureHelper;
|
||||
@Mock private VideoDecoder.Callback mockDecoderCallback;
|
||||
private FakeMediaCodecWrapper fakeMediaCodecWrapper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
MediaFormat outputFormat = new MediaFormat();
|
||||
// TODO(sakal): Add more details to output format as needed.
|
||||
fakeMediaCodecWrapper = spy(new FakeMediaCodecWrapper(outputFormat));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit() {
|
||||
// Set-up.
|
||||
HardwareVideoDecoder decoder =
|
||||
new TestDecoderBuilder().setCodecType(VideoCodecType.VP8).build();
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback))
|
||||
.isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.EXECUTING_RUNNING);
|
||||
|
||||
MediaFormat mediaFormat = fakeMediaCodecWrapper.getConfiguredFormat();
|
||||
assertThat(mediaFormat).isNotNull();
|
||||
assertThat(mediaFormat.getInteger(MediaFormat.KEY_WIDTH))
|
||||
.isEqualTo(TEST_DECODER_SETTINGS.width);
|
||||
assertThat(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT))
|
||||
.isEqualTo(TEST_DECODER_SETTINGS.height);
|
||||
assertThat(mediaFormat.getString(MediaFormat.KEY_MIME))
|
||||
.isEqualTo(VideoCodecType.VP8.mimeType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelease() {
|
||||
// Set-up.
|
||||
HardwareVideoDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.RELEASED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseMultipleTimes() {
|
||||
// Set-up.
|
||||
HardwareVideoDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
assertThat(decoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.RELEASED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeQueuesData() {
|
||||
// Set-up.
|
||||
HardwareVideoDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.decode(createTestEncodedImage(),
|
||||
new DecodeInfo(/* isMissingFrames= */ false, /* renderTimeMs= */ 0)))
|
||||
.isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
ArgumentCaptor<Integer> offsetCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
ArgumentCaptor<Integer> sizeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(fakeMediaCodecWrapper)
|
||||
.queueInputBuffer(indexCaptor.capture(), offsetCaptor.capture(), sizeCaptor.capture(),
|
||||
/* presentationTimeUs= */ anyLong(),
|
||||
/* flags= */ eq(0));
|
||||
|
||||
ByteBuffer inputBuffer = fakeMediaCodecWrapper.getInputBuffers()[indexCaptor.getValue()];
|
||||
CodecTestHelper.assertEqualContents(
|
||||
ENCODED_TEST_DATA, inputBuffer, offsetCaptor.getValue(), sizeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeliversOutputByteBuffers() throws InterruptedException {
|
||||
final byte[] testOutputData = CodecTestHelper.generateRandomData(
|
||||
TEST_DECODER_SETTINGS.width * TEST_DECODER_SETTINGS.height * 3 / 2);
|
||||
final I420Buffer expectedDeliveredBuffer = CodecTestHelper.wrapI420(
|
||||
TEST_DECODER_SETTINGS.width, TEST_DECODER_SETTINGS.height, testOutputData);
|
||||
|
||||
// Set-up.
|
||||
TestDecoder decoder = new TestDecoderBuilder().setUseSurface(/* useSurface = */ false).build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
decoder.decode(createTestEncodedImage(),
|
||||
new DecodeInfo(/* isMissingFrames= */ false, /* renderTimeMs= */ 0));
|
||||
fakeMediaCodecWrapper.addOutputData(
|
||||
testOutputData, /* presentationTimestampUs= */ 0, /* flags= */ 0);
|
||||
|
||||
// Test.
|
||||
decoder.waitDeliverDecodedFrame();
|
||||
|
||||
// Verify.
|
||||
ArgumentCaptor<VideoFrame> videoFrameCaptor = ArgumentCaptor.forClass(VideoFrame.class);
|
||||
verify(mockDecoderCallback)
|
||||
.onDecodedFrame(videoFrameCaptor.capture(),
|
||||
/* decodeTimeMs= */ any(Integer.class),
|
||||
/* qp= */ any());
|
||||
|
||||
VideoFrame videoFrame = videoFrameCaptor.getValue();
|
||||
assertThat(videoFrame).isNotNull();
|
||||
assertThat(videoFrame.getRotatedWidth()).isEqualTo(TEST_DECODER_SETTINGS.width);
|
||||
assertThat(videoFrame.getRotatedHeight()).isEqualTo(TEST_DECODER_SETTINGS.height);
|
||||
assertThat(videoFrame.getRotation()).isEqualTo(0);
|
||||
I420Buffer deliveredBuffer = videoFrame.getBuffer().toI420();
|
||||
assertThat(deliveredBuffer.getDataY()).isEqualTo(expectedDeliveredBuffer.getDataY());
|
||||
assertThat(deliveredBuffer.getDataU()).isEqualTo(expectedDeliveredBuffer.getDataU());
|
||||
assertThat(deliveredBuffer.getDataV()).isEqualTo(expectedDeliveredBuffer.getDataV());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRendersOutputTexture() throws InterruptedException {
|
||||
// Set-up.
|
||||
TestDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
decoder.decode(createTestEncodedImage(),
|
||||
new DecodeInfo(/* isMissingFrames= */ false, /* renderTimeMs= */ 0));
|
||||
int bufferIndex =
|
||||
fakeMediaCodecWrapper.addOutputTexture(/* presentationTimestampUs= */ 0, /* flags= */ 0);
|
||||
|
||||
// Test.
|
||||
decoder.waitDeliverDecodedFrame();
|
||||
|
||||
// Verify.
|
||||
verify(fakeMediaCodecWrapper).releaseOutputBuffer(bufferIndex, /* render= */ true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("TODO(webrtc:9128): Fix")
|
||||
public void testSurfaceTextureStall_FramesDropped() throws InterruptedException {
|
||||
final int numFrames = 10;
|
||||
// Maximum number of frame the decoder can keep queued on the output side.
|
||||
final int maxQueuedBuffers = 3;
|
||||
|
||||
// Set-up.
|
||||
TestDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
|
||||
// Test.
|
||||
int[] bufferIndices = new int[numFrames];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
decoder.decode(createTestEncodedImage(),
|
||||
new DecodeInfo(/* isMissingFrames= */ false, /* renderTimeMs= */ 0));
|
||||
bufferIndices[i] =
|
||||
fakeMediaCodecWrapper.addOutputTexture(/* presentationTimestampUs= */ 0, /* flags= */ 0);
|
||||
decoder.waitDeliverDecodedFrame();
|
||||
}
|
||||
|
||||
// Verify.
|
||||
InOrder releaseOrder = inOrder(fakeMediaCodecWrapper);
|
||||
releaseOrder.verify(fakeMediaCodecWrapper)
|
||||
.releaseOutputBuffer(bufferIndices[0], /* render= */ true);
|
||||
for (int i = 1; i < numFrames - maxQueuedBuffers; i++) {
|
||||
releaseOrder.verify(fakeMediaCodecWrapper)
|
||||
.releaseOutputBuffer(bufferIndices[i], /* render= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeliversRenderedBuffers() throws InterruptedException {
|
||||
// Set-up.
|
||||
TestDecoder decoder = new TestDecoderBuilder().build();
|
||||
decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback);
|
||||
decoder.decode(createTestEncodedImage(),
|
||||
new DecodeInfo(/* isMissingFrames= */ false, /* renderTimeMs= */ 0));
|
||||
fakeMediaCodecWrapper.addOutputTexture(/* presentationTimestampUs= */ 0, /* flags= */ 0);
|
||||
|
||||
// Render the output buffer.
|
||||
decoder.waitDeliverDecodedFrame();
|
||||
|
||||
ArgumentCaptor<VideoSink> videoSinkCaptor = ArgumentCaptor.forClass(VideoSink.class);
|
||||
verify(mockSurfaceTextureHelper).startListening(videoSinkCaptor.capture());
|
||||
|
||||
// Test.
|
||||
Runnable releaseCallback = mock(Runnable.class);
|
||||
VideoFrame.TextureBuffer outputTextureBuffer =
|
||||
new TextureBufferImpl(TEST_DECODER_SETTINGS.width, TEST_DECODER_SETTINGS.height, Type.OES,
|
||||
/* id= */ 0,
|
||||
/* transformMatrix= */ new Matrix(),
|
||||
/* toI420Handler= */ new Handler(), new YuvConverter(), releaseCallback);
|
||||
VideoFrame outputVideoFrame =
|
||||
new VideoFrame(outputTextureBuffer, /* rotation= */ 0, /* timestampNs= */ 0);
|
||||
videoSinkCaptor.getValue().onFrame(outputVideoFrame);
|
||||
outputVideoFrame.release();
|
||||
|
||||
// Verify.
|
||||
ArgumentCaptor<VideoFrame> videoFrameCaptor = ArgumentCaptor.forClass(VideoFrame.class);
|
||||
verify(mockDecoderCallback)
|
||||
.onDecodedFrame(videoFrameCaptor.capture(),
|
||||
/* decodeTimeMs= */ any(Integer.class),
|
||||
/* qp= */ any());
|
||||
|
||||
VideoFrame videoFrame = videoFrameCaptor.getValue();
|
||||
assertThat(videoFrame).isNotNull();
|
||||
assertThat(videoFrame.getBuffer()).isEqualTo(outputTextureBuffer);
|
||||
|
||||
verify(releaseCallback).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConfigureExceptionTriggerSWFallback() {
|
||||
// Set-up.
|
||||
doThrow(new IllegalStateException("Fake error"))
|
||||
.when(fakeMediaCodecWrapper)
|
||||
.configure(any(), any(), any(), anyInt());
|
||||
|
||||
HardwareVideoDecoder decoder = new TestDecoderBuilder().build();
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback))
|
||||
.isEqualTo(VideoCodecStatus.FALLBACK_SOFTWARE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartExceptionTriggerSWFallback() {
|
||||
// Set-up.
|
||||
doThrow(new IllegalStateException("Fake error")).when(fakeMediaCodecWrapper).start();
|
||||
|
||||
HardwareVideoDecoder decoder = new TestDecoderBuilder().build();
|
||||
|
||||
// Test.
|
||||
assertThat(decoder.initDecode(TEST_DECODER_SETTINGS, mockDecoderCallback))
|
||||
.isEqualTo(VideoCodecStatus.FALLBACK_SOFTWARE);
|
||||
}
|
||||
}
|
||||
271
sdk/android/tests/src/org/webrtc/HardwareVideoEncoderTest.java
Normal file
271
sdk/android/tests/src/org/webrtc/HardwareVideoEncoderTest.java
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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 static com.google.common.truth.Truth.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaFormat;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.chromium.testing.local.LocalRobolectricTestRunner;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowSystemClock;
|
||||
import org.webrtc.EglBase.Context;
|
||||
import org.webrtc.EncodedImage;
|
||||
import org.webrtc.EncodedImage.FrameType;
|
||||
import org.webrtc.FakeMediaCodecWrapper.State;
|
||||
import org.webrtc.VideoCodecStatus;
|
||||
import org.webrtc.VideoEncoder;
|
||||
import org.webrtc.VideoEncoder.CodecSpecificInfo;
|
||||
import org.webrtc.VideoEncoder.EncodeInfo;
|
||||
import org.webrtc.VideoEncoder.Settings;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.webrtc.VideoFrame.Buffer;
|
||||
import org.webrtc.VideoFrame.I420Buffer;
|
||||
|
||||
@RunWith(LocalRobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE)
|
||||
public class HardwareVideoEncoderTest {
|
||||
private static final VideoEncoder.Settings TEST_ENCODER_SETTINGS = new Settings(
|
||||
/* numberOfCores= */ 1,
|
||||
/* width= */ 640,
|
||||
/* height= */ 480,
|
||||
/* startBitrate= */ 10000,
|
||||
/* maxFramerate= */ 30,
|
||||
/* automaticResizeOn= */ true);
|
||||
private static final long POLL_DELAY_MS = 10;
|
||||
private static final long DELIVER_ENCODED_IMAGE_DELAY_MS = 10;
|
||||
|
||||
private static class TestEncoder extends HardwareVideoEncoder {
|
||||
private final Object deliverEncodedImageLock = new Object();
|
||||
private boolean deliverEncodedImageDone = true;
|
||||
|
||||
TestEncoder(MediaCodecWrapperFactory mediaCodecWrapperFactory, String codecName,
|
||||
VideoCodecType codecType, Integer surfaceColorFormat, Integer yuvColorFormat,
|
||||
Map<String, String> params, int keyFrameIntervalSec, int forceKeyFrameIntervalMs,
|
||||
BitrateAdjuster bitrateAdjuster, EglBase14.Context sharedContext) {
|
||||
super(mediaCodecWrapperFactory, codecName, codecType, surfaceColorFormat, yuvColorFormat,
|
||||
params, keyFrameIntervalSec, forceKeyFrameIntervalMs, bitrateAdjuster, sharedContext);
|
||||
}
|
||||
|
||||
public void waitDeliverEncodedImage() throws InterruptedException {
|
||||
synchronized (deliverEncodedImageLock) {
|
||||
deliverEncodedImageDone = false;
|
||||
deliverEncodedImageLock.notifyAll();
|
||||
while (!deliverEncodedImageDone) {
|
||||
deliverEncodedImageLock.wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("WaitNotInLoop") // This method is called inside a loop.
|
||||
@Override
|
||||
protected void deliverEncodedImage() {
|
||||
synchronized (deliverEncodedImageLock) {
|
||||
if (deliverEncodedImageDone) {
|
||||
try {
|
||||
deliverEncodedImageLock.wait(DELIVER_ENCODED_IMAGE_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (deliverEncodedImageDone) {
|
||||
return;
|
||||
}
|
||||
super.deliverEncodedImage();
|
||||
deliverEncodedImageDone = true;
|
||||
deliverEncodedImageLock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillInputBuffer(ByteBuffer buffer, Buffer videoFrameBuffer) {
|
||||
I420Buffer i420Buffer = videoFrameBuffer.toI420();
|
||||
buffer.put(i420Buffer.getDataY());
|
||||
buffer.put(i420Buffer.getDataU());
|
||||
buffer.put(i420Buffer.getDataV());
|
||||
buffer.flip();
|
||||
i420Buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestEncoderBuilder {
|
||||
private VideoCodecType codecType = VideoCodecType.VP8;
|
||||
|
||||
public TestEncoderBuilder setCodecType(VideoCodecType codecType) {
|
||||
this.codecType = codecType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestEncoder build() {
|
||||
return new TestEncoder((String name)
|
||||
-> fakeMediaCodecWrapper,
|
||||
"org.webrtc.testencoder", codecType,
|
||||
/* surfaceColorFormat= */ null,
|
||||
/* yuvColorFormat= */ MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
|
||||
/* params= */ new HashMap<>(),
|
||||
/* keyFrameIntervalSec= */ 0,
|
||||
/* forceKeyFrameIntervalMs= */ 0,
|
||||
/* bitrateAdjuster= */ new BaseBitrateAdjuster(),
|
||||
/* sharedContext= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Mock VideoEncoder.Callback mockEncoderCallback;
|
||||
private FakeMediaCodecWrapper fakeMediaCodecWrapper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
MediaFormat outputFormat = new MediaFormat();
|
||||
// TODO(sakal): Add more details to output format as needed.
|
||||
fakeMediaCodecWrapper = spy(new FakeMediaCodecWrapper(outputFormat));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit() {
|
||||
// Set-up.
|
||||
HardwareVideoEncoder encoder =
|
||||
new TestEncoderBuilder().setCodecType(VideoCodecType.VP8).build();
|
||||
|
||||
// Test.
|
||||
assertThat(encoder.initEncode(TEST_ENCODER_SETTINGS, mockEncoderCallback))
|
||||
.isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.EXECUTING_RUNNING);
|
||||
|
||||
MediaFormat mediaFormat = fakeMediaCodecWrapper.getConfiguredFormat();
|
||||
assertThat(mediaFormat).isNotNull();
|
||||
assertThat(mediaFormat.getInteger(MediaFormat.KEY_WIDTH))
|
||||
.isEqualTo(TEST_ENCODER_SETTINGS.width);
|
||||
assertThat(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT))
|
||||
.isEqualTo(TEST_ENCODER_SETTINGS.height);
|
||||
assertThat(mediaFormat.getString(MediaFormat.KEY_MIME))
|
||||
.isEqualTo(VideoCodecType.VP8.mimeType());
|
||||
|
||||
assertThat(fakeMediaCodecWrapper.getConfiguredFlags())
|
||||
.isEqualTo(MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeByteBuffer() {
|
||||
// Set-up.
|
||||
HardwareVideoEncoder encoder = new TestEncoderBuilder().build();
|
||||
encoder.initEncode(TEST_ENCODER_SETTINGS, mockEncoderCallback);
|
||||
|
||||
// Test.
|
||||
byte[] i420 = CodecTestHelper.generateRandomData(
|
||||
TEST_ENCODER_SETTINGS.width * TEST_ENCODER_SETTINGS.height * 3 / 2);
|
||||
final VideoFrame.I420Buffer testBuffer =
|
||||
CodecTestHelper.wrapI420(TEST_ENCODER_SETTINGS.width, TEST_ENCODER_SETTINGS.height, i420);
|
||||
final VideoFrame testFrame =
|
||||
new VideoFrame(testBuffer, /* rotation= */ 0, /* timestampNs= */ 0);
|
||||
assertThat(encoder.encode(testFrame, new EncodeInfo(new FrameType[] {FrameType.VideoFrameKey})))
|
||||
.isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
ArgumentCaptor<Integer> offsetCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
ArgumentCaptor<Integer> sizeCaptor = ArgumentCaptor.forClass(Integer.class);
|
||||
verify(fakeMediaCodecWrapper)
|
||||
.queueInputBuffer(indexCaptor.capture(), offsetCaptor.capture(), sizeCaptor.capture(),
|
||||
anyLong(), anyInt());
|
||||
ByteBuffer buffer = fakeMediaCodecWrapper.getInputBuffers()[indexCaptor.getValue()];
|
||||
CodecTestHelper.assertEqualContents(
|
||||
i420, buffer, offsetCaptor.getValue(), sizeCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeliversOutputData() throws InterruptedException {
|
||||
final int outputDataLength = 100;
|
||||
|
||||
// Set-up.
|
||||
TestEncoder encoder = new TestEncoderBuilder().build();
|
||||
encoder.initEncode(TEST_ENCODER_SETTINGS, mockEncoderCallback);
|
||||
byte[] i420 = CodecTestHelper.generateRandomData(
|
||||
TEST_ENCODER_SETTINGS.width * TEST_ENCODER_SETTINGS.height * 3 / 2);
|
||||
final VideoFrame.I420Buffer testBuffer =
|
||||
CodecTestHelper.wrapI420(TEST_ENCODER_SETTINGS.width, TEST_ENCODER_SETTINGS.height, i420);
|
||||
final VideoFrame testFrame =
|
||||
new VideoFrame(testBuffer, /* rotation= */ 0, /* timestampNs= */ 42);
|
||||
encoder.encode(testFrame, new EncodeInfo(new FrameType[] {FrameType.VideoFrameKey}));
|
||||
|
||||
// Test.
|
||||
byte[] outputData = CodecTestHelper.generateRandomData(outputDataLength);
|
||||
fakeMediaCodecWrapper.addOutputData(outputData,
|
||||
/* presentationTimestampUs= */ 0,
|
||||
/* flags= */ MediaCodec.BUFFER_FLAG_SYNC_FRAME);
|
||||
|
||||
encoder.waitDeliverEncodedImage();
|
||||
|
||||
// Verify.
|
||||
ArgumentCaptor<EncodedImage> videoFrameCaptor = ArgumentCaptor.forClass(EncodedImage.class);
|
||||
verify(mockEncoderCallback)
|
||||
.onEncodedFrame(videoFrameCaptor.capture(), any(CodecSpecificInfo.class));
|
||||
|
||||
EncodedImage videoFrame = videoFrameCaptor.getValue();
|
||||
assertThat(videoFrame).isNotNull();
|
||||
assertThat(videoFrame.encodedWidth).isEqualTo(TEST_ENCODER_SETTINGS.width);
|
||||
assertThat(videoFrame.encodedHeight).isEqualTo(TEST_ENCODER_SETTINGS.height);
|
||||
assertThat(videoFrame.rotation).isEqualTo(0);
|
||||
assertThat(videoFrame.captureTimeNs).isEqualTo(42);
|
||||
assertThat(videoFrame.completeFrame).isTrue();
|
||||
assertThat(videoFrame.frameType).isEqualTo(FrameType.VideoFrameKey);
|
||||
CodecTestHelper.assertEqualContents(
|
||||
outputData, videoFrame.buffer, /* offset= */ 0, videoFrame.buffer.capacity());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelease() {
|
||||
// Set-up.
|
||||
HardwareVideoEncoder encoder = new TestEncoderBuilder().build();
|
||||
encoder.initEncode(TEST_ENCODER_SETTINGS, mockEncoderCallback);
|
||||
|
||||
// Test.
|
||||
assertThat(encoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.RELEASED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReleaseMultipleTimes() {
|
||||
// Set-up.
|
||||
HardwareVideoEncoder encoder = new TestEncoderBuilder().build();
|
||||
encoder.initEncode(TEST_ENCODER_SETTINGS, mockEncoderCallback);
|
||||
|
||||
// Test.
|
||||
assertThat(encoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
assertThat(encoder.release()).isEqualTo(VideoCodecStatus.OK);
|
||||
|
||||
// Verify.
|
||||
assertThat(fakeMediaCodecWrapper.getState()).isEqualTo(State.RELEASED);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user