PulseAudio:クライアント情報

クライアント情報
今度は、サーバーに接続されている、各クライアントの情報を取得してみます。
クライアント情報の取得
pa_operation *pa_context_get_client_info(pa_context *c, uint32_t idx,
    pa_client_info_cb_t cb, void *userdata);

pa_operation *pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata);

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

//クライアント情報の構造体

typedef struct pa_client_info {
    uint32_t index;        //インデックス番号
    const char *name;      //クライアント名
    uint32_t owner_module; //所有モジュールのインデックス
    const char *driver;    //ドライバ名
    pa_proplist *proplist; //プロパティリスト
} pa_client_info;

pa_context_get_client_info() は、インデックス番号を指定して、特定のクライアントの情報を取得します。
pa_context_get_client_info_list() は、すべてのクライアントの情報を取得します。

インデックス番号は、各オブジェクトを、サーバーが識別するための番号です。
オブジェクトの種類ごとに、0〜の番号が割り振られます。

プロパティリストは、キー文字列と値が関連付けられた、任意の値のリストです。
コールバック関数
typedef void (*pa_client_info_cb_t)(pa_context *c, const pa_client_info *i, int eol, void *userdata);

今回のコールバック関数には、eol 引数が追加されています。

複数の情報が取得される場合、コールバック関数は複数回実行されます。
i と eol の値によって、データを判断します。

データがあるi は NULL 以外。eol = 0。
データの終了i = NULL、eol = 正の値
エラーi = NULL、eol = 負の値

データの数だけコールバック関数が来た後、最後に、i = NULL, eol > 0 の状態でコールバック関数が来ます。

なお、pa_context_get_client_info() のように、単一の情報を要求する関数を使ったとしても、上記の動作通り、コールバックは2回来ます。
プログラム
現在、サーバーに接続されている、クライアントのリストを表示するプログラムです。

$ cc -o 05-client 05-client.c util.c -lpulse

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

PulseData *pulse;

static void _client_callback(pa_context *c,const pa_client_info *i,int eol,void *userdata)
{
    if(!i)
    {
        pa_threaded_mainloop_signal(pulse->mainloop, 0);
        return;
    }
    
    printf("[index:%u]\n", i->index);
    printf("name: %s\n", i->name);
    printf("owner_module: %u\n", i->owner_module);
    printf("driver: %s\n", i->driver);

    if(i->proplist)
    {
        char *str;
        str = pa_proplist_to_string(i->proplist);
        printf("<proplist>\n%s", str);
        pa_xfree(str);
    }

    printf("\n");
}

static pa_operation *_get_info(PulseData *p,void *data)
{
    return pa_context_get_client_info_list(pulse->ctx, _client_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;
}
pulse_wait_operation
util.c に、指定した関数 (func 引数) 内で PulseAudio の操作をして、戻り値の pa_operation * を元に、操作が終わるまで待つ関数を用意しています。

PulseAudio の操作をする前にロックが必要になるので、このような形になっています。

typedef pa_operation *(*pulse_func_wait_op)(PulseData *p,void *data);

void pulse_wait_operation(PulseData *p,pulse_func_wait_op func,void *data)
{
    pa_operation *op;

    pa_threaded_mainloop_lock(p->mainloop);

    op = (func)(p, data);
    if(!op)
    {
        pa_threaded_mainloop_unlock(p->mainloop);
        return;
    }

    while(pa_operation_get_state(op) == PA_OPERATION_RUNNING)
        pa_threaded_mainloop_wait(p->mainloop);

    pa_operation_unref(op);
    
    pa_threaded_mainloop_unlock(p->mainloop);
}
実行結果
例えば、以下のように表示されます。
同じサーバーに接続されているクライアントのリストが表示されます。

[index:0]
name: Login Session 3
owner_module: 14
driver: module-systemd-login.c
<proplist>
application.name = "Login Session 3"
systemd-login.session = "3"

...

[index:3]
name: Firefox
owner_module: 8
driver: protocol-native.c
<proplist>
...

[index:21]
name: test-pulse
owner_module: 8
driver: protocol-native.c
<proplist>
application.name = "test-pulse"
native-protocol.peer = "UNIX socket client"
native-protocol.version = "35"
application.process.id = "***"
application.process.user = "***"
application.process.host = "***"
application.process.binary = "run"
application.language = "C"
window.x11.display = ":0"
application.process.machine_id = "***"
application.process.session_id = "2"

プロパティリストには、アプリケーションの情報などが格納されています。

何度かこのプログラムを実行すると、test-pulse の index が増加していくのがわかります。

インデックス番号は、サーバーが起動している間は、常に新しい番号で割り当てられます。
オブジェクトが削除されても、古いインデックスは再利用されません。