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