blob: 0edd3a7e791cb2f934ce460464c8c56fa2578ae6 [file]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/audio/sync_reader.h"
#include <algorithm>
#include <atomic>
#include <limits>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "media/audio/audio_device_thread.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_switches.h"
#include "services/audio/output_glitch_counter.h"
using media::AudioLatency;
namespace audio {
#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_CHROMEOS)
constexpr double kBufferDurationPercent = 0.95;
#else
constexpr double kBufferDurationPercent = 0.5;
#endif
SyncReader::SyncReader(
base::RepeatingCallback<void(const std::string&)> log_callback,
const media::AudioParameters& params,
base::CancelableSyncSocket* foreign_socket)
: SyncReader(std::move(log_callback),
params,
foreign_socket,
std::make_unique<OutputGlitchCounter>(params.latency_tag())) {}
SyncReader::SyncReader(
base::RepeatingCallback<void(const std::string&)> log_callback,
const media::AudioParameters& params,
base::CancelableSyncSocket* foreign_socket,
std::unique_ptr<OutputGlitchCounter> glitch_counter)
: log_callback_(std::move(log_callback)),
latency_tag_(params.latency_tag()),
mute_audio_for_testing_(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kMuteAudio)),
output_bus_buffer_size_(base::checked_cast<uint32_t>(
media::AudioBus::CalculateMemorySize(params.channels(),
params.frames_per_buffer()))),
maximum_wait_time_(params.GetBufferDuration() * kBufferDurationPercent),
read_timeout_glitch_{.duration = params.GetBufferDuration(), .count = 1},
glitch_counter_(std::move(glitch_counter)) {
base::CheckedNumeric<size_t> memory_size =
media::ComputeAudioOutputBufferSizeChecked(params);
if (!memory_size.IsValid())
return;
shared_memory_region_ =
base::UnsafeSharedMemoryRegion::Create(memory_size.ValueOrDie());
shared_memory_mapping_ = shared_memory_region_.Map();
if (shared_memory_region_.IsValid() && shared_memory_mapping_.IsValid() &&
base::CancelableSyncSocket::CreatePair(&socket_, foreign_socket)) {
auto buffer_span = shared_memory_mapping_.GetMemoryAsSpan<uint8_t>();
auto audio_data =
buffer_span.subspan<sizeof(media::AudioOutputBufferParameters)>();
CHECK_EQ(audio_data.size(), output_bus_buffer_size_);
auto* const buffer =
shared_memory_mapping_.GetMemoryAs<media::AudioOutputBuffer>();
CHECK_EQ(audio_data.data(), buffer->audio);
buffer->params.cumulative_glitch_duration_us = 0;
buffer->params.cumulative_glitch_count = 0;
output_bus_ = media::AudioBus::WrapMemory(params, audio_data);
output_bus_->Zero();
#if BUILDFLAG(ENABLE_PASSTHROUGH_AUDIO_CODECS)
output_bus_->set_is_bitstream_format(params.IsBitstreamFormat());
#else
CHECK(!params.IsBitstreamFormat());
#endif
}
}
SyncReader::~SyncReader() {
OutputGlitchCounter::LogStats log_stats = glitch_counter_->GetLogStats();
TRACE_EVENT_INSTANT("audio", "~SyncReader", "Missed callbacks",
log_stats.miss_count_, "Total callbacks",
log_stats.callback_count_);
log_callback_.Run(base::StringPrintf(
"ASR: number of detected audio glitches: %" PRIuS " out of %" PRIuS,
log_stats.miss_count_, log_stats.callback_count_));
}
bool SyncReader::IsValid() const {
if (output_bus_) {
DCHECK(shared_memory_region_.IsValid());
DCHECK(shared_memory_mapping_.IsValid());
DCHECK_NE(socket_.handle(), base::SyncSocket::kInvalidHandle);
return true;
}
return false;
}
base::UnsafeSharedMemoryRegion SyncReader::TakeSharedMemoryRegion() {
return std::move(shared_memory_region_);
}
// AudioOutputController::SyncReader implementations.
void SyncReader::RequestMoreData(base::TimeDelta delay,
base::TimeTicks delay_timestamp,
const media::AudioGlitchInfo& glitch_info) {
TRACE_EVENT("audio", "SyncReader::RequestMoreData", "this",
static_cast<void*>(this), "delay_timestamp (ms)",
(delay_timestamp - base::TimeTicks()).InMillisecondsF(),
"playout_delay (ms)", delay.InMillisecondsF());
// We don't send arguments over the socket since sending more than 4
// bytes might lead to being descheduled. The reading side will zero
// them when consumed.
auto* const buffer =
shared_memory_mapping_.GetMemoryAs<media::AudioOutputBuffer>();
// Increase the number of skipped frames stored in shared memory.
buffer->params.delay_us = delay.InMicroseconds();
buffer->params.delay_timestamp_us =
(delay_timestamp - base::TimeTicks()).InMicroseconds();
// Add platform glitches to the accumulated glitch info.
pending_glitch_info_.Add(glitch_info);
media::AudioOutputBufferParametersHelper::AddGlitchIncrementToBuffer(
buffer->params, pending_glitch_info_.GetAndReset());
// 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();
constexpr uint32_t kControlSignal = 0;
size_t sent_bytes = socket_.Send(base::byte_span_from_ref(kControlSignal));
if (sent_bytes != sizeof(kControlSignal)) {
// Ensure we don't log consecutive errors as this can lead to a large
// amount of logs.
if (!had_socket_error_) {
had_socket_error_ = true;
static const char* socket_send_failure_message =
"ASR: No room in socket buffer.";
PLOG(WARNING) << socket_send_failure_message;
log_callback_.Run(socket_send_failure_message);
TRACE_EVENT_INSTANT("audio",
perfetto::StaticString(socket_send_failure_message));
}
} else {
had_socket_error_ = false;
// The AudioDeviceThread will only increase its own index if the socket
// write succeeds, so only increase our own index on successful writes in
// order not to get out of sync.
++buffer_index_;
}
}
bool SyncReader::Read(media::AudioBus* dest, bool is_mixing) {
bool missed_callback = !WaitUntilDataIsReady();
glitch_counter_->ReportMissedCallback(missed_callback, is_mixing);
if (missed_callback) {
++renderer_missed_callback_count_;
if (renderer_missed_callback_count_ <= 100 &&
renderer_missed_callback_count_ % 10 == 0) {
LOG(WARNING) << "SyncReader::Read timed out, audio glitch count="
<< renderer_missed_callback_count_;
if (renderer_missed_callback_count_ == 100)
LOG(WARNING) << "(log cap reached, suppressing further logs)";
}
dest->Zero();
// Add IPC glitch to the accumulated glitch info.
pending_glitch_info_.Add(read_timeout_glitch_);
return false;
}
// Zeroed buffers may be discarded immediately when outputing compressed
// bitstream.
if (mute_audio_for_testing_ && !output_bus_->is_bitstream_format()) {
dest->Zero();
return true;
}
#if BUILDFLAG(ENABLE_PASSTHROUGH_AUDIO_CODECS)
if (output_bus_->is_bitstream_format()) {
// For bitstream formats, we need the real data size and PCM frame count.
auto* const buffer =
shared_memory_mapping_.GetMemoryAs<media::AudioOutputBuffer>();
uint32_t data_size = buffer->params.bitstream_data_size;
uint32_t bitstream_frames = buffer->params.bitstream_frames;
if (data_size > output_bus_buffer_size_) {
// Received data doesn't fit in the buffer, shouldn't happen.
dest->Zero();
return true;
}
output_bus_->SetBitstreamSize(data_size);
output_bus_->SetBitstreamFrames(bitstream_frames);
output_bus_->CopyTo(dest);
return true;
}
#endif
// Copy and clip data coming across the shared memory since it's untrusted.
output_bus_->CopyAndClipTo(dest);
return true;
}
void SyncReader::Close() {
constexpr uint32_t kExitSignal = std::numeric_limits<uint32_t>::max() - 1;
socket_.Send(base::byte_span_from_ref(kExitSignal));
socket_.Close();
output_bus_.reset();
}
bool SyncReader::WaitUntilDataIsReady() {
TRACE_EVENT0("audio", "SyncReader::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_t renderer_buffer_index = 0;
while (timeout.InMicroseconds() > 0) {
bytes_received = socket_.ReceiveWithTimeout(
base::byte_span_from_ref(renderer_buffer_index), timeout);
if (bytes_received != sizeof(renderer_buffer_index)) {
bytes_received = 0;
break;
}
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_) {
TRACE_EVENT_INSTANT("audio", "SyncReader::Read timed out");
base::TimeDelta time_since_start = base::TimeTicks::Now() - start_time;
base::UmaHistogramCustomTimes("Media.AudioOutputControllerDataNotReady",
time_since_start, base::Milliseconds(1),
base::Milliseconds(1000), 50);
return false;
}
return true;
}
} // namespace audio