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