バッファ
ストリームの再生/録音バッファに関する情報は、pa_stream_get_buffer_attr() で取得できます。
バッファの情報は、ストリームの接続時にクライアントが指定することができ、ストリームの作成後に変更することもできます。
ストリームの現在のバッファ情報を取得します。
ストリームの接続後に、バッファの情報を変更します。
ここで指定された値が、実際にそのまま適用されるとは限らず、サーバーが異なる値を選択する場合もあります。
cb で指定されたコールバックが来た後に、pa_stream_get_buffer_attr() で、実際に設定された値を確認してください。
※バッファ情報を設定する際、各値に (uint32_t)-1 を指定しておくと、自動的に適切な値が選択されます。
なお、ストリームの接続時に、attr 引数に NULL を指定した場合と、構造体の各値に (uint32_t)-1 を指定した場合では、実際に設定された値は異なります。
前者はデフォルトの値で、後者は可能な限り最高の値です。
バッファの情報は、ストリームの接続時にクライアントが指定することができ、ストリームの作成後に変更することもできます。
バッファの情報を取得
const pa_buffer_attr *pa_stream_get_buffer_attr(pa_stream *s);
ストリームの現在のバッファ情報を取得します。
バッファの情報を変更する
pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata); //コールバック関数 typedef void (*pa_stream_success_cb_t)(pa_stream *s, int success, void *userdata);
ストリームの接続後に、バッファの情報を変更します。
ここで指定された値が、実際にそのまま適用されるとは限らず、サーバーが異なる値を選択する場合もあります。
cb で指定されたコールバックが来た後に、pa_stream_get_buffer_attr() で、実際に設定された値を確認してください。
pa_buffer_attr 構造体
typedef struct pa_buffer_attr { uint32_t maxlength; uint32_t tlength; uint32_t prebuf; uint32_t minreq; uint32_t fragsize; } pa_buffer_attr;
maxlength | バッファ全体の最大バイト数。 |
---|---|
tlength | (再生のみ)再生用のターゲットバイト数。 サーバは、未再生のデータとして、再生バッファ上に少なくとも tlength バイトが存在するよう、クライアントにデータを要求する。 実質的に、このサイズが、バッファの長さであるような扱いになります。 バッファの最大サイズ以内であれば、このサイズを超えるデータを要求する場合もあります。 |
prebuf | (再生のみ)プリバッファのバイト数。 再生バッファ上に、少なくとも prebuf バイトのデータが書き込まれるまで、サーバーは再生を開始しない。 0 の場合は、手動で再生の開始/停止を行います。 |
minreq | (再生のみ)クライアントに、minreq バイト未満のデータ要求は行わない。 つまり、このサイズ以上が書き込みできるようになるまで、書き込み可能であるとはみなさない。 |
fragsize | (録音のみ)サーバーは、fragsize バイトのブロックで、データを送信する。 |
※バッファ情報を設定する際、各値に (uint32_t)-1 を指定しておくと、自動的に適切な値が選択されます。
なお、ストリームの接続時に、attr 引数に NULL を指定した場合と、構造体の各値に (uint32_t)-1 を指定した場合では、実際に設定された値は異なります。
前者はデフォルトの値で、後者は可能な限り最高の値です。
前回のプログラムでのバッファ情報
前回のプログラムで、バッファ情報を取得した場合は、以下のような値になります。
前回のサンプルスペックは、16bit, 48000 Hz, 1ch だったので、4194304/(48000*2) = 43.69 秒分の長さになります。
実際に、このサイズ分のバッファが確保されているというわけではなく、最大でこのサイズまでは、データを保持できるということです。
これは、前回最初に pa_stream_writable_size() を実行した時の、書き込み可能なサイズと一致します。
その後も、書き込み済みで未再生のデータも含めて、24000 byte の再生データがバッファに存在するよう、サイズが要求されていました。
サイズの取得やコールバック関数で、データの要求を PulseAudio に任せる場合は、基本的にこのサイズが基準となります。
ただし、この値は”少なくとも”という前置きが付いているので、書き込み可能なサイズとして、tlength 以上の値が返ってくる場合もあります。
pa_stream_writable_size() などを使わずに、自分で適切なサイズを選択して書き込むこともできます。
実際は、バッファの最大サイズ以内であれば、このサイズよりも大きいデータを、バッファに書き込むことができます。
接続直後やバッファアンダーフロー後など、再生が停止している状態で、バッファにこのサイズ以上のデータが書き込まれた時、自動的に再生を開始します。
逆に言うと、このサイズ未満しか書き込まれていない状態では、再生が行われない、ということになります。
少ないデータでも、書き込んだらすぐに再生したいという場合は、pa_stream_trigger() を使います。
サーバーは、常にこのサイズ以上のデータを要求し、このサイズ未満しか書き込めない状態であれば、データは要求しません。
これも、データの要求を PulseAudio に任せる場合に有効です。
再生バッファ上で、このサイズ以上が書き込みできるようになるまでは、pa_stream_writable_size() で 0 を返し、また、書き込み可能になったというコールバック関数を実行しません。
小さいサイズで書き込みを繰り返すと、効率が悪いので、ある程度バッファが空いてから、データを要求するということです。
maxlength: 4194304 tlength: 24000 prebuf: 22082 minreq: 1920 fragsize: 4294967295(-1)
maxlength
maxlength (バッファの最大サイズ) は、4194304 byte です。前回のサンプルスペックは、16bit, 48000 Hz, 1ch だったので、4194304/(48000*2) = 43.69 秒分の長さになります。
実際に、このサイズ分のバッファが確保されているというわけではなく、最大でこのサイズまでは、データを保持できるということです。
tlength
tlength (ターゲットのサイズ) は、24000 byte です。これは、前回最初に pa_stream_writable_size() を実行した時の、書き込み可能なサイズと一致します。
その後も、書き込み済みで未再生のデータも含めて、24000 byte の再生データがバッファに存在するよう、サイズが要求されていました。
サイズの取得やコールバック関数で、データの要求を PulseAudio に任せる場合は、基本的にこのサイズが基準となります。
ただし、この値は”少なくとも”という前置きが付いているので、書き込み可能なサイズとして、tlength 以上の値が返ってくる場合もあります。
pa_stream_writable_size() などを使わずに、自分で適切なサイズを選択して書き込むこともできます。
実際は、バッファの最大サイズ以内であれば、このサイズよりも大きいデータを、バッファに書き込むことができます。
prebuf
prebuf (プリバッファのサイズ) は、22082 byte です。接続直後やバッファアンダーフロー後など、再生が停止している状態で、バッファにこのサイズ以上のデータが書き込まれた時、自動的に再生を開始します。
逆に言うと、このサイズ未満しか書き込まれていない状態では、再生が行われない、ということになります。
少ないデータでも、書き込んだらすぐに再生したいという場合は、pa_stream_trigger() を使います。
minreq
minreq は 1920 byte です。サーバーは、常にこのサイズ以上のデータを要求し、このサイズ未満しか書き込めない状態であれば、データは要求しません。
これも、データの要求を PulseAudio に任せる場合に有効です。
再生バッファ上で、このサイズ以上が書き込みできるようになるまでは、pa_stream_writable_size() で 0 を返し、また、書き込み可能になったというコールバック関数を実行しません。
小さいサイズで書き込みを繰り返すと、効率が悪いので、ある程度バッファが空いてから、データを要求するということです。
アンダーフローとオーバーフロー
再生用バッファには、バッファアンダーフローと、バッファオーバーフローの状態があります。
データの要求を PulseAudio に任せている場合は、オーバーフローにならないよう、書き込み可能なサイズを受け取った上で書き込むので、基本的にオーバーフローは起こりません。
しかし、書き込み可能なサイズに構わず、どんどんデータを書き込んでいくと、最終的にオーバーフローになります。
アンダーフローは、再生中、書き込みが追いつかなくなった場合に、常に起こる可能性があります。
アンダーフロー、オーバーフローの状態になった時は、コールバック関数を呼ぶことができます。
pa_stream_set_underflow_callback() は、アンダーフローになった時に実行されるコールバック関数をセットします。
pa_stream_set_overflow_callback() は、オーバーフローになった時に実行されるコールバック関数をセットします。
- バッファアンダーフロー
再生中に、バッファが空になった。
書き込みよりも、再生の方が追いついてしまった状態。 - バッファオーバーフロー
バッファの最大サイズを超えて、データが書き込まれた。
データの要求を PulseAudio に任せている場合は、オーバーフローにならないよう、書き込み可能なサイズを受け取った上で書き込むので、基本的にオーバーフローは起こりません。
しかし、書き込み可能なサイズに構わず、どんどんデータを書き込んでいくと、最終的にオーバーフローになります。
アンダーフローは、再生中、書き込みが追いつかなくなった場合に、常に起こる可能性があります。
アンダーフロー、オーバーフローの状態になった時は、コールバック関数を呼ぶことができます。
コールバック
void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); void pa_stream_set_overflow_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_overflow_callback() は、オーバーフローになった時に実行されるコールバック関数をセットします。
プログラム
オーバーフローのテストを行うプログラムです。
バッファの最大サイズを 0.5 秒分に変更した後、'ドレミファ' で1秒(各1/4秒)のデータを書き込みます。
書き込み可能な状態になるまで待たずに、1秒分のデータを一気に書き込んでいきます。
バッファの最大サイズを 0.5 秒分に変更した後、'ドレミファ' で1秒(各1/4秒)のデータを書き込みます。
書き込み可能な状態になるまで待たずに、1秒分のデータを一気に書き込んでいきます。
$ cc -o 13-overflow 13-overflow.c util.c -lpulse
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pulse/pulseaudio.h> #include "util.h" #define SAMPRATE 48000 #define BUFMAXLEN SAMPRATE PulseData *pulse; //オーバーフロー static void _cb_overflow(pa_stream *p,void *userdata) { printf("* overflow\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); while(sendsize) { size = pa_stream_writable_size(strm); printf("writable: %d\n", (int)size); size = sendsize; if(size > BUFMAXLEN) size = BUFMAXLEN; if(pa_stream_begin_write(strm, &writebuf, &size)) break; memcpy(writebuf, buf, size); printf("* write: %d\n", (int)size); pa_stream_write(strm, writebuf, size, NULL, 0, PA_SEEK_RELATIVE); //100 ms 待つ (イベント処理のため) pa_threaded_mainloop_unlock(pulse->mainloop); pa_msleep(100); pa_threaded_mainloop_lock(pulse->mainloop); buf += size; sendsize -= size; } pa_threaded_mainloop_unlock(pulse->mainloop); } //バッファ情報のセット static pa_operation *_set_buffer_attr(PulseData *p,void *data) { struct pa_buffer_attr attr; attr.maxlength = BUFMAXLEN; //0.5秒 attr.tlength = BUFMAXLEN / 4; //1/8秒 attr.prebuf = (uint32_t)-1; attr.minreq = (uint32_t)-1; attr.fragsize = (uint32_t)-1; return pa_stream_set_buffer_attr(pulse->stream, &attr, pulse_stream_success_callback_signal, p); } //バッファ情報表示 static void _put_buffer_attr(void) { const pa_buffer_attr *attr; attr = pa_stream_get_buffer_attr(pulse->stream); printf("maxlength: %u\n", attr->maxlength); printf("tlength: %u\n", attr->tlength); printf("prebuf: %u\n", attr->prebuf); printf("minreq: %u\n----\n", attr->minreq); } int main(void) { uint8_t *buf,*dst; int bufsize,i; double freq[4] = {261.626, 293.665, 329.628, 349.228}; pulse = pulse_connect(0); if(!pulse) return 1; if(pulse_create_stream_play(pulse, PA_SAMPLE_S16LE, SAMPRATE, 1)) pulse_enderr(pulse); pulse_wait_operation(pulse, _set_buffer_attr, NULL); _put_buffer_attr(); //コールバック pa_threaded_mainloop_lock(pulse->mainloop); pa_stream_set_overflow_callback(pulse->stream, _cb_overflow, NULL); pa_threaded_mainloop_unlock(pulse->mainloop); //書き込み bufsize = 2 * SAMPRATE; buf = dst = (uint8_t *)malloc(bufsize); for(i = 0; i < 4; i++) { pulse_write_buf_sawtooth(dst, bufsize / 4, SAMPRATE, freq[i]); dst += bufsize / 4; } _write_data(buf, bufsize); free(buf); // pulse_wait_drain(pulse); pulse_free(pulse); return 0; }
実行結果
# stream connected maxlength: 48000 tlength: 12000 prebuf: 10082 minreq: 1920 ---- writable: 24000 * write: 48000 writable: 0 * write: 48000 * overflow
'ドレミファ' の計1秒分のデータを書き込んでいますが、実際に再生されるのは、'ドレ' の2音 (0.5秒) だけです。
つまり、最大バッファサイズを超えた、'ミファ' の音は、実際には再生バッファに書き込まれていません。
解説
デフォルトのバッファ状態の場合、バッファの最大サイズが大きいので、ストリームの接続後に、バッファの最大サイズを 0.5 秒分に設定しています。
tlength は 1/8 秒分です。
オーバーフローのコールバックを設定した後、1秒分のデータを用意して、一気に書き込みます。
書き込む前に pa_stream_writable_size() を実行してみると、tlength (12000) の値ではなく、24000 byte になっています。
このように、書き込み可能な値として、tlength 以上の値が返る場合があります。
ここでは、1/4 秒分が書き込み可能であると通知されています。
バッファの最大サイズを超えるデータを書き込もうとすると、そのデータ全体が書き込まれなくなってしまうからです。
試しに、最初に 96000 byte (1秒分) のサイズを渡した場合は、65472 byte の値が返ってきます。
この時点で、すでに再生バッファの最大サイズ (48000) を超えています。
つまり、再生バッファの最大サイズと、書き込み用のバッファのサイズは異なるということです。
この状態で書き込むと、再生しても全く音が鳴らないので、バッファの最大サイズを超えるようなデータを書き込むと、データの書き込みが全く行われない、という結論になります。
そのため、書き込むサイズは、バッファの最大サイズに制限して、最初は 48000 byte (0.5秒分) を書き込みます。
この時点で、再生バッファはフルの状態です。
そのため、書き込み直後に pa_msleep() を使って、100 ミリ秒 (0.1秒) の時間を待ちます。
この間にイベントが処理され、オーバーフローのコールバックが実行されます。
その後、pa_stream_writable_size() を行うと、書き込み可能なサイズは 0 になっています。
最後の 0.5 秒 (ミ、ファの音) が鳴っていないことを考えると、このデータ全体が書き込まれていないと考えることができます。
つまり、バッファサイズを超える部分のデータは、全く書き込まれません。
tlength は 1/8 秒分です。
オーバーフローのコールバックを設定した後、1秒分のデータを用意して、一気に書き込みます。
書き込む前に pa_stream_writable_size() を実行してみると、tlength (12000) の値ではなく、24000 byte になっています。
このように、書き込み可能な値として、tlength 以上の値が返る場合があります。
ここでは、1/4 秒分が書き込み可能であると通知されています。
pa_stream_begin_write
pa_stream_begin_write() にサイズを渡す時、一度に書き込むサイズは、バッファの最大サイズに調整しています。バッファの最大サイズを超えるデータを書き込もうとすると、そのデータ全体が書き込まれなくなってしまうからです。
試しに、最初に 96000 byte (1秒分) のサイズを渡した場合は、65472 byte の値が返ってきます。
この時点で、すでに再生バッファの最大サイズ (48000) を超えています。
つまり、再生バッファの最大サイズと、書き込み用のバッファのサイズは異なるということです。
この状態で書き込むと、再生しても全く音が鳴らないので、バッファの最大サイズを超えるようなデータを書き込むと、データの書き込みが全く行われない、という結論になります。
そのため、書き込むサイズは、バッファの最大サイズに制限して、最初は 48000 byte (0.5秒分) を書き込みます。
この時点で、再生バッファはフルの状態です。
コールバックを実行させる
なお、書き込み処理中は、メインループのスレッドがロックされているので、このままでは、オーバーフローのコールバックがすぐに実行されません。そのため、書き込み直後に pa_msleep() を使って、100 ミリ秒 (0.1秒) の時間を待ちます。
この間にイベントが処理され、オーバーフローのコールバックが実行されます。
その後、pa_stream_writable_size() を行うと、書き込み可能なサイズは 0 になっています。
オーバーフロー
最後に、残りの 48000 byte を書き込むことになりますが、この時点で、再生は進んでいないので、バッファの最大サイズと同じサイズを追加で書き込もうとすれば、当然、オーバーフローが発生します。最後の 0.5 秒 (ミ、ファの音) が鳴っていないことを考えると、このデータ全体が書き込まれていないと考えることができます。
つまり、バッファサイズを超える部分のデータは、全く書き込まれません。