2010年9月21日 星期二

iPhone Live audio streaming using AudioQueue


原本用AVAudioPlayer來播放一些音效感覺很方便,
可是要播放即時的音訊串流就沒輒了.
請見 Technical Q&A QA1634

好吧~那就自立自強捲起袖子… 趕緊google阿!
不過google大神給的答案實在很朦朧,
勉勉強強找到的殘破程式碼也有看沒有懂.

幸好天無絕人之路 :)
Apress出的iPhone cool projects這本書第六章有寫捏,
又有source code可以run,實在"揪甘心"阿.



官方網



















範例程式:下載

如果剛點開範例程式,跟我一樣有頭暈眼花的症狀.
那不用擔心,沒有這麼難,看完下面解釋說不定就懂了.

從架構上來講,一共分成三個部分:

1. AudioPlayer跟AudioRequest可以視作一個大項
    主要負責下達URLrequest,並將收到的data回傳給AudioStream
    另外就是擔任AudioStream的委託(delegate)去使用AudioQueue

2. AudioStream有三件主要的事情
    * 將一收到的data進行parse並得到StreamID
    * 有了StreamID之後會分成 propertyCallback與packetCallback
       先講propertyCallback, 其目的顧名思義就是要辨識Audio的屬性
       就想像成一段資料收進來,需要貼上標籤,註明格式,頻率等等
    * packetCallback會將收進來的stream分成數個packet
       然後請委託(delegate)幫忙執行AudioQueue的程式部分

3. AudioQueue會接受來自委託(delegate)的幾件事情
    * AudioQueueNewOutput會產生一個新的playback audio queue物件
    * AudioQueuePlay會開始播放AudioQueue裡頭的音訊
    * 上述兩項是延續自propertyCallback的部份,在packetCallback這邊
       因為有一整段stream的大小,所以透過AudioQueueAllocateBuffer給定
       適當的buffer size後,使用AudioQueueEnqueueBuffer一直塞buffer
       到audio queue就會播出我們要的串流音訊啦!


NOTE: 2011/01/10
這個範例被我拿來改用之後發現有memory allocation的問題
我原以為是我改壞了 才造成memory allocation不斷飆升的情況
最近花了一點時間修改一下 發現問題就出在AudioQueueAllocateBuffer
這個function不應該在data每次進來都做一次
如果好好看過Apple提供的playback示意圖
AudioQueueAllocateBuffer只會在AudioQueue宣告出來後指定好

講是這樣講啦~
實際測試了一下書中範例給的mp3
neilmix[dot]com[slash]book[slash]etude[dot]mp3
嗯…一切自我感覺良好,沒有問題
換成linear PCM格式的wav source…結果是"大崩壞"!
追了很久發現有一個叫packetDescription的structure資料全都是null,
難怪在memory copy時都會造成crash.

所以精華來啦~
1. 這邊mp3的audio stream是VBR格式,也就是說每個packet大小可能不一,
    因此packetDescription會特別記錄這些資訊.
    linear PCM的wav是CBR格式自然沒有這些資料,
    所以簡單的方法就是把packetDescriptions copy這段註解掉.

1
2
3
4
5
memcpy(outBufferRef->mAudioData, data.bytes, data.length);
outBufferRef->mAudioDataByteSize = data.length;
//memcpy(outBufferRef->mPacketDescriptions, packetDescriptions, 
  sizeof(AudioStreamPacketDescription) * packetCount); 
outBufferRef->mPacketDescriptionCount = packetCount;


2. 由於CBR格式中的bit rate是固定關係, AudioQueueEnqueueBuffer後兩個
    參數為0,跟NULL.

3. 總結來講,AudioQueue service是精隨.只要搞清楚流程,一邊餵進Audio基本資料
    另一邊allocate好audio queue把data依序塞進去,輕鬆播放串流音訊非難事!

參考資料: iOS Reference Library
                zonble
                cocoaChina
                stackoverflow (synthesize with CoreAudio)
                stackoverflow (iPhone combine audio files)    

歐~差點忘了,
在iOS4的simulator環境下跑,開始的時候都會頓一下
log會出現"AddRunningClient starting device on non-zero client count"訊息
雖然不會造成什麼大問題,不過這樣卡卡真的有點討厭就是了
網路上一片無解阿~討論串

2010年9月20日 星期一

iPhone get rid of UIColor

還在用UIColor嗎?嫌系統顏色不夠豐富嗎?
如果想使用hex value來自定色彩
網路上都有提供簡單作法:

1. 把下面整段marco複製到最前頭,define好RGB的範圍
#define UIColorFromRGB(rgbValue) [UIColor \
colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0xFF00) >> 8))/255.0 \
blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]

2. 之後就有UIColorFromRGB()這個method可以用.例如:
[aButton setTitleColor:UIColorFromRGB(0x999999
              forState:UIControlStateNormal];

3. 就這樣,很簡單的~

   參考資料:iPhone Dev SDK
                 eric e. dolecki

2010年9月13日 星期一

iPhone 使用NSLog來顯示各種型態資料

我在xcode的開發過程中,
除了使用build and debug mode來逐一檢查參數的值
有時候也需要在build and run mode底下留一些訊息,
這時候一定會用到NSLog.
可是使用上若是沒注意資料型態,胡亂指定可是會導致crash的,
因為邪惡的compiler在編譯過程中根本不會有錯誤阿!

所以這邊筆記一下常用的資料型態取法
NSLog(@"%@",myobj);

%@         object
%d, %i     signed int
%u         unsigned int
%f         float/double

%x, %X    hexadecimal int
%o         octal int
%zu        size_t
%p         pointer
%e         float/double (in scientific notation)
%g         float/double (as %f or %e, depending on value)
%s         C string (bytes)
%S         C string (unichar)
%.*s       Pascal string 
(requires two arguments, pass pstr[0] as the first, 
pstr+1 as the second)

%c         character
%C         unichar

%lld       long long
%llu       unsigned long long
%Lf        long double

資料來源: cocoadev
               cocoachina

這篇出自cocoachina的精華文章更清楚喔

2010.12.08  補充顯示boolean參數方法
1
NSLog(@"Bool object is: %@ ",(boolobj ? @"YES" : @"NO"));

2011.10.21 補充顯示CGPoint的方法
1
NSLog(@"The point is: %@ ",NSStringFromCGPoint(point));

2010年9月7日 星期二

iPhone Executing Code in the Background (上)

為了徹底搞清楚iOS提供的多工有什麼規範
我居然很認真的看了官方的一些資料,還筆記起來
想說都筆記了,就分享一下,也歡迎大家多多指教
Executing Code in the Background


大部分的程式只要一進入background就會被suspend住而且不執行任何code。
除非我們特別去notify系統,蘋果有提供幾個option讓不同種類的行為在背景下執行。

application準備在background執行
1. application會先link against iOS4 
2. 接著在假定支援多工的情形下會自動implement 合適的method來處理轉換到背景
     的這件事情

application檢查到底有沒有支援多工
首先,程式有背景執行工作的特性不見得都能被iOS的device所支援
又如果,device根本就不支援背景執行工作或不是搭配iOS4及之後的版本,
那系統會用最早先的方式來處理我們的application.
具體來說,當我們的app要離開時,app會被終止並從memory中清掉,然後app的delegate會收到 applicationWillTerminate:的訊息

反正app在不支援多工的情形下應該要有所準備.
如果我們的code想要有支援背景工作但不去執行這部份,
可以用 UIDevice class中 multitaskingSupported的屬性來檢查到底有沒有支援多工.
如果app是要支援iOS4之前的系統那更要在執行背景工作前check這項屬性

Checking for background support on earlier version of iOS
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:@selector(isMultitaskingSupported)])
   backgroundSupported = device.multitaskingSupported;

先宣告我們支援哪些背景工作
要支援一些類型的背景工作就必須在app執行前先宣告好.
app使用Info.plist中UIBackgroundModes的鍵值來宣告這類的事情,
這些鍵值的內容是包含了一個或以上的array,例如:

audio: app 可以在背景播放有聲內容.
location: app 可以讓使用者知道自己的所在位置.
voip: app 可以讓使用者使用internet 來打電話.

上述的這些value可以讓app知道什麼時間點該起來回應相對的事件.
然後iOS提供了兩種背景工作的方式

- app 可以要求系統提供一個extra的時間給這項工作
- app 預先排好要被傳遞的local notification時間點

背景狀態轉換的支援
基本上來講只要app是使用iOS4或更新的版本都會支援背景狀態的轉換
如果要很完整的support,或是說用比較嚴謹且正確的方式來支援
則需要確保在delegate中有實作下面幾個methods:

上述這些methods都是在app執行當中即時告知我們app將要轉換狀態了
雖然說有些method早在專案建立之初就已經有實作了,
只是這邊需要增加一些行為判斷來確保背景執行起來是很安全的.
例如: 當一個進入背景並且從記憶體被清除的app重新被launch起來時
我們可能會希望可以繼續之前離開時的狀態.
這個method來檢查有沒有額外的information或是其他已儲存的參數需要在app被喚起後把它restore回app的ui

成為多工感知且可靠的Application
在背景當中執行app一定是比前景中執行要來的限制多多,即使app沒有要在背景執行,
官方也建議了以下的原則:
  • Do not make any OpenGL ES calls from your code.
  • 只要createEAGLContext的物件或是牽涉到任何有關 OpenGL ES的drawing指令會讓app馬上被終止掉.
  • Cancel any Bonjour-related services before being suspended.
  • 當我們的app要移到背景但還沒被suspend之前,應該要在Bonjour那邊取消註冊並且關閉所有與網路服務有關的listing sockets.再說,一個被suspend的程式也不能對incoming 的服務要求有所回應.所以為了避免出現占著茅坑不拉屎的狀況出現,如果不在suspend之前自己關閉所有Bonjour服務,系統還是會自動幫我們關掉的.
  • Be prepared to handle connection failures in your network-based sockets.
  • 系統可能會在一些原因下將socket與我們的app拆開來.只要我們socket-based的程式有為一些類型的網路failure做準備,像是:lost signal或是網路轉換就不會導致任何不正常的問題.當我們要返回程式時使用socket遇到失敗可以很簡單的重新建立連線來解決.
  • Save your application state before moving to the background.
  • 在記憶體不足的情況下,背景執行中的程式會從記憶體中被清掉.被suspend的程式首當其衝,而且系統不會有任何的提示.所以app應該在進入背景執行前要備妥充足的資訊,記錄,參數好讓需要的時候可以重新組回來.在回復app的同時會讓使用者看到app主畫面重啟時的snapshot可以增加一致性.
  • Release any unneeded memory when moving to the background.
  • 當然背景執行的程式需要有充足的狀態訊息來達到快速重返前景執行的目的.但是有物件或是滿大的記憶體空間都沒有要再使用(例如沒用到的圖檔)就應該考慮在進入背景執行前把他們都release掉
  • Stop using shared system resources before being suspended.
  • 如果app有用到系統分享出來的資源(例如AddressBook),要在被suspend之前停止使用.這些系統分享出來的資源優先權會落到前景中的app,如果捨不得放掉這些資源系統還是會強迫中斷的.
  • Avoid updating your windows and views.
  • 雖然說在app背景執行中一直去操縱或是增加window,view object不會導致程式被終止.但是這類的事情應該要延後到app又重回前景再來處理.畢竟在背景的程式是看不到畫面,一直update是update心酸的嗎
  • Respond to connect and disconnect notifications for external accessories.
  • 如果app與外部的附件有communication,系統會自動發出斷線的通知給要進入背景的app.而app應該要註冊這個通知好用來關閉與外部附件的session.當app重回前景時也會藉由註冊的通知來給予重新連線的機會
  • Clean up resources for active alerts when moving to the background.
  • 為了在切換app間保留內文,系統並不會在app進入背景執行時自動關掉action sheet(UIActionSheet)或是alert view(UIAlertView).
  • 但app使用的是iOS 3.x或之前版本, alert view與action sheet仍會被關掉,所以app的cancellation handler就有機會在那邊執行了.這時開發者可以自己決定是要在進入背景執行前提供一個合適的清除行為(手動去cancel那些action sheet或是alert view)還是要把內文資訊給存下來,之後再回復那個view.(萬一在app被終止掉的狀況下)
  • Remove sensitive information from views before moving to the background.
  • 在app要進入背景執行前,系統會給app當下的window做一個快照,當app又要回復時會秀這個快照一下下.因此從applicationDidEnterBackground:這個method回復之前,我們因該針對敏感的使用者私人訊息加以隱藏或覆蓋.
  • Do minimal work while running in the background.
  • 在背景工作能執行的時間要比前景有更多的限制.如果我們的app在背景播放音樂或是在監控location的變化,那就要focus在重點上並把較不重要的事情挪後處理.app若是在背景中花很多時間在執行一些事情,系統會縮短給app的時間甚至通通給終止掉.

還有~等我又發瘋把它們認真看完吧...

    內容回應