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