天天看点

ffmpeg学习 pcm文件转wav文件

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

WAV文件格式

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

类似视频封装格式,WAV文件可以认为是保存音频采样数据的容器封装,仅在PCM数据前增加一些wav文件信息。基于PCM编码的标准WAV协议如下图

ffmpeg学习 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个字节。

ffmpeg学习 pcm文件转wav文件

使用MediaInfo查看wav文件信息如下图,文件可以正常播放。

ffmpeg学习 pcm文件转wav文件