blob: 128cee0e31778ab292f59e9a5298a19abd13f24e [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.media;
import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import java.io.File;
import java.nio.ByteBuffer;
@JNINamespace("media")
class WebAudioMediaCodecBridge {
static final String LOG_TAG = "WebAudioMediaCodec";
// TODO(rtoy): What is the correct timeout value for reading
// from a file in memory?
static final long TIMEOUT_MICROSECONDS = 500;
@CalledByNative
private static String createTempFile(Context ctx) throws java.io.IOException {
File outputDirectory = ctx.getCacheDir();
File outputFile = File.createTempFile("webaudio", ".dat", outputDirectory);
return outputFile.getAbsolutePath();
}
@SuppressWarnings("deprecation")
@CalledByNative
private static boolean decodeAudioFile(Context ctx,
long nativeMediaCodecBridge,
int inputFD,
long dataSize) {
if (dataSize < 0 || dataSize > 0x7fffffff)
return false;
MediaExtractor extractor = new MediaExtractor();
ParcelFileDescriptor encodedFD;
encodedFD = ParcelFileDescriptor.adoptFd(inputFD);
try {
extractor.setDataSource(encodedFD.getFileDescriptor(), 0, dataSize);
} catch (Exception e) {
e.printStackTrace();
encodedFD.detachFd();
return false;
}
if (extractor.getTrackCount() <= 0) {
encodedFD.detachFd();
return false;
}
MediaFormat format = extractor.getTrackFormat(0);
// Number of channels specified in the file
int inputChannelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
// Number of channels the decoder will provide. (Not
// necessarily the same as inputChannelCount. See
// crbug.com/266006.)
int outputChannelCount = inputChannelCount;
int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
String mime = format.getString(MediaFormat.KEY_MIME);
long durationMicroseconds = 0;
if (format.containsKey(MediaFormat.KEY_DURATION)) {
try {
durationMicroseconds = format.getLong(MediaFormat.KEY_DURATION);
} catch (Exception e) {
Log.d(LOG_TAG, "Cannot get duration");
}
}
// If the duration is too long, set to 0 to force the caller
// not to preallocate space. See crbug.com/326856.
// FIXME: What should be the limit? We're arbitrarily using
// about 2148 sec (35.8 min).
if (durationMicroseconds > 0x7fffffff) {
durationMicroseconds = 0;
}
Log.d(LOG_TAG, "Initial: Tracks: " + extractor.getTrackCount() + " Format: " + format);
// Create decoder
MediaCodec codec;
try {
codec = MediaCodec.createDecoderByType(mime);
} catch (Exception e) {
Log.w(LOG_TAG, "Failed to create MediaCodec for mime type: " + mime);
encodedFD.detachFd();
return false;
}
codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
codec.start();
ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
// A track must be selected and will be used to read samples.
extractor.selectTrack(0);
boolean sawInputEOS = false;
boolean sawOutputEOS = false;
boolean destinationInitialized = false;
// Keep processing until the output is done.
while (!sawOutputEOS) {
if (!sawInputEOS) {
// Input side
int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_MICROSECONDS);
if (inputBufIndex >= 0) {
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
int sampleSize = extractor.readSampleData(dstBuf, 0);
long presentationTimeMicroSec = 0;
if (sampleSize < 0) {
sawInputEOS = true;
sampleSize = 0;
} else {
presentationTimeMicroSec = extractor.getSampleTime();
}
codec.queueInputBuffer(inputBufIndex,
0, /* offset */
sampleSize,
presentationTimeMicroSec,
sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
if (!sawInputEOS) {
extractor.advance();
}
}
}
// Output side
MediaCodec.BufferInfo info = new BufferInfo();
final int outputBufIndex = codec.dequeueOutputBuffer(info, TIMEOUT_MICROSECONDS);
if (outputBufIndex >= 0) {
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
if (!destinationInitialized) {
// Initialize the destination as late as possible to
// catch any changes in format. But be sure to
// initialize it BEFORE we send any decoded audio,
// and only initialize once.
Log.d(LOG_TAG, "Final: Rate: " + sampleRate
+ " Channels: " + inputChannelCount
+ " Mime: " + mime
+ " Duration: " + durationMicroseconds + " microsec");
nativeInitializeDestination(nativeMediaCodecBridge,
inputChannelCount,
sampleRate,
durationMicroseconds);
destinationInitialized = true;
}
if (destinationInitialized && info.size > 0) {
nativeOnChunkDecoded(nativeMediaCodecBridge, buf, info.size,
inputChannelCount, outputChannelCount);
}
buf.clear();
codec.releaseOutputBuffer(outputBufIndex, false /* render */);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
sawOutputEOS = true;
}
} else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
codecOutputBuffers = codec.getOutputBuffers();
} else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = codec.getOutputFormat();
outputChannelCount = newFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
sampleRate = newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
Log.d(LOG_TAG, "output format changed to " + newFormat);
}
}
encodedFD.detachFd();
codec.stop();
codec.release();
codec = null;
return true;
}
private static native void nativeOnChunkDecoded(
long nativeWebAudioMediaCodecBridge, ByteBuffer buf, int size,
int inputChannelCount, int outputChannelCount);
private static native void nativeInitializeDestination(
long nativeWebAudioMediaCodecBridge,
int inputChannelCount,
int sampleRate,
long durationMicroseconds);
}