Audio Queue Services 解讀之 Playing Audio(下)

解讀Play Audio下集蒸走,如果你沒看上集厉亏,建議先去看看上集.

Audio Queue Services 解讀之 Playing Audio(上)

上集已經(jīng)準(zhǔn)備好音頻隊列的結(jié)構(gòu)體以及回調(diào)函數(shù)跨嘉,那么接下來就可以創(chuàng)建音頻隊列并實現(xiàn)播放了!

五勃黍、Create a Playback Audio Queue(創(chuàng)建播放的音頻隊列)

下面將演示怎么去創(chuàng)建回放音頻隊列踱蛀,注意:AudioQueueNewOutput 函數(shù)中使用的自定義結(jié)構(gòu)體和回調(diào)方法都是在之前寫好了

AudioQueueNewOutput (                                // 1
    &aqData.mDataFormat,                             // 2
    HandleOutputBuffer,                              // 3
    &aqData,                                         // 4
    CFRunLoopGetCurrent (),                          // 5
    kCFRunLoopCommonModes,                           // 6
    0,                                               // 7
    &aqData.mQueue                                   // 8
);
  • 1、通過 AudioQueueNewOutput 函數(shù)來創(chuàng)建一個回放音頻隊列
  • 2馋辈、需要設(shè)置播放的音頻隊列對應(yīng)的音頻文件數(shù)據(jù)格式抚芦,參考Obtaining a File’s Audio Data Format
  • 3、回放音頻隊列的回調(diào)函數(shù)迈螟,參考Write a Playback Audio Queue Callback
  • 4叉抡、音頻隊列對應(yīng)的自定義結(jié)構(gòu)體,可查看Define a Custom Structure to Manage State
  • 5答毫、當(dāng)前運行循環(huán)褥民,以及將調(diào)用音頻隊列回放回調(diào)的循環(huán)
  • 6、當(dāng)前可以被調(diào)用回調(diào)的運行循環(huán)模式洗搂,通常使用 kCFRunLoopCommonModes
  • 7消返、保留字段,傳0
  • 8耘拇、輸出時撵颊,新分配的回放音頻隊列

六、Set Sizes for a Playback Audio Queue(設(shè)置播放音頻隊列的大斜古选)

現(xiàn)在倡勇,你可以為播放音頻隊列設(shè)置一些大小,當(dāng)你給緩沖隊列分配緩沖區(qū)的時候和開始讀取音頻文件之前嘉涌,你可以使用這些設(shè)置的大小

下面代碼將會告訴你怎么設(shè)置

  • 1妻熊、音頻隊列緩沖區(qū)大小
  • 2、每次執(zhí)行回放音頻隊列回調(diào)要讀取的數(shù)據(jù)包個數(shù)
  • 3仑最、用于保存一個緩沖區(qū)的音頻數(shù)據(jù)的分組描述的數(shù)組大小

(1)扔役、Setting Buffer Size and Number of Packets to Read(設(shè)置緩沖區(qū)大小和要讀取的數(shù)據(jù)包數(shù))

下面代碼段會演示如何使用之前寫的 DeriveBufferSize 函數(shù)(see Write a Function to Derive Playback Audio Queue Buffer Size)這里是為每一個音頻緩沖區(qū)設(shè)置一個大小,單位是字節(jié)词身;以及確認(rèn)每次調(diào)用回放音頻回調(diào)要讀取的數(shù)據(jù)包數(shù)

此代碼使用最大包大小的保守估計厅目,Core Audio通過 kAudioFilePropertyPacketSizeUpperBound 屬性提供。 在大多數(shù)情況下法严,最好使用這種方式 - 這是近似但快速的 - 花費時間讀取整個音頻文件以獲得實際的最大包大小

UInt32 maxPacketSize;
UInt32 propertySize = sizeof (maxPacketSize);
AudioFileGetProperty (                               // 1
    aqData.mAudioFile,                               // 2
    kAudioFilePropertyPacketSizeUpperBound,          // 3
    &propertySize,                                   // 4
    &maxPacketSize                                   // 5
);
 
DeriveBufferSize (                                   // 6
    aqData.mDataFormat,                              // 7
    maxPacketSize,                                   // 8
    0.5,                                             // 9
    &aqData.bufferByteSize,                          // 10
    &aqData.mNumPacketsToRead                        // 11
);

下面介紹一下代碼的作用:

  • 1损敷、AudioFileGetProperty 函數(shù),聲明在 AudioFile.h 頭文件中深啤,可以獲取音頻文件中指定屬性的值拗馒,這里你可以用來獲取一個你要播放的音頻文件的音頻數(shù)據(jù)包大小的保守上限
  • 2、音頻文件對象(類型是 AudioFileID)代表你要播放的音頻文件溯街,可查看Opening an Audio File
  • 3诱桂、屬性ID洋丐,用于在音頻文件中獲取數(shù)據(jù)包大小的保守上限
  • 4、輸出時挥等,kAudioFilePropertyPacketSizeUpperBound屬性的大杏丫(以字節(jié)為單位)
  • 5、輸出是肝劲,你要播放的音頻文件數(shù)據(jù)包大小的保守上限(以字節(jié)為單位)
  • 6迁客、DeriveBufferSize 函數(shù),聲明在Write a Function to Derive Playback Audio Queue Buffer Size辞槐,可以設(shè)置緩沖區(qū)大小和每次調(diào)用回調(diào)要讀取的數(shù)據(jù)包數(shù)
  • 7掷漱、你要播放的文件數(shù)據(jù)格式,可查看 Obtaining a File’s Audio Data Format
  • 8榄檬、音頻文件預(yù)估最大的數(shù)據(jù)包大小卜范,上面第5點提到那個
  • 9、每個音頻隊列緩沖區(qū)應(yīng)該保持的音頻的秒數(shù)鹿榜,通常0.5秒是一個很好的選擇
  • 10海雪、輸出時、每個音頻隊列緩沖區(qū)的大胁盏睢(以字節(jié)為單位)喳魏,這個值存放在音頻隊列的自定義結(jié)構(gòu)體中
  • 11、輸出時怀薛,每次執(zhí)行回調(diào)要讀取的數(shù)據(jù)包數(shù),這個值也是存儲在音頻隊列的自定義結(jié)構(gòu)體中

(2)迷郑、Allocating Memory for a Packet Descriptions Array(為包描述數(shù)組分配內(nèi)存)

現(xiàn)在為數(shù)組分配內(nèi)存枝恋,以保存一個緩沖區(qū)的音頻數(shù)據(jù)的數(shù)據(jù)包描述。 恒定比特率數(shù)據(jù)不使用數(shù)據(jù)包描述嗡害,因此下面代碼中CBR情形 - 步驟3非常簡單焚碌。

bool isFormatVBR = (                                       // 1
    aqData.mDataFormat.mBytesPerPacket == 0 ||
    aqData.mDataFormat.mFramesPerPacket == 0
);
 
if (isFormatVBR) {                                         // 2
    aqData.mPacketDescs =
      (AudioStreamPacketDescription*) malloc (
        aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)
      );
} else {                                                   // 3
    aqData.mPacketDescs = NULL;
}

下面介紹代碼作用

  • 1、確定音頻文件的數(shù)據(jù)格式是VBR還是CBR霸妹。 在VBR數(shù)據(jù)中十电,每個字節(jié)的字節(jié)數(shù)或每幀的幀數(shù)是可變的,因此將在音頻隊列的AudioStreamBasicDescription結(jié)構(gòu)中列為0叹螟。
  • 2鹃骂、對于包含VBR數(shù)據(jù)的音頻文件,為包描述數(shù)組分配內(nèi)存罢绽。 基于在每次調(diào)用回放回調(diào)時要讀取的音頻數(shù)據(jù)包的數(shù)量來計算所需的內(nèi)存畏线。 請參閱Setting Buffer Size and Number of Packets to Read
  • 3良价、對于包含CBR數(shù)據(jù)的音頻文件(例如線性PCM)寝殴,音頻隊列不使用包描述數(shù)組蒿叠。

七、Set a Magic Cookie for a Playback Audio Queue(設(shè)置回放音頻隊列的 Magic Cookie )

一些音頻壓縮的音頻格式蚣常,例如 MPEG 4 AAC市咽,利用結(jié)構(gòu)體包含音頻的元數(shù)據(jù)。這些結(jié)構(gòu)體就是Magic Cookie抵蚊,當(dāng)你用 Audio Queue Services 播放這種格式的音頻文件時施绎,你可以從音頻文件中獲取Magic Cookie ,然后在播放之前添加到音頻隊列中

下面代碼教你怎么去從音頻文件中獲取Magic Cookie泌射,并添加到音頻隊列中粘姜,下面的代碼需要在開始回放之前執(zhí)行

UInt32 cookieSize = sizeof (UInt32);                   // 1
bool couldNotGetProperty =                             // 2
    AudioFileGetPropertyInfo (                         // 3
        aqData.mAudioFile,                             // 4
        kAudioFilePropertyMagicCookieData,             // 5
        &cookieSize,                                   // 6
        NULL                                           // 7
    );
 
if (!couldNotGetProperty && cookieSize) {              // 8
    char* magicCookie =
        (char *) malloc (cookieSize);
 
    AudioFileGetProperty (                             // 9
        aqData.mAudioFile,                             // 10
        kAudioFilePropertyMagicCookieData,             // 11
        &cookieSize,                                   // 12
        magicCookie                                    // 13
    );
 
    AudioQueueSetProperty (                            // 14
        aqData.mQueue,                                 // 15
        kAudioQueueProperty_MagicCookie,               // 16
        magicCookie,                                   // 17
        cookieSize                                     // 18
    );
 
    free (magicCookie);                                // 19
}

下面介紹一下代碼的作用:

  • 1、設(shè)置magic cookie 的預(yù)估大小
  • 2熔酷、獲取函數(shù) AudioFileGetPropertyInfo 的返回值孤紧,如果成功,返回 NoErr,相當(dāng)于布爾值 false
  • 3拒秘、函數(shù) AudioFileGetPropertyInfo 聲明在 AudioFile.h 頭文件号显,可以獲取指定屬性值的大小,可以使用它來設(shè)置保存屬性值的變量的大小
  • 4躺酒、你要播放的音頻文件對象押蚤,類型是 AudioFileID
  • 5、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
  • 6羹应、輸入時揽碘,是magic cookie 數(shù)據(jù)的預(yù)估大小园匹;輸出時雳刺,是真實的大小
  • 7、使用 NULL 表示你不關(guān)心這個屬性的讀寫訪問
  • 8裸违、如果音頻文件包含有 magic cookie掖桦,那么就為它分配內(nèi)存進(jìn)行管理
  • 9、函數(shù) AudioFileGetProperty 聲明在 AudioFile.h 頭文件供汛,可以獲取指定屬性值枪汪,在這里是獲取音頻文件的 magic cookie
  • 10、表示你要播放的并且獲取到magic cookie的音頻文件對象怔昨,類型是 AudioFileID
  • 11雀久、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
  • 12、輸入時朱监,通過使用函數(shù) AudioFileGetPropertyInfo 獲取 magicCookie 變量的大邪斗取;輸出時赫编,真實的magic cookie 大小就是寫入到 magicCookie 變量的字節(jié)數(shù)
  • 13巡蘸、輸出時奋隶,音頻文件的 magic cookie
  • 14、函數(shù) AudioQueueSetProperty 設(shè)置音頻隊列的屬性悦荒,在這里唯欣,用來匹配要被播放的音頻文件的magic cookie并設(shè)置給音頻隊列
  • 15、你要設(shè)置magic cookie 的音頻隊列
  • 16搬味、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
  • 17境氢、你要播放的音頻文件的 magic cookie
  • 18、magic cookie 的大小碰纬,單位是字節(jié)
  • 19萍聊、釋放之前被分配內(nèi)存的magic cookie

八、Allocate and Prime Audio Queue Buffers(分配和填充音頻隊列緩沖區(qū))

現(xiàn)在你可以讓你之前創(chuàng)建的音頻對象去準(zhǔn)備一些音頻隊列緩沖區(qū)悦析,下面代碼教你怎么做

aqData.mCurrentPacket = 0;                                // 1
 
for (int i = 0; i < kNumberBuffers; ++i) {                // 2
    AudioQueueAllocateBuffer (                            // 3
        aqData.mQueue,                                    // 4
        aqData.bufferByteSize,                            // 5
        &aqData.mBuffers[i]                               // 6
    );
 
    HandleOutputBuffer (                                  // 7
        &aqData,                                          // 8
        aqData.mQueue,                                    // 9
        aqData.mBuffers[i]                                // 10
    );
}

下面介紹代碼的作用:

  • 1寿桨、將包索引設(shè)置為0,以便在音頻隊列回調(diào)開始填充緩沖區(qū)時(步驟7)强戴,它從音頻文件的開頭開始
  • 2亭螟、分配和填充音頻隊列緩沖區(qū)(你在 Define a Custom Structure to Manage State 設(shè)置了 kNumberBuffers這個值為 3
  • 3、函數(shù) AudioQueueAllocateBuffer 通過分配內(nèi)存來創(chuàng)建音頻隊列緩沖區(qū)
  • 4骑歹、負(fù)責(zé)分配音頻隊列緩沖區(qū)的音頻隊列
  • 5预烙、新的音頻隊列緩沖區(qū)的大小,單位是字節(jié)
  • 6道媚、輸出時扁掸,添加新的音頻隊列緩沖區(qū)到自定義結(jié)構(gòu)體的 mBuffers 數(shù)組中
  • 7、函數(shù) HandleOutputBuffer 是你之前寫的回放音頻隊列回調(diào)最域,可查看 Write a Playback Audio Queue Callback
  • 8也糊、音頻隊列的自定義結(jié)構(gòu)體
  • 9、調(diào)用回調(diào)的音頻隊列
  • 10羡宙、傳遞到音頻隊列回調(diào)的音頻隊列緩沖區(qū)

九、Set an Audio Queue’s Playback Gain(設(shè)置音頻隊列回放增益)

在告訴音頻隊列播放之前掐隐,你可以通過音頻隊列參數(shù)機(jī)制設(shè)置其增益狗热,下面代碼教你怎么設(shè)置,更多設(shè)置可以查看 Audio Queue Parameters

Float32 gain = 1.0;                                       // 1
    // Optionally, allow user to override gain setting here
AudioQueueSetParameter (                                  // 2
    aqData.mQueue,                                        // 3
    kAudioQueueParam_Volume,                              // 4
    gain                                                  // 5
);

下面介紹一下代碼的作用:

  • 1虑省、設(shè)置音頻隊列的增益匿刮,在0-1之間
  • 2、通過函數(shù) AudioQueueSetParameter 設(shè)置音頻隊列的參數(shù)值
  • 3探颈、設(shè)置參數(shù)得音頻隊列
  • 4熟丸、設(shè)置參數(shù)的ID,kAudioQueueParam_Volume常數(shù)可讓你設(shè)置音頻隊列的增益
  • 5伪节、應(yīng)用于音頻隊列的增益設(shè)置

十光羞、Start and Run an Audio Queue(開啟并運行音頻隊列)

所有上述代碼已經(jīng)播放文件的過程绩鸣。 這包括在播放文件時啟動音頻隊列并維護(hù)運行循環(huán),看下面代碼

aqData.mIsRunning = true;                          // 1
 
AudioQueueStart (                                  // 2
    aqData.mQueue,                                 // 3
    NULL                                           // 4
);
 
do {                                               // 5
    CFRunLoopRunInMode (                           // 6
        kCFRunLoopDefaultMode,                     // 7
        0.25,                                      // 8
        false                                      // 9
    );
} while (aqData.mIsRunning);
 
CFRunLoopRunInMode (                               // 10
    kCFRunLoopDefaultMode,
    1,
    false
);

介紹代碼作用:

  • 1纱兑、設(shè)置自定義結(jié)構(gòu)體的標(biāo)識呀闻,指示音頻隊列正在運行
  • 2、使用函數(shù) AudioQueueStart 開啟音頻隊列潜慎,在自己的線程中
  • 3捡多、要開啟的音頻隊列
  • 4、使用 NULL 表示音頻隊列需要馬上開啟播放
  • 5铐炫、定期輪詢自定義結(jié)構(gòu)的mIsRunning字段垒手,以檢查音頻隊列是否已停止
  • 6、CFRunLoopRunInMode 函數(shù)運行包含音頻隊列的線程的運行循環(huán)
  • 7倒信、使用默認(rèn)的運行循環(huán)模式
  • 8科贬、設(shè)置運行循環(huán)的運行時間為0.25秒
  • 9、使用 false 表示運行循環(huán)應(yīng)該繼續(xù)指定整個時間
  • 10堤结、在音頻隊列停止后唆迁,再運行循環(huán)一會兒,以確保當(dāng)前播放的音頻隊列緩沖區(qū)有時間完成

十一竞穷、Clean Up After Playing(播放完畢后清除)

當(dāng)你的音頻文件播放完畢唐责,應(yīng)該要銷毀這個音頻隊列,關(guān)閉音頻文件瘾带,同時要釋放所有資源鼠哥,下面代碼處理了這些步驟

AudioQueueDispose (                            // 1
    aqData.mQueue,                             // 2
    true                                       // 3
);
 
AudioFileClose (aqData.mAudioFile);            // 4
 
free (aqData.mPacketDescs);                    // 5

下面介紹一下代碼的作用:

  • 1、函數(shù) AudioQueueDispose 銷毀音頻隊列和它的所有資源看政,包括它的緩沖區(qū)
  • 2朴恳、要銷毀的音頻隊列
  • 3、使用true可以同步銷毀音頻隊列
  • 4允蚣、關(guān)閉播放完畢的音頻文件于颖,函數(shù) AudioFileClose 聲明在 AudioFile.h 頭文件中
  • 5、釋放用于保存數(shù)據(jù)包描述的內(nèi)存

十二嚷兔、總結(jié):

  • 1森渐、實現(xiàn)上面的步驟,就可以播放本地的音頻文件了冒晰,后續(xù)工作就是優(yōu)化封裝了
  • 2同衣、因為上面步驟只能實現(xiàn)播放本地音頻文件,至于網(wǎng)絡(luò)音頻文件播放就沒提壶运,后續(xù) 出的Demo 會實現(xiàn)播放網(wǎng)絡(luò)音頻文件耐齐,敬請期待
  • 3、playing audio 這篇的翻譯算是完成了,歡迎大家關(guān)注我埠况,喜歡就給個like耸携,如果有問題,請留言喔询枚,謝謝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末违帆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子金蜀,更是在濱河造成了極大的恐慌刷后,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渊抄,死亡現(xiàn)場離奇詭異尝胆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)护桦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門含衔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人二庵,你說我怎么就攤上這事贪染。” “怎么了催享?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵杭隙,是天一觀的道長。 經(jīng)常有香客問我因妙,道長痰憎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任攀涵,我火速辦了婚禮铣耘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘以故。我一直安慰自己蜗细,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布怒详。 她就那樣靜靜地躺著鳄乏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棘利。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天朽缴,我揣著相機(jī)與錄音善玫,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛茅郎,可吹牛的內(nèi)容都是我干的蜗元。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼系冗,長吁一口氣:“原來是場噩夢啊……” “哼奕扣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掌敬,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤惯豆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后奔害,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楷兽,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年华临,在試婚紗的時候發(fā)現(xiàn)自己被綠了芯杀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡雅潭,死狀恐怖揭厚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扶供,我是刑警寧澤筛圆,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站诚欠,受9級特大地震影響顽染,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜轰绵,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一粉寞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧左腔,春花似錦唧垦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鞭莽,卻和暖如春坊秸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背澎怒。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工褒搔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓星瘾,卻偏偏與公主長得像走孽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子琳状,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容