PulseAudio:シンク入力

シンク入力
シンク入力 (sink input)」は、各クライアントが再生用に作成したそれぞれのストリームを、サーバーが識別するためのものです。

クライアントが作成した再生用のストリームを、サーバーと接続すると、サーバー上で1つのシンク入力が作成されます。
このシンク入力を、サーバー上の1つのシンクに接続することによって、オーディオが出力されます。

シンクから見れば、オーディオデータが与えられることになるので、「シンク入力」という名前になっています。

ソース出力 (source output)」の場合は、録音用のストリームを、サーバーが識別するためのもので、ソース出力が作成された後、サーバー上の1つのソースに接続されます。
ソースから見れば、入力したデータを各ストリームに送る形になるので、「ソース出力」となります。

ここでは、サーバーから、シンク入力の情報を取得してみます。
なお、ソース出力についてはやりませんが、構造体の値も含めて、基本的にはシンク入力と同じです。
シンク入力の情報を取得
pa_operation *pa_context_get_sink_input_info(pa_context *c, uint32_t idx,
    pa_sink_input_info_cb_t cb, void *userdata);

pa_operation *pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata);

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

シンク入力にもインデックスが割り振られるので、特定のシンク入力の情報を取得する場合は、pa_context_get_sink_input_info() を使います。

pa_context_get_sink_input_info_list() で、すべてのシンク入力の情報を取得します。
pa_sink_input_info 構造体
typedef struct pa_sink_input_info {
    uint32_t index;
    const char *name;
    uint32_t owner_module;
    uint32_t client;
    uint32_t sink;
    pa_sample_spec sample_spec;
    pa_channel_map channel_map;
    pa_cvolume volume;
    pa_usec_t buffer_usec;
    pa_usec_t sink_usec;
    const char *resample_method;
    const char *driver;
    int mute;
    pa_proplist *proplist;
    int corked;
    int has_volume;
    int volume_writable;
    pa_format_info *format;
} pa_sink_input_info;

indexシンク入力のインデックス
nameシンク入力の名前。
(クライアントが設定したストリームの名前)
clientこのシンク入力が属するクライアントの、インデックス。
どのクライアントにも属していない場合は PA_INVALID_INDEX。
sink接続されているシンクのインデックス
volumeこのシンク入力の音量
buffer_usecシンク入力のバッファのレイテンシ(マイクロ秒)
sink_usecシンクデバイスのレイテンシ(マイクロ秒)
resample_methodこのシンク入力で使用される、再サンプリング方法の文字列
corked再生が停止中か
has_volumevolume の音量を使うか
volume_writablevolume の音量を適用できるか
プログラム
サーバーから、シンク入力の情報を取得して、出力するプログラムです。

$ cc -o 10-sinkinput 10-sinkinput.c util.c -lpulse

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

PulseData *pulse;

static void _sinkinput_callback(pa_context *c,const pa_sink_input_info *i,int eol,void *userdata)
{
    char m[64];

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

    printf("=== [%d] ===\n", i->index);
    printf("name: %s\n", i->name);
    printf("owner_module: %u\n", i->owner_module);
    printf("client: %u\n", i->client);
    printf("sink: %u\n", i->sink);
    
    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);

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

    printf("buffer_usec: %lu\n", i->buffer_usec);
    printf("sink_usec: %lu\n", i->sink_usec);
    printf("resample_method: %s\n", i->resample_method);
    printf("driver: %s\n", i->driver);
    printf("mute: %d\n", i->mute);
    printf("corked: %d\n", i->corked);
    printf("has_volume: %d\n", i->has_volume);
    printf("volume_writable: %d\n", i->volume_writable);
    printf("format: encoding(%d)\n", i->format->encoding);

    pulse_put_proplist(i->proplist);

    printf("\n");
}

static pa_operation *_get_info(PulseData *p,void *data)
{
    return pa_context_get_sink_input_info_list(pulse->ctx, _sinkinput_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;
}
実行結果
※何かしらのアプリで、オーディオを再生している状態で、実行してください。

以下は、SMPlayer で test.mp4 のファイルを再生している時の、出力結果です。

=== [13] ===
name: test.mp4
owner_module: 8
client: 36
sink: 16
sample_spcec: float32le 2ch 48000Hz
channel_map: 'front-left,front-right'
volume: '0: 100% 1: 100%'
buffer_usec: 74666
sink_usec: 60368
resample_method: copy
driver: protocol-native.c
mute: 0
corked: 0
has_volume: 1
volume_writable: 1
format: encoding(1)
<proplist>
media.icon_name = "SMPlayer"
media.name = "test.mp4"
application.name = "SMPlayer"
native-protocol.peer = "UNIX socket client"
native-protocol.version = "35"
application.process.id = "***"
application.process.user = "***"
application.process.host = "***"
application.process.binary = "mpv"
application.language = "C"
window.x11.display = ":0"
application.process.machine_id = "***"
application.process.session_id = "2"
application.icon_name = "mpv"
module-stream-restore.id = "sink-input-by-application-name:SMPlayer"

このシンク入力は、SMPlayer が、"test.mp4" の名前で作成したストリームによって、作成されたものです。
index = 16 のシンクに接続されています。

サーバー情報のデフォルトのサンプルスペックは "s16le 2ch 44100Hz" で、シンクのサンプルスペックは "s16le 2ch 48000Hz" でしたが、このストリームは、"float32le 2ch 48000Hz" で出力されています。
再生中にシンク情報を見ても、"s16le 2ch 48000Hz" でした。

つまり、ストリームには float32le 2ch 48000Hz のデータが書き込まれ、そのデータがシンクに送られる時に、s16le 2ch 48000Hz のデータに変換されます。

サーバー上の音量とは別に、各ストリームには、個別の音量を設定することができます。
2つのシンク入力
2つのアプリで同時に再生を行った場合、以下のようになります(一部省略)。

=== [6] ===
name: Audacious
client: 13
sink: 4
sample_spcec: s16le 2ch 44100Hz
channel_map: 'front-left,front-right'
volume: '0: 100% 1: 100%'
buffer_usec: 465668
sink_usec: 60322
resample_method: (null)

=== [7] ===
name: test.mp4 - mpv
client: 15
sink: 4
sample_spcec: float32le 2ch 48000Hz
channel_map: 'front-left,front-right'
volume: '0: 100% 1: 100%'
buffer_usec: 77170
sink_usec: 60127
resample_method: speex-float-1

Audacious は 16bit で出力、SMPlayer は 32bit float で出力されています。

この場合、再生中にシンクの情報を見てみると、サンプルスペックは "s16le 2ch 44100Hz" になっていました。
Audacious の方が先に再生されているので、こちらの 44100Hz が優先されているようです。

後から再生した SMPlayer の再サンプリング方法は、"speex-float-1" になっています。
float32le 2ch 48000Hz で書き込まれたデータは、現在のシンクのサンプルスペックである、"s16le 2ch 44100Hz" に再サンプリングされます。

ちなみに、24000Hz のストリームを単独で再生すると、シンクは 48000Hz になり、22500Hz のストリームを再生すると、シンクは 44100Hz となります。
低いサンプリングレートの場合は、一番値が高くて、倍数になるような値に補正されます。

このように、シンクのサンプルスペックは、最初に再生されたストリームによって、動的に変更されることがわかります。
その後、新しいシンク入力が増えた場合は、現在のシンクのサンプルスペックに合わせて、再サンプリングされます。
シンク入力の操作
特定のシンク入力の音量を変更したり、ミュートにしたり、削除したりすることができます。
これにより、外部のアプリから、現在再生中のストリームの音量を変更したり、ストリームを終了させることができます。

また、特定のシンク入力を、別のシンクに接続することもできます。