1 /* 2 * Copyright (C) 2019 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 com.android.media.benchmark.library; 18 19 import android.media.MediaCodec; 20 import android.media.MediaCodec.CodecException; 21 import android.media.MediaFormat; 22 import android.view.Surface; 23 import android.util.Log; 24 25 import androidx.annotation.NonNull; 26 27 import java.io.FileInputStream; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.nio.ByteBuffer; 31 32 public class Encoder implements IBufferXfer.IReceiveBuffer { 33 // Change in AUDIO_ENCODE_DEFAULT_MAX_INPUT_SIZE should also be taken to 34 // kDefaultAudioEncodeFrameSize present in BenchmarkCommon.h 35 private static final int AUDIO_ENCODE_DEFAULT_MAX_INPUT_SIZE = 4096; 36 private static final String TAG = "Encoder"; 37 private static final boolean DEBUG = false; 38 private static final int kQueueDequeueTimeoutUs = 1000; 39 private final Object mLock = new Object(); 40 private MediaCodec mCodec = null; 41 private String mMime; 42 private Stats mStats; 43 44 private int mOffset; 45 private int mFrameSize; 46 private int mNumInputFrame; 47 private int mNumFrames = 0; 48 private int mFrameRate; 49 private int mSampleRate; 50 private long mInputBufferSize; 51 52 private int mMinOutputBuffers = 0; 53 private int mNumOutputBuffers = 0; 54 private boolean mUseSurface = false; 55 56 private boolean mSawInputEOS; 57 private boolean mSawOutputEOS; 58 private boolean mSignalledError; 59 60 private FileInputStream mInputStream = null; 61 private FileOutputStream mOutputStream = null; 62 private IBufferXfer.ISendBuffer mIBufferSend = null; 63 /* success for encoder */ 64 public static final int ENCODE_SUCCESS = 0; 65 /* some error happened during encoding */ 66 public static final int ENCODE_ENCODER_ERROR = -1; 67 /* error while creating an encoder */ 68 public static final int ENCODE_CREATE_ERROR = -2; Encoder()69 public Encoder() { 70 mStats = new Stats(); 71 mNumInputFrame = 0; 72 mSawInputEOS = false; 73 mSawOutputEOS = false; 74 mSignalledError = false; 75 } 76 @Override receiveBuffer(IBufferXfer.BufferXferInfo info)77 public boolean receiveBuffer(IBufferXfer.BufferXferInfo info) { 78 if (DEBUG) { 79 Log.d(TAG,"Encoder Getting buffers from external: " 80 + " Bytes Read: " + info.bytesRead 81 + " PresentationUs " + info.presentationTimeUs 82 + " flags: " + info.flag); 83 } 84 MediaCodec codec = (MediaCodec)info.obj; 85 codec.queueInputBuffer(info.idx, 0, info.bytesRead, 86 info.presentationTimeUs, info.flag); 87 return true; 88 } 89 @Override connect(IBufferXfer.ISendBuffer receiver)90 public boolean connect(IBufferXfer.ISendBuffer receiver) { 91 mIBufferSend = receiver; 92 return true; 93 } getStats()94 public Stats getStats() { return mStats; }; 95 96 /** 97 * Setup of encoder 98 * 99 * @param encoderOutputStream Will dump the encoder output in this stream if not null. 100 * @param fileInputStream Will read the decoded output from this stream 101 */ setupEncoder(FileOutputStream encoderOutputStream, FileInputStream fileInputStream)102 public void setupEncoder(FileOutputStream encoderOutputStream, 103 FileInputStream fileInputStream) { 104 this.mInputStream = fileInputStream; 105 this.mOutputStream = encoderOutputStream; 106 } 107 /** 108 * Setup of encoder 109 * 110 * @param useSurface, indicates that application is using surface for input 111 * @param numOutputBuffers indicate the minimum buffers to signal Output 112 * end of stream 113 */ setupEncoder(boolean useSurface, int numOutputBuffers)114 public void setupEncoder(boolean useSurface, int numOutputBuffers) { 115 this.mUseSurface = useSurface; 116 this.mMinOutputBuffers = numOutputBuffers; 117 } 118 createCodec(String codecName, String mime)119 private MediaCodec createCodec(String codecName, String mime) throws IOException { 120 try { 121 MediaCodec codec; 122 if (codecName.isEmpty()) { 123 Log.i(TAG, "Mime type: " + mime); 124 if (mime != null) { 125 codec = MediaCodec.createEncoderByType(mime); 126 Log.i(TAG, "Encoder created for mime type " + mime); 127 return codec; 128 } else { 129 Log.e(TAG, "Mime type is null, please specify a mime type to create encoder"); 130 return null; 131 } 132 } else { 133 codec = MediaCodec.createByCodecName(codecName); 134 Log.i(TAG, "Encoder created with codec name: " + codecName + " and mime: " + mime); 135 return codec; 136 } 137 } catch (IllegalArgumentException ex) { 138 ex.printStackTrace(); 139 Log.e(TAG, "Failed to create encoder for " + codecName + " mime: " + mime); 140 return null; 141 } 142 } 143 /** 144 * Creates and configures the encoder with the given name, format and mime. 145 * provided a valid list of parameters are passed as inputs. This is needed 146 * to first configure the codec and then may be get surface etc and then 147 * use for encode. 148 * 149 * @param codecName Will create the encoder with codecName 150 * @param encodeFormat Format of the output data 151 * @param mime For creating encode format 152 * @return ENCODE_SUCCESS if encode was successful, 153 * ENCODE_CREATE_ERROR for encoder not created 154 * @throws IOException If the codec cannot be created. 155 */ 156 createAndConfigure(String codecName, MediaFormat encodeFormat, String mime)157 public int createAndConfigure(String codecName, MediaFormat encodeFormat, 158 String mime) throws IOException { 159 if (mCodec == null) { 160 mMime = mime; 161 mCodec = createCodec(codecName, mime); 162 if (mCodec == null) { 163 return ENCODE_CREATE_ERROR; 164 } 165 /*Configure Codec*/ 166 try { 167 mCodec.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); 168 } catch(IllegalArgumentException 169 | IllegalStateException 170 | MediaCodec.CryptoException e) { 171 Log.e(TAG, "Failed to configure " + mCodec.getName() + " encoder."); 172 e.printStackTrace(); 173 return ENCODE_CREATE_ERROR; 174 } 175 } 176 return ENCODE_SUCCESS; 177 } 178 /** 179 * Requests the surface to use as input to the encoder 180 * @return a valid surface or null if not called after configure. 181 */ getInputSurface()182 public Surface getInputSurface() { 183 Surface inputSurface = null; 184 if (mCodec != null) { 185 inputSurface = mCodec.createInputSurface(); 186 } 187 return inputSurface; 188 } 189 /** 190 * Encodes the given raw input file and measures the performance of encode operation, 191 * provided a valid list of parameters are passed as inputs. 192 * 193 * @param codecName Will create the encoder with codecName 194 * @param mime For creating encode format 195 * @param encodeFormat Format of the output data 196 * @param frameSize Size of the frame 197 * @param asyncMode Will run on async implementation if true 198 * @return ENCODE_SUCCESS if encode was successful ,ENCODE_ENCODER_ERROR for fail, 199 * ENCODE_CREATE_ERROR for encoder not created 200 * @throws IOException If the codec cannot be created. 201 */ encode(String codecName, MediaFormat encodeFormat, String mime, int frameRate, int sampleRate, int frameSize, boolean asyncMode)202 public int encode(String codecName, MediaFormat encodeFormat, String mime, int frameRate, 203 int sampleRate, int frameSize, boolean asyncMode) throws IOException { 204 mInputBufferSize = (mInputStream != null) ? mInputStream.getChannel().size() : 0; 205 mOffset = 0; 206 mFrameRate = frameRate; 207 mSampleRate = sampleRate; 208 long sTime = mStats.getCurTime(); 209 if (mCodec == null) { 210 int status = createAndConfigure(codecName, encodeFormat, mime); 211 if(status != ENCODE_SUCCESS) { 212 return status; 213 } 214 } 215 if (!mUseSurface) { 216 if (mMime.startsWith("video/")) { 217 mFrameSize = frameSize; 218 } else { 219 int maxInputSize = AUDIO_ENCODE_DEFAULT_MAX_INPUT_SIZE; 220 MediaFormat format = mCodec.getInputFormat(); 221 if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) { 222 maxInputSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); 223 } 224 mFrameSize = frameSize; 225 if (mFrameSize > maxInputSize && maxInputSize > 0) { 226 mFrameSize = maxInputSize; 227 } 228 } 229 mNumFrames = (int) ((mInputBufferSize + mFrameSize - 1) / mFrameSize); 230 } 231 if (asyncMode) { 232 mCodec.setCallback(new MediaCodec.Callback() { 233 @Override 234 public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, 235 int inputBufferId) { 236 try { 237 mStats.addInputTime(); 238 onInputAvailable(mediaCodec, inputBufferId); 239 } catch (Exception e) { 240 e.printStackTrace(); 241 Log.e(TAG, e.toString()); 242 } 243 } 244 245 @Override 246 public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, 247 int outputBufferId, 248 @NonNull MediaCodec.BufferInfo bufferInfo) { 249 mStats.addOutputTime(); 250 onOutputAvailable(mediaCodec, outputBufferId, bufferInfo); 251 if (mSawOutputEOS) { 252 Log.i(TAG, "Saw output EOS"); 253 synchronized (mLock) { mLock.notify(); } 254 } 255 } 256 257 @Override 258 public void onError(@NonNull MediaCodec mediaCodec, @NonNull CodecException e) { 259 mSignalledError = true; 260 Log.e(TAG, "Codec Error: " + e.toString()); 261 e.printStackTrace(); 262 synchronized (mLock) { mLock.notify(); } 263 } 264 265 @Override 266 public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec, 267 @NonNull MediaFormat format) { 268 Log.i(TAG, "Output format changed. Format: " + format.toString()); 269 } 270 }); 271 } 272 mCodec.start(); 273 long eTime = mStats.getCurTime(); 274 mStats.setInitTime(mStats.getTimeDiff(sTime, eTime)); 275 mStats.setStartTime(); 276 if (asyncMode) { 277 try { 278 synchronized (mLock) { mLock.wait(); } 279 if (mSignalledError) { 280 return ENCODE_ENCODER_ERROR; 281 } 282 } catch (InterruptedException e) { 283 e.printStackTrace(); 284 } 285 } else { 286 while (!mSawOutputEOS && !mSignalledError) { 287 /* Queue input data */ 288 if (!mSawInputEOS && !mUseSurface) { 289 int inputBufferId = mCodec.dequeueInputBuffer(kQueueDequeueTimeoutUs); 290 if (inputBufferId < 0 && inputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) { 291 Log.e(TAG, "MediaCodec.dequeueInputBuffer " + "returned invalid index : " + 292 inputBufferId); 293 return ENCODE_ENCODER_ERROR; 294 } 295 mStats.addInputTime(); 296 onInputAvailable(mCodec, inputBufferId); 297 } 298 /* Dequeue output data */ 299 MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo(); 300 int outputBufferId = 301 mCodec.dequeueOutputBuffer(outputBufferInfo, kQueueDequeueTimeoutUs); 302 if (outputBufferId < 0) { 303 if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 304 MediaFormat outFormat = mCodec.getOutputFormat(); 305 Log.i(TAG, "Output format changed. Format: " + outFormat.toString()); 306 } else if (outputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) { 307 Log.e(TAG, "MediaCodec.dequeueOutputBuffer" + " returned invalid index " + 308 outputBufferId); 309 return ENCODE_ENCODER_ERROR; 310 } 311 } else { 312 mStats.addOutputTime(); 313 if (DEBUG) { 314 Log.d(TAG, "Dequeue O/P buffer with BufferID " + outputBufferId); 315 } 316 onOutputAvailable(mCodec, outputBufferId, outputBufferInfo); 317 } 318 } 319 } 320 return ENCODE_SUCCESS; 321 } 322 onOutputAvailable(MediaCodec mediaCodec, int outputBufferId, MediaCodec.BufferInfo outputBufferInfo)323 private void onOutputAvailable(MediaCodec mediaCodec, int outputBufferId, 324 MediaCodec.BufferInfo outputBufferInfo) { 325 if (mSawOutputEOS || outputBufferId < 0) { 326 if (mSawOutputEOS) { 327 Log.i(TAG, "Saw output EOS"); 328 } 329 return; 330 } 331 ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId); 332 if (mOutputStream != null) { 333 try { 334 335 byte[] bytesOutput = new byte[outputBuffer.remaining()]; 336 outputBuffer.get(bytesOutput); 337 mOutputStream.write(bytesOutput); 338 } catch (IOException e) { 339 e.printStackTrace(); 340 Log.d(TAG, "Error Dumping File: Exception " + e.toString()); 341 return; 342 } 343 } 344 mNumOutputBuffers++; 345 if (DEBUG) { 346 Log.d(TAG, 347 "In OutputBufferAvailable ," 348 + " timestamp = " + outputBufferInfo.presentationTimeUs 349 + " size = " + outputBufferInfo.size 350 + " flags = " + outputBufferInfo.flags); 351 } 352 353 mStats.addFrameSize(outputBuffer.remaining()); 354 mediaCodec.releaseOutputBuffer(outputBufferId, false); 355 mSawOutputEOS = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0; 356 if (mUseSurface && !mSawOutputEOS) { 357 mSawOutputEOS = (mNumOutputBuffers >= mMinOutputBuffers) ? true : false; 358 } 359 } 360 onInputAvailable(MediaCodec mediaCodec, int inputBufferId)361 private void onInputAvailable(MediaCodec mediaCodec, int inputBufferId) throws IOException { 362 if (mSawInputEOS || inputBufferId < 0 || this.mUseSurface) { 363 if (mSawInputEOS) { 364 Log.i(TAG, "Saw input EOS"); 365 } 366 return; 367 } 368 if (mInputBufferSize < mOffset) { 369 Log.e(TAG, "Out of bound access of input buffer"); 370 mSignalledError = true; 371 return; 372 } 373 ByteBuffer inputBuffer = mCodec.getInputBuffer(inputBufferId); 374 if (inputBuffer == null) { 375 mSignalledError = true; 376 return; 377 } 378 if (mIBufferSend != null) { 379 IBufferXfer.BufferXferInfo info = new IBufferXfer.BufferXferInfo(); 380 info.buf = inputBuffer; 381 info.idx = inputBufferId; 382 info.obj = mediaCodec; 383 mIBufferSend.sendBuffer(this, info); 384 return; 385 } 386 int bufSize = inputBuffer.capacity(); 387 int bytesToRead = mFrameSize; 388 if (mInputBufferSize - mOffset < mFrameSize) { 389 bytesToRead = (int) (mInputBufferSize - mOffset); 390 } 391 //b/148655275 - Update Frame size, as Format value may not be valid 392 if (bufSize < bytesToRead) { 393 if(mNumInputFrame == 0) { 394 mFrameSize = bufSize; 395 bytesToRead = bufSize; 396 mNumFrames = (int) ((mInputBufferSize + mFrameSize - 1) / mFrameSize); 397 } else { 398 mSignalledError = true; 399 return; 400 } 401 } 402 403 byte[] inputArray = new byte[bytesToRead]; 404 mInputStream.read(inputArray, 0, bytesToRead); 405 inputBuffer.put(inputArray); 406 int flag = 0; 407 if (mNumInputFrame >= mNumFrames - 1 || bytesToRead == 0) { 408 Log.i(TAG, "Sending EOS on input last frame"); 409 mSawInputEOS = true; 410 flag = MediaCodec.BUFFER_FLAG_END_OF_STREAM; 411 } 412 int presentationTimeUs; 413 if (mMime.startsWith("video/")) { 414 presentationTimeUs = mNumInputFrame * (1000000 / mFrameRate); 415 } else { 416 presentationTimeUs = mNumInputFrame * mFrameSize * 1000000 / mSampleRate; 417 } 418 mediaCodec.queueInputBuffer(inputBufferId, 0, bytesToRead, presentationTimeUs, flag); 419 mNumInputFrame++; 420 mOffset += bytesToRead; 421 } 422 423 /** 424 * Stops the codec and releases codec resources. 425 */ deInitEncoder()426 public void deInitEncoder() { 427 long sTime = mStats.getCurTime(); 428 if (mCodec != null) { 429 mCodec.stop(); 430 mCodec.release(); 431 mCodec = null; 432 } 433 long eTime = mStats.getCurTime(); 434 mStats.setDeInitTime(mStats.getTimeDiff(sTime, eTime)); 435 } 436 437 /** 438 * Prints out the statistics in the information log 439 * 440 * @param inputReference The operation being performed, in this case decode 441 * @param componentName Name of the component/codec 442 * @param mode The operating mode: Sync/Async 443 * @param durationUs Duration of the clip in microseconds 444 * @param statsFile The output file where the stats data is written 445 */ dumpStatistics(String inputReference, String componentName, String mode, long durationUs, String statsFile)446 public void dumpStatistics(String inputReference, String componentName, String mode, 447 long durationUs, String statsFile) throws IOException { 448 String operation = "encode"; 449 mStats.dumpStatistics( 450 inputReference, operation, componentName, mode, durationUs, statsFile); 451 } 452 453 /** 454 * Resets the stats 455 */ resetEncoder()456 public void resetEncoder() { 457 mOffset = 0; 458 mInputBufferSize = 0; 459 mNumInputFrame = 0; 460 mMinOutputBuffers = 0; 461 mSawInputEOS = false; 462 mSawOutputEOS = false; 463 mSignalledError = false; 464 mUseSurface = false; 465 mStats.reset(); 466 } 467 } 468