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 } 187