Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Mac OS X 與 iOS 的 Audio API
Search
Weizhong Yang
August 15, 2013
Technology
1.1k
11
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Mac OS X 與 iOS 的 Audio API
Weizhong Yang
August 15, 2013
More Decks by Weizhong Yang
See All by Weizhong Yang
導入 Flutter 前你應該知道的事
zonble
7
1.3k
那些年,被蘋果 Ban 掉的 API
zonble
0
190
為視障朋友打造行動應用
zonble
16
1.4k
Aspect Oriented Programming
zonble
2
260
大家來寫貪食蛇
zonble
7
4.5k
Debug Debug
zonble
6
460
Retina Mac
zonble
3
290
HTML 5 Native Drag
zonble
3
560
Other Decks in Technology
See All in Technology
[AWS 秋のクラウドオペレーション祭り 2026]AWS DevOps Agentで変わるリリースと運用対応 ~リリース管理機能と Directed actions のご紹介~
furuton
4
1.1k
AIに攻撃される前に、AIに攻撃させる
tsuchikazu
0
160
MCPゲートウェイを作って運用してわかったこと — Agent時代の権限管理の現在地
mtpooh
10
3.3k
あなたの知らないAmazon VPC Route Server/Amazon VPC Route Server you don't know about
masakiokuda
2
250
宣言型部分更新は何を解決しどこへ向かうのか
shuta13
0
190
プロダクト価値を、 チームが使える判断軸に変える
vivion
0
140
分析AIエージェントを育てる、動く環境を育てる
codatum
1
690
3つのアプローチで目指す チームを強くするAI駆動開発
thkt
0
140
"優秀な誰か"の下位互換にならないために
yut0naga1_fa
0
1k
Loop Engineering これくらいでいい
mikanichinose
0
160
初対面のメンバーがチームを組み、Claude Codeでゼロからサービスをリリース
mkdev10
1
110
自分で立ててみるLLMサービス
y_sera15
0
150
Featured
See All Featured
Amusing Abliteration
ianozsvald
1
330
AI Search: Where Are We & What Can We Do About It?
aleyda
0
8.1k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
287
14k
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
A Tale of Four Properties
chriscoyier
163
24k
Visualization
eitanlees
153
17k
Navigating Weather and Climate Data
rabernat
0
550
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
400
Optimising Largest Contentful Paint
csswizardry
37
4k
Mind Mapping
helmedeiros
1
400
sira's awesome portfolio website redesign presentation
elsirapls
0
450
Transcript
Mac OS X 與 iOS 的 Audio API 楊維中 a.k.a
zonble
[email protected]
Thursday, August 15,
與 Audio 有關的 API • AVFoundation • Audio Service •
OpenAL • Audio Queue • Audio Unit • Audio Session • MPNowPlayingCenter • Background Task • Remote Control Events • … Thursday, August 15,
任務: 播放網站上的⾳音檔 ⽽而且,抓到了多少資料 就⾺馬上播放多少資料 Thursday, August 15,
這就不會是我們要的 • Audio Service:⽤用來處理簡短的⾳音效,例 如按到虛擬鍵盤上的喀嚓聲 • OpenAL:製作遊戲中的3D⾳音效,可以設 定⾳音效是從哪個⽅方向傳來的 Thursday, August
15,
AVAudioPlayer • ⾼高階的 Audio Player • iOS 2.2 • ⽀支援多種格式
• 可以從 file URL 或是 NSData 建⽴立 player 播放,但只能夠播放 local file • 適合製作遊戲背景⾳音樂 Thursday, August 15,
AVPlayer • ⾼高階的 Audio Player • iOS 4.0 • ⽀支援本機檔案與遠端檔案
• 適合網路廣播 •無法知道檔案總⻑⾧長度 Thursday, August 15,
我們的服務需要… • 可以直接讀多少資料就播放多少資料 • 可以⼀一邊下載⼀一邊 cache ⽇日後離線播放 • 知道載⼊入進度 •
檔案可能加密過,必須解密… • 對,有些公司就是會做這種事 Thursday, August 15,
我們需要了解底層 API CoreAudio Thursday, August 15,
因為太難了 所以先講簡單的… Thursday, August 15,
Audio Session • ⽤用來表⽰示⺫⽬目前的 Audio 屬於哪⼀一種類 • 設定 Audio Session
Category • ⼀一般背景、Media Playback…etc • 然後呼叫 Audio Session Start • 有 C 與 Objective-C API Thursday, August 15,
Audio Session • 還要處理 Interrupt 與 Resume • 實作 delegate,更新
UI • Interrupt 的場合: • 別的 app 播放⾳音樂 • 來電 • 鬧鐘… Thursday, August 15,
回到 Core Audio Thursday, August 15,
說實在 這種 code 寫過⼀一次 不會想寫第⼆二次 Thursday, August 15,
請忘記 OO 忘記Objective-C Audio 串流就是快速處理⼀一堆 連續的 Binary 資料 Thursday, August
15,
基本觀念 • Audio 資料是連續的 binary 資料 • 資料中包含的是連續的sample/frame,⼀一 秒鐘會有 44100
個 sample • MP3/AAC等⺫⽬目前通⽤用的壓縮⾳音檔格式, 會將若干⼤大⼩小的frame變成⼀一個packet • 如果是VBR(變動碼率)的⾳音檔,每個 packet裡頭的資料量不⼀一樣⼤大 Thursday, August 15,
⼀一些術語 • Sample Rate:每秒鐘有多少 sample • Packet Size:每個 Packet 有多少
sample • Bit Rate:每秒鐘有多少 bit Thursday, August 15,
完整流程 • 建⽴立網路連線,從連線 callback 讀取資料 • 解密,並將解密過的資料放⼊入記憶體 • 建⽴立 Audio
Queue 或是 Audio Unit Graph,收取系統通知需要下⼀一段資料的 callback • 提供資料 Thursday, August 15,
我們專注在 • 將資料讀⼊入記憶體 • Parse 出 packet 並保存 • 將資料餵給
Audio API • 註冊 callback • 在 callback 中回傳下⼀一段資料 • 資料要轉換成 Linear PCM 格式 Thursday, August 15,
Audio Unit 與 Audio Queue 兩組 API 的差別? • 在上⾴頁的流程中,最⼤大的差別在於Audio
Queue API 不需要⼿手動將資料轉換成 Linear PCM 格式 • 不容易設定 • 可以直接對Linear PCM資料做⼿手腳… • Audio Unit 中可以在 Audio Graph 中增加 mixer 與 EQ effect node Thursday, August 15,
Packet ID3 data MP3 Header MP3 Data MP3 Header MP3
Data MP3 Header MP3 Data Packet Thursday, August 15,
辨識 MP3 header • MP3 Header 共 4 個 bytes
• 其中前11個bit是sync word(都是1),看 到 sync word就可以知道是⼀一個packet的 開頭 • sync word之後描述這個packet的格式 • http://www.mp3-tech.org/programmer/ frame_header.html Thursday, August 15,
Sample Packet Parser def parse(content): i = 0 while i
+ 2 < len(content): frameSync = (content[i] << 8) | (content[i + 1] & (0x80 | 0x40 | 0x20)) if frameSync != 0xffe0: if foundFirstFrame: pass i += 1; continue if not foundFirstFrame: foundFirstFrame = True audioVersion = (content[i + 1] >> 3) & 0x03; layer = (content[i + 1] >> 1) & 0x03 hasCRC = not(content[i + 1] & 0x01) bitrateIndex = content[i + 2] >> 4; sampleRateIndex = content[i + 2] >> 2 & 0x03; bitrate = [0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0][bitrateIndex]; hasPadding = not(not((content[i + 2] >> 1) & 0x01)) frameLength = 144 * bitrate / 44100 + \ (1 if hasPadding else 0) + \ (2 if hasCRC else 0) i += frameLength Thursday, August 15,
MPEG 4 Audio/Video MOOV MDAT Thursday, August 15,
CoreAudio 提供我們 ⼀一組 Audio Parser • ⽤用途是找出 Packet,並分析出檔案格式 • C
API • iOS 2/Mac OS X 10.5 • 本機檔案可以呼叫 AudioFileOpenURL • 串流資料可以呼叫AudioFileStreamOpen Thursday, August 15,
AudioFileStreamOpen • AudioFileStreamOpen(self, ZBAudioFileStreamPropertyListener, ZBAudioFileStreamPacketsCallback, kAudioFileMP3Type, &audioFileStreamID); • self :傳遞⼀一個callback可以使⽤用的物件
• ZBAudioFileStreamPropertyListener 檔案格式 callback • ZBAudioFileStreamPacketsCallback parse 出 packet 的 callback • kAudioFileMP3Type 給 parser 的 hint • audioFileStreamID 產⽣生 audio file stream ID Thursday, August 15,
在記憶體中保存 packet? • 其實我們只要⽤用個簡單的 structure 就可以 保存 typedef struct {
size_t length; // packet ⻑⾧長度 void *data; // packet 資料的指標 } Thursday, August 15,
保存檔案格式 void ZBAudioFileStreamPropertyListener(void * inClientData, AudioFileStreamID inAudioFileStream, AudioFileStreamPropertyID inPropertyID, UInt32
* ioFlags) { ZBSimplePlayer *self = (ZBSimplePlayer *)inClientData; if (inPropertyID == kAudioFileStreamProperty_DataFormat) { UInt32 dataSize = 0; OSStatus status = 0; AudioStreamBasicDescription audioStreamDescription; Boolean writable = false; status = AudioFileStreamGetPropertyInfo(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &dataSize, &writable); status = AudioFileStreamGetProperty(inAudioFileStream, kAudioFileStreamProperty_DataFormat, &dataSize, &audioStreamDescription); // 然後把 audioStreamDescription 存起來 } } Thursday, August 15,
保存 Packet static void ZBAudioQueueOutputCallback(void * inUserData, AudioQueueRef inAQ,AudioQueueBufferRef inBuffer)
{ ZBSimplePlayer *self = (ZBSimplePlayer *)inClientData; for (int i = 0; i < inNumberPackets; ++i) { SInt64 packetStart = inPacketDescriptions[i].mStartOffset; UInt32 packetSize = inPacketDescriptions[i].mDataByteSize; assert(packetSize > 0); self->packetData[self->packetCount].length = (size_t)packetSize; self->packetData[self->packetCount].data = malloc(packetSize); memcpy(packetData[self->packetCount].data, inInputData + packetStart, packetSize); self->packetCount++; } } Thursday, August 15,
下⼀一步 • 為了要播放順利,我們會在有⼀一定數量 的 packet 之後,才會開始呼叫 Audio API 開始播放。資料不夠會產⽣生爆⾳音 •
等待 packet 的過程叫buffering…緩衝處理 • 資料換算成時間的⽅方式:packet 數量 * frames per packet / sample rate • 100 * 1152 / 44100 = 2.61... Thursday, August 15,
播放 Callback Callback Thursday,
August 15,
Audio Queue 的播放 • 每次 callback,提供⼀一個新的buffer struct • buffer物件包含 •
packet 數量 • 指向 packet 資料的指標 • packet格式 • 將 buffer 送⼊入 queue 中 Thursday, August 15,
AudioUnit 的播放 • Audio Unit API 會給你⼀一個 struct 的指, 標,叫做IOData,並傳⼊入⼀一定⼤大⼩小的
frame 數量 • 對IOData指定所需要⼤大⼩小的Linear PCM 資料 Thursday, August 15,
iOS 上的 Audio Unit • ⼀一般模式與畫⾯面鎖定時,要求的frame數 量不⼀一樣⼤大 • 平時要求1024,但畫⾯面鎖定時要求4096 •
主要考量是節電 • 如果資料量不夠,會停⽌止播放 Thursday, August 15,
Audio Queue Thursday, August 15,
建⽴立 Audio Queue OSStatus status = AudioQueueNewOutput(audioStreamBasicDes cription, ZBAudioQueueOutputCallback, self,
CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &outputQueue); assert(status == noErr); Thursday, August 15,
Enqueue Data AudioQueueBufferRef buffer; status = AudioQueueAllocateBuffer(outputQueue, totalSize, &buffer); assert(status
== noErr); buffer->mAudioDataByteSize = totalSize; buffer->mUserData = self; AudioStreamPacketDescription *packetDescs = calloc(inPacketCount, sizeof(AudioStreamPacketDescription)); totalSize = 0; for (index = 0 ; index < inPacketCount ; index++) { size_t readIndex = index + readHead; memcpy(buffer->mAudioData + totalSize, packetData[readIndex].data, packetData[readIndex].length); AudioStreamPacketDescription description; description.mStartOffset = totalSize; description.mDataByteSize = (UInt32)packetData[readIndex].length; description.mVariableFramesInPacket = 0; totalSize += packetData[readIndex].length; memcpy(&(packetDescs[index]), &description, sizeof(AudioStreamPacketDescription)); } status = AudioQueueEnqueueBuffer(outputQueue, buffer, (UInt32)inPacketCount, packetDescs); free(packetDescs); Thursday, August 15,
Audio Unit Thursday, August 15,
Audio Unit Output Node Effect Node Mixer Node Render Callback
Audio Unit Graph Mixer Unit Effect Unit Output Unit Get Info Get Info Get Info Thursday, August 15,
建⽴立 audio converter AudioConverterRef converter; AudioStreamBasicDescription fromFormat=…⋯; AudioStreamBasicDescription destFormat=…⋯; AudioConverterNew(&fromFormat,
&destFormat, &converter); Thursday, August 15,
使⽤用 audio converter AudioBufferList *list; UInt32 packetSize = 1024; AudioConverterFillComplexBuffer(conve
rter, ZBPlayerConverterFiller, self, &packetSize, list, NULL); Thursday, August 15,
audio converter callback OSStatus ZBPlayerConverterFiller (AudioConverterRef inAudioConverter, UInt32* ioNumberDataPackets, AudioBufferList*
ioData, AudioStreamPacketDescription** outDataPacketDescription, void* inUserData) { ZBSimpleAUPlayer *self = (ZBSimpleAUPlayer *)inUserData; *ioNumberDataPackets = 1; static AudioStreamPacketDescription aspdesc; ioData->mNumberBuffers = 1; void *data = self->packetData[readHead].data; UInt32 length = self->packetData[readHead].length; ioData->mBuffers[0].mData = data; ioData->mBuffers[0].mDataByteSize = length; *outDataPacketDescription = &aspdesc; aspdesc.mDataByteSize = length; aspdesc.mStartOffset = 0; aspdesc.mVariableFramesInPacket = 1; readHead++; return noErr; } Thursday, August 15,
NewAUGraph(&audioGraph); // 建立 audio grapg AudioComponentDescription cdesc; bzero(&cdesc, sizeof(AudioComponentDescription)); cdesc.componentType
= kAudioUnitType_Output; cdesc.componentSubType = kAudioUnitSubType_DefaultOutput; cdesc.componentManufacturer = kAudioUnitManufacturer_Apple; cdesc.componentFlags = 0; cdesc.componentFlagsMask = 0; AUGraphAddNode(audioGraph, &cdesc, &outputNode); // 建立 output node AUGraphOpen(audioGraph); AUGraphNodeInfo(audioGraph, outputNode, &cdesc, &outputUnit); // 建立 output unit AudioStreamBasicDescription destFormat = LFPCMStreamDescription(); // 設定輸出格式 AudioUnitSetProperty(outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &destFormat, sizeof(destFormat)); AURenderCallbackStruct callbackStruct; callbackStruct.inputProc = ZBPlayerAURenderCallback; callbackStruct.inputProcRefCon = self; AudioUnitSetProperty(outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callbackStruct, sizeof(callbackStruct)); // 建立 render callback AUGraphInitialize(audioGraph); // Init audio graph CAShow(audioGraph); // 開始 audio graph Thursday, August 15,
播放 • AUGraphStart(audioGraph); • AUGraphStop(audioGraph); Thursday, August 15,
Sample Code • https://github.com/zonble/ZBSimplePlayer Thursday, August 15,
因為 Audio Unit API 需 要直接使⽤用 Linear PCM 資料,我們也可 以直接修改資料產⽣生
效果 Thursday, August 15,
Thanks! Thursday, August 15,