C代码创建多通道WAV音频文件

Posted aron566

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C代码创建多通道WAV音频文件相关的知识,希望对你有一定的参考价值。

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;

以上是关于C代码创建多通道WAV音频文件的主要内容,如果未能解决你的问题,请参考以下文章

C代码创建多通道WAV音频文件

c - 将数据写入 wav 文件

音频文件获取左/右声道

使用 C 创建立体声 WAV 文件

在 HTC One 上以 16khz 单声道 PCM (WAV) 录制时出现断断续续的音频

使用音频工具 sox,我如何确定立体声录音是不是实际上是单声道?