diff options
Diffstat (limited to 'tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java')
-rw-r--r-- | tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java | 149 |
1 files changed, 107 insertions, 42 deletions
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java index 34387673dc3..018b284d9f7 100644 --- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java +++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeOnlyTest.java @@ -35,6 +35,7 @@ import android.media.cts.MediaTestBase; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.platform.test.annotations.AppModeFull; @@ -54,16 +55,19 @@ import org.junit.runner.RunWith; import java.io.IOException; import java.nio.ByteBuffer; import java.time.Duration; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @MediaHeavyPresubmitTest @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders") -// BUFFER_FLAG_DECODE_ONLY was added in Android U. @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") @RunWith(AndroidJUnit4.class) @ApiTest(apis = {"android.media.MediaCodec#BUFFER_FLAG_DECODE_ONLY"}) @@ -170,7 +174,6 @@ public class DecodeOnlyTest extends MediaTestBase { testNonTunneledTrickPlay(VP9_VIDEO); } - private void testNonTunneledTrickPlay(String fileName) throws Exception { Preconditions.assertTestFileExists(MEDIA_DIR_STRING + fileName); // create the video extractor @@ -184,7 +187,6 @@ public class DecodeOnlyTest extends MediaTestBase { MediaFormat videoFormat = videoExtractor.getTrackFormat(videoTrackIndex); String mime = videoFormat.getString(MediaFormat.KEY_MIME); MediaCodec videoCodec = MediaCodec.createDecoderByType(mime); - videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); AtomicBoolean done = new AtomicBoolean(false); List<Long> expectedPresentationTimes = new ArrayList<>(); @@ -250,6 +252,7 @@ public class DecodeOnlyTest extends MediaTestBase { } }); + videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); // start the video the codec with track selected videoCodec.start(); @@ -289,7 +292,6 @@ public class DecodeOnlyTest extends MediaTestBase { codecName != null); videoFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId); MediaCodec videoCodec = MediaCodec.createByCodecName(codecName); - videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); // create the audio extractor MediaExtractor audioExtractor = createMediaExtractor(fileName); @@ -302,7 +304,6 @@ public class DecodeOnlyTest extends MediaTestBase { MediaFormat audioFormat = audioExtractor.getTrackFormat(audioTrackIndex); String mime = audioFormat.getString(MediaFormat.KEY_MIME); MediaCodec audioCodec = MediaCodec.createDecoderByType(mime); - audioCodec.configure(audioFormat, null, null, 0); // audio track used by audio codec AudioTrack audioTrack = createAudioTrack(audioFormat, audioSessionId); @@ -354,9 +355,15 @@ public class DecodeOnlyTest extends MediaTestBase { } }); - + videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); AtomicBoolean done = new AtomicBoolean(false); - audioCodec.setCallback(new AudioCallback(audioCodec, audioExtractor, audioTrack, done)); + // Since data is written to AudioTrack in a blocking manner, run it in a separate thread to + // not block other operations. + HandlerThread audioThread = new HandlerThread("audioThread"); + audioThread.start(); + audioCodec.setCallback(new AudioCallback(audioCodec, audioExtractor, audioTrack, done), + new Handler(audioThread.getLooper())); + audioCodec.configure(audioFormat, null, null, 0); List<Long> renderedPresentationTimes = new ArrayList<>(); @@ -424,7 +431,6 @@ public class DecodeOnlyTest extends MediaTestBase { codecName != null); videoFormat.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, audioSessionId); MediaCodec videoCodec = MediaCodec.createByCodecName(codecName); - videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); // create the audio extractor MediaExtractor audioExtractor = createMediaExtractor(fileName); @@ -437,50 +443,77 @@ public class DecodeOnlyTest extends MediaTestBase { MediaFormat audioFormat = audioExtractor.getTrackFormat(audioTrackIndex); String mime = audioFormat.getString(MediaFormat.KEY_MIME); MediaCodec audioCodec = MediaCodec.createDecoderByType(mime); - audioCodec.configure(audioFormat, null, null, 0); // audio track used by audio codec AudioTrack audioTrack = createAudioTrack(audioFormat, audioSessionId); // Frames at 2s of each file are not key frame AtomicLong seekTime = new AtomicLong(2000 * 1000); - boolean isPeeking = setKeyTunnelPeek(videoCodec, initialPeek ? 1 : 0); videoExtractor.seekTo(seekTime.get(), MediaExtractor.SEEK_TO_PREVIOUS_SYNC); audioExtractor.seekTo(seekTime.get(), MediaExtractor.SEEK_TO_PREVIOUS_SYNC); List<Long> expectedPresentationTimes = new ArrayList<>(); - AtomicBoolean done = new AtomicBoolean(false); + // Setting it to true so that buffers are not queued in the beginning. + AtomicBoolean done = new AtomicBoolean(true); AtomicBoolean hasDecodeOnlyFrames = new AtomicBoolean(false); - videoCodec.setCallback(new MediaCodec.Callback() { - // before queueing a frame, check if it is the last frame and set the EOS flag - // to the frame if that's the case. If the frame is to be only decoded - // (any frame before the seek timestamp), then set the DECODE_ONLY flag to the frame. - // Only frames not tagged with EOS or DECODE_ONLY are expected to be rendered and added - // to expectedPresentationTimes + class VideoAsyncHandler extends MediaCodec.Callback { + private final Queue<Integer> mAvailableInputIndices = new ArrayDeque<>(); + private final Lock mLock = new ReentrantLock(); + + private void queueInput(MediaCodec codec, int index) { + ByteBuffer inputBuffer = codec.getInputBuffer(index); + int sampleSize = videoExtractor.readSampleData(inputBuffer, 0); + long presentationTime = videoExtractor.getSampleTime(); + int flags = videoExtractor.getSampleFlags(); + if (sampleSize < 0) { + flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; + sampleSize = 0; + } else if (presentationTime < seekTime.get()) { + flags = MediaCodec.BUFFER_FLAG_DECODE_ONLY; + hasDecodeOnlyFrames.set(true); + } else { + expectedPresentationTimes.add(presentationTime); + } + codec.queueInputBuffer(index, 0, sampleSize, presentationTime, flags); + videoExtractor.advance(); + } + @Override public void onInputBufferAvailable(MediaCodec codec, int index) { - if (!done.get()) { - ByteBuffer inputBuffer = videoCodec.getInputBuffer(index); - int sampleSize = videoExtractor.readSampleData(inputBuffer, 0); - long presentationTime = videoExtractor.getSampleTime(); - int flags = videoExtractor.getSampleFlags(); - if (sampleSize < 0) { - flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM; - sampleSize = 0; - } else if (presentationTime < seekTime.get()) { - flags = MediaCodec.BUFFER_FLAG_DECODE_ONLY; - hasDecodeOnlyFrames.set(true); + mLock.lock(); + try { + if (!done.get()) { + queueInput(codec, index); } else { - expectedPresentationTimes.add(presentationTime); + mAvailableInputIndices.offer(index); } - videoCodec.queueInputBuffer(index, 0, sampleSize, presentationTime, flags); - videoExtractor.advance(); + } finally { + mLock.unlock(); + } + } + + public void startProcess() { + mLock.lock(); + try { + done.set(false); + while (!mAvailableInputIndices.isEmpty()) { + queueInput(videoCodec, mAvailableInputIndices.poll()); + } + } finally { + mLock.unlock(); + } + } + + public void clearBufferQueue() { + mLock.lock(); + try { + mAvailableInputIndices.clear(); + } finally { + mLock.unlock(); } } - // nothing to do here - in tunneled mode, the frames are rendered directly by the - // hardware, they are not sent back to the codec for extra processing @Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { @@ -496,9 +529,18 @@ public class DecodeOnlyTest extends MediaTestBase { public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { } - }); + } - audioCodec.setCallback(new AudioCallback(audioCodec, audioExtractor, audioTrack, done)); + VideoAsyncHandler videoAsyncHandler = new VideoAsyncHandler(); + videoCodec.setCallback(videoAsyncHandler); + videoCodec.configure(videoFormat, getActivity().getSurfaceHolder().getSurface(), null, 0); + // Since data is written to AudioTrack in a blocking manner, run it in a separate thread to + // not block other operations. + HandlerThread audioThread = new HandlerThread("audioThread"); + audioThread.start(); + audioCodec.setCallback(new AudioCallback(audioCodec, audioExtractor, audioTrack, done), + new Handler(audioThread.getLooper())); + audioCodec.configure(audioFormat, null, null, 0); List<Long> renderedPresentationTimes = new ArrayList<>(); // play for 500ms @@ -518,6 +560,10 @@ public class DecodeOnlyTest extends MediaTestBase { // start media playback videoCodec.start(); audioCodec.start(); + // Set peeking value after MediaCodec.start() is called. + boolean isPeeking = setKeyTunnelPeek(videoCodec, initialPeek ? 1 : 0); + // start to queue video frames + videoAsyncHandler.startProcess(); // When video codecs are started, large chunks of contiguous physical memory need to be // allocated, which, on low-RAM devices, can trigger high CPU usage for moving memory @@ -564,8 +610,7 @@ public class DecodeOnlyTest extends MediaTestBase { Thread.sleep(500); videoCodec.flush(); audioCodec.flush(); - // Set peek on when it was off, and set it off when it was on. - isPeeking = setKeyTunnelPeek(videoCodec, isPeeking ? 0 : 1); + videoAsyncHandler.clearBufferQueue(); // Frames at 7s of each file are not key frame, and there is non-zero key frame before it seekTime.set(7000 * 1000); @@ -587,12 +632,14 @@ public class DecodeOnlyTest extends MediaTestBase { }, new Handler(Looper.getMainLooper())); // Restart media playback - done.set(false); firstTunnelFrameReady.set(false); videoCodec.start(); audioCodec.start(); + // Set peek on when it was off, and set it off when it was on. + isPeeking = setKeyTunnelPeek(videoCodec, isPeeking ? 0 : 1); + videoAsyncHandler.startProcess(); sleepUntil(firstTunnelFrameReady::get, Duration.ofSeconds(firstFrameReadyTimeoutSeconds)); - assertTrue(String.format("onFirstTunnelFrameReady not called within %d milliseconds", + assertTrue(String.format("onFirstTunnelFrameReady not called within %d seconds", firstFrameReadyTimeoutSeconds), firstTunnelFrameReady.get()); // Sleep for 1s here to ensure that either (1) when peek is on, high-latency display @@ -735,9 +782,27 @@ public class DecodeOnlyTest extends MediaTestBase { byte[] audioArray = new byte[info.size]; outputBuffer.get(audioArray); outputBuffer.clear(); - mAudioTrack.write(ByteBuffer.wrap(audioArray), info.size, - AudioTrack.WRITE_BLOCKING, - info.presentationTimeUs * 1000); + ByteBuffer audioData = ByteBuffer.wrap(audioArray); + int writtenSize = 0; + while (true) { + int written = mAudioTrack.write(audioData, info.size - writtenSize, + AudioTrack.WRITE_BLOCKING, info.presentationTimeUs * 1000); + if (written >= 0) { + writtenSize += written; + if (writtenSize >= info.size) { + break; + } + // When audio track is not in playing state, the write operation does not + // block in WRITE_BLOCKING mode. And when audio track is full, the audio + // data can not be fully written. Must sleep here to wait for free spaces. + try { + Thread.sleep(50); + } catch (InterruptedException ignored) { + } + } else { + Assert.fail("AudioTrack write failure."); + } + } mAudioCodec.releaseOutputBuffer(index, false); } } |