音频demo:将PCM数据封装成wav格式文件(不依赖第三方库)

1、README

a. 编译

编译时需要编译成32位的可执行程序(int需要指定为4字节),所以如果需要在64位主机上运行该程序,编译时就需要在Makefile上添加-m32选项(默认已加),如果运行的主机是32位的则将Makefile上的编译选项-m32移除。

$ make  		# 或者交叉编译: make CC=your-crosscompile-gcc
$ ls -l pcm2wav # 编译生成的可执行程序
b. 运行
$ # 查看帮助信息
$ ./pcm2wav -h
$ ./pcm2wav --help
$ 
$ # 转化
$ ./pcm2wav -i ./audio/test_8000_16_1.pcm -r 8000 -b 16 -c 1 -o ./out_8000_16_1.wav
$ ./pcm2wav --input_pcmfile=./audio/test_44100_16_2.pcm --sample_rate=44100 --sample_bits=16 --channels=2 --output_wavfile=./out_44100_16_2.wav
c. 参考文章
  • PCM音频数据 - 简书

  • wav文件格式分析与详解 - nigaopeng - 博客园

d. demo结构
$ tree
.
├── audio
│   ├── out_44100_16_2.wav
│   ├── out_8000_16_1.wav
│   ├── test_44100_16_2.pcm
│   └── test_8000_16_1.pcm
├── docs
│   ├── PCM音频数据 - 简书.mhtml
│   ├── WAV文件格式分析.pdf
│   └── wav文件格式分析与详解 - nigaopeng - 博客园.mhtml
├── main.c
├── Makefile
├── README.md
└── wav_format.h

2、主要代码片段

wav_format.h
#ifndef __WAV_FORMAT__
#define __WAV_FORMAT__#ifndef WORD
#define WORD unsigned short
#endif#ifndef DWORD
#define DWORD unsigned int
#endiftypedef struct _RIFF_HEADER
{char szRiffID[4]; 		/* 固定:'R','I','F','F' */DWORD dwRiffSize; 		/* 填充数值 = wav文件大小 - "RIFF"4个字节 - dwRiffSize大小4个字节 = wav文件大小 - 8个字节 */char szRiffFormat[4]; 	/* 固定:'W','A','V','E' */
}RIFF_HEADER; 	/* 12 Bytes */typedef struct _WAVE_FORMAT
{WORD wFormatTag; 		/* PCM: 0x0001 */WORD wChannels; 		/* 声道数: 1/2 */DWORD dwSamplesPerSec; 	/* 采样率:8000/16000/441000.. */DWORD dwAvgBytesPerSec; /* 每秒所需字节数,采样频率 */WORD wBlockAlign; 		/* 数据块对齐单位(每个采样需要的字节数,例:16位、单通道 = 16 / 8 * 1 = 2) */WORD wBitsPerSample; 	/* 采样位数:8/16... *///WORD wAddtional; 		/* 2个字节附加信息,非必须,由FMT_BLOCK的dwFmtSize设定 */
}WAVE_FORMAT;
typedef struct _FMT_BLOCK
{char szFmtID[4]; 		/* 固定:'f','m','t',' ' */DWORD dwFmtSize; 		/* WAVE_FORMAT的大小:16或18,18代表包含2个字节附加信息 */WAVE_FORMAT wavFormat; 	/* 音频数据格式 */
}FMT_BLOCK;  	/* 24 Bytes 如果不需要WAVE_FORMAT为16字节 *//* 可选的CHUNK,一些软件会生成 */
typedef struct _FACT_BLOCK
{char szFactID[4]; 		/* 固定:'f','a','c','t' */DWORD dwFactSize;char factData[4];
}FACT_BLOCK; 	/* 12 Bytes */typedef struct _DATA_BLOCK
{char szDataID[4]; 		/* 固定:'d','a','t','a' */DWORD dwDataSize; 		/* PCM或其他音频数据的大小 */
}DATA_BLOCK; 	/* 8 Bytes */#endif
main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>#include "wav_format.h"#define  BUF_SIZE 	1024void print_usage(const char *process)
{printf("examples: \n""\t %s -h\n""\t %s --help\n""\t %s -i ./audio/test_8000_16_1.pcm -r 8000 -b 16 -c 1 -o ./out_8000_16_1.wav\n""\t %s --input_pcmfile=./audio/test_44100_16_2.pcm --sample_rate=44100 --sample_bits=16 --channels=2 --output_wavfile=./out_44100_16_2.wav\n",process, process, process, process);
}int main(int argc, char *argv[])
{/* WAV文件的CHUNK */RIFF_HEADER riff_header;FMT_BLOCK fmt_block;DATA_BLOCK data_block;/* 输入/输出文件 */FILE *fpPcm = NULL;FILE *fpWav = NULL;char pcmFileName[128] = {0};char wavFileName[128] = {0};/* 读取/写入文件用到的中间变量 */char *buf = (char *)malloc(BUF_SIZE);unsigned int readcnt = 0;unsigned long pcmFileSize = 0;/* PCM参数 */unsigned int SampleRate = 0; // 采样率unsigned short SampleBits = 0; // 采样位数unsigned short Channels = 0; // 声道数if(argc <= 1){print_usage(argv[0]);return -1;}/* 解析命令行参数  -- 开始 */char option = 0;int option_index = 0;char *short_options = "hi:r:b:c:o:";struct option long_options[] ={{"help",          no_argument,       NULL, 'h'},{"input_pcmfile", required_argument, NULL, 'i'},{"sample_rate",   required_argument, NULL, 'r'},{"sample_bits",   required_argument, NULL, 'b'},{"channels",      required_argument, NULL, 'c'},{"output_wavfile",required_argument, NULL, 'o'},{NULL,            0,                 NULL,  0 },};while((option = getopt_long_only(argc, argv, short_options, long_options, &option_index)) != -1){switch(option){case 'h':print_usage(argv[0]);return 0;case 'i':strncpy(pcmFileName, optarg, 128);break;case 'o':strncpy(wavFileName, optarg, 128);break;case 'r':SampleRate = atoi(optarg);break;case 'c':Channels = atoi(optarg);break;case 'b':SampleBits = atoi(optarg);break;defalut:break;}}printf("\n**************************************\n""input: \n""\t file name: %s\n""\t sample rate: %d Hz\n""\t sample bits: %d bits\n""\t channels: %d\n""\t bits per second: %d bps\n""output: \n""\t file name: %s\n""**************************************\n\n",pcmFileName, SampleRate, SampleBits, Channels, SampleRate*SampleBits*Channels, wavFileName);/* 解析命令行参数  -- 结束 *//* 先打开输入/输出文件,读取输入文件的大小 */fpPcm = fopen(pcmFileName, "rb+");if(fpPcm == NULL){char errMsg[128] = {0};snprintf(errMsg, 128, "open file(%s) error", pcmFileName);perror(errMsg);return -1;}fseek(fpPcm, 0, SEEK_END);pcmFileSize = ftell(fpPcm);fseek(fpPcm, 0, SEEK_SET);fpWav = fopen(wavFileName, "wb+");if(fpWav == NULL){char errMsg[128] = {0};snprintf(errMsg, 128, "open file(%s) error", wavFileName);perror(errMsg);return -1;}/* 填充WAV的CHUNK */memset(&riff_header, 0, sizeof(RIFF_HEADER));strncpy(riff_header.szRiffID, "RIFF", 4);     // 固定// WAV文件数据大小 = wav文件大小 - "RIFF"4个字节 - dwRiffSize大小4个字节 = wav文件大小 - 8个字节riff_header.dwRiffSize = pcmFileSize + sizeof(RIFF_HEADER) + sizeof(FMT_BLOCK) + sizeof(DATA_BLOCK) - strlen("RIFF") - sizeof(riff_header.dwRiffSize);strncpy(riff_header.szRiffFormat, "WAVE", 4); // 与后面的"fmt "为固定格式fwrite(&riff_header, sizeof(RIFF_HEADER), 1, fpWav);memset(&fmt_block, 0, sizeof(FMT_BLOCK));strncpy(fmt_block.szFmtID, "fmt ", 4); 											// 固定fmt_block.dwFmtSize = sizeof(fmt_block.wavFormat); 								// 16或18,有些软件添加2字节附加信息,这里不需要fmt_block.wavFormat.wFormatTag = 0x0001; 										// 代表PCM数据fmt_block.wavFormat.wChannels = Channels; 										// 1: 单声道; 2: 双声道fmt_block.wavFormat.dwSamplesPerSec = SampleRate; 								// 采样率fmt_block.wavFormat.dwAvgBytesPerSec = SampleRate *  SampleBits / 8 * Channels; // 每秒所需字节数fmt_block.wavFormat.wBlockAlign = SampleBits / 8 * Channels; 					// 数据块对齐单位fmt_block.wavFormat.wBitsPerSample = SampleBits; 								// 每个采样需要的bit数fwrite(&fmt_block, sizeof(FMT_BLOCK), 1, fpWav);memset(&data_block, 0, sizeof(DATA_BLOCK));strncpy(data_block.szDataID, "data", 4); // 固定data_block.dwDataSize = pcmFileSize;     // PCM数据的大小fwrite(&data_block, sizeof(DATA_BLOCK), 1, fpWav);while(!feof(fpPcm)){readcnt = fread(buf, 1, BUF_SIZE, fpPcm);fwrite(buf, 1, readcnt, fpWav);}free(buf);fclose(fpPcm);fclose(fpWav);printf("\n\033[32mPCM to WAV Success!\033[0m\n");return 0;
}

3、demo下载地址(任选一个)

  • https://download.csdn.net/download/weixin_44498318/89524611

  • https://gitee.com/linriming/audio_pcm2wav.git

  • https://github.com/linriming20/audio_pcm2wav.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/43422.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

吴恩达机器学习笔记2.1 - 什么是机器学习

吴恩达机器学习笔记2.1 - 什么是机器学习 最早的机器学习 1959年&#xff0c;亚瑟塞缪尔(Arthur Samuel)将机器学习定义为“Field of study that gives computers the ability to learn without being explicitly programmed”&#xff08;无需编程即可学习的研究领域&#xf…

ROS中不同文件之间的引用小结

在比较大的一些程序中&#xff0c;往往会涉及到一些不同模块的调用&#xff0c;如果这些东西放在一个.cpp文件内&#xff0c;这个文件会变的特别长&#xff0c;因此会使用多个文件互相引用。那么如何在ROS下进行这种不同文件下的引用呢&#xff0c;根据最近所学&#xff0c;简单…

tomcat 安装和优化

tomcatat tomcat和http一样&#xff0c;都是用来处理动态页面的 tomcat也可以作为web服务器&#xff0c;开源的 php.php tomcat.jsp nginx.html tomcat使用java代码写的程序&#xff0c;运行的是java的web服务程序 tomcat的特点和功能&#xff1a; 1、servlet容器&…

MySQL——第一次作业

部署MySQL 8.0环境 1&#xff0c;删除之前存在的MySQL程序 控制面板删除 2&#xff0c;删除完成后下载MySQL 官网&#xff1a; https://www.mysql.com 在window下下载MSI版本 3&#xff0c;自定义安装 4&#xff0c;配置环境变量 1&#xff0c;系统高级系统设置 2&#xff…

Android面试题汇总-RecyclerView、Fragment、WebView、性能优化等

一、RecyclerView 1、RecyclerView的多级缓存机制,每一级缓存具体作用是什么,分别在什么场景下会用到哪些缓存 RecyclerView的多级缓存机制是为了提高滚动和数据更新的效率而设计的。每一级缓存都有其特定的作用和使用场景。以下是各级缓存的作用和它们的使用场景&#xff1a…

子序列问题

目录 最长递增子序列 摆动序列 最长递增子序列的个数 最长数对链 最长定差子序列 最长的斐波那契子序列的长度 最长等差数列 等差数列划分II-子序列 声明&#xff1a;接下来主要使用动态规划来解决问题&#xff01;&#xff01;&#xff01; 最长递增子序列 题目 思路…

Python 中,NumPy 的数组操作

应用场景&#xff1a; 数据分析和预处理&#xff1a;对大量的数据进行清洗、筛选、转换等操作&#xff0c;例如去除异常值、标准化数据等。 图像和信号处理&#xff1a;处理图像的像素值、音频或视频的信号数据。 机器学习和深度学习&#xff1a;对特征数据进行操作&#xff0c…

如何通过文件分发系统,实现能源电力企业文件的安全分发流转?

随着企业业务的快速发展&#xff0c;能源电力企业会在全国乃至全球&#xff0c;设立总部-分部-办事处/网点等多层级的结构&#xff0c;因此会涉及自动化的文件分发的业务场景。文件分发系统是一种将文件从一个地方自动传输到多个接收者的过程&#xff0c;可以提高工作效率&…

香港优才计划多少分获批成功率高?一文看懂各分数段获批情况!

有留意香港优才计划的朋友&#xff0c;应该都了解过&#xff0c;申请优才计划采用打分制&#xff0c;得分多少与最终获批有密不可分的关系。但有一点要提前清楚&#xff0c;申请优才不是得分越高就一定能获批&#xff0c;也不是得分低就一定没希望。 香港优才计划能否获批成功…

正确理解驱动电流与驱动速度

本文主要阐述了在驱动芯片中表征驱动能力的关键参数&#xff1a;驱动电流和驱动时间的关系&#xff0c;并且通过实验解释了如何正确理解这些参数在实际应用中的表现。 驱动芯片概述 功率器件如MOSFET、IGBT需要驱动电路的配合从而得以正常地工作。图1显示了一个驱动芯片驱动一…

迅狐抖音机构号授权矩阵系统源码

在数字化营销的浪潮中&#xff0c;抖音以其独特的短视频形式迅速崛起&#xff0c;成为品牌传播和用户互动的重要平台。迅狐抖音机构号授权矩阵系统源码作为一项创新技术&#xff0c;为品牌在抖音上的深度运营提供了强大支持。 迅狐抖音机构号授权矩阵系统源码简介 迅狐抖音机…

新版Android Studio中设置gradle的JDK版本

旧版android studio 在旧版&#xff08;具体哪个版本号之前搞不清了&#xff09;中设置JDK版本在>File——>Project Structure——>SDK location——>Gradle Setting——>Gradle SDK 新版android studio 某次更新后发现SDK location下找不到Gradle Setting选项…

单机版k8s搭建

环境配置&#xff1a; 关闭防火墙和交换内存 systemctl stop ufwsudo swapoff -a安装Docker 更新安装包 sudo apt-get update安装依赖 sudo apt install -y curl gnupg2 software-properties-common apt-transport-https ca-certificates获取证书 curl -fsSL http://mirrors.a…

通过Vxlan实现数据中心互联有感

随着企业的发展&#xff0c;为满足跨地域运营、用户接入、异地灾备等场景&#xff0c;越来越多的企业通常在多地域部署多个数据中心。 数据中心互联DCl(Data Center Interconnection)是不同数据中心VM之间互相通信的一种解决方案使用VXLAN、BGP EVPN等技术&#xff0c;使数据中…

uniapp+uview实现手机端上传照片带水印(保姆级全过程)

目录 前言&#xff1a;实现思路 步骤一、在界面使用uview的u-upload组件、放置canvas标签 步骤二、在afterRead方法中获取照片url&#xff0c;并创建画布生成水印&#xff0c;再将生成水印的照片上传到服务器 1、afterRead方法 2、照片加水印的方法 3、上传照片至服务器 …

zookeeper加入开机启动项

Windows的任务计划程序&#xff08;Task Scheduler&#xff09;是一个强大的工具&#xff0c;允许你安排程序在特定时间自动运行&#xff0c;包括开机时。 打开任务计划程序&#xff1a; 按下Win R键&#xff0c;打开“运行”对话框。输入taskschd.msc并回车&#xff0c;打开…

Python编写网络嗅探器程序捕获和显示IP数据包的头部信息

Python编写网络嗅探器程序捕获和显示IP数据包的头部信息 抓取网络数据包并解析其中的IP首部信息&#xff0c;并通过GUI界面显示解析结果。程序展示了如何使用Python的socket和ctypes库来捕获和解析网络数据包,并使用Tkinter创建一个简单的GUI界面来显示捕获到的IP头部信息。这…

日志服务SLS入门指南

日志服务SLS入门指南 什么是日志服务SLSNginx日志采集部署Nginx创建Logstore接入数据 数据脱敏创建脱敏Logstore数据加工 告警设置添加告警规则查看告警 写在最后 什么是日志服务SLS 在说到日志服务SLS之前&#xff0c;首先了解一下什么是日志服务SLS&#xff1f;日志服务SLS是…

PHP项目中的前端页面随意点击卡片后会重定向到首页或登录页

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

pycharm使用micropython

一、打开设置 2、搜索micropython、安装、重启 3、第5步需要设置成你插的电脑USB口&#xff0c;一个一个试 4、 5、 6、OK