Linux嵌入式设备下基于Alsa实现wav音频播放

1.当前使用alsa 1.2.6.1版本

2.代码

2.1 wav结构体实现

#pragma pack(push, 1)
typedef struct {
    char     riff[4];          // "RIFF"
    uint32_t file_size;        // 文件总大小 - 8
    char     wave[4];          // "WAVE"
    char     fmt[4];           // "fmt "
    uint32_t fmt_size;         // fmt块大小
    uint16_t audio_format;     // 1=PCM
    uint16_t channels;         // 声道数
    uint32_t sample_rate;      // 采样率
    uint32_t byte_rate;        // 每秒字节数
    uint16_t block_align;      // 每帧字节数
    uint16_t bits_per_sample;  // 位深
} WavHeader;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
    char     data_id[4];       // "data"
    uint32_t data_size;        // 音频数据大小
} DataHeader;
#pragma pack(pop)

2.2混音器设置函数

static snd_mixer_t *mixer = NULL;
static bool status = true;
//设置混音器参数
int set_control(snd_mixer_t *mixer, const char *name, int value) 
{
    snd_mixer_selem_id_t *sid;
    snd_mixer_elem_t *elem;
    int err;
    bool is_bool = false;

    snd_mixer_selem_id_alloca(&sid);
    snd_mixer_selem_id_set_name(sid, name);

    if (!(elem = snd_mixer_find_selem(mixer, sid))) {
        fprintf(stderr, "Control '%s' not found\n", name);
        return -1;
    }
    if (snd_mixer_selem_is_enumerated(elem)) 
    {  
        err = snd_mixer_selem_set_enum_item(elem, SND_MIXER_SCHN_FRONT_LEFT, value);
    } 
    else if (snd_mixer_selem_has_playback_switch(elem)) 
    {  
        is_bool = true;
        err = snd_mixer_selem_set_playback_switch_all(elem, value ? 1 : 0);
    }
     else if (snd_mixer_selem_has_playback_volume(elem)) 
     {  
        long min, max;
        snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
        long scaled_value =max * value / 100;
        err = snd_mixer_selem_set_playback_volume_all(elem, scaled_value);
    } else {
        fprintf(stderr, "Unsupported control type: %s\n", name);
        return -1;
    }
    if (err < 0) 
    {
        fprintf(stderr, "Set %s to %d failed: %s\n", 
                name, value, snd_strerror(err));
        return err;
    }
    printf("Set %-25s to %s%d\n", name, 
           is_bool ? (value ? "ON " : "OFF") : "",
           is_bool ? 0 : value);
    return 0;
}
//混音器初始化和关闭混音器
void pop_noise_protection(snd_mixer_t *mixer, bool enable) {

    if (enable) 
    {
        const char *controls[] = 
        {
            "DAC LRCLK Select",      "0",  
            "DAC",                   "2",
            "DACL",                  "1",
            "DACR",                  "1",
            "EAR Mixer DACLEAR",     "1",
            "Earpiece Function",     "1",
            "Earpiece Mute",         "0", 
            "HPL EAR Sel",           "0",
            NULL
        };
        const char **ptr = controls;
        while (*ptr) 
        {
            const char *name = *ptr++;
            int value = atoi(*ptr++);
            if (enable) 
            { 
                set_control(mixer, name, value);
                usleep(5000); 
            }
        }
    }
    if (!enable) 
    {
        const char *shutdown_seq[] = 
        {
            "DACL",                  "0",
            "DACR",                  "0",
            "EAR Mixer DACLEAR",     "0",
            "EAR",                   "0",
            "Earpiece Function",     "0",
            "Earpiece Mute",         "1", 
            NULL
        };
        const char **p = shutdown_seq;
        while (*p) {
            set_control(mixer, *p++, atoi(*p++));
            usleep(5000);
        }
    }
}
static void open_mixer()
{
    if (snd_mixer_open(&mixer, 0) < 0 || 
        snd_mixer_attach(mixer, "default") < 0 ||
        snd_mixer_selem_register(mixer, NULL, NULL) < 0 ||
        snd_mixer_load(mixer) < 0) {
        fprintf(stderr, "Mixer init failed\n");
    }
}
static void close_mixer()
{
    snd_mixer_close(mixer);
}
void close_play_wav()
{
    status= false;
}

2.3主程序

int play_wav(const char *filename, int volume) 
{
    snd_pcm_t *pcm_handle = NULL;
    FILE *wav_file = NULL;
    WavHeader header;
    DataHeader data_header;
    uint8_t *buffer = NULL;
    int err = 0;
    int status = 1; // 播放状态控制变量

    // 混音器控制部分(根据实际需求实现)
    printf("volume =%d \n ",volume);
    open_mixer();
    set_control(mixer, "EAR", volume);
    pop_noise_protection(mixer, true);
    close_mixer();

     // 打开WAV文件 (使用二进制模式)
    if (!(wav_file = fopen(filename, "rb"))) {
        perror("无法打开文件");
        return -1;
    }
    // 读取基本WAV头
    if (fread(&header, 1, sizeof(WavHeader), wav_file) != sizeof(WavHeader) ||
        memcmp(header.riff, "RIFF", 4) != 0 ||
        memcmp(header.wave, "WAVE", 4) != 0 ||
        memcmp(header.fmt, "fmt ", 4) != 0) 
    {
        fprintf(stderr, "无效的 WAV 文件头\n");
        err = -2;
        goto cleanup;
    }
    
    // 跳过fmt块的扩展数据
    if (header.fmt_size > 16) {
        fseek(wav_file, header.fmt_size - 16, SEEK_CUR);
    }

   // 查找数据块
    while (1) {
        if (fread(&data_header, 1, sizeof(DataHeader), wav_file) != sizeof(DataHeader)) {
            fprintf(stderr, "找不到数据块或文件已结束\n");
            err = -3;
            goto cleanup;
        }
        
        if (memcmp(data_header.data_id, "data", 4) == 0) {
            break; // 找到数据块
        }
        
        // 跳过非数据块
        printf("跳过块: %.4s (大小: %u 字节)\n", data_header.data_id, data_header.data_size);
        fseek(wav_file, data_header.data_size, SEEK_CUR);
    }

    // 打印文件信息
    printf("文件: %s\n", filename);
    printf("声道数: %d\n", header.channels);
    printf("采样率: %d Hz\n", header.sample_rate);
    printf("位深: %d-bit\n", header.bits_per_sample);
    printf("音频大小: %u 字节\n", data_header.data_size);
    printf("持续时间: %.2f 秒\n", 
           (float)data_header.data_size / (header.sample_rate * header.channels * (header.bits_per_sample / 8)));

    // 打开PCM设备
    if ((err = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, 0)) < 0) 
    {
        fprintf(stderr, "设备打开失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    // 设置硬件参数
    snd_pcm_hw_params_t *hw_params;
    snd_pcm_hw_params_alloca(&hw_params);
    snd_pcm_hw_params_any(pcm_handle, hw_params);
    
    // 设置访问类型
    if ((err = snd_pcm_hw_params_set_access(pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        fprintf(stderr, "设置访问类型失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // 设置音频格式
    snd_pcm_format_t format;
    switch (header.bits_per_sample) {
        case 8:  format = SND_PCM_FORMAT_U8; break;
        case 16: format = SND_PCM_FORMAT_S16_LE; break;
        case 24: format = SND_PCM_FORMAT_S24_LE; break;
        case 32: format = SND_PCM_FORMAT_S32_LE; break;
        default:
            fprintf(stderr, "不支持的位深: %d\n", header.bits_per_sample);
            err = -4;
            goto cleanup;
    }
    
    if ((err = snd_pcm_hw_params_set_format(pcm_handle, hw_params, format)) < 0) {
        fprintf(stderr, "设置格式失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // 设置声道数
    if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, header.channels)) < 0) {
        fprintf(stderr, "设置声道数失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // 设置采样率
    unsigned int rate = header.sample_rate;
    int dir = 0;
    if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params, &rate, &dir)) < 0) {
        fprintf(stderr, "设置采样率失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    if (rate != header.sample_rate) {
        fprintf(stderr, "警告: 采样率 %d 不可用,使用 %d 替代\n", 
                header.sample_rate, rate);
    }
    
    // 设置缓冲区大小
    snd_pcm_uframes_t buffer_size = 4096;
    snd_pcm_uframes_t period_size = 1024;
    if ((err = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, &buffer_size)) < 0) {
        fprintf(stderr, "设置缓冲区大小失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    if ((err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &period_size, &dir)) < 0) {
        fprintf(stderr, "设置周期大小失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
    
    // 应用参数
    if ((err = snd_pcm_hw_params(pcm_handle, hw_params)) < 0) {
        fprintf(stderr, "应用参数失败: %s\n", snd_strerror(err));
        goto cleanup;
    }
 
    // 获取实际参数
    snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size);
    snd_pcm_hw_params_get_period_size(hw_params, &period_size, &dir);
    printf("ALSA缓冲区大小: %lu 帧\n", buffer_size);
    printf("ALSA周期大小: %lu 帧\n", period_size);
    
    // 计算帧大小和缓冲区
    const size_t frame_size = header.channels * (header.bits_per_sample / 8);
    const size_t buffer_bytes = period_size * frame_size;
    buffer = malloc(buffer_bytes);
    if (!buffer) {
        perror("内存分配失败");
        err = -5;
        goto cleanup;
    }
  

    // 播放音频数据
    size_t bytes_remaining = data_header.data_size;
    size_t bytes_read_total = 0;
    size_t total_frames_played = 0;
    // 添加调试计时器
    time_t start_time = time(NULL);

    // 混音器控制(在播放前设置POS External PA 消除喇叭pop音)
    open_mixer();
    set_control(mixer, "POS External PA", 1);
    close_mixer();

    while (status && bytes_remaining > 0) 
    {
        // 计算本次读取的大小
        size_t to_read = (bytes_remaining < buffer_bytes) ? bytes_remaining : buffer_bytes;
        size_t bytes_read = fread(buffer, 1, to_read, wav_file);
        
        if (bytes_read == 0) {
            if (feof(wav_file)) {
                printf("文件结束\n");
            } else {
                perror("读取文件错误");
            }
            break;
        }
        bytes_read_total += bytes_read;
        bytes_remaining -= bytes_read;
        
        snd_pcm_sframes_t frames_to_write = bytes_read / frame_size;
        snd_pcm_sframes_t frames_written = 0;
        
        while (frames_written < frames_to_write) 
        {
            snd_pcm_sframes_t result = snd_pcm_writei(
                pcm_handle, 
                buffer + frames_written * frame_size, 
                frames_to_write - frames_written
            );
            
            if (result == -EPIPE) {
                // 欠载处理
                fprintf(stderr, "欠载发生,准备设备\n");
                if ((err = snd_pcm_prepare(pcm_handle)) < 0) {
                    fprintf(stderr, "无法准备设备: %s\n", snd_strerror(err));
                    goto cleanup;
                }
            } else if (result == -ESTRPIPE) {
                // 设备挂起处理
                fprintf(stderr, "设备挂起,尝试恢复\n");
                while ((err = snd_pcm_resume(pcm_handle)) == -EAGAIN) {
                    sleep(1); // 等待直到恢复完成
                }
                if (err < 0) {
                    if ((err = snd_pcm_prepare(pcm_handle)) < 0) {
                        fprintf(stderr, "无法准备设备: %s\n", snd_strerror(err));
                        goto cleanup;
                    }
                }
            } else if (result < 0) {
                fprintf(stderr, "写入错误: %s\n", snd_strerror(result));
                err = result;
                goto cleanup;
            } else {
                frames_written += result;
                total_frames_played += result;
            }
        }
    }
    printf("播放完成,等待设备清空缓冲区...\n");
    if ((err = snd_pcm_drain(pcm_handle)) < 0) {
        fprintf(stderr, "清空缓冲区失败: %s\n", snd_strerror(err));
    }
    snd_pcm_close(pcm_handle);
    pcm_handle = NULL;
    
    // 计算播放时间
    time_t end_time = time(NULL);
    float duration = (float)(end_time - start_time);
    printf("实际播放时间: %.2f 秒\n", duration);
    printf("理论播放时间: %.2f 秒\n", 
           (float)data_header.data_size / (header.sample_rate * frame_size));
    printf("播放帧数: %zu\n", total_frames_played);

cleanup:
    // 混音器控制(在播放后设置POS External PA 消除喇叭pop音)
    open_mixer();
    set_control(mixer, "POS External PA", 0);
    pop_noise_protection(mixer,false);
    close_mixer();
    if (buffer) free(buffer);
    if (wav_file) fclose(wav_file);
    if (pcm_handle) {
        snd_pcm_drop(pcm_handle);
        snd_pcm_close(pcm_handle);
    }
    if (bytes_read_total < data_header.data_size) {
        fprintf(stderr, "警告: 只读取了 %zu/%u 字节音频数据\n", 
                bytes_read_total, data_header.data_size);
    } else if (bytes_read_total > data_header.data_size) {
        fprintf(stderr, "警告: 读取了 %zu/%u 字节 (多读了 %zu 字节)\n", 
                bytes_read_total, data_header.data_size, 
                bytes_read_total - data_header.data_size);
    } else {
        printf("成功读取全部音频数据: %u 字节\n", data_header.data_size);
    }
    return err;
}
void  test_audio(char * filename,int volume)
{
    status=true;
    play_wav(filename,volume);
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值