| // 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 "content/browser/renderer_host/media/audio_sync_reader.h" |
| |
| #include <algorithm> |
| |
| #include "base/command_line.h" |
| #include "base/memory/shared_memory.h" |
| #include "base/metrics/histogram.h" |
| #include "content/public/common/content_switches.h" |
| #include "media/audio/audio_buffers_state.h" |
| #include "media/audio/audio_parameters.h" |
| |
| using media::AudioBus; |
| |
| namespace content { |
| |
| AudioSyncReader::AudioSyncReader(base::SharedMemory* shared_memory, |
| const media::AudioParameters& params) |
| : shared_memory_(shared_memory), |
| mute_audio_(CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kMuteAudio)), |
| packet_size_(shared_memory_->requested_size()), |
| renderer_callback_count_(0), |
| renderer_missed_callback_count_(0), |
| #if defined(OS_MACOSX) |
| maximum_wait_time_(params.GetBufferDuration() / 2), |
| #else |
| // TODO(dalecurtis): Investigate if we can reduce this on all platforms. |
| maximum_wait_time_(base::TimeDelta::FromMilliseconds(20)), |
| #endif |
| buffer_index_(0) { |
| DCHECK_EQ(packet_size_, AudioBus::CalculateMemorySize(params)); |
| output_bus_ = AudioBus::WrapMemory(params, shared_memory->memory()); |
| output_bus_->Zero(); |
| } |
| |
| AudioSyncReader::~AudioSyncReader() { |
| if (!renderer_callback_count_) |
| return; |
| |
| // Recording the percentage of deadline misses gives us a rough overview of |
| // how many users might be running into audio glitches. |
| int percentage_missed = |
| 100.0 * renderer_missed_callback_count_ / renderer_callback_count_; |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Media.AudioRendererMissedDeadline", percentage_missed); |
| } |
| |
| // media::AudioOutputController::SyncReader implementations. |
| void AudioSyncReader::UpdatePendingBytes(uint32 bytes) { |
| // Zero out the entire output buffer to avoid stuttering/repeating-buffers |
| // in the anomalous case if the renderer is unable to keep up with real-time. |
| output_bus_->Zero(); |
| socket_->Send(&bytes, sizeof(bytes)); |
| ++buffer_index_; |
| } |
| |
| void AudioSyncReader::Read(AudioBus* dest) { |
| ++renderer_callback_count_; |
| if (!WaitUntilDataIsReady()) { |
| ++renderer_missed_callback_count_; |
| dest->Zero(); |
| return; |
| } |
| |
| if (mute_audio_) |
| dest->Zero(); |
| else |
| output_bus_->CopyTo(dest); |
| } |
| |
| void AudioSyncReader::Close() { |
| socket_->Close(); |
| } |
| |
| bool AudioSyncReader::Init() { |
| socket_.reset(new base::CancelableSyncSocket()); |
| foreign_socket_.reset(new base::CancelableSyncSocket()); |
| return base::CancelableSyncSocket::CreatePair(socket_.get(), |
| foreign_socket_.get()); |
| } |
| |
| #if defined(OS_WIN) |
| bool AudioSyncReader::PrepareForeignSocketHandle( |
| base::ProcessHandle process_handle, |
| base::SyncSocket::Handle* foreign_handle) { |
| ::DuplicateHandle(GetCurrentProcess(), foreign_socket_->handle(), |
| process_handle, foreign_handle, |
| 0, FALSE, DUPLICATE_SAME_ACCESS); |
| return (*foreign_handle != 0); |
| } |
| #else |
| bool AudioSyncReader::PrepareForeignSocketHandle( |
| base::ProcessHandle process_handle, |
| base::FileDescriptor* foreign_handle) { |
| foreign_handle->fd = foreign_socket_->handle(); |
| foreign_handle->auto_close = false; |
| return (foreign_handle->fd != -1); |
| } |
| #endif |
| |
| bool AudioSyncReader::WaitUntilDataIsReady() { |
| base::TimeDelta timeout = maximum_wait_time_; |
| const base::TimeTicks start_time = base::TimeTicks::Now(); |
| const base::TimeTicks finish_time = start_time + timeout; |
| |
| // Check if data is ready and if not, wait a reasonable amount of time for it. |
| // |
| // Data readiness is achieved via parallel counters, one on the renderer side |
| // and one here. Every time a buffer is requested via UpdatePendingBytes(), |
| // |buffer_index_| is incremented. Subsequently every time the renderer has a |
| // buffer ready it increments its counter and sends the counter value over the |
| // SyncSocket. Data is ready when |buffer_index_| matches the counter value |
| // received from the renderer. |
| // |
| // The counter values may temporarily become out of sync if the renderer is |
| // unable to deliver audio fast enough. It's assumed that the renderer will |
| // catch up at some point, which means discarding counter values read from the |
| // SyncSocket which don't match our current buffer index. |
| size_t bytes_received = 0; |
| uint32 renderer_buffer_index = 0; |
| while (timeout.InMicroseconds() > 0) { |
| bytes_received = socket_->ReceiveWithTimeout( |
| &renderer_buffer_index, sizeof(renderer_buffer_index), timeout); |
| if (!bytes_received) |
| break; |
| |
| DCHECK_EQ(bytes_received, sizeof(renderer_buffer_index)); |
| if (renderer_buffer_index == buffer_index_) |
| break; |
| |
| // Reduce the timeout value as receives succeed, but aren't the right index. |
| timeout = finish_time - base::TimeTicks::Now(); |
| } |
| |
| // Receive timed out or another error occurred. Receive can timeout if the |
| // renderer is unable to deliver audio data within the allotted time. |
| if (!bytes_received || renderer_buffer_index != buffer_index_) { |
| DVLOG(2) << "AudioSyncReader::WaitUntilDataIsReady() timed out."; |
| |
| base::TimeDelta time_since_start = base::TimeTicks::Now() - start_time; |
| UMA_HISTOGRAM_CUSTOM_TIMES("Media.AudioOutputControllerDataNotReady", |
| time_since_start, |
| base::TimeDelta::FromMilliseconds(1), |
| base::TimeDelta::FromMilliseconds(1000), |
| 50); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace content |