C代码创建多通道WAV音频文件
算法调试经常需要在PC端进行仿真输出,涉及到多通道,或者每一级算法的输出对比,这时需要多通道的WAV文件生成
代码
这里给出的是默认16bit单点数据位长,可以自己修改下,改成自己想要的位长
/**
* @file wav_file_opt.c
*
* @date 2022年07月25日 15:36:57 星期一
*
* @author aron566
*
* @copyright Copyright (c) 2021 aron566 <aron566@163.com>.
*
* @brief wav文件建立接口.
*
* @details None.
*
* @version V1.0
*/
/** Includes -----------------------------------------------------------------*/
/* Private includes ----------------------------------------------------------*/
#include "wav_file_opt.h"
/** Use C compiler -----------------------------------------------------------*/
#ifdef __cplusplus ///< use C compiler
extern "C" {
#endif
/** Private macros -----------------------------------------------------------*/
/** Private typedef ----------------------------------------------------------*/
const static char *Err_Str[81] = {
[EPERM] = "不允许执行该操作",
[ENOENT] = "没有此文件或目录",
[ESRCH] = "没有此进程",
[EINTR] = "函数中断",
[EIO] = "I/O 错误",
[ENXIO] = "没有此设备或地址",
[E2BIG] = "参数列表太长",
[ENOEXEC] = "执行格式错误",
[EBADF] = "文件编号错误",
[ECHILD] = "没有生成的进程",
[EAGAIN] = "没有更多进程、没有足够内存或达到最大嵌套级别",
[ENOMEM] = "没有足够内存",
[EACCES] = "权限被拒绝",
[EFAULT] = "地址错误",
[EBUSY] = "设备或资源忙碌",
[EEXIST] = "文件已存在",
[EXDEV] = "跨设备链接",
[ENODEV] = "没有此设备",
[ENOTDIR] = "不是目录",
[EISDIR] = "是目录",
[EINVAL] = "参数无效",
[ENFILE] = "系统中打开的文件太多",
[EMFILE] = "打开的文件太多",
[ENOTTY] = "不适当的 I/O 控制操作",
[EFBIG] = "文件太大",
[ENOSPC] = "设备上没有剩余空间",
[ESPIPE] = "搜寻无效",
[EROFS] = "只读文件系统",
[EMLINK] = "链接太多",
[EPIPE] = "管道损坏",
[EDOM] = "数学参数",
[ERANGE] = "结果太大",
[EDEADLK] = "会发生资源死锁",
[EDEADLOCK] = "与 EDEADLK 相同,以便与早期的 Microsoft C 版本兼容",
[ENAMETOOLONG] = "文件名太长",
[ENOLCK] = "无可用锁",
[ENOSYS] = "函数不受支持",
[ENOTEMPTY] = "目录不为空",
[EILSEQ] = "非法字节序列",
[STRUNCATE] = "字符串被截断",
};
/** Private constants --------------------------------------------------------*/
/** Public variables ---------------------------------------------------------*/
/** Private variables --------------------------------------------------------*/
const static WAVFILEHEADER_Typedef_t wav_file_header =
{
.RiffName = {'R','I','F','F'},
.nRiffLength = 44,
.WavName = {'W', 'A', 'V', 'E'},
.FmtName = {'f', 'm', 't', ' '},
.nFmtLength = 16,
.nAudioFormat = 1,
.nChannleNumber = 2,
.nSampleRate = 16000,
.nBytesPerSecond = 16000 * 2 * 16 / 8,
.nBytesPerSample = 4,
.nBitsPerSample = 16,
.DATANAME = {'d', 'a', 't', 'a'},
.nDataLength = 0,
};
/** Private function prototypes ----------------------------------------------*/
/** Private user code --------------------------------------------------------*/
/** Private application code -------------------------------------------------*/
/*******************************************************************************
*
* Static code
*
********************************************************************************
*/
/** Public application code --------------------------------------------------*/
/*******************************************************************************
*
* Public code
*
********************************************************************************
*/
/**
* @brief 关闭wav文件
*
* @param instance 句柄
*/
void wav_file_close(WAV_FILE_Typedef_t *instance)
{
instance->wav_file_header.nRiffLength = instance->file_size - 8;
instance->wav_file_header.nDataLength = instance->file_size - 44;
fseek(instance->fp, 0, SEEK_SET);
fwrite(&instance->wav_file_header, sizeof(char), 44, instance->fp);
fclose(instance->fp);
}
/**
* @brief 音频数据写入文件
*
* @param instance 句柄
* @param Channel_Number 通道数 >= 1 <= 8
* @param ... 通道数据
*/
void wav_file_put_data(WAV_FILE_Typedef_t *instance, uint8_t Channel_Number, ...)
{
static int16_t Audio_Data[8 * WAV_ONCE_WRITE_FRAME_SIZE];
va_list args;
uint32_t index = 0;
for(uint32_t i = 0; i < WAV_ONCE_WRITE_FRAME_SIZE; i++)
{
/* args point to the first variable parameter */
va_start(args, Channel_Number);
for(uint8_t Channel_Index = 0; Channel_Index < Channel_Number; Channel_Index++)
{
Audio_Data[index++] = (va_arg(args, uint16_t *))[i];
}
}
va_end(args);
fwrite(Audio_Data, sizeof(int16_t), index, instance->fp);
instance->file_size += (index * sizeof(int16_t));
}
/**
* @brief Set the wav info object
*
* @param record_sec 音频秒数
* @param nChannleNumber 1单声道 2双声道
* @param nSampleRate 采样率 点/s
* @param nBitsPerSample 每次采样得到的样本数据位数值 采样位宽 8/16位/24位/32位
*/
void wav_file_set_info(WAV_FILE_Typedef_t *instance, uint64_t record_sec, uint16_t nChannleNumber, uint32_t nSampleRate, uint16_t nBitsPerSample)
{
/* 设置该时长下文件大小 */
// instance->file_size = 44 + (record_sec * nChannleNumber * nSampleRate * (nBitsPerSample / 8));
instance->wav_file_header.nChannleNumber = nChannleNumber;
instance->wav_file_header.nSampleRate = nSampleRate;
instance->wav_file_header.nBitsPerSample = nBitsPerSample;
instance->wav_file_header.nBytesPerSecond = nSampleRate * nChannleNumber * nBitsPerSample / 8;
instance->wav_file_header.nBytesPerSample = nChannleNumber * nBitsPerSample / 8;
}
/**
* @brief 创建wav文件初始化对象
*
* @param instance 句柄
* @param file_name 文件名
*/
void wav_file_create(WAV_FILE_Typedef_t *instance, const char *file_name)
{
memset(instance, 0, sizeof(WAV_FILE_Typedef_t));
/* 设置文件名 */
memcpy(instance->file_name, file_name, strlen(file_name));
/* 设置wav文件信息 */
memcpy(&instance->wav_file_header, &wav_file_header, sizeof(WAVFILEHEADER_Typedef_t));
instance->file_size = sizeof(WAVFILEHEADER_Typedef_t);
/* 创建wav文件 */
errno_t err = fopen_s(&instance->fp, instance->file_name, "wb+");
if(err != 0)
{
printf("%s\r\n", Err_Str[err]);
return;
}
/* 跳过文件信息头部描述区域 */
fseek(instance->fp, 44, SEEK_SET);
}
#ifdef __cplusplus ///<end extern c
}
#endif
/******************************** End of file *********************************/
头文件
/**
* @file wav_file_opt.h
*
* @date 2022年07月25日 15:37:10 星期一
*
* @author Copyright (c) 2021 aron566 <aron566@163.com>.
*
* @brief 提供生成wav的接口.
*
* @version V1.0
*/
#ifndef WAV_FILE_OPT_H
#define WAV_FILE_OPT_H
/** Includes -----------------------------------------------------------------*/
#include <stdint.h> /**< need definition of uint8_t */
#include <stddef.h> /**< need definition of NULL */
#include <stdbool.h>/**< need definition of BOOL */
#include <stdio.h> /**< if need printf */
#include <stdlib.h>
#include <string.h>
#include <limits.h> /**< need variable max value */
#include <stdarg.h>
/** Private includes ---------------------------------------------------------*/
/** Use C compiler -----------------------------------------------------------*/
#ifdef __cplusplus ///< use C compiler
extern "C" {
#endif
/** Private defines ----------------------------------------------------------*/
#define WAV_ONCE_WRITE_FRAME_SIZE 64//MONO_FRAME_SIZE /**< once write frame size */
/** Exported typedefines -----------------------------------------------------*/
/* ref:https://www.cnblogs.com/zoneofmine/p/10850465.html */
typedef struct WAVFILEHEADER
{
/*RIFF头*/
char RiffName[4]; /**< 文件标识字符串 "RIFF"*/
uint32_t nRiffLength; /**< 头后文件长度 = 文件总长 - 8*/
/*数据类型标识符*/
char WavName[4]; /**< 波形文件标识字符串 "WAVE"*/
/*格式块中的块头*/
char FmtName[4]; /**< 格式标识字符串 "fmt"*/
uint32_t nFmtLength; /**< 头后块长度 16 or 18*/
/*
一般为16 或者 18 也有 大于18的数值表示格式块中块数据大小,通常为16,
为18时表示格式块中块数据有附加信息(即扩展域大小-nAppendMessage)
主要由一些软件制成的wav格式中含有该2个字节,当大于18时,nFmtLength - 18 (或者用nAppendMessage代替)
即为扩展域信息数据所占的字节数
*/
/*格式块中的块数据*/
uint16_t nAudioFormat; /**< 格式标识 PCM为1 其他压缩>1*/
uint16_t nChannleNumber; /**< 声道标识 1单声道 2双声道*/
uint32_t nSampleRate; /**< 采样率 点/s*/
uint32_t nBytesPerSecond;/**< 平均字节率 Bytes/s = 采样频率 × 音频通道数 × 每次采样得到的样本位数 / 8*/
uint16_t nBytesPerSample;/**< 每个采样需要的字节数 Bytes/sample once = 通道数 × 每次采样得到的样本数据位值/8*/
uint16_t nBitsPerSample; /**< 每次采样得到的样本数据位数值 采样位宽 8位/16位/24位/32位*/
/*
扩展域数据,nFmtLength >= 18存在
quint16 nAppendSize; 固定两个字节描述扩展区域数据长度
AppendMessage[nAppendSize];
*/
/*数据块中的块头*/
char DATANAME[4];
uint32_t nDataLength;
}WAVFILEHEADER_Typedef_t;
/* WAV文件对象 */
typedef struct
{
FILE *fp;
char file_name[64];
uint64_t file_size;
WAVFILEHEADER_Typedef_t wav_file_header;
}WAV_FILE_Typedef_t;
/** Exported constants -------------------------------------------------------*/
/** Exported macros-----------------------------------------------------------*/
/** Exported variables -------------------------------------------------------*/
/** Exported functions prototypes --------------------------------------------*/
/**
* @brief 创建wav文件初始化对象
*
* @param instance 句柄
* @param file_name 文件名
*/
void wav_file_create(WAV_FILE_Typedef_t *instance, const char *file_name);
/**
* @brief Set the wav info object
*
* @param record_sec 音频秒数
* @param nChannleNumber 1单声道 2双声道,最多8声道
* @param nSampleRate 采样率 点/s
* @param nBitsPerSample 每次采样得到的样本数据位数值 采样位宽 8/16位/24位/32位
*/
void wav_file_set_info(WAV_FILE_Typedef_t *instance, uint64_t record_sec, uint16_t nChannleNumber, uint32_t nSampleRate, uint16_t nBitsPerSample);
/**
* @brief 音频数据写入文件
*
* @param instance 句柄
* @param Channel_Number 通道数 >= 1 <= 8
* @param ... 通道数据
*/
void wav_file_put_data(WAV_FILE_Typedef_t *instance, uint8_t Channel_Number, ...);
/**
* @brief 关闭wav文件
*
* @param instance 句柄
*/
void wav_file_close(WAV_FILE_Typedef_t *instance);
#ifdef __cplusplus ///<end extern c
}
#endif
#endif
/******************************** End of file *********************************/
使用方式
int main(void)
{
/* 初始化wav文件 */
WAV_FILE_Typedef_t wav_file;
wav_file_create(&wav_file, "test.wav");
/* 设置通道数 采样率 位长 */
wav_file_set_info(&wav_file, 0, 3, 16000, 16);
while(1)
{
/* 音频处理输出 */
/* 结果写入wav文件 */
wav_file_put_data(&wav_file, 3, result_0, result_l, result_r);
}
/* 关闭文件 */
wav_file_close(&wav_file);
return 0;
}