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