• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
8 import android.media.MediaCodec;
9 import android.media.MediaCodec.BufferInfo;
10 import android.media.MediaExtractor;
11 import android.media.MediaFormat;
12 import android.os.ParcelFileDescriptor;
13 import android.util.Log;
14 
15 import org.chromium.base.CalledByNative;
16 import org.chromium.base.JNINamespace;
17 
18 import java.io.File;
19 import java.nio.ByteBuffer;
20 
21 @JNINamespace("media")
22 class WebAudioMediaCodecBridge {
23     static final String LOG_TAG = "WebAudioMediaCodec";
24     // TODO(rtoy): What is the correct timeout value for reading
25     // from a file in memory?
26     static final long TIMEOUT_MICROSECONDS = 500;
27     @CalledByNative
CreateTempFile(Context ctx)28     private static String CreateTempFile(Context ctx) throws java.io.IOException {
29         File outputDirectory = ctx.getCacheDir();
30         File outputFile = File.createTempFile("webaudio", ".dat", outputDirectory);
31         return outputFile.getAbsolutePath();
32     }
33 
34     @CalledByNative
decodeAudioFile(Context ctx, long nativeMediaCodecBridge, int inputFD, long dataSize)35     private static boolean decodeAudioFile(Context ctx,
36                                            long nativeMediaCodecBridge,
37                                            int inputFD,
38                                            long dataSize) {
39 
40         if (dataSize < 0 || dataSize > 0x7fffffff)
41             return false;
42 
43         MediaExtractor extractor = new MediaExtractor();
44 
45         ParcelFileDescriptor encodedFD;
46         encodedFD = ParcelFileDescriptor.adoptFd(inputFD);
47         try {
48             extractor.setDataSource(encodedFD.getFileDescriptor(), 0, dataSize);
49         } catch (Exception e) {
50             e.printStackTrace();
51             encodedFD.detachFd();
52             return false;
53         }
54 
55         if (extractor.getTrackCount() <= 0) {
56             encodedFD.detachFd();
57             return false;
58         }
59 
60         MediaFormat format = extractor.getTrackFormat(0);
61 
62         // Number of channels specified in the file
63         int inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
64 
65         // Number of channels the decoder will provide. (Not
66         // necessarily the same as inputChannelCount.  See
67         // crbug.com/266006.)
68         int outputChannelCount = inputChannelCount;
69 
70         int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
71         String mime = format.getString(MediaFormat.KEY_MIME);
72 
73         long durationMicroseconds = 0;
74         if (format.containsKey(MediaFormat.KEY_DURATION)) {
75             try {
76                 durationMicroseconds = format.getLong(MediaFormat.KEY_DURATION);
77             } catch (Exception e) {
78                 Log.d(LOG_TAG, "Cannot get duration");
79             }
80         }
81 
82         Log.d(LOG_TAG, "Initial: Tracks: " + extractor.getTrackCount() +
83               " Format: " + format);
84 
85         // Create decoder
86         MediaCodec codec;
87         try {
88             codec = MediaCodec.createDecoderByType(mime);
89         } catch (Exception e) {
90             Log.w(LOG_TAG, "Failed to create MediaCodec for mime type: " + mime);
91             encodedFD.detachFd();
92             return false;
93         }
94 
95         codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
96         codec.start();
97 
98         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
99         ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
100 
101         // A track must be selected and will be used to read samples.
102         extractor.selectTrack(0);
103 
104         boolean sawInputEOS = false;
105         boolean sawOutputEOS = false;
106         boolean destinationInitialized = false;
107 
108         // Keep processing until the output is done.
109         while (!sawOutputEOS) {
110             if (!sawInputEOS) {
111                 // Input side
112                 int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECONDS);
113 
114                 if (inputBufIndex >= 0) {
115                     ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
116                     int sampleSize = extractor.readSampleData(dstBuf, 0);
117                     long presentationTimeMicroSec = 0;
118 
119                     if (sampleSize < 0) {
120                         sawInputEOS = true;
121                         sampleSize = 0;
122                     } else {
123                         presentationTimeMicroSec = extractor.getSampleTime();
124                     }
125 
126                     codec.queueInputBuffer(inputBufIndex,
127                                            0, /* offset */
128                                            sampleSize,
129                                            presentationTimeMicroSec,
130                                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
131 
132                     if (!sawInputEOS) {
133                         extractor.advance();
134                     }
135                 }
136             }
137 
138             // Output side
139             MediaCodec.BufferInfo info = new BufferInfo();
140             final int outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSECONDS);
141 
142             if (outputBufIndex >= 0) {
143                 ByteBuffer buf = codecOutputBuffers[outputBufIndex];
144 
145                 if (!destinationInitialized) {
146                     // Initialize the destination as late as possible to
147                     // catch any changes in format. But be sure to
148                     // initialize it BEFORE we send any decoded audio,
149                     // and only initialize once.
150                     Log.d(LOG_TAG, "Final:  Rate: " + sampleRate +
151                           " Channels: " + inputChannelCount +
152                           " Mime: " + mime +
153                           " Duration: " + durationMicroseconds + " microsec");
154 
155                     nativeInitializeDestination(nativeMediaCodecBridge,
156                                                 inputChannelCount,
157                                                 sampleRate,
158                                                 durationMicroseconds);
159                     destinationInitialized = true;
160                 }
161 
162                 if (destinationInitialized && info.size > 0) {
163                     nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
164                                          inputChannelCount, outputChannelCount);
165                 }
166 
167                 buf.clear();
168                 codec.releaseOutputBuffer(outputBufIndex, false /* render */);
169 
170                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
171                     sawOutputEOS = true;
172                 }
173             } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
174                 codecOutputBuffers = codec.getOutputBuffers();
175             } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
176                 MediaFormat newFormat = codec.getOutputFormat();
177                 outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
178                 sampleRate = newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
179                 Log.d(LOG_TAG, "output format changed to " + newFormat);
180             }
181         }
182 
183         encodedFD.detachFd();
184 
185         codec.stop();
186         codec.release();
187         codec = null;
188 
189         return true;
190     }
191 
nativeOnChunkDecoded( long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size, int inputChannelCount, int outputChannelCount)192     private static native void nativeOnChunkDecoded(
193         long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size,
194         int inputChannelCount, int outputChannelCount);
195 
nativeInitializeDestination( long nativeWebAudioMediaCodecBridge, int inputChannelCount, int sampleRate, long durationMicroseconds)196     private static native void nativeInitializeDestination(
197         long nativeWebAudioMediaCodecBridge,
198         int inputChannelCount,
199         int sampleRate,
200         long durationMicroseconds);
201 }
202