1// Copyright 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.MediaCodecInfo; 12import android.media.MediaCodecList; 13import android.media.MediaCrypto; 14import android.media.MediaFormat; 15import android.os.Build; 16import android.os.Bundle; 17import android.util.Log; 18import android.view.Surface; 19 20import org.chromium.base.CalledByNative; 21import org.chromium.base.JNINamespace; 22 23import java.nio.ByteBuffer; 24import java.util.ArrayList; 25import java.util.HashMap; 26import java.util.Map; 27 28/** 29 * A wrapper of the MediaCodec class to facilitate exception capturing and 30 * audio rendering. 31 */ 32@JNINamespace("media") 33class MediaCodecBridge { 34 private static final String TAG = "MediaCodecBridge"; 35 36 // Error code for MediaCodecBridge. Keep this value in sync with 37 // MediaCodecStatus in media_codec_bridge.h. 38 private static final int MEDIA_CODEC_OK = 0; 39 private static final int MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER = 1; 40 private static final int MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER = 2; 41 private static final int MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED = 3; 42 private static final int MEDIA_CODEC_OUTPUT_FORMAT_CHANGED = 4; 43 private static final int MEDIA_CODEC_INPUT_END_OF_STREAM = 5; 44 private static final int MEDIA_CODEC_OUTPUT_END_OF_STREAM = 6; 45 private static final int MEDIA_CODEC_NO_KEY = 7; 46 private static final int MEDIA_CODEC_STOPPED = 8; 47 private static final int MEDIA_CODEC_ERROR = 9; 48 49 // Codec direction. Keep this in sync with media_codec_bridge.h. 50 private static final int MEDIA_CODEC_DECODER = 0; 51 private static final int MEDIA_CODEC_ENCODER = 1; 52 53 // Max adaptive playback size to be supplied to the decoder. 54 private static final int MAX_ADAPTIVE_PLAYBACK_WIDTH = 1920; 55 private static final int MAX_ADAPTIVE_PLAYBACK_HEIGHT = 1080; 56 57 // After a flush(), dequeueOutputBuffer() can often produce empty presentation timestamps 58 // for several frames. As a result, the player may find that the time does not increase 59 // after decoding a frame. To detect this, we check whether the presentation timestamp from 60 // dequeueOutputBuffer() is larger than input_timestamp - MAX_PRESENTATION_TIMESTAMP_SHIFT_US 61 // after a flush. And we set the presentation timestamp from dequeueOutputBuffer() to be 62 // non-decreasing for the remaining frames. 63 private static final long MAX_PRESENTATION_TIMESTAMP_SHIFT_US = 100000; 64 65 private ByteBuffer[] mInputBuffers; 66 private ByteBuffer[] mOutputBuffers; 67 68 private MediaCodec mMediaCodec; 69 private AudioTrack mAudioTrack; 70 private boolean mFlushed; 71 private long mLastPresentationTimeUs; 72 private String mMime; 73 private boolean mAdaptivePlaybackSupported; 74 75 private static class DequeueInputResult { 76 private final int mStatus; 77 private final int mIndex; 78 79 private DequeueInputResult(int status, int index) { 80 mStatus = status; 81 mIndex = index; 82 } 83 84 @CalledByNative("DequeueInputResult") 85 private int status() { return mStatus; } 86 87 @CalledByNative("DequeueInputResult") 88 private int index() { return mIndex; } 89 } 90 91 /** 92 * This class represents supported android codec information. 93 */ 94 private static class CodecInfo { 95 private final String mCodecType; // e.g. "video/x-vnd.on2.vp8". 96 private final String mCodecName; // e.g. "OMX.google.vp8.decoder". 97 private final int mDirection; 98 99 private CodecInfo(String codecType, String codecName, 100 int direction) { 101 mCodecType = codecType; 102 mCodecName = codecName; 103 mDirection = direction; 104 } 105 106 @CalledByNative("CodecInfo") 107 private String codecType() { return mCodecType; } 108 109 @CalledByNative("CodecInfo") 110 private String codecName() { return mCodecName; } 111 112 @CalledByNative("CodecInfo") 113 private int direction() { return mDirection; } 114 } 115 116 private static class DequeueOutputResult { 117 private final int mStatus; 118 private final int mIndex; 119 private final int mFlags; 120 private final int mOffset; 121 private final long mPresentationTimeMicroseconds; 122 private final int mNumBytes; 123 124 private DequeueOutputResult(int status, int index, int flags, int offset, 125 long presentationTimeMicroseconds, int numBytes) { 126 mStatus = status; 127 mIndex = index; 128 mFlags = flags; 129 mOffset = offset; 130 mPresentationTimeMicroseconds = presentationTimeMicroseconds; 131 mNumBytes = numBytes; 132 } 133 134 @CalledByNative("DequeueOutputResult") 135 private int status() { return mStatus; } 136 137 @CalledByNative("DequeueOutputResult") 138 private int index() { return mIndex; } 139 140 @CalledByNative("DequeueOutputResult") 141 private int flags() { return mFlags; } 142 143 @CalledByNative("DequeueOutputResult") 144 private int offset() { return mOffset; } 145 146 @CalledByNative("DequeueOutputResult") 147 private long presentationTimeMicroseconds() { return mPresentationTimeMicroseconds; } 148 149 @CalledByNative("DequeueOutputResult") 150 private int numBytes() { return mNumBytes; } 151 } 152 153 /** 154 * Get a list of supported android codec mimes. 155 */ 156 @SuppressWarnings("deprecation") 157 @CalledByNative 158 private static CodecInfo[] getCodecsInfo() { 159 // Return the first (highest-priority) codec for each MIME type. 160 Map<String, CodecInfo> encoderInfoMap = new HashMap<String, CodecInfo>(); 161 Map<String, CodecInfo> decoderInfoMap = new HashMap<String, CodecInfo>(); 162 int count = MediaCodecList.getCodecCount(); 163 for (int i = 0; i < count; ++i) { 164 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); 165 int direction = 166 info.isEncoder() ? MEDIA_CODEC_ENCODER : MEDIA_CODEC_DECODER; 167 String codecString = info.getName(); 168 String[] supportedTypes = info.getSupportedTypes(); 169 for (int j = 0; j < supportedTypes.length; ++j) { 170 Map<String, CodecInfo> map = info.isEncoder() ? encoderInfoMap : decoderInfoMap; 171 if (!map.containsKey(supportedTypes[j])) { 172 map.put(supportedTypes[j], new CodecInfo( 173 supportedTypes[j], codecString, direction)); 174 } 175 } 176 } 177 ArrayList<CodecInfo> codecInfos = new ArrayList<CodecInfo>( 178 decoderInfoMap.size() + encoderInfoMap.size()); 179 codecInfos.addAll(encoderInfoMap.values()); 180 codecInfos.addAll(decoderInfoMap.values()); 181 return codecInfos.toArray(new CodecInfo[codecInfos.size()]); 182 } 183 184 /** 185 * Get a name of default android codec. 186 */ 187 @SuppressWarnings("deprecation") 188 @CalledByNative 189 private static String getDefaultCodecName(String mime, int direction) { 190 String codecName = ""; 191 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { 192 try { 193 MediaCodec mediaCodec = null; 194 if (direction == MEDIA_CODEC_ENCODER) { 195 mediaCodec = MediaCodec.createEncoderByType(mime); 196 } else { 197 mediaCodec = MediaCodec.createDecoderByType(mime); 198 } 199 codecName = mediaCodec.getName(); 200 mediaCodec.release(); 201 } catch (Exception e) { 202 Log.w(TAG, "getDefaultCodecName: Failed to create MediaCodec: " + 203 mime + ", direction: " + direction, e); 204 } 205 } 206 return codecName; 207 } 208 209 @SuppressWarnings("deprecation") 210 private static String getDecoderNameForMime(String mime) { 211 int count = MediaCodecList.getCodecCount(); 212 for (int i = 0; i < count; ++i) { 213 MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i); 214 if (info.isEncoder()) { 215 continue; 216 } 217 218 String[] supportedTypes = info.getSupportedTypes(); 219 for (int j = 0; j < supportedTypes.length; ++j) { 220 if (supportedTypes[j].equalsIgnoreCase(mime)) { 221 return info.getName(); 222 } 223 } 224 } 225 226 return null; 227 } 228 229 private MediaCodecBridge( 230 MediaCodec mediaCodec, String mime, boolean adaptivePlaybackSupported) { 231 assert mediaCodec != null; 232 mMediaCodec = mediaCodec; 233 mMime = mime; 234 mLastPresentationTimeUs = 0; 235 mFlushed = true; 236 mAdaptivePlaybackSupported = adaptivePlaybackSupported; 237 } 238 239 @CalledByNative 240 private static MediaCodecBridge create(String mime, boolean isSecure, int direction) { 241 // Creation of ".secure" codecs sometimes crash instead of throwing exceptions 242 // on pre-JBMR2 devices. 243 if (isSecure && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { 244 return null; 245 } 246 MediaCodec mediaCodec = null; 247 boolean adaptivePlaybackSupported = false; 248 try { 249 // |isSecure| only applies to video decoders. 250 if (mime.startsWith("video") && isSecure && direction == MEDIA_CODEC_DECODER) { 251 String decoderName = getDecoderNameForMime(mime); 252 if (decoderName == null) { 253 return null; 254 } 255 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 256 // To work around an issue that we cannot get the codec info from the secure 257 // decoder, create an insecure decoder first so that we can query its codec 258 // info. http://b/15587335. 259 MediaCodec insecureCodec = MediaCodec.createByCodecName(decoderName); 260 adaptivePlaybackSupported = codecSupportsAdaptivePlayback(insecureCodec, mime); 261 insecureCodec.release(); 262 } 263 mediaCodec = MediaCodec.createByCodecName(decoderName + ".secure"); 264 } else { 265 if (direction == MEDIA_CODEC_ENCODER) { 266 mediaCodec = MediaCodec.createEncoderByType(mime); 267 } else { 268 mediaCodec = MediaCodec.createDecoderByType(mime); 269 adaptivePlaybackSupported = codecSupportsAdaptivePlayback(mediaCodec, mime); 270 } 271 } 272 } catch (Exception e) { 273 Log.e(TAG, "Failed to create MediaCodec: " + mime + ", isSecure: " 274 + isSecure + ", direction: " + direction, e); 275 } 276 277 if (mediaCodec == null) { 278 return null; 279 } 280 return new MediaCodecBridge(mediaCodec, mime, adaptivePlaybackSupported); 281 } 282 283 @CalledByNative 284 private void release() { 285 try { 286 mMediaCodec.release(); 287 } catch (IllegalStateException e) { 288 // The MediaCodec is stuck in a wrong state, possibly due to losing 289 // the surface. 290 Log.e(TAG, "Cannot release media codec", e); 291 } 292 mMediaCodec = null; 293 if (mAudioTrack != null) { 294 mAudioTrack.release(); 295 } 296 } 297 298 @SuppressWarnings("deprecation") 299 @CalledByNative 300 private boolean start() { 301 try { 302 mMediaCodec.start(); 303 mInputBuffers = mMediaCodec.getInputBuffers(); 304 } catch (IllegalStateException e) { 305 Log.e(TAG, "Cannot start the media codec", e); 306 return false; 307 } 308 return true; 309 } 310 311 @CalledByNative 312 private DequeueInputResult dequeueInputBuffer(long timeoutUs) { 313 int status = MEDIA_CODEC_ERROR; 314 int index = -1; 315 try { 316 int indexOrStatus = mMediaCodec.dequeueInputBuffer(timeoutUs); 317 if (indexOrStatus >= 0) { // index! 318 status = MEDIA_CODEC_OK; 319 index = indexOrStatus; 320 } else if (indexOrStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 321 Log.e(TAG, "dequeueInputBuffer: MediaCodec.INFO_TRY_AGAIN_LATER"); 322 status = MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER; 323 } else { 324 Log.e(TAG, "Unexpected index_or_status: " + indexOrStatus); 325 assert false; 326 } 327 } catch (Exception e) { 328 Log.e(TAG, "Failed to dequeue input buffer", e); 329 } 330 return new DequeueInputResult(status, index); 331 } 332 333 @CalledByNative 334 private int flush() { 335 try { 336 mFlushed = true; 337 if (mAudioTrack != null) { 338 // Need to call pause() here, or otherwise flush() is a no-op. 339 mAudioTrack.pause(); 340 mAudioTrack.flush(); 341 } 342 mMediaCodec.flush(); 343 } catch (IllegalStateException e) { 344 Log.e(TAG, "Failed to flush MediaCodec", e); 345 return MEDIA_CODEC_ERROR; 346 } 347 return MEDIA_CODEC_OK; 348 } 349 350 @CalledByNative 351 private void stop() { 352 mMediaCodec.stop(); 353 if (mAudioTrack != null) { 354 mAudioTrack.pause(); 355 } 356 } 357 358 @CalledByNative 359 private int getOutputHeight() { 360 return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_HEIGHT); 361 } 362 363 @CalledByNative 364 private int getOutputWidth() { 365 return mMediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_WIDTH); 366 } 367 368 @CalledByNative 369 private ByteBuffer getInputBuffer(int index) { 370 return mInputBuffers[index]; 371 } 372 373 @CalledByNative 374 private ByteBuffer getOutputBuffer(int index) { 375 return mOutputBuffers[index]; 376 } 377 378 @CalledByNative 379 private int getInputBuffersCount() { 380 return mInputBuffers.length; 381 } 382 383 @CalledByNative 384 private int getOutputBuffersCount() { 385 return mOutputBuffers != null ? mOutputBuffers.length : -1; 386 } 387 388 @CalledByNative 389 private int getOutputBuffersCapacity() { 390 return mOutputBuffers != null ? mOutputBuffers[0].capacity() : -1; 391 } 392 393 @SuppressWarnings("deprecation") 394 @CalledByNative 395 private boolean getOutputBuffers() { 396 try { 397 mOutputBuffers = mMediaCodec.getOutputBuffers(); 398 } catch (IllegalStateException e) { 399 Log.e(TAG, "Cannot get output buffers", e); 400 return false; 401 } 402 return true; 403 } 404 405 @CalledByNative 406 private int queueInputBuffer( 407 int index, int offset, int size, long presentationTimeUs, int flags) { 408 resetLastPresentationTimeIfNeeded(presentationTimeUs); 409 try { 410 mMediaCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags); 411 } catch (Exception e) { 412 Log.e(TAG, "Failed to queue input buffer", e); 413 return MEDIA_CODEC_ERROR; 414 } 415 return MEDIA_CODEC_OK; 416 } 417 418 @CalledByNative 419 private void setVideoBitrate(int bps) { 420 Bundle b = new Bundle(); 421 b.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bps); 422 mMediaCodec.setParameters(b); 423 } 424 425 @CalledByNative 426 private void requestKeyFrameSoon() { 427 Bundle b = new Bundle(); 428 b.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0); 429 mMediaCodec.setParameters(b); 430 } 431 432 @CalledByNative 433 private int queueSecureInputBuffer( 434 int index, int offset, byte[] iv, byte[] keyId, int[] numBytesOfClearData, 435 int[] numBytesOfEncryptedData, int numSubSamples, long presentationTimeUs) { 436 resetLastPresentationTimeIfNeeded(presentationTimeUs); 437 try { 438 MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo(); 439 cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, 440 keyId, iv, MediaCodec.CRYPTO_MODE_AES_CTR); 441 mMediaCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, 0); 442 } catch (MediaCodec.CryptoException e) { 443 Log.e(TAG, "Failed to queue secure input buffer", e); 444 if (e.getErrorCode() == MediaCodec.CryptoException.ERROR_NO_KEY) { 445 Log.e(TAG, "MediaCodec.CryptoException.ERROR_NO_KEY"); 446 return MEDIA_CODEC_NO_KEY; 447 } 448 Log.e(TAG, "MediaCodec.CryptoException with error code " + e.getErrorCode()); 449 return MEDIA_CODEC_ERROR; 450 } catch (IllegalStateException e) { 451 Log.e(TAG, "Failed to queue secure input buffer", e); 452 return MEDIA_CODEC_ERROR; 453 } 454 return MEDIA_CODEC_OK; 455 } 456 457 @CalledByNative 458 private void releaseOutputBuffer(int index, boolean render) { 459 try { 460 mMediaCodec.releaseOutputBuffer(index, render); 461 } catch (IllegalStateException e) { 462 // TODO(qinmin): May need to report the error to the caller. crbug.com/356498. 463 Log.e(TAG, "Failed to release output buffer", e); 464 } 465 } 466 467 @SuppressWarnings("deprecation") 468 @CalledByNative 469 private DequeueOutputResult dequeueOutputBuffer(long timeoutUs) { 470 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); 471 int status = MEDIA_CODEC_ERROR; 472 int index = -1; 473 try { 474 int indexOrStatus = mMediaCodec.dequeueOutputBuffer(info, timeoutUs); 475 if (info.presentationTimeUs < mLastPresentationTimeUs) { 476 // TODO(qinmin): return a special code through DequeueOutputResult 477 // to notify the native code the the frame has a wrong presentation 478 // timestamp and should be skipped. 479 info.presentationTimeUs = mLastPresentationTimeUs; 480 } 481 mLastPresentationTimeUs = info.presentationTimeUs; 482 483 if (indexOrStatus >= 0) { // index! 484 status = MEDIA_CODEC_OK; 485 index = indexOrStatus; 486 } else if (indexOrStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { 487 status = MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED; 488 } else if (indexOrStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { 489 status = MEDIA_CODEC_OUTPUT_FORMAT_CHANGED; 490 } else if (indexOrStatus == MediaCodec.INFO_TRY_AGAIN_LATER) { 491 status = MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER; 492 } else { 493 Log.e(TAG, "Unexpected index_or_status: " + indexOrStatus); 494 assert false; 495 } 496 } catch (IllegalStateException e) { 497 Log.e(TAG, "Failed to dequeue output buffer", e); 498 } 499 500 return new DequeueOutputResult( 501 status, index, info.flags, info.offset, info.presentationTimeUs, info.size); 502 } 503 504 @CalledByNative 505 private boolean configureVideo(MediaFormat format, Surface surface, MediaCrypto crypto, 506 int flags) { 507 try { 508 if (mAdaptivePlaybackSupported) { 509 format.setInteger(MediaFormat.KEY_MAX_WIDTH, MAX_ADAPTIVE_PLAYBACK_WIDTH); 510 format.setInteger(MediaFormat.KEY_MAX_HEIGHT, MAX_ADAPTIVE_PLAYBACK_HEIGHT); 511 } 512 mMediaCodec.configure(format, surface, crypto, flags); 513 return true; 514 } catch (IllegalStateException e) { 515 Log.e(TAG, "Cannot configure the video codec", e); 516 } 517 return false; 518 } 519 520 @CalledByNative 521 private static MediaFormat createAudioFormat(String mime, int sampleRate, int channelCount) { 522 return MediaFormat.createAudioFormat(mime, sampleRate, channelCount); 523 } 524 525 @CalledByNative 526 private static MediaFormat createVideoDecoderFormat(String mime, int width, int height) { 527 return MediaFormat.createVideoFormat(mime, width, height); 528 } 529 530 @CalledByNative 531 private static MediaFormat createVideoEncoderFormat(String mime, int width, int height, 532 int bitRate, int frameRate, int iFrameInterval, int colorFormat) { 533 MediaFormat format = MediaFormat.createVideoFormat(mime, width, height); 534 format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); 535 format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); 536 format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); 537 format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); 538 return format; 539 } 540 541 @CalledByNative 542 private boolean isAdaptivePlaybackSupported(int width, int height) { 543 if (!mAdaptivePlaybackSupported) 544 return false; 545 return width <= MAX_ADAPTIVE_PLAYBACK_WIDTH && height <= MAX_ADAPTIVE_PLAYBACK_HEIGHT; 546 } 547 548 private static boolean codecSupportsAdaptivePlayback(MediaCodec mediaCodec, String mime) { 549 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT || mediaCodec == null) { 550 return false; 551 } 552 try { 553 MediaCodecInfo info = mediaCodec.getCodecInfo(); 554 if (info.isEncoder()) { 555 return false; 556 } 557 MediaCodecInfo.CodecCapabilities capabilities = info.getCapabilitiesForType(mime); 558 return (capabilities != null) && capabilities.isFeatureSupported( 559 MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback); 560 } catch (IllegalArgumentException e) { 561 Log.e(TAG, "Cannot retrieve codec information", e); 562 } 563 return false; 564 } 565 566 @CalledByNative 567 private static void setCodecSpecificData(MediaFormat format, int index, byte[] bytes) { 568 String name = null; 569 if (index == 0) { 570 name = "csd-0"; 571 } else if (index == 1) { 572 name = "csd-1"; 573 } 574 if (name != null) { 575 format.setByteBuffer(name, ByteBuffer.wrap(bytes)); 576 } 577 } 578 579 @CalledByNative 580 private static void setFrameHasADTSHeader(MediaFormat format) { 581 format.setInteger(MediaFormat.KEY_IS_ADTS, 1); 582 } 583 584 @CalledByNative 585 private boolean configureAudio(MediaFormat format, MediaCrypto crypto, int flags, 586 boolean playAudio) { 587 try { 588 mMediaCodec.configure(format, null, crypto, flags); 589 if (playAudio) { 590 int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); 591 int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); 592 int channelConfig = getAudioFormat(channelCount); 593 // Using 16bit PCM for output. Keep this value in sync with 594 // kBytesPerAudioOutputSample in media_codec_bridge.cc. 595 int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, 596 AudioFormat.ENCODING_PCM_16BIT); 597 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig, 598 AudioFormat.ENCODING_PCM_16BIT, minBufferSize, AudioTrack.MODE_STREAM); 599 if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) { 600 mAudioTrack = null; 601 return false; 602 } 603 } 604 return true; 605 } catch (IllegalStateException e) { 606 Log.e(TAG, "Cannot configure the audio codec", e); 607 } 608 return false; 609 } 610 611 /** 612 * Play the audio buffer that is passed in. 613 * 614 * @param buf Audio buffer to be rendered. 615 * @return The number of frames that have already been consumed by the 616 * hardware. This number resets to 0 after each flush call. 617 */ 618 @CalledByNative 619 private long playOutputBuffer(byte[] buf) { 620 if (mAudioTrack == null) { 621 return 0; 622 } 623 624 if (AudioTrack.PLAYSTATE_PLAYING != mAudioTrack.getPlayState()) { 625 mAudioTrack.play(); 626 } 627 int size = mAudioTrack.write(buf, 0, buf.length); 628 if (buf.length != size) { 629 Log.i(TAG, "Failed to send all data to audio output, expected size: " + 630 buf.length + ", actual size: " + size); 631 } 632 // TODO(qinmin): Returning the head position allows us to estimate 633 // the current presentation time in native code. However, it is 634 // better to use AudioTrack.getCurrentTimestamp() to get the last 635 // known time when a frame is played. However, we will need to 636 // convert the java nano time to C++ timestamp. 637 // If the stream runs too long, getPlaybackHeadPosition() could 638 // overflow. AudioTimestampHelper in MediaSourcePlayer has the same 639 // issue. See http://crbug.com/358801. 640 return mAudioTrack.getPlaybackHeadPosition(); 641 } 642 643 @SuppressWarnings("deprecation") 644 @CalledByNative 645 private void setVolume(double volume) { 646 if (mAudioTrack != null) { 647 mAudioTrack.setStereoVolume((float) volume, (float) volume); 648 } 649 } 650 651 private void resetLastPresentationTimeIfNeeded(long presentationTimeUs) { 652 if (mFlushed) { 653 mLastPresentationTimeUs = 654 Math.max(presentationTimeUs - MAX_PRESENTATION_TIMESTAMP_SHIFT_US, 0); 655 mFlushed = false; 656 } 657 } 658 659 private int getAudioFormat(int channelCount) { 660 switch (channelCount) { 661 case 1: 662 return AudioFormat.CHANNEL_OUT_MONO; 663 case 2: 664 return AudioFormat.CHANNEL_OUT_STEREO; 665 case 4: 666 return AudioFormat.CHANNEL_OUT_QUAD; 667 case 6: 668 return AudioFormat.CHANNEL_OUT_5POINT1; 669 case 8: 670 return AudioFormat.CHANNEL_OUT_7POINT1; 671 default: 672 return AudioFormat.CHANNEL_OUT_DEFAULT; 673 } 674 } 675} 676