More-iOS開發中的音頻相關內容總結

這段時間陸陸續續的在作一些關於iOS開發細節的東西,先是跟進了音頻部分(如下簡稱爲Audio),主要分爲如下幾大部分:git

  1. Audio的架構和框架
  2. 編解碼/文件封裝格式
  3. 播放系統聲音/震動/提示聲音
  4. 綜合demo
  5. 使用AVFoundation框架進行中英文語音識別

提及iOS中的Audio,耳熟能詳的就是AVFoundation,畢竟它是個全能型的框架,不過的AVFoundation如今的地位能夠類比JavaScript如今的地位,JavaScript如今甚至都插手嵌入式開發了🙂。github

但也就是這種什麼所謂的全能型選手,擁有大而全的技能,卻缺乏了一些底蘊。也就是在這段時間中,我才發現,竟然還有專門針對3D音效的openAL、擅長編解碼過程的AudioToolBox等等一些很是優秀的音頻處理框架,重點是這些框架都是iOS SDK中自己就提供了的。算法

根據網上資料,梳理了以下一張在iOS中的音頻處理各個框架所處的位置, api

ww (3).png

高層服務

AVAudioPlayer

**基本操做:**播放、暫停、中止、循環等等一些基本的音頻播放功能。架構

**控制:**可對音頻進行任意時間位置播放;進度控制。框架

**其它:**可從文件或緩衝區播放聲音;獲取音視頻關鍵參數,如音頻標題、做者、功率等等。ide

若是咱們並不想實現好比3D立體音效,精確的音頻歌詞同步等功能,那麼這個框架所提供的API是徹底足夠的,可是若是咱們想要的進行一些好比對音頻流的捕獲,捕獲後還要進行一些RTSP、RTMP等流媒體協議的處理,再或者進行一些RAC、PCM或PCM轉MP3等一些音頻的轉碼方式處理,那這個框架就很是捉雞了。🙂可是它可以很是輕鬆的進行簡單的音頻操做,如上所示基本操做、控制等。oop

AudioQueue

相對於AVAudioPlayer來講,其更增強大!它不只可以完成播放音頻和錄製音頻,還可以經過AudioQueue拿到音頻的原始信息,想一想看!咱們可以拿到音頻的原始信息,那就能夠作好比任意的編碼解碼、一些特效轉化如變音等等騷操做!咱們還能夠進行任意的應用層封裝,好比說封裝成適用於RTMP、RTSP的流媒體協議處理。測試

使用Audio Queue,咱們只須要進行三個步驟便可:ui

  1. 初始化Audio Queue。添加一些播放源、音頻格式等。
  2. 管理回調方法。在回調方法中咱們能夠拿到音頻的原始數據。
  3. 實例化Audio Queue。使用AudioQueueOutput完成音頻的最終播放。

openAL

emmm,看到openAL我會想到openGL,openGL主要是用於處理一些3D的圖像或變化,openAL主要是在聲源物體、音效緩衝和收聽者這三者之間進行設置來實現3D效果,好比能夠設置聲源的方向、速度、狀態等,因此咱們能夠聽到聲音由遠及近的這種3D效果。

總的來講,openAL主要有三個方面,

  1. 聲源的設置;
  2. 接收者的控制;
  3. 聲源模式的設置。例如聲源是由遠及近運動,仍是由近及遠運動,咱們還能夠把聲源設置在一個3D空間中。

AudioFile

對音頻文件的信息進行讀取(注意不是對音頻文件進行編解碼),經過AudioFile框架的相關API對一個音頻文件信息進行讀取,主要有如下幾大步驟:

  1. AudioFileOpenURL。首先咱們要經過一個URL打開音頻文件。

  2. AudioFileGetPropertyInfo。獲取咱們想要讀取的音頻文件信息類型。

  3. AudioFileGetProperty。獲得相關音頻的屬性NSLog出來便可。

  4. AudioFileClose。關閉音頻文件。(打開文件就要關閉文件🙂)

從上咱們看到基本上都是歸類於Get方法,可是AudioFile也提供了一個豐富的set方法,能夠實時的修改對應音頻相關信息。

舉個🌰🍐!!!

咱們首先得引入#import <AudioToolbox/AudioToolbox.h>框架,從Xcode 7開始,咱們就不須要手動引入framework了,由於當咱們引入iOS SDK中對應的framework中的相關.h文件時,Xcode會自動幫咱們導入對應的framework。

// 首先從應用沙盒中提取音頻文件路徑
    NSString *audioPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"];
    // 轉置成URL
    NSURL *audioURL = [NSURL fileURLWithPath:audioPath];
    // 打開音頻
    // 設置音頻文件標識符
    AudioFileID audioFile;
    // 經過轉置後的音頻文件URL,打開獲取到的音頻文件
    // kAudioFileReadPermission:只讀方式打開音頻文件;(__bridge CFURLRef):只接受C語言風格類型變量,因此咱們要用一個強轉橋接類型轉回去
    AudioFileOpenURL((__bridge CFURLRef)audioURL, kAudioFileReadPermission, 0, &audioFile);
    // 讀取
    UInt32 dictionarySize = 0;
    AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyInfoDictionary, &dictionarySize, 0);
    CFDictionaryRef dictionary;
    AudioFileGetProperty(audioFile, kAudioFilePropertyInfoDictionary, &dictionarySize, &dictionary);
    // 通過以上兩步,咱們就拿到了對應音頻的相關信息。再強轉橋接類型回去便可。
    NSDictionary *audioDic = (__bridge NSDictionary *)dictionary;
    for (int i = 0; i < [audioDic allKeys].count; i++) {
        NSString *key = [[audioDic allKeys] objectAtIndex:i];
        NSString *value = [audioDic valueForKey:key];
        NSLog(@"%@-%@", key, value);
    }
    CFRelease(dictionary);
    AudioFileClose(audioFile);
複製代碼

運行工程後,便可看到對應的log,

Audio 2.png

與iOS Audio有關的framework有:

framework Name uses
MediaPlayer.framework VC,提供一些控制類ViewController,使用起來較爲簡單,致命缺點:功能單一,對底層API高度封裝、高度集成,不利於自定義
AudioIUnit.framework 底層,提供核心音頻處理插件,例如音頻單元類型、音頻組件接口、音頻輸入輸出單元,用於控制音頻的底層交互
OpenAL.framework 3D,提供3D音頻效果
AVFoundation.framework 全能型,音頻的錄製、播放及後期處理等(基於C)
AudioToolbox.framework 編解碼,音頻編解碼格式轉化

綜上所述,在平常開發中我和你們也要重點關注iOS音頻架構中的高層服務框架,這部分框架是平常開發中常常會手擼代碼的地方,而在framework層面,咱們要重點關注AVFoundation,雖然它是一個基於C的framework。🙂,可是它卻可以對音頻進行精細入微的控制,當咱們使用AVFoundation進行錄音和播放時,可以拿到音頻的原始PCM解碼以後的數據,拿到這些數據可以對音頻進行特效的處理。若是咱們要作一個音頻播放類的產品,那麼用到MediaPlayer.framework的次數會不少。

在中層服務中,若是你們有對音頻作了一些好比RTMP、RTSP等流媒體處理的時,可能會用到Audio Convert Services(感受我是用不到了😂)。好比這麼個場景,當咱們使用RTMP進行語音直播的時候,經過麥克風採集到的數據多是原始的PCM數據,可是咱們想在播放時候使用AAC格式進行播放,那就得把PCM轉成AAC,那就得用Audio Convert Services這個中間層服務。

當咱們想作一些音頻加密算法或音頻的加密聲波,那可能就會使用到中間層的Audio Unit Services,它能夠對硬件層進行一些精細的控制。而Audio File Services是對音頻文件的封裝和變化。所以啊,除了底層服務的相關框架外,中間層和高層服務是須要咱們(尤爲是我本身🙂)去重點掌握的。

Audio SystemSound

SystemSound框架用於播放系統聲音,好比某些特殊的提示音、震動等,若咱們要使用該框架來播放自定義聲音,要求對應的音頻編碼方式爲PCM的原始音頻,長度通常不超過30秒(你要想超過也無法,只不過不推薦🙂)。

當咱們使用該框架調用震動功能時,只能用於iPhone系列設備,iPod和iPad系列均無效,由於只有iPhone系列設備的厚度可以容許塞下震動模塊(並且仍是改進後的Tapic Engine)。當咱們使用該框架播放系統音樂效果時,靜音狀況下無效;播放提示音樂效果時,不管靜音與否均有效。

所以使用SystemSound適用於播放提示音及遊戲中的特殊短音效用處會更大。

舉個🌰🍐!

NSString *deviceType = [[UIDevice currentDevice] model];
    if ([deviceType isEqualToString:@"iPhone"]) {
        // 調用正常的震動模塊,靜音後無效
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    } else {
        UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"注意" message:@"您的設備不支持震動" preferredStyle:UIAlertControllerStyleAlert];
        [self presentViewController:alertVC animated:true completion:^{
            
        }];
    }
複製代碼

以上是咱們進行調用震動模塊的測試代碼,上文已經說明只有iPhone系列設備中才能體現效果,所以咱們最好是加上設備類型判斷(固然你能夠不加🙂),改框架也是基於C的(比較直接操做底層硬件),代碼風格也是趨向於C,實際上就這一句話AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);,你們能夠從這篇文章中找到其它SystemSoundID,若是系統提供的音效並不適合咱們,那麼咱們能夠載入自定義音效,

NSURL *systemSoundURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"]];
    // 建立ID
    SystemSoundID systemSoundID;
    AudioServicesCreateSystemSoundID((CFURLRef)CFBridgingRetain(systemSoundURL), &systemSoundID);
    // 註冊callBack
    AudioServicesAddSystemSoundCompletion(systemSoundID, nil, nil, soundFinishPlaying, nil);
    // 播放聲音
    AudioServicesPlaySystemSound(systemSoundID);
複製代碼

分析以上測試代碼發現一個有趣的現象,就算是自定義音效也是要經過AudioServicesPlaySystemSound去載入音頻文件標識符,因此能夠大膽的推測!之因此iOS系統佔用這麼大的存儲空間是有至關大的一部分爲系統音效音頻資源。不用的音效還無法刪除,估計也是怕其餘App會用到吧。🙂

音頻參數(瞭解的很少,先記錄一波)

採樣率:

經常使用的如44100,CD就是。還有一些其它的32千赫茲。採樣頻率越高,所能描繪的聲波頻率也就越高。

量化精度

精度嘛,衡量一個東西的精確程度。是將模擬信號分紅多個等級的量化單位。量化的精度越高,聲音的振幅就越接近原音。由於咱們平時聽到的音樂或者聲音都是模擬信號,而通過計算機處理的都是數字信號,將模擬信號轉換爲數字信號的這個過程咱們稱之爲量化。而量化,咱們得須要必定的信號來逼近它,這種逼近的過程,也就是量化的過程,這種逼近的精度,也就成爲量化精度。因此無論咱們如何逼近,那也只是逼近而已,與原來的模擬信息仍是有些不一樣。精度越高,聽起來就越細膩

比特率

數字信號每秒鐘傳輸的信號量。

看一個綜合實例,🌰🍐

audio3.png

經過使用<AVFoundation/AVFoundation.h><AudioToolbox/AudioToolbox.h>框架來完成這個實例,在這個實例中,講讀取一個音頻文件,對其進行播放、暫停、中止等操做,並可設置是否靜音、循環播放次數、調節音量、時間,並可看到當前音頻播放進度。

界面的搭建很是簡單,你們自定義便可,只須要拖拽出對應的相關控件屬性及方法便可。

// 播放按鈕點擊事件
- (IBAction)playerBtnClick:(id)sender {
    // 設置音頻資源路徑
    NSString *playMusicPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mp3"];
    if (playMusicPath) {
        // 開啓Audio會話實例
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
        NSURL *musicURL = [NSURL fileURLWithPath:playMusicPath];
        audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:nil];
        audioPlayer.delegate = self;
        audioPlayer.meteringEnabled = true;
        // 設置定時器,每隔0.1秒刷新音頻對應文件信息(假裝成實時🙂)
        timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(monitor) userInfo:nil repeats:true];
        [audioPlayer play];
    }
}
複製代碼
// 定時器任務
- (void)monitor {
    // numberOfChannels聲道數,通常都是2吧,表明左右雙聲道
    NSUInteger channels = audioPlayer.numberOfChannels;
    NSTimeInterval duration = audioPlayer.duration;
    [audioPlayer updateMeters];
    NSString *peakValue = [NSString stringWithFormat:@"%f, %f\n channels=%lu duration=%lu\n currentTime=%f", [audioPlayer peakPowerForChannel:0], [audioPlayer peakPowerForChannel:1], (unsigned long)channels, (unsigned long)duration, audioPlayer.currentTime];
    self.audioInfo.text = peakValue;
    self.musicProgress.progress = audioPlayer.currentTime / audioPlayer.duration;
}
複製代碼
// 暫停按鈕點擊事件
- (IBAction)pauseBtnClick:(id)sender {
    // 再次點擊暫停纔會播放
    if ([audioPlayer isPlaying]) {
        [audioPlayer pause];
    } else {
        [audioPlayer play];
    }
}
複製代碼
// 中止按鈕點擊事件
- (IBAction)stopBtnClick:(id)sender {
    self.volSlider.value = 0;
    self.timeSlider.value = 0;
    [audioPlayer stop];
}
複製代碼
// 靜音按鈕點擊方法
- (IBAction)muteSwitchClick:(id)sender {
    // 實際上音量爲0即靜音
    // 恰好這仍是個Switch開關
    audioPlayer.volume = [sender isOn];
}
複製代碼
// 調節音頻時間方法(UIProgress)
- (IBAction)timeSliderClick:(id)sender {
    [audioPlayer pause];
    // 防止歸一化(Xcode默認都是0~1,轉化爲實際值)
    [audioPlayer setCurrentTime:(NSTimeInterval)self.timeSlider.value * audioPlayer.duration];
    [audioPlayer play];
}
複製代碼
// UIStepper點擊事件(音頻循環播放)
- (IBAction)cycBtnClick:(id)sender {
    audioPlayer.numberOfLoops = self.cyc.value;
}
複製代碼

Jan-29-2018 23-11-32.gif

語音識別

在iOS 7以後,AVFoundation提供了語音識別功能,使用它很是的簡單,

// 語音識別控制器
AVSpeechSynthesizer* speechManager = [[AVSpeechSynthesizer alloc] init];
speechManager.delegate = self;
// 語音識別單元
AVSpeechUtterance* uts = [[AVSpeechUtterance alloc] initWithString:@"23333"];
uts.rate = 0.5;
[speechManager speakUtterance:uts];
複製代碼

須要注意,若是本機系統語言設置成了英文是不可以識別中文的喔!

相關Demo見這。

原文連接:pjhubs.com

相關文章
相關標籤/搜索