タイミング情報
バッファ上の現在の書き込み/読み込み位置や、遅延情報など、ストリームに関する、現在のタイミング情報を取得することができます。
ただし、タイミング情報は、サーバー上の現在の処理に関わる値なので、動作が非同期である以上、クライアントは、サーバーから情報を要求しなければなりません。
サーバーから取得済みの、タイミング情報を返します。
タイミング情報が受信されていない場合は、NULL が返ります。
ただし、タイミング情報は、サーバー上の現在の処理に関わる値なので、動作が非同期である以上、クライアントは、サーバーから情報を要求しなければなりません。
タイミング情報の要求
タイミング情報を取得したい時は、その前に、pa_stream_update_timing_info() を使って、サーバーにタイミング情報の要求を行う必要があります。
これを行うと、サーバーからタイミング情報が送られてくるので、操作が完了すると、クライアントは、そのデータを最新のタイミング情報として保持します。
pa_operation *pa_stream_update_timing_info(pa_stream *p, pa_stream_success_cb_t cb, void *userdata); //コールバック関数 typedef void (*pa_stream_success_cb_t)(pa_stream *s, int success, void *userdata);
これを行うと、サーバーからタイミング情報が送られてくるので、操作が完了すると、クライアントは、そのデータを最新のタイミング情報として保持します。
自動更新
ストリームの接続時に、flags 引数で PA_STREAM_AUTO_TIMING_UPDATE を ON にした場合、タイミング情報の要求が、定期的に自動で行われます。
一定時間の間隔で、または、書き込みなど特定の関数が実行された時に、更新の要求が行われます。
この場合、取得済みの情報が、最新の情報であるとは限らないので、注意してください。
何かを行う前に、現在の最新情報を取得したい場合は、手動で pa_stream_update_timing_info() を実行する必要があります。
一定時間の間隔で、または、書き込みなど特定の関数が実行された時に、更新の要求が行われます。
この場合、取得済みの情報が、最新の情報であるとは限らないので、注意してください。
何かを行う前に、現在の最新情報を取得したい場合は、手動で pa_stream_update_timing_info() を実行する必要があります。
タイミング情報の取得
const pa_timing_info *pa_stream_get_timing_info(pa_stream *s);
サーバーから取得済みの、タイミング情報を返します。
タイミング情報が受信されていない場合は、NULL が返ります。
タイミング情報構造体
typedef struct pa_timing_info { struct timeval timestamp; int synchronized_clocks; pa_usec_t sink_usec; pa_usec_t source_usec; pa_usec_t transport_usec; int playing; int write_index_corrupt; int64_t write_index; int read_index_corrupt; int64_t read_index; pa_usec_t configured_sink_usec; pa_usec_t configured_source_usec; int64_t since_underrun; } pa_timing_info;
timestamp | この情報のシステムクロック時間。 struct timeval は、<sys/time.h> で定義されています。 |
---|---|
synchronized_clocks | ローカルマシンとリモートマシンのクロックが同期している場合は、0 以外。 |
sink_usec | シンクでサンプルを再生するのにかかる時間 (マイクロ秒)。 |
source_usec | サンプルが録音されてから、クライアントに配信されるまでにかかる時間 (マイクロ秒)。 (録音時のみ) |
transport_usec | サンプルがデーモンとの間で転送されるのにかかる推定時間 (マイクロ秒)。 |
playing | ストリームが現在アンダーランしておらず、データがデバイス (シンク/ソース) に渡されている場合は、0 以外。 (再生時のみ) |
write_index_corrupt | 情報の受信後に書き込みが行われ、write_index が破損したことにより、write_index が最新でない場合は、0 以外。 SEEK_RELATIVE_ON_READ および SEEK_RELATIVE_END での書き込み時のみ、write_index を破損する可能性がある。 |
write_index | 再生バッファへの現在の書き込み位置 (バイト数) |
read_index_corrupt | 情報の受信後に、一時停止またはフラッシュが行われたことにより、read_index が破損したため、read_index が最新でない場合は、0 以外。 |
read_index | 再生バッファへの現在の読み込み位置 (バイト数) |
configured_sink_usec | シンクに設定されたレイテンシ (マイクロ秒) |
configured_source_usec | ソースに設定されたレイテンシ (マイクロ秒) |
since_underrun | 最後のアンダーランが発生してから、または、最後のアンダーラン後に再生が再開されてから、シンクに渡されたバイト数 |
再生の場合、write_index は、次に書き込みを行う時の、再生バッファへの書き込み位置です。
read_index は、サーバーがデータを再生する際、次に読み込みを行う位置です。
時間の取得
受け取ったタイミング情報を元に、時間を計算する関数があります。
現在の再生/録音時間をマイクロ秒で返します。
最後に受信したタイミング情報によって計算されます。
成功時は 0 が返り、タイミング情報が受信されていないなど、エラーの場合は、負の値が返ります。
ストリームの接続時、flags に PA_STREAM_INTERPOLATE_TIMING が設定されていた場合、最後にタイミング情報を受信した後に、ハードウェアによって実際に再生されたサンプル数を推測し、時間を補正します。
現在書き込まれているデータが、実際にハードウェア上で再生し終わるまでの時間を返します。
これも、最後に受信したタイミング情報によって計算されます。
※この関数は、内部で pa_stream_get_time() を使います。
negative は、時間が負の値になった場合、1 が返ります。
再生/録音時間の取得
int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec);
現在の再生/録音時間をマイクロ秒で返します。
最後に受信したタイミング情報によって計算されます。
成功時は 0 が返り、タイミング情報が受信されていないなど、エラーの場合は、負の値が返ります。
ストリームの接続時、flags に PA_STREAM_INTERPOLATE_TIMING が設定されていた場合、最後にタイミング情報を受信した後に、ハードウェアによって実際に再生されたサンプル数を推測し、時間を補正します。
レイテンシの取得
int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative);
現在書き込まれているデータが、実際にハードウェア上で再生し終わるまでの時間を返します。
これも、最後に受信したタイミング情報によって計算されます。
※この関数は、内部で pa_stream_get_time() を使います。
negative は、時間が負の値になった場合、1 が返ります。
プログラム
データを書き込む前に、タイミング情報を取得し、出力するプログラムです。
ただし、録音に関する情報などは表示していません。
'ドレミドレミ' (各0.5秒) の合計3秒分を再生しています。
ただし、録音に関する情報などは表示していません。
'ドレミドレミ' (各0.5秒) の合計3秒分を再生しています。
$ cc -o 16-timing 16-timing.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_write(pa_stream *p,size_t nbytes,void *userdata) { printf("- writeable: %d\n", (int)nbytes); pa_threaded_mainloop_signal(pulse->mainloop, 0); } //タイミング情報完了時 static void _success_timing(pa_stream *s,int success,void *userdata) { const pa_timing_info *i; pa_usec_t usec; int n; i = pa_stream_get_timing_info(s); printf("synchronized_clocks: %d\n", i->synchronized_clocks); printf("sink_usec: %lu\n", i->sink_usec); printf("transport_usec: %lu\n", i->transport_usec); printf("playing: %d\n", i->playing); printf("write_index: %ld (%lu us)\n", i->write_index, i->write_index * 1000 * 1000 / (SAMPRATE * 2)); printf("read_index: %ld (%lu us)\n", i->read_index, i->read_index * 1000 * 1000 / (SAMPRATE * 2)); printf("configured_sink_usec: %lu\n", i->configured_sink_usec); pa_stream_get_time(s, &usec); printf("<time> %lu (%.2f ms)\n", usec, usec / 1000.0); pa_stream_get_latency(s, &usec, &n); printf("<latency> %lu (%.2f ms)\n", usec, usec / 1000.0); printf("-----\n"); pa_threaded_mainloop_signal(pulse->mainloop, 0); } static pa_operation *_get_timing(PulseData *p,void *data) { return pa_stream_update_timing_info(p->stream, _success_timing, NULL); } //再生しつつ、バッファのすべてのデータを書き込み 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) { //書き込み可能なサイズ取得 while(1) { size = pa_stream_writable_size(strm); if(size == (size_t)-1) goto END; if(size) break; //書き込み可能になるまで待つ pa_threaded_mainloop_wait(pulse->mainloop); } //タイミング情報 pa_threaded_mainloop_unlock(pulse->mainloop); pulse_wait_operation(pulse, _get_timing, NULL); pa_threaded_mainloop_lock(pulse->mainloop); //書き込み if(size > sendsize) size = sendsize; 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); //次へ buf += size; sendsize -= size; } END: pa_threaded_mainloop_unlock(pulse->mainloop); } int main(void) { uint8_t *buf; int i,bufsize; double freq[3] = {261.626, 293.665, 329.628}; 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_write_callback(pulse->stream, _cb_write, NULL); pa_threaded_mainloop_unlock(pulse->mainloop); //再生 bufsize = SAMPRATE; buf = (uint8_t *)malloc(bufsize); for(i = 0; i < 6; i++) { pulse_write_buf_sawtooth(buf, bufsize, SAMPRATE, freq[i % 3]); _write_data(buf, bufsize); } free(buf); // pulse_wait_drain(pulse); pulse_free(pulse); return 0; }
実行結果
# stream connected synchronized_clocks: 1 sink_usec: 1833498 transport_usec: 382 playing: 0 write_index: 0 (0 us) read_index: 0 (0 us) configured_sink_usec: 210000 <time> 0 (0.00 ms) <latency> 0 (0.00 ms) ----- * write: 24000 - writeable: 20032 synchronized_clocks: 1 sink_usec: 210035 transport_usec: 237 playing: 1 write_index: 24000 (250000 us) read_index: 20032 (208666 us) configured_sink_usec: 210000 <time> 0 (0.00 ms) <latency> 250000 (250.00 ms) ----- * write: 20032 - writeable: 3968 - writeable: 18384 synchronized_clocks: 1 sink_usec: 208908 transport_usec: 1390 playing: 1 write_index: 44032 (458666 us) read_index: 38416 (400166 us) configured_sink_usec: 210000 <time> 192648 (192.65 ms) <latency> 266018 (266.02 ms) ----- * write: 3968 synchronized_clocks: 1 sink_usec: 205380 transport_usec: 277 playing: 1 write_index: 48000 (500000 us) read_index: 38416 (400166 us) configured_sink_usec: 210000 <time> 195063 (195.06 ms) <latency> 304937 (304.94 ms) ... ----- * write: 17824 - writeable: 18928 synchronized_clocks: 1 sink_usec: 208838 transport_usec: 1562 playing: 1 write_index: 264000 (2750000 us) read_index: 258928 (2697166 us) configured_sink_usec: 210000 <time> 2489890 (2489.89 ms) <latency> 260110 (260.11 ms) ----- * write: 18928 - writeable: 18400 synchronized_clocks: 1 sink_usec: 209204 transport_usec: 1236 playing: 1 write_index: 282928 (2947166 us) read_index: 277328 (2888833 us) configured_sink_usec: 210000 <time> 2680865 (2680.86 ms) <latency> 266301 (266.30 ms) ----- * write: 5072 - writeable: 24000
書き込み前にタイミング情報を取得しているので、write_index は、これまでに書き込んだ総バイト数となります。
2回目
synchronized_clocks: 1 sink_usec: 210035 transport_usec: 237 playing: 1 write_index: 24000 (250000 us) read_index: 20032 (208666 us) configured_sink_usec: 210000 <time> 0 (0.00 ms) <latency> 250000 (250.00 ms)
2回目の情報では、read_index が 20032 byte になっているので、サーバーによってここまで読み込まれたということがわかります。
ただし、再生データが読み込まれたというだけであって、実際に、このサイズ分の音が鳴り終えているかは別です。
time の値を見てみると、0 になっています。
ただ、これらの情報はあくまで推定であって、厳密に正確な値ではないので、参考程度に見ておいた方がいいでしょう。