PulseAudio:録音(1)

録音
オーディオ入力からの録音を行う場合は、ストリームを作成した後、pa_stream_connect_record() を実行して、ストリームを、サーバー上の1つのソースに接続します。

これにより、サーバー上で、それに対応するソース出力が作成されます。

その他の操作は、再生時と同じような形になります。
録音用に接続
int pa_stream_connect_record(pa_stream *s, const char *dev,
    const pa_buffer_attr *attr, pa_stream_flags_t flags);

録音用に、ストリームを、サーバー上のソースと接続します。

dev は、接続するソースの名前です。NULL でサーバーが選択します。
フラグメントサイズ
録音ストリームの場合、pa_buffer_attr 構造体のバッファ情報は、maxlength と fragsize のみ有効となります。

サーバーは、fragsize バイトのブロック単位で、データを送信します。
録音バッファ
サーバーは、録音用にバッファを作成します。
オーディオデータが入力されると、サーバーは録音バッファにデータを書き込んでいきます。

タイミング情報で言うところの write_index は、サーバーが次に書き込むバッファ位置になります。
read_index は、クライアントが次に読み込む位置です。

再生バッファとは逆に、PulseAudio が自動でデータを書き込んでいくので、クライアントは、バッファが溢れないように、順次データを読み込んでいく必要があります。

録音の場合は、クライアントの読み込みが追いつかず、バッファが溢れた場合、バッファオーバーフローになります。
データを読み込む
読み込み
int pa_stream_peek(pa_stream *p, const void **data, size_t *nbytes);

録音バッファに記録されたオーディオデータを読み込みます。

ただし、この関数は、録音バッファの読み込み位置を移動することはしません。
この関数で取得したポインタを使ってデータを処理した後、pa_stream_drop() を実行することで、読み込んだデータを削除し、録音バッファの読み込み位置を移動します。

data にデータのポインタが、nbytes にデータのバイト数が返ります。

バッファが空の場合は、*data = NULL, *nbytes = 0 になります。
この場合、drop は行わないようにしてください。

記録したデータに穴がある場合(途中で入力が途切れた場合)、*data = NULL, *nbytes = 穴のサイズとなります。

成功時は 0、失敗時は負の値が返ります。
読み込みデータの削除
int pa_stream_drop(pa_stream *p);

前回実行した pa_stream_peek() で取得した、読み込みデータを削除します。

バッファが空だった場合は呼び出す必要はありませんが、穴のデータだった場合は、呼び出す必要があります。
読み込み可能なサイズ
size_t pa_stream_readable_size(const pa_stream *p);

pa_stream_peek() で読み込むことができるバイト数を返します。
エラー時は、(size_t)-1 が返ります。

peek 時、実際にこのサイズ分を、一気に読み込めるとは限りません。
コールバック
void pa_stream_set_read_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata);

//コールバック関数
typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t nbytes, void *userdata);

新しいデータが読み込み可能になった時に実行される、コールバック関数をセットします。
プログラム
16bit, 48000 Hz, 2ch で、約5秒間録音を行い、record.wav に出力するプログラムです。

$ cc -o 17-record 17-record.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_read(pa_stream *p,size_t nbytes,void *userdata)
{
    printf("- readable: %d\n", (int)nbytes);
    
    pa_threaded_mainloop_signal(pulse->mainloop, 0);
}

//読み込み (5秒分)

static uint32_t _read_data(PulseData *p,FILE *fp)
{
    pa_stream *strm = p->stream;
    const void *readbuf;
    size_t readsize;
    uint32_t writesize = 0;

    pa_threaded_mainloop_lock(p->mainloop);

    while(writesize < SAMPRATE * 4 * 5)
    {
        //読み込み可能なサイズ取得

        while(1)
        {
            readsize = pa_stream_readable_size(strm);
            if(readsize == (size_t)-1) goto END;

            if(readsize) break;

            pa_threaded_mainloop_wait(p->mainloop);
        }

        //読み込み

        if(pa_stream_peek(strm, &readbuf, &readsize))
            break;

        printf("* peek: %c %d\n", (readbuf)? 'o':'-', (int)readsize);

        //書き込み

        if(readbuf)
        {
            fwrite(readbuf, 1, readsize, fp);
            writesize += readsize;
        }

        //削除
        
        if(readsize)
            pa_stream_drop(strm);
    }

END:
    pa_threaded_mainloop_unlock(p->mainloop);

    return writesize;
}

//ストリームの状態変化コールバック

static void _cb_stream_state(pa_stream *strm,void *data)
{
    int state = pa_stream_get_state(strm);

    if(state == PA_STREAM_READY || state == PA_STREAM_FAILED)
        pa_threaded_mainloop_signal(pulse->mainloop, 0);
}

//録音用に接続

static int _connect_record(PulseData *p)
{
    pa_stream *strm;
    pa_sample_spec ss;
    pa_stream_state_t state;

    pa_threaded_mainloop_lock(p->mainloop);

    //作成

    ss.format = PA_SAMPLE_S16LE;
    ss.rate = SAMPRATE;
    ss.channels = 2;

    strm = pa_stream_new(p->ctx, "test-stream", &ss, NULL);

    //待つ

    pa_stream_set_state_callback(strm, _cb_stream_state, p);

    if(pa_stream_connect_record(strm, NULL, NULL, 0))
    {
        printf("! failed stream connect\n");
        pa_stream_unref(strm);
        pa_threaded_mainloop_unlock(p->mainloop);
        return 1;
    }

    while(1)
    {
        state = pa_stream_get_state(strm);
        if(state == PA_STREAM_READY || state == PA_STREAM_FAILED) break;

        pa_threaded_mainloop_wait(p->mainloop);
    }

    //

    p->stream = strm;

    printf("# stream connected\n");

    pa_threaded_mainloop_unlock(p->mainloop);

    return 0;
}

int main(void)
{
    FILE *fp;
    uint32_t size;

    pulse = pulse_connect(0);
    if(!pulse) return 1;

    if(_connect_record(pulse))
        pulse_enderr(pulse);

    pulse_put_buffer_attr(pulse->stream);

    //コールバック

    pa_threaded_mainloop_lock(pulse->mainloop);
    pa_stream_set_read_callback(pulse->stream, _cb_read, NULL);
    pa_threaded_mainloop_unlock(pulse->mainloop);

    //録音

    fp = fopen("record.wav", "wb");
    if(!fp) goto END;

    pulse_write_wave_header(fp, SAMPRATE, 2);

    size = _read_data(pulse, fp);

    printf("* write size: %u\n", size);

    pulse_write_wave_result_size(fp, size);

    fclose(fp);

    //

END:
    pulse_free(pulse);

    return 0;
}
実行結果
# stream connected
--- buffer ---
maxlength: 4194304
tlength: 48000
prebuf: 4294967295
minreq: 4294967295
fragsize: 384000
--------------
- readable: 65472
- readable: 130944
- readable: 196416
- readable: 261888
- readable: 327360
- readable: 349280
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 21920
- readable: 3520
- readable: 68992
* peek: o 3520
* peek: o 65472
- readable: 65472
- readable: 130944
- readable: 196416
- readable: 261888
- readable: 280352
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 18464
- readable: 6976
* peek: o 6976
- readable: 65472
- readable: 130944
- readable: 196416
- readable: 261888
- readable: 327360
- readable: 342304
* peek: o 65472
* peek: o 65472
* peek: o 65472
* peek: o 65472
* write size: 967488

pa_stream_readable_size() で 1 以上が返ったら、データを読み込み、ファイルに出力します。

fragsize の値を見てみると、384000 byte (2秒分) です。

最初に、読み込み可能になったというコールバックが何回か続きますが、この間、読み込みは行われていません。
つまり、読み込みは可能であるが、pa_stream_readable_size() で 0 が返っているので、読み込み可能なサイズがどんどん増えている状態です。

その後、読み込み可能なサイズが 349280 になった時、実際に読み込みが行われています。
これは fragsize に近い値なので、pa_stream_readable_size() は、ある程度読み込み可能な状態であっても、fragsize のサイズを基準にして、値を返しているようです。

pa_stream_readable_size() で 1 以上が返ったら、実際に読み込みを行います。
最初に peek で取得されたサイズは 65472 で、その後も、これ以上のサイズでは取得されていないので、読み込み用のバッファの最大値は、65472 byte であると思われます。

実際に読み込めるサイズと、peek で取得できるバッファの最大サイズは異なることがわかります。

書き込みサイズが5秒分を超えると、プログラムは終了します。

ちなみに、オーディオ入力が全くない(端子が1つも接続されていない)状態でも、録音はできます。
その場合、データは無音になります。