1 /*
2  * Copyright (C) 2013 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.example.android.common.media;
18 
19 import android.media.*;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.view.Surface;
23 
24 import java.io.IOException;
25 import java.nio.ByteBuffer;
26 import java.util.ArrayDeque;
27 import java.util.Queue;
28 
29 /**
30  * Simplifies the MediaCodec interface by wrapping around the buffer processing operations.
31  */
32 public class MediaCodecWrapper {
33 
34     // Handler to use for {@code OutputSampleListener} and {code OutputFormatChangedListener}
35     // callbacks
36     private Handler mHandler;
37 
38 
39     // Callback when media output format changes.
40     public interface OutputFormatChangedListener {
outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat)41         void outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat);
42     }
43 
44     private OutputFormatChangedListener mOutputFormatChangedListener = null;
45 
46     /**
47      * Callback for decodes frames. Observers can register a listener for optional stream
48      * of decoded data
49      */
50     public interface OutputSampleListener {
outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer)51         void outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer);
52     }
53 
54     /**
55      * The {@link MediaCodec} that is managed by this class.
56      */
57     private MediaCodec mDecoder;
58 
59     // References to the internal buffers managed by the codec. The codec
60     // refers to these buffers by index, never by reference so it's up to us
61     // to keep track of which buffer is which.
62     private ByteBuffer[] mInputBuffers;
63     private ByteBuffer[] mOutputBuffers;
64 
65     // Indices of the input buffers that are currently available for writing. We'll
66     // consume these in the order they were dequeued from the codec.
67     private Queue<Integer> mAvailableInputBuffers;
68 
69     // Indices of the output buffers that currently hold valid data, in the order
70     // they were produced by the codec.
71     private Queue<Integer> mAvailableOutputBuffers;
72 
73     // Information about each output buffer, by index. Each entry in this array
74     // is valid if and only if its index is currently contained in mAvailableOutputBuffers.
75     private MediaCodec.BufferInfo[] mOutputBufferInfo;
76 
77     // An (optional) stream that will receive decoded data.
78     private OutputSampleListener mOutputSampleListener;
79 
MediaCodecWrapper(MediaCodec codec)80     private MediaCodecWrapper(MediaCodec codec) {
81         mDecoder = codec;
82         codec.start();
83         mInputBuffers = codec.getInputBuffers();
84         mOutputBuffers = codec.getOutputBuffers();
85         mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
86         mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length);
87         mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length);
88     }
89 
90     /**
91      * Releases resources and ends the encoding/decoding session.
92      */
stopAndRelease()93     public void stopAndRelease() {
94         mDecoder.stop();
95         mDecoder.release();
96         mDecoder = null;
97         mHandler = null;
98     }
99 
100     /**
101      * Getter for the registered {@link OutputFormatChangedListener}
102      */
getOutputFormatChangedListener()103     public OutputFormatChangedListener getOutputFormatChangedListener() {
104         return mOutputFormatChangedListener;
105     }
106 
107     /**
108      *
109      * @param outputFormatChangedListener the listener for callback.
110      * @param handler message handler for posting the callback.
111      */
setOutputFormatChangedListener(final OutputFormatChangedListener outputFormatChangedListener, Handler handler)112     public void setOutputFormatChangedListener(final OutputFormatChangedListener
113             outputFormatChangedListener, Handler handler) {
114         mOutputFormatChangedListener = outputFormatChangedListener;
115 
116         // Making sure we don't block ourselves due to a bad implementation of the callback by
117         // using a handler provided by client.
118         Looper looper;
119         mHandler = handler;
120         if (outputFormatChangedListener != null && mHandler == null) {
121             if ((looper = Looper.myLooper()) != null) {
122                 mHandler = new Handler();
123             } else {
124                 throw new IllegalArgumentException(
125                         "Looper doesn't exist in the calling thread");
126             }
127         }
128     }
129 
130     /**
131      * Constructs the {@link MediaCodecWrapper} wrapper object around the video codec.
132      * The codec is created using the encapsulated information in the
133      * {@link MediaFormat} object.
134      *
135      * @param trackFormat The format of the media object to be decoded.
136      * @param surface Surface to render the decoded frames.
137      * @return
138      */
fromVideoFormat(final MediaFormat trackFormat, Surface surface)139     public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat,
140             Surface surface) throws IOException {
141         MediaCodecWrapper result = null;
142         MediaCodec videoCodec = null;
143 
144         // BEGIN_INCLUDE(create_codec)
145         final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
146 
147         // Check to see if this is actually a video mime type. If it is, then create
148         // a codec that can decode this mime type.
149         if (mimeType.contains("video/")) {
150             videoCodec = MediaCodec.createDecoderByType(mimeType);
151             videoCodec.configure(trackFormat, surface, null,  0);
152 
153         }
154 
155         // If codec creation was successful, then create a wrapper object around the
156         // newly created codec.
157         if (videoCodec != null) {
158             result = new MediaCodecWrapper(videoCodec);
159         }
160         // END_INCLUDE(create_codec)
161 
162         return result;
163     }
164 
165 
166     /**
167      * Write a media sample to the decoder.
168      *
169      * A "sample" here refers to a single atomic access unit in the media stream. The definition
170      * of "access unit" is dependent on the type of encoding used, but it typically refers to
171      * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor}
172      * extracts data from a stream one sample at a time.
173      *
174      * @param input A ByteBuffer containing the input data for one sample. The buffer must be set
175      * up for reading, with its position set to the beginning of the sample data and its limit
176      * set to the end of the sample data.
177      *
178      * @param presentationTimeUs  The time, relative to the beginning of the media stream,
179      * at which this buffer should be rendered.
180      *
181      * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int,
182      * int, int, long, int)}
183      *
184      * @throws MediaCodec.CryptoException
185      */
writeSample(final ByteBuffer input, final MediaCodec.CryptoInfo crypto, final long presentationTimeUs, final int flags)186     public boolean writeSample(final ByteBuffer input,
187             final MediaCodec.CryptoInfo crypto,
188             final long presentationTimeUs,
189             final int flags) throws MediaCodec.CryptoException, WriteException {
190         boolean result = false;
191         int size = input.remaining();
192 
193         // check if we have dequed input buffers available from the codec
194         if (size > 0 &&  !mAvailableInputBuffers.isEmpty()) {
195             int index = mAvailableInputBuffers.remove();
196             ByteBuffer buffer = mInputBuffers[index];
197 
198             // we can't write our sample to a lesser capacity input buffer.
199             if (size > buffer.capacity()) {
200                 throw new MediaCodecWrapper.WriteException(String.format(
201                         "Insufficient capacity in MediaCodec buffer: "
202                             + "tried to write %d, buffer capacity is %d.",
203                         input.remaining(),
204                         buffer.capacity()));
205             }
206 
207             buffer.clear();
208             buffer.put(input);
209 
210             // Submit the buffer to the codec for decoding. The presentationTimeUs
211             // indicates the position (play time) for the current sample.
212             if (crypto == null) {
213                 mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
214             } else {
215                 mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags);
216             }
217             result = true;
218         }
219         return result;
220     }
221 
222     static MediaCodec.CryptoInfo cryptoInfo= new MediaCodec.CryptoInfo();
223 
224     /**
225      * Write a media sample to the decoder.
226      *
227      * A "sample" here refers to a single atomic access unit in the media stream. The definition
228      * of "access unit" is dependent on the type of encoding used, but it typically refers to
229      * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor}
230      * extracts data from a stream one sample at a time.
231      *
232      * @param extractor  Instance of {@link android.media.MediaExtractor} wrapping the media.
233      *
234      * @param presentationTimeUs The time, relative to the beginning of the media stream,
235      * at which this buffer should be rendered.
236      *
237      * @param flags  Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int,
238      * int, int, long, int)}
239      *
240      * @throws MediaCodec.CryptoException
241      */
writeSample(final MediaExtractor extractor, final boolean isSecure, final long presentationTimeUs, int flags)242     public boolean writeSample(final MediaExtractor extractor,
243             final boolean isSecure,
244             final long presentationTimeUs,
245             int flags) {
246         boolean result = false;
247         boolean isEos = false;
248 
249         if (!mAvailableInputBuffers.isEmpty()) {
250             int index = mAvailableInputBuffers.remove();
251             ByteBuffer buffer = mInputBuffers[index];
252 
253             // reads the sample from the file using extractor into the buffer
254             int size = extractor.readSampleData(buffer, 0);
255             if (size <= 0) {
256                 flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
257             }
258 
259             // Submit the buffer to the codec for decoding. The presentationTimeUs
260             // indicates the position (play time) for the current sample.
261             if (!isSecure) {
262                 mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
263             } else {
264                 extractor.getSampleCryptoInfo(cryptoInfo);
265                 mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags);
266             }
267 
268             result = true;
269         }
270         return result;
271     }
272 
273     /**
274      * Performs a peek() operation in the queue to extract media info for the buffer ready to be
275      * released i.e. the head element of the queue.
276      *
277      * @param out_bufferInfo An output var to hold the buffer info.
278      *
279      * @return True, if the peek was successful.
280      */
peekSample(MediaCodec.BufferInfo out_bufferInfo)281     public boolean peekSample(MediaCodec.BufferInfo out_bufferInfo) {
282         // dequeue available buffers and synchronize our data structures with the codec.
283         update();
284         boolean result = false;
285         if (!mAvailableOutputBuffers.isEmpty()) {
286             int index = mAvailableOutputBuffers.peek();
287             MediaCodec.BufferInfo info = mOutputBufferInfo[index];
288             // metadata of the sample
289             out_bufferInfo.set(
290                     info.offset,
291                     info.size,
292                     info.presentationTimeUs,
293                     info.flags);
294             result = true;
295         }
296         return result;
297     }
298 
299     /**
300      * Processes, releases and optionally renders the output buffer available at the head of the
301      * queue. All observers are notified with a callback. See {@link
302      * OutputSampleListener#outputSample(MediaCodecWrapper, android.media.MediaCodec.BufferInfo,
303      * java.nio.ByteBuffer)}
304      *
305      * @param render True, if the buffer is to be rendered on the {@link Surface} configured
306      *
307      */
popSample(boolean render)308     public void popSample(boolean render) {
309         // dequeue available buffers and synchronize our data structures with the codec.
310         update();
311         if (!mAvailableOutputBuffers.isEmpty()) {
312             int index = mAvailableOutputBuffers.remove();
313 
314             if (render && mOutputSampleListener != null) {
315                 ByteBuffer buffer = mOutputBuffers[index];
316                 MediaCodec.BufferInfo info = mOutputBufferInfo[index];
317                 mOutputSampleListener.outputSample(this, info, buffer);
318             }
319 
320             // releases the buffer back to the codec
321             mDecoder.releaseOutputBuffer(index, render);
322         }
323     }
324 
325     /**
326      * Synchronize this object's state with the internal state of the wrapped
327      * MediaCodec.
328      */
update()329     private void update() {
330         // BEGIN_INCLUDE(update_codec_state)
331         int index;
332 
333         // Get valid input buffers from the codec to fill later in the same order they were
334         // made available by the codec.
335         while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
336             mAvailableInputBuffers.add(index);
337         }
338 
339 
340         // Likewise with output buffers. If the output buffers have changed, start using the
341         // new set of output buffers. If the output format has changed, notify listeners.
342         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
343         while ((index = mDecoder.dequeueOutputBuffer(info, 0)) !=  MediaCodec.INFO_TRY_AGAIN_LATER) {
344             switch (index) {
345                 case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
346                     mOutputBuffers = mDecoder.getOutputBuffers();
347                     mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
348                     mAvailableOutputBuffers.clear();
349                     break;
350                 case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
351                     if (mOutputFormatChangedListener != null) {
352                         mHandler.post(new Runnable() {
353                             @Override
354                             public void run() {
355                                 mOutputFormatChangedListener
356                                         .outputFormatChanged(MediaCodecWrapper.this,
357                                                 mDecoder.getOutputFormat());
358 
359                             }
360                         });
361                     }
362                     break;
363                 default:
364                     // Making sure the index is valid before adding to output buffers. We've already
365                     // handled INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED &
366                     // INFO_OUTPUT_BUFFERS_CHANGED i.e all the other possible return codes but
367                     // asserting index value anyways for future-proofing the code.
368                     if(index >= 0) {
369                         mOutputBufferInfo[index] = info;
370                         mAvailableOutputBuffers.add(index);
371                     } else {
372                         throw new IllegalStateException("Unknown status from dequeueOutputBuffer");
373                     }
374                     break;
375             }
376 
377         }
378         // END_INCLUDE(update_codec_state)
379 
380     }
381 
382     private class WriteException extends Throwable {
WriteException(final String detailMessage)383         private WriteException(final String detailMessage) {
384             super(detailMessage);
385         }
386     }
387 }
388