• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.mediav2.common.cts.CodecTestBase.getHeight;
20 import static android.mediav2.common.cts.CodecTestBase.getWidth;
21 
22 import static org.junit.Assert.assertTrue;
23 
24 import android.media.MediaCodec;
25 import android.media.MediaFormat;
26 import android.os.PersistableBundle;
27 import android.util.Log;
28 import android.util.Pair;
29 
30 import androidx.annotation.NonNull;
31 
32 import java.util.LinkedList;
33 import java.util.concurrent.TimeUnit;
34 import java.util.concurrent.locks.Condition;
35 import java.util.concurrent.locks.Lock;
36 import java.util.concurrent.locks.ReentrantLock;
37 
38 /**
39  * Helper class for running mediacodec in asynchronous mode. All mediacodec callback events are
40  * registered in this object so that the client can take appropriate action in time.
41  * <p>
42  * TODO(b/262696149): Calls to getInput(), getOutput(), getWork() return if there is a valid input
43  * or output buffer available for client or when the codec is in error state. Have to add support
44  * to return from these calls after a timeout. Currently the wait is indefinite.
45  */
46 public class CodecAsyncHandler extends MediaCodec.Callback {
47     private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
48     protected final Lock mLock = new ReentrantLock();
49     protected final Condition mCondition = mLock.newCondition();
50     protected final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbInputQueue;
51     private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mCbOutputQueue;
52     private MediaFormat mOutFormat;
53     private boolean mSignalledOutFormatChangedSubSession;
54     protected volatile boolean mSignalledError;
55     private String mErrorMsg;
56     private int mMinExpectedMetricsFlushCount;
57     private int mActualMetricsFlushCount;
58 
CodecAsyncHandler()59     public CodecAsyncHandler() {
60         mCbInputQueue = new LinkedList<>();
61         mCbOutputQueue = new LinkedList<>();
62         mOutFormat = null;
63         mSignalledError = false;
64         mSignalledOutFormatChangedSubSession = false;
65         mErrorMsg = "";
66         mMinExpectedMetricsFlushCount = 0;
67         mActualMetricsFlushCount = 0;
68     }
69 
clearQueues()70     public void clearQueues() {
71         mLock.lock();
72         try {
73             mCbInputQueue.clear();
74             mCbOutputQueue.clear();
75         } finally {
76             mLock.unlock();
77         }
78     }
79 
resetContext()80     public void resetContext() {
81         clearQueues();
82         mOutFormat = null;
83         mSignalledOutFormatChangedSubSession = false;
84         mErrorMsg = "";
85         mSignalledError = false;
86         mMinExpectedMetricsFlushCount = 0;
87         mActualMetricsFlushCount = 0;
88     }
89 
90     @Override
onInputBufferAvailable(@onNull MediaCodec codec, int bufferIndex)91     public void onInputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex) {
92         assertTrue(bufferIndex >= 0);
93         mLock.lock();
94         try {
95             mCbInputQueue.add(new Pair<>(bufferIndex, null));
96             mCondition.signalAll();
97         } finally {
98             mLock.unlock();
99         }
100     }
101 
102     @Override
onOutputBufferAvailable(@onNull MediaCodec codec, int bufferIndex, @NonNull MediaCodec.BufferInfo info)103     public void onOutputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex,
104             @NonNull MediaCodec.BufferInfo info) {
105         assertTrue(bufferIndex >= 0);
106         mLock.lock();
107         try {
108             mCbOutputQueue.add(new Pair<>(bufferIndex, info));
109             mCondition.signalAll();
110         } finally {
111             mLock.unlock();
112         }
113     }
114 
115     @Override
onError(@onNull MediaCodec codec, MediaCodec.CodecException e)116     public void onError(@NonNull MediaCodec codec, MediaCodec.CodecException e) {
117         mErrorMsg = "###################  Async Error Details  #####################\n";
118         mErrorMsg += e.toString() + "\n";
119         mLock.lock();
120         try {
121             mSignalledError = true;
122             mCondition.signalAll();
123         } finally {
124             mLock.unlock();
125         }
126         Log.e(LOG_TAG, "received media codec error : " + e.getMessage());
127     }
128 
129     @Override
onCryptoError(@onNull MediaCodec codec, @NonNull MediaCodec.CryptoException e)130     public void onCryptoError(@NonNull MediaCodec codec, @NonNull MediaCodec.CryptoException e) {
131         mErrorMsg = "###################  Crypto Error Details  #####################\n";
132         mErrorMsg += e.getMessage() + "\n";
133         mLock.lock();
134         try {
135             mSignalledError = true;
136             mCondition.signalAll();
137         } finally {
138             mLock.unlock();
139         }
140         Log.e(LOG_TAG, "received media codec crypto error : " + e.getMessage());
141     }
142 
143     @Override
onMetricsFlushed(@onNull MediaCodec codec, @NonNull PersistableBundle metrics)144     public void onMetricsFlushed(@NonNull MediaCodec codec, @NonNull PersistableBundle metrics) {
145         mLock.lock();
146         try {
147             mActualMetricsFlushCount++;
148             mCondition.signalAll();
149         } finally {
150             mLock.unlock();
151         }
152         Log.i(LOG_TAG, "final metrics for the previous subsession: " + metrics);
153     }
154 
155     @Override
onOutputFormatChanged(@onNull MediaCodec codec, @NonNull MediaFormat format)156     public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
157         mLock.lock();
158         try {
159             if (mOutFormat != null
160                     && mOutFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
161                 if (getWidth(mOutFormat) != getWidth(format)
162                         || getHeight(mOutFormat) != getHeight(format)) {
163                     mMinExpectedMetricsFlushCount++;
164                 }
165             }
166             mOutFormat = format;
167             mSignalledOutFormatChangedSubSession = true;
168             mCondition.signalAll();
169         } finally {
170             mLock.unlock();
171         }
172         Log.i(LOG_TAG, "Output format changed: " + format);
173     }
174 
setCallBack(MediaCodec codec, boolean isCodecInAsyncMode)175     public void setCallBack(MediaCodec codec, boolean isCodecInAsyncMode) {
176         if (isCodecInAsyncMode) {
177             codec.setCallback(this);
178         } else {
179             codec.setCallback(null);
180         }
181     }
182 
waitOnFormatChange()183     public void waitOnFormatChange() throws InterruptedException {
184         int retry = CodecTestBase.RETRY_LIMIT;
185         mLock.lock();
186         try {
187             while (!mSignalledError) {
188                 if (mSignalledOutFormatChangedSubSession || retry == 0) break;
189                 if (!mCondition.await(CodecTestBase.Q_DEQ_TIMEOUT_US, TimeUnit.MICROSECONDS)) {
190                     retry--;
191                 }
192             }
193         } finally {
194             mLock.unlock();
195         }
196         if (!mSignalledError) {
197             assertTrue(
198                     "taking too long to receive onOutputFormatChanged callback",
199                     mSignalledOutFormatChangedSubSession);
200         }
201     }
202 
getInput()203     public Pair<Integer, MediaCodec.BufferInfo> getInput() throws InterruptedException {
204         Pair<Integer, MediaCodec.BufferInfo> element = null;
205         mLock.lock();
206         try {
207             while (!mSignalledError) {
208                 if (mCbInputQueue.isEmpty()) {
209                     mCondition.await();
210                 } else {
211                     element = mCbInputQueue.remove(0);
212                     break;
213                 }
214             }
215         } finally {
216             mLock.unlock();
217         }
218         return element;
219     }
220 
getOutput()221     public Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException {
222         Pair<Integer, MediaCodec.BufferInfo> element = null;
223         mLock.lock();
224         try {
225             while (!mSignalledError) {
226                 if (mCbOutputQueue.isEmpty()) {
227                     mCondition.await();
228                 } else {
229                     element = mCbOutputQueue.remove(0);
230                     break;
231                 }
232             }
233         } finally {
234             mLock.unlock();
235         }
236         return element;
237     }
238 
getWork()239     public Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException {
240         Pair<Integer, MediaCodec.BufferInfo> element = null;
241         mLock.lock();
242         try {
243             while (!mSignalledError) {
244                 if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) {
245                     mCondition.await();
246                 } else {
247                     if (!mCbOutputQueue.isEmpty()) {
248                         element = mCbOutputQueue.remove(0);
249                     } else {
250                         element = mCbInputQueue.remove(0);
251                     }
252                     break;
253                 }
254             }
255         } finally {
256             mLock.unlock();
257         }
258         return element;
259     }
260 
isInputQueueEmpty()261     public boolean isInputQueueEmpty() {
262         boolean isEmpty = true;
263         mLock.lock();
264         try {
265             isEmpty = mCbInputQueue.isEmpty();
266         } finally {
267             mLock.unlock();
268         }
269         return isEmpty;
270     }
271 
hasSeenError()272     public boolean hasSeenError() {
273         return mSignalledError;
274     }
275 
getErrMsg()276     public String getErrMsg() {
277         return mErrorMsg;
278     }
279 
hasOutputFormatChanged()280     public boolean hasOutputFormatChanged() {
281         return mSignalledOutFormatChangedSubSession;
282     }
283 
getMinExpectedMetricsFlushCount()284     public int getMinExpectedMetricsFlushCount() {
285         return mMinExpectedMetricsFlushCount;
286     }
287 
getActualMetricsFlushCount()288     public int getActualMetricsFlushCount() {
289         return mActualMetricsFlushCount;
290     }
291 
getOutputFormat()292     public MediaFormat getOutputFormat() {
293         return mOutFormat;
294     }
295 }
296