| Index: media/audio/android/audio_track_output_android.cc
|
| diff --git a/media/audio/android/audio_track_output_android.cc b/media/audio/android/audio_track_output_android.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..d65ff193bcf9aee5737069ff7a2e7d1680c648b9
|
| --- /dev/null
|
| +++ b/media/audio/android/audio_track_output_android.cc
|
| @@ -0,0 +1,311 @@
|
| +// Copyright (c) 2011 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.
|
| +
|
| +#include "media/audio/android/audio_track_output_android.h"
|
| +
|
| +#include "base/android/jni_android.h"
|
| +#include "base/logging.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/time.h"
|
| +
|
| +using base::android::AttachCurrentThread;
|
| +using base::android::CheckException;
|
| +
|
| +static const int kTimerIntervalInMilliseconds = 50;
|
| +
|
| +class AudioTrackOutputStream::StreamBuffer {
|
| + public:
|
| + explicit StreamBuffer(uint32 buffer_size);
|
| +
|
| + uint32 ReadStream(uint8* dest, uint32 max_size);
|
| + void ResetBuffer(uint32 data_size);
|
| + uint8* GetWritableBuffer();
|
| + const uint8* ReadBuffer();
|
| + void AdvancePosition(uint32 advance);
|
| +
|
| + uint32 buffer_size() { return buffer_size_; }
|
| + uint32 data_len() { return data_size_ - current_; }
|
| +
|
| + private:
|
| + scoped_array<uint8> buffer_;
|
| + uint32 buffer_size_;
|
| + uint32 data_size_;
|
| + uint32 current_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(StreamBuffer);
|
| +};
|
| +
|
| +AudioTrackOutputStream::StreamBuffer::StreamBuffer(uint32 buffer_size)
|
| + : buffer_(new uint8[buffer_size]),
|
| + buffer_size_(buffer_size),
|
| + data_size_(0),
|
| + current_(0) {
|
| +}
|
| +
|
| +uint32 AudioTrackOutputStream::StreamBuffer::ReadStream(uint8* dest,
|
| + uint32 max_size) {
|
| + uint32 copy_size = data_len() < max_size ? data_len() : max_size;
|
| + memcpy(dest, buffer_.get() + current_, copy_size);
|
| + current_ += copy_size;
|
| + return copy_size;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::StreamBuffer::ResetBuffer(uint32 data_size) {
|
| + CHECK_LE(data_size, buffer_size_);
|
| + data_size_ = data_size;
|
| + current_ = 0;
|
| +}
|
| +
|
| +uint8* AudioTrackOutputStream::StreamBuffer::GetWritableBuffer() {
|
| + return buffer_.get();
|
| +}
|
| +
|
| +const uint8* AudioTrackOutputStream::StreamBuffer::ReadBuffer() {
|
| + return buffer_.get() + current_;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::StreamBuffer::AdvancePosition(uint32 advance) {
|
| + current_ += advance;
|
| + CHECK(current_ <= data_size_);
|
| +}
|
| +
|
| +AudioTrackOutputStream::AudioTrackOutputStream(const AudioParameters& params)
|
| + : source_callback_(NULL),
|
| + params_(params.format,
|
| + params.sample_rate,
|
| + params.bits_per_sample,
|
| + params.samples_per_packet,
|
| + params.channels),
|
| + status_(IDLE),
|
| + volume_(0),
|
| + buffer_size_(0),
|
| + j_class_(NULL),
|
| + j_audio_track_(NULL) {
|
| + data_buffer_.reset(
|
| + new AudioTrackOutputStream::StreamBuffer(params.GetPacketSize()));
|
| +}
|
| +
|
| +AudioTrackOutputStream::~AudioTrackOutputStream() {
|
| + Close();
|
| +
|
| + if (j_class_ && j_audio_track_) {
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| + env->DeleteGlobalRef(j_audio_track_);
|
| + j_audio_track_ = NULL;
|
| + env->DeleteGlobalRef(j_class_);
|
| + j_class_ = NULL;
|
| + }
|
| +}
|
| +
|
| +bool AudioTrackOutputStream::Open() {
|
| + if (!params_.IsValid())
|
| + return false;
|
| +
|
| + if (status_ == OPENED)
|
| + return true;
|
| + else if (status_ != IDLE)
|
| + return false;
|
| +
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + jclass cls = env->FindClass("android/media/AudioTrack");
|
| + CHECK(cls);
|
| + j_class_ = static_cast<jclass>(env->NewGlobalRef(cls));
|
| + env->DeleteLocalRef(cls);
|
| +
|
| + jint channels;
|
| + if (params_.channels == 1)
|
| + channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_MONO");
|
| + else if (params_.channels == 2)
|
| + channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_STEREO");
|
| + else if (params_.channels == 4)
|
| + channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_QUAD");
|
| + else
|
| + return false;
|
| +
|
| + jint bits_per_sample;
|
| + if (params_.bits_per_sample == 16)
|
| + bits_per_sample = GetStaticIntField("AudioFormat", "ENCODING_PCM_16BIT");
|
| + else if (params_.bits_per_sample == 8)
|
| + bits_per_sample = GetStaticIntField("AudioFormat", "ENCODING_PCM_8BIT");
|
| + else
|
| + return false;
|
| +
|
| + jmethodID min_method = env->GetStaticMethodID(j_class_, "getMinBufferSize",
|
| + "(III)I");
|
| + CHECK(min_method);
|
| +
|
| + int min_buffer_size = env->CallStaticIntMethod(
|
| + j_class_, min_method, static_cast<jint>(params_.sample_rate),
|
| + channels, bits_per_sample);
|
| +
|
| + if (params_.GetPacketSize() < min_buffer_size)
|
| + return false;
|
| + buffer_size_ = params_.GetPacketSize();
|
| +
|
| + jmethodID constructor = env->GetMethodID(j_class_, "<init>", "(IIIIII)V");
|
| + CHECK(constructor);
|
| +
|
| + jobject tmp = env->NewObject(
|
| + j_class_, constructor,
|
| + GetStaticIntField("AudioManager", "STREAM_MUSIC"),
|
| + static_cast<jint>(params_.sample_rate), channels, bits_per_sample,
|
| + static_cast<jint>(buffer_size_),
|
| + GetStaticIntField("AudioTrack", "MODE_STREAM"));
|
| +
|
| + CHECK(tmp);
|
| + j_audio_track_ = env->NewGlobalRef(tmp);
|
| + env->DeleteLocalRef(tmp);
|
| +
|
| + status_ = OPENED;
|
| + return true;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::Close() {
|
| + if (!j_audio_track_)
|
| + return;
|
| +
|
| + Stop();
|
| + CallVoidMethod("flush");
|
| + status_ = INVALID;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::Start(AudioSourceCallback* callback) {
|
| + if (status_ != OPENED)
|
| + return;
|
| + if (!j_audio_track_)
|
| + return;
|
| +
|
| + source_callback_ = callback;
|
| + data_buffer_->ResetBuffer(0);
|
| +
|
| + FillAudioBufferTask();
|
| + CallVoidMethod("play");
|
| + status_ = PLAYING;
|
| +
|
| + timer_.Start(
|
| + FROM_HERE,
|
| + base::TimeDelta::FromMilliseconds(kTimerIntervalInMilliseconds),
|
| + this, &AudioTrackOutputStream::FillAudioBufferTask);
|
| +}
|
| +
|
| +void AudioTrackOutputStream::Stop() {
|
| + if (!j_audio_track_)
|
| + return;
|
| +
|
| + if (status_ == PLAYING) {
|
| + timer_.Stop();
|
| + CallVoidMethod("stop");
|
| + status_ = OPENED;
|
| + }
|
| +}
|
| +
|
| +void AudioTrackOutputStream::SetVolume(double volume) {
|
| + volume_ = volume;
|
| +
|
| + if (!j_audio_track_)
|
| + return;
|
| +
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + jmethodID method = env->GetMethodID(j_class_, "setStereoVolume", "(FF)I");
|
| + CHECK(method);
|
| + env->CallIntMethod(j_audio_track_, method, static_cast<jfloat>(volume),
|
| + static_cast<jfloat>(volume));
|
| + CheckException(env);
|
| +}
|
| +
|
| +void AudioTrackOutputStream::GetVolume(double* volume) {
|
| + if (volume)
|
| + *volume = volume_;
|
| +}
|
| +
|
| +// static
|
| +AudioOutputStream* AudioTrackOutputStream::MakeStream(
|
| + const AudioParameters& params) {
|
| + if (params.IsValid())
|
| + return new AudioTrackOutputStream(params);
|
| +
|
| + return NULL;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::CallVoidMethod(std::string method_name) {
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + jmethodID method = env->GetMethodID(j_class_, method_name.c_str(), "()V");
|
| + CHECK(method);
|
| + env->CallVoidMethod(j_audio_track_, method);
|
| + CheckException(env);
|
| +}
|
| +
|
| +jint AudioTrackOutputStream::GetStaticIntField(std::string class_name,
|
| + std::string field_name) {
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| +
|
| + class_name.insert(0, "android/media/");
|
| + jclass cls = env->FindClass(class_name.c_str());
|
| + CHECK(cls);
|
| +
|
| + jfieldID field = env->GetStaticFieldID(cls, field_name.c_str(), "I");
|
| + CHECK(field);
|
| + jint int_field = env->GetStaticIntField(cls, field);
|
| + env->DeleteLocalRef(cls);
|
| + return int_field;
|
| +}
|
| +
|
| +void AudioTrackOutputStream::FillAudioBufferTask() {
|
| + if (status_ != PLAYING)
|
| + return;
|
| +
|
| + JNIEnv* env = AttachCurrentThread();
|
| + CHECK(env);
|
| + jmethodID method = env->GetMethodID(j_class_, "getPlaybackHeadPosition",
|
| + "()I");
|
| + CHECK(method);
|
| +
|
| + int64 position = env->CallIntMethod(j_audio_track_, method);
|
| + CheckException(env);
|
| +
|
| + // Calculate how many bytes we can fill in.
|
| + position *= params_.sample_rate * params_.bits_per_sample *
|
| + params_.channels / 8;
|
| + position %= buffer_size_;
|
| +
|
| + int need_buffer = static_cast<int>(buffer_size_ - position);
|
| + CHECK(need_buffer >= 0 && need_buffer <= buffer_size_);
|
| +
|
| + if (!need_buffer)
|
| + return;
|
| +
|
| + // Fill the internal buffer first.
|
| + if (!data_buffer_->data_len()) {
|
| + uint32 src_data_size = source_callback_->OnMoreData(
|
| + this,
|
| + data_buffer_->GetWritableBuffer(),
|
| + data_buffer_->buffer_size(),
|
| + AudioBuffersState());
|
| + data_buffer_->ResetBuffer(src_data_size);
|
| + }
|
| + need_buffer = std::min(need_buffer,
|
| + static_cast<int>(data_buffer_->data_len()));
|
| +
|
| + // Prepare a Java array that contains the samples.
|
| + jbyteArray buf = env->NewByteArray(need_buffer);
|
| + env->SetByteArrayRegion(
|
| + buf, 0, need_buffer,
|
| + reinterpret_cast<const jbyte*>(data_buffer_->ReadBuffer()));
|
| + data_buffer_->AdvancePosition(need_buffer);
|
| +
|
| + // Invoke method to submit samples.
|
| + method = env->GetMethodID(j_class_, "write", "([BII)I");
|
| + env->CallIntMethod(j_audio_track_, method, buf, static_cast<jint>(0),
|
| + static_cast<jint>(need_buffer));
|
| + CheckException(env);
|
| + env->DeleteLocalRef(buf);
|
| +}
|
|
|