録音
録音を行う場合は、snd_pcm_open() で PCM を開く時、stream 引数に SND_PCM_STREAM_CAPTURE を指定します。
name 引数には、再生時と同じように、PCM 定義名を指定しますが、録音が可能な PCM である必要があります。
名前ヒントで、IOID が NULL または "Input" であれば、録音用に開くことができます。
HDMI などで "Output" の場合は、出力しか行えません。
後は、再生の時と同じように、読み込み関数または MMAP の関数を使って、データを読み込みます。
ブロックモードの場合は、指定されたフレーム数が読み込めるまでブロックされます。
非ブロッキングモードの場合は、バッファにデータがない場合、-EAGAIN エラーですぐに戻ります。
MMAP を使う場合は、取得したバッファからデータを読み込みます。
name 引数には、再生時と同じように、PCM 定義名を指定しますが、録音が可能な PCM である必要があります。
名前ヒントで、IOID が NULL または "Input" であれば、録音用に開くことができます。
HDMI などで "Output" の場合は、出力しか行えません。
-- [0] -- NAME: 'null' DESC: 'Discard all samples (playback) or generate zero samples (capture)' IOID: <null> -- [1] -- NAME: 'default:CARD=PCH' DESC: 'HDA Intel PCH, ALC892 Analog Default Audio Device' IOID: <null> -- [2] -- NAME: 'sysdefault:CARD=PCH' DESC: 'HDA Intel PCH, ALC892 Analog Default Audio Device' IOID: <null> -- [3] -- NAME: 'front:CARD=PCH,DEV=0' DESC: 'HDA Intel PCH, ALC892 Analog Front output / input' IOID: <null>
後は、再生の時と同じように、読み込み関数または MMAP の関数を使って、データを読み込みます。
読み込み関数
//インターリーブ snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t *pcm, void *buffer, snd_pcm_uframes_t size); //非インターリーブ snd_pcm_sframes_t snd_pcm_readn(snd_pcm_t *pcm, void **bufs, snd_pcm_uframes_t size);
ブロックモードの場合は、指定されたフレーム数が読み込めるまでブロックされます。
非ブロッキングモードの場合は、バッファにデータがない場合、-EAGAIN エラーですぐに戻ります。
MMAP を使う場合は、取得したバッファからデータを読み込みます。
入力端子の選択
録音の場合は、入力を行う端子を、一つだけ選択する必要があります。
alsamixer で、F4 [録音] を選択し、左右キーで "Input Source" を選択。
上下キーで、端子を選択できます。
「Line (ラインイン)」「Front Mic (前面マイク端子)」「Rear Mic (背面マイク端子)」があります。
ただし、端子にコードを接続すれば、それが自動で選択されるので、通常は手動で選択する必要はありません。
複数の入力端子が接続されている場合は、選択する必要があります。
alsamixer で、F4 [録音] を選択し、左右キーで "Input Source" を選択。
上下キーで、端子を選択できます。
「Line (ラインイン)」「Front Mic (前面マイク端子)」「Rear Mic (背面マイク端子)」があります。
ただし、端子にコードを接続すれば、それが自動で選択されるので、通常は手動で選択する必要はありません。
複数の入力端子が接続されている場合は、選択する必要があります。
プログラム
非ブロッキングモードで開きます。
ハードウェア構成のバッファサイズ分のバッファを確保し、3秒を超えるまでデータを読み込みます。
結果は、record.wav に出力されます。
ハードウェア構成のバッファサイズ分のバッファを確保し、3秒を超えるまでデータを読み込みます。
結果は、record.wav に出力されます。
$ cc -o 19-record 19-record.c util.c -lasound
#include <stdio.h> #include <stdlib.h> #include <alsa/asoundlib.h> #include "util.h" #define SAMPRATE 48000 int main(int argc,char **argv) { snd_pcm_t *pcm; snd_pcm_uframes_t bufs,periods; uint8_t *buf; uint32_t write_frames; FILE *fp; int ret; if(snd_pcm_open(&pcm, (argc >= 2)? argv[1]: "default", SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) { printf("open error\n"); return 1; } //ハードウェア構成 (16bit LE, 2ch) snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, 2, SAMPRATE, 1, 500 * 1000); snd_pcm_get_params(pcm, &bufs, &periods); printf("buf size: %lu frames\n", bufs); printf("period size: %lu\n", periods); //出力ファイル fp = fopen("record.wav", "wb"); if(!fp) goto END; util_write_wave_header(fp, SAMPRATE, 2); //読み込み (3秒程度) buf = (uint8_t *)malloc(bufs * 4); write_frames = 0; while(write_frames < SAMPRATE * 3) { ret = snd_pcm_readi(pcm, buf, bufs); if(ret > 0) { printf("read: %d\n", ret); fwrite(buf, 1, ret * 4, fp); write_frames += ret; } else { if(ret == -EAGAIN) ret = snd_pcm_wait(pcm, SND_PCM_WAIT_IO); if(ret < 0) snd_pcm_recover(pcm, ret, 1); } } // printf("avail: %ld frames\n", snd_pcm_avail(pcm)); ret = snd_pcm_drain(pcm); printf("> drain: %d\n", ret); printf("> %s\n", snd_pcm_state_name(snd_pcm_state(pcm))); //残りデータ if(ret == -EAGAIN) { ret = snd_pcm_readi(pcm, buf, bufs); printf("read: %d\n", ret); if(ret > 0) { fwrite(buf, 1, ret * 4, fp); write_frames += ret; } } printf("> %s\n", snd_pcm_state_name(snd_pcm_state(pcm))); //書き込み終了 printf("write frames: %u\n", write_frames); util_write_wave_result_size(fp, write_frames * 4); fclose(fp); free(buf); // END: snd_pcm_close(pcm); return 0; }
実行結果
buf size: 16384 frames period size: 1024 read: 32 read: 1048 read: 8 read: 2072 read: 8 read: 8 read: 4 ... read: 8 read: 2016 avail: 8 frames > drain: -11 > RUNNING read: 12 > RUNNING write frames: 144440
"default" の場合、非ブロッキングモードだと、snd_pcm_wait() で待ったとしても、少し読み込めるようになっただけで、関数が戻ってくるようです。
snd_pcm_avail_update() で、読み込み可能なサイズを確認してから、読み込んだ方がいいかもしれません。
3秒分の読み込みを終了した後、snd_pcm_avail() で読み込み可能なフレーム数を確認すると、8 になっています。
snd_pcm_drain() を行った場合、停止した時点で残っているデータは読み込み可能ということになっていますが、"default" の場合は、drain 後も RUNNING 状態のままになっています。
hw
PCM を "hw" で開いた場合は、以下のようになります。
"default" と違って、ある程度大きなサイズで読み込みができているようです。
終了時の avail は 0 なので、読み込み可能なデータはありません。
drain では -EAGAIN が返っていますが、直後に SETUP 状態になり、readi でもエラーが返ります。
-77 は、-EBADFD です。デバイスが不良状態ということで、読み込めない状態になっています。
buf size: 24000 frames period size: 6000 read: 6040 read: 8 read: 8 read: 6000 read: 8 read: 11968 read: 8 read: 8 read: 6000 read: 6016 read: 8 read: 11968 read: 6016 read: 11976 read: 6016 read: 6016 read: 11968 read: 6016 read: 6008 read: 11976 read: 6016 read: 6016 read: 11968 read: 6016 avail: 0 frames > drain: -11 > SETUP read: -77 > SETUP write frames: 144048
"default" と違って、ある程度大きなサイズで読み込みができているようです。
終了時の avail は 0 なので、読み込み可能なデータはありません。
drain では -EAGAIN が返っていますが、直後に SETUP 状態になり、readi でもエラーが返ります。
-77 は、-EBADFD です。デバイスが不良状態ということで、読み込めない状態になっています。
avail が 0 でない場合
avail: 8 frames > drain: -11 > DRAINING read: -77 > DRAINING write frames: 150040
"hw" で avail が 0 でない場合、drain で -EAGAIN が返り、DRAINING 状態になりますが、readi は -EBADFD で失敗します。
総括
録音時は、drain を行っても、残っているデータは読み込めないと思ったほうがいいでしょう。
再生データの録音
ALSA で出力された音声を、ALSA で録音したい場合、シンプルな方法では実現できません。
Loopback のデバイス番号 0 は再生用で、デバイス番号 1 は録音用です。
Loopback の設定は、/usr/share/alsa/cards/Loopback.conf にあります。
ループバックによって、CARD=Loopback,DEV=0 に送られたデータを、CARD=Loopback,DEV=1 で録音できるようになります。
ただし、PC 上で音は鳴りません。
ALSA の定義ファイルで、PCM のデフォルト CARD を Loopback にしておけば、アプリケーション側で再生先を指定する必要はなくなります。
PC 上で再生されたすべての音を録音したいなら、PulseAudio を使った方が簡単です。
- ループバックデバイスを使えるようにするため、snd-aloop モジュールをロードする。
# modprobe snd-aloop
これにより、"Loopback" という名前で PCM デバイスが作成される。 - ALSA での再生時、"default:Loopback" または "plughw:Loopback,0,0" の PCM で再生する。
- 録音時、"plughw:Loopback,1,0" の PCM で録音する。
Loopback のデバイス番号 0 は再生用で、デバイス番号 1 は録音用です。
Loopback の設定は、/usr/share/alsa/cards/Loopback.conf にあります。
ループバックによって、CARD=Loopback,DEV=0 に送られたデータを、CARD=Loopback,DEV=1 で録音できるようになります。
ただし、PC 上で音は鳴りません。
ALSA の定義ファイルで、PCM のデフォルト CARD を Loopback にしておけば、アプリケーション側で再生先を指定する必要はなくなります。
PC 上で再生されたすべての音を録音したいなら、PulseAudio を使った方が簡単です。