1. 程式人生 > >iOS CoreAudio學習筆記(二)—— The Story of Sound

iOS CoreAudio學習筆記(二)—— The Story of Sound

在上一章,我們初次嘗試了CoreAudio API:它提供了什麼以及怎樣呼叫它的函式。現在是時候往回一步來看看一張更大的圖:一開始CoreAudio訪問的問題。

這一章將介紹基礎的聲音科學,它是什麼,它怎樣工作。事實證明,計算機的數字化天性使它們並不那麼適合處理連續的模擬訊號。這引導了對訊號取樣的思想,或者將平滑的聲波斬為頻率足夠大的離散值,而人耳無法注意到差別。這一章覆蓋了這些取樣在數字化形態中是怎樣被表示和整理分類的。

Making Waves

如果你在人行道上猛推一個人,那個人就會動一下而已。但是如果你在一個擁擠的地方猛推一個人,比如在音樂會上,他會碰到他前面的人然後彈回他原來的位置。由於你推的力在人群中傳遞,這將會觸發一個連鎖反應,最終穿過房間打到你的朋友。你剛剛通過一群生氣的代理推了你的朋友。

在科學術語裡面,一群足夠近的相互反彈的物體被叫做介質,能量可以通過它們傳遞。你推的那個人的運動(一開始向前,然後向後)稱為一個迴圈。完成一個迴圈的時間叫做週期。如果你反覆地推一個人,人們相互反彈的這種模式就是一個壓縮的波。

這個波有兩個屬性。你推這個波的力形成了波的振幅。你推這個波的速度形成了波的頻率。你越頻繁地推,這個波的頻率就會越高。如果你改變你推的振幅和頻率,你的朋友將會感受到這樣的變化。你不再只是生產波,而是在通過介質傳遞資料。

當你對某人說話,你的聲帶來回移動或者振動,推動空氣中的原子。這些原子相互反彈就像音樂會裡面的人一樣,直到它們擊中你朋友的鼓膜。揚聲器和麥克風就是這樣做的。聲音就是通過介質傳遞的能量而已。

要錄音的話,你所需做的就是描述聲音使膜振動的方式。要播放聲音的話,你需要像提供的描述那樣讓一個膜振動。一種描述聲音的方式是基於時間畫出膜的位置,如圖所示。
與振動的膜位置相關的聲波

Figure 2.1

圖中央的橫線代表膜靜止時的狀態。頂部和底部代表膜位移的最大值。隨著時間從左到右移動,膜的位置一上一下。y軸代表振幅最大值的百分比,正值和負值來回變化的頻率代表聲音的頻率。

一種表示這段聲波的方法是把它的影象雕刻成一些物理物件。這種技術叫做模擬記錄法,它的好處在於它將產生一個非常精確的複製品。而它的缺點在於錄音會像它描述的聲波那樣不精確。這樣的不精確來源自複製的同時播放。它同樣不相容計算機,因為計算機需要的東西要有精確的數值計算來描述。

Digital Audio

對於計算機,你需要把波通過數字來表示。你可以沿著波的路徑繪製一系列(x,y)座標點來近似這段波。如果你提供了足夠多的點,你將會得到一個不錯的展現。一個標準叫做脈衝編碼調製(pulse code modulation, PCM) ,它每隔一段規律的時間記錄一次y值,意思是x值(時間)是隱式的。其最常見的形式是線性脈衝編碼調製,你指定一個代表y值最大值百分比的值。舉個栗子,如果一個圖表示的值從0到1,給出的一個點的y值是最大值的一半,那麼你指定的值就是0.5。這種在規律的時間間隔的時候指定一個值的處理叫做取樣。每個值是它本身的一個取樣。

CD音質的音訊取樣率為44.1 kHz,也就是每秒44100個取樣。一種看清這個的方法是認識到CD音質的音訊每秒有44100個少於23微秒的間隙。沒有任何資料存在於這些間隙中,在這段時間發生在聲波上的任何東西都消失了。下圖闡述了取樣率是如何影響資料精確地模擬聲波曲線的能力的。三張圖都代表了Figure2.1的波,但是削減了取樣率。如你所見,使用更少的取樣會使得聲波表示得更不精確。事實上,t=2.5的峰值和t=2.1的谷值以及t=4.4的資料在最後一張圖中完全遺失了。
a
b
c

Figure 2.2 某取樣率下的聲波模擬

你可以通過增加更多的取樣模擬的更加精確,但是知道存在一些“足夠好”的粒度是很有幫助的。找到一個足夠好的模擬的關鍵在於你聽到的不是個別的取樣,而是通過空氣推出的聲波。你聽到的是頻率,振動的重複形式,無論是從吉他和絃還是喉嚨發出來的。你需要弄明白需要多快的取樣來再現這些振動頻率。

事實上你可以從奈奎斯特-夏農取樣定律(Nyquist-Shannon Sampling Theorem)中找到這個數字,這個定律說,如果你有一個一個函式表示沒有頻率能高過B Hz,那麼你可以用間隔1/(2B)秒分開的點精確地呈現它。對於音訊,這就意味著要再現任何頻率,你需要用兩倍或更高頻率取樣訊號。

這就解釋了為什麼CD音質的音訊的取樣率為44.1 kHz。它的一半是22.05Hz,超出了絕大部分人類可以聽到的頻率。感知到高頻率聲音的能力隨著年齡的增長而惡化。年輕人差不多能聽到20kHz,而一箇中年人可能只聽得到14或者15kHz。所以通過44.1kHz的取樣,你可能會丟失一些資訊,但是不用擔心,真正重要的其實是聽眾在音訊訊號中感知到振動頻率的能力。

每一個取樣代表了波的振幅,或者膜的位移,作為最大可能值的百分比。使用百分比的好處在於硬體獨立性。一半就是一半,直接忽視什麼被折半了。

不像整數那樣越大的數需要更多的數字來表達,分數需要更多的數字來表達更小的數。寫100比寫10需要更多的數字,但是寫1/100要比寫1/10需要更多的數字。一個取樣擁有多少數字叫做它的位深(畫素深度)。如果兩個聲音的差距(最大可能位移的百分比)比取樣擁有的數字還小,那麼這個差距就將會丟失。

位深(每個取樣擁有的bit量)乘以取樣率(每秒取樣的量)得到位元率(每秒bit量)。這就描述了音訊1秒鐘需要多少個bit。更高的位元率會提供更高質量的錄音,但是那也意味著硬體需要儲存和處理更多的bit。

數字高保真的根本問題在於通過給定限制的硬體找到最好的近似值。每一個不同的格式都是一系列不同的妥協解決這個問題的方法。不僅在數字音訊中,更廣泛地,舉個栗子,在數碼相片中同樣有這麼多問題,對於相同的格式字母湯,每種格式提供自己的解決方案。

在數字影象中,取樣率被轉換成了畫素值,而位深則轉換成了每畫素的顏色值。增加一個bit將會得到兩倍的收益。在圖2.3中兩個相鄰影象的差距為1bit。
數碼相片的位深和取樣率

Figure 2.3

一個瑣碎的實現細節是,這個比喻只適用於灰度影象。計算機不能像人類看見的那樣確切地顯示顏色畫素。每一個顏色畫素點由紅、綠和藍組成。每一點又需要它們自己的資料集,叫做通道。大多數影象格式將每一個通道的取樣結合成一坨表示單個畫素。

數字音訊也存在這些問題。一段單聲道聲波就像一張灰度圖一樣,但是許多聲音系統都有多道發聲器。就像畫素需要紅色、藍色和綠色作為通道一樣,立體聲需要左聲道和右聲道。環繞聲增加了額外的通道。一個典型的5.1環繞聲訊號有6個通道:左右聲道來處理前和後;一箇中心通道;一個無方向通道來處理低頻效果,比如貝斯。

和它們的圖形同胞一樣,音訊格式把每個通道的一個取樣結合成一坨,稱為幀。鑑於一個畫素代表在空間中一個區域裡面所有的顏色通道,一幀代表在時間中一個時刻所有的音訊通道。所以對於單聲道而言,一幀只有一個取樣;而對於立體聲則有兩個取樣。如果你把多通道的聲音放入一個流,則它們被稱為交錯模式 。對於播放這是很常見的:因為你想要同時讀取所有的通道,有理的做法就是安排資料簡單地這樣做。然而,當處理音訊(比如新增效果或者做一些其他的訊號處理的時候)時,你可能想要非交錯模式的音訊,那麼你就可以分離地關注每一個通道了。

一些音訊格式將許多幀結合成分組。這一概念完全是一個給定的音訊格式建立和代表在該格式的一個不可分割的單元。LPCM不使用分組,但是壓縮的音訊格式則會使用,因為它們使用隨一組取樣的數學技術來獲得它們的近似值。一個給定的取樣可以經常被預測成某個音階,通過那些圍繞著它(音階)的,所以壓縮格式能使用取樣組,安排為幀,來通過比無損的源LPCM更少的資料生成相似的(如果不是完全相同的話)波。

我們之前提到了位元率,在某個格式中用來表示音訊一個給定時間的週期的資料量。對於PCM,比率是常量:CD音質的音訊的位元率為1411200bits每秒或者1411kbps,因為它有2通道16位*44100每秒的取樣。PCM擁有一個常量位元率因為對於一個給定通道數量、位深和取樣率的組合,資料率永遠不會改變。有損格式經常使用可變位元率* ,意味著需要壓縮的資料的任意特定部分的資料量會發生變化。CoreAudio支援可變位元率格式:對於任意給定幀的資料量可能是不同的,而分組則保留相同大小。CoreAudio同樣支援可變分組率,這樣甚至分組率也可能改變。然而,目前沒有可變分組率格式被廣泛使用。

關心這個區別的一個原因在於恆定位元率(CBR)資料有時採用比可變位元率(VBR)更簡單的API呼叫。舉個栗子,當你從一個檔案或者流裡面讀音訊資料的時候,VBR資料提供給你一個數據塊和一個分組描述的陣列來幫你弄清楚哪些取樣在什麼時候去了。而對於CBR這就不是必要的了,每一幀的資料量都是不變的,所以你可以通過幀大小乘以時間得出一個給定時間的取樣。

DIY Samples

我們已經談論了大量關於取樣和音訊波。你自己來寫一些東西可能有助於理解音訊取樣。

我們將再次使用Audio File Services,這一次建立一個檔案然後寫一些原生的取樣到裡面去。建立一個命令列工具工程取名為CAToneFileGenerator。新增AudioToolbox.framework到工程裡面然後重寫main.m:

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

// 把檔名define到一個巨集裡面,這樣一會可以改名字。
// 這裡我們建立一個方波,是最簡單的一種波的形式
#define FILENAME_FORMAT @"%0.3f-square.aif"

#define SAMPLE_RATE 44100               // define一個44100取樣每秒的取樣率
#define DURATION    5.0                 // define你想要建立多少秒的音訊

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        if (argc < 2) {
            printf ("Usage: CAToneFileGenerator n\n(where n is tone in Hz)");
            return -1;
        }

        // 和上次一樣,我們需要一個命令列引數
        // 這一次是一個浮點型數作為你想要生成的音的頻率
        // 如果你想要執行這個程式,就要到Scheme Editor裡面去像上次那樣設定引數。
        // 你可以把音符頻率設定成261.626,這是鋼琴上中央C的頻率,或者440,是在C之上的A(叫做中央A)
        double hz = atof(argv[1]);   
        assert(hz > 0);
        NSLog(@"generating %f hz tone", hz);

        // 這兩行程式碼生成一個檔案路徑,使用了我們的巨集和頻率來生成名字檔名,比如261.626-square.aif
        // 然後它們來生成一個NSURL因為Audio File Services函式要的是URL而不是檔案路徑
        NSString * fileName = [NSString stringWithFormat:FILENAME_FORMAT,hz];  
        NSString * filePath = [[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:fileName];
        NSLog(@"%@",filePath);
        NSURL * fileURL = [NSURL fileURLWithPath:filePath];

        // prepare the format
        // 要建立一個音訊檔案,你必須要提供一個這個檔案包含的音訊的描述。
        // 你要用到的可能是CoreAudio最重要和最常見的資料結構,AudioStreamBasicDescription
        // 這個結構體定義了一個音訊流最普遍的特徵:它有多少聲道,它在什麼格式下,位元率等等
        AudioStreamBasicDescription asbd;  

        // 在有些情況下,CoreAudio會為一個AudioStreamBasicDescription填充一些區域而你在程式設計的時候完全不知道
        // 要這樣做,這些區域必須被初始化為0。作為一次普通的實踐,請一直要在設定它們任何一個之前使用memset()把ASBD的區域清空
        memset(&asbd, 0, sizeof(asbd)); 
        // 接下來的8行程式碼使用ASBD各自的區域來描述你要寫入檔案的資料。
        // 這裡,它們描述了一個流:
        // 只有一個聲道(單聲道)的PCM,資料率為44100
        // 使用16位取樣(再說一次,和CD一樣),所以每一幀為2個位元組(1聲道*2位元組的取樣資料)
        // LPCM不使用分組(它們只對可變位元率格式有用)所以bytesPerFrame和bytesPerPackt相等。
        // 其他針對一個音符的區域是mFormatFlags,它的內容是不同的,基於你用的格式
        // 對於PCM,你必須表明你的取樣是大端模式(位元組或者文字的的高位在數字上對其意義的影響更大)亦或相反。
        // 這裡你要寫入一個AIFF檔案,它可以只取大端模式的PCM,所以你需要在你的ASBD中設定它。
        // 你同樣需要表明取樣的數值格式(kAudioFormatFlagIsSignedInteger)
        // 以及,你傳入的第三個標識來表明你的取樣值使用每一個位元組的所有可用位(kAudioFormatFlagIsPacked)。
        // mFormatFlags是一個bit區域,所以你可以使用算術或運算子(|)把這些標識結合到一起
        asbd.mSampleRate = SAMPLE_RATE;
        asbd.mFormatID = kAudioFormatLinearPCM;
        asbd.mFormatFlags = kAudioFormatFlagIsBigEndian | kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
        asbd.mBitsPerChannel = 16;
        asbd.mChannelsPerFrame = 1;
        asbd.mFramesPerPacket = 1;
        asbd.mBytesPerFrame = 2;
        asbd.mBytesPerPacket = 2;

        // set up the file
        AudioFileID audioFile;
        OSStatus audioErr = noErr;

        // 現在你可以讓CoreAudio建立一個AudioFileID了,準備用來寫在你設定好的URL
        // AudioFileCreateWithURL()函式接受一個URL(注意到你再次使用橋接來從一個Cocoa NSURL轉換到CoreFoundation CFURLRef)
        // 一個用來描述AIFF檔案格式的常量
        // 一個指向描述音訊資料的AudioStreamBasicDescription的指標
        // 一個行為標識(在這裡,表明你希望如果已存在同名的檔案就把它覆蓋掉)
        // 一個用來填充我們建立的AudioFileID的指標
        audioErr = AudioFileCreateWithURL((__bridge CFURLRef)fileURL,
                                          kAudioFileAIFFType,
                                          &asbd,
                                          kAudioFileFlags_EraseFile,
                                          &audioFile);        
        assert(audioErr == noErr);

        // start writing samples
        // 你馬上就可以準備完畢寫取樣了。
        // 在我們進入這個寫取樣的迴圈之前,你要計算一下在每秒SAMPLE_RATE個取樣下對於DURATION秒的聲音需要多少採樣
        // 隨著一個計數變數,sampleCount
        // 你定義了bytesToWrite作為區域性變數,只因為寫取樣的呼叫需要一個紙箱UInt32的指標。
        // 你不能就直接把這個值放進引數
        long maxSampleCount = SAMPLE_RATE * DURATION;   
        long sampleCount = 0;
        UInt32 bytesToWrite = 2;

        // 你需要跟蹤在一個波長裡面有多少採樣
        // 然後你就可以計算組成一個波需要多少採樣值
        double wavelengthInSamples = SAMPLE_RATE / hz;  

        while (sampleCount < maxSampleCount) {


            for (int i = 0; i < wavelengthInSamples; i++) {
                // Square wave
                SInt16 sample;

                // 對於第一個例子,你將會寫最簡單的波之一,方波。其取樣是非常簡單的
                // 對於波長的前半部分,你要提供一個最大值,對於波長的剩下部分,你提供一個最小值
                // 所以僅有兩個可能的取樣值可能會唄用到:一個高的和一個低的。
                // 對於16位有符號整數,你要使用C常量來代表最大值和最小值:SHRT_MAX和SHRT_MIN
                if (i < wavelengthInSamples/2) {       

                    // 你在ASBD中聲明瞭將大端有符號整數作為音訊格式,所以你不得不在這個格式中小心地持有者2位元組的取樣
                    // 現代Mac執行中小端模式的Intel CPU上,而且iPhone的ARM處理器也是小端的
                    // 所以你需要把CPU表示的字元切換為大端模式。CoreFoundation函式CFSwapInt16HostToBig()會幫到你。
                    // 這個呼叫同樣可以在大端模式的CPU上,比如老Mac上的PowerPC,因為它會意識到主機的格式是大端模式然後就什麼也不做
                    sample = CFSwapInt16HostToBig(SHRT_MAX);
                } else {
                    sample = CFSwapInt16HostToBig(SHRT_MIN);
                }

                // 已經計算好了你的取樣,使用AudioFileWriteBytes()把它寫進檔案。
                // 這個呼叫接受5個引數:AudioFileID用來寫入、快取標識、你要寫的音訊資料的偏移量
                // 你要寫的字元數和一個指向被寫入字元的指標。
                // 你可以使用這個函式因為你擁有常量位元率資料。
                // 在更多的一般情況下,比如寫一個有損格式,你必須使用更負責的AudioFileWritePackets()

                audioErr = AudioFileWriteBytes(audioFile, false, sampleCount*2, &bytesToWrite, &sample);    
                assert(audioErr == noErr);

                // 增量sampleCount,然後你就一點點地將新資料寫進檔案
                sampleCount ++;       
            }
        }


        // 最後呼叫AudioFileClose()來完成並關閉檔案
        audioErr = AudioFileClose(audioFile); 
        assert(audioErr == noErr);
        NSLog(@"wrote %ld samples",sampleCount);

    }
    return 0;

編譯並執行這個程式。為了找到我們寫入的檔案,開啟Organizer,進入Project選項,選擇CAToneFileGenerator工程,然後點選Derived Data path右邊的圓形箭頭。這回開啟一個工程元資料和成品的Finder視窗,在裡面你可以找到路徑Build/Products/Debug。在這個資料夾中,你應該可以看到CAToneFileGenerator的可執行檔案和一個聲音檔案,名字代表你設定的命令列引數代表的頻率,比如880.000-square.aif。你可以用QuickTime Player、iTunes或者直接在Finder裡面選中它然後按空格 —— 但是在你那樣做之前,請把音量調到最小!方波對於我們的耳朵來說是非常重的。

這就帶來了一個重要的觀點。波重複的頻率就是你感知到的音調:一個聲音多高或者多低。但是那並不是故事的全部。波的形狀賦予了聲音它的角色,它的音色。

考慮一下三種最基礎的波形,你可以簡單地通過程式來建立:

  • 方波,如你所見,只是在兩個值之間迴圈交替。
  • 鋸齒波在一個波長中從最大值到最小值擁有線性的增量,然後在下一個波到來時重置為最小值。
  • 正弦波是准許你三角函式屬性的曲線。它們聽起來更加自然一點,因為正弦函式可以表示簡單的諧波運動,這類似於自然現象,比如琴絃的振動。

下面三個圖展示了三種波:
方波

方波

鋸齒波

鋸齒波

正弦波

正弦波

修改程式來生成這些不同的波型別是小菜一碟。我們先拿鋸齒波開刀。首先改變#define檔名格式,這樣你就可以區分多個檔案:

#define FILENAME_FORMAT @"%0.3f-saw.aif"

然後重寫迴圈:


for (int i=0; i<wavelengthInSamples; i++) {

    // 鋸齒波
    SInt16 sample = CFSwapInt16HostToBig (((i / wavelengthInSamples) * SHRT_MAX *2) - SHRT_MAX);

    assert (audioErr == noErr);

    sampleCount++; 
}

唯一的不同在於取樣的計算。這個方法用i除以波長然後使用一些縮放所以值會均勻地從SHRT_MIN增加到SHRT_MAX。

在你編譯並執行這個版本後,你可以比較方波的聲音和鋸齒波的聲音。你可以聽到它們擁有相同的音調,但是聲音略有不同。

如果你有音訊編輯器來降低原生取樣等級,你同樣可以用它來檢查這些檔案。

我們再次來調整程式碼,這一次來生成正弦波。這是經典參考訊號。在電視中,”酒吧和音”過去常常使用一個工程師能用示波器驗證的1000Hz的音來校驗裝置。首先修改檔名格式:

#define FILENAME_FORMAT @"%0.3f-sine.aif"

然後重寫for迴圈。你可以重用鋸齒波的for迴圈然後只改變計算取樣的那一行:

sample = CFSwapInt16HostToBig ((SInt16) SHRT_MAX * sin (2 * M_PI * (i / wavelengthInSamples)));

Yeah,歡迎回到高中三角幾何。將相位(i / wavelengthInSamples)轉換成了正弦函式的弧度(通過乘以2π),用結果乘以SHRT_MAX(因為正弦函式返回的值在-1.0到1.0之間),並且將整個結果轉換成16位值,然後你就可以在大端小端之間轉換然後寫進檔案。

注意 如果你使用浮點型取樣宣告你的ASBD,你就不必這樣放大。然後,CoreAudio的iOS4版本只能在PCM中用整型取樣,並且我們想讓程式碼在兩個平臺之間是便攜的。

嘗試一下 — 你會聽到比無論是方波還是鋸齒波都要更加令人愉悅的音,儘管你同樣能夠清楚的聽見,對於一個給定的頻率,它們播放的是同樣的音調。

你可以嘗試把頻率增高然後看看你能否識別出什麼時候你到達了一個點,你無法再聽到任何音。你應該可以輕易地聽到在10000Hz左右的音,但是你可能無法聽到20000Hz或者更高的音。實際上,你停應高的能力隨著年齡的增長而惡化,所以如果你現在能聽到15000Hz,你可能在10年後就聽不到了。

考慮這個例子結束的點。當頻率是22050的時候會發生什麼?那正好是取樣率的一半。在這個例子的任何一個版本中,wavelengthInSamples都將是2。意味著只有兩個取樣來表示波;對於方波,你通過SHRT_MIN獲得一個,通過SHRT_MAX獲得另一個。但那是重複的東西,至少這是有模式的。在更高的頻率,每一個波你有的取樣少於兩個。並且,因此,沒有重複的模式。這是另一種方法你可以思考尼奎斯特和它看似隨意的概念 — 在你想要再現的最高頻率的兩倍處取樣。

Buffers

你可能注意到將所有采樣寫進你的檔案花了幾秒鐘的時間。執行5秒來生成一個5秒的音訊可並不高效!

一次讀一個取樣或者寫一個取樣是非常低效的。如果你重新思考AudioFileWriteBytes,你可能會想起最後的兩個引數是要寫進檔案位元組數以及一個指向取樣的指標。不要一次寫一個取樣到檔案裡,會在這個簡單的例子中一個函式呼叫的開銷超過200000次。你本可以建立一個記憶體緩衝來持有一堆取樣然後將這些取樣集體寫入檔案。

這並不僅僅是寫檔案的問題。同樣也是當你在執行時生成聲音時的問題。一言以蔽之,計算機不同的部分計算速度是不同的。音訊硬體生產或者消耗音訊資料的分組所花的時間要比把分組移入移出記憶體所花的時間要少得多。這種放緩被稱為馮諾依曼瓶頸。

當音訊硬體沒事可做的時候,它會產生糟糕的噪音。為了避免這樣的事情發生,你可以使用緩衝使音訊分組來回穿梭。許多的大緩衝意味著計算機慢的部分中的短暫停頓能夠在快的部分注意到之前被解決:當一個音訊資料的緩衝耗盡時,另一個(如期而至)到達了。

除了進一步增加問題的複雜性,緩衝可能會在你真正想要得到硬體注意的時候使其變得困難。把它想象成資料級別的官僚制度 —— 有些是必要的,但是太多的話又會讓一切變得太長。

技術術語叫做延時,是從初始化一個動作到看到這個動作的結果的延遲。舉個栗子,iPhone和iPod Touch的硬體延時在15到30毫秒,取決於模型。當你把一個取樣的緩衝從你程式碼中推入前一章的音訊引擎中的一個,在第一個取樣走出揚聲器或者耳機之前,有幾毫秒的時間消逝了。

緩衝和延時是一對微秒的平衡:大的緩衝是用來對付高延時的,但是如果你在低延時的探求上賭運氣的話(強行寫出低延時的程式碼),你在冒險使你的緩衝耗盡並聽見靜音或者噪音。

Audio Formats

在例子中,你使用了一個AudioStreamBasicDescription來描述你程式碼生成的音訊流的格式:16位整型取樣,單聲道,44100的取樣率等等。但是這並不是故事的全部。然後你把音訊放進了一個AIFF檔案。你可能會從管理你自己的iTunes收藏中知道許多不同的檔案格式存在:AIFF,WAV,MP3,M4A,等等。

注意 描述音訊和把音訊資料存進檔案系統是完全不同的問題。資料格式解決第一個問題,檔案格式解決後面一個。

考慮一個音訊檔案作為一個持有音訊資料的容器。一些檔案格式被定製為指定的資料格式,比如MP3;一個.mp3檔案不可能包含PCM或者Windows Media資料 —— 只有MP3資料。正如AIFF檔案掌握PCM音訊資料,除非它是大端模式的,相反地,WAV檔案中的PCM必須是小端模式。其他檔案格式是更不可知的並且處理幾個資料格式。舉個栗子,MP4檔案格式能包含一部分資料格式的資料,包括AAC,PCM和AC3。

CoreAudio支援的內容最不可知的檔案格式是它自己的CoreAudio格式,縮寫為CAF並且檔案擴充套件名錶示為.caf。一個CAF檔案包含任意CoreAudio支援的音訊格式:MP3,AAC,Apple Lossless,你隨便取名。這使得CAF成為一個極佳的作為一個你應用內部的音訊的容器格式的選擇,比如背景音樂或者音效。

CAF還採用了一些技巧來提高效能。舉個栗子,考慮MP3音訊:因為它有可變的位元率,在一個.mp3檔案中跳到任何點都需要從當前播放位置解壓縮所有的資料直到它到達你想跳的時間。你沒法知道檔案的哪一部分代表目標時間。這就需要大量的I/O和CPU成本並且旺旺需要較長時間來執行。CAF建立了一個內部的時間對映到取樣的瀏覽表,所以它可以幾乎瞬間跳躍。

注意 格式經常是專有的以及廣泛可變的。CoreAudio通過完整的抽象掉它們來處理這個實現細節。
在這個例子中,你可以用kAudioFileM4AType或者kAudioFileCAFType來代替格式常量kAudioFileAIFFType,改變檔名來讓字尾適配格式,並且不必改變你程式碼的任意東西。CoreAudio來負責解決怎麼把你的PCM資料放進指定格式中,只要音訊格式和檔案格式是相容的。

總結

這一章走了很長一段路。我們從現實世界出發,談論了自然源如何生成聲音,聲音作為壓力的波是如何穿過空氣的,你的耳朵是怎樣抓住聲音的。然後我們看到如何在一個聲波的數字表示中模擬它們,這樣的數字表示對於計算機來講處理起來更加順手;如何儲存在數字媒體上以及通過網路傳送。要具體化這些概念,你再次使用CoreAudio的Audio File Services,通過一個一個的取樣把自己的聲波寫進一個磁碟上的檔案。你簡要地考慮了音訊流格式和音訊檔案格式的區別。最後你看見了CoreAudio是如何抽象出多種檔案格式的不同的,這意味著你讀寫.aif,.caf和.m4a檔案或多或少是用的相同的方法。

伴隨這種數字音訊的概念中的基礎,是時候真正的呈現CoreAudio了 —— 它是怎樣表示和處理音訊的。

相關推薦

iOS CoreAudio學習筆記—— The Story of Sound

在上一章,我們初次嘗試了CoreAudio API:它提供了什麼以及怎樣呼叫它的函式。現在是時候往回一步來看看一張更大的圖:一開始CoreAudio訪問的問題。 這一章將介紹基礎的聲音科學,它是什麼,它怎樣工作。事實證明,計算機的數字化天性使它們並不那麼適合處

iOS CoreAudio學習筆記—— Overview of CoreAudio

既然是Overview,那麼這一章文字的內容會佔絕大部分比例。 The Core Audio Frameworks CoreAudio是一堆框架的集合,他們通常被分為兩組:audio engines,用來處理音訊流;helper APIs,提供了便

iOS開發學習筆記 -- 動態新增控制元件和事件處理

剛開始學iOS開發的時候,經常要跟interface builder打交道,乍一看拖控制元件是挺方便的,跟以前做C#開發類似,但是Xcode比較噁心的一點是,拖完控制元件之後,還得手動地在Connections Inspector中繫結控制元件和變數的關係,事件的繫結也在這

黑馬程式設計師-IOS學習筆記常用關鍵字和方法

------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流!------- 常用關鍵字和方法: 一.記憶體管理情況 1- autorelease,當用戶的程式碼在持續執行時,自動釋放池是不會被銷燬的,這段時間內使用者可以安全地使用自動釋放的物件。當

IOS學習】CoreText學習筆記設定文字屬性和插入圖片

設定文字和圖片的方法: 繪製文字的步驟是:設定NSAttributedString 或NSMutableAttributedString——> 通過attributedString 生成frameSetter ——> 生成CTFrame——>畫出來設定文字

php laravel框架學習筆記 數據庫操作

true 數據 mar sql show top 一行 ati del 原博客鏈接:http://www.cnblogs.com/bitch1319453/p/6810492.html mysql基本配置 你可用通過配置環境變量,使用cmd進入mysql,當然還有一種東

java學習筆記圖形用戶接口

star strong per getwidth cep runnable graphics s2d gb2 這個學期主要放在ACM比賽上去了,比賽結束了。不知不覺就15周了,這周就要java考試了,復習一下java吧。java的學習的目的還是讓我們學以致用,讓我們可以

數據結構學習筆記 線性表的順序存儲和鏈式存儲

出錯 初始化 node != test span 輸入 des val 線性表:由同類型數據元素構成有序序列的線性結構  --》表中元素的個數稱為線性表的長度  --》沒有元素時,成為空表  --》表起始位置稱表頭,表結束位置稱表尾 順序存儲:    1 package

Memcache 學習筆記---- PHP 腳本操作 Memcache 服務器

ext status ram var_dump 介紹 修改 memcache local dbn    PHP 腳本操作 Memcache 服務器 一、PHP腳本操作Memcache方法     使用 PHP 腳本操作 Memcache,在 PHP 手冊中有詳細的介紹,我們

javascript學習筆記:定義函數、調用函數、參數、返回值、局部和全局變量

兩個 cnblogs bsp 結果 value ava ase com 調用 定義函數、調用函數、參數、返回值 關鍵字function定義函數,格式如下: function 函數名(){ 函數體 } 調用函數、參數、返回值的規則和c語言規則類似。 1 <!DOC

神箭手爬蟲學習筆記

暫存 自動 表達 eve doc 常用 學習 數據 .sh 一,可以使用神劍手已經做好的爬蟲市場直接跑,不需要自己定義爬取規則 二,爬蟲市場裏沒有的網站,需要自己去定義規則來爬數據。 三,爬取的數據可以先存放在神劍手,也可以放到七牛暫存。(提醒下,網站需要數據備份如果數量不

thinkphp5.0學習筆記API後臺處理與命名空間

mac code 輸入 -1 pub 基礎 select() color 第一個 命名空間 先來看命名空間吧; 命名空間是學習TP的基礎, <?php namespace app\lian\c1; class yi{ public $obj = "這是第一個

MongoDB學習筆記

.get 條件過濾 條件 $set system.in ins version tle 不存在 一、Mongodb命令 說明:Mongodb命令是區分大小寫的,使用的命名規則是駝峰命名法。 對於database和collection無需主動創建,在插入數據時,如果dat

設計模式學習筆記 設計基本原則之【單一職責原則】

code 分享 開發者 實際應用 需要 ret ext file類 tor 單一職責原則(SRP: Single Responsibility Principle) 名詞解釋: 1) 職責:是指類變化的原因。 2) 職責擴散:就是因為某種原因,職責P被分化為粒度更細的職責P

CSS學習筆記:特性

code 背景色 左移 line tex lin 安裝 其中 cas 一、顏色特性 1. 前景色:color 用種方式指定前景色,3種方式分別是rgb顏色,#16進制編碼,顏色名稱: color: rgb(100,100,100); color: #ee3e80; col

tensorflow學習筆記

example initial turn rate mnist pac rac test mode import tensorflow as tfimport numpy as npimport mathimport tensorflow.examples.tutorial

SSH學習筆記

via linu inf 一段時間 isp x-window window max tcl 1 # 1. 關於 SSH Server 的整體設定,包含使用的 port 啦,以及使用的密碼演算方式 2 Port 22          # SSH 預設使用 22 這

Git學習筆記

== 我們 ash 發出 效率 媳婦兒 src 每天 apply 一、分支管理 1、什麽是分支   分支就相當於我們看科幻片裏的平行宇宙,如果兩個平行宇宙互不幹擾,那鐵定是啥事兒沒有。不過,在某個時間點,兩個平行宇宙合並了呢?假如兩個宇宙中都有你的影子, 合並之後相當於你們

MySql學習筆記

ati 保存 ron setting mysql的安裝 use t-sql語句 cnblogs 完全卸載mysql MySql的安裝配置與卸載: 安裝:(1)將MySql的綠色版免安裝包放到D盤,命令行進入mysql綠色版解壓縮後的bin目錄:cd D:\mysql-5.

Unity3D之Mecanim動畫系統學習筆記:模型導入

leg character ... sdk ocs 物體 mat 版本 sset 我們要在Unity3D中使用上模型和動畫,需要經過下面幾個階段的制作,下面以一個人形的模型開發為準來介紹。 模型制作 模型建模(Modelling) 我們的美術在建模時一般會制作一個稱為