PulseAudio:ストリーム

ストリーム
ここから、再生/録音のメインとなる、ストリームを扱っていきます。
ストリームの作成
pa_stream *pa_stream_new(pa_context *c, const char *name,
    const pa_sample_spec *ss, const pa_channel_map *map);

//プロパティリストを指定

pa_stream *pa_stream_new_with_proplist(pa_context *c, const char *name,
    const pa_sample_spec *ss, const pa_channel_map *map, pa_proplist *p);

//圧縮エンコード対応

pa_stream *pa_stream_new_extended(pa_context *c, const char *name,
    pa_format_info *const *formats, unsigned int n_formats, pa_proplist *p);

ストリームを作成する関数は、3つあります。

PCM で出力するなら、通常は pa_stream_new() を使います。
さらに、プロパティを指定したい場合は、pa_stream_new_with_proplist() を使います。
PCM または圧縮エンコードで出力したい場合は、pa_stream_new_extended() を使います。

pa_stream は、クライアントが、自身で作成したストリームを識別するためのオブジェクトです。
参照カウント
pa_stream *pa_stream_ref(pa_stream *s);
void pa_stream_unref(pa_stream *s);

ストリームの解放関数はないので、使用後は参照カウンタを -1 します。
サーバーと接続
//再生用にシンクと接続

int pa_stream_connect_playback(pa_stream *s, const char *dev, const pa_buffer_attr *attr,
    pa_stream_flags_t flags, const pa_cvolume *volume, pa_stream *sync_stream);

//録音用にソースと接続

int pa_stream_connect_record(pa_stream *s, const char *dev,
    const pa_buffer_attr *attr, pa_stream_flags_t flags);

作成したストリームは、再生用ならサーバー上のシンクに、録音用なら、サーバー上のソースと接続する必要があります。
接続されると、サーバー上で、シンク入力またはソース出力が作成されます。

dev接続するシンクまたはソースの名前。
NULL でサーバーが選択する(通常は NULL で良い)。
attr再生バッファの属性。NULL でデフォルト。
flagsフラグ。

(以下、一部抜粋)
PA_STREAM_START_CORKED : ストリームを停止状態で作成。
PA_STREAM_START_MUTED : ストリームをミュート状態で作成。
PA_STREAM_START_UNMUTED : ストリームをミュート解除状態で作成。
PA_STREAM_RELATIVE_VOLUME : volume 引数の値を、常に相対音量として扱う。
volume(再生用) ストリームの初期音量。NULL でデフォルト。
相対的または絶対的な音量として扱われるかは、シンクの状態やフラグによる。
ver 5.0 以降の場合、1つのチャンネルの音量しか指定されてない場合、すべてのチャンネルにその音量が適用される。
sync_streamNULL 以外の場合、指定ストリームと同期される
戻り値成功時は 0

flags で、ミュート関連のフラグ指定がない場合、ストリームをミュート状態にするかどうかは、サーバーが選択します。

volume 引数での音量指定では、ver 0.9.20 以降の場合、シンクがフラットボリュームモード (struct pa_sink_info の flags で、PA_SINK_FLAT_VOLUME が ON) の場合は絶対音量で、それ以外は相対音量になります。

基本的には相対音量になるので、全体的な絶対音量はシンクで指定し、必要であれば、再生ストリームごとに相対音量を指定することになります。
切断
int pa_stream_disconnect(pa_stream *s);

ストリームを、サーバー上のシンク/ソースから切断します。
これにより、再生出力などは行われなくなります。

不要になったストリームは、切断した後、pa_stream_unref() で解放します。
準備ができるのを待つ
サーバーとの接続時と同様に、ストリームを作成または接続しても、すぐに操作が完了するとは限りません。
ストリームの現在の状態を取得した上で、準備ができるのを待つ必要があります。
状態の取得
pa_stream_state_t pa_stream_get_state(const pa_stream *p);

ストリームの現在の状態を取得します。

PA_STREAM_UNCONNECTEDストリームはまだ、シンクまたはソースに接続されていない
PA_STREAM_CREATINGストリームを作成中
PA_STREAM_READYストリームが準備できた。オーディオデータを渡すことができる。
PA_STREAM_FAILEDストリームが無効になるエラーが発生した
PA_STREAM_TERMINATEDストリームは正常に終了した
ストリームの状態変化時のコールバックをセット
void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata);

//コールバック関数
typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata);

ストリームの状態が変化した時に呼び出される、コールバック関数をセットします。
プログラム
ストリームを作成した後、削除するプログラムです。
また、サーバーイベントの通知を設定して、シンク入力のイベントを出力しています。

$ cc -o 11-stream 11-stream.c util.c -lpulse

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

PulseData *pulse;

//ストリームの状態変化

static void _cb_state(pa_stream *strm,void *data)
{
    int state = pa_stream_get_state(strm);

    switch(state)
    {
        case PA_STREAM_UNCONNECTED: printf("UNCONNECTED\n"); break;
        case PA_STREAM_CREATING: printf("CREATING\n"); break;
        case PA_STREAM_READY: printf("READY\n"); break;
        case PA_STREAM_FAILED: printf("FAILED\n"); break;
        case PA_STREAM_TERMINATED: printf("TERMINATED\n"); break;
    }

    if(state == PA_STREAM_READY || state == PA_STREAM_FAILED
        || state == PA_STREAM_TERMINATED)
    {
        pa_threaded_mainloop_signal(pulse->mainloop, 0);
    }
}

//ストリームの作成と接続

static pa_stream *_create_stream(PulseData *p)
{
    pa_stream *strm;
    pa_sample_spec ss;
    pa_stream_state_t state;

    ss.format = PA_SAMPLE_S16LE;
    ss.rate = 48000;
    ss.channels = 1;

    strm = pa_stream_new(p->ctx, "test-stream", &ss, NULL);

    pa_stream_set_state_callback(strm, _cb_state, NULL);

    if(pa_stream_connect_playback(strm, NULL, NULL, 0, NULL, NULL))
    {
        printf("! failed connect\n");
        pa_stream_unref(strm);
        return NULL;
    }

    while(1)
    {
        state = pa_stream_get_state(strm);
        if(state == PA_STREAM_READY || state == PA_STREAM_FAILED) break;

        pa_threaded_mainloop_wait(p->mainloop);
    }

    return strm;
}

//サーバーイベント

static void _event_callback(pa_context *c,pa_subscription_event_type_t t,uint32_t idx,void *userdata)
{
    int n = t & PA_SUBSCRIPTION_EVENT_TYPE_MASK;
    const char *mes = NULL;

    switch(n)
    {
        case PA_SUBSCRIPTION_EVENT_NEW: mes = "NEW"; break;
        case PA_SUBSCRIPTION_EVENT_CHANGE: mes = "CHANGE"; break;
        case PA_SUBSCRIPTION_EVENT_REMOVE: mes = "REMOVE"; break;
    }

    printf("sink-input: %s, idx=%d\n", mes, idx);
}

static pa_operation *_set_event(PulseData *p,void *data)
{
    pa_context_set_subscribe_callback(p->ctx, _event_callback, NULL);

    return pa_context_subscribe(p->ctx, PA_SUBSCRIPTION_MASK_SINK_INPUT, pulse_success_callback_signal, p);
}

int main(void)
{
    pa_stream *strm;

    pulse = pulse_connect(0);
    if(!pulse) return 1;

    //サーバーイベントをセット

    pulse_wait_operation(pulse, _set_event, NULL);

    //ストリームの作成

    pa_threaded_mainloop_lock(pulse->mainloop);

    strm = _create_stream(pulse);

    if(strm)
    {
        pa_stream_disconnect(strm);

        //実際に終了するまで待つ (REMOVE イベント取得のため)

        while(pa_stream_get_state(strm) != PA_STREAM_TERMINATED)
            pa_threaded_mainloop_wait(pulse->mainloop);

        pa_stream_unref(strm);
    }

    pa_threaded_mainloop_unlock(pulse->mainloop);

    pulse_free(pulse);

    return 0;
}
解説
実行すると、以下のように出力されます。

CREATING
READY
sink-input: NEW, idx=11
TERMINATED
sink-input: REMOVE, idx=11

ストリームの作成後、コールバック関数を設定して、接続を開始し、準備ができるか失敗するまで待ちます。

実際に準備が出来た段階で、サーバー上に、新しいシンク入力が作成されています。

その後はすぐに切断し、ストリームの状態が TERMINATED になって、正常に終了するまで待ちます。

これは、シンク入力の REMOVE イベントを出力するためです。
待たずにプログラムを終了してしまうと、その後のサーバーイベントを受け取る機会がないので、REMOVE が出力されません。

通常は待つ必要はないので、切断後すぐに unref して構いません。
シンク入力/ソース出力のインデックスを取得
pa_stream_get_index() を使うと、ストリームのシンク入力/ソース出力のインデックスを取得することができます。

uint32_t pa_stream_get_index(const pa_stream *s);