非同期 API
シンプル API だけでは、本当に単純な操作しかできないので、PulseAudio を扱う場合は、基本的に非同期 API を使うことになります。
シンプル API は、内部で、非同期 API を使って実装されています。
シンプル API は、内部で、非同期 API を使って実装されています。
非同期
PulseAudio は、サーバーとしてデーモンが立ち上がった後、再生や録音をするクライアントとやり取りをする、という形の動作となります。
クライアントが何かしら要求を行うと、それを受け取ったサーバーが、実際に処理を行うという形になるため、非同期の動作となります。
例えば、クライアントが、サーバーとの接続を行う関数を実行したとしても、関数が戻った後、すぐに接続状態になるとは限りません。
ほとんどの処理では、サーバー上で実際に操作が完了するまで、待つ必要があるので、少々プログラムが複雑になります。
クライアントが何かしら要求を行うと、それを受け取ったサーバーが、実際に処理を行うという形になるため、非同期の動作となります。
例えば、クライアントが、サーバーとの接続を行う関数を実行したとしても、関数が戻った後、すぐに接続状態になるとは限りません。
ほとんどの処理では、サーバー上で実際に操作が完了するまで、待つ必要があるので、少々プログラムが複雑になります。
メインループ
PulseAudio の非同期 API では、GUI と同じように、メインループ内で、サーバーからのイベントを受け取る必要があります。
これによって、処理が完了したなどの通知を受けることができます。
これによって、処理が完了したなどの通知を受けることができます。
メインループの種類
非同期のメインループとして、以下の3つが用意されています。
poll のメインループは、PulseAudio からイベントが来るまで、ブロックして待ちます。
GUI など、PulseAudio とその他の処理を並行して実行したい場合は、プログラム独自のスレッド内で実行することも出来ます。
スレッドのメインループの場合は、PulseAudio が新しいスレッドを作成して、その中で自動的にイベントを処理させます。
スレッドに関して、実行環境に左右されないコードを書けるという利点があります。
どちらかで何かが制限されるということもないので、自分が使いたい方を使って構いません。
メインループ | poll() によってイベントを待つ |
---|---|
スレッド・メインループ | スレッド内で自動的にイベントが処理される |
GLIB メインループ | GLib のメインループのラッパー |
poll のメインループは、PulseAudio からイベントが来るまで、ブロックして待ちます。
GUI など、PulseAudio とその他の処理を並行して実行したい場合は、プログラム独自のスレッド内で実行することも出来ます。
スレッドのメインループの場合は、PulseAudio が新しいスレッドを作成して、その中で自動的にイベントを処理させます。
スレッドに関して、実行環境に左右されないコードを書けるという利点があります。
どちらかで何かが制限されるということもないので、自分が使いたい方を使って構いません。
作成
非同期 API を使う場合、最初に、必要なメインループを作成する必要があります。
new で作成、free で解放、get_api で API を取得します (コンテキストの作成時に必要)。
//poll メインループ pa_mainloop *pa_mainloop_new(void); void pa_mainloop_free(pa_mainloop *m); pa_mainloop_api *pa_mainloop_get_api(pa_mainloop *m); //スレッドメインループ pa_threaded_mainloop *pa_threaded_mainloop_new(void); void pa_threaded_mainloop_free(pa_threaded_mainloop *m); pa_mainloop_api *pa_threaded_mainloop_get_api(pa_threaded_mainloop *m);
new で作成、free で解放、get_api で API を取得します (コンテキストの作成時に必要)。
コンテキスト
メインループを作成したら、次にコンテキスト (pa_context) を作成します。
pa_context は、PulseAudio サーバーと接続するための、基本的なオブジェクトです。
複数のサーバーと接続するようなことでもない限りは、1つのアプリケーションで、1つのコンテキストを使います。
pa_context_new(), pa_context_new_with_proplist() は、新しいコンテキストを作成します。
with_proplist の場合は、追加でプロパティリストを指定できます。
name 引数は、アプリケーション名を指定します。
コンテキストは参照カウントされるので、解放関数がありません。
代わりに、pa_context_unref() で、参照カウンタを -1 します。
カウンタが 0 になると、実際に解放されます。
関数の失敗時、エラーコードを取得したい時は、pa_context_errno() を使います。
最後に発生したエラーの、エラーコードが返ります。
エラーコードから、説明用の文字列を取得したい場合は、pa_strerror() を使います。
コンテキストを、PulseAudio サーバーと接続します。
まず、サーバーと接続しないことには、何も操作ができません。
server = NULL で、PA_CONTEXT_NOAUTOSPAWN フラグがセットされていない場合、サーバーにアクセスできない時は、新しいデーモンが生成されます。
サーバーから切断します。
この関数は、すぐに処理されます。
pa_context は、PulseAudio サーバーと接続するための、基本的なオブジェクトです。
複数のサーバーと接続するようなことでもない限りは、1つのアプリケーションで、1つのコンテキストを使います。
作成
pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name); pa_context *pa_context_new_with_proplist(pa_mainloop_api *mainloop, const char *name, const pa_proplist *proplist);
pa_context_new(), pa_context_new_with_proplist() は、新しいコンテキストを作成します。
with_proplist の場合は、追加でプロパティリストを指定できます。
name 引数は、アプリケーション名を指定します。
参照カウント
pa_context *pa_context_ref(pa_context *c); void pa_context_unref(pa_context *c);
コンテキストは参照カウントされるので、解放関数がありません。
代わりに、pa_context_unref() で、参照カウンタを -1 します。
カウンタが 0 になると、実際に解放されます。
エラー
int pa_context_errno(const pa_context *c); const char *pa_strerror(int error);
関数の失敗時、エラーコードを取得したい時は、pa_context_errno() を使います。
最後に発生したエラーの、エラーコードが返ります。
エラーコードから、説明用の文字列を取得したい場合は、pa_strerror() を使います。
接続
int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
コンテキストを、PulseAudio サーバーと接続します。
まず、サーバーと接続しないことには、何も操作ができません。
server | 接続するサーバー名。NULL でデフォルト |
---|---|
flags | PA_CONTEXT_NOFLAGS (0) : オプションなし。 PA_CONTEXT_NOAUTOSPAWN : PulseAudio デーモンの自動生成を無効にする。 PA_CONTEXT_NOFAIL : デーモンが利用できない場合でも失敗にせず、代わりに PA_CONTEXT_CONNECTING 状態に入って、デーモンが現れるのを待つ。 |
api | NULL 指定可。 自動生成されたデーモンを、アプリケーションに統合するために使用する。 |
戻り値 | 負の値でエラー |
server = NULL で、PA_CONTEXT_NOAUTOSPAWN フラグがセットされていない場合、サーバーにアクセスできない時は、新しいデーモンが生成されます。
切断
void pa_context_disconnect(pa_context *c);
サーバーから切断します。
この関数は、すぐに処理されます。
接続結果
pa_context_connect() でサーバーと接続しても、関数が戻った時点で、実際に接続されているとは限りません。
まだ接続を試している段階で、成功または失敗が確定していない可能性があります。
とりあえず、最初に PulseAudio サーバーと接続しない限りは、何の操作も行えないので、接続後は、接続が確立するか失敗するまで、待つ必要があります。
接続の結果を判断するには、pa_context_get_state() で、現在のコンテキストの状態を取得します。
この値によって、接続に成功したか、失敗したかを判断することができます。
まだ結果が確定していない場合は、PulseAudio のメインループを実行し、サーバーからの通知を待ちます。
サーバーからの通知が来たら、再び状態を取得します。
これを、接続に成功するか失敗するまで繰り返します。
コンテキストの現在の状態を取得します。
正常に接続された状態ならば、PA_CONTEXT_READY となり、失敗時は PA_CONTEXT_FAILED になります。
pa_context_disconnect() で切断すると、PA_CONTEXT_TERMINATED になります。
コンテキストの状態が変化した時に実行される、コールバック関数をセットすることができます。
メインループでイベントが処理されると、指定されたコールバック関数が実行されます。
cb に NULL を指定すると、設定を解除できます。
まだ接続を試している段階で、成功または失敗が確定していない可能性があります。
とりあえず、最初に PulseAudio サーバーと接続しない限りは、何の操作も行えないので、接続後は、接続が確立するか失敗するまで、待つ必要があります。
接続の結果を判断するには、pa_context_get_state() で、現在のコンテキストの状態を取得します。
この値によって、接続に成功したか、失敗したかを判断することができます。
まだ結果が確定していない場合は、PulseAudio のメインループを実行し、サーバーからの通知を待ちます。
サーバーからの通知が来たら、再び状態を取得します。
これを、接続に成功するか失敗するまで繰り返します。
現在の状態を取得
pa_context_state_t pa_context_get_state(const pa_context *c);
コンテキストの現在の状態を取得します。
PA_CONTEXT_UNCONNECTED | まだ接続されていない |
---|---|
PA_CONTEXT_CONNECTING | 接続が確立されている |
PA_CONTEXT_AUTHORIZING | クライアントは、デーモンに対して自身を認証している |
PA_CONTEXT_SETTING_NAME | クライアントは、アプリケーション名をデーモンに渡している |
PA_CONTEXT_READY | 接続が確立され、コンテキストで操作を実行する準備が整っている |
PA_CONTEXT_FAILED | 接続に失敗したか、切断された |
PA_CONTEXT_TERMINATED | 接続は正常に終了した |
正常に接続された状態ならば、PA_CONTEXT_READY となり、失敗時は PA_CONTEXT_FAILED になります。
pa_context_disconnect() で切断すると、PA_CONTEXT_TERMINATED になります。
状態変化時のコールバック
void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata); //コールバック関数 typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata);
コンテキストの状態が変化した時に実行される、コールバック関数をセットすることができます。
メインループでイベントが処理されると、指定されたコールバック関数が実行されます。
cb に NULL を指定すると、設定を解除できます。
メインループ (poll)
クライアントが、サーバーからのイベントを処理するためには、PulseAudio のメインループを実行する必要があります。
メインループの種類によって、やり方が異なるので、ここでは、poll のメインループについて説明します。
メインループの種類によって、やり方が異なるので、ここでは、poll のメインループについて説明します。
メインループの処理
以下の3つの関数は、1回分のメインループ処理を行うために必要になります。
1回分とは、サーバーからのイベントを受け取って、受け取ったイベントを処理することです。
まず、pa_mainloop_prepare() で、1回のメインループを準備します。
timeout 引数は、次に実行する pa_mainloop_poll() の、タイムアウト時間 (ms) です。-1 で無限に待ちます。
エラー、または終了要求が来た時は、負の値が返ります。
次に、pa_mainloop_poll() で、PulseAudio サーバーからイベントを受け取るまで待ちます。
エラー時は負の値が返ります。
poll の成功時は、pa_mainloop_dispatch() を使って、受け取ったイベントを、クライアント側で処理します。
各イベントに対応するコールバック関数がセットされていれば、それが実行されます。
成功時は、処理されたソースの数が返り、エラー時は負の値が返ります。
pa_mainloop_iterate() を使うと、上記の3つをまとめて実行できます。
1回分とは、サーバーからのイベントを受け取って、受け取ったイベントを処理することです。
//メインループの準備 int pa_mainloop_prepare(pa_mainloop *m, int timeout); //イベントが起こるまで待つ int pa_mainloop_poll(pa_mainloop *m); //イベントを処理 int pa_mainloop_dispatch(pa_mainloop *m);
まず、pa_mainloop_prepare() で、1回のメインループを準備します。
timeout 引数は、次に実行する pa_mainloop_poll() の、タイムアウト時間 (ms) です。-1 で無限に待ちます。
エラー、または終了要求が来た時は、負の値が返ります。
次に、pa_mainloop_poll() で、PulseAudio サーバーからイベントを受け取るまで待ちます。
エラー時は負の値が返ります。
poll の成功時は、pa_mainloop_dispatch() を使って、受け取ったイベントを、クライアント側で処理します。
各イベントに対応するコールバック関数がセットされていれば、それが実行されます。
成功時は、処理されたソースの数が返り、エラー時は負の値が返ります。
まとめて実行
int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval);
pa_mainloop_iterate() を使うと、上記の3つをまとめて実行できます。
block | イベントが来るまでブロックするか。 0 で、pa_mainloop_prepare() の timeout を 0 に、それ以外で -1 (無限) にします。 |
---|---|
retval | NULL 以外の場合、quit でループの終了が要求された時に、quit で指定された戻り値が格納されます。 |
戻り値 | 成功時は、処理されたソースの数。 エラー、または終了要求が来た時は、負の値。 |
プログラム (1)
poll メインループを使って、サーバーと接続し、メインループ内で状態を取得して、接続の成否を判断するプログラムです。
接続が成功するか失敗するまで、状態を取得します。
接続が確定していない場合は、イベントループを1回行って、何かしらイベントが来るまで待ち、再度状態を取得します。
順番的には、CONNECTING → AUTHORIZING → SETTING_NAME → READY となって、接続が成功します。
コンテキストの状態変化以外にもイベントは来るので、同じ state が何度か続きます。
$ cc -o 02a-connect 02a-connect.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <pulse/pulseaudio.h> pa_mainloop *ml; pa_context *ctx; static void _appfree(void) { pa_context_unref(ctx); pa_mainloop_free(ml); } static void _enderr(void) { int err = pa_context_errno(ctx); printf("[error] (%d) %s\n", err, pa_strerror(err)); _appfree(); exit(1); } int main(void) { pa_context_state_t state; ml = pa_mainloop_new(); ctx = pa_context_new(pa_mainloop_get_api(ml), "test-pulse"); //接続 printf("connect..\n"); if(pa_context_connect(ctx, NULL, 0, NULL) < 0) _enderr(); //待つ while(1) { state = pa_context_get_state(ctx); switch(state) { case PA_CONTEXT_CONNECTING: printf(">CONNECTING\n"); break; case PA_CONTEXT_AUTHORIZING: printf(">AUTHORIZING\n"); break; case PA_CONTEXT_SETTING_NAME: printf(">SETTING_NAME\n"); break; case PA_CONTEXT_READY: printf(">READY\n"); pa_context_disconnect(ctx); //切断 break; case PA_CONTEXT_FAILED: printf(">FAILED\n"); break; default: printf(">state: %d\n", state); break; } if(state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED) break; if(pa_mainloop_iterate(ml, 1, NULL) < 0) _enderr(); } //解放 _appfree(); return 0; }
実行結果
connect.. >CONNECTING >AUTHORIZING >AUTHORIZING >AUTHORIZING >AUTHORIZING >AUTHORIZING >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >SETTING_NAME >READY
接続が成功するか失敗するまで、状態を取得します。
接続が確定していない場合は、イベントループを1回行って、何かしらイベントが来るまで待ち、再度状態を取得します。
順番的には、CONNECTING → AUTHORIZING → SETTING_NAME → READY となって、接続が成功します。
コンテキストの状態変化以外にもイベントは来るので、同じ state が何度か続きます。
デーモンが動作しない場合
以下のコマンドで、PulseAudio のサービスを停止させた場合は、エラーが出ます。$ systemctl --user stop pulseaudio.socket $ systemctl --user stop pulseaudio.service # 実行時 connect.. [error] (6) Connection refused
プログラム (2)
次は、コールバック関数を使った場合のプログラムです。
内容的には、最初のプログラムとそれほど変わりませんが、状態変化のコールバック関数が来た時だけ、状態を出力しているので、他のイベントが来た時は何も表示されません。
そのため、出力結果はシンプルになっています。
なお、最後に TERMINATED が表示されていますが、これは、pa_context_disconnect() の関数内で、直接コールバック関数が呼ばれた結果です。
READY が来た時点でメインループを抜けているので、クライアントのメインループ内で実行されたコールバックではありません。
$ cc -o 02b-callback 02b-callback.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <pulse/pulseaudio.h> pa_mainloop *ml; pa_context *ctx; static void _appfree(void) { pa_context_unref(ctx); pa_mainloop_free(ml); } static void _enderr(void) { int err = pa_context_errno(ctx); printf("[error] (%d) %s\n", err, pa_strerror(err)); _appfree(); exit(1); } //コールバック static void _callback(pa_context *c,void *userdata) { pa_context_state_t state; state = pa_context_get_state(c); switch(state) { case PA_CONTEXT_CONNECTING: printf(">CONNECTING\n"); break; case PA_CONTEXT_AUTHORIZING: printf(">AUTHORIZING\n"); break; case PA_CONTEXT_SETTING_NAME: printf(">SETTING_NAME\n"); break; case PA_CONTEXT_READY: printf(">READY\n"); break; case PA_CONTEXT_FAILED: printf(">FAILED\n"); break; case PA_CONTEXT_TERMINATED: printf(">TERMINATED\n"); break; default: printf(">state: %d\n", state); break; } } int main(void) { pa_context_state_t state; ml = pa_mainloop_new(); ctx = pa_context_new(pa_mainloop_get_api(ml), "test-pulse"); //接続 printf("connect..\n"); pa_context_set_state_callback(ctx, _callback, NULL); if(pa_context_connect(ctx, NULL, 0, NULL) < 0) _enderr(); //待つ while(1) { state = pa_context_get_state(ctx); if(state == PA_CONTEXT_READY || state == PA_CONTEXT_FAILED) break; if(pa_mainloop_iterate(ml, 1, NULL) < 0) _enderr(); } if(state == PA_CONTEXT_READY) pa_context_disconnect(ctx); //解放 _appfree(); return 0; }
実行結果
connect.. >CONNECTING >AUTHORIZING >SETTING_NAME >READY >TERMINATED
内容的には、最初のプログラムとそれほど変わりませんが、状態変化のコールバック関数が来た時だけ、状態を出力しているので、他のイベントが来た時は何も表示されません。
そのため、出力結果はシンプルになっています。
なお、最後に TERMINATED が表示されていますが、これは、pa_context_disconnect() の関数内で、直接コールバック関数が呼ばれた結果です。
READY が来た時点でメインループを抜けているので、クライアントのメインループ内で実行されたコールバックではありません。