
Bug: webrtc:9646 Change-Id: Iac14930653969eed4f7b2207149512bb3fb87cee Reviewed-on: https://webrtc-review.googlesource.com/96242 Reviewed-by: Sami Kalliomäki <sakal@webrtc.org> Commit-Queue: Rasmus Brandt <brandtr@webrtc.org> Cr-Commit-Position: refs/heads/master@{#24582}
273 lines
10 KiB
Java
273 lines
10 KiB
Java
/*
|
|
* 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,
|
|
/* numberOfSimulcastStreams= */ 1,
|
|
/* 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);
|
|
}
|
|
}
|