webrtc_m130/sdk/android/instrumentationtests/src/org/webrtc/AndroidVideoDecoderInstrumentationTest.java
Jiwon Jung 5a79d28eba Require 16x16 alignment when using HardwareVideoEncoder for encoding.
It seems the Android CTS tests only verify that 16x16 aligned resolutions
are supported.

This change checks the validity of input frame's size when initialing
or encoding processes are about to start using H/W MediaCodec.

This change has additional APIs to retrieve
|requested_resolution_alignment| and |apply_alignment_to_all_simulcast_layers|
from JAVA VideoEncoder class and its inherited classes. HardwareVideoEncoder
using MediaCodec has values of 16 and true for above variables.

Bug: webrtc:13089
Change-Id: I0c4ebf94eb36da29c2e384a3edf85b82e779b7f9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/229460
Reviewed-by: Sergey Silkin <ssilkin@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Commit-Queue: Sergey Silkin <ssilkin@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35169}
2021-10-08 13:15:12 +00:00

212 lines
8.5 KiB
Java

/*
* 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 androidx.annotation.Nullable;
import androidx.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 */,
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
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 */,
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()));
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(
getAlignedNumber(TEST_FRAME_WIDTH, HardwareVideoEncoderTest.getPixelAlignmentRequired()),
getAlignedNumber(
TEST_FRAME_HEIGHT, HardwareVideoEncoderTest.getPixelAlignmentRequired()));
// 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());
}
private static int getAlignedNumber(int number, int alignment) {
return (number / alignment) * alignment;
}
@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());
}
}