バッファアンダーフロー
バッファアンダーフローは、再生中、再生バッファに次のデータがなくなった時に発生します。
再生の方が早く、書き込みが追いつかなくなった時に起こります。
再生するデータがないので、アンダーフローになると、再生は自動で停止します (ただし、prebuf が 0 の場合は、自動で停止しない)。
アンダーフロー後に、新しくデータが書き込まれた場合は、自動的に再生が再開されるので、通常の再生であれば、アンダーフローになったからといって、特別何かをする必要はありません。
ただ、再生が途中で途切れると問題がある場合は、何かしら処理を行う必要があるでしょう。
pa_stream_set_underflow_callback() は、アンダーフローになった時に実行される、コールバック関数をセットします。
pa_stream_set_started_callback() は、再生が開始する時に実行されるコールバック関数をセットします。
サーバーとの接続後に、初めてデータを書き込んだ時や、アンダーフローで再生が停止した後に、新しいデータが書き込まれて、再生が再開された時に実行されます。
最後にアンダーフローが発生した時の、再生バッファの位置(バイト数)を返します。
アンダーフローが発生していない場合などは、-1 が返ります。
PulseAudio の再生/録音バッファは、先頭と終端が繋がっているようなリングバッファではないので、再生開始位置を 0 として、絶対的なバッファ位置が返ります。
そのため、64bit の整数になっています。
実際、64bit 整数でどれだけの時間を表現できるかというと、16bit 48000 Hz 2 ch の場合、
INT64_MAX / (48000*4) = 48038396025285 秒 = 13343998895.913 時間 = 555999953.996 日となります。
これだけの時間を再生し続けることは、ほぼ不可能なので、64bit の整数であれば、バッファ位置を絶対値で表現できるということになります。
再生の方が早く、書き込みが追いつかなくなった時に起こります。
再生するデータがないので、アンダーフローになると、再生は自動で停止します (ただし、prebuf が 0 の場合は、自動で停止しない)。
アンダーフロー後に、新しくデータが書き込まれた場合は、自動的に再生が再開されるので、通常の再生であれば、アンダーフローになったからといって、特別何かをする必要はありません。
ただ、再生が途中で途切れると問題がある場合は、何かしら処理を行う必要があるでしょう。
コールバック
void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); void pa_stream_set_started_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_underflow_callback() は、アンダーフローになった時に実行される、コールバック関数をセットします。
pa_stream_set_started_callback() は、再生が開始する時に実行されるコールバック関数をセットします。
サーバーとの接続後に、初めてデータを書き込んだ時や、アンダーフローで再生が停止した後に、新しいデータが書き込まれて、再生が再開された時に実行されます。
アンダーフロー時の位置を取得
int64_t pa_stream_get_underflow_index(const pa_stream *p);
最後にアンダーフローが発生した時の、再生バッファの位置(バイト数)を返します。
アンダーフローが発生していない場合などは、-1 が返ります。
PulseAudio の再生/録音バッファは、先頭と終端が繋がっているようなリングバッファではないので、再生開始位置を 0 として、絶対的なバッファ位置が返ります。
そのため、64bit の整数になっています。
実際、64bit 整数でどれだけの時間を表現できるかというと、16bit 48000 Hz 2 ch の場合、
INT64_MAX / (48000*4) = 48038396025285 秒 = 13343998895.913 時間 = 555999953.996 日となります。
これだけの時間を再生し続けることは、ほぼ不可能なので、64bit の整数であれば、バッファ位置を絶対値で表現できるということになります。
プログラム
アンダーフローのテストを行うプログラムです。
最初に、書き込み可能なサイズ分のバッファを用意して、サーバーに書き込みます。
その後、その時間分スリープして、意図的にアンダーフローを発生させます。
そして、再度同じデータを書き込み、再生が再開して、その再生が終わるまで待ちます。
最初に、書き込み可能なサイズ分のバッファを用意して、サーバーに書き込みます。
その後、その時間分スリープして、意図的にアンダーフローを発生させます。
そして、再度同じデータを書き込み、再生が再開して、その再生が終わるまで待ちます。
$ cc -o 14-underflow 14-underflow.c util.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <string.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\n", pa_stream_get_underflow_index(p)); } //再生開始 static void _cb_started(pa_stream *p,void *userdata) { printf("* started\n"); } //バッファのデータを書き込み static void _write_data(void *buf,int sendsize) { pa_stream *strm = pulse->stream; size_t size; void *writebuf; pa_threaded_mainloop_lock(pulse->mainloop); size = sendsize; if(pa_stream_begin_write(strm, &writebuf, &size)) goto END; memcpy(writebuf, buf, size); printf("* write: %d\n", (int)size); pa_stream_write(strm, writebuf, size, NULL, 0, PA_SEEK_RELATIVE); END: pa_threaded_mainloop_unlock(pulse->mainloop); } int main(void) { uint8_t *buf; size_t bufsize; pulse = pulse_connect(0); if(!pulse) return 1; if(pulse_create_stream_play(pulse, PA_SAMPLE_S16LE, SAMPRATE, 1)) pulse_enderr(pulse); //コールバックなど pa_threaded_mainloop_lock(pulse->mainloop); pa_stream_set_underflow_callback(pulse->stream, _cb_underflow, NULL); pa_stream_set_started_callback(pulse->stream, _cb_started, NULL); bufsize = pa_stream_writable_size(pulse->stream); printf("writable: %d\n", (int)bufsize); pa_threaded_mainloop_unlock(pulse->mainloop); //再生 buf = (uint8_t *)malloc(bufsize); pulse_write_buf_sawtooth(buf, bufsize, SAMPRATE, 261.626); _write_data(buf, bufsize); pa_msleep(bufsize * 1000 / (SAMPRATE * 2) + 100); _write_data(buf, bufsize); free(buf); // printf("# wait\n"); pulse_wait_drain(pulse); pulse_free(pulse); return 0; }
解説
# stream connected writable: 24000 * write: 24000 * started * underflow: 24000 * write: 24000 # wait * started * underflow: 48000
コールバックをセットした後、pa_stream_writable_size() で、最初に書き込み可能なサイズを取得します。
ここでは、24000 byte (1/4秒) です。
そのサイズ分の波形データを用意した後、一度にそれを書き込みます。
書き込み可能なサイズであることはわかっているので、一度の pa_stream_write() で、すべて書き込めます。
そして、そのサイズの時間 (250 ms) + 100 ms の時間を、スリープで待ちます。
スリープしている間に再生が進むので、意図的にアンダーフローを起こすことができます。
まず、データの書き込み後、再生バッファに初めてデータを書き込んだことにより、再生が開始したというコールバックが来ています。
バッファ情報はデフォルトの状態なので、prebuf は 22082 byte ですから、24000 byte を書き込んだ時点で、自動的に再生を開始する条件をクリアしています。
その後、再生が進み、再生データがなくなると、アンダーフローのコールバックが来ます。
pa_stream_get_underflow_index() で、再生が停止した時のバッファ位置を確認すると、24000 です。
書き込んだデータは 24000 byte なので、終端まで来たということがわかります。
スリープから抜けた後、再び同じデータを書き込み、pa_stream_drain() で、再生が終わるまで待ちます。
アンダーフローで再生が停止した後、再びデータが書き込まれたので、started のコールバックが再び来ます。
drain で再生が終わるまで待つ場合も、アンダーフローは発生しています。
再生が停止した時のバッファ位置は、48000 です。
全体で 24000 + 24000 のデータを書き込んでいるので、これが絶対位置であることがわかります。