当前位置: 首页 > news >正文

WAV文件结构和PCM数据转存WAV文件

Alsa相关命令

arecord 录制

可以使用arecord录制音频输入,保存为wav文件

arecord -f <格式> -r <采样率> -c <声道数> -d <时长> output.wav例如:
arecord -f S16_LE -r 16000 -c 1 -d 5 output.wav

常用参数说明

  • -f:指定音频格式(常用 S16_LE 表示 16 位 PCM,WAV 常用格式)
  • -r:采样率(如 44100 Hz,CD 音质)
  • -c:声道数(1 单声道,2 立体声)
  • -d:录制时长(单位:秒,省略则手动停止)

    aplay 播放

    再使用aplay来播放

    aplay output.wav

    实验环境:

    下面这个我是直接在Linux虚拟机上做的实验

    WAV文件结构

    可以参考这一篇:【音视频 | wav】wav音频文件格式详解——包含RIFF规范、完整的各个块解析、PCM转wav代码

    我有一个wav文件,除了LIST段的Chuck,我都做了简单的对照

    WAV文件头结构体:

    除音频数据外的信息都属于WAV文件头的部分

    先定义一下相关结构体,用于解析头部数据

    #include <stdio.h>
    #include <stdlib.h>
    #include <string>
    #include <vector>
    #include <algorithm> 
    #include <stddef.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <string.h>#include <alsa/asoundlib.h>
    #include <stdint.h>/*
    编译命令: gcc wav_tool.c -o AlsaTool -lasound -lstdc++
    使用方法:录音: ./AlsaTool播放: ./AlsaTool 文件名.wav
    */// 固定宽度类型定义
    typedef uint16_t  WORD;
    typedef uint32_t  DWORD;
    typedef int8_t    CHAR;// 结构体定义(按字节对齐)
    typedef struct 
    {WORD    wFormatTag;         // 编码格式(1=PCM)WORD    nChannels;          // 声道数DWORD   nSamplesPerSec;     // 采样率DWORD   nAvgBytesPerSec;    // 平均字节率WORD    nBlockAlign;        // 块对齐
    } __attribute__((packed)) WAVEFORMAT;typedef struct 
    {WAVEFORMAT wf;WORD       wBitsPerSample;  // 采样位数
    } __attribute__((packed)) PCMWAVEFORMAT;typedef struct 
    {CHAR    SubchunkID[4];      // 块标识("fact")DWORD   SubchunkSize;       // 块大小DWORD   dwSampleLength;     // 采样帧数
    } __attribute__((packed)) FACT_CHUNK;typedef struct 
    {CHAR    ChunkID[4];         // 标识("RIFF")DWORD   ChunkSize;          // 总大小(不含自身ID和大小)CHAR    Format[4];          // 格式("WAVE")
    } __attribute__((packed)) RIFF_CHUNK;typedef struct 
    {CHAR    SubchunkID[4];      // 标识("data")DWORD   SubchunkSize;       // 数据大小
    } __attribute__((packed)) DATA_CHUNK;// 完整WAV头结构体(RIFF + fmt块 + data块)
    typedef struct 
    {RIFF_CHUNK    riff;           // RIFF块(12字节)char          fmt_id[4];      // fmt块ID("fmt ",4字节)DWORD         fmt_size;       // fmt块大小(4字节)PCMWAVEFORMAT fmt;            // fmt块内容(24字节)DATA_CHUNK    data;           // data块(8字节)
    } __attribute__((packed)) WAV_HEADER;  // 总大小:12+4+4+24+8=52字节
    

    解析头部信息的函数

    根据上面的结构体,一个一个chuck来解析,适应有额外元素和LIST Chuck的情况

    通过参数传出解析出来的采样率,通道数,位深

    
    // 解析WAV文件头
    int ParseWav(const char* filename, unsigned int* sample_rate, unsigned short* channels, unsigned short* bits_per_sample) 
    {if (!sample_rate || !channels || !bits_per_sample) {fprintf(stderr, "参数错误:指针不能为空\n");return -1;}FILE* fp = fopen(filename, "rb");if (!fp) {perror("文件打开失败");return -1;}// 解析RIFF块RIFF_CHUNK riff;size_t read_size = fread(&riff, 1, sizeof(RIFF_CHUNK), fp);if (read_size != sizeof(RIFF_CHUNK)) {fprintf(stderr, "RIFF块读取失败(实际%zu字节,预期%zu字节)\n", read_size, sizeof(RIFF_CHUNK));fclose(fp);return -1;}printf("检测到的标识: ChunkID=%.4s, Format=%.4s\n", riff.ChunkID, riff.Format);if (memcmp(riff.ChunkID, "RIFF", 4) != 0 || memcmp(riff.Format, "WAVE", 4) != 0) {fprintf(stderr, "不是有效的WAV文件\n");fclose(fp);return -1;}// 查找并解析fmt块PCMWAVEFORMAT pcmFmt = {0};FACT_CHUNK fact = {0};int foundFmt = 0, foundFact = 0;CHAR subchunkID[4];DWORD subchunkSize;while (1) {if (fread(subchunkID, 1, 4, fp) != 4 || fread(&subchunkSize, sizeof(DWORD), 1, fp) != 1) {perror("子块信息读取失败");fclose(fp);return -1;}if (memcmp(subchunkID, "fmt ", 4) == 0) {if (subchunkSize < sizeof(PCMWAVEFORMAT)) {fprintf(stderr, "fmt块大小异常(%u字节 < %zu字节)\n", subchunkSize, sizeof(PCMWAVEFORMAT));fclose(fp);return -1;}if (fread(&pcmFmt, sizeof(PCMWAVEFORMAT), 1, fp) != 1) {perror("fmt块内容读取失败");fclose(fp);return -1;}if (subchunkSize > sizeof(PCMWAVEFORMAT)) {fseek(fp, subchunkSize - sizeof(PCMWAVEFORMAT), SEEK_CUR);}// 提取参数*sample_rate = pcmFmt.wf.nSamplesPerSec;*channels = pcmFmt.wf.nChannels;*bits_per_sample = pcmFmt.wBitsPerSample;foundFmt = 1;break;}else if (memcmp(subchunkID, "fact", 4) == 0) {foundFact = 1;memcpy(fact.SubchunkID, subchunkID, 4);fact.SubchunkSize = subchunkSize;if (subchunkSize >= sizeof(DWORD)) {fread(&fact.dwSampleLength, sizeof(DWORD), 1, fp);}if (subchunkSize > sizeof(DWORD)) {fseek(fp, subchunkSize - sizeof(DWORD), SEEK_CUR);}}else {fseek(fp, subchunkSize, SEEK_CUR);}if (ftell(fp) > (long)(riff.ChunkSize + 8)) {fprintf(stderr, "未找到fmt块\n");fclose(fp);return -1;}}// 解析data块DATA_CHUNK dataChunk = {0};int foundData = 0;while (1) {if (fread(dataChunk.SubchunkID, 1, 4, fp) != 4 || fread(&dataChunk.SubchunkSize, sizeof(DWORD), 1, fp) != 1) {perror("data块信息读取失败");fclose(fp);return -1;}if (memcmp(dataChunk.SubchunkID, "data", 4) == 0) {foundData = 1;break;} else if (memcmp(dataChunk.SubchunkID, "fact", 4) == 0 && !foundFact) {foundFact = 1;fact.SubchunkSize = dataChunk.SubchunkSize;if (dataChunk.SubchunkSize >= sizeof(DWORD)) {fread(&fact.dwSampleLength, sizeof(DWORD), 1, fp);}if (dataChunk.SubchunkSize > sizeof(DWORD)) {fseek(fp, dataChunk.SubchunkSize - sizeof(DWORD), SEEK_CUR);}}else {fseek(fp, dataChunk.SubchunkSize, SEEK_CUR);}if (ftell(fp) > (long)(riff.ChunkSize + 8)) {fprintf(stderr, "未找到data块\n");fclose(fp);return -1;}}// 打印头信息printf("===== WAV 文件头信息 =====\n");printf("RIFF 块:\n");printf("  标识: %.4s\n", riff.ChunkID);printf("  总大小: %u 字节(不含 RIFF 头)\n", riff.ChunkSize);printf("  格式: %.4s\n", riff.Format);printf("\nWAVEFORMAT:\n");printf("  编码格式: %u (%s)\n", pcmFmt.wf.wFormatTag,pcmFmt.wf.wFormatTag == 1 ? "WAVE_FORMAT_PCM" : "非 PCM");printf("  声道数: %u\n", pcmFmt.wf.nChannels);printf("  采样率: %u Hz\n", pcmFmt.wf.nSamplesPerSec);printf("  平均字节率: %u 字节/秒\n", pcmFmt.wf.nAvgBytesPerSec);printf("  块对齐: %u 字节\n", pcmFmt.wf.nBlockAlign);printf("\nPCMWAVEFORMAT:\n");printf("  采样位数: %u bits\n", pcmFmt.wBitsPerSample);if (foundFact) {printf("\nfact 块:\n");printf("  标识: %.4s\n", fact.SubchunkID);printf("  块大小: %u 字节\n", fact.SubchunkSize);printf("  总采样帧数: %u\n", fact.dwSampleLength);printf("  音频时长: %.2f 秒(基于采样帧)\n",(float)fact.dwSampleLength / pcmFmt.wf.nSamplesPerSec);}printf("\ndata 块:\n");printf("  标识: %.4s\n", dataChunk.SubchunkID);printf("  数据大小: %u 字节\n", dataChunk.SubchunkSize);if (!foundFact) {printf("  音频时长: %.2f 秒(估算)\n",(float)dataChunk.SubchunkSize / pcmFmt.wf.nAvgBytesPerSec);}fclose(fp);return 0;
    }

    设置声卡参数

    录制和播放都需要设置声卡

    
    // 设置PCM硬件参数
    static int set_hw_params(snd_pcm_t *pcmHandler, unsigned int sample_rate, unsigned short channels, unsigned short bits_per_sample) 
    {snd_pcm_hw_params_t *hwParams;int ret;int dir;snd_pcm_format_t pcm_format;// 映射采样位数到ALSA格式if (bits_per_sample == 8)pcm_format = SND_PCM_FORMAT_U8;else if (bits_per_sample == 16)pcm_format = SND_PCM_FORMAT_S16_LE;else if (bits_per_sample == 24)pcm_format = SND_PCM_FORMAT_S24_LE;else if (bits_per_sample == 32)pcm_format = SND_PCM_FORMAT_S32_LE;else{fprintf(stderr, "不支持的采样位数: %u bits\n", bits_per_sample);return -1;}snd_pcm_hw_params_malloc(&hwParams);do {ret = snd_pcm_hw_params_any(pcmHandler, hwParams);if (ret < 0) {fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(ret));break;}ret = snd_pcm_hw_params_set_access(pcmHandler, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED);if (ret < 0) {fprintf(stderr, "set_access error: %s\n", snd_strerror(ret));break;}ret = snd_pcm_hw_params_set_format(pcmHandler, hwParams, pcm_format);if (ret < 0) {fprintf(stderr, "set_format error: %s\n", snd_strerror(ret));break;}ret = snd_pcm_hw_params_set_channels(pcmHandler, hwParams, channels);if (ret < 0) {fprintf(stderr, "set_channels error: %s\n", snd_strerror(ret));break;}ret = snd_pcm_hw_params_set_rate_near(pcmHandler, hwParams, &sample_rate, &dir);if (ret < 0) {fprintf(stderr, "set_rate error: %s\n", snd_strerror(ret));break;}printf("实际设置的采样率: %u Hz\n", sample_rate);unsigned int period_time = 10000;  // 10ms周期ret = snd_pcm_hw_params_set_period_time_near(pcmHandler, hwParams, &period_time, &dir);if (ret < 0) {fprintf(stderr, "set_period_time error: %s\n", snd_strerror(ret));break;}ret = snd_pcm_hw_params(pcmHandler, hwParams);if (ret < 0) {fprintf(stderr, "apply hw_params error: %s\n", snd_strerror(ret));break;}snd_pcm_hw_params_free(hwParams);return 0;} while (0);snd_pcm_hw_params_free(hwParams);return -1;
    }

    Record 麦克风输入为WAV文件

    因为需要保存在标准的WAV文件中,所以需要先写入头部数据。

    写头部数据

    写入各个字段

    // 写入WAV文件头
    void write_wav_header(int fd, unsigned int sample_rate, unsigned short channels, unsigned short bits_per_sample, int duration) 
    {WAV_HEADER header = {0};DWORD byte_rate = sample_rate * channels * (bits_per_sample / 8);WORD block_align = channels * (bits_per_sample / 8);DWORD data_size = duration * byte_rate;  // 预估数据大小// 填充RIFF块memcpy(header.riff.ChunkID, "RIFF", 4);header.riff.ChunkSize = data_size + sizeof(WAV_HEADER) - 8;  // 总大小计算memcpy(header.riff.Format, "WAVE", 4);// 填充fmt块memcpy(header.fmt_id, "fmt ", 4);header.fmt_size = sizeof(PCMWAVEFORMAT);header.fmt.wf.wFormatTag = 1;  // PCM格式header.fmt.wf.nChannels = channels;header.fmt.wf.nSamplesPerSec = sample_rate;header.fmt.wf.nAvgBytesPerSec = byte_rate;header.fmt.wf.nBlockAlign = block_align;header.fmt.wBitsPerSample = bits_per_sample;// 填充data块memcpy(header.data.SubchunkID, "data", 4);header.data.SubchunkSize = data_size;// 写入完整头部write(fd, &header, sizeof(WAV_HEADER));
    }
    

    读声卡数据,存为WAV文件

    // 录音函数
    int RecordAudio(const char *filename, int duration) 
    {printf("Calling RecordAudio() filename = %s, duration = %d\n", filename, duration);unsigned int sample_rate = 48000;unsigned short channels = 2;unsigned short bits_per_sample = 16;snd_pcm_t *pcm_handle = NULL;snd_pcm_uframes_t period_size;unsigned char *buff = NULL;unsigned int buf_bytes;int ret = -1;int fd = -1;snd_pcm_hw_params_t *temp_hw_params = NULL;do {// 打开PCM捕获设备ret = snd_pcm_open(&pcm_handle, "hw:0,0", SND_PCM_STREAM_CAPTURE, 0);if (ret < 0) {printf("Open PCM device error: %s\n", snd_strerror(ret));break;}// 设置硬件参数if (set_hw_params(pcm_handle, sample_rate, channels, bits_per_sample) < 0) {snd_pcm_close(pcm_handle);break;}// 获取周期大小snd_pcm_hw_params_malloc(&temp_hw_params);ret = snd_pcm_hw_params_current(pcm_handle, temp_hw_params);if (ret < 0) {fprintf(stderr, "get current hw_params error: %s\n", snd_strerror(ret));break;}snd_pcm_hw_params_get_period_size(temp_hw_params, &period_size, NULL);snd_pcm_hw_params_free(temp_hw_params);// 分配缓冲区buf_bytes = period_size * channels * (bits_per_sample / 8);buff = (unsigned char *)malloc(buf_bytes);if (buff == NULL) {printf("Malloc buffer failed\n");break;}// 打开文件fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);if (fd < 0) {perror("Open file failed");break;}// 写入WAV头write_wav_header(fd, sample_rate, channels, bits_per_sample, duration);// 录音循环unsigned int total_periods = (duration * sample_rate) / period_size;for (unsigned int i = 0; i < total_periods; i++) {ret = snd_pcm_readi(pcm_handle, buff, period_size);if (ret < 0) {fprintf(stderr, "snd_pcm_readi error: %s\n", snd_strerror(ret));ret = snd_pcm_recover(pcm_handle, ret, 0);if (ret < 0) {fprintf(stderr, "Recover PCM failed: %s\n", snd_strerror(ret));break;}continue;}int write_len = ret * channels * (bits_per_sample / 8);if (write(fd, buff, write_len) != write_len) {perror("Write file failed");break;}}// 修正文件头if (fd != -1 && ret >= 0) {off_t total_file_size = lseek(fd, 0, SEEK_END);DWORD real_data_size = total_file_size - sizeof(WAV_HEADER);DWORD real_riff_size = real_data_size + sizeof(WAV_HEADER) - 8;lseek(fd, offsetof(WAV_HEADER, data.SubchunkSize), SEEK_SET);write(fd, &real_data_size, sizeof(real_data_size));lseek(fd, offsetof(WAV_HEADER, riff.ChunkSize), SEEK_SET);write(fd, &real_riff_size, sizeof(real_riff_size));printf("录音完成,实际数据大小: %u 字节\n", real_data_size);}if (ret >= 0) printf("Recording completed successfully.\n");ret = 0;} while (0);// 资源清理if (fd != -1) close(fd);if (buff != NULL) free(buff);if (pcm_handle != NULL) snd_pcm_close(pcm_handle);return ret;
    }

    播放WAV文件

    1. 先读取WAV文件头部信息,读出采样率,通道数和位深
    2. 然后根据读出的数据,设置声卡
    3. 然后读取文件数据,写到声卡中播放
    
    // 播放函数
    int PlayWavFile(const char * cstrFileName)
    {printf("Start Play Wav File %s\n", cstrFileName);// 解析WAV参数unsigned int sample_rate;unsigned short channels, bits_per_sample;if (ParseWav(cstrFileName, &sample_rate, &channels, &bits_per_sample) != 0){fprintf(stderr, "WAV文件解析失败,无法播放\n");return -1;}// 打开WAV文件FILE *file = fopen(cstrFileName, "rb");if (!file) {perror("fopen");return 1;}// 定位到音频数据(跳过WAV头)fseek(file, sizeof(WAV_HEADER), SEEK_SET);snd_pcm_t *handle;snd_pcm_hw_params_t *params;snd_pcm_uframes_t period_size;int err;// 打开播放设备err = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);if (err < 0) {fprintf(stderr, "cannot open audio device %s (%s)\n", "default", snd_strerror(err));fclose(file);return 1;}// 设置播放参数snd_pcm_hw_params_alloca(&params);err = snd_pcm_hw_params_any(handle, params);if (err < 0){fprintf(stderr, "snd_pcm_hw_params_any error: %s\n", snd_strerror(err));snd_pcm_close(handle);fclose(file);return 1;}if (set_hw_params(handle, sample_rate, channels, bits_per_sample) < 0){snd_pcm_close(handle);fclose(file);return 1;}// 获取周期大小snd_pcm_hw_params_get_period_size(params, &period_size, NULL);unsigned int buf_bytes = period_size * channels * (bits_per_sample / 8);unsigned char *buffer = (unsigned char *)malloc(buf_bytes);if (!buffer) {fprintf(stderr, "malloc failed\n");snd_pcm_close(handle);fclose(file);return 1;}// 播放循环while (1) {size_t read_bytes = fread(buffer, 1, buf_bytes, file);if (read_bytes == 0) {if (feof(file)) {printf("播放完成(已到文件末尾)\n");break;} else {fprintf(stderr, "fread failed\n");break;}}snd_pcm_uframes_t play_frames = read_bytes / (channels * (bits_per_sample / 8));err = snd_pcm_writei(handle, buffer, play_frames);if (err < 0) {fprintf(stderr, "write error: %s\n", snd_strerror(err));err = snd_pcm_recover(handle, err, 0);if (err < 0) {fprintf(stderr, "Recover PCM failed: %s\n", snd_strerror(err));break;}continue;}}// 清理资源free(buffer);snd_pcm_drain(handle);snd_pcm_close(handle);fclose(file);return 0;
    }
    

    调用函数

    // 检查是否为WAV文件
    bool IsAudioFile(const std::string &strFileName)
    {size_t pos = strFileName.rfind('.');if (pos == std::string::npos) return false;std::string extension = strFileName.substr(pos);std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);return extension == ".wav";
    }int main(int argc, char *argv[]) 
    {if (argc == 1) {RecordAudio("Record.wav", 10);  // 录制10秒return 0;} else if (argc == 2) {char cstrArg[256] = {0};strncpy(cstrArg, argv[1], sizeof(cstrArg)-1);if (IsAudioFile(cstrArg)) {PlayWavFile(cstrArg);}else{fprintf(stderr, "仅支持WAV格式文件\n");ShowHelpInfo();}} else {ShowHelpInfo();}return 0;
    }

    http://www.dtcms.com/a/475126.html

    相关文章:

  • 美妆网站开发规划书有没有帮忙做问卷调查的网站
  • 绵阳手机网站建设分公司注册流程及需要的材料
  • 手工活接单在家做有正规网站吗湖南省郴州市天气预报
  • Linux中重定向举例!!
  • 建设厅八大员在哪个网站查询视频拍摄器材
  • 济南网站建设推广服务网站建设的目的及目标
  • php 网站开发缓存有那几种关键词挖掘ppt
  • 三、从 MinIO 存储到 OCR 提取,再到向量索引生成
  • 适合设计师看的设计网站网站建设jnlongji
  • 测试题-2
  • 萝岗哪家网站建设好工作态度
  • 企业网站首页设计公司网站规划与建设心得
  • 东莞连衣裙 东莞网站建设国内常见响应式网站
  • 四川省住房与城乡建设厅网站管网crm软件系统的构成包括
  • 江阴建设局官方网站六安哪里有做推广网站
  • 秦皇岛网站开发哪家好室内设计导航
  • 网站怎么上传代码吗六安市城市建设档案馆网站
  • 什么都不会怎么做网站郑州商城网站建设
  • 五种热门编程语言(Java/C/Python/PHP/C#/C++)在当代软件开发中的综合应用与趋势分析
  • 微淘客网站建设wordpress输入密码查看内容
  • 网站系统修改不了怎么回事杭州网站建设公司代理加盟
  • PowerShell 基础文本处理语法教程
  • 计算机组成原理---计算机系统概述
  • 网站建设设计开发公司云免网站空间
  • wordpress网站特别卡石家庄求职信息网
  • 好的网站制作网站简述企业注册的流程
  • GESP C++等级认证三级14-原码反码补码2-1
  • HashMap之线程安全问题
  • 网站安全建设方案报告gta5显示网站建设中
  • Python学习AI大模型:零基础快速入门指南