草庐IT

【音频】WAV 格式详解

tyustli 2023-04-07 原文

文章目录

WAV 文件格式解析

概述

wav 文件支持多种不同的比特率、采样率、多声道音频。

WAV 文件格式是 Microsoft 的 RIFF 规范的一个子集,用于存储多媒体文件。RIFF(resource interchange file format 资源互换文件格式,以 chunk(块) 为单位组织文件)格式文件。在 windows 上,大部分多媒体文件都是 RIFF 文件。wav 文件由若干个 RIFF chunk 构成,分别为: RIFF WAVE Chunk,Format Chunk,Fact Chunk(可选),Data Chunk。另外,文件中还可能包含一些可选的区块,如:Fact chunk、Cue points chunk、Playlist chunk、Associated data list chunk 等。具体格式如下:

块解析

wav 文件都是由 chunk 组成,chunk 的格式如下

size内容含义
4 bytesID如:RIFF,fmt,data
4 byteschund size如标准的 fmt chunk 为 16 字节
N bytesdatachunk 的内容

RIFF chunk

typedef struct
{
    char ChunkID[4]; //'R','I','F','F'
    unsigned int ChunkSize;
    char Format[4];  //'W','A','V','E'
}riff_chunk;
size内容含义
4 bytesChunkIDRIFF
4 bytesChunkSize从下一个字段首地址开始到文件末尾的总字节数。该字段的数值加 8 为当前文件的实际长度
4 bytesFormatWAVE

其中 ChunkSize 代表的是整个 file_size 的大小减去 ChunkID 和 ChunkSize 的大小,即 file_size=ChunkSize+8。

fmt chunk

typedef struct
{
    char FmtID[4];
    unsigned int FmtSize;
    unsigned short FmtTag;
    unsigned short FmtChannels;
    unsigned int SampleRate;
    unsigned int ByteRate;
    unsigned short BlockAilgn;
    unsigned short BitsPerSample;
}fmt_chunk;
size内容含义
4 bytesFmtIDfmt
4 bytesFmtSizefmt chunk 的大小,一般有 16/18/20/22/40 字节 (也有超过 40 字节的情况,如果不知道后面部分的含义,直接跳过即可),超过 16 字节部分为扩展块
4 bytesFmtTag编码格式代码,其值见下 常见编码格式 表,如果上述取值为 16,则此值通常为 1,代表该音频的编码方式是 PCM 编码
4 bytesFmtChannels声道数目,1 代表单声道,2 代表双声道
4 bytesSampleRate采样频率,8/11.025/12/16/22.05/24/32/44.1/48/64/88.2/96/176.4/192 kHZ
4 bytesByteRate传输速率,每秒的字节数,计算公式为:SampleRate * FmtChannels * BitsPerSample/8
4 bytesBlockAilgn块对齐,告知播放软件一次性需处理多少字节,公式为: BitsPerSample*FmtChannels/8
4 bytesBitsPerSample采样位数,一般有8/16/24/32/64,值越大,对声音的还原度越高

常见编码格式

格式编码格式名称fmt 块长度fact 块
0x01PCM / 非压缩格式16
0x02Microsoft ADPCM18
0x03IEEE float18
0x06ITU G.711 a-law18
0x07ITU G.711 μ-law18
0x031GSM 6.1020
0x040ITU G.721 ADPCM
0xFFFE见子格式块中的编码格式40

data chunk

struct DATA_CHUNK
{
    char DataID[4]; //'d','a','t','a'
    unsigned int DataSize;
};
size内容含义
4 bytesDataIDdata
4 bytesDataSize原始音频数据的大小

示例分析

Linux 平台下使用 mediainfo 分析 wav 文件

安装

 sudo apt-get install mediainfo

解析

执行命令:mediainfo test.wav

General
Complete name                            : test.wav
Format                                   : Wave
File size                                : 1.35 MiB
Duration                                 : 8 s 0 ms
Overall bit rate mode                    : Constant
Overall bit rate                         : 1 411 kb/s

Audio
Format                                   : PCM
Format settings                          : Little / Signed
Codec ID                                 : 1
Duration                                 : 8 s 0 ms
Bit rate mode                            : Constant
Bit rate                                 : 1 411.2 kb/s
Channel(s)                               : 2 channels
Sampling rate                            : 44.1 kHz
Bit depth                                : 16 bits
Stream size                              : 1.35 MiB (100%)

查看文件大小

 ls test.wav -l
-rw-rw-r-- 1 tyustli tyustli 1411248 729 15:03 test.wav

hd 工具查看原始数据

hd test.wav -n 128
00000000  52 49 46 46 a8 88 15 00  57 41 56 45 66 6d 74 20  |RIFF....WAVEfmt |
00000010  10 00 00 00 01 00 02 00  44 ac 00 00 10 b1 02 00  |........D.......|
00000020  04 00 10 00 64 61 74 61  84 88 15 00 0e fc fa fe  |....data........|
00000030  6c fb 8a fe c2 fa 19 fe  1e fa b5 fd 85 f9 64 fd  |l.............d.|
00000040  f5 f8 20 fd 73 f8 df fc  09 f8 92 fc bb f7 28 fc  |.. .s.........(.|
00000050  83 f7 9d fb 5f f7 fe fa  46 f7 55 fa 2e f7 a3 f9  |...._...F.U.....|
00000060  23 f7 fa f8 36 f7 6b f8  57 f7 ef f7 84 f7 83 f7  |#...6.k.W.......|
00000070  c7 f7 2d f7 1e f8 eb f6  88 f8 c3 f6 0b f9 c5 f6  |..-.............|
  • 52 49 46 46:对应的 ASCII 字符为:RIFF
  • a8 88 15 00:ChunkSize,对应的十六进制是 0x1588a8=1411240 +8 和上面的文件大小一致
  • 57 41 56 45:对应的 ASCII 字符为 WAVE
  • 66 6d 74 20:对应的 ASCII 字符为 fmt
  • 10 00 00 00:FmtSize 0x10=16 代表PCM编码方式
  • 01 00:对应为1,代表PCM编码方式
  • 02 00:通道个数,通道数为2
  • 44 ac 00 00:采样频率 0xac44=44100=44.1KHz
  • 10 b1 02 00:每秒所需的字节数,转化为十六进制为: 0x2b110=176400 通过此值可以计算该音频的时长:1411240/176400 = 8s
  • 04 00:数据对齐单位
  • 10 00:采样位数 0x10=16
  • 64 61 74 61:对应的 ASCII 字符为 data
  • 84 88 15 00:对应该音频的raw数据的大小,转化为十六进制为 0x158884=1411204,此值等于 1411248-44

代码解析

/*
 * Change Logs:
 * Date           Author       Notes
 * 2022-08-26     tyustli      first version
 */

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>

typedef struct {
    char ChunkID[4]; //'R','I','F','F'
    unsigned int ChunkSize;
    char Format[4]; //'W','A','V','E'
} riff_chunk;

typedef struct {
    char FmtID[4];
    unsigned int FmtSize;
    unsigned short FmtTag;
    unsigned short FmtChannels;
    unsigned int SampleRate;
    unsigned int ByteRate;
    unsigned short BlockAilgn;
    unsigned short BitsPerSample;
} fmt_chunk;

typedef struct {
    char DataID[4]; //'d','a','t','a'
    unsigned int DataSize;
} data_chunk;


typedef struct {
    riff_chunk riff_region;
    fmt_chunk fmt_region;
    data_chunk data_region;
} wav_struct;

static void *map_file(const char *path);
static void data_dump(wav_struct *data);

int main(int argc, char *argv[]) {

    assert(sizeof(wav_struct) == 44); /* defensive */

    if (argc < 2) {
        printf("usage: %s file_path\r\n", argv[0]);
        exit(-1);
    }

    /* map file */
    wav_struct *map_data = map_file(argv[1]);

    /* data dump */
    data_dump(map_data);

    /* munmap file */
    munmap(map_data, map_data->riff_region.ChunkSize + 8);

    return 1;
}

static void *map_file(const char *path) {

    assert(path != NULL);

    int fd = open(path, O_RDWR);
    if (fd == -1) {
        goto __release;
    }

#if 0
    off_t size = lseek(fd, 0, SEEK_END);
    if (size == -1) {
        goto __release;
    }
#endif

    /* get file size */
    struct stat stat;
    int ret = fstat(fd, &stat);
    if (ret == -1) {
        goto __release;
    }

    size_t size = stat.st_size;

    /* map file */
    void *file_data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
    if(file_data == (void *)-1) {
        goto __release;
    }
    close(fd);

    return file_data;

    /* error handle */
__release:
    perror("map file");
    if (fd > 0) {
        close(fd);
    }
    exit(1);
}

static void data_dump(wav_struct *data) {

    /* riff chunkid */
    printf("riff chunk id          :");
    for(int i = 0; i < 4; i++) {
        printf("%c", data->riff_region.ChunkID[i]);
    }
    printf("\r\n");

    /* file size */
    printf("wav file size          :%d\r\n", data->riff_region.ChunkSize + 8);

    /* riff Format */
    printf("riff format id         :");
    for(int i = 0; i < 4; i++) {
        printf("%c", data->riff_region.Format[i]);
    }
    printf("\r\n");

    /* fmt chunkid */
    printf("fmt chunk id           :");
    for(int i = 0; i < 4; i++) {
        printf("%c", data->fmt_region.FmtID[i]);
    }
    printf("\r\n");

    printf("FmtChannels            :%d(1 单声道, 2 双声道)\r\n", data->fmt_region.FmtChannels);
    printf("FmtTag                 :%d(1 PCM 编码)\r\n", data->fmt_region.FmtTag);
    printf("SampleRate             :%d\r\n", data->fmt_region.SampleRate);
    printf("ByteRate               :%d\r\n", data->fmt_region.ByteRate);
    printf("BitsPerSample          :%d\r\n", data->fmt_region.BitsPerSample);

    /* data chunkid */
    printf("fmt chunk id           :");
    for(int i = 0; i < 4; i++) {
        printf("%c", data->data_region.DataID[i]);
    }
    printf("\r\n");
}
/**
 * 编译:gcc wav_parse.c
 * 运行:./a.out sample.wav
 * 结果:
 * riff chunk id          :RIFF
 * wav file size          :497904
 * riff format id         :WAVE
 * fmt chunk id           :fmt 
 * FmtChannels            :2(1 单声道, 2 双声道)
 * FmtTag                 :1(1 PCM 编码)
 * SampleRate             :44100
 * ByteRate               :176400
 * BitsPerSample          :16
 * fmt chunk id           :data
 * 
*/

/*************** end of file ***************/

有关【音频】WAV 格式详解的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby-on-rails - 将 Ruby 中的日期/时间格式化为 YYYY-MM-DD HH :MM:SS - 2

    这个问题在这里已经有了答案:Railsformattingdate(4个答案)关闭4年前。我想格式化Time.Now函数以显示YYYY-MM-DDHH:MM:SS而不是:“2018-03-0909:47:19+0000”该函数需要放在时间中.现在功能。require‘roo’require‘roo-xls’require‘byebug’file_name=ARGV.first||“Template.xlsx”excel_file=Roo::Spreadsheet.open(“./#{file_name}“,extension::xlsx)xml=Nokogiri::XML::Build

  3. ruby - 我可以将我的 README.textile 以正确的格式放入我的 RDoc 中吗? - 2

    我喜欢使用Textile或Markdown为我的项目编写自述文件,但是当我生成RDoc时,自述文件被解释为RDoc并且看起来非常糟糕。有没有办法让RDoc通过RedCloth或BlueCloth而不是它自己的格式化程序运行文件?它可以配置为自动检测文件后缀的格式吗?(例如README.textile通过RedCloth运行,但README.mdown通过BlueCloth运行) 最佳答案 使用YARD直接代替RDoc将允许您包含Textile或Markdown文件,只要它们的文件后缀是合理的。我经常使用类似于以下Rake任务的东西:

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  6. ruby-on-rails - 如何在 Rails 中设置路由的默认格式? - 2

    路由有如下代码:resources:orders,only:[:create],defaults:{format:'json'}resources:users,only:[:create,:update],defaults:{format:'json'}resources:delivery_types,only:[:index],defaults:{format:'json'}resources:time_corrections,only:[:index],defaults:{format:'json'}是否可以使用1个字符串为所有资源设置默认格式,每行不带“默认值”散列?谢谢。

  7. ruby-on-rails - Rails 4 WYSIWYG Bootsy 不显示格式 - 2

    我刚刚按照thebootsygempage上的安装说明进行操作在我保存并查看帖子内容之前,一切看起来都不错。这是输出在View中的样子:HeaderSubhead:似乎没有呈现任何html格式,因为它被引号或类似的东西转义了-其他人有这个问题吗?我没有在github页面或SO上看到任何问题来指出我正确的方向。除了遵循gem安装说明之外,我还没有做任何事情,但也许我错过了什么或者只是犯了一个愚蠢的错误。如果你还有什么想知道的,请尽管问。干杯 最佳答案 你需要有这样的东西,转义html: 关

  8. ruby - 在 Ruby 中将整数格式化为固定长度的字符串 - 2

    有没有一种简单的方法可以将给定的整数格式化为具有固定长度和前导零的字符串?#convertnumberstostringsoffixedlength3[1,12,123,1234].map{|e|???}=>["001","012","123","234"]我找到了解决方案,但也许还有更聪明的方法。format('%03d',e)[-3..-1] 最佳答案 如何使用%1000而不是进行字符串操作来获取最后三位数字?[1,12,123,1234].map{|e|format('%03d',e%1000)}更新:根据theTinMan的

  9. ruby-on-rails - 如何正确格式化字符串,如 'mccdougal' 到 'McDougal' - 2

    什么Ruby或RailsDSL会将字符串"mccdougal"格式化为"McDougal",同时留下字符串"McDougal"原样?将titleize传递给"McDougal"结果如下:"McDougal".titleize#=>"McDougal" 最佳答案 据我所知,没有可以处理这种情况的Rails助手。这是一个非标准的边缘案例,需要特殊处理。但是,您可以创建自定义字符串变形。您可以将这段代码放入初始化程序中:ActiveSupport::Inflector.inflections(:en)do|inflect|inflect.

  10. ruby-on-rails - Rails 格式验证——字母数字,但不是纯数字 - 2

    什么是测试格式验证的最佳方法让我们说一个用户名,使用字母数字的正则表达式,但不是纯数字?我一直在我的模型中使用以下验证validates:username,:format=>{:with=>/^[a-z0-9]+[-a-z0-9]*[a-z0-9]+$/i}数字用户名(例如“342”)通过了验证,这是我不想要的。 最佳答案 您想“向前看”一封信:/\A(?=.*[a-z])[a-z\d]+\Z/i 关于ruby-on-rails-Rails格式验证——字母数字,但不是纯数字,我们在Sta

随机推荐