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
}