PulseAudio:シンク情報

シンク情報の取得
「シンク」は、サーバーが認識するオーディオ出力を意味します。

各クライアントから送られてきたオーディオデータは、最終的にこのシンクに送られます。
そして、シンクが選択しているハードウェアの端子に、オーディオが送られます。
シンク情報の取得
pa_operation *pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx,
    pa_sink_info_cb_t cb, void *userdata);

pa_operation *pa_context_get_sink_info_by_name(pa_context *c, const char *name,
    pa_sink_info_cb_t cb, void *userdata);

pa_operation *pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata);

//コールバック関数
typedef void (*pa_sink_info_cb_t)(pa_context *c, const pa_sink_info *i, int eol, void *userdata);

by_index はシンクのインデックス番号、by_name はシンク名を指定して取得します。
list は、すべてのシンク情報を取得します。
シンクの数
サーバーは、複数のシンクを持つことができます。
ただし、カードのプロファイルごとに、作成されるシンクの数は決まっています。

出力が有効なプロファイルなら、通常は1です。
入力のみのプロファイルであれば、0となります。
プログラム
すべてのシンク情報を取得して、出力するプログラムです。

$ cc -o 07-sink 07-sink.c util.c -lpulse

#include <stdio.h>
#include <pulse/pulseaudio.h>
#include "util.h"

PulseData *pulse;

static void _sink_callback(pa_context *c,const pa_sink_info *i,int eol,void *userdata)
{
    int j;
    char m[64];
    pa_sink_port_info *sport;

    if(!i)
    {
        pa_threaded_mainloop_signal(pulse->mainloop, 0);
        return;
    }

    printf("=== [%d] ===\n", i->index);
    printf("name: %s\n", i->name);
    printf("description: '%s'\n", i->description);
    
    pa_sample_spec_snprint(m, 64, &i->sample_spec);
    printf("sample_spcec: %s\n", m);

    pa_channel_map_snprint(m, 64, &i->channel_map);
    printf("channel_map: '%s'\n", m);

    printf("owner_module: %u\n", i->owner_module);

    pa_cvolume_snprint(m, 64, &i->volume);
    printf("volume: '%s'\n", m);

    printf("mute: %d\n", i->mute);
    printf("monitor_source: %u\n", i->monitor_source);
    printf("monitor_source_name: %s\n", i->monitor_source_name);
    printf("latency(us): %lu\n", i->latency);
    printf("driver: %s\n", i->driver);
    printf("flags: 0x%X\n", i->flags);
    printf("configured_latency(us): %lu\n", i->configured_latency);
    
    pa_volume_snprint(m, 64, i->base_volume);
    printf("base_volume: %u '%s'\n", i->base_volume, m);

    printf("state: %d\n", i->state);
    printf("n_volume_steps: %u\n", i->n_volume_steps);
    printf("card: %u\n", i->card);
    printf("n_ports: %u\n", i->n_ports);
    printf("n_formats: %u\n", i->n_formats);

    pulse_put_proplist(i->proplist);

    //ports

    printf("\n-- ports --\n");

    for(j = 0; j < i->n_ports; j++)
    {
        sport = i->ports[j];
        
        printf("-[%d]-%s\n", j, (sport == i->active_port)? "(*)": "");

        printf("name: %s\n", sport->name);
        printf("description: %s\n", sport->description);
        printf("priority: %u\n", sport->priority);
        printf("available: %d\n", sport->available);
        printf("availability_group: %s\n", sport->availability_group);
        printf("type: %u\n", sport->type);
    }

    //formats

    printf("\n-- formats --\n");

    for(j = 0; j < i->n_formats; j++)
    {
        printf("[%d] encoding:%d\n", j, i->formats[j]->encoding);

        pulse_put_proplist(i->formats[j]->plist);
    }

    printf("\n");
}

static pa_operation *_get_info(PulseData *p,void *data)
{
    return pa_context_get_sink_info_list(pulse->ctx, _sink_callback, NULL);
}

int main(void)
{
    pulse = pulse_connect(0);
    if(!pulse) return 1;

    pulse_wait_operation(pulse, _get_info, NULL);

    pulse_free(pulse);

    return 0;
}
シンク情報 (pa_sink_info)
シンク情報は、pavucontol の「出力デバイス」タブで表示される情報になります。

typedef struct pa_sink_info {
    const char *name;
    uint32_t index;
    const char *description;
    pa_sample_spec sample_spec;
    pa_channel_map channel_map;
    uint32_t owner_module;
    pa_cvolume volume;
    int mute;
    uint32_t monitor_source;
    const char *monitor_source_name;
    pa_usec_t latency;
    const char *driver;
    pa_sink_flags_t flags;
    pa_proplist *proplist;
    pa_usec_t configured_latency;
    pa_volume_t base_volume;
    pa_sink_state_t state;
    uint32_t n_volume_steps;
    uint32_t card;
    uint32_t n_ports;
    pa_sink_port_info **ports;
    pa_sink_port_info *active_port;
    uint8_t n_formats;
    pa_format_info **formats;
} pa_sink_info;

//シンクのポート情報
//(値はカードのポート情報と同じ)

typedef struct pa_sink_port_info {
    const char *name;
    const char *description;
    uint32_t priority;
    int available;
    const char *availability_group; //ver 14.0
    uint32_t type; //ver 14.0
} pa_sink_port_info;

//フォーマット情報

typedef struct pa_format_info {
    pa_encoding_t encoding;
    pa_proplist *plist;
} pa_format_info;

nameシンク名
indexインデックス番号
descriptionシンクの詳細説明
sample_specサンプルスペック
channel_mapチャンネルマップ
owner_module所有モジュールのインデックス、または PA_INVALID_INDEX
volume音量
mute0 以外でミュート状態
monitor_sourceこのシンクに接続されている、モニターソースのインデックス
monitor_source_nameこのシンクに接続されている、モニターソースの名前
latency出力バッファ内のキューに入れられたオーディオの長さ(マイクロ秒)
driverドライバ名
flagsフラグ。

PA_SINK_NOFLAGS (0): フラグなし。
PA_SINK_HW_VOLUME_CTRL: ハードウェア音量コントロールをサポート。これは動的フラグであり、シンクが初期化された後、実行時に変更される可能性がある。
PA_SINK_LATENCY: 遅延要求をサポート。
PA_SINK_HARDWARE: 仮想/ソフトウェアシンクとは対照的に、ある種のハードウェアシンク。
PA_SINK_NETWORK: ある種のネットワーク化されたシンク。
PA_SINK_HW_MUTE_CTRL: ハードウェアのミュート操作をサポート。※動的フラグ
PA_SINK_DECIBEL_VOLUME: 音量は、pa_sw_volume_to_dB() で dB に変換できる。※動的
PA_SINK_FLAT_VOLUME: このシンクはフラットボリュームモード。接続されているすべての入力の音量が、常に最大になる。
PA_SINK_DYNAMIC_LATENCY: 遅延は、接続されたストリームの要求に応じて動的に調整できる。
PA_SINK_SET_FORMATS: 接続されたハードウェアが、サポートするフォーマットを設定できる。これを行うための実際の機能は、拡張機能によって提供される場合がある。
proplistプロパティリスト
configured_latencyこのデバイスに設定されているレイテンシ(マイクロ秒)
base_volume出力デバイスのコンテキストで増幅/減衰されていないベース音量
state状態フラグ。

PA_SINK_INVALID_STATE: サーバーが、シンク状態の取得をサポートしていない。
PA_SINK_RUNNING: 実行中で、シンクが再生中。少なくとも1つの、停止中でないシンク入力によって使用されている。
PA_SINK_IDLE: アイドル状態。シンクは再生できるが、停止中でないシンク入力が接続されていない。
PA_SINK_SUSPENDED: 一時停止。シンクアクセスを閉じることができる。
n_volume_steps任意の音量値をサポートしない場合の、ボリュームステップの数
cardカードのインデックス番号、または PA_INVALID_INDEX
n_portsports 配列の数
portsシンクのポート情報のポインタの配列、または NULL
active_port現在アクティブなシンクポートのポインタ(ports の配列内の一つ)、または NULL
n_formatsformats 配列の数
formatsシンクがサポートしているフォーマット情報のポインタの配列。
出力されるオーディオのエンコーディングタイプ。

インデックス番号は、サーバーでシンクが作成されるたびに増加するので、プロファイルを変更したりすると、値が変わります。
出力結果 (1)
選択しているプロファイルが「アナログステレオデュプレックス」の場合。
volume は、音量コントロールなどで表示される値になります。



=== [0] ===
name: alsa_output.pci-0000_00_1f.3.analog-stereo
description: '内部オーディオ アナログステレオ'
sample_spcec: s16le 2ch 48000Hz
channel_map: 'front-left,front-right'
owner_module: 6
volume: '0:  23% 1:  23%'
mute: 0
monitor_source: 0
monitor_source_name: alsa_output.pci-0000_00_1f.3.analog-stereo.monitor
latency(us): 0
driver: module-alsa-card.c
flags: 0xB7
 HW_VOLUME_CTRL:LATENCY:HARDWARE:HW_MUTE_CTRL:DECIBEL_VOLUME:DYNAMIC_LATENCY:
configured_latency(us): 0
base_volume: 65536 '100%'
state: 2
n_volume_steps: 65537
card: 0
n_ports: 2
n_formats: 1
<proplist>
alsa.resolution_bits = "16"
device.api = "alsa"
device.class = "sound"
alsa.class = "generic"
alsa.subclass = "generic-mix"
alsa.name = "ALC892 Analog"
alsa.id = "ALC892 Analog"
alsa.subdevice = "0"
alsa.subdevice_name = "subdevice #0"
alsa.device = "0"
alsa.card = "0"
alsa.card_name = "HDA Intel PCH"
alsa.long_card_name = "HDA Intel PCH at 0xdf140000 irq 130"
alsa.driver_name = "snd_hda_intel"
device.bus_path = "pci-0000:00:1f.3"
sysfs.path = "/devices/pci0000:00/0000:00:1f.3/sound/card0"
device.bus = "pci"
device.vendor.id = "8086"
device.vendor.name = "Intel Corporation"
device.product.id = "a170"
device.product.name = "100 Series/C230 Series Chipset Family HD Audio Controller"
device.form_factor = "internal"
device.string = "front:0"
device.buffering.buffer_size = "352800"
device.buffering.fragment_size = "176400"
device.access_mode = "mmap+timer"
device.profile.name = "analog-stereo"
device.profile.description = "アナログステレオ"
device.description = "内部オーディオ アナログステレオ"
module-udev-detect.discovered = "1"
device.icon_name = "audio-card-pci"

-- ports --
-[0]-
name: analog-output-lineout
description: ライン出力
priority: 9000
available: 1
availability_group: (null)
type: 4
-[1]-(*)
name: analog-output-headphones
description: アナログヘッドフォン
priority: 9900
available: 2
availability_group: (null)
type: 3

-- formats --
[0] encoding:1
<proplist>

シンクのポート情報は、基本的にカードのポート情報と同じですが、構造体やメンバは異なります。
シンクの ports 配列は、このシンクで出力できる端子のリストになります。

このシンクにおいて、ハードウェア上のどの端子に音が出力されるかは、active_port で判断できます。
pavucontrol などを使うと、ユーザーが、出力するポートを選択できます。
ここでは、ヘッドホン端子が選択されています。

フォーマットの encoding = 1 は、PA_ENCODING_PCM です。
出力結果 (2)
プロファイルが「Digital Stereo (HDMI) 出力」の場合。

=== [10] ===
name: alsa_output.pci-0000_00_1f.3.hdmi-stereo
description: '内部オーディオ Digital Stereo (HDMI)'
sample_spcec: s16le 2ch 44100Hz
channel_map: 'front-left,front-right'
owner_module: 6
volume: '0: 100% 1: 100%'
mute: 0
monitor_source: 13
monitor_source_name: alsa_output.pci-0000_00_1f.3.hdmi-stereo.monitor
latency(us): 40953
driver: module-alsa-card.c
flags: 0x1A6
 LATENCY:HARDWARE:DECIBEL_VOLUME:DYNAMIC_LATENCY:SET_FORMATS:
configured_latency(us): 40000
base_volume: 65536 '100%'
state: 1
n_volume_steps: 65537
card: 0
n_ports: 1
n_formats: 1
<proplist>
alsa.resolution_bits = "16"
device.api = "alsa"
device.class = "sound"
alsa.class = "generic"
alsa.subclass = "generic-mix"
alsa.name = "PHL 224E5"
alsa.id = "HDMI 0"
alsa.subdevice = "0"
alsa.subdevice_name = "subdevice #0"
alsa.device = "3"
alsa.card = "0"
alsa.card_name = "HDA Intel PCH"
alsa.long_card_name = "HDA Intel PCH at 0xdf140000 irq 130"
alsa.driver_name = "snd_hda_intel"
device.bus_path = "pci-0000:00:1f.3"
sysfs.path = "/devices/pci0000:00/0000:00:1f.3/sound/card0"
device.bus = "pci"
device.vendor.id = "8086"
device.vendor.name = "Intel Corporation"
device.product.id = "a170"
device.product.name = "100 Series/C230 Series Chipset Family HD Audio Controller"
device.form_factor = "internal"
device.string = "hdmi:0"
device.buffering.buffer_size = "352800"
device.buffering.fragment_size = "176400"
device.access_mode = "mmap+timer"
device.profile.name = "hdmi-stereo"
device.profile.description = "Digital Stereo (HDMI)"
device.description = "内部オーディオ Digital Stereo (HDMI)"
module-udev-detect.discovered = "1"
device.icon_name = "audio-card-pci"

-- ports --
-[0]-(*)
name: hdmi-output-0
description: HDMI / DisplayPort
priority: 5900
available: 2
availability_group: (null)
type: 10

-- formats --
[0] encoding:1
<proplist>
シンクの操作
シンクのいくつかの情報は、クライアントから変更できます。
出力ポートの変更
指定したシンクで、出力先のポートを変更したい場合は、以下の関数を使います。

pa_operation *pa_context_set_sink_port_by_index(pa_context *c, uint32_t idx,
    const char *port, pa_context_success_cb_t cb, void *userdata);

pa_operation *pa_context_set_sink_port_by_name(pa_context *c, const char *name,
    const char *port, pa_context_success_cb_t cb, void *userdata);

//コールバック関数
typedef void (*pa_context_success_cb_t)(pa_context *c, int success, void *userdata);

by_index はシンクのインデックス番号を指定、by_name はシンクの名前を指定します。
port 引数で、変更するポートの名前を指定します。
ミュート状態の変更
pa_operation *pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx,
    int mute, pa_context_success_cb_t cb, void *userdata);

pa_operation *pa_context_set_sink_mute_by_name(pa_context *c, const char *name,
    int mute, pa_context_success_cb_t cb, void *userdata);

指定したシンクのミュート状態を変更します。
音量変更
pa_operation *pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx,
    const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);

pa_operation *pa_context_set_sink_volume_by_name(pa_context *c, const char *name,
    const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);

指定したシンクの音量を変更します。

一般的に、PulseAudio の出力の音量を変更したい場合は、この関数を使います。
音量についての詳細は、次回で説明します。
シンクの一時停止
pa_operation *pa_context_suspend_sink_by_name(pa_context *c, const char *sink_name,
    int suspend, pa_context_success_cb_t cb, void *userdata);

pa_operation *pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx,
    int suspend, pa_context_success_cb_t cb, void *userdata);

指定したシンクを、一時停止または再開させます。
これにより、このシンクに接続されている、すべてのオーディオ再生を止めることができます。