blob: 5aeac33523b20ccfdf7b8655bd2180efdccca19f [file] [log] [blame]
// Copyright 2023 The Fuchsia 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 "src/media/audio/consumer/consumer.h"
#include <fidl/fuchsia.media.audio/cpp/markers.h>
#include <fidl/fuchsia.media/cpp/common_types.h>
#include <lib/async/dispatcher.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/errors.h>
namespace media::audio {
namespace {
constexpr uint64_t kMinimumLeadTime = ZX_MSEC(30);
constexpr uint64_t kMaximumLeadTime = ZX_MSEC(500);
template <typename Protocol>
void EnsureUnbound(std::optional<fidl::ServerBindingRef<Protocol>>& binding_ref) {
if (binding_ref) {
binding_ref->Unbind();
binding_ref.reset();
}
}
template <typename Protocol>
void EnsureUnbound(fidl::Client<Protocol>& client) {
client = fidl::Client<Protocol>();
}
} // namespace
// static
void Consumer::CreateAndBind(async_dispatcher_t* dispatcher,
fidl::ClientEnd<fuchsia_media::AudioCore> audio_core_client_end,
fidl::ServerEnd<fuchsia_media::AudioConsumer> server_end) {
auto impl = std::make_shared<Consumer>(dispatcher, std::move(audio_core_client_end));
// This is a separate step, because the |Consumer| must be managed by a |shared_ptr| before
// |Bind| may be called.
impl->Bind(std::move(server_end));
// If |Bind| fails, |impl| is the sole reference to the |Consumer| at this point, so the
// |Consumer| will be deleted here when |impl| goes out of scope.
}
Consumer::Consumer(async_dispatcher_t* dispatcher,
fidl::ClientEnd<fuchsia_media::AudioCore> audio_core_client_end)
: dispatcher_(dispatcher),
audio_core_event_handler_(fit::bind_member(this, &Consumer::OnAudioCoreFidlError)),
audio_core_(
fidl::Client(std::move(audio_core_client_end), dispatcher_, &audio_core_event_handler_)),
gain_control_event_handler_(fit::bind_member(this, &Consumer::OnGainControlFidlError)) {}
void Consumer::Bind(fidl::ServerEnd<fuchsia_media::AudioConsumer> server_end) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_media::AudioRenderer>();
if (!endpoints.is_ok()) {
FX_LOGS(ERROR) << "Failed to create endponts: " << endpoints.status_string();
return;
}
fit::result<fidl::OneWayError> result =
audio_core_->CreateAudioRenderer({{.audio_out_request = std::move(endpoints->server)}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to create audio renderer: " << result.error_value().status_string();
return;
}
renderer_ = fidl::Client(std::move(endpoints->client), dispatcher_, this);
result = renderer_->SetUsage({{.usage = fuchsia_media::AudioRenderUsage::kMedia}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to set usage: " << result.error_value().status_string();
return;
}
// Having performed the preceding operations successfully, we reference this |Consumer| from
// the server binding here so it won't be deleted when |CreateAndBind| completes.
consumer_binding_ref_ = fidl::BindServer(
dispatcher_, std::move(server_end), shared_from_this(),
[this](fidl::Server<fuchsia_media::AudioConsumer>* impl_unused, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_media::AudioConsumer> server_end) { UnbindAll(); });
}
void Consumer::UnbindAll() {
pending_stream_sink_completer_.reset();
EnsureUnbound(consumer_binding_ref_);
EnsureUnbound(stream_sink_binding_ref_);
EnsureUnbound(volume_binding_ref_);
EnsureUnbound(audio_core_);
EnsureUnbound(renderer_);
EnsureUnbound(gain_control_);
}
void Consumer::CreateStreamSink(CreateStreamSinkRequest& request,
CreateStreamSinkCompleter::Sync& completer) {
pending_stream_sink_request_ = std::move(request);
pending_stream_sink_completer_ = completer.ToAsync();
if (!stream_sink_binding_ref_) {
MaybeCompletePendingCreateStreamSinkRequest();
}
}
void Consumer::MaybeCompletePendingCreateStreamSinkRequest() {
FX_DCHECK(!stream_sink_binding_ref_);
if (!renderer_) {
return;
}
if (!pending_stream_sink_completer_) {
// No request pending.
return;
}
if (buffers_previously_added_ != 0) {
fit::result<fidl::OneWayError> result = renderer_->PauseNoReply();
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to pause: " << result.error_value().status_string();
}
result = renderer_->DiscardAllPacketsNoReply();
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to discard packets: " << result.error_value().status_string();
}
RemoveAddedPayloadBuffers();
}
// Compression isn't currently supported. Close the |StreamSink| server end indicating invalid
// args.
if (pending_stream_sink_request_.compression() != nullptr) {
FX_LOGS(WARNING) << "Compression not supported, compression type "
<< pending_stream_sink_request_.compression()->type() << " requested";
pending_stream_sink_request_.stream_sink_request().Close(ZX_ERR_INVALID_ARGS);
return;
}
fit::result<fidl::OneWayError> result = renderer_->SetPcmStreamType(
{{.type = std::move(pending_stream_sink_request_.stream_type())}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to set stream type: " << result.error_value().status_string();
pending_stream_sink_completer_->Close(result.error_value().status());
pending_stream_sink_completer_.reset();
return;
}
uint32_t buffers_previously_added_ = 0;
for (auto& buffer : pending_stream_sink_request_.buffers()) {
result = renderer_->AddPayloadBuffer({{
.id = buffers_previously_added_++,
.payload_buffer = std::move(buffer),
}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to add payload buffer: " << result.error_value().status_string();
pending_stream_sink_completer_->Close(result.error_value().status());
pending_stream_sink_completer_.reset();
RemoveAddedPayloadBuffers();
return;
}
}
stream_sink_binding_ref_ =
fidl::BindServer(dispatcher_, std::move(pending_stream_sink_request_.stream_sink_request()),
shared_from_this(),
[this](fidl::Server<fuchsia_media::StreamSink>* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_media::StreamSink> server_end) {
stream_sink_binding_ref_.reset();
MaybeCompletePendingCreateStreamSinkRequest();
});
pending_stream_sink_completer_.reset();
}
void Consumer::RemoveAddedPayloadBuffers() {
while (buffers_previously_added_ != 0) {
fit::result<fidl::OneWayError> result =
renderer_->RemovePayloadBuffer({{.id = --buffers_previously_added_}});
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to remove payload buffer: "
<< result.error_value().status_string();
break;
}
}
}
void Consumer::Start(StartRequest& request, StartCompleter::Sync& completer) {
if (!renderer_) {
return;
}
fit::result<fidl::OneWayError> result = renderer_->PlayNoReply({{
.reference_time = request.reference_time(),
.media_time = request.media_time(),
}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to PlayNoReply: " << result.error_value().status_string();
completer.Close(result.error_value().status());
return;
}
timeline_function_ =
media::TimelineFunction(request.media_time(), request.reference_time(), 1, 1);
StatusChanged();
}
void Consumer::Stop(StopCompleter::Sync& completer) {
if (!renderer_) {
return;
}
fit::result<fidl::OneWayError> result = renderer_->PauseNoReply();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to PauseNoReply: " << result.error_value().status_string();
completer.Close(result.error_value().status());
return;
}
auto reference_time = zx::clock::get_monotonic().get();
auto media_time = timeline_function_(reference_time);
timeline_function_ = media::TimelineFunction(media_time, reference_time, 0, 1);
StatusChanged();
}
void Consumer::SetRate(SetRateRequest& request, SetRateCompleter::Sync& completer) {
// Not supported.
FX_LOGS(WARNING) << "Call to unsupported SetRate method";
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
void Consumer::BindVolumeControl(BindVolumeControlRequest& request,
BindVolumeControlCompleter::Sync& completer) {
if (!renderer_) {
return;
}
zx::result endpoints = fidl::CreateEndpoints<fuchsia_media_audio::GainControl>();
if (!endpoints.is_ok()) {
FX_LOGS(ERROR) << "Failed to create endponts: " << endpoints.status_string();
return;
}
fit::result<fidl::OneWayError> result = renderer_->BindGainControl(std::move(endpoints->server));
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to BindGainControl: " << result.error_value().status_string();
return;
}
gain_control_ =
fidl::Client(std::move(endpoints->client), dispatcher_, &gain_control_event_handler_);
volume_binding_ref_ = fidl::BindServer(
dispatcher_, std::move(request.volume_control_request()), shared_from_this(),
[this](fidl::Server<fuchsia_media_audio::VolumeControl>* impl, fidl::UnbindInfo info,
fidl::ServerEnd<fuchsia_media_audio::VolumeControl> server_end) {
volume_binding_ref_.reset();
EnsureUnbound(gain_control_);
});
}
void Consumer::WatchStatus(WatchStatusCompleter::Sync& completer) {
watch_status_completer_ = completer.ToAsync();
MaybeCompleteWatchStatus();
}
void Consumer::SendPacket(SendPacketRequest& request, SendPacketCompleter::Sync& completer) {
if (!renderer_) {
return;
}
renderer_->SendPacket({{.packet = std::move(request.packet())}})
.Then([completer = completer.ToAsync()](auto& result) mutable {
if (result.is_ok()) {
completer.Reply();
} else {
FX_LOGS(ERROR) << "Failed to SendPacket: " << result.error_value().status_string();
completer.Close(result.error_value().status());
}
});
}
void Consumer::SendPacketNoReply(SendPacketNoReplyRequest& request,
SendPacketNoReplyCompleter::Sync& completer) {
if (!renderer_) {
return;
}
fit::result<fidl::OneWayError> result =
renderer_->SendPacketNoReply({{.packet = std::move(request.packet())}});
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to SendPacketNoReply: " << result.error_value().status_string();
completer.Close(result.error_value().status());
return;
}
}
void Consumer::EndOfStream(EndOfStreamCompleter::Sync& completer) {
if (!renderer_) {
return;
}
// TODO(https://fxbug.dev/42077439): Maybe need logic to send OnEndOfStream, if clients need it.
fit::result<fidl::OneWayError> result = renderer_->EndOfStream();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to EndOfStream: " << result.error_value().status_string();
completer.Close(result.error_value().status());
return;
}
}
void Consumer::DiscardAllPackets(DiscardAllPacketsCompleter::Sync& completer) {
if (!renderer_) {
return;
}
renderer_->DiscardAllPackets().Then([completer = completer.ToAsync()](auto& result) mutable {
if (result.is_ok()) {
completer.Reply();
} else {
FX_LOGS(ERROR) << "Failed to DiscardAllPackets: " << result.error_value().status_string();
completer.Close(result.error_value().status());
}
});
}
void Consumer::DiscardAllPacketsNoReply(DiscardAllPacketsNoReplyCompleter::Sync& completer) {
if (!renderer_) {
return;
}
fit::result<fidl::OneWayError> result = renderer_->DiscardAllPacketsNoReply();
if (result.is_error()) {
FX_LOGS(ERROR) << "Failed to DiscardAllPacketsNoReply: "
<< result.error_value().status_string();
completer.Close(result.error_value().status());
return;
}
}
void Consumer::SetVolume(SetVolumeRequest& request, SetVolumeCompleter::Sync& completer) {
if (!gain_control_) {
return;
}
if (!std::isfinite(request.volume())) {
FX_LOGS(WARNING) << "Consumer::SetVolume(" << request.volume()
<< ") is indefinite; ignoring this call";
return;
}
audio_core_
->GetDbFromVolume({{
.usage = fuchsia_media::Usage::WithRenderUsage(fuchsia_media::AudioRenderUsage::kMedia),
.volume = request.volume(),
}})
.Then([this](auto& result) {
if (!gain_control_) {
FX_LOGS(WARNING) << "Gain control lost during SetVolume processing";
return;
}
fit::result<fidl::OneWayError> set_gain_result =
gain_control_->SetGain({{.gain_db = result.value().gain_db()}});
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to SetGain to " << result.value().gain_db() << ": "
<< set_gain_result.error_value().status_string();
return;
}
});
}
void Consumer::SetMute(SetMuteRequest& request, SetMuteCompleter::Sync& completer) {
if (!gain_control_) {
return;
}
fit::result<fidl::OneWayError> result = gain_control_->SetMute({{.muted = request.mute()}});
if (result.is_error()) {
FX_LOGS(WARNING) << "Failed to SetMute: " << result.error_value().status_string();
return;
}
}
void Consumer::OnMinLeadTimeChanged(
fidl::Event<fuchsia_media::AudioRenderer::OnMinLeadTimeChanged>& event) {}
void Consumer::on_fidl_error(fidl::UnbindInfo error) {
FX_LOGS(WARNING) << "AudioRenderer unbound, " << error.status_string();
UnbindAll();
}
void Consumer::StatusChanged() {
status_dirty_ = true;
MaybeCompleteWatchStatus();
}
void Consumer::MaybeCompleteWatchStatus() {
if (!status_dirty_) {
return;
}
if (!watch_status_completer_) {
return;
}
// The fixed lead time values are consistent with the previous implementation.
watch_status_completer_->Reply({{.status = {{
.presentation_timeline = fuchsia_media::TimelineFunction{{
.subject_time = timeline_function_.subject_time(),
.reference_time = timeline_function_.reference_time(),
.subject_delta = timeline_function_.subject_delta(),
.reference_delta = timeline_function_.reference_delta(),
}},
.min_lead_time = kMinimumLeadTime,
.max_lead_time = kMaximumLeadTime,
}}}});
status_dirty_ = false;
watch_status_completer_.reset();
}
void Consumer::OnAudioCoreFidlError(fidl::UnbindInfo error) {
FX_LOGS(WARNING) << "AudioCore unbound, " << error.status_string() << ", closing all channels.";
UnbindAll();
}
void Consumer::OnGainControlFidlError(fidl::UnbindInfo error) {
FX_LOGS(WARNING) << "GainControl unbound, " << error.status_string() << ", closing all channels.";
UnbindAll();
}
} // namespace media::audio