| Index: media/base/android/java/src/org/chromium/media/AudioTrackOutputStream.java
|
| diff --git a/media/base/android/java/src/org/chromium/media/AudioTrackOutputStream.java b/media/base/android/java/src/org/chromium/media/AudioTrackOutputStream.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..54045e4bcda85d42eeb65e229e589b775c2eb0fc
|
| --- /dev/null
|
| +++ b/media/base/android/java/src/org/chromium/media/AudioTrackOutputStream.java
|
| @@ -0,0 +1,142 @@
|
| +// Copyright 2015 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.annotation.TargetApi;
|
| +import android.media.AudioFormat;
|
| +import android.media.AudioManager;
|
| +import android.media.AudioTrack;
|
| +
|
| +import org.chromium.base.Log;
|
| +import org.chromium.base.annotations.CalledByNative;
|
| +import org.chromium.base.annotations.JNINamespace;
|
| +
|
| +import java.nio.ByteBuffer;
|
| +
|
| +@JNINamespace("media")
|
| +class AudioTrackOutputStream implements AudioTrack.OnPlaybackPositionUpdateListener {
|
| + private static final String TAG = "cr.media";
|
| +
|
| + private AudioTrack mAudioTrack;
|
| + private long mNativeAudioTrackOutputStream;
|
| + private int mBufferSizeInBytes;
|
| + private ByteBuffer mAudioBuffer;
|
| + private long mPeriodCount;
|
| +
|
| + @CalledByNative
|
| + private static AudioTrackOutputStream create() {
|
| + return new AudioTrackOutputStream();
|
| + }
|
| +
|
| + @CalledByNative
|
| + private boolean open(int channelCount, int sampleRate, int sampleFormat, int framesPerBuffer) {
|
| + assert (channelCount == 1 || channelCount == 2);
|
| + assert (sampleFormat == AudioFormat.ENCODING_PCM_16BIT || sampleFormat == AudioFormat.ENCODING_PCM_FLOAT);
|
| +
|
| + int bytesPerSample = sampleFormat == AudioFormat.ENCODING_PCM_16BIT ? 2 : 4;
|
| + mBufferSizeInBytes = framesPerBuffer * channelCount * bytesPerSample;
|
| + int allocatedBufferSize = (int)(mBufferSizeInBytes * 3);
|
| + allocatedBufferSize += allocatedBufferSize % (channelCount * bytesPerSample);
|
| +
|
| + // Size the playout buffer at 1.5 times the requested size; this allows
|
| + // us to refill at the halfway mark and have room to spare.
|
| + try {
|
| + mAudioTrack = new AudioTrack(
|
| + AudioManager.STREAM_MUSIC, sampleRate,
|
| + channelCount == 1 ? AudioFormat.CHANNEL_OUT_MONO
|
| + : AudioFormat.CHANNEL_OUT_STEREO,
|
| + sampleFormat, allocatedBufferSize, AudioTrack.MODE_STREAM);
|
| + } catch (IllegalArgumentException ile) {
|
| + Log.e(TAG, "Exception creating AudioTrack for playback: ", ile);
|
| + return false;
|
| + }
|
| +
|
| + if (mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED) {
|
| + Log.e(TAG, "Failed to create AudioTrack...");
|
| + return false;
|
| + }
|
| +
|
| + // Setup periodic callbacks to deliver audio data.
|
| + mAudioTrack.setPlaybackPositionUpdateListener(this);
|
| + if (mAudioTrack.setPositionNotificationPeriod(framesPerBuffer) != AudioTrack.SUCCESS) {
|
| + Log.e(TAG, "Failed to set AudioTrack position listener.");
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + @CalledByNative
|
| + private void start(long nativeAudioTrackOutputStream) {
|
| + Log.w(TAG, "AudioTrackOutputStream.start()");
|
| + mPeriodCount = 0;
|
| + mNativeAudioTrackOutputStream = nativeAudioTrackOutputStream;
|
| +
|
| + // Prime the buffer with silence.
|
| + mAudioBuffer = ByteBuffer.allocateDirect(mBufferSizeInBytes);
|
| + int bytesWritten = 0;
|
| + do {
|
| + bytesWritten = mAudioTrack.write(mAudioBuffer.asReadOnlyBuffer(), mBufferSizeInBytes, AudioTrack.WRITE_BLOCKING);
|
| + } while (bytesWritten == mBufferSizeInBytes);
|
| + mAudioTrack.play();
|
| +
|
| + //onPeriodicNotification(mAudioTrack);
|
| + }
|
| +
|
| + @CalledByNative
|
| + private void stop() {
|
| + Log.w(TAG, "AudioTrackOutputStream.stop()");
|
| + mAudioTrack.pause();
|
| + mAudioTrack.flush();
|
| + mNativeAudioTrackOutputStream = 0;
|
| + }
|
| +
|
| + @SuppressWarnings("deprecation")
|
| + @CalledByNative
|
| + private void setVolume(double volume) {
|
| + Log.w(TAG, "AudioTrackOutputStream.setVolume()");
|
| + // Chrome sends the volume in the range [0, 1.0], whereas Android
|
| + // expects the volume to be within [0, getMaxVolume()].
|
| + float scaledVolume = (float)(volume * mAudioTrack.getMaxVolume());
|
| + mAudioTrack.setStereoVolume(scaledVolume, scaledVolume);
|
| + }
|
| +
|
| + @CalledByNative
|
| + private void close() {
|
| + Log.w(TAG, "AudioTrackOutputStream.close()");
|
| + if (mAudioTrack != null)
|
| + mAudioTrack.release();
|
| + }
|
| +
|
| + @Override
|
| + public void onMarkerReached(AudioTrack track) {
|
| + }
|
| +
|
| + @Override
|
| + public void onPeriodicNotification(AudioTrack track) {
|
| + if (mNativeAudioTrackOutputStream == 0)
|
| + return;
|
| + // // Our period is half the requested size and our buffer size is 1.5
|
| + // // times the requested size, so skip every other interval such that we
|
| + // // have enough data left for glitch free playback and enough space to
|
| + // // fill a full request.
|
| + // if (mPeriodCount++ % 2 == 1)
|
| + // return;
|
| +
|
| + // TODO(dalecurtis): Add delay information here.
|
| + // TODO(dalecurtis): Add < lollipop support.
|
| + //short[] nativeAudioData = nativeOnMoreData(nativeAudioTrackOutputStream, 0);
|
| + nativeOnMoreData(mNativeAudioTrackOutputStream, mAudioBuffer, 0);
|
| +
|
| + long start = System.nanoTime();
|
| + mAudioTrack.write(mAudioBuffer.asReadOnlyBuffer(), mBufferSizeInBytes,
|
| + AudioTrack.WRITE_NON_BLOCKING);
|
| + //Log.w(TAG, "Write took: " + (System.nanoTime() - start) / (1000.0 * 1000.0) + "ms");
|
| + }
|
| +
|
| + //private native short[] nativeOnMoreDataOld(long nativeAudioTrackOutputStream, int delayInFrames);
|
| + private native void nativeOnMoreData(long nativeAudioTrackOutputStream, ByteBuffer audioData, int delayInFrames);
|
| + private native void nativeOnError(long nativeAudioTrackOutputStream);
|
| +}
|
|
|