en

hi, it seems you are using microsoft internet explorer. it doesn't match web standard and causes problems browsing this site. please please please use mozilla firefox or google chrome instead. thank you!

zh

哦哦!您正在使用Internet Explorer 瀏覽器,它與我們的網頁標準並不相容,可能會導致畫面顯示不正常。
請改用 Mozilla Firefox 或者 Google Chrome 才能正常瀏覽本網站,謝謝!

4.22.2011

簡單控制 iPhone 的麥克風

iPhone 的麥克風裝置是一個重要的輸入 Sensor ,在 iOS 3.0 後加入了 AVAudioRecorder Class ,我們可以利用 AVFoundation Framework來對此進行控制,在本篇教學文章中將介紹如何利用 AVAudioRecorder 搭配 NSTimer 來簡單的偵測輸入麥克風裝置的訊息。

首先加入 AVFoundation.framework 與 CoreAudio.framework,需要用裡面的 AVAudioRecorder Class 來偵測


接下來需要引入一些程式中會使用到類別

#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudioTypes.h>

宣告一個 AVAudioRecorder 和 NSTimer

AVAudioRecorder *Recorder;
NSTimer *Timer;

宣告完成後我們要運用 AVAudioRecorder 中的一些物件和方法來進行實作

NSURL *url=[NSURL fileURLWithPath:@"/dev/null"];

//以下宣告的set可以參照Apple的Settings文件
NSDictionary *set = [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithFloat:44100.0],
    AVSampleRateKey,
    [NSNumber numberWithInt:kAudioFormatAppleLossless],
    AVFormatIDKey,
    [NSNumber numberWithInt:1],
    AVNumberOfChannelsKey,
    [NSNumber numberWithInt:AVAudioQualityMax],
    AVEncoderAudioQualityKey,
    nil];

NSError *error;
Recorder=[[AVAudioRecorder alloc] initWithURL:url settings:set error:&error];

//若初始化成功則利用NSTimer來持續偵測聲音
if (Recorder) {
    [Recorder prepareToRecord];
    Recorder.meteringEnabled=YES;
    [Recorder record];

    Timer=[NSTimer scheduledTimerWithTimeInterval:0.3
                   target:self
                   selector:@selector(TimerCallback:)
                   userInfo:nil repeats:YES];
}
else {
    NSLog(@"error");
}

需要注意的是 [Recorder PrepareToRecord] 必須放在 Recorder.meteringEnable 之前。 再來要設定一個 TimerCallback 方法來執行 onTimer 的內容

-(void)TimerCallback:(NSTimer *)timer {
    [Recorder updateMeters];
    NSLog(@"平均:%f 波峰:%f",[Recorder averagePowerForChannel:0],[Recorder peakPowerForChannel:0]);
}

在使用 [Recorder averagePowerForChannel:0] 和 [Recorder peakPowerForChannel:0] 讀取 Power 的平均和波峰前必須先用 updateMeters 更新目前麥克風收到的訊息才行,而 Power 的數值會落在 -160 dB ~ 0 dB 之間。







16 則留言:

  1. 請問此範例程式可以用在實機嗎?可以請問所謂的@"/dev/null"代表的含義是什麼呢?

    回覆刪除
    回覆
    1. Oliver Chen 您好:
      這範例就是在實機上執行的,我不確定模擬器能不能使用,畢竟麥克風輸入並不在電腦的標準配備裡。
      @"/dev/null" 代表並未指明dev/下的那一個檔案,相關的資訊您可以參考官方的文件說明
      https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioRecorder_ClassReference/Reference/Reference.html

      刪除
    2. 謝謝牛奶,找了一下官方說明,再連結到*NSURL部份還是沒看到為什麼,還是這個意思『指向Device下的檔案,但未指明哪一個』的意思嗎?另一個問題是為什麼NSNumber numberWithFloat:44100.0,為什麼是44100這個值呢?

      刪除
    3. 抱歉,我有找到為何是44100這個值了,單聲軌 (Mono) 標準高傳真 (Hi-Fi) 音效:取樣頻率 44,100 Hz,

      刪除
    4. 您好:
      沒關係,我已鞥該把44100宣告成常數,就不會有這個誤會了。
      路徑的問題,其實我不太確定,我猜它是需要一個絕對路徑,因為我們不想錄製下來,所以使用null,不過這樣翻譯有點奇怪位在官方文件中對於該NSURL的解釋是:
      The file system location to record to. The file type to record to is inferred from the file extension included in this parameter’s value.
      在使用其他路徑得結果反而沒辦法取得mic的相關數值。

      另外不使用此方式來實作使用AudioQueueNewInput同樣也可以達到相同效果,但是實作的程式碼相對也比較複雜。

      刪除
    5. 看一下原文說明:
      The primary function of AVAudioRecorder is, as the name implies, to record audio. As a secondary function it provides audio-level information. So, here we discard the audio input by dumping it to the /dev/null bit bucket — while I can’t find any documentation to support it, the consensus seems to be that /dev/null will perform the same as on any Unix — and explicitly turn on audio metering.

      刪除
    6. 謝謝牛奶的說明,但是我很好奇你所說的原文說明,是你上幾層樓貼給我的官方說明連結嗎?以及你有回答實機測試,但資訊確輸出在console裡?可以這麼做嗎?console的波峰值變化,是哪裡的麥克風傳進去的呢?是iOS裝置傳的嗎?

      刪除
    7. 您好:
      原文來自http://mobileorchard.com/
      http://mobileorchard.com/tutorial-detecting-when-a-user-blows-into-the-mic/

      console 與你是不是用實機測試沒有任何關係,只要你使用NSLOG,需席就會被傾印在console中,如果是實機測試當然是實機的mic收音,若是使用模擬器就要看mac本身有沒有連結mic。

      刪除
    8. 謝謝牛奶的善心提供,讓我又看到一個好網站,可供學習!
      原來我一直以為console就是只能以模擬器的方式去做,原來也可以接著ios的裝置,再將ios收到的訊息再回傳到電腦內部,真的沒問都不知道,也謝謝你的說明。

      刪除
    9. 牛奶,想請教你知道利用音源孔傳資料進去給iOS裝置的技術嗎?就像是ipod suffle可以利用音源孔再轉usb就可和itune同步了!我一直好奇這樣的技術怎麼完成的呢?!

      刪除
    10. 您好:
      資料本身就只有0與1,用什麼端子來傳送都無所謂阿,你看你的3.5接口,應該是3環4節的音源線,表示他可以有4種不同的表示方式,除了原先立體聲道佔用兩節,接地佔用一節,另一節通常是用來控制音量或是其他操作訊息的像是mic,不過實際的運作還是可以個別定義每個環節的作用。(單環雙結就一定是單聲道接地與單聲道)

      回到傳輸面,只要讓某一節發出high或是low的訊號就可以進行資料的傳送。
      再來是麥克風,只有high或是low當然不足以表示麥克風接收到的資訊,所以在電流實際上在傳送時會搭配 Pulse Width Modulation(PWM),想像成在high與low之間存在的曖昧數值,這是一個使用在類比訊號上的技巧。

      我不確定這樣解釋你了不了解,因為在講下去就要講電子電路學了....

      刪除
    11. 謝謝牛奶的回覆,
      1. 由你上述說法,那我從音源孔傳回去時,以上述程式而言,利用相關的avRecoder,只能解出dB訊號,又怎麼能解出所要的資料訊號呢?
      2. 我今日試寫了一下程式後,我發現complier到實機時,對著iphone吹氣,nslog並無法呈現訊號變化,但是用模擬器時,對著macboo k的mic吹氣,nslog會有數據變化的反應,請問這問題會出在哪裡呢?

      對不起,總是好多問題要請教。

      刪除
    12. 您好:
      我手邊暫時沒有device可以測試,不過當初寫這篇文章的時候,是在手機上執行的沒錯,有可能是你的device沒有允許你的程式使用mic,你可以能要先針對這部份下手。

      刪除
    13. hi, 牛奶,過了一個年放了六天假,一如往常,還是個新手!但針對所謂的實機測試,今天終於試出來了,的確從iOS7後,要讀取使用mic,就要先做RequestRecordPermission的動作,因此不論在stackflow和對岸的博客都有相關的說明(台灣資訊好少= = )!
      如下所示的程式寫法:

      if ([audioSession respondsToSelector:@selector(requestRecordPermission:)]) {
      [audioSession performSelector:@selector(requestRecordPermission:) withObject:^(BOOL granted) {
      if (granted) {
      // Microphone enabled code
      }
      else {
      // Microphone disabled code
      }
      }];
      }
      但困擾我的是原本以為加這段在viewdidload就可以,但還是一直進入模擬器可跑但實機還是沒反應的無限問題迴圈。
      最後發現還得加入這段才行
      [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
      這樣才能就能用實機測試了!
      以上心得與您分享!謝謝你的幫忙。

      刪除
    14. 我忘了說我有宣告 AVAudioSession *audioSession=[AVAudioSession sharedInstance];

      刪除
  2. Oliver Chen 您好:
    你比起去年的你已經厲害很多了,謝謝你的分享,感恩,過段時間我會把它整理進來。

    回覆刪除