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