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