| // Copyright (c) 2012 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/audio_output_mixer.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/message_loop.h" |
| #include "base/time.h" |
| #include "media/audio/audio_io.h" |
| #include "media/audio/audio_output_proxy.h" |
| #include "media/audio/audio_util.h" |
| |
| namespace media { |
| |
| AudioOutputMixer::AudioOutputMixer(AudioManager* audio_manager, |
| const AudioParameters& params, |
| const base::TimeDelta& close_delay) |
| : AudioOutputDispatcher(audio_manager, params), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_this_(this)), |
| close_timer_(FROM_HERE, |
| close_delay, |
| weak_this_.GetWeakPtr(), |
| &AudioOutputMixer::ClosePhysicalStream), |
| pending_bytes_(0) { |
| // TODO(enal): align data. |
| mixer_data_.reset(new uint8[params_.GetBytesPerBuffer()]); |
| } |
| |
| AudioOutputMixer::~AudioOutputMixer() { |
| } |
| |
| bool AudioOutputMixer::OpenStream() { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| if (physical_stream_.get()) |
| return true; |
| AudioOutputStream* stream = audio_manager_->MakeAudioOutputStream(params_); |
| if (!stream) |
| return false; |
| if (!stream->Open()) { |
| stream->Close(); |
| return false; |
| } |
| pending_bytes_ = 0; // Just in case. |
| physical_stream_.reset(stream); |
| close_timer_.Reset(); |
| return true; |
| } |
| |
| bool AudioOutputMixer::StartStream( |
| AudioOutputStream::AudioSourceCallback* callback, |
| AudioOutputProxy* stream_proxy) { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| // May need to re-open the physical stream if no active proxies and |
| // enough time had pass. |
| OpenStream(); |
| if (!physical_stream_.get()) |
| return false; |
| |
| double volume = 0.0; |
| stream_proxy->GetVolume(&volume); |
| bool should_start = proxies_.empty(); |
| { |
| base::AutoLock lock(lock_); |
| ProxyData* proxy_data = &proxies_[stream_proxy]; |
| proxy_data->audio_source_callback = callback; |
| proxy_data->volume = volume; |
| proxy_data->pending_bytes = 0; |
| } |
| // We cannot start physical stream under the lock, |
| // OnMoreData() would try acquiring it... |
| if (should_start) { |
| physical_stream_->SetVolume(1.0); |
| physical_stream_->Start(this); |
| } |
| return true; |
| } |
| |
| void AudioOutputMixer::StopStream(AudioOutputProxy* stream_proxy) { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| // Because of possible deadlock we cannot stop physical stream under the lock |
| // (physical_stream_->Stop() can call OnError(), and it acquires the lock to |
| // iterate through proxies), so acquire the lock, update proxy list, release |
| // the lock, and only then stop physical stream if necessary. |
| bool stop_physical_stream = false; |
| { |
| base::AutoLock lock(lock_); |
| ProxyMap::iterator it = proxies_.find(stream_proxy); |
| if (it != proxies_.end()) { |
| proxies_.erase(it); |
| stop_physical_stream = proxies_.empty(); |
| } |
| } |
| if (physical_stream_.get()) { |
| if (stop_physical_stream) { |
| physical_stream_->Stop(); |
| pending_bytes_ = 0; // Just in case. |
| } |
| close_timer_.Reset(); |
| } |
| } |
| |
| void AudioOutputMixer::StreamVolumeSet(AudioOutputProxy* stream_proxy, |
| double volume) { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| ProxyMap::iterator it = proxies_.find(stream_proxy); |
| |
| // Do nothing if stream is not currently playing. |
| if (it != proxies_.end()) { |
| base::AutoLock lock(lock_); |
| it->second.volume = volume; |
| } |
| } |
| |
| void AudioOutputMixer::CloseStream(AudioOutputProxy* stream_proxy) { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| StopStream(stream_proxy); |
| } |
| |
| void AudioOutputMixer::Shutdown() { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| // Cancel any pending tasks to close physical stream. |
| weak_this_.InvalidateWeakPtrs(); |
| |
| while (!proxies_.empty()) { |
| CloseStream(proxies_.begin()->first); |
| } |
| ClosePhysicalStream(); |
| |
| // No AudioOutputProxy objects should hold a reference to us when we get |
| // to this stage. |
| DCHECK(HasOneRef()) << "Only the AudioManager should hold a reference"; |
| } |
| |
| void AudioOutputMixer::ClosePhysicalStream() { |
| DCHECK_EQ(MessageLoop::current(), message_loop_); |
| |
| if (proxies_.empty() && physical_stream_.get() != NULL) |
| physical_stream_.release()->Close(); |
| } |
| |
| // AudioSourceCallback implementation. |
| uint32 AudioOutputMixer::OnMoreData(uint8* dest, |
| uint32 max_size, |
| AudioBuffersState buffers_state) { |
| max_size = std::min(max_size, |
| static_cast<uint32>(params_.GetBytesPerBuffer())); |
| // TODO(enal): consider getting rid of lock as it is in time-critical code. |
| // E.g. swap |proxies_| with local variable, and merge 2 lists |
| // at the end. That would speed things up but complicate stopping |
| // the stream. |
| base::AutoLock lock(lock_); |
| |
| DCHECK_GE(pending_bytes_, buffers_state.pending_bytes); |
| if (proxies_.empty()) { |
| pending_bytes_ = buffers_state.pending_bytes; |
| return 0; |
| } |
| uint32 actual_total_size = 0; |
| uint32 bytes_per_sample = params_.bits_per_sample() >> 3; |
| |
| // Go through all the streams, getting data for every one of them |
| // and mixing it into destination. |
| // Minor optimization: for the first stream we are writing data directly into |
| // destination. This way we don't have to mix the data when there is only one |
| // active stream, and net win in other cases, too. |
| bool first_stream = true; |
| uint8* actual_dest = dest; |
| for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) { |
| ProxyData* proxy_data = &it->second; |
| |
| // If proxy's pending bytes are the same as pending bytes for combined |
| // stream, both are either pre-buffering or in the steady state. In either |
| // case new pending bytes for proxy is the same as new pending bytes for |
| // combined stream. |
| // Note: use >= instead of ==, that way is safer. |
| if (proxy_data->pending_bytes >= pending_bytes_) |
| proxy_data->pending_bytes = buffers_state.pending_bytes; |
| |
| // Note: there is no way we can deduce hardware_delay_bytes for the |
| // particular proxy stream. Use zero instead. |
| uint32 actual_size = proxy_data->audio_source_callback->OnMoreData( |
| actual_dest, |
| max_size, |
| AudioBuffersState(proxy_data->pending_bytes, 0)); |
| if (actual_size == 0) |
| continue; |
| double volume = proxy_data->volume; |
| |
| // Different handling for first and all subsequent streams. |
| if (first_stream) { |
| if (volume != 1.0) { |
| media::AdjustVolume(actual_dest, |
| actual_size, |
| params_.channels(), |
| bytes_per_sample, |
| volume); |
| } |
| if (actual_size < max_size) |
| memset(dest + actual_size, 0, max_size - actual_size); |
| first_stream = false; |
| actual_dest = mixer_data_.get(); |
| actual_total_size = actual_size; |
| } else { |
| media::MixStreams(dest, |
| actual_dest, |
| actual_size, |
| bytes_per_sample, |
| volume); |
| actual_total_size = std::max(actual_size, actual_total_size); |
| } |
| } |
| |
| // Now go through all proxies once again and increase pending_bytes |
| // for each proxy. Could not do it earlier because we did not know |
| // actual_total_size. |
| for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) { |
| it->second.pending_bytes += actual_total_size; |
| } |
| pending_bytes_ = buffers_state.pending_bytes + actual_total_size; |
| |
| return actual_total_size; |
| } |
| |
| void AudioOutputMixer::OnError(AudioOutputStream* stream, int code) { |
| base::AutoLock lock(lock_); |
| for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) { |
| it->second.audio_source_callback->OnError(it->first, code); |
| } |
| } |
| |
| void AudioOutputMixer::WaitTillDataReady() { |
| base::AutoLock lock(lock_); |
| for (ProxyMap::iterator it = proxies_.begin(); it != proxies_.end(); ++it) { |
| it->second.audio_source_callback->WaitTillDataReady(); |
| } |
| } |
| |
| } // namespace media |