AIFF和AIFF-C音频交换文件格式的简单介绍
正文
AIFF,全稱 Audio Interchange File Format,可簡寫為 Audio IFF 或 AIFF,是蘋果公司推出的一種音頻文件格式。
AIFF-C,是 AIFF 的擴充,C 意為 Compressed,說明這是一種可以存儲壓縮數據的格式,由蘋果公司進行擴展,可簡寫為AIFC。
由此可知,AIFF 和 AIFF-C 主要用于蘋果公司推出的設備和系統,但由于文件交換的需要,在 Windows 下偶爾還是會有使用的需求。
關于這個兩個格式的詳細文檔,可以從 這個頁面 下載,本文將基于這兩份文檔,進行一些簡單的介紹。
關于AIFF格式和相關的代碼,可在https://gitcode.net/PeaZomboss/learnaudios找到(include和source文件夾)。
AIFF
由于 AIFF 是未壓縮的格式,所以只能用來保存未經壓縮的 PCM 編碼格式,關于這種格式,本文不做介紹,需要的仔細搜索即可。
在具體介紹之前,先了解一些 AIFF 文件所用到的特殊數據結構:
- ID,與 WAV 格式的 Four Char Code 類似,使用四個可打印 ASCII 字符組成,其本質就是一個 long int
- pstring,Pascal 風格的字符串類型,由一個表示長度的字節和若干個字符構成,最大長度為 255
- extended,擴展精度的浮點數類型,是 IEEE 754 標準之一,用 80 bit 表示的浮點數,占用10個字節,是 Pascal 語言的基礎類型,但在x86下使用存在明顯問題,稍后詳解
注意:AIFF 的所有數據都按大端序存儲,在 Intel、AMD、ARM 等小端序的 CPU 中讀取需要進行轉換
如果對 WAV 格式比較熟悉的話,那么下面的內容也不難理解
首先介紹幾個基本 ID
#define AIFFIDFORM 'FORM' #define AIFFIDAIFF 'AIFF' #define AIFFIDCommon 'COMM' #define AIFFIDSoundData 'SSND'之所以說基本,是因為每個 AIFF 文件至少要用到這四個 ID,其余格式請查閱文檔
AIFF 文件按塊進行存儲,每個塊都由塊頭和塊數據組成,其中塊頭的定義如下:
typedef struct {long ID; // 這個是前面提到的 ID,記住是大端存儲的,在小端 CPU 下不能直接使用上面的定義long Size; // 塊數據的大小,不包括塊頭 } ChunkHeader;注意:所有塊都必須按2字節對齊,即使Size可能是奇數
接著介紹 AIFF 文件頭定義
typedef struct {long ID; // 這個必須是 'FORM'long Size; // 文件大小減去 Size 和 ID 的大小,即文件字節數減8long Type; // 這個必須是 'AIFF' } AIFFHeader;與 WAV 格式的 RIFF 定義十分相似,這個主要是用來標識文件類型用的
然后我們來看看 Common 塊的定義
typedef struct {long id; // 必須是 'COMM'long size; // 必須是 18short channels; // 聲道數,比如 1 代表單聲道,2 代表立體聲unsigned long SampleFrames; // 采樣幀數,每一個幀的大小取決于聲道數和位深度short SampleSize; // 位深度,即每個采樣的大小,用位表示,常見的有 16,24 等extended SampleRate; // 采樣率,常見的如 44100,48000 等 } CommonChunk;- 和 WAV 一樣,音頻數據是交錯存儲的,這意味著每個采樣幀都包含所有聲道的數據,所以總采樣數據大小等于采樣幀數*聲道數*位深度/8
- 位深度可能不為8的整數倍,但實際存儲按字節大小整數倍確定,比如位深度為12,實際每個采樣占用2個字節,其余位補零
- 由于c/c++語言并不支持extended類型,所以實際使用中需要將其轉化為單精度的float或者雙精度的double
最后說一下 SoundData 塊的定義
typedef struct {long id; // 必須是 'SSND'long size;unsigned long offset;unsigned long BlockSize;// 按 PCM 編碼的數據 } SoundDataChunk;- offset 和 BlockSize 主要用來對齊,一般情況下為0,很少遇到非0的,具體請參閱官方文檔
AIFC
AIFC 是 AIFF 的擴展,主要是添加了一個新塊 FormatVersion 塊,同時擴充了原來的 Common 塊
先介紹下新增的常量
#define AIFCVersion1 0xA2805140 #define AIFCIDAIFC 'AIFC' // 替換文件頭的 'AIFF' #define AIFCIDFVER 'FVER'新的 FormatVersion 塊
typedef struct {long ID; // 必須是 'FVER'long Size; // 4unsigned long TimeStamp; // AIFCVersion1,目前只有這個 } FormatVersionChunk;擴展的 Common 塊
typedef struct {long id; // 必須是 'COMM'long size; // 22 + CompressionName 大小short channels; // 和 AIFF 一樣unsigned long SampleFrames; // 和 AIFF 一樣short SampleSize; // 和 AIFF 一樣extended SampleRate; // 和 AIFF 一樣long CompressionType;// pstring CompressionName; // 可選 } AIFCCommonChunk;- CompressionType 有許多,可以在開頭的鏈接找到,下面介紹幾個常見的
- ‘NONE’,未經壓縮的,與 AIFF 完全一致
- ‘sowt’,數據是交換端序存儲的,對于小端 CPU 來說就不用轉換了
- ‘fl32’,數據是按照 IEEE 32位浮點數存儲的
- ‘fl64’,數據是按照 IEEE 64位浮點數存儲的
- ‘FL32’,與 ‘fl32’ 相同,可以理解為別名
- CompressionName 可選,用于指示壓縮方法名,即使有一般也可忽略
除了sowt類型,其余全部為大端
文件的讀取方法
- 所有的內容信息都要進行大小端轉換
- 音頻數據的大小端轉換取決于每個采樣的字節數
其他細節內容
蘋果曾經主要是使用 Pascal 語言進行開發的,后來才換了 C 系的語言,而 Pascal 原生支持 extended 浮點數類型,且在 x86 架構(包括64位)下得到完美的支持(除了Windows x64,這是由于微軟ABI的限制,故編譯器默認不支持,但可以使用匯編手動調用x87 FPU進行處理)。
但是C/C++(大多數語言)不支持extended類型,所以我們需要手動處理,這里介紹兩種方法,一個如何在x86架構的cpu進行操作,因為其余架構本人并不熟悉,另一個是通用解決方法,也就是根據浮點數的定義手動處理轉換。
我們首先定義 extended 類型結構體
typedef struct {unsigned char data[10]; } extended;關于大小端的轉換此處不再贅述,類似字符串的逆置
然后寫兩個函數,一個是 ExtendedToFloat,一個是 FloatToExtended,使用 double 同理,只需簡單的修改
以下內容只能在 MinGW-gcc 下編譯,在 MSVC 下需要修改,但原理一致,都是利用 x86 架構自帶的 x87 FPU,這個方法并不推薦,且有較大的局限性,故推薦看后面的通用方案
void ExtendedToFloat(const extended *e, const float *s) { #ifdef _WIN32 #ifdef _WIN64// Windows x64 下,前兩個參數分別在 rcx 和 rdx 中// 如果是 Linux x64,則前兩個參數分別在 rdi 和 rsi 中asm("fldt (%rcx)\n\t""fstps (%rdx)\n\t"); // AT&T 匯編風格下,如果是 double 則使用 fstpq #elseasm("movl 4(%esp), %ecx\n\t" // esp+4 是第一個參數"movl 8(%esp), %edx\n\t" // esp+8 是第二個參數"fldt (%ecx)\n\t""fstps (%edx)\n\t"); #endif #else #error "Do not support now" #endif }void FloatToExtended(const float *s, const extended *e) { #ifdef _WIN32 #ifdef _WIN64asm("flds (%rcx)\n\t" // AT&T 匯編風格下,如果是 double 則使用 fldq"fstpt (%rdx)\n\t"); #elseasm("movl 4(%esp), %ecx\n\t""movl 8(%esp), %edx\n\t""flds (%ecx)\n\t""fstpt (%edx)\n\t"); #endif #else #error "Do not support now" #endif通用方法如下(注意這里的尾數是截斷的,沒有舍入):
void ExtendedToFloat(const extended *e, const float *s) {unsigned char *ps = (unsigned char *)s;unsigned char *pe = (unsigned char *)e;unsigned short exponent;exponent = ((pe[9] & 0x7f) << 8 | pe[8]) - 16383 + 127;ps[3] = pe[9] & 0x80 | exponent >> 1;ps[2] = exponent << 7 | (pe[7] & 0x7f); // 實際上尾數的第一位被舍棄了ps[1] = pe[6];ps[0] = pe[5]; }void FloatToExtended(const float *s, const extended *e) {unsigned char *ps = (unsigned char *)s;unsigned char *pe = (unsigned char *)e;unsigned short exponent;exponent = ((ps[3] & 0x7f) << 1 | (ps[2] >> 7)) - 127 + 16383;pe[9] = ps[3] & 0x80 | exponent >> 8;pe[8] = exponent;pe[7] = 0x80 | (ps[2] & 0x7f); // 同理,尾數第一位要置位1pe[6] = ps[1];pe[5] = ps[0];pe[4] = 0;*((long *)pe) = 0; }void ExtendedToDouble(const extended *e, const double *d) {unsigned char *pd = (unsigned char *)&d;unsigned char *pe = (unsigned char *)&e;unsigned short exponent = (((pe[9] & 0x7f) << 8) | pe[8]) - 16383 + 1023;pd[7] = pe[9] & 0x80 | exponent >> 4;pd[6] = exponent << 4 | ((pe[7] >> 3) & 0xf); // 同理pd[5] = pe[7] << 5 | pe[6] >> 3;pd[4] = pe[6] << 5 | pe[5] >> 3;pd[3] = pe[5] << 5 | pe[4] >> 3;pd[2] = pe[4] << 5 | pe[3] >> 3;pd[1] = pe[3] << 5 | pe[2] >> 3;pd[0] = pe[2] << 5 | pe[1] >> 3; }void DoubleToExtended(const double *d, const extended *e) {unsigned char *pd = (unsigned char *)&d;unsigned char *pe = (unsigned char *)&e;unsigned short exponent = (((pd[7] & 0x7f) << 4) | (pd[6] >> 4)) - 1023 + 16383;pe[9] = pd[7] & 0x80 | exponent >> 8;pe[8] = exponent;pe[7] = 0x80 | pd[6] << 3 | pd[5] >> 5; // 同理pe[6] = pd[5] << 3 | pd[4] >> 5;pe[5] = pd[4] << 3 | pd[3] >> 5;pe[4] = pd[3] << 3 | pd[2] >> 5;pe[3] = pd[2] << 3 | pd[1] >> 5;pe[2] = pd[1] << 3 | pd[0] >> 5;pe[1] = pd[0] << 3;pe[0] = 0; }實際上這個方法在某種程度上也并不通用,因為不同平臺對80位浮點數的表示方法不一樣,但是由于Intel的x87 FPU出現時間較早,其使用的80位浮點數表示方式(即尾數第一位固定為1,而沒有省略)至少在AIFF文件已經成為了一種事實上的標準,幾乎所有的AIFF(AIFC)文件都是按照這種方式的。不過關于80位浮點數和x87協處理器的處理已經屬于歷史遺留問題了,沒有必要討論好壞,所以目前我們只需要用特點的代碼將其轉換到double類型使用即可。
有興趣可以看看這個鏈接https://blog.sina.com.cn/s/blog_6449050e0100p85z.html,對比了x86和MIPS的區別。
水平有限,敬請理解
更新記錄
2023-01-22:修正了關于extended浮點數的錯誤表述,修正了錯誤的代碼,添加了double類型的轉換,添加了代碼庫。
總結
以上是生活随笔為你收集整理的AIFF和AIFF-C音频交换文件格式的简单介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: amd显卡没有屏幕旋转快捷键,自己写一个
- 下一篇: Python 的xlrd库读取日期和数字