目录
一、安装语音库
bash
sudo apt update
apt-get install libasound2-dev
二、生成音频文件
bash
# 文字生成 MP3
网地:https://www.text-to-speech.cn/
# MP3 转 WAV
网址:https://www.aconvert.com/cn/audio/mp3-to-wav/
三、语音播放代码
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/asoundlib.h>
#pragma pack(1)
struct tagWavHeader
{
char riff[4]; // RIFF标志
int chunk_size; // 文件大小
char type[4]; // 格式类型(wave)
char fmt[4]; // "fmt"
int subchunk_size; // sizeof(wave format matex)
short audio_format; // 音频格式
short channel_nums; // 声道数
int sample_rate; // 采样率
int byte_rate; // 比特率
short block_align; // 块对齐
short bits_per_sample; // 每个采样点的位数
char data[4]; // "data"
int data_size; // 音频数据的大小
};
#pragma pack()
void play_volume_set(long volume) {
snd_mixer_t* handle;
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, "default");
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
snd_mixer_selem_id_t* sid;
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, "Master");
long min = 0, max = 0;
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);
snd_mixer_close(handle);
}
int play_wav_file(const char* wav_file_path) {
if (access(wav_file_path, F_OK) != 0) {
printf("audio file (%s) not exist.", wav_path);
return -1001;
}
FILE* fp = fopen(wav_file_path, "r+b");
if (fp == NULL) {
perror("\n fopen() failed: ");
return -1002;
}
struct tagWavHeader wav_header;
memset(&wav_header, 0, sizeof(wav_header));
int rc = fread(&wav_header, 1, sizeof(wav_header), fp);
printf("\n wav_header_size = %d", rc);
if (rc == 0) {
fclose(fp);
return -1003;
}
snd_pcm_t* dev_handle;
print_audio_info(wav_header);
rc = snd_pcm_open(&dev_handle, "default", SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
perror("\n snd_pcm_open() failed: ");
fclose(fp);
return -1004;
}
// 初始化结构体
snd_pcm_hw_params_t* dev_params;
snd_pcm_hw_params_alloca(&dev_params);
rc = snd_pcm_hw_params_any(dev_handle, dev_params);
if (rc < 0) {
perror("\n snd_pcm_hw_params_any() failed: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1005;
}
// 初始化参数权限
rc = snd_pcm_hw_params_set_access(dev_handle, dev_params, SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0) {
perror("\n snd_pcm_hw_params_set_access() failed: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1006;
}
// 设置采样的位数
int bit = wav_header.bits_per_sample;
switch (bit / 8) {
case 1:
snd_pcm_hw_params_set_format(dev_handle, dev_params, SND_PCM_FORMAT_U8);
break;
case 2:
snd_pcm_hw_params_set_format(dev_handle, dev_params, SND_PCM_FORMAT_S16_LE);
break;
case 3:
snd_pcm_hw_params_set_format(dev_handle, dev_params, SND_PCM_FORMAT_S24_LE);
break;
}
// 设置声道播放(1表示单声>道,2表示立体声)
int channel = wav_header.channel_nums;
rc = snd_pcm_hw_params_set_channels(dev_handle, dev_params, channel);
if (rc < 0) {
perror("\n snd_pcm_hw_params_set_channels() failed: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1007;
}
// 设置语音的播放频率
int frequency = wav_header.sample_rate;
int dir = 0; unsigned int freq = frequency;
rc = snd_pcm_hw_params_set_rate_near(dev_handle, dev_params, &freq, &dir);
if (rc < 0) {
perror("\n snd_pcm_hw_params_set_rate_near() failied: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1008;
}
// 更新播放参数到设备
rc = snd_pcm_hw_params(dev_handle, dev_params);
if (rc < 0) {
perror("\n snd_pcm_hw_params() failed: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1009;
}
// 获取播放周期长度
snd_pcm_uframes_t frames = 0;
rc = snd_pcm_hw_params_get_period_size(dev_params, &frames, &dir);
if (rc < 0) {
perror("\n snd_pcm_hw_params_get_period_size() failed: ");
snd_pcm_close(dev_handle);
fclose(fp);
return -1010;
}
// 定位到音频的数据区
fseek(fp, 58, SEEK_SET);
snd_pcm_sframes_t frame_num = 0;
int size = frames * wav_header.block_align;
char* buffer = (char*)malloc(size);
while (true) {
memset(buffer, 0, size);
int rv = fread(buffer, 1, size, fp);
if (rv == 0) {
printf("\n end of wav audio file");
break;
}
// 下面开始写音频数据到 PCM 设备上进行播放
while ((frame_num = snd_pcm_writei(dev_handle, buffer, frames)) < 0) {
usleep(2000);
if (frame_num == -EPIPE) {
// EPIPE means underrun
fprintf(stderr, "underrun occurred \n");
// 完成参数设置,准备好设备
snd_pcm_prepare(dev_handle);
} else if (frame_num < 0) {
fprintf(stderr, "snd_pcm_writei() failed: %s\n", snd_strerror(frame_num));
}
}
}
snd_pcm_drain(dev_handle);
snd_pcm_close(dev_handle);
free(buffer);
fclose(fp);
return 0;
}