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