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);
}