Index: media/audio/audio_output_mixer.cc |
=================================================================== |
--- media/audio/audio_output_mixer.cc (revision 0) |
+++ media/audio/audio_output_mixer.cc (revision 0) |
@@ -0,0 +1,251 @@ |
+// 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) { |
+ // 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; |
+ } |
+ 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(); |
+ 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(AudioOutputStream* stream, |
+ 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_); |
+ if (proxies_.empty()) |
+ 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) { |
+ AudioOutputProxy* stream_proxy = it->first; |
+ ProxyData* proxy_data = &it->second; |
+ // TODO(enal): We don't know |pending _bytes| for individual stream, and we |
+ // should give that value to individual stream's OnMoreData(). I believe it |
+ // can be used there to evaluate exact position of data it should return. |
+ // Current code "sorta works" if everything works perfectly, but would have |
+ // problems if some of the buffers are only partially filled -- we don't |
+ // know how how much data was in the buffer OS returned to us, so we cannot |
+ // correctly calculate new value. If we know number of buffers we can solve |
+ // the problem by storing not one value but vector of them. |
+ int pending_bytes = std::min(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( |
+ stream_proxy, |
+ actual_dest, |
+ max_size, |
+ AudioBuffersState(pending_bytes, 0)); |
+ |
+ // Should update pending_bytes for each proxy. |
+ // If stream ended, pending_bytes goes down by max_size. |
+ if (actual_size == 0) { |
+ pending_bytes -= max_size; |
+ proxy_data->pending_bytes = std::max(pending_bytes, 0); |
+ continue; |
+ } |
+ |
+ // Otherwise, it goes up by amount of data. It cannot exceed max amount of |
+ // data we can buffer, but we don't know that value. So we increment |
+ // pending_bytes unconditionally but adjust it before actual use (which |
+ // would be on a next OnMoreData() call). |
+ proxy_data->pending_bytes = pending_bytes + actual_size; |
+ |
+ // No need to mix muted stream. |
+ double volume = proxy_data->volume; |
+ if (volume == 0.0) |
+ continue; |
+ |
+ // 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); |
+ } |
+ } |
+ 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 |