#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "media/audio/fuchsia/audio_output_stream_fuchsia.h"
#include <fuchsia/media/cpp/fidl.h>
#include <lib/sys/cpp/component_context.h>
#include <zircon/syscalls.h>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/process_context.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/writable_shared_memory_region.h"
#include "media/audio/fuchsia/audio_manager_fuchsia.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_sample_types.h"
#include "media/base/audio_timestamp_helper.h"
namespace media {
namespace {
const uint32_t kBufferId = 0;
std::optional<fuchsia::media::AudioRenderUsage> GetStreamUsage(
const AudioParameters& parameters) {
int usage_flags = parameters.effects() &
(AudioParameters::FUCHSIA_RENDER_USAGE_BACKGROUND |
AudioParameters::FUCHSIA_RENDER_USAGE_MEDIA |
AudioParameters::FUCHSIA_RENDER_USAGE_INTERRUPTION |
AudioParameters::FUCHSIA_RENDER_USAGE_SYSTEM_AGENT |
AudioParameters::FUCHSIA_RENDER_USAGE_COMMUNICATION);
switch (usage_flags) {
case AudioParameters::FUCHSIA_RENDER_USAGE_BACKGROUND:
return fuchsia::media::AudioRenderUsage::BACKGROUND;
case AudioParameters::FUCHSIA_RENDER_USAGE_MEDIA:
return fuchsia::media::AudioRenderUsage::MEDIA;
case AudioParameters::FUCHSIA_RENDER_USAGE_INTERRUPTION:
return fuchsia::media::AudioRenderUsage::INTERRUPTION;
case AudioParameters::FUCHSIA_RENDER_USAGE_SYSTEM_AGENT:
return fuchsia::media::AudioRenderUsage::SYSTEM_AGENT;
case AudioParameters::FUCHSIA_RENDER_USAGE_COMMUNICATION:
return fuchsia::media::AudioRenderUsage::COMMUNICATION;
case 0:
if (parameters.latency_tag() == AudioLatency::Type::kRtc) {
return fuchsia::media::AudioRenderUsage::COMMUNICATION;
}
return fuchsia::media::AudioRenderUsage::MEDIA;
default:
DLOG(FATAL) << "More than one FUCHSIA_RENDER_USAGE flag is set";
return std::nullopt;
}
}
}
AudioOutputStreamFuchsia::AudioOutputStreamFuchsia(
AudioManagerFuchsia* manager,
const AudioParameters& parameters)
: manager_(manager),
parameters_(parameters),
audio_bus_(AudioBus::Create(parameters)) {}
AudioOutputStreamFuchsia::~AudioOutputStreamFuchsia() {
DCHECK(!audio_renderer_);
}
bool AudioOutputStreamFuchsia::Open() {
DCHECK(!audio_renderer_);
fuchsia::media::AudioPtr audio_server =
base::ComponentContextForProcess()
->svc()
->Connect<fuchsia::media::Audio>();
audio_server->CreateAudioRenderer(audio_renderer_.NewRequest());
audio_renderer_.set_error_handler(
fit::bind_member(this, &AudioOutputStreamFuchsia::OnRendererError));
auto usage = GetStreamUsage(parameters_);
if (!usage)
return false;
audio_renderer_->SetUsage(usage.value());
fuchsia::media::AudioStreamType format;
format.sample_format = fuchsia::media::AudioSampleFormat::FLOAT;
format.channels = parameters_.channels();
format.frames_per_second = parameters_.sample_rate();
audio_renderer_->SetPcmStreamType(std::move(format));
audio_renderer_->SetPtsUnits(parameters_.sample_rate(), 1);
audio_renderer_.events().OnMinLeadTimeChanged =
fit::bind_member(this, &AudioOutputStreamFuchsia::OnMinLeadTimeChanged);
audio_renderer_->EnableMinLeadTimeEvents(true);
return true;
}
void AudioOutputStreamFuchsia::Start(AudioSourceCallback* callback) {
DCHECK(!callback_);
DCHECK(reference_time_.is_null());
DCHECK(!timer_.IsRunning());
callback_ = callback;
if (!min_lead_time_.has_value() || pause_pending_)
return;
PumpSamples();
}
void AudioOutputStreamFuchsia::Stop() {
callback_ = nullptr;
timer_.Stop();
if (reference_time_.is_null() || pause_pending_)
return;
reference_time_ = base::TimeTicks();
pause_pending_ = true;
audio_renderer_->Pause(
fit::bind_member(this, &AudioOutputStreamFuchsia::OnPauseComplete));
audio_renderer_->DiscardAllPacketsNoReply();
}
void AudioOutputStreamFuchsia::Flush() {}
void AudioOutputStreamFuchsia::SetVolume(double volume) {
DCHECK(0.0 <= volume && volume <= 1.0) << volume;
volume_ = volume;
}
void AudioOutputStreamFuchsia::GetVolume(double* volume) {
*volume = volume_;
}
void AudioOutputStreamFuchsia::Close() {
Stop();
audio_renderer_.Unbind();
manager_->ReleaseOutputStream(this);
}
base::TimeTicks AudioOutputStreamFuchsia::GetCurrentStreamTime() {
DCHECK(!reference_time_.is_null());
return reference_time_ +
AudioTimestampHelper::FramesToTime(stream_position_samples_,
parameters_.sample_rate());
}
size_t AudioOutputStreamFuchsia::GetMinBufferSize() {
int min_packets = (AudioTimestampHelper::TimeToFrames(
min_lead_time_.value(), parameters_.sample_rate()) +
parameters_.frames_per_buffer() - 1) /
parameters_.frames_per_buffer() +
1;
return parameters_.GetBytesPerBuffer(kSampleFormatF32) * min_packets;
}
bool AudioOutputStreamFuchsia::InitializePayloadBuffer() {
size_t buffer_size = GetMinBufferSize();
auto region = base::WritableSharedMemoryRegion::Create(buffer_size);
payload_buffer_ = region.Map();
if (!payload_buffer_.IsValid()) {
LOG(WARNING) << "Failed to allocate VMO of size " << buffer_size;
return false;
}
payload_buffer_pos_ = 0;
audio_renderer_->AddPayloadBuffer(
kBufferId, base::WritableSharedMemoryRegion::TakeHandleForSerialization(
std::move(region))
.PassPlatformHandle());
return true;
}
void AudioOutputStreamFuchsia::OnMinLeadTimeChanged(int64_t min_lead_time) {
if (min_lead_time <= 0) {
return;
}
bool min_lead_time_was_unknown = !min_lead_time_.has_value();
min_lead_time_ = base::Nanoseconds(min_lead_time);
if (payload_buffer_.IsValid() &&
GetMinBufferSize() > payload_buffer_.size()) {
payload_buffer_ = {};
audio_renderer_->DiscardAllPacketsNoReply();
audio_renderer_->RemovePayloadBuffer(kBufferId);
}
if (is_started() && min_lead_time_was_unknown) {
DCHECK(!timer_.IsRunning());
PumpSamples();
}
}
void AudioOutputStreamFuchsia::OnRendererError(zx_status_t status) {
ZX_LOG(WARNING, status) << "AudioRenderer has failed";
ReportError();
}
void AudioOutputStreamFuchsia::ReportError() {
reference_time_ = base::TimeTicks();
timer_.Stop();
if (callback_)
callback_->OnError(AudioSourceCallback::ErrorType::kUnknown);
}
void AudioOutputStreamFuchsia::OnPauseComplete(int64_t reference_time,
int64_t media_time) {
DCHECK(pause_pending_);
pause_pending_ = false;
if (is_started())
PumpSamples();
}
void AudioOutputStreamFuchsia::PumpSamples() {
DCHECK(is_started());
DCHECK(audio_renderer_);
if (!payload_buffer_.IsValid() && !InitializePayloadBuffer()) {
ReportError();
return;
}
base::TimeTicks now = base::TimeTicks::Now();
base::TimeDelta delay;
if (reference_time_.is_null()) {
delay = min_lead_time_.value() + parameters_.GetBufferDuration() / 2;
stream_position_samples_ = 0;
} else {
auto stream_time = GetCurrentStreamTime();
if (now + min_lead_time_.value() > stream_time) {
stream_position_samples_ += AudioTimestampHelper::TimeToFrames(
now + min_lead_time_.value() - stream_time,
parameters_.sample_rate());
}
delay = stream_time - now;
}
int frames_filled = callback_->OnMoreData(delay, now, {}, audio_bus_.get());
DCHECK_EQ(frames_filled, audio_bus_->frames());
audio_bus_->Scale(volume_);
size_t packet_size = parameters_.GetBytesPerBuffer(kSampleFormatF32);
DCHECK_LE(payload_buffer_pos_ + packet_size, payload_buffer_.size());
audio_bus_->ToInterleaved<Float32SampleTypeTraitsNoClip>(
audio_bus_->frames(),
reinterpret_cast<float*>(static_cast<uint8_t*>(payload_buffer_.memory()) +
payload_buffer_pos_));
fuchsia::media::StreamPacket packet;
packet.pts = stream_position_samples_;
packet.payload_buffer_id = kBufferId;
packet.payload_offset = payload_buffer_pos_;
packet.payload_size = packet_size;
packet.flags = 0;
audio_renderer_->SendPacketNoReply(std::move(packet));
if (reference_time_.is_null()) {
reference_time_ = now + delay;
audio_renderer_->PlayNoReply(reference_time_.ToZxTime(),
stream_position_samples_);
}
stream_position_samples_ += frames_filled;
payload_buffer_pos_ =
(payload_buffer_pos_ + packet_size) % payload_buffer_.size();
SchedulePumpSamples();
}
void AudioOutputStreamFuchsia::SchedulePumpSamples() {
base::TimeTicks next_pump_time = GetCurrentStreamTime() -
min_lead_time_.value() -
parameters_.GetBufferDuration() / 2;
timer_.Start(FROM_HERE, next_pump_time,
base::BindOnce(&AudioOutputStreamFuchsia::PumpSamples,
base::Unretained(this)));
}
}