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); |
+} |