MMAP
今までは、snd_pcm_writei() を使ってデータを書き込んできましたが、ここでは、MMAP を使って、取得したバッファに直接データを書き込む方法を紹介します。
MMAP でのアクセスを開始して、情報を取得します。
この関数を実行する直前に、snd_pcm_avail_update() 関数を呼び出す必要があります。
でないと、frames に間違った数が返る可能性があります。
この関数の後、addr に offset を加えた位置に、データを書き込みます。
読み書き可能なフレーム数を返します。
snd_pcm_avail() と違い、リングバッファ上のハードウェア位置と同期しません。
MMAP へのアクセスを完了します。
snd_pcm_mmap_begin() と対で実行する必要があります。
アクセスタイプ
まず、ハードウェア構成で、アクセスタイプを MMAP に指定する必要があります。
SND_PCM_ACCESS_MMAP_INTERLEAVED SND_PCM_ACCESS_MMAP_NONINTERLEAVED SND_PCM_ACCESS_MMAP_COMPLEX
アクセスを開始
int snd_pcm_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames); typedef struct _snd_pcm_channel_area { void *addr; //ベースアドレス unsigned int first; //最初のサンプルのオフセット (ビット単位) unsigned int step; //サンプルの距離 (ビット単位) } snd_pcm_channel_area_t;
MMAP でのアクセスを開始して、情報を取得します。
areas | 各チャンネルの領域情報の配列ポインタが返ります。 インターリーブなら、1つのみ。 非インターリーブなら、チャンネル数分あります。 |
---|---|
offset | MMAP 領域のオフセット位置 (フレーム単位) が返ります。 addr のベースアドレスに、この位置を加算する必要があります。 |
frames | 読み書きしたいフレーム数をセットします。 実際に読み書きできるフレーム数が返ります。 バッファがいっぱいの時などは、0 が返る場合があります。 |
この関数を実行する直前に、snd_pcm_avail_update() 関数を呼び出す必要があります。
でないと、frames に間違った数が返る可能性があります。
この関数の後、addr に offset を加えた位置に、データを書き込みます。
読み書き可能なフレーム数を返す
snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm);
読み書き可能なフレーム数を返します。
snd_pcm_avail() と違い、リングバッファ上のハードウェア位置と同期しません。
アクセスを完了する
snd_pcm_sframes_t snd_pcm_mmap_commit(snd_pcm_t *pcm, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames);
MMAP へのアクセスを完了します。
snd_pcm_mmap_begin() と対で実行する必要があります。
offset | begin で返されたオフセット値を指定する |
---|---|
frames | 実際に読み書きしたフレーム数を指定する。 begin で返されたフレーム数を超えないこと。 |
戻り値 | 転送されたフレーム数。負の値でエラーコード |
処理方法
MMAP を使用する場合は、snd_pcm_writei() と同じような動作にはならないので、注意してください。
例えば、MMAP のバッファにデータを書き込んでも、自動的に再生が開始されることはありません。
また、ブロックモードの場合に、すべてのフレーム数が書き込めるようになるまで、再生しながら待つといったことも出来ません。
MMAP によるアクセスは、ブロックモードが選択されていても、非ブロッキングモードと同じような扱いをする必要があります。
例えば、MMAP のバッファにデータを書き込んでも、自動的に再生が開始されることはありません。
また、ブロックモードの場合に、すべてのフレーム数が書き込めるようになるまで、再生しながら待つといったことも出来ません。
MMAP によるアクセスは、ブロックモードが選択されていても、非ブロッキングモードと同じような扱いをする必要があります。
待つ
snd_pcm_mmap_begin() を行う前に、snd_pcm_avail_update() の実行が必要になるので、まずは snd_pcm_avail_update() で、書き込み可能なフレーム数を取得します。
最初の書き込みであれば、バッファサイズと同じ値が返るので、そのまま普通に書き込みます。
次の書き込みを行う前に、また snd_pcm_avail_update() を実行しますが、書き込み後、時間を置かずに実行した場合は、ごく小さな値だけが返ります。
小さなサイズで書き込むのは効率が悪いので、返った値が一定サイズ (ピリオドサイズなど) 以下であれば、snd_pcm_wait(pcm, SND_PCM_WAIT_IO) を実行して、書き込み可能になったというイベントが起こるまで待ちます。
(wait は、ブロックモードに関係なく使用できます)
wait は、内部で poll() を使っているので、ソフトウェア構成の snd_pcm_sw_params_get_avail_min() で取得できるフレーム数以上が、読み書きできるようになった時に、関数が戻ってきます。
その後、再び snd_pcm_avail_update() を実行すれば、大きな値が返ってくるので、そのフレーム数で読み書きを行います。
最初の書き込みであれば、バッファサイズと同じ値が返るので、そのまま普通に書き込みます。
次の書き込みを行う前に、また snd_pcm_avail_update() を実行しますが、書き込み後、時間を置かずに実行した場合は、ごく小さな値だけが返ります。
小さなサイズで書き込むのは効率が悪いので、返った値が一定サイズ (ピリオドサイズなど) 以下であれば、snd_pcm_wait(pcm, SND_PCM_WAIT_IO) を実行して、書き込み可能になったというイベントが起こるまで待ちます。
(wait は、ブロックモードに関係なく使用できます)
wait は、内部で poll() を使っているので、ソフトウェア構成の snd_pcm_sw_params_get_avail_min() で取得できるフレーム数以上が、読み書きできるようになった時に、関数が戻ってきます。
その後、再び snd_pcm_avail_update() を実行すれば、大きな値が返ってくるので、そのフレーム数で読み書きを行います。
注意点
最初にバッファサイズ全体分のデータを書き込んだとしても、PCM は自動的に再生を開始しません。
そのため、最初の書き込みが終わったら、手動で snd_pcm_start() を実行する必要があります。
アンダーラン状態になった場合は、自動的に再生が停止します。
この場合、snd_pcm_avail_update() 時に -EPIPE が返りますが、snd_pcm_mmap_begin() では 0 が返ります。
バッファの書き込み自体は成功しますが、snd_pcm_mmap_commit() 時には -EPIPE が返ります。
バッファの書き込みは、デバイスとは関係ないところで行われるため問題ないが、デバイスにデータを送る時点でエラーが出る、ということになります。
そのため、アンダーランは、snd_pcm_avail_update() の時点で判定するようにします。
そのため、最初の書き込みが終わったら、手動で snd_pcm_start() を実行する必要があります。
アンダーラン状態になった場合は、自動的に再生が停止します。
この場合、snd_pcm_avail_update() 時に -EPIPE が返りますが、snd_pcm_mmap_begin() では 0 が返ります。
バッファの書き込み自体は成功しますが、snd_pcm_mmap_commit() 時には -EPIPE が返ります。
バッファの書き込みは、デバイスとは関係ないところで行われるため問題ないが、デバイスにデータを送る時点でエラーが出る、ということになります。
そのため、アンダーランは、snd_pcm_avail_update() の時点で判定するようにします。
プログラム
MMAP を使って、1秒分のデータを書き込みます。
$ cc -o 18-mmap 18-mmap.c util.c -lasound
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <alsa/asoundlib.h> #include "util.h" #define SAMPRATE 48000 static void _write_buf(snd_pcm_t *pcm,uint8_t *buf,int buf_frames) { const snd_pcm_channel_area_t *area; snd_pcm_uframes_t offset,frames; snd_pcm_sframes_t avail,retf; int first = 1; while(buf_frames > 0) { //1024 フレーム以上が有効になるまで待つ while(1) { avail = snd_pcm_avail_update(pcm); if(avail >= 1024) break; printf("# wait (avail:%ld)\n", avail); snd_pcm_wait(pcm, SND_PCM_WAIT_IO); } //書き込むフレーム数 frames = buf_frames; if(frames > avail) frames = avail; printf("avail: %ld, write_frames: %lu\n", avail, frames); //開始 snd_pcm_mmap_begin(pcm, &area, &offset, &frames); printf("> begin: %lu frames ", frames); printf("[area] first:%u, step:%u\n", area->first, area->step); //書き込み memcpy((uint8_t *)area->addr + offset * 4, buf, frames * 4); retf = snd_pcm_mmap_commit(pcm, offset, frames); printf("> commit: %ld frames\n", retf); //最初の書き込み後、再生を開始 if(first) { snd_pcm_start(pcm); first = 0; } //次へ buf += retf * 4; buf_frames -= retf; } } int main(int argc,char **argv) { snd_pcm_t *pcm; uint8_t *buf; if(snd_pcm_open(&pcm, (argc >= 2)? argv[1]: "default", SND_PCM_STREAM_PLAYBACK, 0)) { printf("open error\n"); return 1; } //ハードウェア構成 (16bit LE, 2ch) snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_MMAP_INTERLEAVED, 2, SAMPRATE, 1, 500 * 1000); //書き込み buf = (uint8_t *)malloc(SAMPRATE * 4); util_write_buf_sawtooth(buf, SAMPRATE, SAMPRATE, AUDIO_FREQ_DO); _write_buf(pcm, buf, SAMPRATE); free(buf); // snd_pcm_drain(pcm); snd_pcm_close(pcm); return 0; }
実行結果
avail: 16384, write_frames: 16384 > begin: 16384 frames [area] first:0, step:32 > commit: 16384 frames # wait (avail:24) avail: 1808, write_frames: 1808 > begin: 1808 frames [area] first:0, step:32 > commit: 1808 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 1040, write_frames: 1040 > begin: 256 frames [area] first:0, step:32 > commit: 256 frames # wait (avail:788) avail: 1792, write_frames: 1792 > begin: 1792 frames [area] first:0, step:32 > commit: 1792 frames # wait (avail:8) avail: 1040, write_frames: 1040 > begin: 1040 frames [area] first:0, step:32 > commit: 1040 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 1040, write_frames: 1040 > begin: 1040 frames [area] first:0, step:32 > commit: 1040 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 2040, write_frames: 2040 > begin: 2040 frames [area] first:0, step:32 > commit: 2040 frames # wait (avail:8) avail: 2048, write_frames: 2048 > begin: 2048 frames [area] first:0, step:32 > commit: 2048 frames # wait (avail:8) avail: 1040, write_frames: 1040 > begin: 1040 frames [area] first:0, step:32 > commit: 1040 frames # wait (avail:8) avail: 2040, write_frames: 112 > begin: 112 frames [area] first:0, step:32 > commit: 112 frames
一番最初は、バッファサイズ分のデータを書き込みます。
領域情報の first と step は、ビット情報になっていますが、ここでは使用する必要はありません。
最初に書き込んだ後の avail_update では、24 が返っているので、wait で待ちます。
すると、avail_update で 1808 が返るので、次はこのサイズで書き込みます。
以降は、1秒分のデータが書き込めるまで繰り返します。
なお、エラー処理は全く行っていませんが、実際は、各処理で負の値のエラーコードが返った場合、適切に処理する必要があります。