arm 32linux,C语言实现文件pcm转换wav,提供代码实验用PCM音频文件

该网站详细描述了WAV文件的头部信息网址

尝试过其他人开源的代码,但是实际运行有问题,并且代码注释不全,于是尝试自己写

运行平台:嵌入式32位linux操作系统

首先要知道一些基础知识:

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

WAV文件的头部信息数据结构如下图,图片来自开头提到的网址

在这里插入图片描述

代码注释自认为是很全面了,有疑问可以看开头提到的网址

/* 参考网址: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;
}

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

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

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值