语音处理:wav头格式非44字节场景处理分析
语音处理:wav头格式非44字节场景处理分析
- 问题来源
- 46字节wav头处理分析
- 实践:处理46字节前文C代码修改
- 相关参考
问题来源
前文《C学习:100行C代码简洁实现wav读写并附demo》使用简单的44字节wav头实现了核心读写功能。
但在实际使用过程中,可能会遇到少部分非44字节头的wav格式文件,这里需要注意。
非44字节头主要分为3种情况:
-
46字节(数据为PCM,但chunk_size为18)
-
Non-PCM DATA,即采用u-law/a-law等量化
-
Extensible Format,比较少见
情况2和3见《论wav文件格式和wav读写(c代码)》中方法,第一是不要默认按44字节读写wav头,第二是将非data区数据跳过即可。
46字节wav头处理分析
这里结合前文C代码实现讨论pcm data下的46字节wav头处理注意事项。
如何分辨44byte还是46byte字节头?
- 44字节头的wav文件时,data字段在0x24-0x27地址中。而46字节头时,data字段则通常在0x26-0x29地址中。
- 46字节头时,fmt格式后的chunk_size为18,而44字节头则默认是16byte。
- 除此以外,两者头文件格式应无差异,且pcm字段格式的值(format_tag)应为1。
知道46byte字节头了,那要怎么改?
首先,相关默认44字节头的假设就有问题,需要在C代码里做结构体更换,并对文件移位时按实际字节头处理以便得到正确的pcm数据输入。
其次,在补齐结构体里多出来的2字节扩展位后(位置在fmt的末尾,data段前),要注意实现header结构体时,由于44byte能被4整除,结构体设置不会出现内存和数据不对应的问题。而46byte时,由于C代码机制有自动内存对齐,结构体实际内存46byte可能会补成48byte。
最后,通过指定46byte而非sizeof结构体(会出来结果48)读取数据,并对结构体data末尾datalength数据重新解释即可。
小结44byte到46byte的坑点:
- 数据段数据读取正确,跳过末尾的元数据
- 跳过前面的非data数据,如果有fact等字段数据的话。 (若是audition保存的wav,可以在另存为时,取消包含标记和元数据选项。)
- 注意C代码里结构体内部数据单元地址对齐自动增加的内存。 比如46字节大小的结构体,实际会补成48字节,导致读入的数据不符合预期在自动增补字节时,原C代码在读取datalength的时候就会因增补的字节错位,导致数据不对。
实践:处理46字节前文C代码修改
wav_io_basic.h
before
struct wave_header
{char riff[4]; /* "RIFF" */int32_t flength; /* file length in bytes */char wave[4]; /* "WAVE" */char fmt[4]; /* "fmt " */int32_t chunk_size; /* size of FMT chunk in bytes (usually 16) */int16_t format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */int16_t num_chans; /* 1=mono, 2=stereo */int32_t srate; /* Sampling rate in samples per second */int32_t bytes_per_sec; /* bytes per second = srate*bytes_per_samp */int16_t bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */int16_t bits_per_samp; /* Number of bits per sample */char data[4]; /* "data" */int32_t dlength; /* data length in bytes (filelength - 44) */
};
after
struct wave_header
{char riff[4]; /* "RIFF" */int32_t flength; /* file length in bytes */char wave[4]; /* "WAVE" */char fmt[4]; /* "fmt " */int32_t chunk_size; /* size of FMT chunk in bytes (usually 16) */int16_t format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */int16_t num_chans; /* 1=mono, 2=stereo */int32_t srate; /* Sampling rate in samples per second */int32_t bytes_per_sec; /* bytes per second = srate*bytes_per_samp */int16_t bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */int16_t bits_per_samp; /* Number of bits per sample */// newint16_t reserved0; // for 46byte and format_tag=1 is pcm, extension byte, chunk_size: 16->18char data[4]; /* "data" if reserved0 exits, data offset: 38 */int32_t dlength; /* data length in bytes (filelength - 46), 以为dlength offset is 42,但实际dlength前面自动补两字节便于四字节对齐,故dlength地址是44,from 46->48 */
};
wav_io_basic.c
before
// old 1
const int header_size_com = 44;/* Read function */
int WavGetHeader(FILE *fp, struct wave_header *wavh)
{ // load the header from the fileif ((int)fread(wavh, 1, sizeof(struct wave_header), fp) != header_size_com) {perror("Error loading header in read mode");return -11;}// after loading the header, we reach the data segmentreturn 0;
}// old 2wavh->chunk_size = 16; // default size of fmt definition
after
// new 1
const int header_size_com = 46;/* Read function */
int WavGetHeader(FILE *fp, struct wave_header *wavh)
{ // load the header from the fileif ((int)fread(wavh, 1, header_size_com, fp) != header_size_com) {perror("Error loading header in read mode");return -11;}// after loading the header, we reach the data segmentif (wavh->chunk_size == 18) { // means 44byte->46byteint *val = (int *)(&wavh->data[0] + 4);wavh->dlength = *val;}return 0;
}// new 2wavh->chunk_size = 18; // default size of fmt definition
以上代码修改完成后,可以正常读取46byte字节头的wav文件。
相关参考
- https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html
- https://stackoverflow.com/questions/19991405/how-can-i-detect-whether-a-wav-file-has-a-44-or-46-byte-header
- https://blog.argcv.com/articles/6850.c
- https://blog.csdn.net/book_bbyuan/article/details/110188074