解析wav和mp3文件头以获得采样率和通道数

解析wav文件头

解析MP3文件头



前言

最近在工作中,会涉及到播放mp3文件和wav文件,会想要获得音频文件的采样率和通道数,所以去查了一下mp3文件头和wav文件头的格式,写代码来解析。


一、解析wav文件头

wav文件头格式

typedef struct
{
    uint8_t  ChunkID[4];    // 文档标识。       大写字符串"RIFF",标明该文件为有效的 RIFF 格式文档。
    uint32_t ChunkSize;     // 文件数据长度。    从下一个字段首地址开始到文件末尾的总字节数。该字段的数值加 8 为当前文件的实际长度。
    uint8_t  Format[4];     // 文件格式类型。    所有 WAV 格式的文件此处为字符串"WAVE",标明该文件是 WAV 格式文件。
    uint8_t  FmtChunkID[4]; // 格式块标识。     小写字符串,"fmt "。
    uint32_t FmtChunkSize;  // 格式块长度。     其数值不确定,取决于编码格式。可以是 16、 18 、20、40 等。
    uint16_t AudioFormat;   // 编码格式代码。    常见的 WAV 文件使用 PCM 脉冲编码调制格式,该数值通常为 1。
    uint16_t NumChannels;   // 声道个数。       单声道为 1,立体声或双声道为 2。
    uint32_t SampleRate;    // 采样频率。       每个声道单位时间采样次数。常用的采样频率有 11025, 22050 和 44100 kHz。
    uint32_t ByteRate;      // 数据传输速率。    该数值为:声道数×采样频率×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。
    uint16_t BlockAlign;    // 数据块对齐单位。  采样帧大小。该数值为:声道数×位数/8。播放软件需要一次处理多个该值大小的字节数据,用该数值调整缓冲区。
    uint16_t BitsPerSample; // 采样位数。       存储每个采样值所用的二进制数位数。常见的位数有 4、8、12、16、24、32。
    uint8_t  DataChunkID[4];
    uint32_t DataChunkSize;
}__attribute__((packed, aligned(1))) WaveHeader;

解析代码

static int audio_parse_wav_file(char *filename, int *sample_rate, int *channel_num, int *bit_width)
{
    int ret = 0;
    if(filename == NULL)
    {
        SAMPLE_PRT("wav filename is NULL!\n");
        return -1;
    }

    WavHeader header;
    FILE *file = fopen(filename, "rb");

    // 读取WAV文件头
    fread(&header, sizeof(header), 1, file);

    // 输出解析结果
    printf("%s Number of channels: %d\n", filename, header.numChannels);
    printf("%s Sample rate: %d Hz\n", filename, header.sampleRate);
    printf("%s Bits per sample: %d bits\n", filename, header.bitsPerSample);

    *sample_rate = header.sampleRate;// out
    *channel_num = header.numChannels;// out
    *bit_width   = header.bitsPerSample;// out

    // 关闭文件
    fclose(file);

    return ret;
}

二、解析MP3文件头

mp3文件结构

mp3文件结构大致结构如下:
在这里插入图片描述
注意:并不是所有mp3文件都包含ID3V2部分,有的MP3文件包含ID3V2部分,有的MP3文件不包含ID3V2部分。

ID3V2:

ID3V2 到现在一共有 4 个版本,但流行的播放软件一般只支持第 3 版,既 ID3v2.3。由于
ID3V1 记录在 MP3 文件的末尾,ID3V2 就只好记录在 MP3 文件的首部了。
每个 ID3V2.3 的标签都一个标签头若干个标签帧一个扩展标签头组成。关于曲目的信息如标题、作者等都存放在不同的标签帧中,扩展标签头和标签帧并不是必要的,但每个标
签至少要有一个标签帧。标签头和标签帧一起顺序存放在 MP3 文件的首部。

(一)、标签头

在文件的首部顺序记录 10 个字节的 ID3V2.3 的头部。数据结构如下:

typedef struct
{
    char Header[3]; /*必须为"ID3"否则认为标签不存在*/
    char Ver; /*版本号 ID3V2.3 就记录 3*/
    char Revision; /*副版本号此版本记录为 0*/
    char Flag; /*存放标志的字节,这个版本只定义了三位,稍后详细解说*/
    char Size[4]; /*标签大小,包括标签头的 10 个字节和所有的标签帧的大小*/
}__attribute__((packed, aligned(1))) Mp3ID3V2TagHeader;

注:对这里我有疑惑,因为在实际寻找首帧的过程中,我发现有的 mp3 文件的标签大小是不
包含标签头的,但有的又是包含的,可能是某些 mp3 编码器写标签的 BUG,所以为了兼容
只好认为其是包含的,如果按大小找不到,再向后搜索,直到找到首帧为止。

(1).标志字节

标志字节一般为 0,定义如下:
abc00000
a – 表示是否使用 Unsynchronisation(这个单词不知道是什么意思,字典里也没有找到,一般
不设置)
b – 表示是否有扩展头部,一般没有(至少 Winamp 没有记录),所以一般也不设置
c – 表示是否为测试标签(99.99%的标签都不是测试用的啦,所以一般也不设置)

(2).标签大小

一共四个字节,但每个字节只用 7 位,最高位不使用恒为 0。所以格式如下
0xxxxxxx 0xxxxxxx 0xxxxxxx 0xxxxxxx
计算大小时要将 0 去掉,得到一个 28 位的二进制数,就是标签大小(不懂为什么要这样做),
计算公式如
下:

int total_size;
total_size = (Size[0]&0x7F)*0x200000
			+(Size[1]&0x7F)*0x4000
			+(Size[2]&0x7F)*0x80
			+(Size[3]&0x7F)

Frame帧头结构如下:

使用字符 A 到 M 表示不同的区域。在表格中你可以看到每一区域
的详细内容。
AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

帧头定义

typedef struct
{
    unsigned int sync1: 8; //同步信息 1
    unsigned int error_protection: 1; //CRC 校验
    unsigned int layer: 2; //层
    unsigned int version: 2; //版本
    unsigned int sync2: 3; //同步信息 2
    unsigned int extension: 1; //版权
    unsigned int padding: 1; //填充空白字
    unsigned int sample_rate_index: 2; //采样率索引
    unsigned int bit_rate_index: 4; //位率索引
    unsigned int emphasis: 2; //强调方式
    unsigned int original: 1; //原始媒体
    unsigned int copyright: 1; //版权标志
    unsigned int mode_extension: 2; //扩展模式,仅用于联合立体声
    unsigned int channel_mode: 2; //声道模式
}__attribute__((packed, aligned(1))) Mp3FrameHeader;

请注意这里同步信息分成了两个部分,而且其他的位的顺序也和上表列出的有所差别,这个
主要是因为 c 语言在存取数据时总是从低位开始,而这个帧头是需要从高位来读取的。

解析帧头,获得采样率和通道数示例代码

static int audio_parse_mp3_file(char *filename, int *sample_rate, int *channel_num)
{
    int ret = 0;
    if(filename == NULL)
    {
        SAMPLE_PRT("mp3 filename is NULL!\n");
        return -1;
    }

    // ID3V2标签头格式 共10Bytes 可以无
    typedef struct{
        char Header[3]; /*必须为"ID3"否则认为标签不存在*/
        char Ver; /*版本号 ID3V2.3 就记录 3*/
        char Revision; /*副版本号此版本记录为 0*/
        char Flag; /*存放标志的字节,这个版本只定义了三位,稍后详细解说*/
        char Size[4]; /*标签大小,包括标签头的 10 个字节和所有的标签帧的大小*/    
    }Mp3ID3V2TagHeader;

    // Frame帧头格式
    typedef struct{
        unsigned int sync1:8; //同步信息 1
        unsigned int error_protection:1; //CRC 校验
        unsigned int layer:2; //层
        unsigned int version:2; //版本
        unsigned int sync2:3; //同步信息 2
        unsigned int extension:1; //版权
        unsigned int padding:1; //填充空白字
        unsigned int sample_rate_index:2; //采样率索引
        unsigned int bit_rate_index:4; //位率索引
        unsigned int emphasis:2; //强调方式
        unsigned int original:1; //原始媒体
        unsigned int copyright:1; //版权标志
        unsigned int mode_extension:2; //扩展模式,仅用于联合立体声
        unsigned int channel_mode:2; //声道模式
    }Mp3FrmHeader;

    typedef enum {
        MPEG_2_5 = 0,
        MPEG_RESERVED = 1,
        MPEG_2   = 2,
        MPEG_1   = 3,
        MPEG_VER_BUTT
    } MPEG_VERSION_TYPE_E;

    typedef enum {
        LAYER_RESERVED = 0,
        LAYER_3  = 1,
        LAYER_2  = 2,
        LAYER_1  = 3,
        LAYER_BUTT
    } MPEG_LAYER_TYPE_E;
    
    Mp3ID3V2TagHeader mp3_id3v2_tag_header;
    Mp3FrmHeader mp3_frm_header;

    FILE *file = fopen(filename, "rb");

    // 读取ID3V2标签头
    fread(&mp3_id3v2_tag_header, sizeof(mp3_id3v2_tag_header), 1, file);
    printf("mp3_id3v2_tag_header.Header = %c%c%c\n", mp3_id3v2_tag_header.Header[0], mp3_id3v2_tag_header.Header[1], mp3_id3v2_tag_header.Header[2]);

    if(mp3_id3v2_tag_header.Header[0] == 'I' && 
        mp3_id3v2_tag_header.Header[1] == 'D' && 
        mp3_id3v2_tag_header.Header[2] == '3')//包含ID3V2标签,MP3文件开头是ID3V2标签
    {
        //包含ID3V2标签

        //找到帧头
        int tag_total_size;
        tag_total_size = (mp3_id3v2_tag_header.Size[0]&0x7F)*0x200000
                        +(mp3_id3v2_tag_header.Size[1]&0x7F)*0x4000
                        +(mp3_id3v2_tag_header.Size[2]&0x7F)*0x80
                        +(mp3_id3v2_tag_header.Size[3]&0x7F);
        printf("tag_total_size = 0x%x\n", tag_total_size);
        tag_total_size -= 10;//减去tag自己的长度(有的size会包含tag头的10字节,有的不包含,这里可以一一尝试)
        //假设tag长度包含10字节的tag头
        //移动文件指针到当前位置后的tag_total_size字节处,看是否是Frame帧头
        fseek(file, tag_total_size, SEEK_CUR);
        // A.假设此时是MP3帧头,读取MP3 Frame帧头
        fread(&mp3_frm_header, sizeof(mp3_frm_header), 1, file);
        
        if(mp3_frm_header.sync1 == 0xFF && mp3_frm_header.sync2 == 0x7)
        {
            //找到了帧头帧同步,确实是帧头,继续往下解析
            //1. get sample_rate
            if(mp3_frm_header.version == MPEG_1)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 44100;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 48000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 32000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            else if(mp3_frm_header.version == MPEG_2)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 22050;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 24000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 16000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            else if(mp3_frm_header.version == MPEG_2_5)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 11025;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 12000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 8000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            
            printf("%s Sample rate: %d Hz\n", filename, *sample_rate);

            //2. get channel_num
            if(mp3_frm_header.channel_mode == 0)//立体声
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 1)//联合立体声(立体声)
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 2)//双声道(立体声)
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 3)//单声道(单声)
            {
                *channel_num = 1;// out
            }
            else
            {
                SAMPLE_PRT("Unsupported mp3_frm_header.channel_mode\n");
            }

            printf("%s Number of channels: %d\n", filename, *channel_num);
        }
        else //没有找到了帧头帧同步,不是帧头,重新计算偏移tag_total_size,重新解析
        {
            // 将文件位置指针移动到文件的开头位置
            fseek(file, 0, SEEK_SET);
            // 读取ID3V2标签头
            fread(&mp3_id3v2_tag_header, sizeof(mp3_id3v2_tag_header), 1, file);
            printf("mp3_id3v2_tag_header.Header = %c%c%c\n", mp3_id3v2_tag_header.Header[0], mp3_id3v2_tag_header.Header[1], mp3_id3v2_tag_header.Header[2]);

            if(mp3_id3v2_tag_header.Header[0] == 'I' && 
                mp3_id3v2_tag_header.Header[1] == 'D' && 
                mp3_id3v2_tag_header.Header[2] == '3')//包含ID3V2标签,MP3文件开头是ID3V2标签
            {
                //包含ID3V2标签

                //找到帧头
                int tag_total_size;
                tag_total_size = (mp3_id3v2_tag_header.Size[0]&0x7F)*0x200000
                                +(mp3_id3v2_tag_header.Size[1]&0x7F)*0x4000
                                +(mp3_id3v2_tag_header.Size[2]&0x7F)*0x80
                                +(mp3_id3v2_tag_header.Size[3]&0x7F);
                
                // tag_total_size -= 10;//现在不减去tag自己的长度(这里size不包含tag头的10字节)
                //tag长度不包含10字节的tag头
                //移动文件指针到当前位置后的tag_total_size字节处,看是否是Frame帧头
                fseek(file, tag_total_size, SEEK_CUR);
                // A.假设此时是MP3帧头,读取MP3 Frame帧头
                fread(&mp3_frm_header, sizeof(mp3_frm_header), 1, file);
                
                if(mp3_frm_header.sync1 == 0xFF && mp3_frm_header.sync2 == 0x7)
                {
                    //找到了帧头帧同步,确实是帧头,继续往下解析
                    //1. get sample_rate
                    if(mp3_frm_header.version == MPEG_1)
                    {
                        if(mp3_frm_header.sample_rate_index == 0)
                        {
                            *sample_rate = 44100;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 1)
                        {
                            *sample_rate = 48000;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 2)
                        {
                            *sample_rate = 32000;// out
                        }
                        else
                        {
                            SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                        }
                    }
                    else if(mp3_frm_header.version == MPEG_2)
                    {
                        if(mp3_frm_header.sample_rate_index == 0)
                        {
                            *sample_rate = 22050;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 1)
                        {
                            *sample_rate = 24000;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 2)
                        {
                            *sample_rate = 16000;// out
                        }
                        else
                        {
                            SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                        }
                    }
                    else if(mp3_frm_header.version == MPEG_2_5)
                    {
                        if(mp3_frm_header.sample_rate_index == 0)
                        {
                            *sample_rate = 11025;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 1)
                        {
                            *sample_rate = 12000;// out
                        }
                        else if(mp3_frm_header.sample_rate_index == 2)
                        {
                            *sample_rate = 8000;// out
                        }
                        else
                        {
                            SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                        }
                    }
                    
                    printf("%s Sample rate: %d Hz\n", filename, *sample_rate);

                    //2. get channel_num
                    if(mp3_frm_header.channel_mode == 0)//立体声
                    {
                        *channel_num = 2;// out
                    }
                    else if(mp3_frm_header.channel_mode == 1)//联合立体声(立体声)
                    {
                        *channel_num = 2;// out
                    }
                    else if(mp3_frm_header.channel_mode == 2)//双声道(立体声)
                    {
                        *channel_num = 2;// out
                    }
                    else if(mp3_frm_header.channel_mode == 3)//单声道(单声)
                    {
                        *channel_num = 1;// out
                    }
                    else
                    {
                        SAMPLE_PRT("Unsupported mp3_frm_header.channel_mode\n");
                    }

                    printf("%s Number of channels: %d\n", filename, *channel_num);
                }
                else
                {
                    SAMPLE_PRT("found mp3 ID3V2 tag header, but can not find mp3 frame header!");
                }
            }

        }
    }
    else//不包含ID3V2标签,MP3文件开头就是Frame帧头
    {
        // 将文件位置指针移动到文件的开头位置
        fseek(file, 0, SEEK_SET);
        // 此时因是MP3帧头,读取MP3 Frame帧头
        fread(&mp3_frm_header, sizeof(mp3_frm_header), 1, file);
        
        if(mp3_frm_header.sync1 == 0xFF && mp3_frm_header.sync2 == 0x7)
        {
            //找到了帧头帧同步,确实是帧头,继续往下解析
            //1. get sample_rate
            if(mp3_frm_header.version == MPEG_1)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 44100;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 48000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 32000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            else if(mp3_frm_header.version == MPEG_2)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 22050;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 24000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 16000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            else if(mp3_frm_header.version == MPEG_2_5)
            {
                if(mp3_frm_header.sample_rate_index == 0)
                {
                    *sample_rate = 11025;// out
                }
                else if(mp3_frm_header.sample_rate_index == 1)
                {
                    *sample_rate = 12000;// out
                }
                else if(mp3_frm_header.sample_rate_index == 2)
                {
                    *sample_rate = 8000;// out
                }
                else
                {
                    SAMPLE_PRT("Unsupported mp3_frm_header.sample_rate_index\n");
                }
            }
            
            printf("%s Sample rate: %d Hz\n", filename, *sample_rate);

            //2. get channel_num
            if(mp3_frm_header.channel_mode == 0)//立体声
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 1)//联合立体声(立体声)
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 2)//双声道(立体声)
            {
                *channel_num = 2;// out
            }
            else if(mp3_frm_header.channel_mode == 3)//单声道(单声)
            {
                *channel_num = 1;// out
            }
            else
            {
                SAMPLE_PRT("Unsupported mp3_frm_header.channel_mode\n");
            }

            printf("%s Number of channels: %d\n", filename, *channel_num);
        }
    }

    // 关闭文件
    fclose(file);
    return ret;
}

总结

以上就是所有的内容,本文仅仅简单介绍了使用C语言解析wav头和mp3头获得采样率和通道数的方法。

参考:MP3格式音频文件结构解析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值