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