其他Alsa相关博客:

交叉编译AlsaLib和AlsaUtils_libalsa交叉编译-CSDN博客

嵌入式板子使用Alsa接口和C++程序,录制Wav音频文件-CSDN博客

嵌入式板子使用Alsa接口和C++程序,播放Wav音频文件-CSDN博客

函数原型

void play_selected_audio(const char *filename, int start_time, int duration) 

参数:

filename        : WAV文件名

start_time      : 播放开始位置(秒数)

duration         :播放音频数据长度(秒数)

如下调用意思为,播放test.wav文件,从第10秒开始播放,播放5秒钟的音频

play_selected_audio("test.wav", 10, 5);

流程图:

关键点是:

算出开始播放位置对应的偏移和需要播放的数据量大小。

后续的循环就读取这范围内的数据播放即可。

WAV头部结构体:

/* sizeof(WAV_FMT) = 12 */
typedef struct WAV_RIFF 
{
    char ChunkID[4]; /* "RIFF" */
    uint32_t ChunkSize; /* 从下一个地址开始到文件末尾的总字节数 */
    char Format[4]; /* "WAVE" */
} __attribute__ ((packed)) RIFF_t;

/* sizeof(WAV_FMT) = 24 */
typedef struct WAV_FMT 
{
    char Subchunk1ID[4]; /* "fmt " */
    uint32_t Subchunk1Size; /* 16 for PCM */
    uint16_t AudioFormat; /* PCM = 1*/
    uint16_t NumChannels; /* Mono = 1, Stereo = 2, etc. */
    uint32_t SampleRate; /* 8000, 44100, etc. */
    uint32_t ByteRate; /* = SampleRate * NumChannels * BitsPerSample/8 */
    uint16_t BlockAlign; /* = NumChannels * BitsPerSample/8 */
    uint16_t BitsPerSample; /* 8bits, 16bits, etc. */
} __attribute__ ((packed)) FMT_t;

typedef struct WAV_DATA 
{
    char Subchunk2ID[4]; /* "data" */
    uint32_t Subchunk2Size; /* data size */
} __attribute__ ((packed)) DATA_t;


typedef struct WavHeaderSt
{
    RIFF_t wavRiffSt;
    FMT_t  wavFmtSt;
    WAV_DATA wavDataSt;
} WavHeader;

源代码:

// 从 WAV 文件中选取指定时间段的数据并播放
void play_selected_audio(const char *filename, int start_time, int duration) 
{
    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        perror("Failed to open file");
        return;
    }

    WavHeader header;
    if (read(fd, &header, sizeof(WavHeader)) != sizeof(WavHeader)) {
        perror("Failed to read WAV header");
        close(fd);
        return;
    }

    // 检查是否为有效的 WAV 文件
    if (strncmp(header.wavRiffSt.ChunkID, "RIFF", 4) != 0 || strncmp(header.wavRiffSt.Format, "WAVE", 4) != 0) {
        fprintf(stderr, "Not a valid WAV file\n");
        close(fd);
        return;
    }

    /*
        每秒钟音频所含的数据(单位:字节)
        SampleRate:是音频的采样率,指的是每秒对音频信号进行采样的次数,采样率越高,音频的细节就越丰富,声音还原就越准确。
        NumChannels:代表音频的声道数。常见的有单声道(num_channels为 1)和立体声(num_channels为 2)。
                             如果是单声道,每个采样点只包含一个声道的音频信息;如果是立体声,则包含左声道和右声道两个声道的音频信息。
        BitsPerSample:是每个采样点所占用的位数,用来表示每个采样值的精度。
                                比如,常见的有 8 位、16 位、24 位等。8 位表示每个采样点可以用 256 个不同的值来表示,16 位则可以表示 65536 个不同的值,位数越高,音频的动态范围就越大,声音的质量也就越高。
    */
    long dataBytes_Per_Second = header.wavFmtSt.SampleRate * header.wavFmtSt.NumChannels * (header.wavFmtSt.BitsPerSample / 8);

    // 计算起始偏移量
    int start_offset = start_time * dataBytes_Per_Second;

    // 计算要读取的数据大小
    int data_size = duration * dataBytes_Per_Second;
    printf("data_size = %ld, duration = %ld, sample_rate = %ld, bits_per_sample= %ld\n", 
            data_size, duration, header.wavFmtSt.SampleRate, header.wavFmtSt.BitsPerSample);

    // 定位到起始位置(从头开始便宜)
    if (lseek(fd, sizeof(WavHeader) + start_offset, SEEK_SET) == -1) 
    {
        perror("Failed to seek to start position");
        close(fd);
        return;
    }

    // 初始化 ALSA 设备
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *params;
    if (snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0) < 0) 
    {
        perror("Failed to open PCM device");
        close(fd);
        return;
    }

    int dir;
    snd_pcm_hw_params_alloca(&params);
    snd_pcm_hw_params_any(handle, params);
    snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
    snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);
    snd_pcm_hw_params_set_channels(handle, params, header.wavFmtSt.NumChannels);
    snd_pcm_hw_params_set_rate_near(handle, params, &header.wavFmtSt.SampleRate, &dir);

    if (snd_pcm_hw_params(handle, params) < 0) 
    {
        perror("Failed to set PCM hardware parameters");
        snd_pcm_close(handle);
        close(fd);
        return;
    }

    // 分块读取并播放数据
    const int buffer_size = 4096; // 每次读取的块大小
    char *buffer = (char *)malloc(buffer_size);
    if (buffer == NULL) 
    {
        perror("Failed to allocate memory");
        snd_pcm_close(handle);
        close(fd);
        return;
    }

    int remaining = data_size;
    while (remaining > 0) 
    {
        int read_size = (remaining < buffer_size) ? remaining : buffer_size;
        if (read(fd, buffer, read_size) != read_size) 
        {
            perror("Failed to read data");
            break;
        }


        snd_pcm_sframes_t frames_written = snd_pcm_writei(handle, buffer, read_size / header.wavFmtSt.BlockAlign);
        if (frames_written < 0) 
        {
            frames_written = snd_pcm_recover(handle, frames_written, 0);
            if (frames_written < 0) 
            {
                perror("Failed to write data to PCM device");
                break;
            }
        }

        remaining -= read_size;
    }

    // 清理资源
    free(buffer);
    snd_pcm_drain(handle);
    snd_pcm_close(handle);
    close(fd);
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐