MediaCodecBridge.java revision 3551c9c881056c480085172ff9840cab31610854
1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.media;
6
7import android.media.AudioFormat;
8import android.media.AudioManager;
9import android.media.AudioTrack;
10import android.media.MediaCodec;
11import android.media.MediaCrypto;
12import android.media.MediaFormat;
13import android.view.Surface;
14import android.util.Log;
15
16import java.io.IOException;
17import java.nio.ByteBuffer;
18
19import org.chromium.base.CalledByNative;
20import org.chromium.base.JNINamespace;
21
22/**
23 * A wrapper of the MediaCodec class to facilitate exception capturing and
24 * audio rendering.
25 */
26@JNINamespace("media")
27class MediaCodecBridge {
28
29    private static final String TAG = "MediaCodecBridge";
30
31    // Error code for MediaCodecBridge. Keep this value in sync with
32    // INFO_MEDIA_CODEC_ERROR in media_codec_bridge.h.
33    private static final int MEDIA_CODEC_ERROR = -1000;
34
35    // After a flush(), dequeueOutputBuffer() can often produce empty presentation timestamps
36    // for several frames. As a result, the player may find that the time does not increase
37    // after decoding a frame. To detect this, we check whether the presentation timestamp from
38    // dequeueOutputBuffer() is larger than input_timestamp - MAX_PRESENTATION_TIMESTAMP_SHIFT_US
39    // after a flush. And we set the presentation timestamp from dequeueOutputBuffer() to be
40    // non-decreasing for the remaining frames.
41    private static final long MAX_PRESENTATION_TIMESTAMP_SHIFT_US = 100000;
42
43    private ByteBuffer[] mInputBuffers;
44    private ByteBuffer[] mOutputBuffers;
45
46    private MediaCodec mMediaCodec;
47    private AudioTrack mAudioTrack;
48    private boolean mFlushed;
49    private long mLastPresentationTimeUs;
50
51    private static class DequeueOutputResult {
52        private final int mIndex;
53        private final int mFlags;
54        private final int mOffset;
55        private final long mPresentationTimeMicroseconds;
56        private final int mNumBytes;
57
58        private DequeueOutputResult(int index, int flags, int offset,
59                long presentationTimeMicroseconds, int numBytes) {
60            mIndex = index;
61            mFlags = flags;
62            mOffset = offset;
63            mPresentationTimeMicroseconds = presentationTimeMicroseconds;
64            mNumBytes = numBytes;
65        }
66
67        @CalledByNative("DequeueOutputResult")
68        private int index() { return mIndex; }
69
70        @CalledByNative("DequeueOutputResult")
71        private int flags() { return mFlags; }
72
73        @CalledByNative("DequeueOutputResult")
74        private int offset() { return mOffset; }
75
76        @CalledByNative("DequeueOutputResult")
77        private long presentationTimeMicroseconds() { return mPresentationTimeMicroseconds; }
78
79        @CalledByNative("DequeueOutputResult")
80        private int numBytes() { return mNumBytes; }
81    }
82
83    private MediaCodecBridge(String mime) throws IOException {
84        mMediaCodec = MediaCodec.createDecoderByType(mime);
85        mLastPresentationTimeUs = 0;
86        mFlushed = true;
87    }
88
89    @CalledByNative
90    private static MediaCodecBridge create(String mime) {
91        MediaCodecBridge mediaCodecBridge = null;
92        try {
93            mediaCodecBridge = new MediaCodecBridge(mime);
94        } catch (IOException e) {
95            Log.e(TAG, "Failed to create MediaCodecBridge " + e.toString());
96        }
97
98        return mediaCodecBridge;
99    }
100
101    @CalledByNative
102    private void release() {
103        mMediaCodec.release();
104        if (mAudioTrack != null) {
105            mAudioTrack.release();
106        }
107    }
108
109    @CalledByNative
110    private void start() {
111        mMediaCodec.start();
112        mInputBuffers = mMediaCodec.getInputBuffers();
113    }
114
115    @CalledByNative
116    private int dequeueInputBuffer(long timeoutUs) {
117        try {
118            return mMediaCodec.dequeueInputBuffer(timeoutUs);
119        } catch(Exception e) {
120            Log.e(TAG, "Cannot dequeue Input buffer " + e.toString());
121        }
122        return MEDIA_CODEC_ERROR;
123    }
124
125    @CalledByNative
126    private void flush() {
127        mMediaCodec.flush();
128        mFlushed = true;
129        if (mAudioTrack != null) {
130            mAudioTrack.flush();
131        }
132    }
133
134    @CalledByNative
135    private void stop() {
136        mMediaCodec.stop();
137        if (mAudioTrack != null) {
138            mAudioTrack.pause();
139        }
140    }
141
142    @CalledByNative
143    private int getOutputHeight() {
144        return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_HEIGHT);
145    }
146
147    @CalledByNative
148    private int getOutputWidth() {
149        return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_WIDTH);
150    }
151
152    @CalledByNative
153    private ByteBuffer getInputBuffer(int index) {
154        return mInputBuffers[index];
155    }
156
157    @CalledByNative
158    private ByteBuffer getOutputBuffer(int index) {
159        return mOutputBuffers[index];
160    }
161
162    @CalledByNative
163    private void queueInputBuffer(
164            int index, int offset, int size, long presentationTimeUs, int flags) {
165        resetLastPresentationTimeIfNeeded(presentationTimeUs);
166        try {
167            mMediaCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
168        } catch(IllegalStateException e) {
169            Log.e(TAG, "Failed to queue input buffer " + e.toString());
170        }
171    }
172
173    @CalledByNative
174    private void queueSecureInputBuffer(
175            int index, int offset, byte[] iv, byte[] keyId, int[] numBytesOfClearData,
176            int[] numBytesOfEncryptedData, int numSubSamples, long presentationTimeUs) {
177        resetLastPresentationTimeIfNeeded(presentationTimeUs);
178        try {
179            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
180            cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData,
181                    keyId, iv, MediaCodec.CRYPTO_MODE_AES_CTR);
182            mMediaCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, 0);
183        } catch(IllegalStateException e) {
184            Log.e(TAG, "Failed to queue secure input buffer " + e.toString());
185        }
186    }
187
188    @CalledByNative
189    private void releaseOutputBuffer(int index, boolean render) {
190        mMediaCodec.releaseOutputBuffer(index, render);
191    }
192
193    @CalledByNative
194    private void getOutputBuffers() {
195        mOutputBuffers = mMediaCodec.getOutputBuffers();
196    }
197
198    @CalledByNative
199    private DequeueOutputResult dequeueOutputBuffer(long timeoutUs) {
200        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
201        int index = MEDIA_CODEC_ERROR;
202        try {
203            index = mMediaCodec.dequeueOutputBuffer(info, timeoutUs);
204            if (info.presentationTimeUs < mLastPresentationTimeUs) {
205                // TODO(qinmin): return a special code through DequeueOutputResult
206                // to notify the native code the the frame has a wrong presentation
207                // timestamp and should be skipped.
208                info.presentationTimeUs = mLastPresentationTimeUs;
209            }
210            mLastPresentationTimeUs = info.presentationTimeUs;
211        } catch (IllegalStateException e) {
212            Log.e(TAG, "Cannot dequeue output buffer " + e.toString());
213        }
214        return new DequeueOutputResult(
215                index, info.flags, info.offset, info.presentationTimeUs, info.size);
216    }
217
218    @CalledByNative
219    private boolean configureVideo(MediaFormat format, Surface surface, MediaCrypto crypto,
220            int flags) {
221        try {
222            mMediaCodec.configure(format, surface, crypto, flags);
223            return true;
224        } catch (IllegalStateException e) {
225          Log.e(TAG, "Cannot configure the video codec " + e.toString());
226        }
227        return false;
228    }
229
230    @CalledByNative
231    private static MediaFormat createAudioFormat(String mime, int SampleRate, int ChannelCount) {
232        return MediaFormat.createAudioFormat(mime, SampleRate, ChannelCount);
233    }
234
235    @CalledByNative
236    private static MediaFormat createVideoFormat(String mime, int width, int height) {
237        return MediaFormat.createVideoFormat(mime, width, height);
238    }
239
240    @CalledByNative
241    private static void setCodecSpecificData(MediaFormat format, int index, ByteBuffer bytes) {
242        String name = null;
243        if (index == 0) {
244            name = "csd-0";
245        } else if (index == 1) {
246            name = "csd-1";
247        }
248        if (name != null) {
249            format.setByteBuffer(name, bytes);
250        }
251    }
252
253    @CalledByNative
254    private static void setFrameHasADTSHeader(MediaFormat format) {
255        format.setInteger(MediaFormat.KEY_IS_ADTS, 1);
256    }
257
258    @CalledByNative
259    private boolean configureAudio(MediaFormat format, MediaCrypto crypto, int flags,
260            boolean playAudio) {
261        try {
262            mMediaCodec.configure(format, null, crypto, flags);
263            if (playAudio) {
264                int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
265                int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
266                int channelConfig = (channelCount == 1) ? AudioFormat.CHANNEL_OUT_MONO :
267                        AudioFormat.CHANNEL_OUT_STEREO;
268                // Using 16bit PCM for output. Keep this value in sync with
269                // kBytesPerAudioOutputSample in media_codec_bridge.cc.
270                int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
271                        AudioFormat.ENCODING_PCM_16BIT);
272                mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
273                        AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM);
274            }
275            return true;
276        } catch (IllegalStateException e) {
277            Log.e(TAG, "Cannot configure the audio codec " + e.toString());
278        }
279        return false;
280    }
281
282    @CalledByNative
283    private void playOutputBuffer(byte[] buf) {
284        if (mAudioTrack != null) {
285            if (AudioTrack.PLAYSTATE_PLAYING != mAudioTrack.getPlayState()) {
286                mAudioTrack.play();
287            }
288            int size = mAudioTrack.write(buf, 0, buf.length);
289            if (buf.length != size) {
290                Log.i(TAG, "Failed to send all data to audio output, expected size: " +
291                        buf.length + ", actual size: " + size);
292            }
293        }
294    }
295
296    @CalledByNative
297    private void setVolume(double volume) {
298        if (mAudioTrack != null) {
299            mAudioTrack.setStereoVolume((float) volume, (float) volume);
300        }
301    }
302
303    private void resetLastPresentationTimeIfNeeded(long presentationTimeUs) {
304        if (mFlushed) {
305            mLastPresentationTimeUs =
306                    Math.max(presentationTimeUs - MAX_PRESENTATION_TIMESTAMP_SHIFT_US, 0);
307            mFlushed = false;
308        }
309    }
310}
311