PipeWire를 사용하여 오디오 장치 프로필과 라우팅을 직접 설정하려면 어떻게 해야 합니까?

PipeWire를 사용하여 오디오 장치 프로필과 라우팅을 직접 설정하려면 어떻게 해야 합니까?

내가 아는 한, 를 사용하여 오디오 장치에 대한 프로필을 선택할 수 있습니다 pactl set-card-profile CARD PROFILE. 하지만 이는 pactlPulseAudio 유틸리티이므로 불필요한 레이어를 추가한다는 의미이며, 물론 어느 정도 PulseAudio를 설치해야 합니다.

PipeWire를 사용하여 오디오 장치 프로필을 설정하는 기본 방법이 무엇인지 알고 싶습니다.

pw-cli나에게 필요할 것 같은 PipeWire라는 유틸리티를 찾았 지만 이를 올바르게 사용하는 방법은 확실하지 않습니다. pw-cli [옵션] [명령] -h, --help 이 도움말 표시 --version 버전 표시 -d, --daemon 데몬으로 시작(기본값 false) -r, --remote 원격 데몬 이름

Available commands:
    help                    Show this help
    load-module             Load a module. <module-name> [<module-arguments>]
    unload-module           Unload a module. <module-var>
    connect                 Connect to a remote. [<remote-name>]
    disconnect              Disconnect from a remote. [<remote-var>]
    list-remotes            List connected remotes.
    switch-remote           Switch between current remotes. [<remote-var>]
    list-objects            List objects or current remote. [<interface>]
    info                    Get info about an object. <object-id>|all
    create-device           Create a device from a factory. <factory-name> [<properties>]
    create-node             Create a node from a factory. <factory-name> [<properties>]
    destroy                 Destroy a global object. <object-id>
    create-link             Create a link between nodes. <node-id> <port-id> <node-id> <port-id> [<properties>]
    export-node             Export a local node to the current remote. <node-id> [remote-var]
    enum-params             Enumerate params of an object <object-id> <param-id>
    set-param               Set param of an object <object-id> <param-id> <param-json>
    permissions             Set permissions for a client <client-id> <object> <permission>
    get-permissions         Get permissions of a client <client-id>
    send-command            Send a command <object-id>
    dump                    Dump objects in ways that are cleaner for humans to understand [short|deep|resolve|notype] [-sdrt] [all|Core|Module|Device|Node|Port|Factory|Client|Link|Session|Endpoint|EndpointStream|<id>]
    quit                    Quit

오류 enum-params없이 호출할 수 있지만 출력은 매우 비밀스럽고 구문 분석하기 어렵습니다. 그러나 두 명령 모두 예상되는 출력을 제공합니다.RouteProfile

pw-cli enum-params 45 Route
pw-cli enum-params 45 Profile

pw-dump출력 형식이 JSON이므로 데이터를 검색하는 것이 더 의미가 있는 것 같습니다. 필요한 데이터를 전달하는 데 get_audio_devices필요한 모든 정보를 얻기 위해 여러 도우미 함수( ,,,,,,,,, )를 작성한 다음 이를 더 합리적으로 만드는 두 개의 함수( 및 )를 더 작성했습니다. 사용.get_object_by_idget_device_by_nameget_active_profilesget_active_routesget_all_profilesget_all_routesget_nodes_for_device_by_idpw-cli set-paramset_profileset_routepw-cli

내 기능은 다음과 같습니다.

#!/bin/bash

# Function: get_audio_devices
# Description: Get all audio devices as a JSON array of objects
function get_audio_devices() {
    pw-dump | jq -r '.[] | select(.type == "PipeWire:Interface:Device" and .info.props."media.class" == "Audio/Device")'
}

# Function: get_object_by_id
# Description: Get a object by object ID as a JSON object
# Parameters:
#   - $1: The object ID of the device, node, module, factory, port, link or whatever
function get_object_by_id() {
    local object_id="$1"
    pw-dump | jq -r ".[] | select(.id == $object_id)"
}

# Function: get_device_by_name
# Description: Get a device by name as a JSON object
# Parameters:
#   - $1: The name of the device
function get_device_by_name() {
    local device_name="$1"
    pw-dump | jq -r ".[] | select(.info.props.\"device.name\" == \"$device_name\")"
}

# Function: get_active_profiles
# Description: Get active profiles for a device by object ID as a JSON array of objects
# Parameters:
#   - $1: The object ID of the device
function get_active_profiles() {
    local device_object_id="$1"
    pw-dump | jq -r ".[] | select(.id == $device_object_id) | .info.params.Profile"
}

# Function: get_active_routes
# Description: Get active routes for all nodes of the active profile(s) of a device by object ID as a JSON array of objects
# Parameters:
#   - $1: The object ID of the device
function get_active_routes() {
    local device_object_id="$1"
    pw-dump | jq -r ".[] | select(.id == $device_object_id) | .info.params.Route"
}

# Function: get_all_profiles
# Description: Get all profiles for a device by object ID as a JSON array of objects
# Parameters:
#   - $1: The object ID of the device
function get_all_profiles() {
    local device_object_id="$1"
    pw-dump | jq -r ".[] | select(.id == $device_object_id) | .info.params.EnumProfile"
}

# Function: get_all_routes
# Description: Get all routes for all nodes of a device by object ID as a JSON array of objects
# Parameters:
#   - $1: The object ID of the device
function get_all_routes() {
    local device_object_id="$1"
    pw-dump | jq -r ".[] | select(.id == $device_object_id) | .info.params.EnumRoute"
}

# Function: get_nodes_for_device_by_id
# Description: Get all nodes for a device by object ID as a JSON array of objects
# Parameters:
#   - $1: The object ID of the device
function get_nodes_for_device_by_id() {
    local device_object_id="$1"
    pw-dump | jq -r --argjson dev_id "$device_object_id" '[.[] | select(.type == "PipeWire:Interface:Node" and .info.props."device.id" == $dev_id)]'
    #pw-dump | jq -r '[.[] | select(.type == "PipeWire:Interface:Node" and .info.props."device.id" == "'"$device_object_id"'")]'
}

# Function: set_route
# Description: Set route for a given node by object ID
# Parameters:
#   - $1: The object ID of the node
#   - $2: The index of the new route
function set_route() {
    local node_object_id="$1"
    local route_index="$2"

    local node=$(get_object_by_id $node_object_id)
    local device_object_id=$(echo "$node" | jq -r '.info.props["device.id"]')

    # Get available route "templates" for the device's active profile(s)
    routes=$(get_all_routes $device_object_id)

    # Get the first route template (edit: using the fourth because the first 3 are for input rather than output)
    first_route_template=$(echo "$routes" | jq '.[3]')

    # Create a Routes entry for the given node based on the given template and save as new JSON object
    route_to_set=$(echo "$first_route_template")

    # Set the "route.hw-mute" property to false beacuase I have no clu how to find out the right value
    route_to_set=$(echo "$route_to_set" | jq '.info += ["route.hw-mute", "true"]')

    # Set the "route.hw-volume" property to false beacuase I have no clu how to find out the right value
    route_to_set=$(echo "$route_to_set" | jq '.info += ["route.hw-volume", "true"]')

    # Calculate the length of the "info" array and set the first element accordingly
    info_length=$(echo "$route_to_set" | jq '.info | length')
    route_to_set=$(echo "$route_to_set" | jq ".info[0] = ($info_length - 1) / 2")

    # Get the index of the node of which we want to change the route
    node_index=$(echo "$node" | jq -r '.info.props["card.profile.device"]')

    # Set device property
    route_to_set=$(echo "$route_to_set" | jq ". + { \"device\": $node_index }")

    # Gather values for the properties of the "props" section 
    mute=$(echo "$node" | jq -r '.info.params.Props[0].mute')
    channel_volumes=$(echo "$node" | jq -r '.info.params.Props[0].channelVolumes')
    volume_base=$(echo "$node" | jq -r '.info.params.Props[0].volume')
    volume_step=0.000015 # No clue how to get the correct value
    channel_map=$(echo "$node" | jq -r '.info.params.Props[0].channelMap')
    soft_volumes=$(echo "$node" | jq -r '.info.params.Props[0].softVolumes')
    latency_offset=$(echo "$node" | jq -r '.info.params.Props[1].latencyOffsetNsec')

    # Set the properties in the "props" section
    route_to_set=$(echo "$route_to_set" | jq "
        .props += {
            \"mute\": $mute,
            \"channelVolumes\": $channel_volumes,
            \"volumeBase\": $volume_base,
            \"volumeStep\": $volume_step,
            \"channelMap\": $channel_map,
            \"softVolumes\": $soft_volumes,
            \"latencyOffsetNsec\": $latency_offset
        }"
    )

    # Get active profile
    profiles=$(get_active_profiles $device_object_id)
    first_active_profile=$(echo "$profiles" | jq '.[0]')

    # Get profile index
    first_active_profile_index=$(echo "$first_active_profile" | jq -r '.index')
    route_to_set=$(echo "$route_to_set" | jq ". + { \"profile\": $first_active_profile_index }")

    # Add the "save" property to the "route_to_set" object
    route_to_set=$(echo "$route_to_set" | jq '. + { "save": false }')

    # Get active routes
    old_active_routes="$(get_active_routes $device_object_id)"

    # Get route index
    route_index=$(echo "$old_active_routes" | jq -r "map(.device == $node_index) | index(true)")

    # Create a new routes array where the route we want to set replaces the old one
    updated_active_routes=$(echo "$old_active_routes" | jq ".[$route_index] = $route_to_set")

    # Check diff between old and new routes array
    #file1=/tmp/updated_active_routes && file2=/tmp/old_active_routes && echo "$updated_active_routes" > "$file1" && echo "$old_active_routes" > "$file2" && meld "$file1" "$file2" ; rm "$file1" "$file2"
    
    # Set the updated routes
    pw-cli set-param $device_object_id Route "$updated_active_routes"
}

# Function: set_profile
# Description: Set route for a given device by object ID
# Parameters:
#   - $1: The object ID of the device
#   - $2: The index of the new profile
function set_profile() {
    local device_object_id="$1"
    local profile_index="$2"

    # Get available profile "templates" for device with object ID 78
    profiles="$(get_all_profiles $device_object_id)"

    # Get desired profile template
    profile_template=$(echo "$profiles" | jq ".[] | select(.index == $PROFILE_INDEX)")

    # Add the "save" property and save as new JSON object
    profile_to_set=$(echo "$profile_template" | jq '. + { "save": false }')

    # Set the new profile(s)
    pw-cli set-param $device_object_id Profile "[ $profile_to_set ]"
}

# Example usage:
# get_audio_devices
# get_object_by_id 78
# get_device_by_name alsa_card.pci-0000_00_1f.3
# get_active_profiles 78
# get_active_routes 78
# get_all_profiles 78
# get_all_routes 78
# get_nodes_for_device_by_id 78
# set_profile 78 1
# set_route 45 0

그러나 약간의 추측이 포함되어 있습니다. 나는 set_profile과 set_routes를 사용하려고 시도했지만 성공하지 못했습니다.
프로필을 변경하려고 합니다.

#####################################################
# Attempt to change the profile                     #
#####################################################

# My audio device
DEVICE_NAME="alsa_card.pci-0000_00_1f.3"

# Index of the profile I want to use
PROFILE_INDEX=1

# Get device id
device_object_id=$(get_device_by_name "$DEVICE_NAME" | jq -r '.id')

# Set the desired profile
set_profile $device_object_id $PROFILE_INDEX
# Doesn't work and results in:
#    Array: child.size 2, child.type Spa:String
#    String "{"
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#####################################################

경로를 변경해 보았습니다.

#####################################################
# Attempt to change the route                       #
#####################################################

# My audio device
DEVICE_NAME="alsa_card.pci-0000_00_1f.3"

# Get device id
device_object_id=$(get_device_by_name "$DEVICE_NAME" | jq -r '.id')

# Get all nodes for the device
nodes=$(get_nodes_for_device_by_id $device_object_id)

# Error if there are no nodes
if [ "$nodes" == "[]" ]; then
    echo "There are no nodes for the active profile(s) of device with name '$DEVICE_NAME' (object ID: $device_object_id)" >&2
    exit 1
fi

# Get first sink node of current profile
first_sink_node_object_id=$(echo "$nodes" | jq '.[] | select(.info.props."media.class" == "Audio/Sink")'| jq -r '.id')

# Get first route output route (it's not guaranteed that this route is available for that specific node and profile)
first_output_route_index=$(get_all_routes $device_object_id | jq '.[] | select(.direction == "Output") | .index' | head -n 1)

# Set the route
set_route $first_sink_node_object_id $first_output_route_index
# Doesn't work and results in:
#    Array: child.size 2, child.type Spa:String
#    String "{"
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#    String ""
#####################################################

이제 제가 무엇을 하려고 했는지 자세히 설명하겠습니다. 서명은 set-params다음과 같습니다.

pw-cli set-param <object-id> <param-id> <param-json>

각각 object-id장치 ID 및 입니다.param-idRouteProfile

첫 번째 문제는 설정하려는 경로/프로필의 ID나 인덱스만 전달할 수 없고 전체 JSON 개체를 전달해야 하며 어떻게 보이는지 명확하지 않다는 것입니다.

모든 장치에는 가능한 프로필과 경로 세트가 있는 것 같습니다. 이들은 각각 .info.params.EnumProfile및 에서 객체 배열로 찾을 수 있습니다 .info.params.EnumRoute. 내가 아는 한 이러한 개체는 템플릿입니다. 활성 프로필과 경로는 각각 .info.params.Profile및 에 저장됩니다 .info.params.Route. 언뜻 보면 템플릿과 동일하지만 추가 속성이 있습니다. 의 각 객체에는 .info.params.Profiletrue 또는 false일 수 있는 부울 값인 "save"라는 추가 속성이 있습니다. .info.params.Route배열의 더 나쁜 점은 추가 속성 device, profile 및 save가 있고 이름이 지정된 속성 중 하나에 속성 , , , 및 object 가 info있는 속성인 새 속성 "props"가 있다는 것 입니다 . 이러한 속성에 대한 올바른 값을 어디서 얻을 수 있는지 모르겠습니다. 이러한 속성 중 일부는 연결된 (싱크/소스) 노드(예: 및 ) 에서 검색할 수 있는 것처럼 보이지만 일부의 경우 어떤 값을 설정해야 하는지 추측하고 있습니다.mutechannelVolumesvolumeBasevolumeStepchannelMapsoftVolumeslatencyOffsetNsecchannelMapsoftVolumes

.info.params.Profile또 다른 문제는 왜 배열인지 이해할 수 없다는 것입니다 . 이는 여러 개의 활성 프로필이 있을 수 있음을 의미하지만 장치가 이를 지원하는지 어떻게 알 수 있습니까?

어떤 도움이라도 대단히 감사하겠습니다.

답변1

문제는"PipeWire를 사용하여 오디오 장치 프로필을 설정하는 기본 방법은 무엇입니까?"

pw-cli것 같다너무 관여하다내 취향에 적합합니다(IMHO 오디오 청취는 두뇌 유출이 되어서는 안 됩니다). 다루기 더 쉬운 또 다른 도구가 있습니다. 바로 wpctl(와이어 배관공 제어)라는 것입니다. 분명히 그것은 데비안의 일부이지만 (Debian)에 포함되어 있으므로 내 책에서는 wireplumber"기본"으로 간주됩니다 .wireplumberapt installpipewire

내 시스템은 매우 간단합니다. Raspberry Pi와 Bluetooth 스피커입니다. CLI에서 볼륨을 쉽게 설정할 수 있습니다.

wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+

매뉴얼은 없고 도움말 옵션 wpctl만 있지만 -hArch 직원이 하나를 만들었습니다.아주 좋은 참고자료.

관련 정보