ALSA:録音

録音
録音を行う場合は、snd_pcm_open() で PCM を開く時、stream 引数に SND_PCM_STREAM_CAPTURE を指定します。

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 (背面マイク端子)」があります。

ただし、端子にコードを接続すれば、それが自動で選択されるので、通常は手動で選択する必要はありません。
複数の入力端子が接続されている場合は、選択する必要があります。
プログラム
非ブロッキングモードで開きます。
ハードウェア構成のバッファサイズ分のバッファを確保し、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" で開いた場合は、以下のようになります。

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 で録音したい場合、シンプルな方法では実現できません。

  1. ループバックデバイスを使えるようにするため、snd-aloop モジュールをロードする。
    # modprobe snd-aloop
    これにより、"Loopback" という名前で PCM デバイスが作成される。
  2. ALSA での再生時、"default:Loopback" または "plughw:Loopback,0,0" の PCM で再生する。
  3. 録音時、"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 を使った方が簡単です。