Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / media / base / android / java / src / org / chromium / media / MediaCodecBridge.java
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
5 package org.chromium.media;
6
7 import android.media.AudioFormat;
8 import android.media.AudioManager;
9 import android.media.AudioTrack;
10 import android.media.MediaCodec;
11 import android.media.MediaCodecInfo;
12 import android.media.MediaCodecList;
13 import android.media.MediaCrypto;
14 import android.media.MediaFormat;
15 import android.os.Build;
16 import android.os.Bundle;
17 import android.util.Log;
18 import android.view.Surface;
19
20 import org.chromium.base.CalledByNative;
21 import org.chromium.base.JNINamespace;
22
23 import java.nio.ByteBuffer;
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.Map;
27
28 /**
29  * A wrapper of the MediaCodec class to facilitate exception capturing and
30  * audio rendering.
31  */
32 @JNINamespace("media")
33 class 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 }