diff options
-rw-r--r-- | chrome/browser/media/audio_stream_indicator.cc | 16 | ||||
-rw-r--r-- | chrome/browser/media/audio_stream_indicator.h | 2 | ||||
-rw-r--r-- | chrome/browser/media/media_capture_devices_dispatcher.cc | 5 | ||||
-rw-r--r-- | chrome/browser/media/media_capture_devices_dispatcher.h | 2 | ||||
-rw-r--r-- | content/browser/renderer_host/media/audio_renderer_host.cc | 364 | ||||
-rw-r--r-- | content/browser/renderer_host/media/audio_renderer_host.h | 38 | ||||
-rw-r--r-- | content/browser/renderer_host/media/audio_renderer_host_unittest.cc | 10 | ||||
-rw-r--r-- | content/public/browser/media_observer.h | 5 | ||||
-rw-r--r-- | media/audio/audio_output_controller.cc | 38 | ||||
-rw-r--r-- | media/audio/audio_output_controller.h | 25 | ||||
-rw-r--r-- | media/audio/audio_output_controller_unittest.cc | 35 | ||||
-rw-r--r-- | media/audio/audio_silence_detector.cc | 134 | ||||
-rw-r--r-- | media/audio/audio_silence_detector.h | 112 | ||||
-rw-r--r-- | media/audio/audio_silence_detector_unittest.cc | 227 | ||||
-rw-r--r-- | media/media.gyp | 3 |
15 files changed, 291 insertions, 725 deletions
diff --git a/chrome/browser/media/audio_stream_indicator.cc b/chrome/browser/media/audio_stream_indicator.cc index 6cd6232..91396ce 100644 --- a/chrome/browser/media/audio_stream_indicator.cc +++ b/chrome/browser/media/audio_stream_indicator.cc @@ -18,16 +18,16 @@ using content::WebContents; AudioStreamIndicator::AudioStreamIndicator() {} AudioStreamIndicator::~AudioStreamIndicator() {} -void AudioStreamIndicator::UpdateWebContentsStatus( - int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible) { +void AudioStreamIndicator::UpdateWebContentsStatus(int render_process_id, + int render_view_id, + int stream_id, + bool playing) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::Bind(&AudioStreamIndicator::UpdateWebContentsStatusOnUIThread, this, - render_process_id, render_view_id, stream_id, - is_playing_and_audible)); + base::Bind(&AudioStreamIndicator::UpdateWebContentsStatusOnUIThread, + this, render_process_id, render_view_id, stream_id, playing)); } bool AudioStreamIndicator::IsPlayingAudio(WebContents* contents) { @@ -56,10 +56,10 @@ void AudioStreamIndicator::UpdateWebContentsStatusOnUIThread( int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible) { + bool playing) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); RenderViewId id(render_process_id, render_view_id); - if (is_playing_and_audible) { + if (playing) { audio_streams_[id].insert(stream_id); } else { std::map<RenderViewId, std::set<int> >::iterator it = diff --git a/chrome/browser/media/audio_stream_indicator.h b/chrome/browser/media/audio_stream_indicator.h index 2ce504e..a996830 100644 --- a/chrome/browser/media/audio_stream_indicator.h +++ b/chrome/browser/media/audio_stream_indicator.h @@ -23,7 +23,7 @@ class AudioStreamIndicator void UpdateWebContentsStatus(int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible); + bool playing); // This method should be called on the UI thread. bool IsPlayingAudio(content::WebContents* contents); diff --git a/chrome/browser/media/media_capture_devices_dispatcher.cc b/chrome/browser/media/media_capture_devices_dispatcher.cc index 1997d69..062012c 100644 --- a/chrome/browser/media/media_capture_devices_dispatcher.cc +++ b/chrome/browser/media/media_capture_devices_dispatcher.cc @@ -204,12 +204,11 @@ void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged( } void MediaCaptureDevicesDispatcher::OnAudioStreamPlayingChanged( - int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible) { + int render_process_id, int render_view_id, int stream_id, bool playing) { audio_stream_indicator_->UpdateWebContentsStatus(render_process_id, render_view_id, stream_id, - is_playing_and_audible); + playing); } void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread( diff --git a/chrome/browser/media/media_capture_devices_dispatcher.h b/chrome/browser/media/media_capture_devices_dispatcher.h index 5186946..f156ffb 100644 --- a/chrome/browser/media/media_capture_devices_dispatcher.h +++ b/chrome/browser/media/media_capture_devices_dispatcher.h @@ -97,7 +97,7 @@ class MediaCaptureDevicesDispatcher : public content::MediaObserver { int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible) OVERRIDE; + bool playing) OVERRIDE; scoped_refptr<MediaStreamCaptureIndicator> GetMediaStreamCaptureIndicator(); diff --git a/content/browser/renderer_host/media/audio_renderer_host.cc b/content/browser/renderer_host/media/audio_renderer_host.cc index 0853b80..f57d182 100644 --- a/content/browser/renderer_host/media/audio_renderer_host.cc +++ b/content/browser/renderer_host/media/audio_renderer_host.cc @@ -5,7 +5,6 @@ #include "content/browser/renderer_host/media/audio_renderer_host.h" #include "base/bind.h" -#include "base/bind_helpers.h" #include "base/metrics/histogram.h" #include "base/process.h" #include "base/shared_memory.h" @@ -24,82 +23,34 @@ using media::AudioBus; namespace content { -class AudioRendererHost::AudioEntry - : public media::AudioOutputController::EventHandler { - public: - AudioEntry(AudioRendererHost* host, - int stream_id, - int render_view_id, - const media::AudioParameters& params, - scoped_ptr<base::SharedMemory> shared_memory, - scoped_ptr<media::AudioOutputController::SyncReader> reader); - virtual ~AudioEntry(); - - int stream_id() const { - return stream_id_; - } - - int render_view_id() const { - return render_view_id_; - } - // TODO(miu): Remove this setter once my IPC clean-up change (in code review) - // lands! - void set_render_view_id(int render_view_id) { - render_view_id_ = render_view_id; - } - - media::AudioOutputController* controller() const { - return controller_; - } - - base::SharedMemory* shared_memory() { - return shared_memory_.get(); - } - - media::AudioOutputController::SyncReader* reader() const { - return reader_.get(); - } +struct AudioRendererHost::AudioEntry { + AudioEntry(); + ~AudioEntry(); - private: - // media::AudioOutputController::EventHandler implementation. - virtual void OnCreated() OVERRIDE; - virtual void OnPlaying() OVERRIDE; - virtual void OnAudible(bool is_audible) OVERRIDE; - virtual void OnPaused() OVERRIDE; - virtual void OnError(int error_code) OVERRIDE; - virtual void OnDeviceChange(int new_buffer_size, int new_sample_rate) - OVERRIDE; + // The AudioOutputController that manages the audio stream. + scoped_refptr<media::AudioOutputController> controller; - AudioRendererHost* const host_; - const int stream_id_; + // The audio stream ID. + int stream_id; // The routing ID of the source render view. - int render_view_id_; - - // The AudioOutputController that manages the audio stream. - const scoped_refptr<media::AudioOutputController> controller_; + int render_view_id; // Shared memory for transmission of the audio data. - const scoped_ptr<base::SharedMemory> shared_memory_; + base::SharedMemory shared_memory; + + // The synchronous reader to be used by the controller. We have the + // ownership of the reader. + scoped_ptr<media::AudioOutputController::SyncReader> reader; - // The synchronous reader to be used by the controller. - const scoped_ptr<media::AudioOutputController::SyncReader> reader_; + // Set to true after we called Close() for the controller. + bool pending_close; }; -AudioRendererHost::AudioEntry::AudioEntry( - AudioRendererHost* host, int stream_id, int render_view_id, - const media::AudioParameters& params, - scoped_ptr<base::SharedMemory> shared_memory, - scoped_ptr<media::AudioOutputController::SyncReader> reader) - : host_(host), - stream_id_(stream_id), - render_view_id_(render_view_id), - ALLOW_THIS_IN_INITIALIZER_LIST( - controller_(media::AudioOutputController::Create( - host->audio_manager_, this, params, reader.get()))), - shared_memory_(shared_memory.Pass()), - reader_(reader.Pass()) { - DCHECK(controller_); +AudioRendererHost::AudioEntry::AudioEntry() + : stream_id(0), + render_view_id(MSG_ROUTING_NONE), + pending_close(false) { } AudioRendererHost::AudioEntry::~AudioEntry() {} @@ -123,77 +74,76 @@ AudioRendererHost::~AudioRendererHost() { } void AudioRendererHost::OnChannelClosing() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - BrowserMessageFilter::OnChannelClosing(); // Since the IPC channel is gone, close all requested audio streams. - AudioEntryMap zombies; - zombies.swap(audio_entries_); - for (AudioEntryMap::iterator i = zombies.begin(); i != zombies.end(); ++i) { - CloseAndDeleteStream(i->second); - } + DeleteEntries(); } void AudioRendererHost::OnDestruct() const { BrowserThread::DeleteOnIOThread::Destruct(this); } -void AudioRendererHost::AudioEntry::OnCreated() { - BrowserThread::PostTask( - BrowserThread::IO, - FROM_HERE, - base::Bind(&AudioRendererHost::DoCompleteCreation, host_, this)); -} - -void AudioRendererHost::AudioEntry::OnPlaying() { +/////////////////////////////////////////////////////////////////////////////// +// media::AudioOutputController::EventHandler implementations. +void AudioRendererHost::OnCreated(media::AudioOutputController* controller) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind( - base::IgnoreResult(&AudioRendererHost::Send), host_, - new AudioMsg_NotifyStreamStateChanged( - stream_id_, media::AudioOutputIPCDelegate::kPlaying))); + &AudioRendererHost::DoCompleteCreation, + this, + make_scoped_refptr(controller))); } -void AudioRendererHost::AudioEntry::OnAudible(bool is_audible) { +void AudioRendererHost::OnPlaying(media::AudioOutputController* controller) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, - base::Bind(&AudioRendererHost::DoNotifyAudibleState, host_, - this, is_audible)); + base::Bind( + &AudioRendererHost::DoSendPlayingMessage, + this, + make_scoped_refptr(controller))); } -void AudioRendererHost::AudioEntry::OnPaused() { +void AudioRendererHost::OnPaused(media::AudioOutputController* controller) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind( - base::IgnoreResult(&AudioRendererHost::Send), host_, - new AudioMsg_NotifyStreamStateChanged( - stream_id_, media::AudioOutputIPCDelegate::kPaused))); + &AudioRendererHost::DoSendPausedMessage, + this, + make_scoped_refptr(controller))); } -void AudioRendererHost::AudioEntry::OnError(int error_code) { +void AudioRendererHost::OnError(media::AudioOutputController* controller, + int error_code) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, - base::Bind(&AudioRendererHost::DeleteEntryOnError, host_, this)); + base::Bind(&AudioRendererHost::DoHandleError, + this, make_scoped_refptr(controller), error_code)); } -void AudioRendererHost::AudioEntry::OnDeviceChange(int new_buffer_size, - int new_sample_rate) { +void AudioRendererHost::OnDeviceChange(media::AudioOutputController* controller, + int new_buffer_size, + int new_sample_rate) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, - base::Bind(base::IgnoreResult(&AudioRendererHost::Send), host_, - new AudioMsg_NotifyDeviceChanged( - stream_id_, new_buffer_size, new_sample_rate))); + base::Bind(&AudioRendererHost::DoSendDeviceChangeMessage, + this, make_scoped_refptr(controller), new_buffer_size, + new_sample_rate)); } -void AudioRendererHost::DoCompleteCreation(AudioEntry* entry) { +void AudioRendererHost::DoCompleteCreation( + media::AudioOutputController* controller) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + if (!peer_handle()) { NOTREACHED() << "Renderer process handle is invalid."; DeleteEntryOnError(entry); @@ -203,15 +153,16 @@ void AudioRendererHost::DoCompleteCreation(AudioEntry* entry) { // Once the audio stream is created then complete the creation process by // mapping shared memory and sharing with the renderer process. base::SharedMemoryHandle foreign_memory_handle; - if (!entry->shared_memory()->ShareToProcess(peer_handle(), - &foreign_memory_handle)) { + if (!entry->shared_memory.ShareToProcess(peer_handle(), + &foreign_memory_handle)) { // If we failed to map and share the shared memory then close the audio // stream and send an error message. DeleteEntryOnError(entry); return; } - AudioSyncReader* reader = static_cast<AudioSyncReader*>(entry->reader()); + AudioSyncReader* reader = + static_cast<AudioSyncReader*>(entry->reader.get()); #if defined(OS_WIN) base::SyncSocket::Handle foreign_socket_handle; @@ -228,29 +179,59 @@ void AudioRendererHost::DoCompleteCreation(AudioEntry* entry) { } Send(new AudioMsg_NotifyStreamCreated( - entry->stream_id(), + entry->stream_id, foreign_memory_handle, foreign_socket_handle, - media::PacketSizeInBytes(entry->shared_memory()->created_size()))); + media::PacketSizeInBytes(entry->shared_memory.created_size()))); } -void AudioRendererHost::DoNotifyAudibleState(AudioEntry* entry, - bool is_audible) { +void AudioRendererHost::DoSendPlayingMessage( + media::AudioOutputController* controller) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - MediaObserver* const media_observer = - GetContentClient()->browser()->GetMediaObserver(); - if (media_observer) { - DVLOG(1) << "AudioRendererHost@" << this - << "::DoNotifyAudibleState(is_audible=" << is_audible - << ") for stream_id=" << entry->stream_id(); + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; - media_observer->OnAudioStreamPlayingChanged( - render_process_id_, entry->render_view_id(), entry->stream_id(), - is_audible); - } -}; + Send(new AudioMsg_NotifyStreamStateChanged( + entry->stream_id, media::AudioOutputIPCDelegate::kPlaying)); +} +void AudioRendererHost::DoSendPausedMessage( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + Send(new AudioMsg_NotifyStreamStateChanged( + entry->stream_id, media::AudioOutputIPCDelegate::kPaused)); +} + +void AudioRendererHost::DoSendDeviceChangeMessage( + media::AudioOutputController* controller, int new_buffer_size, + int new_sample_rate) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + Send(new AudioMsg_NotifyDeviceChanged( + entry->stream_id, new_buffer_size, new_sample_rate)); +} + +void AudioRendererHost::DoHandleError(media::AudioOutputController* controller, + int error_code) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + AudioEntry* entry = LookupByController(controller); + if (!entry) + return; + + DeleteEntryOnError(entry); +} /////////////////////////////////////////////////////////////////////////////// // IPC Messages handler @@ -284,35 +265,54 @@ void AudioRendererHost::OnCreateStream( return; } + media::AudioParameters audio_params(params); + // Calculate output and input memory size. - int output_memory_size = AudioBus::CalculateMemorySize(params); - int frames = params.frames_per_buffer(); + int output_memory_size = AudioBus::CalculateMemorySize(audio_params); + + int frames = audio_params.frames_per_buffer(); int input_memory_size = AudioBus::CalculateMemorySize(input_channels, frames); + scoped_ptr<AudioEntry> entry(new AudioEntry()); + // Create the shared memory and share with the renderer process. // For synchronized I/O (if input_channels > 0) then we allocate // extra memory after the output data for the input data. uint32 io_buffer_size = output_memory_size + input_memory_size; + uint32 shared_memory_size = media::TotalSharedMemorySizeInBytes(io_buffer_size); - scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory()); - if (!shared_memory->CreateAndMapAnonymous(shared_memory_size)) { + if (!entry->shared_memory.CreateAndMapAnonymous(shared_memory_size)) { + // If creation of shared memory failed then send an error message. SendErrorMessage(stream_id); return; } + // Create sync reader and try to initialize it. scoped_ptr<AudioSyncReader> reader( - new AudioSyncReader(shared_memory.get(), params, input_channels)); + new AudioSyncReader(&entry->shared_memory, params, input_channels)); + if (!reader->Init()) { SendErrorMessage(stream_id); return; } - audio_entries_.insert(std::make_pair(stream_id, new AudioEntry( - this, stream_id, MSG_ROUTING_NONE, params, shared_memory.Pass(), - reader.PassAs<media::AudioOutputController::SyncReader>()))); + // If we have successfully created the SyncReader then assign it to the + // entry and construct an AudioOutputController. + entry->reader.reset(reader.release()); + entry->controller = media::AudioOutputController::Create( + audio_manager_, this, audio_params, entry->reader.get()); + if (!entry->controller) { + SendErrorMessage(stream_id); + return; + } + + // If we have created the controller successfully, create an entry and add it + // to the map. + entry->stream_id = stream_id; + audio_entries_.insert(std::make_pair(stream_id, entry.release())); if (media_internals_) media_internals_->OnSetAudioStreamStatus(this, stream_id, "created"); } @@ -331,7 +331,7 @@ void AudioRendererHost::OnAssociateStreamWithProducer(int stream_id, return; } - if (entry->render_view_id() == render_view_id) + if (entry->render_view_id == render_view_id) return; // TODO(miu): Merge "AssociateWithProducer" message into "CreateStream" @@ -339,12 +339,12 @@ void AudioRendererHost::OnAssociateStreamWithProducer(int stream_id, // once" scheme. http://crbug.com/166779 if (mirroring_manager_) { mirroring_manager_->RemoveDiverter( - render_process_id_, entry->render_view_id(), entry->controller()); + render_process_id_, entry->render_view_id, entry->controller); } - entry->set_render_view_id(render_view_id); + entry->render_view_id = render_view_id; if (mirroring_manager_) { mirroring_manager_->AddDiverter( - render_process_id_, entry->render_view_id(), entry->controller()); + render_process_id_, entry->render_view_id, entry->controller); } } @@ -357,9 +357,16 @@ void AudioRendererHost::OnPlayStream(int stream_id) { return; } - entry->controller()->Play(); + entry->controller->Play(); if (media_internals_) media_internals_->OnSetAudioStreamPlaying(this, stream_id, true); + + MediaObserver* media_observer = + GetContentClient()->browser()->GetMediaObserver(); + if (media_observer) { + media_observer->OnAudioStreamPlayingChanged( + render_process_id_, entry->render_view_id, stream_id, true); + } } void AudioRendererHost::OnPauseStream(int stream_id) { @@ -371,9 +378,16 @@ void AudioRendererHost::OnPauseStream(int stream_id) { return; } - entry->controller()->Pause(); + entry->controller->Pause(); if (media_internals_) media_internals_->OnSetAudioStreamPlaying(this, stream_id, false); + + MediaObserver* media_observer = + GetContentClient()->browser()->GetMediaObserver(); + if (media_observer) { + media_observer->OnAudioStreamPlayingChanged( + render_process_id_, entry->render_view_id, stream_id, false); + } } void AudioRendererHost::OnFlushStream(int stream_id) { @@ -385,7 +399,7 @@ void AudioRendererHost::OnFlushStream(int stream_id) { return; } - entry->controller()->Flush(); + entry->controller->Flush(); if (media_internals_) media_internals_->OnSetAudioStreamStatus(this, stream_id, "flushed"); } @@ -416,9 +430,17 @@ void AudioRendererHost::OnSetVolume(int stream_id, double volume) { // Make sure the volume is valid. if (volume < 0 || volume > 1.0) return; - entry->controller()->SetVolume(volume); + entry->controller->SetVolume(volume); if (media_internals_) media_internals_->OnSetAudioStreamVolume(this, stream_id, volume); + + MediaObserver* media_observer = + GetContentClient()->browser()->GetMediaObserver(); + if (media_observer) { + bool playing = volume > 0; + media_observer->OnAudioStreamPlayingChanged( + render_process_id_, entry->render_view_id, stream_id, playing); + } } void AudioRendererHost::SendErrorMessage(int32 stream_id) { @@ -426,18 +448,33 @@ void AudioRendererHost::SendErrorMessage(int32 stream_id) { stream_id, media::AudioOutputIPCDelegate::kError)); } -void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { +void AudioRendererHost::DeleteEntries() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - if (mirroring_manager_) { - mirroring_manager_->RemoveDiverter( - render_process_id_, entry->render_view_id(), entry->controller()); + for (AudioEntryMap::iterator i = audio_entries_.begin(); + i != audio_entries_.end(); ++i) { + CloseAndDeleteStream(i->second); } - entry->controller()->Close( - base::Bind(&AudioRendererHost::DeleteEntry, this, entry)); +} - // Erase the entry identified by |stream_id| from the map. - audio_entries_.erase(entry->stream_id()); +void AudioRendererHost::CloseAndDeleteStream(AudioEntry* entry) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + MediaObserver* media_observer = + GetContentClient()->browser()->GetMediaObserver(); + if (media_observer) { + media_observer->OnAudioStreamPlayingChanged( + render_process_id_, entry->render_view_id, entry->stream_id, false); + } + if (!entry->pending_close) { + if (mirroring_manager_) { + mirroring_manager_->RemoveDiverter( + render_process_id_, entry->render_view_id, entry->controller); + } + entry->controller->Close( + base::Bind(&AudioRendererHost::DeleteEntry, this, entry)); + entry->pending_close = true; + } } void AudioRendererHost::DeleteEntry(AudioEntry* entry) { @@ -446,17 +483,12 @@ void AudioRendererHost::DeleteEntry(AudioEntry* entry) { // Delete the entry when this method goes out of scope. scoped_ptr<AudioEntry> entry_deleter(entry); - // At this point, make the final "say" in audio playback state. - MediaObserver* const media_observer = - GetContentClient()->browser()->GetMediaObserver(); - if (media_observer) { - media_observer->OnAudioStreamPlayingChanged( - render_process_id_, entry->render_view_id(), entry->stream_id(), false); - } + // Erase the entry identified by |stream_id| from the map. + audio_entries_.erase(entry->stream_id); // Notify the media observer. if (media_internals_) - media_internals_->OnDeleteAudioStream(this, entry->stream_id()); + media_internals_->OnDeleteAudioStream(this, entry->stream_id); } void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) { @@ -464,18 +496,40 @@ void AudioRendererHost::DeleteEntryOnError(AudioEntry* entry) { // Sends the error message first before we close the stream because // |entry| is destroyed in DeleteEntry(). - SendErrorMessage(entry->stream_id()); + SendErrorMessage(entry->stream_id); if (media_internals_) - media_internals_->OnSetAudioStreamStatus(this, entry->stream_id(), "error"); + media_internals_->OnSetAudioStreamStatus(this, entry->stream_id, "error"); CloseAndDeleteStream(entry); } AudioRendererHost::AudioEntry* AudioRendererHost::LookupById(int stream_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - AudioEntryMap::const_iterator i = audio_entries_.find(stream_id); - return i != audio_entries_.end() ? i->second : NULL; + AudioEntryMap::iterator i = audio_entries_.find(stream_id); + if (i != audio_entries_.end() && !i->second->pending_close) + return i->second; + return NULL; +} + +AudioRendererHost::AudioEntry* AudioRendererHost::LookupByController( + media::AudioOutputController* controller) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Iterate the map of entries. + // TODO(hclam): Implement a faster look up method. + for (AudioEntryMap::iterator i = audio_entries_.begin(); + i != audio_entries_.end(); ++i) { + if (!i->second->pending_close && controller == i->second->controller.get()) + return i->second; + } + return NULL; +} + +media::AudioOutputController* AudioRendererHost::LookupControllerByIdForTesting( + int stream_id) { + AudioEntry* const entry = LookupById(stream_id); + return entry ? entry->controller : NULL; } } // namespace content diff --git a/content/browser/renderer_host/media/audio_renderer_host.h b/content/browser/renderer_host/media/audio_renderer_host.h index b34eee7..f4733b2 100644 --- a/content/browser/renderer_host/media/audio_renderer_host.h +++ b/content/browser/renderer_host/media/audio_renderer_host.h @@ -63,7 +63,9 @@ class AudioMirroringManager; class MediaInternals; class ResourceContext; -class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter { +class CONTENT_EXPORT AudioRendererHost + : public BrowserMessageFilter, + public media::AudioOutputController::EventHandler { public: // Called from UI thread from the owner of this object. AudioRendererHost(int render_process_id, @@ -77,6 +79,16 @@ class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter { virtual bool OnMessageReceived(const IPC::Message& message, bool* message_was_ok) OVERRIDE; + // AudioOutputController::EventHandler implementations. + virtual void OnCreated(media::AudioOutputController* controller) OVERRIDE; + virtual void OnPlaying(media::AudioOutputController* controller) OVERRIDE; + virtual void OnPaused(media::AudioOutputController* controller) OVERRIDE; + virtual void OnError(media::AudioOutputController* controller, + int error_code) OVERRIDE; + virtual void OnDeviceChange(media::AudioOutputController* controller, + int new_buffer_size, + int new_sample_rate) OVERRIDE; + private: friend class AudioRendererHostTest; friend class BrowserThread; @@ -85,7 +97,7 @@ class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter { FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, CreateMockStream); FRIEND_TEST_ALL_PREFIXES(AudioRendererHostTest, MockStreamDataConversation); - class AudioEntry; + struct AudioEntry; typedef std::map<int, AudioEntry*> AudioEntryMap; virtual ~AudioRendererHost(); @@ -120,14 +132,23 @@ class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter { // Complete the process of creating an audio stream. This will set up the // shared memory or shared socket in low latency mode. - void DoCompleteCreation(AudioEntry* entry); + void DoCompleteCreation(media::AudioOutputController* controller); + + // Send a state change message to the renderer. + void DoSendPlayingMessage(media::AudioOutputController* controller); + void DoSendPausedMessage(media::AudioOutputController* controller); + void DoSendDeviceChangeMessage(media::AudioOutputController* controller, + int new_buffer_size, int new_sample_rate); - // Propagate audible signal to MediaObserver. - void DoNotifyAudibleState(AudioEntry* entry, bool is_audible); + // Handle error coming from audio stream. + void DoHandleError(media::AudioOutputController* controller, int error_code); // Send an error message to the renderer. void SendErrorMessage(int stream_id); + // Delete all audio entry and all audio streams + void DeleteEntries(); + // Closes the stream. The stream is then deleted in DeleteEntry() after it // is closed. void CloseAndDeleteStream(AudioEntry* entry); @@ -143,6 +164,13 @@ class CONTENT_EXPORT AudioRendererHost : public BrowserMessageFilter { // Returns NULL if not found. AudioEntry* LookupById(int stream_id); + // Search for a AudioEntry having the reference to |controller|. + // This method is used to look up an AudioEntry after a controller + // event is received. + AudioEntry* LookupByController(media::AudioOutputController* controller); + + media::AudioOutputController* LookupControllerByIdForTesting(int stream_id); + // ID of the RenderProcessHost that owns this instance. const int render_process_id_; diff --git a/content/browser/renderer_host/media/audio_renderer_host_unittest.cc b/content/browser/renderer_host/media/audio_renderer_host_unittest.cc index 4310e35..eaaadcc 100644 --- a/content/browser/renderer_host/media/audio_renderer_host_unittest.cc +++ b/content/browser/renderer_host/media/audio_renderer_host_unittest.cc @@ -275,16 +275,18 @@ class AudioRendererHostTest : public testing::Test { void SimulateError() { EXPECT_CALL(*observer_, OnSetAudioStreamStatus(_, kStreamId, "error")); - EXPECT_EQ(1u, host_->audio_entries_.size()) + // Find the first AudioOutputController in the AudioRendererHost. + CHECK(host_->audio_entries_.size()) << "Calls Create() before calling this method"; + media::AudioOutputController* controller = + host_->LookupControllerByIdForTesting(kStreamId); + CHECK(controller) << "AudioOutputController not found"; // Expect an error signal sent through IPC. EXPECT_CALL(*host_, OnStreamError(kStreamId)); // Simulate an error sent from the audio device. - AudioRendererHost::AudioEntry* const entry = host_->LookupById(kStreamId); - ASSERT_TRUE(entry); - host_->DeleteEntryOnError(entry); + host_->OnError(controller, 0); SyncWithAudioThread(); // Expect the audio stream record is removed. diff --git a/content/public/browser/media_observer.h b/content/public/browser/media_observer.h index b708d7f..6b61528 100644 --- a/content/public/browser/media_observer.h +++ b/content/public/browser/media_observer.h @@ -43,13 +43,12 @@ class MediaObserver { const MediaStreamDevice& device, MediaRequestState state) = 0; - // Called when an audio stream transitions into playing audible or silent - // sound, and when the stream is stopped. + // Called when an audio stream is played or paused. virtual void OnAudioStreamPlayingChanged( int render_process_id, int render_view_id, int stream_id, - bool is_playing_and_audible) = 0; + bool playing) = 0; protected: virtual ~MediaObserver() {} diff --git a/media/audio/audio_output_controller.cc b/media/audio/audio_output_controller.cc index d162cc2..9120706 100644 --- a/media/audio/audio_output_controller.cc +++ b/media/audio/audio_output_controller.cc @@ -10,7 +10,6 @@ #include "base/threading/platform_thread.h" #include "base/time.h" #include "build/build_config.h" -#include "media/audio/audio_silence_detector.h" #include "media/audio/audio_util.h" #include "media/audio/shared_memory_util.h" @@ -19,17 +18,6 @@ using base::TimeDelta; namespace media { -// Amount of contiguous time where all audio is silent before considering the -// stream to have transitioned and EventHandler::OnAudible() should be called. -static const int kQuestionableSilencePeriodMillis = 50; - -// Sample value range below which audio is considered indistinguishably silent. -// -// TODO(miu): This value should be specified in dbFS units rather than full -// scale. See TODO in audio_silence_detector.h. -static const float kIndistinguishableSilenceThreshold = - 1.0f / 4096.0f; // Note: This is approximately -72 dbFS. - // Polling-related constants. const int AudioOutputController::kPollNumAttempts = 3; const int AudioOutputController::kPollPauseInMilliseconds = 3; @@ -121,7 +109,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) { state_ = kError; // TODO(hclam): Define error types. - handler_->OnError(0); + handler_->OnError(this, 0); return; } @@ -130,7 +118,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) { state_ = kError; // TODO(hclam): Define error types. - handler_->OnError(0); + handler_->OnError(this, 0); return; } @@ -147,7 +135,7 @@ void AudioOutputController::DoCreate(bool is_for_device_change) { // And then report we have been created if we haven't done so already. if (!is_for_device_change) - handler_->OnCreated(); + handler_->OnCreated(this); } void AudioOutputController::DoPlay() { @@ -197,20 +185,12 @@ void AudioOutputController::StartStream() { DCHECK(message_loop_->BelongsToCurrentThread()); state_ = kPlaying; - silence_detector_.reset(new AudioSilenceDetector( - params_.sample_rate(), - TimeDelta::FromMilliseconds(kQuestionableSilencePeriodMillis), - kIndistinguishableSilenceThreshold)); - // We start the AudioOutputStream lazily. AllowEntryToOnMoreIOData(); stream_->Start(this); - // Tell the event handler that we are now playing, and also start the silence - // detection notifications. - handler_->OnPlaying(); - silence_detector_->Start( - base::Bind(&EventHandler::OnAudible, base::Unretained(handler_))); + // Tell the event handler that we are now playing. + handler_->OnPlaying(this); } void AudioOutputController::StopStream() { @@ -223,8 +203,6 @@ void AudioOutputController::StopStream() { } else if (state_ == kPlaying) { stream_->Stop(); DisallowEntryToOnMoreIOData(); - silence_detector_->Stop(true); - silence_detector_.reset(); state_ = kPaused; } } @@ -240,7 +218,7 @@ void AudioOutputController::DoPause() { // Send a special pause mark to the low-latency audio thread. sync_reader_->UpdatePendingBytes(kPauseMark); - handler_->OnPaused(); + handler_->OnPaused(this); } void AudioOutputController::DoFlush() { @@ -281,7 +259,7 @@ void AudioOutputController::DoSetVolume(double volume) { void AudioOutputController::DoReportError(int code) { DCHECK(message_loop_->BelongsToCurrentThread()); if (state_ != kClosed) - handler_->OnError(code); + handler_->OnError(this, code); } int AudioOutputController::OnMoreData(AudioBus* dest, @@ -310,8 +288,6 @@ int AudioOutputController::OnMoreIOData(AudioBus* source, sync_reader_->UpdatePendingBytes( buffers_state.total_bytes() + frames * params_.GetBytesPerFrame()); - silence_detector_->Scan(dest, frames); - AllowEntryToOnMoreIOData(); return frames; } diff --git a/media/audio/audio_output_controller.h b/media/audio/audio_output_controller.h index cfb685f..e8fae22 100644 --- a/media/audio/audio_output_controller.h +++ b/media/audio/audio_output_controller.h @@ -8,9 +8,7 @@ #include "base/atomic_ref_count.h" #include "base/callback.h" #include "base/memory/ref_counted.h" -#include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" -#include "base/timer.h" #include "media/audio/audio_buffers_state.h" #include "media/audio/audio_io.h" #include "media/audio/audio_manager.h" @@ -59,8 +57,6 @@ class MessageLoop; namespace media { -class AudioSilenceDetector; - class MEDIA_EXPORT AudioOutputController : public base::RefCountedThreadSafe<AudioOutputController>, public AudioOutputStream::AudioSourceCallback, @@ -71,12 +67,12 @@ class MEDIA_EXPORT AudioOutputController // following methods are called on the audio manager thread. class MEDIA_EXPORT EventHandler { public: - virtual void OnCreated() = 0; - virtual void OnPlaying() = 0; - virtual void OnAudible(bool is_audible) = 0; - virtual void OnPaused() = 0; - virtual void OnError(int error_code) = 0; - virtual void OnDeviceChange(int new_buffer_size, int new_sample_rate) = 0; + virtual void OnCreated(AudioOutputController* controller) = 0; + virtual void OnPlaying(AudioOutputController* controller) = 0; + virtual void OnPaused(AudioOutputController* controller) = 0; + virtual void OnError(AudioOutputController* controller, int error_code) = 0; + virtual void OnDeviceChange(AudioOutputController* controller, + int new_buffer_size, int new_sample_rate) = 0; protected: virtual ~EventHandler() {} @@ -197,10 +193,6 @@ class MEDIA_EXPORT AudioOutputController void DoStartDiverting(AudioOutputStream* to_stream); void DoStopDiverting(); - // Called at regular intervals during playback to check for a change in - // silence and call EventHandler::OnAudible() when state changes occur. - void MaybeInvokeAudibleCallback(); - // Helper methods that start/stop physical stream. void StartStream(); void StopStream(); @@ -253,11 +245,6 @@ class MEDIA_EXPORT AudioOutputController // (when starting-up a stream). base::WeakPtrFactory<AudioOutputController> weak_this_; - // Scans audio samples from OnMoreIOData() as input and causes - // EventHandler::OnAudbile() to be called whenever a transition to a period of - // silence or non-silence is detected. - scoped_ptr<AudioSilenceDetector> silence_detector_; - DISALLOW_COPY_AND_ASSIGN(AudioOutputController); }; diff --git a/media/audio/audio_output_controller_unittest.cc b/media/audio/audio_output_controller_unittest.cc index 699810f..944d661 100644 --- a/media/audio/audio_output_controller_unittest.cc +++ b/media/audio/audio_output_controller_unittest.cc @@ -38,13 +38,13 @@ class MockAudioOutputControllerEventHandler public: MockAudioOutputControllerEventHandler() {} - MOCK_METHOD0(OnCreated, void()); - MOCK_METHOD0(OnPlaying, void()); - MOCK_METHOD1(OnAudible, void(bool is_audible)); - MOCK_METHOD0(OnPaused, void()); - MOCK_METHOD1(OnError, void(int error_code)); - MOCK_METHOD2(OnDeviceChange, void(int new_buffer_size, int new_sample_rate)); - + MOCK_METHOD1(OnCreated, void(AudioOutputController* controller)); + MOCK_METHOD1(OnPlaying, void(AudioOutputController* controller)); + MOCK_METHOD1(OnPaused, void(AudioOutputController* controller)); + MOCK_METHOD2(OnError, void(AudioOutputController* controller, + int error_code)); + MOCK_METHOD3(OnDeviceChange, void(AudioOutputController* controller, + int new_buffer_size, int new_sample_rate)); private: DISALLOW_COPY_AND_ASSIGN(MockAudioOutputControllerEventHandler); }; @@ -117,7 +117,7 @@ class AudioOutputControllerTest : public testing::Test { kSampleRate, kBitsPerSample, samples_per_packet); if (params_.IsValid()) { - EXPECT_CALL(mock_event_handler_, OnCreated()) + EXPECT_CALL(mock_event_handler_, OnCreated(NotNull())) .WillOnce(SignalEvent(&create_event_)); } @@ -131,17 +131,14 @@ class AudioOutputControllerTest : public testing::Test { } void Play() { - // Expect the event handler to receive one OnPlaying() call and one or more - // OnAudible() calls. - EXPECT_CALL(mock_event_handler_, OnPlaying()) + // Expect the event handler to receive one OnPlaying() call. + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) .WillOnce(SignalEvent(&play_event_)); - EXPECT_CALL(mock_event_handler_, OnAudible(_)) - .Times(AtLeast(1)); // During playback, the mock pretends to provide audio data rendered and // sent from the render process. EXPECT_CALL(mock_sync_reader_, UpdatePendingBytes(_)) - .Times(AtLeast(1)); + .Times(AtLeast(2)); EXPECT_CALL(mock_sync_reader_, Read(_, _)) .WillRepeatedly(DoAll(PopulateBuffer(), SignalEvent(&read_event_), @@ -154,7 +151,7 @@ class AudioOutputControllerTest : public testing::Test { void Pause() { // Expect the event handler to receive one OnPaused() call. - EXPECT_CALL(mock_event_handler_, OnPaused()) + EXPECT_CALL(mock_event_handler_, OnPaused(NotNull())) .WillOnce(SignalEvent(&pause_event_)); controller_->Pause(); @@ -163,9 +160,9 @@ class AudioOutputControllerTest : public testing::Test { void ChangeDevice() { // Expect the event handler to receive one OnPaying() call and no OnPaused() // call. - EXPECT_CALL(mock_event_handler_, OnPlaying()) + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) .WillOnce(SignalEvent(&play_event_)); - EXPECT_CALL(mock_event_handler_, OnPaused()) + EXPECT_CALL(mock_event_handler_, OnPaused(NotNull())) .Times(0); // Simulate a device change event to AudioOutputController from the @@ -179,7 +176,7 @@ class AudioOutputControllerTest : public testing::Test { if (was_playing) { // Expect the handler to receive one OnPlaying() call as a result of the // stream switching. - EXPECT_CALL(mock_event_handler_, OnPlaying()) + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) .WillOnce(SignalEvent(&play_event_)); } @@ -211,7 +208,7 @@ class AudioOutputControllerTest : public testing::Test { if (was_playing) { // Expect the handler to receive one OnPlaying() call as a result of the // stream switching back. - EXPECT_CALL(mock_event_handler_, OnPlaying()) + EXPECT_CALL(mock_event_handler_, OnPlaying(NotNull())) .WillOnce(SignalEvent(&play_event_)); } diff --git a/media/audio/audio_silence_detector.cc b/media/audio/audio_silence_detector.cc deleted file mode 100644 index 4f4bc06..0000000 --- a/media/audio/audio_silence_detector.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2013 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_silence_detector.h" - -#include "base/float_util.h" -#include "base/time.h" -#include "media/base/audio_bus.h" - -using base::AtomicRefCountDec; -using base::AtomicRefCountInc; -using base::AtomicRefCountIsOne; -using base::AtomicRefCountIsZero; - -namespace media { - -AudioSilenceDetector::AudioSilenceDetector( - int sample_rate, - const base::TimeDelta& questionable_silence_period, - float indistinguishable_silence_threshold) - : polling_period_(questionable_silence_period), - frames_before_observing_silence_( - sample_rate * questionable_silence_period.InSecondsF()), - silence_threshold_(indistinguishable_silence_threshold), - frames_silent_so_far_(frames_before_observing_silence_), - observing_silence_(1), - was_audible_(false) { -} - -AudioSilenceDetector::~AudioSilenceDetector() { - DCHECK(thread_checker_.CalledOnValidThread()); - // Note: If active, ~RepeatingTimer() will StopAndAbandon(). -} - -void AudioSilenceDetector::Start(const AudibleCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(notify_is_audible_.is_null()); - DCHECK(!callback.is_null()); - - notify_is_audible_ = callback; - was_audible_ = AtomicRefCountIsZero(&observing_silence_); - notify_is_audible_.Run(was_audible_); - poll_timer_.Start( - FROM_HERE, polling_period_, - this, &AudioSilenceDetector::MaybeInvokeAudibleCallback); -} - -void AudioSilenceDetector::Stop(bool notify_ending_in_silence) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!notify_is_audible_.is_null()); - - poll_timer_.Stop(); - if (notify_ending_in_silence) - notify_is_audible_.Run(false); - notify_is_audible_.Reset(); -} - -void AudioSilenceDetector::Scan(const AudioBus* buffer, int frames) { - // Determine whether the frames just read are probably silence. If enough - // frames of silence have been observed, flip the |observing_silence_| - // boolean, which will be read by another thread. - if (ProbablyContainsSilence(buffer, frames)) { - // Note: Prevent indefinite incrementing of |frames_silent_so_far_|, to - // avoid eventual integer overflow. - if (frames_silent_so_far_ < frames_before_observing_silence_) { - frames_silent_so_far_ += frames; - if (frames_silent_so_far_ >= frames_before_observing_silence_) { - DCHECK(AtomicRefCountIsZero(&observing_silence_)); - AtomicRefCountInc(&observing_silence_); - } - } - } else { - if (frames_silent_so_far_ >= frames_before_observing_silence_) { - DCHECK(AtomicRefCountIsOne(&observing_silence_)); - AtomicRefCountDec(&observing_silence_); - } - frames_silent_so_far_ = 0; - } -} - -void AudioSilenceDetector::MaybeInvokeAudibleCallback() { - DCHECK(thread_checker_.CalledOnValidThread()); - - const bool is_now_audible = AtomicRefCountIsZero(&observing_silence_); - if (was_audible_ && !is_now_audible) - notify_is_audible_.Run(was_audible_ = false); - else if (!was_audible_ && is_now_audible) - notify_is_audible_.Run(was_audible_ = true); -} - -bool AudioSilenceDetector::ProbablyContainsSilence(const AudioBus* buffer, - int num_frames) { - if (!buffer) - return true; - DCHECK_LE(num_frames, buffer->frames()); - if (buffer->frames() <= 0) - return true; - - // Scan the data in each channel. If any one channel contains sound whose - // range of values exceeds |silence_threshold_|, return false immediately. - for (int i = 0; i < buffer->channels(); ++i) { - // Examine the 1st, 2nd (+1), 4th (+2), 7th (+3), 11th (+4), etc. samples, - // checking whether |silence_threshold_| has been breached each time. For - // typical AudioBus sizes, this algorithm will in the worst case examine - // fewer than 10% of the samples. - // - // Note that there *is* a heavy bias in sampling at the beginning of the - // channels, but that doesn't matter. The buffer sizes are simply too - // small. For example, it is commonplace to use 128-sample buffers, which - // represents ~3 ms of audio at 44.1 kHz; and this means that frequencies - // below ~350 Hz will span more than one buffer to make a full cycle. In - // all, the algorithm here is meant to be dirt-simple math that isn't - // susceptible to periodic bias within a single buffer. - const float* p = buffer->channel(i); - const float* const end_of_samples = p + num_frames; - int skip = 1; - float min_value = *p; - float max_value = *p; - for (p += skip; p < end_of_samples; ++skip, p += skip) { - DCHECK(base::IsFinite(*p)); - if (*p < min_value) - min_value = *p; - else if (max_value < *p) - max_value = *p; - if ((max_value - min_value) > silence_threshold_) - return false; - } - } - - return true; -} - -} // namespace media diff --git a/media/audio/audio_silence_detector.h b/media/audio/audio_silence_detector.h deleted file mode 100644 index b6c52a7..0000000 --- a/media/audio/audio_silence_detector.h +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) 2013 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. - -#ifndef MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ -#define MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ - -#include "base/atomic_ref_count.h" -#include "base/callback.h" -#include "base/memory/scoped_ptr.h" -#include "base/threading/thread_checker.h" -#include "base/timer.h" -#include "media/base/media_export.h" - -// An audio silence detector. It is periodically provided an AudioBus by the -// native audio thread, where simple logic determines whether the audio samples -// in each channel in the buffer represent silence. If a long-enough period of -// contiguous silence is observed in all channels, a notification callback is -// run on the thread that constructed AudioSilenceDetector. -// -// Note that extreme care has been taken to make the -// AudioSilenceDetector::Scan() method safe to be called on the native audio -// thread. The code acquires no locks, nor engages in any operation that could -// result in an undetermined/unbounded amount of run-time. Comments in -// audio_silence_detector.cc elaborate further on the silence detection -// algorithm. - -namespace base { -class TimeDelta; -} - -namespace media { - -class AudioBus; - -class MEDIA_EXPORT AudioSilenceDetector { - public: - typedef base::Callback<void(bool)> AudibleCallback; - - // Tunable parameters: |questionable_silence_period| is the amount of time - // where audio must remain silent before triggerring a callback. - // |indistinguishable_silence_threshold| is the value range below which audio - // is considered silent, in full-scale units. - // - // TODO(miu): |indistinguishable_silence_threshold| should be specified in - // dbFS units rather than full-scale. We need a dbFS data type for - // media/audio first. - AudioSilenceDetector(int sample_rate, - const base::TimeDelta& questionable_silence_period, - float indistinguishable_silence_threshold); - - ~AudioSilenceDetector(); - - // Start detecting silence, notifying via the given callback. - void Start(const AudibleCallback& notify_is_audible); - - // Stop detecting silence. If |notify_ending_in_silence| is true, a final - // notify_is_audible(false) call will be made here. - void Stop(bool notify_ending_in_silence); - - // Scan more |frames| of audio data from |buffer|. This is usually called - // within the native audio thread's "more data" callback. - void Scan(const AudioBus* buffer, int frames); - - private: - // Called by |poll_timer_| at regular intervals to determine whether to invoke - // the callback due to a silence state change. - void MaybeInvokeAudibleCallback(); - - // Returns true if the first |num_frames| frames in all channels in |buffer| - // probably contain silence. A simple heuristic is used to quickly examine a - // subset of the samples in each channel, hence the name of this method. - // "Silence" means that the range of the sample values examined does not - // exceed |silence_threshold_|. - bool ProbablyContainsSilence(const AudioBus* buffer, int num_frames); - - // Time between polls for changes in state. - const base::TimeDelta polling_period_; - - // Number of frames of contiguous silence to be observed before setting - // |observing_silence_| to false. - const int frames_before_observing_silence_; - - // Threshold below which audio should be considered indistinguishably silent. - const float silence_threshold_; - - // Number of frames of contiguous silence observed thus far on the native - // audio thread. - int frames_silent_so_far_; - - // Boolean state (0 or 1) set by the native audio thread. This is polled - // regularly by the thread that invokes the callback. - base::AtomicRefCount observing_silence_; - - // Callback for notifying of a detected transition to silence or non-silence. - AudibleCallback notify_is_audible_; - - // Last reported audible state, used for de-duping callback invocations. - bool was_audible_; - - // Fires regularly, calling MaybeInvokeAudibleCallback(). - base::RepeatingTimer<AudioSilenceDetector> poll_timer_; - - // Constructor, destructor and most methods must be called on the same thread. - base::ThreadChecker thread_checker_; - - DISALLOW_COPY_AND_ASSIGN(AudioSilenceDetector); -}; - -} // namespace media - -#endif // MEDIA_AUDIO_AUDIO_SILENCE_DETECTOR_H_ diff --git a/media/audio/audio_silence_detector_unittest.cc b/media/audio/audio_silence_detector_unittest.cc deleted file mode 100644 index 52e6958..0000000 --- a/media/audio/audio_silence_detector_unittest.cc +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2013 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_silence_detector.h" - -#include "base/bind.h" -#include "base/bind_helpers.h" -#include "base/message_loop.h" -#include "base/synchronization/waitable_event.h" -#include "base/threading/thread.h" -#include "base/time.h" -#include "media/base/audio_bus.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using ::testing::_; -using ::testing::InvokeWithoutArgs; -using ::testing::TestWithParam; -using ::testing::Values; - -namespace media { - -static const int kSampleRate = 48000; -static const int kFramesPerBuffer = 128; - -namespace { - -class MockObserver { - public: - MOCK_METHOD1(OnAudible, void(bool)); -}; - -struct TestScenario { - const float* data; - int num_channels; - int num_frames; - float value_range; - - TestScenario(const float* d, int c, int f, float v) - : data(d), num_channels(c), num_frames(f), value_range(v) {} -}; - -} // namespace - -class AudioSilenceDetectorTest : public TestWithParam<TestScenario> { - public: - AudioSilenceDetectorTest() - : audio_manager_thread_(new base::Thread("AudioThread")), - notification_received_(false, false) { - audio_manager_thread_->Start(); - audio_message_loop_ = audio_manager_thread_->message_loop_proxy(); - } - - virtual ~AudioSilenceDetectorTest() { - SyncWithAudioThread(); - } - - AudioSilenceDetector* silence_detector() const { - return silence_detector_.get(); - } - - void StartSilenceDetector(float threshold, MockObserver* observer) { - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&AudioSilenceDetectorTest::StartDetectorOnAudioThread, - base::Unretained(this), threshold, observer)); - SyncWithAudioThread(); - } - - void StopSilenceDetector() { - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&AudioSilenceDetectorTest::StopDetectorOnAudioThread, - base::Unretained(this))); - SyncWithAudioThread(); - } - - // Creates an AudioBus, sized and populated with kFramesPerBuffer frames of - // data. The given test |data| is repeated to fill the buffer. - scoped_ptr<AudioBus> CreatePopulatedBuffer( - const float* data, int num_channels, int num_frames) { - scoped_ptr<AudioBus> bus = AudioBus::Create(num_channels, kFramesPerBuffer); - for (int ch = 0; ch < num_channels; ++ch) { - for (int frames = 0; frames < kFramesPerBuffer; frames += num_frames) { - const int num_to_copy = std::min(num_frames, kFramesPerBuffer - frames); - memcpy(bus->channel(ch) + frames, data + num_frames * ch, - sizeof(float) * num_to_copy); - } - } - return bus.Pass(); - } - - void SignalNotificationReceived() { - notification_received_.Signal(); - } - - void WaitForNotificationReceived() { - notification_received_.Wait(); - } - - // Post a task on the audio thread and block until it is executed. This - // provides a barrier: All previous tasks pending on the audio thread have - // completed before this method returns. - void SyncWithAudioThread() { - base::WaitableEvent done(false, false); - audio_message_loop_->PostTask( - FROM_HERE, - base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); - done.Wait(); - } - - private: - void StartDetectorOnAudioThread(float threshold, MockObserver* observer) { - const AudioSilenceDetector::AudibleCallback notify_is_audible = - base::Bind(&MockObserver::OnAudible, base::Unretained(observer)); - silence_detector_.reset(new AudioSilenceDetector( - kSampleRate, base::TimeDelta::FromMilliseconds(1), threshold)); - silence_detector_->Start(notify_is_audible); - } - - void StopDetectorOnAudioThread() { - silence_detector_->Stop(true); - silence_detector_.reset(); - } - - scoped_ptr<base::Thread> audio_manager_thread_; - scoped_refptr<base::MessageLoopProxy> audio_message_loop_; - - base::WaitableEvent notification_received_; - - scoped_ptr<AudioSilenceDetector> silence_detector_; - - DISALLOW_COPY_AND_ASSIGN(AudioSilenceDetectorTest); -}; - -TEST_P(AudioSilenceDetectorTest, TriggersAtVariousThresholds) { - static const float kThresholdsToTry[] = - { 0.0f, 0.125f, 0.25f, 0.5f, 0.75f, 1.0f }; - - const TestScenario& scenario = GetParam(); - - for (size_t i = 0; i < arraysize(kThresholdsToTry); ++i) { - MockObserver observer; - - // Start the detector. This should cause a single callback to indicate - // we're starting out in silence. - EXPECT_CALL(observer, OnAudible(false)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - StartSilenceDetector(kThresholdsToTry[i], &observer); - WaitForNotificationReceived(); - - // Send more data to the silence detector. For some test scenarios, the - // sound data will trigger a callback. - const bool expect_a_callback = (kThresholdsToTry[i] < scenario.value_range); - if (expect_a_callback) { - EXPECT_CALL(observer, OnAudible(true)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - } - scoped_ptr<AudioBus> bus = CreatePopulatedBuffer( - scenario.data, scenario.num_channels, scenario.num_frames); - silence_detector()->Scan(bus.get(), bus->frames()); - if (expect_a_callback) - WaitForNotificationReceived(); - - // Stop the detector. This should cause another callback to indicate we're - // ending in silence. - EXPECT_CALL(observer, OnAudible(false)) - .WillOnce(InvokeWithoutArgs( - this, &AudioSilenceDetectorTest::SignalNotificationReceived)) - .RetiresOnSaturation(); - StopSilenceDetector(); - WaitForNotificationReceived(); - - SyncWithAudioThread(); - } -} - -static const float kAllZeros[] = { - // left channel - 0.0f, - // right channel - 0.0f -}; - -static const float kAllOnes[] = { - // left channel - 1.0f, - // right channel - 1.0f -}; - -static const float kSilentLeftChannel[] = { - // left channel - 0.5f, 0.5f, 0.5f, 0.5f, - // right channel - 0.0f, 0.25f, 0.0f, 0.0f -}; - -static const float kSilentRightChannel[] = { - // left channel - 1.0f, 1.0f, 0.75f, 0.5f, 1.0f, - // right channel - 0.0f, 0.0f, 0.0f, 0.0f, 0.0f -}; - -static const float kAtDifferentVolumesAndBias[] = { - // left channel - 1.0f, 0.9f, 0.8f, 0.7f, 0.6f, 0.5f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, - // right channel - 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f, 0.0f, 0.2f, 0.4f, 0.6f, 0.4f, 0.2f -}; - -INSTANTIATE_TEST_CASE_P( - Scenarios, AudioSilenceDetectorTest, - Values( - TestScenario(kAllZeros, 2, 1, 0.0f), - TestScenario(kAllOnes, 2, 1, 0.0f), - TestScenario(kSilentLeftChannel, 2, 4, 0.25f), - TestScenario(kSilentRightChannel, 2, 5, 0.5f), - TestScenario(kAtDifferentVolumesAndBias, 2, 12, 0.6f))); - -} // namespace media diff --git a/media/media.gyp b/media/media.gyp index c6977e4..ee117a0 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -97,8 +97,6 @@ 'audio/audio_output_proxy.h', 'audio/audio_output_resampler.cc', 'audio/audio_output_resampler.h', - 'audio/audio_silence_detector.cc', - 'audio/audio_silence_detector.h', 'audio/audio_source_diverter.h', 'audio/audio_util.cc', 'audio/audio_util.h', @@ -859,7 +857,6 @@ 'audio/audio_output_device_unittest.cc', 'audio/audio_output_proxy_unittest.cc', 'audio/audio_parameters_unittest.cc', - 'audio/audio_silence_detector_unittest.cc', 'audio/audio_util_unittest.cc', 'audio/cross_process_notification_unittest.cc', 'audio/fake_audio_consumer_unittest.cc', |