blob: 723012bf81406b456f08aa1200edb2a9b9ec8e4d [file] [log] [blame]
/*
* Copyright 2017 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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.support.annotation.Nullable;
import android.support.test.filters.SmallTest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.chromium.base.test.params.BaseJUnit4RunnerDelegate;
import org.chromium.base.test.params.ParameterAnnotations.ClassParameter;
import org.chromium.base.test.params.ParameterAnnotations.UseRunnerDelegate;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link AndroidVideoDecoder}. */
@RunWith(ParameterizedRunner.class)
@UseRunnerDelegate(BaseJUnit4RunnerDelegate.class)
public final class AndroidVideoDecoderInstrumentationTest {
@ClassParameter private static List<ParameterSet> CLASS_PARAMS = new ArrayList<>();
static {
CLASS_PARAMS.add(new ParameterSet()
.value(/* codecName= */ "VP8", false /* useEglContext */)
.name("VP8WithoutEglContext"));
CLASS_PARAMS.add(new ParameterSet()
.value(/* codecName= */ "VP8", true /* useEglContext */)
.name("VP8WithEglContext"));
CLASS_PARAMS.add(new ParameterSet()
.value(/* codecName= */ "H264", false /* useEglContext */)
.name("H264WithoutEglContext"));
CLASS_PARAMS.add(new ParameterSet()
.value(/* codecName= */ "H264", true /* useEglContext */)
.name("H264WithEglContext"));
}
private final VideoCodecInfo codecType;
private final boolean useEglContext;
public AndroidVideoDecoderInstrumentationTest(String codecName, boolean useEglContext) {
if (codecName.equals("H264")) {
this.codecType = H264Utils.DEFAULT_H264_BASELINE_PROFILE_CODEC;
} else {
this.codecType = new VideoCodecInfo(codecName, new HashMap<>());
}
this.useEglContext = useEglContext;
}
private static final String TAG = "AndroidVideoDecoderInstrumentationTest";
private static final int TEST_FRAME_COUNT = 10;
private static final int TEST_FRAME_WIDTH = 640;
private static final int TEST_FRAME_HEIGHT = 360;
private VideoFrame.I420Buffer[] TEST_FRAMES;
private static final boolean ENABLE_INTEL_VP8_ENCODER = true;
private static final boolean ENABLE_H264_HIGH_PROFILE = true;
private static final VideoEncoder.Settings ENCODER_SETTINGS =
new VideoEncoder.Settings(1 /* core */, TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT, 300 /* kbps */,
30 /* fps */, 1 /* numberOfSimulcastStreams */, true /* automaticResizeOn */,
/* capabilities= */ new VideoEncoder.Capabilities(false /* lossNotification */));
private static final int DECODE_TIMEOUT_MS = 1000;
private static final VideoDecoder.Settings SETTINGS =
new VideoDecoder.Settings(1 /* core */, TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT);
private static class MockDecodeCallback implements VideoDecoder.Callback {
private BlockingQueue<VideoFrame> frameQueue = new LinkedBlockingQueue<>();
@Override
public void onDecodedFrame(VideoFrame frame, Integer decodeTimeMs, Integer qp) {
assertNotNull(frame);
frameQueue.offer(frame);
}
public void assertFrameDecoded(EncodedImage testImage, VideoFrame.I420Buffer testBuffer) {
VideoFrame decodedFrame = poll();
VideoFrame.Buffer decodedBuffer = decodedFrame.getBuffer();
assertEquals(testImage.encodedWidth, decodedBuffer.getWidth());
assertEquals(testImage.encodedHeight, decodedBuffer.getHeight());
// TODO(sakal): Decoder looses the nanosecond precision. This is not a problem in practice
// because C++ EncodedImage stores the timestamp in milliseconds.
assertEquals(testImage.captureTimeNs / 1000, decodedFrame.getTimestampNs() / 1000);
assertEquals(testImage.rotation, decodedFrame.getRotation());
}
public VideoFrame poll() {
try {
VideoFrame frame = frameQueue.poll(DECODE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
assertNotNull("Timed out waiting for the frame to be decoded.", frame);
return frame;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private static VideoFrame.I420Buffer[] generateTestFrames() {
VideoFrame.I420Buffer[] result = new VideoFrame.I420Buffer[TEST_FRAME_COUNT];
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
result[i] = JavaI420Buffer.allocate(TEST_FRAME_WIDTH, TEST_FRAME_HEIGHT);
// TODO(sakal): Generate content for the test frames.
}
return result;
}
private final EncodedImage[] encodedTestFrames = new EncodedImage[TEST_FRAME_COUNT];
private EglBase14 eglBase;
private VideoDecoderFactory createDecoderFactory(EglBase.Context eglContext) {
return new HardwareVideoDecoderFactory(eglContext);
}
private @Nullable VideoDecoder createDecoder() {
VideoDecoderFactory factory =
createDecoderFactory(useEglContext ? eglBase.getEglBaseContext() : null);
return factory.createDecoder(codecType);
}
private void encodeTestFrames() {
VideoEncoderFactory encoderFactory = new HardwareVideoEncoderFactory(
eglBase.getEglBaseContext(), ENABLE_INTEL_VP8_ENCODER, ENABLE_H264_HIGH_PROFILE);
VideoEncoder encoder = encoderFactory.createEncoder(codecType);
HardwareVideoEncoderTest.MockEncoderCallback encodeCallback =
new HardwareVideoEncoderTest.MockEncoderCallback();
assertEquals(VideoCodecStatus.OK, encoder.initEncode(ENCODER_SETTINGS, encodeCallback));
long lastTimestampNs = 0;
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
lastTimestampNs += TimeUnit.SECONDS.toNanos(1) / ENCODER_SETTINGS.maxFramerate;
VideoEncoder.EncodeInfo info = new VideoEncoder.EncodeInfo(
new EncodedImage.FrameType[] {EncodedImage.FrameType.VideoFrameDelta});
HardwareVideoEncoderTest.testEncodeFrame(
encoder, new VideoFrame(TEST_FRAMES[i], 0 /* rotation */, lastTimestampNs), info);
encodedTestFrames[i] = encodeCallback.poll();
}
assertEquals(VideoCodecStatus.OK, encoder.release());
}
@Before
public void setUp() {
NativeLibrary.initialize(new NativeLibrary.DefaultLoader(), TestConstants.NATIVE_LIBRARY);
TEST_FRAMES = generateTestFrames();
eglBase = EglBase.createEgl14(EglBase.CONFIG_PLAIN);
eglBase.createDummyPbufferSurface();
eglBase.makeCurrent();
encodeTestFrames();
}
@After
public void tearDown() {
eglBase.release();
}
@Test
@SmallTest
public void testInitialize() {
VideoDecoder decoder = createDecoder();
assertEquals(VideoCodecStatus.OK, decoder.initDecode(SETTINGS, null /* decodeCallback */));
assertEquals(VideoCodecStatus.OK, decoder.release());
}
@Test
@SmallTest
public void testDecode() {
VideoDecoder decoder = createDecoder();
MockDecodeCallback callback = new MockDecodeCallback();
assertEquals(VideoCodecStatus.OK, decoder.initDecode(SETTINGS, callback));
for (int i = 0; i < TEST_FRAME_COUNT; i++) {
assertEquals(VideoCodecStatus.OK,
decoder.decode(encodedTestFrames[i],
new VideoDecoder.DecodeInfo(false /* isMissingFrames */, 0 /* renderTimeMs */)));
callback.assertFrameDecoded(encodedTestFrames[i], TEST_FRAMES[i]);
}
assertEquals(VideoCodecStatus.OK, decoder.release());
}
}