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