ALSA学习笔记

本文详细介绍了ALSA(AdvancedLinuxSoundArchitecture)音频框架中的关键概念,包括PCM数据类型、访问方式、格式、子格式和状态,以及如何使用ALSA进行音频回放和录音的示例代码。
摘要由CSDN通过智能技术生成

        ALSA框架介绍:ALSA-LINUX音频框架学习笔记-CSDN博客

        代码参考(博客园):Alsa音频编程【精华】

        对原博客代码进行了修改并添加了注释(测试通过,可直接运行),代码包含三个测试用例:1、显示了一些ALSA使用的PCM数据类型和参数;2、添加声音回放;3、添加录音。

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#if 0
typedef enum _snd_pcm_stream {
        /** Playback stream */
        SND_PCM_STREAM_PLAYBACK = 0,
        /** Capture stream */
        SND_PCM_STREAM_CAPTURE,
        SND_PCM_STREAM_LAST = SND_PCM_STREAM_CAPTURE
} snd_pcm_stream_t;
typedef enum _snd_pcm_access {
        /** mmap access with simple interleaved channels */
        SND_PCM_ACCESS_MMAP_INTERLEAVED = 0,     /* 使用简单交错通道的 mmap 访问 */
        /** mmap access with simple non-interleaved channels */
        SND_PCM_ACCESS_MMAP_NONINTERLEAVED,      /* 使用简单非交错通道的 mmap 访问 */
        /** mmap access with complex placement */
        SND_PCM_ACCESS_MMAP_COMPLEX,             /* 使用复杂布局的 mmap 访问 */
        /** snd_pcm_readi/snd_pcm_writei access */
        SND_PCM_ACCESS_RW_INTERLEAVED,           /* 使用 snd_pcm_readi/snd_pcm_writei 访问 */
        /** snd_pcm_readn/snd_pcm_writen access */
        SND_PCM_ACCESS_RW_NONINTERLEAVED,        /* 使用 snd_pcm_readn/snd_pcm_writen 访问 */
        SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED
} snd_pcm_access_t;
typedef enum _snd_pcm_format {
        /** Unknown */
        SND_PCM_FORMAT_UNKNOWN = -1,
        /** Signed 8 bit */
        SND_PCM_FORMAT_S8 = 0,
        /** Unsigned 8 bit */
        SND_PCM_FORMAT_U8,
        /** Signed 16 bit Little Endian */
        SND_PCM_FORMAT_S16_LE,
        ... 
} snd_pcm_format_t;
typedef enum _snd_pcm_subformat {
        /** Unknown */
        SND_PCM_SUBFORMAT_UNKNOWN = -1,
        /** Standard */
        SND_PCM_SUBFORMAT_STD = 0,
        /** Maximum bits based on PCM format */
        SND_PCM_SUBFORMAT_MSBITS_MAX = 1,
        /** 20 most significant bits */
        SND_PCM_SUBFORMAT_MSBITS_20 = 2,
        /** 24 most significant bits */
        SND_PCM_SUBFORMAT_MSBITS_24 = 3,
        SND_PCM_SUBFORMAT_LAST = SND_PCM_SUBFORMAT_MSBITS_24
} snd_pcm_subformat_t;
typedef enum _snd_pcm_state {
        /** Open */
        SND_PCM_STATE_OPEN = 0,
        /** Setup installed */
        SND_PCM_STATE_SETUP,
        /** Ready to start */
        SND_PCM_STATE_PREPARED,
        /** Running */
        SND_PCM_STATE_RUNNING,
        /** Stopped: underrun (playback) or overrun (capture) detected */
        SND_PCM_STATE_XRUN,
        /** Draining: running (playback) or stopped (capture) */
        SND_PCM_STATE_DRAINING,
        /** Paused */
        SND_PCM_STATE_PAUSED,
        /** Hardware is suspended */
        SND_PCM_STATE_SUSPENDED,
        /** Hardware is disconnected */
        SND_PCM_STATE_DISCONNECTED,
        SND_PCM_STATE_LAST = SND_PCM_STATE_DISCONNECTED,
        /** Private - used internally in the library - do not use*/
        SND_PCM_STATE_PRIVATE1 = 1024
} snd_pcm_state_t;
#endif
// 1、显示了一些ALSA使用的PCM数据类型和参数。
int main1()
{
    int val;
    printf("ALSA library version: %s\n", SND_LIB_VERSION_STR);

    printf("\nPCM stream types:\n");
    for (val = 0; val <= SND_PCM_STREAM_LAST; val++)
        printf(" %s\n", snd_pcm_stream_name((snd_pcm_stream_t)val));

    printf("\nPCM access types:\n");
    for (val = 0; val <= SND_PCM_ACCESS_LAST; val++) {
        printf(" %s\n", snd_pcm_access_name((snd_pcm_access_t)val));
    }

    printf("\nPCM formats:\n");
    for (val = 0; val <= SND_PCM_FORMAT_LAST; val++) {
        if (snd_pcm_format_name((snd_pcm_format_t)val) != NULL) {
            printf(" %s (%s)\n", snd_pcm_format_name((snd_pcm_format_t)val), snd_pcm_format_description((snd_pcm_format_t)val));
        }
    }
    printf("\nPCM subformats:\n");
    for (val = 0; val <= SND_PCM_SUBFORMAT_LAST; val++) {
        printf(" %s (%s)\n", snd_pcm_subformat_name((snd_pcm_subformat_t)val), snd_pcm_subformat_description((snd_pcm_subformat_t)val));
    }
    printf("\nPCM states:\n");
    for (val = 0; val <= SND_PCM_STATE_LAST; val++)
        printf(" %s\n", snd_pcm_state_name((snd_pcm_state_t)val));

    return 0;
}
// 2、添加声音回放
// ./example  </dev/urandom
int main2()
{
    long loops;
    int rc;
    int size;
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *params;
    unsigned int val;
    int dir;
    snd_pcm_uframes_t frames;
    char *buffer;

    /* Open PCM device for playback. */
    rc = snd_pcm_open(&handle, "hw:0,0", SND_PCM_STREAM_PLAYBACK, 0);
    if (rc < 0) {
        fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
        exit(1);
    }

    /* Allocate a hardware parameters object. */
    snd_pcm_hw_params_malloc(&params);

    /* Fill it in with default values. */
    snd_pcm_hw_params_any(handle, params); // 设置默认值

    /* Set the desired hardware parameters. */

    /* Interleaved mode */
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); // 交错模式并使用 snd_pcm_readi/snd_pcm_writei 访问

    /* Signed 16-bit little-endian format */
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); // 一个采样点16bits 小端

    /* Two channels (stereo) */
    snd_pcm_hw_params_set_channels(handle, params, 2); // 通道数

    /* 44100 bits/second sampling rate (CD quality) */
    val = 44100;
    /*
      snd_pcm_hw_params_set_rate_near:设置最接近所需采样率的合适值。它会尝试在给定的范围内找到最接近所需采样率的值,并将其设置为参数 params 的采样率。
      snd_pcm_hw_params_set_rate_first:设置为第一个可用的采样率值。它将采样率设置为所需范围内的最小值。
      snd_pcm_hw_params_set_rate_last:设置为最后一个可用的采样率值。它将采样率设置为所需范围内的最大值。
      这些函数的最后一个参数 int *dir 是一个指针,用于获取设置的方向。方向可以是以下值之一:
      0 表示没有方向的限制。
      1 表示设置的值是一个最小值。
      -1 表示设置的值是一个最大值。
      调整音频设备的采样率时,如果用户更希望选择一个低于或等于所需采样率的最大可能值,则可以将采样率方向设置为 -1。相反,如果用户更希望选择一个高于或等于所需采样率的最小可能值,则可以将采样率方向设置为 1。如果不关心方向,则可以将方向参数设置为 0,这样函数将在所需范围内选择最合适的值
    */
    dir = 0;
    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); // 采样率

    frames = 1024;
    snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); // 一个周期1024个采样点,alsa中一帧就是一个采样点(这个概念真坑)

    // 设置周期数量
    snd_pcm_hw_params_set_periods(handle, params, 5, 0);

    /* Write the parameters to the driver */
    rc = snd_pcm_hw_params(handle, params); // 参数设置到硬件驱动
    if (rc < 0) {
        fprintf(stderr,
                "unable to set hw parameters: %s\n",
                snd_strerror(rc));
        exit(1);
    }

    /* Use a buffer large enough to hold one period */
    snd_pcm_hw_params_get_period_size(params, &frames, &dir); // 获取一个周期大小
    size = frames * 4;                                        /* 2 bytes/sample, 2 channels */
    buffer = (char *)malloc(size);                            // 分配一个周期大小的buffer

    /* We want to loop for 5 seconds */
    snd_pcm_hw_params_get_period_time(params, &val, &dir); // 获取一个周期的时间 微妙
    /* 5 seconds in microseconds divided by period time */
    loops = 5000000 / val; // 录制5秒需要的循环次数

    while (loops > 0) // 循环录音 5 s
    {
        loops--;
        printf("loops:%lld\n", loops);
        rc = read(0, buffer, size); // 从标准输入中读取一个周期的数据(1024个采样点,在alsa中就是1024帧)
        if (rc == 0)                // 没有读取到数据
        {
            fprintf(stderr, "end of file on input\n");
            break;
        } else if (rc != size) // 实际读取 的数据 小于 要读取的数据
        {
            fprintf(stderr, "short read: read %d bytes\n", rc);
        }

        rc = snd_pcm_writei(handle, buffer, frames); // 写入声卡  (放音) 最后一个参数是一个周期帧数量,不是一个周期的数据总大小
        // 返回值为EPIPE表明发生了under run,使得PCM音频流进入到XRUN状态并停止处理数据。从该状态中恢复过来的标准方法是调用snd_pcm_prepare()函数,把PCM流置于PREPARED状态,这样下次我们向该PCM流中数据时,它就能重新开始处理数据
        // 应用程序写入数据到环形缓冲区 buffer 中的速度不够快,缓存区将会“饿死”(缓冲区中无数据可播放)
        if (rc == -EPIPE) {
            /* EPIPE means underrun */
            fprintf(stderr, "underrun occurred\n");
            snd_pcm_prepare(handle);
        } else if (rc < 0) {
            fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));
        } else if (rc != (int)frames) {
            fprintf(stderr, "short write, write %d frames\n", rc);
        }
    }
    snd_pcm_hw_params_free(params);
    // snd_pcm_drain用于等待当前音频播放完毕并清空音频缓冲区。它的作用是在停止音频播放之前等待音频缓冲区中的所有数据被完全播放,以避免意外丢失音频数据或造成不完整的播放。
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    free(buffer);

    return 0;
}
// 3、添加录音
// ffplay -f s16le -ar 44100 -ac 2 out.pcm
int main()
{
    long loops;
    int rc;
    int size;
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *params;
    unsigned int val;
    int dir;
    snd_pcm_uframes_t frames;
    char *buffer;
    FILE *out;
    out = fopen("out.pcm", "w");
    /* Open PCM device for recording (capture). */
    rc = snd_pcm_open(&handle, "hw:0,0", SND_PCM_STREAM_CAPTURE, 0);
    if (rc < 0) {
        fprintf(stderr,
                "unable to open pcm device: %s\n",
                snd_strerror(rc));
        exit(1);
    }

    /* Allocate a hardware parameters object. */
    snd_pcm_hw_params_malloc(&params);

    /* Fill it in with default values. */
    snd_pcm_hw_params_any(handle, params);

    /* Set the desired hardware parameters. */

    /* Interleaved mode */
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

    /* Signed 16-bit little-endian format */
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

    /* Two channels (stereo) */
    snd_pcm_hw_params_set_channels(handle, params, 2);

    /* 44100 bits/second sampling rate (CD quality) */
    val = 44100;
    snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

    frames = 1024;
    snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

    // 设置周期数量
    snd_pcm_hw_params_set_periods(handle, params, 5, 0);

    /* Write the parameters to the driver */
    rc = snd_pcm_hw_params(handle, params);
    if (rc < 0) {
        fprintf(stderr,
                "unable to set hw parameters: %s\n",
                snd_strerror(rc));
        exit(1);
    }

    /* Use a buffer large enough to hold one period */
    snd_pcm_hw_params_get_period_size(params, &frames, &dir);
    printf("frames:%lld\n", frames);
    size = frames * 4; /* 2 bytes/sample, 2 channels */
    buffer = (char *)malloc(size);

    /* We want to loop for 5 seconds */
    snd_pcm_hw_params_get_period_time(params, &val, &dir);
    loops = 5000000 / val;
    printf("loops:%lld\n", loops);
    while (loops > 0) {
        loops--;
        printf("loops:%lld\n", loops);
        rc = snd_pcm_readi(handle, buffer, frames);
        if (rc == -EPIPE) {
            /* EPIPE means overrun */
            fprintf(stderr, "overrun occurred\n");
            snd_pcm_prepare(handle);
        } else if (rc < 0) {
            fprintf(stderr,
                    "error from read: %s\n",
                    snd_strerror(rc));
        } else if (rc != (int)frames) {
            fprintf(stderr, "short read, read %d frames\n", rc);
        }
        rc = fwrite(buffer, 1, size, out);
        if (rc != size)
            fprintf(stderr,
                    "short write: wrote %d bytes\n", rc);
    }
    snd_pcm_hw_params_free(params);
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    fclose(out);
    free(buffer);

    return 0;
}

         我的开源:

        1、Nvidia视频硬解码、渲染、软/硬编码并写入MP4文件。项目地址:https://github.com/BreakingY/Nvidia-Video-Codec
        2、Jetson Jetpack5.x视频编解码。项目地址:https://github.com/BreakingY/jetpack-dec-enc
        3、ffmpeg音视频(H264/H265/AAC)封装、解封装、编解码pipeline,支持NVIDIA硬编解码。项目地址:https://github.com/BreakingY/FFmpeg-Media-Codec-Pipeline
        4、simple rtsp server,小而高效的rtsp服务器,支持H264、H265、AAC、PCMA;支持TCP、UDP;支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-server

        5、simple rtp client,rtsp客户端,支持TCP、UDP、H264、H265、AAC、PCMA,支持鉴权。项目地址:https://github.com/BreakingY/simple-rtsp-client

        6、libflv,flv muxer/demuxer,支持H264/H265、AAC。项目地址:https://github.com/BreakingY/libflv

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值