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.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 {
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 {
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
 
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
     */
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
     */
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
     */
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
     */
138
    public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat,
139
            Surface surface) {
140
        MediaCodecWrapper result = null;
141
        MediaCodec videoCodec = null;
142
 
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
        }
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
     */
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
     */
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
     */
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
     */
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
     */
328
    private void update() {
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
        }
378
 
379
    }
380
 
381
    private class WriteException extends Throwable {
382
        private WriteException(final String detailMessage) {
383
            super(detailMessage);
384
        }
385
    }
386
}