Add node volume
This commit is contained in:
parent
fa67dd3708
commit
e649dea9c1
3 changed files with 157 additions and 0 deletions
|
|
@ -137,6 +137,11 @@ struct RouteRule {
|
||||||
std::string target_node;
|
std::string target_node;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct VolumeState {
|
||||||
|
float volume = 1.0f;
|
||||||
|
bool mute = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct MetadataInfo {
|
struct MetadataInfo {
|
||||||
std::string default_sink_name;
|
std::string default_sink_name;
|
||||||
std::string default_source_name;
|
std::string default_source_name;
|
||||||
|
|
@ -166,6 +171,9 @@ class Client {
|
||||||
const VirtualNodeOptions& options = VirtualNodeOptions{});
|
const VirtualNodeOptions& options = VirtualNodeOptions{});
|
||||||
Status RemoveNode(NodeId node);
|
Status RemoveNode(NodeId node);
|
||||||
|
|
||||||
|
Status SetNodeVolume(NodeId node, float volume, bool mute);
|
||||||
|
Result<VolumeState> GetNodeVolume(NodeId node) const;
|
||||||
|
|
||||||
Result<Link> CreateLink(PortId output, PortId input, const LinkOptions& options);
|
Result<Link> CreateLink(PortId output, PortId input, const LinkOptions& options);
|
||||||
Result<Link> CreateLinkByName(std::string_view output_node,
|
Result<Link> CreateLinkByName(std::string_view output_node,
|
||||||
std::string_view output_port,
|
std::string_view output_port,
|
||||||
|
|
@ -193,6 +201,8 @@ class Client {
|
||||||
Status Test_ForceDisconnect();
|
Status Test_ForceDisconnect();
|
||||||
Status Test_TriggerPolicyCheck();
|
Status Test_TriggerPolicyCheck();
|
||||||
size_t Test_GetPendingAutoLinkCount() const;
|
size_t Test_GetPendingAutoLinkCount() const;
|
||||||
|
Status Test_SetNodeVolume(NodeId node, float volume, bool mute);
|
||||||
|
Result<VolumeState> Test_GetNodeVolume(NodeId node) const;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@
|
||||||
#include <pipewire/extensions/metadata.h>
|
#include <pipewire/extensions/metadata.h>
|
||||||
|
|
||||||
#include <spa/param/audio/format-utils.h>
|
#include <spa/param/audio/format-utils.h>
|
||||||
|
#include <spa/param/props.h>
|
||||||
|
#include <spa/pod/builder.h>
|
||||||
#include <spa/utils/defs.h>
|
#include <spa/utils/defs.h>
|
||||||
|
|
||||||
#include <spa/utils/result.h>
|
#include <spa/utils/result.h>
|
||||||
|
|
@ -277,6 +279,8 @@ struct Client::Impl {
|
||||||
std::unordered_map<uint32_t, std::unique_ptr<StreamData>> virtual_streams;
|
std::unordered_map<uint32_t, std::unique_ptr<StreamData>> virtual_streams;
|
||||||
std::unordered_map<uint32_t, std::unique_ptr<LinkProxy>> link_proxies;
|
std::unordered_map<uint32_t, std::unique_ptr<LinkProxy>> link_proxies;
|
||||||
|
|
||||||
|
std::unordered_map<uint32_t, VolumeState> volume_states;
|
||||||
|
|
||||||
uint32_t next_rule_id = 1;
|
uint32_t next_rule_id = 1;
|
||||||
std::unordered_map<uint32_t, RouteRule> route_rules;
|
std::unordered_map<uint32_t, RouteRule> route_rules;
|
||||||
std::vector<PendingAutoLink> pending_auto_links;
|
std::vector<PendingAutoLink> pending_auto_links;
|
||||||
|
|
@ -1216,6 +1220,64 @@ Status Client::RemoveNode(NodeId node) {
|
||||||
return Status::Ok();
|
return Status::Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status Client::SetNodeVolume(NodeId node, float volume, bool mute) {
|
||||||
|
Status status = impl_->EnsureConnected();
|
||||||
|
if (!status.ok()) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
if (node.value == 0) {
|
||||||
|
return Status::Error(StatusCode::kInvalidArgument, "invalid node id");
|
||||||
|
}
|
||||||
|
|
||||||
|
volume = std::clamp(volume, 0.0f, 1.5f);
|
||||||
|
|
||||||
|
pw_thread_loop_lock(impl_->thread_loop);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
|
if (impl_->nodes.find(node.value) == impl_->nodes.end()) {
|
||||||
|
pw_thread_loop_unlock(impl_->thread_loop);
|
||||||
|
return Status::Error(StatusCode::kNotFound, "node not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* proxy = static_cast<pw_node*>(
|
||||||
|
pw_registry_bind(impl_->registry, node.value,
|
||||||
|
PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0));
|
||||||
|
if (!proxy) {
|
||||||
|
pw_thread_loop_unlock(impl_->thread_loop);
|
||||||
|
return Status::Error(StatusCode::kInternal, "failed to bind node proxy");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t buffer[128];
|
||||||
|
spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||||
|
auto* param = reinterpret_cast<const spa_pod*>(spa_pod_builder_add_object(
|
||||||
|
&builder,
|
||||||
|
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
|
||||||
|
SPA_PROP_volume, SPA_POD_Float(volume),
|
||||||
|
SPA_PROP_mute, SPA_POD_Bool(mute)));
|
||||||
|
|
||||||
|
pw_node_set_param(proxy, SPA_PARAM_Props, 0, param);
|
||||||
|
pw_proxy_destroy(reinterpret_cast<pw_proxy*>(proxy));
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
|
impl_->volume_states[node.value] = VolumeState{volume, mute};
|
||||||
|
}
|
||||||
|
|
||||||
|
pw_thread_loop_unlock(impl_->thread_loop);
|
||||||
|
return Status::Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<VolumeState> Client::GetNodeVolume(NodeId node) const {
|
||||||
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
|
auto it = impl_->volume_states.find(node.value);
|
||||||
|
if (it == impl_->volume_states.end()) {
|
||||||
|
return {Status::Ok(), VolumeState{}};
|
||||||
|
}
|
||||||
|
return {Status::Ok(), it->second};
|
||||||
|
}
|
||||||
|
|
||||||
Result<Link> Client::CreateLink(PortId output, PortId input, const LinkOptions& options) {
|
Result<Link> Client::CreateLink(PortId output, PortId input, const LinkOptions& options) {
|
||||||
Status status = impl_->EnsureConnected();
|
Status status = impl_->EnsureConnected();
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
|
|
@ -1719,6 +1781,30 @@ size_t Client::Test_GetPendingAutoLinkCount() const {
|
||||||
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
return impl_->pending_auto_links.size();
|
return impl_->pending_auto_links.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Status Client::Test_SetNodeVolume(NodeId node, float volume, bool mute) {
|
||||||
|
if (!impl_) {
|
||||||
|
return Status::Error(StatusCode::kUnavailable, "no impl");
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
|
if (impl_->nodes.find(node.value) == impl_->nodes.end()) {
|
||||||
|
return Status::Error(StatusCode::kNotFound, "node not found");
|
||||||
|
}
|
||||||
|
impl_->volume_states[node.value] = VolumeState{std::clamp(volume, 0.0f, 1.5f), mute};
|
||||||
|
return Status::Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<VolumeState> Client::Test_GetNodeVolume(NodeId node) const {
|
||||||
|
if (!impl_) {
|
||||||
|
return {Status::Error(StatusCode::kUnavailable, "no impl"), {}};
|
||||||
|
}
|
||||||
|
std::lock_guard<std::mutex> lock(impl_->cache_mutex);
|
||||||
|
auto it = impl_->volume_states.find(node.value);
|
||||||
|
if (it == impl_->volume_states.end()) {
|
||||||
|
return {Status::Ok(), VolumeState{}};
|
||||||
|
}
|
||||||
|
return {Status::Ok(), it->second};
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace warppipe
|
} // namespace warppipe
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <catch2/catch_approx.hpp>
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
@ -792,3 +793,63 @@ TEST_CASE("policy mode does not override user defaults") {
|
||||||
REQUIRE(defaults2.value.configured_sink_name == defaults.value.configured_sink_name);
|
REQUIRE(defaults2.value.configured_sink_name == defaults.value.configured_sink_name);
|
||||||
REQUIRE(defaults2.value.configured_source_name == defaults.value.configured_source_name);
|
REQUIRE(defaults2.value.configured_source_name == defaults.value.configured_source_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test_SetNodeVolume sets and retrieves volume state") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
REQUIRE(result.ok());
|
||||||
|
auto &client = result.value;
|
||||||
|
|
||||||
|
warppipe::NodeInfo node;
|
||||||
|
node.id = warppipe::NodeId{900};
|
||||||
|
node.name = "vol-sink";
|
||||||
|
node.media_class = "Audio/Sink";
|
||||||
|
REQUIRE(client->Test_InsertNode(node).ok());
|
||||||
|
|
||||||
|
auto vol = client->Test_GetNodeVolume(warppipe::NodeId{900});
|
||||||
|
REQUIRE(vol.ok());
|
||||||
|
REQUIRE(vol.value.volume == Catch::Approx(1.0f));
|
||||||
|
REQUIRE(vol.value.mute == false);
|
||||||
|
|
||||||
|
REQUIRE(client->Test_SetNodeVolume(warppipe::NodeId{900}, 0.5f, false).ok());
|
||||||
|
vol = client->Test_GetNodeVolume(warppipe::NodeId{900});
|
||||||
|
REQUIRE(vol.ok());
|
||||||
|
REQUIRE(vol.value.volume == Catch::Approx(0.5f));
|
||||||
|
REQUIRE(vol.value.mute == false);
|
||||||
|
|
||||||
|
REQUIRE(client->Test_SetNodeVolume(warppipe::NodeId{900}, 0.75f, true).ok());
|
||||||
|
vol = client->Test_GetNodeVolume(warppipe::NodeId{900});
|
||||||
|
REQUIRE(vol.ok());
|
||||||
|
REQUIRE(vol.value.volume == Catch::Approx(0.75f));
|
||||||
|
REQUIRE(vol.value.mute == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test_SetNodeVolume clamps volume") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
REQUIRE(result.ok());
|
||||||
|
auto &client = result.value;
|
||||||
|
|
||||||
|
warppipe::NodeInfo node;
|
||||||
|
node.id = warppipe::NodeId{901};
|
||||||
|
node.name = "vol-clamp";
|
||||||
|
node.media_class = "Audio/Sink";
|
||||||
|
REQUIRE(client->Test_InsertNode(node).ok());
|
||||||
|
|
||||||
|
REQUIRE(client->Test_SetNodeVolume(warppipe::NodeId{901}, 2.0f, false).ok());
|
||||||
|
auto vol = client->Test_GetNodeVolume(warppipe::NodeId{901});
|
||||||
|
REQUIRE(vol.ok());
|
||||||
|
REQUIRE(vol.value.volume == Catch::Approx(1.5f));
|
||||||
|
|
||||||
|
REQUIRE(client->Test_SetNodeVolume(warppipe::NodeId{901}, -1.0f, false).ok());
|
||||||
|
vol = client->Test_GetNodeVolume(warppipe::NodeId{901});
|
||||||
|
REQUIRE(vol.ok());
|
||||||
|
REQUIRE(vol.value.volume == Catch::Approx(0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test_SetNodeVolume fails for nonexistent node") {
|
||||||
|
auto result = warppipe::Client::Create(DefaultOptions());
|
||||||
|
REQUIRE(result.ok());
|
||||||
|
|
||||||
|
auto status = result.value->Test_SetNodeVolume(warppipe::NodeId{999}, 0.5f, false);
|
||||||
|
REQUIRE_FALSE(status.ok());
|
||||||
|
REQUIRE(status.code == warppipe::StatusCode::kNotFound);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue