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