当前位置 博文首页 > wanggao的专栏:ffmpeg学习 pcm文件转wav文件

    wanggao的专栏:ffmpeg学习 pcm文件转wav文件

    作者:[db:作者] 时间:2021-09-09 09:51

    前面有详细介绍PCM文件数据格式,本文简单说明PCM文件如何转换成WAV文件,并通过代码实现转换过程。

    WAV文件格式

    WAV是微软公司专门为Windows开发的一种标准数字音频文件,符合资源互换文件格式(RIFF)规范,支持多种音频位数、采样频率和声道。基于PCM编码的WAV格式的音频数据无压缩,能被生声卡直接支持,文件体积大。

    类似视频封装格式,WAV文件可以认为是保存音频采样数据的容器封装,仅在PCM数据前增加一些wav文件信息。基于PCM编码的标准WAV协议如下图
    在这里插入图片描述
    整个wav文件只有一个RIFF块,默认id为“RIFF”,其chunksize为不包含前两个字段文件大小(filesize - 8),format默认为”WAVE”。子块“fmt ”(注意有一个空字符)描述了音频采样数据格式,块长度固定为16字节。子块“data”承载pcm数据。

    示例代码

    根据pcm文件信息,首先创建WAV文件格式文件头信息,再读取pcm数据,之后写出到文件即可。

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    
    // PCM WAV Format (44 bytes)
    typedef struct WavHeader {
        uint32_t chunk_id;   // "RIFF"
        uint32_t chunk_size; // (44-8) + pcm_data_len
        uint32_t format;     // "WAVE"
    
        uint32_t sub_chunk_1_id;     // "fmt "
        uint32_t sub_chunk_1_size;   // 16 (PCM)
        uint16_t audio_format;       //  1 - PCM
        uint16_t num_channels;       // 1-mono, 2-stero
        uint32_t sample_rate;
        uint32_t byte_rate;          // sample_rate * block_align
        uint16_t block_align;        // num_channels * bit_per_sample
        uint16_t bit_per_sample;    
    
        uint32_t sub_chunk_2_id;     // "data"
        uint32_t sub_chunk_2_size;   //  pcm_data_len
        
        uint8_t  data[0];  // data pointer (不占内存)
    }WavHeader;
    
    int main()
    {
        // pcm 数据
        const char *pcm_file_name = "../files/Titanic_8000_s16_stero.pcm";
    
        FILE *pcm_file = fopen(pcm_file_name,"rb");
        if(!pcm_file) {
             printf("can not read pcm file %s\n", pcm_file_name);
             return 0;
        }
    
        int32_t pos_s = ftell(pcm_file);
        fseek(pcm_file, 0, SEEK_END);
        int32_t pos_e = ftell(pcm_file);
      
        int32_t pcm_data_len = pos_e - pos_s;
    
        rewind(pcm_file);
    
        // wav 数据
        const char *wav_file_name = "Titanic_8000_s16_stero.wav";
    
        FILE *wav_file = fopen(wav_file_name, "wb");
        if(!wav_file) {
            printf("can not read pcm file %s\n", wav_file_name);
            return 0;
        }
    
        void *wav_buff = malloc(sizeof(WavHeader) + pcm_data_len);
    
        WavHeader *wavHeader = (WavHeader*)wav_buff;
    
        memcpy(&wavHeader->chunk_id,"RIFF",4);
        wavHeader->chunk_size = 36 + pcm_data_len;
        memcpy(&wavHeader->format, "WAVE", 4);
    
        memcpy(&wavHeader->sub_chunk_1_id, "fmt ", 4);
        wavHeader->sub_chunk_1_size = 16;
        wavHeader->audio_format = 1; 
        wavHeader->num_channels = 2;
        wavHeader->sample_rate = 8000;
        wavHeader->bit_per_sample = 16;
        wavHeader->block_align = wavHeader->num_channels * wavHeader->bit_per_sample / 8;
        wavHeader->byte_rate = wavHeader->sample_rate * wavHeader->block_align;
    
        memcpy(&wavHeader->sub_chunk_2_id, "data", 4);
        wavHeader->sub_chunk_2_size = pcm_data_len;
    
        // read pcm data
        fread(wavHeader->data, 1, pcm_data_len, pcm_file);
    
        // 写wav头、PCM数据
        fwrite(wavHeader, 1, sizeof(WavHeader) + pcm_data_len, wav_file);
    
        // 关闭文件
        fclose(pcm_file);
        fclose(wav_file);
    
        free(wav_buff);
    
        return 0;
    }
    

    注意WAV文件结构体中存在一个数组长度为0的uint8_t data[0]成员,该指针data不占用结构体大小,通常这种方用于不定长读的内存分配

    程序运行保存的文件大小如下图所示,wav文件也确实比pcm文件大了44个字节。
    在这里插入图片描述

    使用MediaInfo查看wav文件信息如下图,文件可以正常播放。
    在这里插入图片描述

    cs