blob: c416efe93911e5a6de080f5c03564a7ae9d88e4f [file] [edit]
/*
* Copyright 2026 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.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import androidx.test.runner.AndroidJUnit4;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
@Config(manifest = Config.NONE)
public class VideoDecoderWrapperTest {
private static final long FAKE_NATIVE_DECODER = 0x12345678L;
private static final long TIMEOUT_MS = 5000;
@Mock private VideoDecoderWrapper.Natives mockNatives;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
VideoDecoderWrapperJni.setInstanceForTesting(mockNatives);
}
@After
public void tearDown() {
VideoDecoderWrapperJni.setInstanceForTesting(null);
}
@Test
public void testCallbackForwardsFramesToNative() {
VideoDecoderWrapper.NativeDecoderCallback callback =
VideoDecoderWrapper.createDecoderCallback(FAKE_NATIVE_DECODER);
callback.onDecodedFrame(/* frame= */ null, /* decodeTimeMs= */ 1, /* qp= */ 2);
verify(mockNatives).onDecodedFrame(eq(FAKE_NATIVE_DECODER), isNull(), eq(1), eq(2));
}
@Test
public void testCallbackDropsFramesAfterInvalidate() {
VideoDecoderWrapper.NativeDecoderCallback callback =
VideoDecoderWrapper.createDecoderCallback(FAKE_NATIVE_DECODER);
callback.invalidate();
callback.onDecodedFrame(/* frame= */ null, /* decodeTimeMs= */ 1, /* qp= */ 2);
verifyNoInteractions(mockNatives);
}
@Test
public void testInvalidateWaitsForOngoingCallback() throws InterruptedException {
final CountDownLatch nativeEntered = new CountDownLatch(1);
final CountDownLatch allowNativeReturn = new CountDownLatch(1);
doAnswer(invocation -> {
nativeEntered.countDown();
allowNativeReturn.await();
return null;
})
.when(mockNatives)
.onDecodedFrame(anyLong(), isNull(), eq(1), eq(2));
VideoDecoderWrapper.NativeDecoderCallback callback =
VideoDecoderWrapper.createDecoderCallback(FAKE_NATIVE_DECODER);
Thread deliveryThread = new Thread(
() -> callback.onDecodedFrame(/* frame= */ null, /* decodeTimeMs= */ 1, /* qp= */ 2));
deliveryThread.start();
assertThat(nativeEntered.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
final CountDownLatch invalidated = new CountDownLatch(1);
Thread invalidateThread = new Thread(() -> {
callback.invalidate();
invalidated.countDown();
});
invalidateThread.start();
// invalidate() must not return while the native callback is running.
assertThat(invalidated.await(100, TimeUnit.MILLISECONDS)).isFalse();
allowNativeReturn.countDown();
assertThat(invalidated.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
deliveryThread.join(TIMEOUT_MS);
invalidateThread.join(TIMEOUT_MS);
}
}