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