close
The Wayback Machine - https://web.archive.org/web/20171110022544/https://developer.android.com/samples/BasicMediaDecoder/src/com.example.android.common.media/MediaCodecWrapper.html
Skip to content

Most visited

Recently visited

navigation
BasicMediaDecoder / src / com.example.android.common.media /

MediaCodecWrapper.java

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 {
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 {
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
 
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
     */
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
     */
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
     */
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
     */
139
    public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat,
140
            Surface surface) throws IOException {
141
        MediaCodecWrapper result = null;
142
        MediaCodec videoCodec = null;
143
 
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
        }
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
     */
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
     */
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
     */
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
     */
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
     */
329
    private void update() {
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
        }
379
 
380
    }
381
 
382
    private class WriteException extends Throwable {
383
        private WriteException(final String detailMessage) {
384
            super(detailMessage);
385
        }
386
    }
387
}
This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Follow Google Developers on WeChat

BERJAYA

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a short survey?
Help us improve the Android developer experience.
(Sep 2017 survey)