MediaRecorder / 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
}