プリバッファ
前回までに、pa_buffer_attr の構造体で説明した通り、prebuf に 0 以外の値が設定されていると、再生が停止している状態で、再生バッファに prebuf バイト以上のデータが書き込まれた時、自動的に再生が開始されます。
prebuf の値は、デフォルトでは、tlength より少しだけ小さい値に設定されます。
tlength = 24000 なら、prebuf = 22082 というような感じです。
その場合、ストリームの接続時に、flags 引数に PA_STREAM_START_CORKED を設定して、ストリームを停止状態にしておく必要があります。
でないと、何もデータがないのに、再生が進んでしまうという状態になります。
プリバッファが無効な場合は、どれだけデータを書き込んでも、自動で再生が開始されないので、手動で pa_stream_cork() を実行して、再生の開始や停止を行う必要があります。
また、プリバッファが無効な場合、アンダーフローが発生した時は、自動で再生が停止しないので、注意してください。
ストリームの再生を停止したり、再開させたりします。
b が 0 で再生の開始、0 以外で再生の停止になります。
プリバッファが無効な状態で、prebuf の値を変更した後、プリバッファを有効にする時に実行します。
プリバッファに関係なく、すぐに再生を開始させます。
プリバッファが有効な時、prebuf 未満のデータが書き込まれている状態で、再生を開始したい時に使います。
prebuf の値は、デフォルトでは、tlength より少しだけ小さい値に設定されます。
tlength = 24000 なら、prebuf = 22082 というような感じです。
プリバッファを無効にする
デフォルトではプリバッファが有効なので、通常はその状態で使えばいいのですが、データを書き込んだ後、再生を開始するタイミングを自分で決めたいという場合などは、prebuf の値を 0 にして、バッファ情報を設定します。その場合、ストリームの接続時に、flags 引数に PA_STREAM_START_CORKED を設定して、ストリームを停止状態にしておく必要があります。
でないと、何もデータがないのに、再生が進んでしまうという状態になります。
プリバッファが無効な場合は、どれだけデータを書き込んでも、自動で再生が開始されないので、手動で pa_stream_cork() を実行して、再生の開始や停止を行う必要があります。
また、プリバッファが無効な場合、アンダーフローが発生した時は、自動で再生が停止しないので、注意してください。
再生の停止/再開
pa_operation *pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata); //コールバック関数 typedef void (*pa_stream_success_cb_t)(pa_stream *s, int success, void *userdata);
ストリームの再生を停止したり、再開させたりします。
b が 0 で再生の開始、0 以外で再生の停止になります。
プリバッファを有効にする
pa_operation *pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
プリバッファが無効な状態で、prebuf の値を変更した後、プリバッファを有効にする時に実行します。
すぐに再生を開始する
pa_operation *pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata);
プリバッファに関係なく、すぐに再生を開始させます。
プリバッファが有効な時、prebuf 未満のデータが書き込まれている状態で、再生を開始したい時に使います。
プログラム
プリバッファを無効にして、手動で再生を開始するプログラムです。
バッファの最大サイズを2秒分にし、2秒分のデータを書き込んだ後、手動で再生を開始させます。
その後、スリープで待ち、意図的にアンダーフローを発生させます。
バッファの最大サイズを2秒分にし、2秒分のデータを書き込んだ後、手動で再生を開始させます。
その後、スリープで待ち、意図的にアンダーフローを発生させます。
$ cc -o 15-prebuf 15-prebuf.c util.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <pulse/pulseaudio.h> #include "util.h" #define SAMPRATE 48000 PulseData *pulse; //アンダーフロー static void _cb_underflow(pa_stream *p,void *userdata) { printf("* underflow: %ld, is_corked(%d)\n", pa_stream_get_underflow_index(p), pa_stream_is_corked(pulse->stream)); } static pa_operation *_set_uncork(PulseData *p,void *data) { //再生開始 return pa_stream_cork(p->stream, 0, pulse_stream_success_callback_signal, p); } int main(void) { uint8_t *buf; size_t bufsize; pa_buffer_attr attr; pulse = pulse_connect(0); if(!pulse) return 1; //flags とバッファ情報指定 attr.maxlength = SAMPRATE * 2 * 2; //2秒 attr.tlength = SAMPRATE / 2; attr.prebuf = 0; attr.minreq = (uint32_t)-1; attr.fragsize = (uint32_t)-1; if(pulse_create_stream_play_ex(pulse, PA_SAMPLE_S16LE, SAMPRATE, 1, PA_STREAM_START_CORKED, &attr)) pulse_enderr(pulse); //コールバック pa_threaded_mainloop_lock(pulse->mainloop); pa_stream_set_underflow_callback(pulse->stream, _cb_underflow, NULL); pa_threaded_mainloop_unlock(pulse->mainloop); //------ bufsize = SAMPRATE * 2; buf = (uint8_t *)malloc(bufsize); pulse_write_buf_sawtooth(buf, bufsize, SAMPRATE, 261.626); pulse_write_force(pulse, buf, bufsize); pulse_write_buf_sawtooth(buf, bufsize, SAMPRATE, 293.665); pulse_write_force(pulse, buf, bufsize); //再生開始 pulse_wait_operation(pulse, _set_uncork, NULL); pa_msleep(3000); //3秒待つ free(buf); //----- pulse_free(pulse); return 0; }
解説
# stream connected * write: 65472 * write: 30528 * write: 65472 * write: 30528 # wait 3s * underflow: 192000, is_corked(0)
まず、ストリームの接続時に、flags とバッファ属性を指定しています。
プリバッファを無効にし、ストリームは再生停止状態にします。
'ドレ'の音をそれぞれ1秒、合計2秒分のデータを、強制的に(書き込み可能なサイズをチェックせずに)書き込んだ後、pa_stream_cork() で再生を開始させます。
スリープで3秒待ち、再生が終端にたどり着くと、アンダーフローが発生します。
その時、pa_stream_is_corked() で、現在再生が停止中かどうかをチェックしてみると、値は 0 なので、再生中となっています。
プリバッファが無効な場合、アンダーフローが発生しても、再生は続けられている状態となります。
(実際は再生データがないので、最後の1秒は無音状態になります)
シンクの一時停止
ちなみに、サーバー操作で、特定のシンクの再生を一時停止することができます。
シンクの再生を一時停止するということは、そのシンクに接続されている、すべてのシンク入力(ストリーム)の再生が停止するということです。
基本的に、サーバー上で作成されるシンクは一つなので、それを一時停止させるということは、すべてのクライアントのオーディオ出力が停止されることになります。
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);
シンクの再生を一時停止するということは、そのシンクに接続されている、すべてのシンク入力(ストリーム)の再生が停止するということです。
基本的に、サーバー上で作成されるシンクは一つなので、それを一時停止させるということは、すべてのクライアントのオーディオ出力が停止されることになります。
ストリーム側の操作
ストリームが接続されているシンク/ソースが、現在一時停止状態であるかは、pa_stream_is_suspended() で確認できます。
シンクが一時停止状態になっていると、再生が行われないので、いつまで経っても新しいデータが書き込めない状態となります。
外部のクライアントによって、再生の一時停止が行えてしまうので、長期間停止状態になっていると問題がある場合は、この状態もチェックしておく必要があります。
pa_stream_set_suspended_callback() で、シンク/ソースが一時停止または再開した時に実行されるコールバック関数をセットできます。
int pa_stream_is_suspended(const pa_stream *s); //コールバックセット void pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata);
シンクが一時停止状態になっていると、再生が行われないので、いつまで経っても新しいデータが書き込めない状態となります。
外部のクライアントによって、再生の一時停止が行えてしまうので、長期間停止状態になっていると問題がある場合は、この状態もチェックしておく必要があります。
pa_stream_set_suspended_callback() で、シンク/ソースが一時停止または再開した時に実行されるコールバック関数をセットできます。