MP3是現在相當流行的一種音樂格式,全名為Moving Picture Experts Group Audio Layer III(MPEG-1 Audio Layer 3)。MP3使用失真壓縮的方式大幅降低儲存音訊資料所需的空間,大大增加它的可攜性。不過,雖然MP3採用失真壓縮,它還是保留了一定程度的音質,因此相當受大眾歡迎。然而,MP3的音訊編碼訊息,並沒有包含額外的文字資訊,我們只能從中得知MP3音訊的音訊格式(從MP3標頭中讀取)以及壓縮過後的聲音訊號。如果我們想要儲存或是讀取更詳細的訊息,例如MP3的歌曲名稱、專輯名稱甚至是專輯圖片,那就只能依靠安插在MP3資料前後的標籤訊息了!



MP3的標籤格式非常多樣,其中最常見的是ID3和APE兩種,而這兩種也是大部分的音樂播放器能支援標籤格式。雖然這些標籤格式都是用於內嵌在MP3的標籤格式,但它們的結構卻頗有差異,本文將在之後分別詳細介紹各標籤的結構。

ID3

ID3可說是每個音樂播放器都必須要支援的標籤格式,它較APE還更為流行。我們常聽到「MP3的ID3訊息」,就是在指ID3標籤訊息,它就好比數位相片幾乎都會使用的EXIF訊息,ID3簡直就是每個MP3檔案都應該具備的基本識別證!

ID3目前主要分為兩個版本,ID3v1與ID3v2,它們兩個雖然都是ID3,但結構幾乎完全不同。ID3v2較ID3v1有著更良好的擴展性,因為它的大小是可延展的,不像ID3v1為固定的128bytes,超過長度的訊息只能捨棄掉。因此ID3v2的重要性又比ID3v1 還大得多,是每個音樂播放器都應該支援的ID3版本。

以下,將分別介紹ID3v1與ID3v2。

ID3v1

ID3v1的起源

前面提到過MP3的音訊編碼訊息(Audio Data)並沒有辦法儲存有關這MP3檔案的文字訊息,因此在1996年的時候,有個名叫Eric Kemp alias NamkraD的人,他提出了一個名為Studio3的解決方案來處理這個問題。這方案執行起來十分簡單,就是在MP3檔案的結尾再添加一段很小的數據資料,而這段添加進去的資料儲存了對這個MP3檔案的一些基本訊息,如前面提過的歌曲名稱、專輯名稱等等,方便使用者進行辨識。這就是ID3v1標籤,現在由id3.org組織進行制訂與維護。

原始的ID3v1僅能儲存歌曲標題(Title)、演出者(Artist)、專輯名稱(Album)、年份(Year)、注釋(Comment)與風格(Genre)。後來由於CD逐漸流行且CD上的音樂大多會採用分割音軌(Track)的方式來區別曲目,讓使用者能快速選擇要聽第幾首的關係,因此ID3v1後來又衍生出能夠儲存音軌(Track)訊息的版本,即為ID3v1_1。

基本上ID3v1_1可向下相容於ID3v1,因此在實作程式上,僅需完成ID3v1_1就好。不過,在此還是會逐一演示ID3v1和ID3v1_1的結構。

ID3v1的結構

以上為ID3v1的結構,標頭(Header)用來提供給程式識別MP3檔案的尾端是否有ID3v1標籤存在。至於其他的欄位,除了Genre和Year外,皆是直接儲存文字資訊,且採用ISO-8859-1格式進行編碼(但有很多人會無視這項規定,用別的編碼方式,因此常常出現亂碼),如果編碼後的文字長度超過30 bytes,則必定會有部分文字無法儲存進去;未滿30 bytes則補0進去。Year欄位儲存四位數字,如西元2013年就儲存{'2','0','1','3'}進去。Genre欄位可儲存0~255的數值,每個數值分別對應一種不同的風格類型,列表如下:

ID3v1_1的結構

ID3v1_1的結構和ID3v1僅差在Comment欄位後又切出兩個bytes出來,如下:

經切割後Comment欄位剩下28 bytes,其後多出了1 byte的Reserve和Track欄位。Track欄位儲存音軌資訊,直接以數值來表示,如第一軌道就存1。另外,在此的Reserve欄位扮演著重要的角色,如果Reserve欄位的值為0,表示說Comment欄位中的資訊並未超過28 bytes,後面擁有的空間是儲存Track訊息,而不是Comment訊息,此時的Reserve欄位可說是一個分隔字元,區分開Comment和Track欄位,使它們不會被混雜在一起。前面有提到說ID3v1_1向下相容於ID3v1,其實就是當 Comment長度不超過28 bytes時,原本Comment的30 bytes欄位的最後兩個bytes即變成Reserve和Track欄位用以額外存取Track的訊息,增進空間使用率。然而,當Comment有超過28 bytes的資料時,很顯然的,這時候Reserve欄位的位置存放的資料就絕對不會是0,此時的Comment欄位就可以有30 bytes的空間來儲存完整的訊息,故在這個時候的標籤結構就如同ID3v1。當然,還是會有一種情況是「Comment的訊息超過28 bytes,且還有Track訊息需要存入」,一般來說,這時後應該要優先考量Track訊息使其能儲存進去,只保留Comment前面的28 bytes。

ID3v2

ID3v2的起源

由於ID3v1的固定長度設計實在是令人詬病,於是在1998年id3.org的一群貢獻者又商討出另一種標籤格式來解決這個問題,於是ID3v2就誕生了!ID3v2的制定速度頗快,從1998年的ID3v2(ID3v2_2)到現在位居主流的ID3v2_3格式只花了一年的時間。然而,事實上ID3v2的最新版本早在2000年就已經提升到ID3v2_4,但經過了10年的時間卻依然沒有許多的軟體能支援ID3v2_4,其中的原因筆者認為是ID3v2_4實作起來較為困難,且ID3v2_3已經足以應付大部分的狀況,因此沒必要再花功夫去支援ID3v2_4。

ID3v2_2

ID3v2_2為ID3v2最早的公開版本,但目前很少有程式支援,故以下不針對ID3v2_2做討論。

ID3v2_3

ID3v2_3於1999年制定,它與ID3v2_2最大的差異在於ID3v2_3使用的Frame ID為4個字元,例如「TIT2」;而ID3v2_2使用的Frame ID為3個字元,例如「TT2」。ID3v2_3還允許一個Frame擁有多個內容(以/來分隔)。為目前應用範圍最廣的ID3v2版本。

ID3v2_3的標籤訊息位於MP3檔案的開頭,長度是可變的。

像這樣把Metadata加到檔案開頭會有個很大的缺點,那就是當檔案大小改變的時候必須重寫整個檔案,會造成在寫入標籤時的效率過於低下,因此ID3v2_3提供了保留長度的欄位使我們在寫入標籤時能有個緩衝空間,才不至於每次更改標籤都得重寫整個檔案,如此便能增加程式的執行效率。

以上是ID3v2_3的結構,看起來很簡單,實作起來卻不容易呀!首先我們來看看 ID3v2的10 bytes Header欄位到底包含著什麼資訊。

ID3v2的標頭相較於ID3v1複雜了許多,唯一相同的地方是它們在最開頭的地方都有個讓程式識別字元的欄位,ID3v1存放的字元是TAG,而ID3v2則是存ID3。Version欄位描述了這個ID3v2的版本,因為這是ID3v2_3標籤,所以Version欄位數值永遠是 3,這是不變的。而其後的Revision標籤則儲存著ID3v2_3的修訂版本號,此欄位一般填入數值0即可。

而Flags欄位則存放著整個ID3v2標籤的旗標訊息。在ID3v2_3中僅用了前3個位元。我們可以將這個位元組寫成:abc00000。

  • a: 不同步(Unsynchronisation):決定這個標籤是否使用不同步。由於許多程式根本忽略這個旗標,一般設為0就好了。
  • b: 擴展標頭(Extended Header):決定這個標頭後面是否還有擴展標頭。
  • c: 實驗標籤(Experimental Indicator):決定這個標籤是否為實驗或測試用的標籤。

Size欄位存放的值為計算非同步標籤後(若Unsynchronisation旗標設定為0,則無需計算非同步),除了Header之外的所有欄位大小。其實就是整個ID3v2_3標籤的所有大小(Total Size)再減掉Header的10 bytes,才是Size欄位應該儲存的值。

ID3v2 Header的Size欄位的數值計算方式十分奇怪,他雖然用了32 bits(4 bytes)的空間來儲存資料,但實際上在計算的時候卻只用了28 bits,每個byte的最高位元(MSB,最左邊的位元)均不列入計算。假設Size欄位中存放著{byte[0],byte[1],byte[2],byte[3]}這四個bytes,它實際的數值之計算方式如下:

Real Size = ((byte[0]&0x7F)<<21) + ((byte[1]&0x7F)<<14) + ((byte[2]&0x7F)<<7) + (byte[3]&0x7F)

上式中的0x7F表示一個16進制的數值,7F(16) = 01111111(2),以這個8 bits數跟其他8 bits數值做AND(&)位元邏輯運算,可以視為一個遮罩(MASK),將原本的8 bits數值的MSB永遠變成0,且保留之後7個位元的值(X AND 0 = 0, X AND 1 = X)。「<<」是個位元位移運算子,能將二進位數值往左移動N個位元(寫成「<< N」),並把右邊空缺的位元以0來填充。如4 bits的數值1001,往左移動2個位元(寫成「0001 << 2」)後,數值將變成100100,但由於儲存空間為4 bits,因此捨棄掉移出儲存空間的左邊兩個位元後,最後的數值即是0100。如果我們將整個Size當成一個28 bits的數值(位元編號分別為:27 26...0),則byte[0]存放著第27~21個位元,byte[1]存放著第20~14個位元,byte[2]存放著第13~7個位元,byte[3]存放著第6~0個位元,由此我們便可知道這四個位元組究竟要往左位移多少個位元,再彼此相加後才是真正的Size數值。

若我們想要將一個32 bits的整數值轉換成4個bytes的數值儲存在Size欄位中,可以使用以下式子:

假設Size = 0000 abcdefg hijklmn opqrstu vwxyzAB(2)(雖然整數有32bits的空間但最多只能使用28 bits至Size欄位中)

byte[0] = (byte)((Size << 4) >>> 25) 00000000 00000000 00000000 0abcdefg
byte[1] = (byte) ((Size << 11) >>> 25) 00000000 00000000 00000000 0hijklmn
byte[2] = (byte) ((Size << 18) >>> 25) 00000000 00000000 00000000 0opqrstu
byte[3] = (byte) ((Size << 25) >>> 25) 00000000 00000000 00000000 0vwxyzAB

上式中的「>>>」是個位元位移運算子,能將二進位數值往右移動N個位元(寫成「<<< N」),並把左邊空缺的位元以0來填充,捨棄掉移出儲存空間的右邊位元。經位元位移運算後,即可正確地把28bits的數值分割存在4個bytes裡!

Header的結構基本上就是這樣,接下來看看Extended Header的結構。

Extended Header存放了許多Header沒有的進階訊息。一開始的Extended Header Size欄位儲存著這個Extended Header的大小,不包括Extended Header Size欄位本身的4 bytes,因此Extended Header的Size值可能是6 bytes或是10 bytes,而整個Extended Header的大小可能是10 bytes或是14 bytes。這裡的Size欄位計算方式不像Header欄位的Size那樣只能使用28 bits,而是整個32 bits都可以使用,計算方法請參考之後的Frames欄位說明。

Extended Flags欄位目前僅使用了第一個byte的最高位元(MSB)來記錄是否計算Frames的CRC-32資訊,用以檢查Frames的欄位訊息是否有缺損。我們可以將這個欄位表示成:x0000000 00000000,其中x就是指Extended Header最後是否擁有Total Frame CRC欄位。

Size of Padding欄位儲存著此ID3v2標籤的Padding欄位大小,同樣的也是以32 bits做計算,計算方式將在之後說明。

Total Frame CRC欄位儲存著Frames欄位在非同步前計算出來的CRC-32數值。以後便能以此CRC-32數值來比對檢查目前Frames欄位裡的資料是否完全正確。若Extended Flags並沒有開啟Total Frame CRC欄位,則此欄位無作用。

CRC-32為一種檢查數據正確性的方法,可將任何數據計算成一組唯一的數值,可利用比對CRC-32數值的方式,來快速判定兩數據的內容是否相同。由於CRC-32的計算方式繁雜,已超出本文範圍,故不說明。

介紹完Header和Extended Header後,來看看ID3v2的主幹──Frames欄位。Frames欄位結構如下:

Frames欄位存放著許多個Frame欄位,即為標籤欄位,且每個Frame欄位的大小都不一定相同。以下是Frame欄位的結構:

每個Frame欄位都擁有一個4 bytes的Frame ID欄位來識別其身分,當然,在ID3v2標籤中,是允許重複的Frame ID欄位存在的(標準規範:只有特定的幾種ID能擁有多個Frame),且使用者可任意自訂Frame ID(標準規範:僅能使用特定的 Frame ID,如要自定標籤欄位,須將訊息放置在Frame ID為TXXX或WXXX的Frame欄位中)。Frame ID的四個字元僅能以大寫字母A~Z或是數字0~9來組成。

Frame Size欄位存放著整個Frame除了Frame ID,Frame Size和Frame Flags欄位的大小,以32bits來計算。假設Frame Size欄位中存放著{byte[0],byte[1], byte[2],byte[3]}這四個bytes,它實際的數值之計算方式如下:

Real Size = (byte[0] << 24) + (byte[1] << 16) + (byte[2] << 8) + byte[3]

32 bits的整數轉換成4 bytes的數值儲存在Frame Size欄位中,可以使用以下式子:

byte[0] = (byte)(Size >>> 24)
byte[1] = (byte)((Size << 8) >>> 24)
byte[2] = (byte)((Size << 16) >>> 24)
byte[3] = (byte)((Size << 24) >>> 24)

Frame Flags存放著整個Frame的旗標訊息,共兩個位元組,格式可寫成:abc00000 ijk00000

  • a: 保留標籤(Tag Alter Preservation):設為1時,當檔案的 ID3v2 標籤內容被改變時,如果軟體不認識此Frame,則此Frame應該被丟棄。
  • b: 保留檔案(File Alter Preservation):設為1時,當檔案的Audio Data被改變時,如果軟體不認識此Frame,則此Frame應該被丟棄。
  • c: 唯讀(Read Only):設為1時,則此Frame的Content欄位不能被改變。
  • i: 壓縮(Compression):設為1時,此Frame的Content欄位資料使用zlib進行壓縮,且須將解壓縮後的資料大小(Decompressed Size)記錄在Frame的Content欄位之前,為 4 bytes 的數值。解壓縮大小以32 bits來計算。
  • j: 加密(Encryption):設為1時,此Frame的Content欄位為加密狀態。
  • k: 群組(Grouping identity):設為1時,此Frame跟其他的Frame為同一個群組。

以上一大串只是Frame Header的介紹,Frame真正的主體是其接下來的Content欄位。Content欄位儲存了Frame所搭載的標籤訊息或是其他設定訊息,不同的Frame ID擁有不同的Content讀取方式,不過最常用的還是Frame ID以T開頭的Text Frame了(例如TALB(Album),TPE1(Artist),TIT2(Title))!以下將會介紹Text Frame的結構。

原則上,Frame ID以「T」開頭的Frame就是Text Frame,其Content的結構如下:

Text Encoding存放了Information欄位資料使用的編碼方式,數值0表示使用ISO-8859-1;數值1表示使用了UTF-16(檔首含 BOM)。ISO-8859-1編碼方式為1字元1 byte,而UTF-16則為1字元2 bytes,這點在實作程式的時候需要注意。

Information欄位存放的資料可直接將byte陣列解碼還原出原始的Text文字資訊。若想將Text文字資訊寫入進Information,也必須先將其編碼轉為byte陣列後再儲存進去。

前面提到:原則上,Frame ID以「T」開頭的Frame就是Text Frame,但在這裡有個特殊的例外,那就是Frame ID為TXXX的Frame。TXXX Frame用來儲存使用者的自定義文字標籤,在標準規範中允許多個TXXX Frame存在於同一個ID3v2標籤裡,它的Content欄位結構如下:

這裡的Text Encoding欄位和前面提到過Text Frame的Text Encoding欄位類似,只是在此使用到Text Encoding欄位裡儲存的Encoding資訊的欄位有兩個,那就是接下來的Description和Value欄位。

Description欄位儲存了標籤名稱,Value欄位儲存了標籤內容。因為這兩個欄位的大小都不是固定的,因此在Description欄位之後會有一個0或是兩個0來區隔開 Description和Value欄位。至於究竟是要一個0還是兩個0,那就看Text Encoding欄位使用了什麼編碼格式,ISO-8859-1因為一次讀取一個byte,故結尾用1個0;UTF-16因為一次會讀取2個bytes,故結尾用2個0。

基本上,若要在 ID3v2_3標籤中新增自定的Text Frame,照標準規範就只能靠新增TXXX Frame的方式去設定Description和Value欄位,來表示新的標籤資訊。

ID3v2的Content欄位內容格式多樣,由於篇幅關係在此先說明Text Frame和TXXX Frame的Content格式,其餘還有一些不同的Content格式請有興趣的讀者自行參考官方的文件吧!以下將繼續介紹ID3v2_3的最後一個欄位。

Padding欄位是ID3v2_3的最後一個欄位,它真的十分簡單,因為它的內容完完全全是由數值0來組成。Padding的用途是加長ID3v2標籤的長度,至於原因前面有稍微提到過ID3v2_3 Metadata是寫在Audio Data之前,因此若是ID3v2_3長度有任何更動,就必須重新寫入整個檔案。若檔案有10MB資料,就得重新寫入10MB資料,一般ID3v2標籤不含圖片資料的話也不會超過1KB,為了寫這不到1KB的資料而重寫整個10MB檔案,實在是非常不值得的事情。ID3v2的Padding欄位就是為了減少檔案重寫的狀況發生,ID3v2標籤的編輯程式可以利用Padding欄位來將較長的ID3v2資料先寫入檔案開頭,這樣日後需要更動到ID3v2的標籤內容時,若新ID3v2的內容大小不超過原先的ID3v2大小,那麼程式只須重寫檔案開頭這段不到1KB的資料,如此一來執行效率會好很多。

ID3v2_3的說明到此結束,以下將繼續ID3v2_4的說明。

ID3v2_4

ID3v2_4於2000年制定,它與ID3v2_3最大的差異在於ID3v2_4增加了Footer欄位,使其可置於檔案的開頭或是結尾(ID3v2_3僅能置於檔案開頭),且加入了UTF-16BE和UTF-8編碼方式。其他還有許多地方ID3v2_4與ID3v2_3差異頗大,故在程式實作上要使ID3v2_3和ID3v2_4彼此相容頗為困難。

當ID3v2_4置於檔案開頭時,為了節省空間,原則上無須再加入Footer欄位,其在檔案裡的位置與ID3v2_3無異。

而當ID3v2_4置於檔案結尾時,應該要有Footer欄位,而且如果檔案也存在著ID3v1標籤,ID3v2_4應該放在ID3v1之前。

ID3v2_4雖解決了ID3v2_3僅能存在於檔案開頭造成寫入效率低落的問題,可也讓它的結構比ID3v2_3還複雜許多。部分與ID3v2_3重覆的地方,就不再作說明。

以上是ID3v2_4的結構,以下一一詳細說明。

首先是Header欄位,ID3v2_4的Header和ID3v2_3結構完全一樣,只差在版本號以及旗標的內容還有Size的算法,下圖是Header的結構:

File ID、Revision欄位的用途都和ID3v2_3完全一樣,而Version欄位的內容則變成4。Flags欄位則在第四個位元多了一個Footer旗標,表示成abcd0000:

  • a: 不同步(Unsynchronisation):決定這個標籤是否使用不同步。由於許多程式根本忽略這個旗標,一般設為0就好了。
  • b: 擴展標頭(Extended Header):決定這個標頭後面是否還有擴展標頭。
  • c: 實驗標籤(Experimental Indicator):決定這個標籤是否為實驗或測試用的標籤。
  • d: 檔尾欄位(Footer Present):決定這個ID3v2_4標籤是否含有檔尾欄位。

Size欄位存放的數值同樣是以28bits作計算,其大小為除了Header和Footer欄位之外的大小。以整個ID3v2_4的大小來算,若有Footer欄位,則Size = Total Size – 20 bytes;若沒有Footer欄位,則Size = Total Size – 10 bytes。

Header的結構基本上就是這樣,接下來看看Extended Header的結構。

ID3v2_4的Extended Header的結構跟ID3v2_3完全不同。Extended Header Size欄位儲存著整個Extended Header的大小,數值以28 bits來計算。

Number of Flag Bytes儲存著Extended Flags欄位的Flags數量,在ID3v2_4 中,Number of Flag Bytes欄位的值永遠為1,這數字代表著Extended Flags 欄位只有1組(1 byte)的旗標資訊。ID3v2_4之所以會使用這種可擴展的旗標結構,筆者認為在ID3v2_4之後的版本還是會沿用原來的Extended Header的結構,且到時候可能會需要更多的旗標。

在講Extended Flags欄位前,先來說明Flags Data欄位的用途。Flags Data欄位的結構如下:

Flags Data欄位包含了許多的Flag Data欄位,數量要視Extended Flags欄位啟用(旗標數值為1)了多少的旗標而定,Flag Data欄位在Flags Data欄位裡的順序就是Extended Flags欄位裡旗標的順序,而未設定為啟用的旗標(數值為0),則沒有 Flag Data欄位。Flag Data欄位的結構如下:

Flag Data Length欄位存放著Flag Data Content欄位的大小,例如:假設Flag Data Length欄位存放的數值是1,則Flag Data Content欄位擁有1 bytes的大小。而Flag Data Content欄位存放著設為啟用的旗標使用到的額外資訊。

回到Extended Header的Extended前只有一組旗標,可表示成0bcd0000:

  • b: 標籤是一個更新(Tag is an update):使用這個ID3v2標籤裡的Frame來更新前面讀取到的ID3v2標籤對應的Frame。就是當程式要讀取前面標籤裡的Frame時,若在之後的更新標籤也擁有該Frame的話,那麼讀取到的內容應為更新標籤的Frame之內容。不過通常應該是不會出現在同一個檔案裡有多個ID3v2標籤的情況。這個旗標開啟時產生的Flag Data,其Flag Data Length欄位數值為0,表示它沒有Flag Data Content 欄位。
  • c: 含有 CRC 資料(CRC data present):這個標籤是否包含著Frames和Padding欄位所計算出來的CRC-32數值,此數值會存成35 bits,共5 bytes,跟28 bits的Size數值一樣,每個byte的MSB不使用。這個旗標開啟時產生的Flag Data,其Flag Data Length欄位數值為5,表示它有5 bytes的Flag Data Content欄位可儲存5 bytes的CRC數值。
  • d: 標籤限制(Tag restrictions):對寫入標籤進行限制,所以寫入標籤時必須要檢查這個旗標以及其Flag Data欄位。這個旗標開啟時產生的Flag Data,其Flag Data Length欄位數值為1,表示它有1 bytes的Flag Data Content欄位,為一組旗標,可表示成ppqrrstt:
    • pp: 標籤大小限制:
      • 00: 128以下的Frame數量,1MB以下的總標籤大小(Total Size)。
      • 01: 64以下的Frame數量,128KB以下的總標籤大小。
      • 10: 32以下的Frame數量,40KB以下的總標籤大小。
      • 11: 32以下的Frame數量,4KB以下的總標籤大小。
    • q: 文字編碼限制:啟用時只能使用ISO-8859-1和UTF-8進行編碼。
    • rr:文字大小限制:
      • 00: 沒有限制。
      • 01: 字串的長度不可以超過1024個字元。
      • 10: 字串的長度不可以超過128個字元。
      • 11: 字串的長度不可以超過30個字元。
    • s: 圖片格式限制:啟用時只能使用PNG或是JPEG來對圖片進行編碼。
    • tt: 圖片大小限制:
      • 00: 沒有限制。
      • 01: 圖片大小不能超過256x256像素。
      • 10: 圖片大小不能超過64x64像素。
      • 11: 圖片大小只能為64x64像素。

ID3v2_4和ID3v2_3的Frames欄位結構一樣,參考下圖:

Frames欄位存放著許多個Frame欄位,即為標籤欄位,且每個Frame欄位的大小都不一定相同。以下是Frame欄位的結構:

ID3v2_4的Frame ID和Frame Size欄位與ID3v2_3一樣,而Frame Flags欄位則多了幾個旗標可使用,可表示成:0abc0000 0h00kmnp。

  • a: 保留標籤(Tag Alter Preservation):設為1時,當檔案的ID3v2標籤內容被改變時,如果軟體不認識此Frame,則此Frame應該被丟棄。
  • b: 保留檔案(File Alter Preservation):設為1時,當檔案的Audio Data被改變時,如果軟體不認識此Frame,則此Frame應該被丟棄。
  • h: 群組(Grouping identity):設為1時,此Frame跟其他的Frame為同一個群組。
  • c: 唯讀(Read Only):設為1時,則此Frame的Content欄位不能被改變。
  • k: 壓縮(Compression):設為1時,此Frame的Content欄位資料使用zlib進行壓縮。
  • m: 加密(Encryption):設為1時,此Frame的Content欄位為加密狀態。
  • n: 非同步(Unsynchronisation):設為1時,此Frame為非同步。
  • p: 資料長度指標(Data length indicator):設為1時,此Frame有資料長度指標。

Content欄位則是在Text Encoding中多了兩種編碼方式:

  • 0: ISO-8859-1
  • 1: UTF-16(檔首有高、低位元位置)
  • 2: UTF-16BE(檔首無高、低位元位置,高位元在前)
  • 3: UTF-8

ID3v2_4的Padding欄位跟ID3v2_3一樣,都是用來擴充ID3v2標籤的大小,使它置於檔案開頭時能有個緩衝空間,增加寫入效率。而新增的Footer欄位結構和Header一樣,只是差在File ID的辨識字元變成3DI。當ID3v2_4置於檔案結尾時,應該要有Footer欄位使程式能從檔尾找到ID3v2_4標籤。

ID3v2_4實作起來較ID3v2_3複雜許多,如今依然只有部分播放器能支援,而且這些播放器對於 ID3v2_4的實作結果也不盡相同,常常出現ID3v2_4無法跨播放器支援的情形。對於如此情況,筆者也無從解釋誰對誰錯,也許是官方的ID3v2_4文件寫得不夠清楚,造成大家理解上的誤差,筆者在模糊地帶的部分在本文也僅一筆帶過,有興趣實作ID3v2_4的朋友們,建議先將基本功能寫出來後,再考慮是否要加入進階功能(如加密、非同步、CRC32 檢查、群組等等多數使用者用不到的功能)。ID3v2_4的介紹就到此結束。

APEv2

APEv2的起源

APEv2的前一版本APEv1首先被用在Monkey's Audio格式(就是我們常聽到的APE無損格式)。但由於APEv1僅支援ASCII編碼,且僅有Footer而無Header,雖然長度能擴展,但使用起來依然非常不方便,所以在後來有個名叫Frank Klemm的人將Header加入了APEv1中,使它能置於檔案開頭或結尾,且改採UTF-8編碼方式,再讓它能支援更自由的欄位格式,於是APEv2便出現了!APEv2起初被用在Musepack音樂檔中,後來因為它簡單好用的結構,被移植進MP3音樂檔案裡,成為現在MP3標籤的主流格式之一。

但MP3已經有ID3標籤了,APEv2標籤要放在哪裡呢?

以上兩個分別是APEv2 Metadata置於檔尾與檔首的位置圖示,這是在沒有其他ID3標籤的情況之下加入APEv2標籤於MP3檔案中,看起來都沒什麼大問題。接著我們再來看看APEv2和ID3v1、ID3v2共存的時候,APEv2應該要擺在哪個位置比較好呢?

上圖顯示ID3v1和ID3v2同時存在於一個MP3檔案時的狀況,大致可分為兩種案例:第一種是 ID3v2標籤置於檔案開頭,ID3v1標籤置於檔案結尾;第二種是由前面提到過可置於檔尾的 ID3v2_4和ID3v1都置於檔案結尾。在這種有些欄位長度無法確定的時候,APEv2標籤應該要置於哪個地方比較合適呢?

先從第一種案例來看,若我們直接將APEv2插入在檔案最開頭的位置,那麼我們之後想讀取 ID3v2標籤的時候,就必須先計算APEv2標籤的大小才能找到我們要的ID3v2標籤,如此一來程式執行效率將不太好。同理,若將APEv2插入ID3v2和Audio Data之間,也會造成需要先計算ID3v2大小才能找到APEv2的狀況。故在第一個案例中,我們絕對不能考慮把APEv2加入檔案的開頭。不加到檔案開頭就只能加到結尾,可是究竟要加在ID3v1前面還是後面比較好呢?大家可以想想看,當我們把APEv2加到ID3v1之後的時候,我們之後想讀取ID3v1標籤,是不是也要去計算APEv2的大小才能從檔案尾端讀取到ID3v1標籤呢?而ID3v1是固定長度的標籤,若APEv2在ID3v1之前,我們可以很快速的找到APEv2位於MP3檔案中的位置。

再來談談第二種案例,很明顯地,此時APEv2應該要加在檔案開頭比較適當。當然,事實上,為了能夠統一這些標籤存放的位置,我們很少會一下子將APEv2擺到檔案開頭,一下子卻又將它擺到檔案結尾。一般來說,我們會將ID3v2置於檔案開頭,將APEv2與ID3v1置於檔案結尾。原因在於前面提到過的,ID3v2有獨立的Padding欄位可以快速擴展ID3v2的長度,增加標籤的寫入效率,而APEv2則無此機制,因此將APEv2一律置於檔案結尾是比較明智的選擇。經過以上分析得出來的結論,我們可以畫出ID3與APEv2在一個MP3檔案裡最常見的存放位置:

APEv2的結構

花了不少的篇幅講解了APEv2在MP3檔案裡的最佳位置,接著我們來單獨看看APEv2標籤的結構吧!

APEv2的結構真的是十分簡單,首先我們來看看它的Header和Footer。實際上,APEv2的Header和Footer的結構可說是完全一樣,兩者只差了1 bit的旗標內容,用以辨識此欄位是 Header或是Footer。不管是Header還是Footer,使用者都可自行決定APEv2標籤是否要擁有這兩個欄位,但基本上這兩個欄位在APEv2標籤中必須存在至少一個,當然最好的情況還是Header和Footer欄位皆在APEv2標籤中。

Preamble欄位中存放著APEv2的識別字元APETAGEX。Version Number存放的數值為APE標籤的版本,1000為APEv1.000,2000為APEv2.000。講到這裡要稍微停頓一下,來先說明APEv2的數值計算方式:

APEv2的數值計算方式和ID3v2差異頗大,雖然不像ID3v2的Header那樣僅用了28 bits,而是完完整整地用了32 bits來表示整個數值,不過APEv2的32 bits數值最高位元並不像ID3v2那樣是在byte[0]中,而是在byte[3]。說得更清楚一點,現在有4個bytes的數值,分別為{byte[0],byte[1],byte[2],byte[3]},若要將這4 bytes數值組合起來轉成一個32 bits的數值,位元編號分別為31 30 29 ... 0,第31~24個位元就為byte[3],第23~16個位元為byte[2],第15~8個位元為byte[1],第7~0個位元為byte[0],可寫成算式如下:

Value = (byte[3] << 24) + (byte[2] << 16) + (byte[1] << 8) + byte[0]

而要從32 bits數值轉成4 bytes數值的話,可以使用以下式子:

byte[0] = (byte) ((Value << 24) >>> 24)
byte[1] = (byte) ((Value << 16) >>> 24)
byte[2] = (byte) ((Value << 8) >>> 24)
byte[3] = (byte) (Value >>> 24)

APEv2所有的欄位數值都是以上面這種方式進行計算。

接下來的Tag Size欄位,存放著APEv2除了Header欄位之外的大小。

Item Count欄位,存放著整個APEv2標籤擁有的Item欄位數量。

Tag Flags欄位,存放著APEv2標籤使用到的旗標,共有{byte[0],byte[1],byte[2],byte[3]}這4個bytes。以下是各個位元組的各個位元所表示的旗標項目:

  • byte[0]: 0000000c
    • c: 此APEv2標籤是否不能被更改。
  • byte[1]: 00000000
  • byte[2]: 00000000
  • byte[3]: def00000
    • d: 此APEv2標籤是否含有Header。
    • e: 此APEv2標籤是否沒有Footer。
    • f: 此Tag Flags欄位是否在Header欄位中。

而Tag Flags欄位之後的Reserved欄位APEv2標籤目前還沒有使用到,必須全部填0。

APEv2的Header(Footer)欄位結構大致上便是如此,接下來說明Item欄位的結構。

Item Value Size欄位存放著這個Item的Item Value欄位大小。

Item Flags欄位存放著這個Item所使用到的標籤,共有{byte[0],byte[1],byte[2],byte[3]}這4個bytes。以下是各個位元組的各個位元所表示的旗標項目:

  • byte[0]: 00000abc
    • ab:
      • 00: 這個Item存放著Text文字訊息,Item Value欄位使用UTF-8進行編碼。
      • 01: 這個Item存放著Binary二進位訊息,Item Value欄位無須編碼。
      • 10: 這個Item存放著位址訊息(例如:檔案位址、網址),Item Value欄位使用UTF-8進行編碼。
      • 11: 保留,尚未被APEv2使用。
    • c: 這個Item是否不能被更改。
  • byte[1]: 00000000
  • byte[2]: 00000000
  • byte[3]: 00000000

Item Key欄位存放著這個Item所表示的標籤名稱,如Title、Album、Artist,並採用ASCII編碼(每個字元值範圍在 0x20~0x7E)。因為這個欄位的文字長度不固定,所以必須再以一個0來與Item Value作區隔。

Item Value欄位存放著這個Item所表示的標籤內容,其內容的類型必須參考之前Item Flags欄位的ab旗標來決定,才能正確地讀取與寫入。

APEv2標籤的結構較ID3v2簡單易懂,基本上就這個樣子,APEv2的說明到此結束。