OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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 #include "media/audio/android/audio_track_output_android.h" |
| 6 |
| 7 #include "base/android/jni_android.h" |
| 8 #include "base/logging.h" |
| 9 #include "base/memory/scoped_ptr.h" |
| 10 #include "base/time.h" |
| 11 |
| 12 using base::android::AttachCurrentThread; |
| 13 using base::android::CheckException; |
| 14 |
| 15 static const int kTimerIntervalInMilliseconds = 50; |
| 16 |
| 17 class AudioTrackOutputStream::StreamBuffer { |
| 18 public: |
| 19 explicit StreamBuffer(uint32 buffer_size); |
| 20 |
| 21 uint32 ReadStream(uint8* dest, uint32 max_size); |
| 22 void ResetBuffer(uint32 data_size); |
| 23 uint8* GetWritableBuffer(); |
| 24 const uint8* ReadBuffer(); |
| 25 void AdvancePosition(uint32 advance); |
| 26 |
| 27 uint32 buffer_size() { return buffer_size_; } |
| 28 uint32 data_len() { return data_size_ - current_; } |
| 29 |
| 30 private: |
| 31 scoped_array<uint8> buffer_; |
| 32 uint32 buffer_size_; |
| 33 uint32 data_size_; |
| 34 uint32 current_; |
| 35 |
| 36 DISALLOW_COPY_AND_ASSIGN(StreamBuffer); |
| 37 }; |
| 38 |
| 39 AudioTrackOutputStream::StreamBuffer::StreamBuffer(uint32 buffer_size) |
| 40 : buffer_(new uint8[buffer_size]), |
| 41 buffer_size_(buffer_size), |
| 42 data_size_(0), |
| 43 current_(0) { |
| 44 } |
| 45 |
| 46 uint32 AudioTrackOutputStream::StreamBuffer::ReadStream(uint8* dest, |
| 47 uint32 max_size) { |
| 48 uint32 copy_size = data_len() < max_size ? data_len() : max_size; |
| 49 memcpy(dest, buffer_.get() + current_, copy_size); |
| 50 current_ += copy_size; |
| 51 return copy_size; |
| 52 } |
| 53 |
| 54 void AudioTrackOutputStream::StreamBuffer::ResetBuffer(uint32 data_size) { |
| 55 CHECK_LE(data_size, buffer_size_); |
| 56 data_size_ = data_size; |
| 57 current_ = 0; |
| 58 } |
| 59 |
| 60 uint8* AudioTrackOutputStream::StreamBuffer::GetWritableBuffer() { |
| 61 return buffer_.get(); |
| 62 } |
| 63 |
| 64 const uint8* AudioTrackOutputStream::StreamBuffer::ReadBuffer() { |
| 65 return buffer_.get() + current_; |
| 66 } |
| 67 |
| 68 void AudioTrackOutputStream::StreamBuffer::AdvancePosition(uint32 advance) { |
| 69 current_ += advance; |
| 70 CHECK(current_ <= data_size_); |
| 71 } |
| 72 |
| 73 AudioTrackOutputStream::AudioTrackOutputStream(const AudioParameters& params) |
| 74 : source_callback_(NULL), |
| 75 params_(params.format, |
| 76 params.sample_rate, |
| 77 params.bits_per_sample, |
| 78 params.samples_per_packet, |
| 79 params.channels), |
| 80 status_(IDLE), |
| 81 volume_(0), |
| 82 buffer_size_(0), |
| 83 j_class_(NULL), |
| 84 j_audio_track_(NULL) { |
| 85 data_buffer_.reset( |
| 86 new AudioTrackOutputStream::StreamBuffer(params.GetPacketSize())); |
| 87 } |
| 88 |
| 89 AudioTrackOutputStream::~AudioTrackOutputStream() { |
| 90 Close(); |
| 91 |
| 92 if (j_class_ && j_audio_track_) { |
| 93 JNIEnv* env = AttachCurrentThread(); |
| 94 CHECK(env); |
| 95 env->DeleteGlobalRef(j_audio_track_); |
| 96 j_audio_track_ = NULL; |
| 97 env->DeleteGlobalRef(j_class_); |
| 98 j_class_ = NULL; |
| 99 } |
| 100 } |
| 101 |
| 102 bool AudioTrackOutputStream::Open() { |
| 103 if (!params_.IsValid()) |
| 104 return false; |
| 105 |
| 106 if (status_ == OPENED) |
| 107 return true; |
| 108 else if (status_ != IDLE) |
| 109 return false; |
| 110 |
| 111 JNIEnv* env = AttachCurrentThread(); |
| 112 CHECK(env); |
| 113 |
| 114 jclass cls = env->FindClass("android/media/AudioTrack"); |
| 115 CHECK(cls); |
| 116 j_class_ = static_cast<jclass>(env->NewGlobalRef(cls)); |
| 117 env->DeleteLocalRef(cls); |
| 118 |
| 119 jint channels; |
| 120 if (params_.channels == 1) |
| 121 channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_MONO"); |
| 122 else if (params_.channels == 2) |
| 123 channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_STEREO"); |
| 124 else if (params_.channels == 4) |
| 125 channels = GetStaticIntField("AudioFormat", "CHANNEL_OUT_QUAD"); |
| 126 else |
| 127 return false; |
| 128 |
| 129 jint bits_per_sample; |
| 130 if (params_.bits_per_sample == 16) |
| 131 bits_per_sample = GetStaticIntField("AudioFormat", "ENCODING_PCM_16BIT"); |
| 132 else if (params_.bits_per_sample == 8) |
| 133 bits_per_sample = GetStaticIntField("AudioFormat", "ENCODING_PCM_8BIT"); |
| 134 else |
| 135 return false; |
| 136 |
| 137 jmethodID min_method = env->GetStaticMethodID(j_class_, "getMinBufferSize", |
| 138 "(III)I"); |
| 139 CHECK(min_method); |
| 140 |
| 141 int min_buffer_size = env->CallStaticIntMethod( |
| 142 j_class_, min_method, static_cast<jint>(params_.sample_rate), |
| 143 channels, bits_per_sample); |
| 144 |
| 145 if (params_.GetPacketSize() < min_buffer_size) |
| 146 return false; |
| 147 buffer_size_ = params_.GetPacketSize(); |
| 148 |
| 149 jmethodID constructor = env->GetMethodID(j_class_, "<init>", "(IIIIII)V"); |
| 150 CHECK(constructor); |
| 151 |
| 152 jobject tmp = env->NewObject( |
| 153 j_class_, constructor, |
| 154 GetStaticIntField("AudioManager", "STREAM_MUSIC"), |
| 155 static_cast<jint>(params_.sample_rate), channels, bits_per_sample, |
| 156 static_cast<jint>(buffer_size_), |
| 157 GetStaticIntField("AudioTrack", "MODE_STREAM")); |
| 158 |
| 159 CHECK(tmp); |
| 160 j_audio_track_ = env->NewGlobalRef(tmp); |
| 161 env->DeleteLocalRef(tmp); |
| 162 |
| 163 status_ = OPENED; |
| 164 return true; |
| 165 } |
| 166 |
| 167 void AudioTrackOutputStream::Close() { |
| 168 if (!j_audio_track_) |
| 169 return; |
| 170 |
| 171 Stop(); |
| 172 CallVoidMethod("flush"); |
| 173 status_ = INVALID; |
| 174 } |
| 175 |
| 176 void AudioTrackOutputStream::Start(AudioSourceCallback* callback) { |
| 177 if (status_ != OPENED) |
| 178 return; |
| 179 if (!j_audio_track_) |
| 180 return; |
| 181 |
| 182 source_callback_ = callback; |
| 183 data_buffer_->ResetBuffer(0); |
| 184 |
| 185 FillAudioBufferTask(); |
| 186 CallVoidMethod("play"); |
| 187 status_ = PLAYING; |
| 188 |
| 189 timer_.Start( |
| 190 FROM_HERE, |
| 191 base::TimeDelta::FromMilliseconds(kTimerIntervalInMilliseconds), |
| 192 this, &AudioTrackOutputStream::FillAudioBufferTask); |
| 193 } |
| 194 |
| 195 void AudioTrackOutputStream::Stop() { |
| 196 if (!j_audio_track_) |
| 197 return; |
| 198 |
| 199 if (status_ == PLAYING) { |
| 200 timer_.Stop(); |
| 201 CallVoidMethod("stop"); |
| 202 status_ = OPENED; |
| 203 } |
| 204 } |
| 205 |
| 206 void AudioTrackOutputStream::SetVolume(double volume) { |
| 207 volume_ = volume; |
| 208 |
| 209 if (!j_audio_track_) |
| 210 return; |
| 211 |
| 212 JNIEnv* env = AttachCurrentThread(); |
| 213 CHECK(env); |
| 214 |
| 215 jmethodID method = env->GetMethodID(j_class_, "setStereoVolume", "(FF)I"); |
| 216 CHECK(method); |
| 217 env->CallIntMethod(j_audio_track_, method, static_cast<jfloat>(volume), |
| 218 static_cast<jfloat>(volume)); |
| 219 CheckException(env); |
| 220 } |
| 221 |
| 222 void AudioTrackOutputStream::GetVolume(double* volume) { |
| 223 if (volume) |
| 224 *volume = volume_; |
| 225 } |
| 226 |
| 227 // static |
| 228 AudioOutputStream* AudioTrackOutputStream::MakeStream( |
| 229 const AudioParameters& params) { |
| 230 if (params.IsValid()) |
| 231 return new AudioTrackOutputStream(params); |
| 232 |
| 233 return NULL; |
| 234 } |
| 235 |
| 236 void AudioTrackOutputStream::CallVoidMethod(std::string method_name) { |
| 237 JNIEnv* env = AttachCurrentThread(); |
| 238 CHECK(env); |
| 239 |
| 240 jmethodID method = env->GetMethodID(j_class_, method_name.c_str(), "()V"); |
| 241 CHECK(method); |
| 242 env->CallVoidMethod(j_audio_track_, method); |
| 243 CheckException(env); |
| 244 } |
| 245 |
| 246 jint AudioTrackOutputStream::GetStaticIntField(std::string class_name, |
| 247 std::string field_name) { |
| 248 JNIEnv* env = AttachCurrentThread(); |
| 249 CHECK(env); |
| 250 |
| 251 class_name.insert(0, "android/media/"); |
| 252 jclass cls = env->FindClass(class_name.c_str()); |
| 253 CHECK(cls); |
| 254 |
| 255 jfieldID field = env->GetStaticFieldID(cls, field_name.c_str(), "I"); |
| 256 CHECK(field); |
| 257 jint int_field = env->GetStaticIntField(cls, field); |
| 258 env->DeleteLocalRef(cls); |
| 259 return int_field; |
| 260 } |
| 261 |
| 262 void AudioTrackOutputStream::FillAudioBufferTask() { |
| 263 if (status_ != PLAYING) |
| 264 return; |
| 265 |
| 266 JNIEnv* env = AttachCurrentThread(); |
| 267 CHECK(env); |
| 268 jmethodID method = env->GetMethodID(j_class_, "getPlaybackHeadPosition", |
| 269 "()I"); |
| 270 CHECK(method); |
| 271 |
| 272 int64 position = env->CallIntMethod(j_audio_track_, method); |
| 273 CheckException(env); |
| 274 |
| 275 // Calculate how many bytes we can fill in. |
| 276 position *= params_.sample_rate * params_.bits_per_sample * |
| 277 params_.channels / 8; |
| 278 position %= buffer_size_; |
| 279 |
| 280 int need_buffer = static_cast<int>(buffer_size_ - position); |
| 281 CHECK(need_buffer >= 0 && need_buffer <= buffer_size_); |
| 282 |
| 283 if (!need_buffer) |
| 284 return; |
| 285 |
| 286 // Fill the internal buffer first. |
| 287 if (!data_buffer_->data_len()) { |
| 288 uint32 src_data_size = source_callback_->OnMoreData( |
| 289 this, |
| 290 data_buffer_->GetWritableBuffer(), |
| 291 data_buffer_->buffer_size(), |
| 292 AudioBuffersState()); |
| 293 data_buffer_->ResetBuffer(src_data_size); |
| 294 } |
| 295 need_buffer = std::min(need_buffer, |
| 296 static_cast<int>(data_buffer_->data_len())); |
| 297 |
| 298 // Prepare a Java array that contains the samples. |
| 299 jbyteArray buf = env->NewByteArray(need_buffer); |
| 300 env->SetByteArrayRegion( |
| 301 buf, 0, need_buffer, |
| 302 reinterpret_cast<const jbyte*>(data_buffer_->ReadBuffer())); |
| 303 data_buffer_->AdvancePosition(need_buffer); |
| 304 |
| 305 // Invoke method to submit samples. |
| 306 method = env->GetMethodID(j_class_, "write", "([BII)I"); |
| 307 env->CallIntMethod(j_audio_track_, method, buf, static_cast<jint>(0), |
| 308 static_cast<jint>(need_buffer)); |
| 309 CheckException(env); |
| 310 env->DeleteLocalRef(buf); |
| 311 } |
OLD | NEW |