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