OLD | NEW |
| (Empty) |
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 | |
14 import org.chromium.base.Log; | |
15 import org.chromium.base.annotations.CalledByNative; | |
16 import org.chromium.base.annotations.JNINamespace; | |
17 | |
18 import java.io.File; | |
19 import java.nio.ByteBuffer; | |
20 | |
21 @JNINamespace("media") | |
22 class WebAudioMediaCodecBridge { | |
23 private static final String TAG = "cr.media"; | |
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 | |
28 private static String createTempFile(Context ctx) throws java.io.IOException
{ | |
29 File outputDirectory = ctx.getCacheDir(); | |
30 File outputFile = File.createTempFile("webaudio", ".dat", outputDirector
y); | |
31 return outputFile.getAbsolutePath(); | |
32 } | |
33 | |
34 @SuppressWarnings("deprecation") | |
35 @CalledByNative | |
36 private static boolean decodeAudioFile(Context ctx, long nativeMediaCodecBri
dge, | |
37 int inputFD, long dataSize) { | |
38 | |
39 if (dataSize < 0 || dataSize > 0x7fffffff) return false; | |
40 | |
41 MediaExtractor extractor = new MediaExtractor(); | |
42 | |
43 ParcelFileDescriptor encodedFD; | |
44 encodedFD = ParcelFileDescriptor.adoptFd(inputFD); | |
45 try { | |
46 extractor.setDataSource(encodedFD.getFileDescriptor(), 0, dataSize); | |
47 } catch (Exception e) { | |
48 e.printStackTrace(); | |
49 encodedFD.detachFd(); | |
50 return false; | |
51 } | |
52 | |
53 if (extractor.getTrackCount() <= 0) { | |
54 encodedFD.detachFd(); | |
55 return false; | |
56 } | |
57 | |
58 MediaFormat format = extractor.getTrackFormat(0); | |
59 | |
60 // If we are unable to get the input channel count, the sample | |
61 // rate or the mime type for any reason, just give up. | |
62 // Without these, we don't know what to do. | |
63 | |
64 int inputChannelCount; | |
65 try { | |
66 // Number of channels specified in the file | |
67 inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)
; | |
68 } catch (Exception e) { | |
69 // Give up. | |
70 Log.w(TAG, "Unable to determine number of channels"); | |
71 encodedFD.detachFd(); | |
72 return false; | |
73 } | |
74 | |
75 // Number of channels the decoder will provide. (Not | |
76 // necessarily the same as inputChannelCount. See | |
77 // crbug.com/266006.) | |
78 int outputChannelCount = inputChannelCount; | |
79 | |
80 int sampleRate; | |
81 try { | |
82 sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); | |
83 } catch (Exception e) { | |
84 // Give up. | |
85 Log.w(TAG, "Unable to determine sample rate"); | |
86 encodedFD.detachFd(); | |
87 return false; | |
88 } | |
89 | |
90 String mime; | |
91 try { | |
92 mime = format.getString(MediaFormat.KEY_MIME); | |
93 } catch (Exception e) { | |
94 // Give up. | |
95 Log.w(TAG, "Unable to determine type of encoding used by the file"); | |
96 encodedFD.detachFd(); | |
97 return false; | |
98 } | |
99 | |
100 long durationMicroseconds = 0; | |
101 if (format.containsKey(MediaFormat.KEY_DURATION)) { | |
102 try { | |
103 durationMicroseconds = format.getLong(MediaFormat.KEY_DURATION); | |
104 } catch (Exception e) { | |
105 Log.d(TAG, "Cannot get duration"); | |
106 } | |
107 } | |
108 | |
109 // If the duration is too long, set to 0 to force the caller | |
110 // not to preallocate space. See crbug.com/326856. | |
111 // FIXME: What should be the limit? We're arbitrarily using | |
112 // about 2148 sec (35.8 min). | |
113 if (durationMicroseconds > 0x7fffffff) { | |
114 durationMicroseconds = 0; | |
115 } | |
116 | |
117 Log.d(TAG, "Initial: Tracks: %d Format: %s", extractor.getTrackCount(),
format); | |
118 | |
119 // Create decoder | |
120 MediaCodec codec; | |
121 try { | |
122 codec = MediaCodec.createDecoderByType(mime); | |
123 } catch (Exception e) { | |
124 Log.w(TAG, "Failed to create MediaCodec for mime type: %s", mime); | |
125 encodedFD.detachFd(); | |
126 return false; | |
127 } | |
128 | |
129 try { | |
130 codec.configure(format, null /* surface */, null /* crypto */, 0 /*
flags */); | |
131 } catch (Exception e) { | |
132 Log.w(TAG, "Unable to configure codec for format " + format, e); | |
133 encodedFD.detachFd(); | |
134 return false; | |
135 } | |
136 try { | |
137 codec.start(); | |
138 } catch (Exception e) { | |
139 Log.w(TAG, "Unable to start()", e); | |
140 encodedFD.detachFd(); | |
141 return false; | |
142 } | |
143 | |
144 ByteBuffer[] codecInputBuffers; | |
145 try { | |
146 codecInputBuffers = codec.getInputBuffers(); | |
147 } catch (Exception e) { | |
148 Log.w(TAG, "getInputBuffers() failed", e); | |
149 encodedFD.detachFd(); | |
150 return false; | |
151 } | |
152 ByteBuffer[] codecOutputBuffers; | |
153 try { | |
154 codecOutputBuffers = codec.getOutputBuffers(); | |
155 } catch (Exception e) { | |
156 Log.w(TAG, "getOutputBuffers() failed", e); | |
157 encodedFD.detachFd(); | |
158 return false; | |
159 } | |
160 | |
161 // A track must be selected and will be used to read samples. | |
162 extractor.selectTrack(0); | |
163 | |
164 boolean sawInputEOS = false; | |
165 boolean sawOutputEOS = false; | |
166 boolean destinationInitialized = false; | |
167 boolean decodedSuccessfully = true; | |
168 | |
169 // Keep processing until the output is done. | |
170 while (!sawOutputEOS) { | |
171 if (!sawInputEOS) { | |
172 // Input side | |
173 int inputBufIndex; | |
174 try { | |
175 inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECOND
S); | |
176 } catch (Exception e) { | |
177 Log.w(TAG, "dequeueInputBuffer(%d) failed.", TIMEOUT_MICROSE
CONDS, e); | |
178 decodedSuccessfully = false; | |
179 break; | |
180 } | |
181 | |
182 if (inputBufIndex >= 0) { | |
183 ByteBuffer dstBuf = codecInputBuffers[inputBufIndex]; | |
184 int sampleSize; | |
185 | |
186 try { | |
187 sampleSize = extractor.readSampleData(dstBuf, 0); | |
188 } catch (Exception e) { | |
189 Log.w(TAG, "readSampleData failed."); | |
190 decodedSuccessfully = false; | |
191 break; | |
192 } | |
193 | |
194 long presentationTimeMicroSec = 0; | |
195 | |
196 if (sampleSize < 0) { | |
197 sawInputEOS = true; | |
198 sampleSize = 0; | |
199 } else { | |
200 presentationTimeMicroSec = extractor.getSampleTime(); | |
201 } | |
202 | |
203 try { | |
204 codec.queueInputBuffer(inputBufIndex, | |
205 0, /* offset */ | |
206 sampleSize, | |
207 presentationTimeMicroSec, | |
208 sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STRE
AM : 0); | |
209 } catch (Exception e) { | |
210 Log.w(TAG, "queueInputBuffer(%d, 0, %d, %d, %d) failed."
, | |
211 inputBufIndex, sampleSize, presentationTimeMicro
Sec, | |
212 (sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STR
EAM : 0), e); | |
213 decodedSuccessfully = false; | |
214 break; | |
215 } | |
216 | |
217 if (!sawInputEOS) { | |
218 extractor.advance(); | |
219 } | |
220 } | |
221 } | |
222 | |
223 // Output side | |
224 MediaCodec.BufferInfo info = new BufferInfo(); | |
225 final int outputBufIndex; | |
226 | |
227 try { | |
228 outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSE
CONDS); | |
229 } catch (Exception e) { | |
230 Log.w(TAG, "dequeueOutputBuffer(%s, %d) failed", info, TIMEOUT_M
ICROSECONDS); | |
231 e.printStackTrace(); | |
232 decodedSuccessfully = false; | |
233 break; | |
234 } | |
235 | |
236 if (outputBufIndex >= 0) { | |
237 ByteBuffer buf = codecOutputBuffers[outputBufIndex]; | |
238 | |
239 if (!destinationInitialized) { | |
240 // Initialize the destination as late as possible to | |
241 // catch any changes in format. But be sure to | |
242 // initialize it BEFORE we send any decoded audio, | |
243 // and only initialize once. | |
244 Log.d(TAG, "Final: Rate: %d Channels: %d Mime: %s Duration:
%d microsec", | |
245 sampleRate, inputChannelCount, mime, durationMicrose
conds); | |
246 | |
247 nativeInitializeDestination(nativeMediaCodecBridge, | |
248 inputChannelCount, | |
249 sampleRate, | |
250 durationMicroseconds); | |
251 destinationInitialized = true; | |
252 } | |
253 | |
254 if (destinationInitialized && info.size > 0) { | |
255 nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size, | |
256 inputChannelCount, outputChannelCount); | |
257 } | |
258 | |
259 buf.clear(); | |
260 codec.releaseOutputBuffer(outputBufIndex, false /* render */); | |
261 | |
262 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { | |
263 sawOutputEOS = true; | |
264 } | |
265 } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
{ | |
266 codecOutputBuffers = codec.getOutputBuffers(); | |
267 } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
{ | |
268 MediaFormat newFormat = codec.getOutputFormat(); | |
269 outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNE
L_COUNT); | |
270 sampleRate = newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); | |
271 Log.d(TAG, "output format changed to " + newFormat); | |
272 } | |
273 } | |
274 | |
275 encodedFD.detachFd(); | |
276 | |
277 codec.stop(); | |
278 codec.release(); | |
279 codec = null; | |
280 | |
281 return decodedSuccessfully; | |
282 } | |
283 | |
284 private static native void nativeOnChunkDecoded( | |
285 long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size, | |
286 int inputChannelCount, int outputChannelCount); | |
287 | |
288 private static native void nativeInitializeDestination( | |
289 long nativeWebAudioMediaCodecBridge, | |
290 int inputChannelCount, | |
291 int sampleRate, | |
292 long durationMicroseconds); | |
293 } | |
OLD | NEW |