• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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