前言

WAV文件头数据结构参考:网址, 代码测试平台:Arm32_linux

1. 基础知识:

  1. 一帧数据大小(单位是字节) = 声道数量 * 数据量化比特数 / 8
  2. 采集频率 = 1秒采集的帧数
  3. 文件1秒采集数据大小(单位是字节) = 一帧数据大小(单位是字节) * 采集频率
  4. pcm文件 + wav文件头 = wav文件

2. WAV文件头数据结构,如下图

在这里插入图片描述

3. 代码实现

/* 参考网址:http://soundfile.sapp.org/doc/WaveFormat/    */
/* 该网站说明了WAV文件头部信息的所有参数含义以及参数所占空间大小 */

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <linux/types.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

static struct sampParam{
    __u8    channels;       //声道数
    __u16   sample_rate;    //采样率
    __u8    bitsPerSample;  //量化位数
}sampParam;
/**
 * Convert PCM raw data to WAVE format
 * @param pcmpath       Input PCM file.
 * @param sampParam     采样参数.
 * @param wavepath      Output WAVE file.
 */
int transform_pcm_to_wave(const char *pcmpath, struct sampParam sampParam, const char *wavepath)
{
    /*-------------------------------------------------------------------------------*/
    /* --------------------------- 创建并填充wav头部信息 -------------------------------*/
    /*-------------------------------------------------------------------------------*/

    int     fd_pcm, fd_wav;       //pcm,wav文件描述符
    __u8    WAV_HEAD_SIZE = 44;   //RIFF_HEADER + FMT_SUBCHUNK + DATA_SUBCHUNK = WAV_HEAD_SIZE(单位字节)    
    off_t   offset;               //文件读写偏移位置
    __u8    buf[1024];            //fd_pcm读取到buf,然后写到fd_wav
    ssize_t len;                  //实际从fd_pcm读取到的字节数,作为read函数的返回值

    typedef struct RIFF_HEADER{
        __u8    ChunkID[4];     //内容为"RIFF"
        __u32   ChunkSize;      //最后填写,WAVE格式音频的大小
        __u8    Format[4];      //内容为"WAVE"
    }RIFF_HEADER;

    typedef struct FMT_SUBCHUNK{
        __u8    Subchunk1ID[4];     //内容为"fmt "
        __u32   Subchunk1Size;      //内容为WAVE_FMT占的字节数,为16
        __u16   AudioFormat;        //如果为PCM,改值为 1
        __u16   NumChannels;        //通道数,单通道=1,双通道=2
        __u32   SampleRate;         //采样频率
        __u32   ByteRate;           /* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
        __u16   BlockAlign;         //==wChannels*uiBitsPerSample/8
        __u16   BitsPerSample;      //每个采样点的bit数,8bits=8, 16bits=16
    }FMT_SUBCHUNK;

    typedef struct DATA_SUBCHUNK{
        __u8    Subchunk2ID[4];     //内容为"data"
        __u32   Subchunk2Size;      //==NumSamples*wChannels*uiBitsPerSample/8
    }DATA_SUBCHUNK;

    typedef struct WAV_HEAD{
        RIFF_HEADER     pcmHeader;
        FMT_SUBCHUNK    pcmFmt;
        DATA_SUBCHUNK   pcmData;
    }WAV_HEAD;
    
    WAV_HEAD* wavHead;  //整个wav文件的头部

    //只读
    fd_pcm = open(pcmpath, O_RDONLY); 
    if(0 > fd_pcm){
        perror("open pcm function error");
        goto err1;
    }

    //可读可写,创建新文件,允许文件所属者读、写、执行文件
    fd_wav = open(wavepath, O_RDWR | O_CREAT | O_EXCL, S_IRWXU | S_IRWXG | S_IRWXO);  
    if (0 > fd_wav){
        perror("open wav function error");
        goto err2;
    }
    
    /* WAVE_HEADER -------------------------------------------------------------------*/
    memcpy(wavHead->pcmHeader.ChunkID, "RIFF", 4);  //规定填RIFF
    memcpy(wavHead->pcmHeader.Format, "WAVE", 4);   //规定填WAVE

    /* WAVE_FMT -------------------------------------------------------------------*/
    //规定填fmt,不要忘记fmt后面的空格,凑齐4字节
    memcpy(wavHead->pcmFmt.Subchunk1ID, "fmt ", 4);
    //一般都是填16, 这个数字说明WAVE_FMT子块中Subchunk1Size后面剩下的字节空间大小
    wavHead->pcmFmt.Subchunk1Size = 16;    
    //这个数字取决于音频数据的编码格式,如果是PCM则填1。其余如MP3、ADPCM等编码格式我也不知道。         
    wavHead->pcmFmt.AudioFormat = 0x0001; 
    //声道数量,由函数参数提供         
    wavHead->pcmFmt.NumChannels = sampParam.channels;     
    //采样频率,由函数参数提供    
    wavHead->pcmFmt.SampleRate = sampParam.sample_rate;    
    //表示音频数据每秒的字节数     
    wavHead->pcmFmt.ByteRate = sampParam.sample_rate * 
                                sampParam.channels * 
                                sampParam.bitsPerSample / 8;   
    /* 一帧数据大小(单位字节) */
    wavHead->pcmFmt.BlockAlign = sampParam.channels * sampParam.bitsPerSample / 8;
    /* 量化位数 */
    wavHead->pcmFmt.BitsPerSample = sampParam.bitsPerSample;

    /* WAVE_DATA -------------------------------------------------------------------*/
    memcpy(wavHead->pcmData.Subchunk2ID, "data", 4);
    /* 获取PCM文件大小,写给Subchunk2Size */
    offset = lseek(fd_pcm, 0, SEEK_END);   //fd_pcm文件的读写偏移量移动至文件最后
    if(offset == -1){
        perror("lseek SEEK_END function error");
        goto err2;
    }
    /* PCM文件大小(单位字节) */
    wavHead->pcmData.Subchunk2Size = (__u32)offset;

    /* 不要忘记 WAVE_HEADER 部分信息还有一个 ChunkSize 还没计算---------------------------*/
    wavHead->pcmHeader.ChunkSize = 36 + wavHead->pcmData.Subchunk2Size;

    /*-------------------------------------------------------------------------------*/
    /* ----------------- wav头部信息创建结束,开始写入fd_wav文件中 -----------------------*/
    /*------------------------------------------------------------------------------*/

    /* fd_pcm文件的读写偏移量移动回0位置 */
    offset = lseek(fd_pcm, 0, SEEK_SET);   
    if(offset == -1){
        perror("lseek SEEK_SET function error");
        goto err2;
    }

    /* 先向fd_wav写入头部信息 */
    if( 0 > write(fd_wav, wavHead, sizeof(WAV_HEAD))){
        perror("write wavHead function error");
    }

    while(( len = read(fd_pcm, buf, sizeof(buf)) ) > 0 ) {
        // 每读一次数据,就写入到拷贝文件中
        if(0 > write(fd_wav, buf, len)){
            perror("write fd_wav function error");
            goto err2;
        }
    }
    if(0 > len){
        perror("read fd_pcm function error");
        goto err2;
    }
    return 0;

err2:
    close(fd_wav);
err1:
    close(fd_pcm);
    return -1;
}

int main(int argc, char* argv[])
{    
    sampParam.bitsPerSample = 16;
    sampParam.channels = 2;
    sampParam.sample_rate = 44100;
    if(0 > transform_pcm_to_wave(argv[1], sampParam, argv[2])){
        perror("transform_pcm_to_wave function error");
        return -1;
    }
    return 0;
}

4. 最后,提供一份PCM音频文件,来尝试代码的运行效果,文件如下

提取码: ajqj 百度网盘连接
这份PCM格式:44100hz,16位,双通道

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐