blob: b786a2ef9f385567265f5d885cea6a196bade18e [file] [log] [blame]
// Copyright 2022 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/audio_core/v2/usage_volume.h"
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include "src/media/audio/audio_core/v2/reference_clock.h"
namespace media_audio {
namespace {
using ::fuchsia_audio::wire::GainControlSetGainRequest;
using ::fuchsia_audio::wire::GainUpdateMethod;
using ::fuchsia_audio::wire::RampedGain;
using ::fuchsia_audio::wire::RampFunction;
using ::fuchsia_audio::wire::RampFunctionLinearSlope;
using ::media::audio::StreamUsage;
using ::media::audio::VolumeCommand;
using ::media::audio::VolumeCurve;
} // namespace
void UsageVolume::Create(Args args) {
// Save this because `args` will be moved into the `constructor` closure.
auto& graph_client = *args.graph_client;
auto clock = ReferenceClock::FromMonotonic();
const std::string usage_name =
args.device_name + (args.usage.is_render_usage()
? media::audio::RenderUsageToString(args.usage.render_usage())
: media::audio::CaptureUsageToString(args.usage.capture_usage()));
// The GainControl objects are constructed asynchronously.
auto state = std::make_shared<ConstructorState>(ConstructorState{});
auto constructor = std::make_shared<fit::closure>([state, args = std::move(args)]() mutable {
if (!state->ids[0] || !state->ids[1]) {
return;
}
auto callback = std::move(args.callback);
callback(std::shared_ptr<UsageVolume>(new UsageVolume(std::move(args), *state)));
});
fidl::Arena<> arena;
for (int k = 0; k < 2; k++) {
// In practice this should never fail. The only possible error is ZX_ERR_NO_MEMORY, of which the
// kernel docs say "In a future build this error will no longer occur".
auto endpoints = fidl::CreateEndpoints<fuchsia_audio::GainControl>();
if (!endpoints.is_ok()) {
FX_PLOGS(FATAL, endpoints.status_value()) << "fidl::CreateEndpoints failed";
}
state->clients[k] = fidl::WireSharedClient(std::move(endpoints->client), args.dispatcher);
graph_client
->CreateGainControl(
fuchsia_audio_mixer::wire::GraphCreateGainControlRequest::Builder(arena)
.name((k == kPrimary ? "PrimaryGainControlFor" : "AdjustmentGainControlFor") +
usage_name)
.control(std::move(endpoints->server))
.reference_clock(clock.ToFidl(arena))
.Build())
.Then([state, constructor, k](auto& result) {
if (!result.ok()) {
FX_LOGS(WARNING) << "CreateGainControl: failed with transport error: " << result;
return;
}
if (!result->is_ok()) {
FX_LOGS(ERROR) << "CreateGainControl: failed with code: "
<< fidl::ToUnderlying(result->error_value());
return;
}
if (!result->value()->has_id()) {
FX_LOGS(ERROR) << "CreateGainControl bug: response missing `id`";
return;
}
state->ids[k] = result->value()->id();
(*constructor)();
});
}
}
UsageVolume::UsageVolume(Args args, ConstructorState& state)
: graph_client_(std::move(args.graph_client)),
volume_curve_(std::move(args.volume_curve)),
usage_(args.usage) {
// std::vector's constructor cannot accept an initializer list of move-only objects, so this
// initialization must be done with push_back.
for (int k = 0; k < 2; k++) {
clients_.push_back(std::move(*state.clients[k]));
gain_controls_.push_back(*state.ids[k]);
}
}
UsageVolume::~UsageVolume() {
// Delete graph objects.
for (int k = 0; k < 2; k++) {
fidl::Arena<> arena;
(*graph_client_)
->DeleteGainControl(fuchsia_audio_mixer::wire::GraphDeleteGainControlRequest::Builder(arena)
.id(gain_controls_[k])
.Build())
.Then([k](auto& result) {
if (result.ok() && !result->is_ok()) {
FX_LOGS(ERROR) << "DeleteGainControl(" << k
<< "): failed with code: " << fidl::ToUnderlying(result->error_value());
}
});
}
}
fuchsia::media::Usage UsageVolume::GetStreamUsage() const {
if (usage_.is_render_usage()) {
auto out = media::audio::FidlRenderUsageFromRenderUsage(usage_.render_usage());
FX_CHECK(out);
return fuchsia::media::Usage::WithRenderUsage(std::move(*out));
} else {
auto out = media::audio::FidlCaptureUsageFromCaptureUsage(usage_.capture_usage());
FX_CHECK(out);
return fuchsia::media::Usage::WithCaptureUsage(std::move(*out));
}
}
void UsageVolume::RealizeVolume(VolumeCommand command) {
const float target_dbs[2] = {
/*primary*/ volume_curve_->VolumeToDb(command.volume),
/*adjustment*/ command.gain_db_adjustment,
};
for (int k = 0; k < 2; k++) {
fidl::Arena<> arena;
if (command.ramp) {
FX_CHECK(command.ramp->ramp_type == fuchsia::media::audio::RampType::SCALE_LINEAR);
clients_[k]
->SetGain(GainControlSetGainRequest::Builder(arena)
.how(GainUpdateMethod::WithRamped(
arena, RampedGain::Builder(arena)
.target_gain_db(target_dbs[k])
.duration(command.ramp->duration.to_nsecs())
.function(RampFunction::WithLinearSlope(
arena, RampFunctionLinearSlope::Builder(arena).Build()))
.Build()))
.when(fuchsia_media2::wire::RealTime::WithAsap({}))
.Build())
.Then([k](auto& result) {
if (result.ok() && !result->is_ok()) {
FX_LOGS(ERROR) << "SetGain(" << k << ", with ramp): failed with code: "
<< fidl::ToUnderlying(result->error_value());
}
});
} else {
clients_[k]
->SetGain(GainControlSetGainRequest::Builder(arena)
.how(GainUpdateMethod::WithGainDb(target_dbs[k]))
.when(fuchsia_media2::wire::RealTime::WithAsap({}))
.Build())
.Then([k](auto& result) {
if (result.ok() && !result->is_ok()) {
FX_LOGS(ERROR) << "SetGain(" << k << "): failed with code: "
<< fidl::ToUnderlying(result->error_value());
}
});
}
}
}
} // namespace media_audio