1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.mediav2.common.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertTrue; 21 import static org.junit.Assume.assumeNotNull; 22 23 import android.media.MediaCodec; 24 import android.media.MediaExtractor; 25 import android.media.MediaFormat; 26 import android.os.Build; 27 import android.util.Log; 28 29 import androidx.annotation.RequiresApi; 30 31 import org.junit.After; 32 33 import java.nio.ByteBuffer; 34 35 /** 36 * Wrapper class for trying and testing mediacodec decoder components in block model mode. 37 */ 38 @RequiresApi(api = Build.VERSION_CODES.R) 39 public class CodecDecoderBlockModelTestBase extends CodecDecoderTestBase { 40 private static final String LOG_TAG = CodecDecoderBlockModelTestBase.class.getSimpleName(); 41 42 protected final LinearBlockWrapper mLinearInputBlock = new LinearBlockWrapper(); 43 44 /** 45 * Wrapper class for {@link MediaCodec.LinearBlock} 46 */ 47 public static class LinearBlockWrapper { 48 private MediaCodec.LinearBlock mBlock; 49 private ByteBuffer mBuffer; 50 private int mOffset; 51 getBlock()52 public MediaCodec.LinearBlock getBlock() { 53 return mBlock; 54 } 55 getBuffer()56 public ByteBuffer getBuffer() { 57 return mBuffer; 58 } 59 getBufferCapacity()60 public int getBufferCapacity() { 61 return mBuffer == null ? 0 : mBuffer.capacity(); 62 } 63 getOffset()64 public int getOffset() { 65 return mOffset; 66 } 67 setOffset(int size)68 public void setOffset(int size) { 69 mOffset = size; 70 } 71 allocateBlock(String codec, int size)72 public void allocateBlock(String codec, int size) { 73 recycle(); 74 mBlock = MediaCodec.LinearBlock.obtain(size, new String[]{codec}); 75 assumeNotNull("failed to obtain LinearBlock for component " + codec + "\n", mBlock); 76 assertTrue("Blocks obtained through LinearBlock.obtain must be mappable" + "\n", 77 mBlock.isMappable()); 78 mBuffer = mBlock.map(); 79 mOffset = 0; 80 } 81 recycle()82 public void recycle() { 83 if (mBlock != null) { 84 mBlock.recycle(); 85 mBlock = null; 86 } 87 mBuffer = null; 88 mOffset = 0; 89 } 90 } 91 CodecDecoderBlockModelTestBase(String decoder, String mediaType, String testFile, String allTestParams)92 public CodecDecoderBlockModelTestBase(String decoder, String mediaType, String testFile, 93 String allTestParams) { 94 super(decoder, mediaType, testFile, allTestParams); 95 } 96 97 @After tearDownCodecDecoderBlockModelTestBase()98 public void tearDownCodecDecoderBlockModelTestBase() { 99 mLinearInputBlock.recycle(); 100 } 101 102 @Override configureCodec(MediaFormat format, boolean isAsyncUnUsed, boolean signalEOSWithLastFrame, boolean isEncoder)103 protected void configureCodec(MediaFormat format, boolean isAsyncUnUsed, 104 boolean signalEOSWithLastFrame, boolean isEncoder) { 105 if (ENABLE_LOGS) { 106 if (!isAsyncUnUsed) { 107 Log.d(LOG_TAG, "Ignoring synchronous mode of operation request"); 108 } 109 } 110 configureCodec(format, true, signalEOSWithLastFrame, isEncoder, 111 MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL); 112 } 113 114 @Override enqueueEOS(int bufferIndex)115 protected void enqueueEOS(int bufferIndex) { 116 if (!mSawInputEOS) { 117 MediaCodec.QueueRequest request = mCodec.getQueueRequest(bufferIndex); 118 mLinearInputBlock.allocateBlock(mCodecName, 64); 119 request.setLinearBlock(mLinearInputBlock.getBlock(), mLinearInputBlock.getOffset(), 0); 120 request.setPresentationTimeUs(0); 121 request.setFlags(MediaCodec.BUFFER_FLAG_END_OF_STREAM); 122 request.queue(); 123 mSawInputEOS = true; 124 if (ENABLE_LOGS) { 125 Log.v(LOG_TAG, "Queued End of Stream"); 126 } 127 } 128 } 129 130 @Override resetContext(boolean isAsync, boolean signalEOSWithLastFrame)131 protected void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) { 132 mLinearInputBlock.recycle(); 133 super.resetContext(isAsync, signalEOSWithLastFrame); 134 } 135 136 @Override enqueueCodecConfig(int bufferIndex)137 void enqueueCodecConfig(int bufferIndex) { 138 throw new RuntimeException("In block model mode, client MUST NOT submit csd(s) explicitly." 139 + " These are to be sent via format during configure"); 140 } 141 142 @Override enqueueInput(int bufferIndex)143 protected void enqueueInput(int bufferIndex) { 144 int sampleSize = (int) mExtractor.getSampleSize(); 145 if (sampleSize < 0) { 146 enqueueEOS(bufferIndex); 147 return; 148 } 149 if (mLinearInputBlock.getOffset() + sampleSize > mLinearInputBlock.getBufferCapacity()) { 150 int requestSize = 8192; 151 requestSize = Math.max(sampleSize, requestSize); 152 mLinearInputBlock.allocateBlock(mCodecName, requestSize); 153 } 154 long pts = mExtractor.getSampleTime(); 155 mExtractor.readSampleData(mLinearInputBlock.getBuffer(), mLinearInputBlock.getOffset()); 156 int extractorFlags = mExtractor.getSampleFlags(); 157 int codecFlags = 0; 158 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) { 159 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME; 160 } 161 if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) { 162 codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME; 163 } 164 if (!mExtractor.advance() && mSignalEOSWithLastFrame) { 165 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; 166 mSawInputEOS = true; 167 } 168 if (ENABLE_LOGS) { 169 Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + sampleSize + " pts: " + pts 170 + " flags: " + codecFlags); 171 } 172 MediaCodec.QueueRequest request = mCodec.getQueueRequest(bufferIndex); 173 request.setLinearBlock(mLinearInputBlock.getBlock(), mLinearInputBlock.getOffset(), 174 sampleSize); 175 request.setPresentationTimeUs(pts); 176 request.setFlags(codecFlags); 177 request.queue(); 178 if (sampleSize > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG 179 | MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) { 180 mOutputBuff.saveInPTS(pts); 181 mInputCount++; 182 mLinearInputBlock.setOffset(mLinearInputBlock.getOffset() + sampleSize); 183 } 184 } 185 186 @Override dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info)187 protected void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) { 188 MediaCodec.OutputFrame frame = mCodec.getOutputFrame(bufferIndex); 189 long framePts = frame.getPresentationTimeUs(); 190 long infoPts = info.presentationTimeUs; 191 int frameFlags = frame.getFlags(); 192 int infoFlags = info.flags; 193 assertEquals("presentation timestamps from OutputFrame does not match with the value " 194 + "obtained from callback: framePts=" + framePts + ", infoPts=" + infoPts + "\n" 195 + mTestConfig + mTestEnv, framePts, infoPts); 196 assertEquals("Flags from OutputFrame does not match with the value obtained from " 197 + "callback: frameFlags=" + frameFlags + ", infoFlags=" + infoFlags + "\n" 198 + mTestConfig + mTestEnv, frameFlags, infoFlags); 199 if (info.size > 0 && mSaveToMem) { 200 flattenBufferInfo(info, mIsAudio); 201 mOutputBuff.checksum(mFlatBuffer, mFlatBuffer.limit()); 202 if (frame.getLinearBlock() != null) { 203 ByteBuffer buf = frame.getLinearBlock().map(); 204 mOutputBuff.checksum(buf, info.size); 205 mOutputBuff.saveToMemory(buf, info); 206 frame.getLinearBlock().recycle(); 207 } 208 } 209 if ((infoFlags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { 210 mSawOutputEOS = true; 211 } 212 if (ENABLE_LOGS) { 213 Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + infoFlags + " size: " 214 + info.size + " timestamp: " + infoPts); 215 } 216 if (info.size > 0 && (infoFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { 217 mOutputBuff.saveOutPTS(infoPts); 218 mOutputCount++; 219 } 220 mCodec.releaseOutputBuffer(bufferIndex, false); 221 } 222 } 223