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