• 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         // If the duration is too long, set to 0 to force the caller
83         // not to preallocate space.  See crbug.com/326856.
84         // FIXME: What should be the limit? We're arbitrarily using
85         // about 2148 sec (35.8 min).
86         if (durationMicroseconds > 0x7fffffff) {
87             durationMicroseconds = 0;
88         }
89 
90         Log.d(LOG_TAG, "Initial: Tracks: " + extractor.getTrackCount() +
91               " Format: " + format);
92 
93         // Create decoder
94         MediaCodec codec;
95         try {
96             codec = MediaCodec.createDecoderByType(mime);
97         } catch (Exception e) {
98             Log.w(LOG_TAG, "Failed to create MediaCodec for mime type: " + mime);
99             encodedFD.detachFd();
100             return false;
101         }
102 
103         codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
104         codec.start();
105 
106         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
107         ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
108 
109         // A track must be selected and will be used to read samples.
110         extractor.selectTrack(0);
111 
112         boolean sawInputEOS = false;
113         boolean sawOutputEOS = false;
114         boolean destinationInitialized = false;
115 
116         // Keep processing until the output is done.
117         while (!sawOutputEOS) {
118             if (!sawInputEOS) {
119                 // Input side
120                 int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECONDS);
121 
122                 if (inputBufIndex >= 0) {
123                     ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
124                     int sampleSize = extractor.readSampleData(dstBuf, 0);
125                     long presentationTimeMicroSec = 0;
126 
127                     if (sampleSize < 0) {
128                         sawInputEOS = true;
129                         sampleSize = 0;
130                     } else {
131                         presentationTimeMicroSec = extractor.getSampleTime();
132                     }
133 
134                     codec.queueInputBuffer(inputBufIndex,
135                                            0, /* offset */
136                                            sampleSize,
137                                            presentationTimeMicroSec,
138                                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
139 
140                     if (!sawInputEOS) {
141                         extractor.advance();
142                     }
143                 }
144             }
145 
146             // Output side
147             MediaCodec.BufferInfo info = new BufferInfo();
148             final int outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSECONDS);
149 
150             if (outputBufIndex >= 0) {
151                 ByteBuffer buf = codecOutputBuffers[outputBufIndex];
152 
153                 if (!destinationInitialized) {
154                     // Initialize the destination as late as possible to
155                     // catch any changes in format. But be sure to
156                     // initialize it BEFORE we send any decoded audio,
157                     // and only initialize once.
158                     Log.d(LOG_TAG, "Final:  Rate: " + sampleRate +
159                           " Channels: " + inputChannelCount +
160                           " Mime: " + mime +
161                           " Duration: " + durationMicroseconds + " microsec");
162 
163                     nativeInitializeDestination(nativeMediaCodecBridge,
164                                                 inputChannelCount,
165                                                 sampleRate,
166                                                 durationMicroseconds);
167                     destinationInitialized = true;
168                 }
169 
170                 if (destinationInitialized && info.size > 0) {
171                     nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
172                                          inputChannelCount, outputChannelCount);
173                 }
174 
175                 buf.clear();
176                 codec.releaseOutputBuffer(outputBufIndex, false /* render */);
177 
178                 if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
179                     sawOutputEOS = true;
180                 }
181             } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
182                 codecOutputBuffers = codec.getOutputBuffers();
183             } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
184                 MediaFormat newFormat = codec.getOutputFormat();
185                 outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
186                 sampleRate = newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
187                 Log.d(LOG_TAG, "output format changed to " + newFormat);
188             }
189         }
190 
191         encodedFD.detachFd();
192 
193         codec.stop();
194         codec.release();
195         codec = null;
196 
197         return true;
198     }
199 
nativeOnChunkDecoded( long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size, int inputChannelCount, int outputChannelCount)200     private static native void nativeOnChunkDecoded(
201         long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size,
202         int inputChannelCount, int outputChannelCount);
203 
nativeInitializeDestination( long nativeWebAudioMediaCodecBridge, int inputChannelCount, int sampleRate, long durationMicroseconds)204     private static native void nativeInitializeDestination(
205         long nativeWebAudioMediaCodecBridge,
206         int inputChannelCount,
207         int sampleRate,
208         long durationMicroseconds);
209 }
210