如果圖片不顯示可以到我的小站去看
簡介
AVFoundation
在iOS上多媒體的處理主要依賴的是AVFoundation框架,而AVFoundation是基于CoreAudio、CoreVideo、CoreMedia、CoreAnimation之上高層框架,在AVFoundation框架之上蘋果還提供給我們更高層一些處理媒體數(shù)據(jù)的框架。
如AVKit、iOS的UIKit、OS的AppKit。AVFoundation提供了大量強(qiáng)大的工具集,可通過這個(gè)框架處理音視頻編程,但是如同蘋果中的的Kit一樣,封裝的越高級,個(gè)性化就會困難些,一些實(shí)際項(xiàng)目中的奇葩需求難以實(shí)現(xiàn)。本章所講的內(nèi)容是AVFoundation上層加下層的AVAudioEngine實(shí)現(xiàn)。
AVAudioEngine
AVAudioEngine是Objective-C的音頻API接口,具有低延遲(low-latency)和實(shí)時(shí)(real-time)的音頻功能,并且具有如下特點(diǎn):
讀寫所有Core Audio支持的格式音頻文件
播放和錄音使用 (files) 和音頻緩沖區(qū) (buffers)
動態(tài)配置音頻處理模塊 (audio processing blocks)
可以進(jìn)行音頻挖掘處理 (tap processing)
可以進(jìn)行立體聲音頻信號混合和3d效果的混合
音樂設(shè)備數(shù)字接口MIDI 回放和控制,通過樂器的采樣器
AVAudioEngine的工作原理可以簡單的分為三個(gè)部分:
從圖中可以看出AVAudioEngine的每一步操作都是一個(gè)音頻操作節(jié)點(diǎn)(Node),每個(gè)完整的操作都包含輸入節(jié)點(diǎn)和輸出節(jié)點(diǎn)以及經(jīng)中間的若干個(gè)處理節(jié)點(diǎn),包括但不限于,添加音效、混音、音頻處理等。整體的流程和GPUImage的流程差不多,都是鏈?zhǔn)浇Y(jié)構(gòu),通過節(jié)點(diǎn)來鏈接成一個(gè)完整的流水線,其中每個(gè)節(jié)點(diǎn)都有自己特有的屬性,可以通過改變屬性的值來改變經(jīng)由該節(jié)點(diǎn)后的音頻輸出效果,用音效節(jié)點(diǎn)舉例:一個(gè)聲音流通過這個(gè)音效節(jié)點(diǎn),假如這個(gè)節(jié)點(diǎn)可以給該段聲音添加一個(gè)回響的效果,那么通過該節(jié)點(diǎn)特有的屬性可以設(shè)置回想的間隔、干濕程度等,這樣一來經(jīng)過這個(gè)節(jié)點(diǎn)處理過的聲音流就會變成我們想要的樣子,然后他作為為一個(gè)輸入了再次流入其他節(jié)點(diǎn)。上圖的Mixer其實(shí)是包含若干個(gè)這樣的音效節(jié)點(diǎn)
原理
清唱的功能很簡單,就是通過麥克風(fēng)錄制聲音,然后添加音效或者做一些處理之后再輸出,因?yàn)椴灰錁罚允÷粤艘淮蟛糠植僮?添加配樂完整K歌在下期會講到),但是有一個(gè)問題就是耳返,也叫返送:
這個(gè)東西是必不可少的,因?yàn)橛辛硕的憔涂梢詫?shí)時(shí)調(diào)整自己的聲音,極大的降低了走調(diào)的風(fēng)險(xiǎn)和尷尬,一個(gè)很簡單的例子,現(xiàn)在有不少人喜歡在水房唱歌或者是洗澡的時(shí)候唱歌,原因就是在水房或者是衛(wèi)生間通常會有回音,而回音就是天然的耳返,所以在有回音的地方唱歌就會感覺自己的聲音洪亮而且音準(zhǔn)很好(因?yàn)槟憧梢詫?shí)時(shí)的通過回音來調(diào)整自己的聲調(diào))。演唱會上唱歌的人的耳機(jī)中都是耳返。而且耳返要有一個(gè)要求就是,你所聽到的你自己的聲音一定要和觀眾或者是其他的人聽到的一樣,不然就不會有作用,我們平時(shí)自己說話自己能聽到是因?yàn)槁曇敉ㄟ^骨傳導(dǎo)到達(dá)我們的耳朵,而聽眾聽到的是通過空氣介質(zhì)傳播,所以是否有耳返直接決定了你演唱質(zhì)量的好壞。
使用AVAudioEngine來完成這個(gè)功能其實(shí)就是運(yùn)用了他的實(shí)時(shí)音頻的特點(diǎn),他可以幾乎在沒有延遲的情況下同時(shí)創(chuàng)建音頻的輸入和輸出,而且對這個(gè)做了高度的封裝使我們能更加關(guān)心音效調(diào)整
實(shí)現(xiàn)
創(chuàng)建音頻文件用來接收待錄制的聲音:
//創(chuàng)建音頻文件。
NSString * path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString * filePath = [path stringByAppendingPathComponent:@"123.caf"];
NSURL * url = [NSURL fileURLWithPath:filePath];
創(chuàng)建AVAudioEngine,并打通輸入和輸出節(jié)點(diǎn):
-
創(chuàng)建AVAudioEngine,并初始化。這里要弄成屬性不然會被釋放,沒有效果
@interface ViewController (){ @property (nonatomic, strong) AVAudioEngine * engine; @property (nonatomic, strong) AVAudioMixerNode * mixer; @end
self.engine = [[AVAudioEngine alloc] init]; self.mixer = [[AVAudioMixerNode alloc] init];
-
打通輸入和輸出節(jié)點(diǎn):
[_engine connect:_engine.inputNode to:_engine.outputNode format:[_engine.inputNode inputFormatForBus:AVAudioPlayerNodeBufferLoops]];
所使用的是如下方法。
/*! @method connect:to:format: @abstract Establish a connection between two nodes @discussion This calls connect:to:fromBus:toBus:format: using bus 0 on the source node, and bus 0 on the destination node, except in the case of a destination which is a mixer, in which case the destination is the mixer's nextAvailableInputBus. */ - (void)connect:(AVAudioNode *)node1 to:(AVAudioNode *)node2 format:(AVAudioFormat * __nullable)format;
-
開啟AVAudioEngine:
該方法可能會開啟失敗,需要開發(fā)者自定去處理
[_engine startAndReturnError:nil];
以上步驟走完后并且開啟成功你就會發(fā)現(xiàn)你從耳機(jī)里面可以實(shí)時(shí)的聽到你的聲音了。
-
音效:
正常來說光有耳返還不夠,因?yàn)榍宄m然沒有配樂伴奏,但是是支持用戶調(diào)節(jié)音效的,類似于變聲。這就用到AVAudioEngine中的AVAudioUnitEffect類。
image- 1.AVAudioUnitReverb:混響,混響可以模擬咱們在一個(gè)空曠的環(huán)境,比如教堂、大房間等,這樣咱們在說話的時(shí)候,就會有回音,并且聲音也比較有立體感。其中該類別下面又分為
typedef NS_ENUM(NSInteger, AVAudioUnitReverbPreset) { AVAudioUnitReverbPresetSmallRoom = 0, AVAudioUnitReverbPresetMediumRoom = 1, AVAudioUnitReverbPresetLargeRoom = 2, AVAudioUnitReverbPresetMediumHall = 3, AVAudioUnitReverbPresetLargeHall = 4, AVAudioUnitReverbPresetPlate = 5, AVAudioUnitReverbPresetMediumChamber = 6, AVAudioUnitReverbPresetLargeChamber = 7, AVAudioUnitReverbPresetCathedral = 8, AVAudioUnitReverbPresetLargeRoom2 = 9, AVAudioUnitReverbPresetMediumHall2 = 10, AVAudioUnitReverbPresetMediumHall3 = 11, AVAudioUnitReverbPresetLargeHall2 = 12 } NS_ENUM_AVAILABLE(10_10, 8_0);
從名字可以看出是在模擬不同環(huán)境下的音效,比如其中的大中小屋子,大廳等。
該類別可以自定義的屬性是wetDryMix,就是可以讓我們的聲音更空靈。
/*! @property wetDryMix @abstract Blend of the wet and dry signals Range: 0 (all dry) -> 100 (all wet) Unit: Percent */ @property (nonatomic) float wetDryMix;
可以通過如下方式創(chuàng)建AVAudioUnitReverb
AVAudioUnitReverb * reverd = [[AVAudioUnitReverb alloc] init]; reverd.wetDryMix = 100; [reverd loadFactoryPreset:AVAudioUnitReverbPresetLargeRoom];
-
2.AVAudioUnitEQ:均衡器,咱們可以使用均衡器來調(diào)節(jié)咱們音頻的各個(gè)頻段,比如,我想讓我的低音更加渾厚,我就可以調(diào)節(jié)EQ的20-150HZ的頻段,如果你想讓你的聲音更加明亮,那可以調(diào)節(jié)500-1KHZ的頻段,這個(gè)調(diào)節(jié)涉及到一些專業(yè)方面的知識,如果只是想讓用戶去使用的話,可以用蘋果給我們更封裝好的幾個(gè)效果即可,這個(gè)就類似于photoshop和美圖秀秀的區(qū)別。
typedef NS_ENUM(NSInteger, AVAudioUnitEQFilterType) { AVAudioUnitEQFilterTypeParametric = 0, AVAudioUnitEQFilterTypeLowPass = 1, AVAudioUnitEQFilterTypeHighPass = 2, AVAudioUnitEQFilterTypeResonantLowPass = 3, AVAudioUnitEQFilterTypeResonantHighPass = 4, AVAudioUnitEQFilterTypeBandPass = 5, AVAudioUnitEQFilterTypeBandStop = 6, AVAudioUnitEQFilterTypeLowShelf = 7, AVAudioUnitEQFilterTypeHighShelf = 8, AVAudioUnitEQFilterTypeResonantLowShelf = 9, AVAudioUnitEQFilterTypeResonantHighShelf = 10, } NS_ENUM_AVAILABLE(10_10, 8_0);
上面是一些蘋果幫助我們定義好的濾波器,比如低通濾波器 衰弱高頻、可以引發(fā)共鳴的 低通濾波器
不過一般在清唱的時(shí)候這個(gè)用處不大,這個(gè)效果主要用到在配合伴奏的時(shí)候,如果伴奏音調(diào)過高,可以使用該方法適當(dāng)?shù)奶岣呷寺曇粽{(diào)或者降低伴奏的音調(diào),可以通過如下方式使用,然后更改這個(gè)節(jié)點(diǎn)一些屬性值。
AVAudioUnitEQ * eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:1]; AVAudioUnitEQFilterParameters * filter = eq.bands.firstObject; filter.filterType = AVAudioUnitEQFilterTypeResonantHighShelf; filter.bandwidth = 10; filter.gain = 20;
-
3.AVAudioUnitDistortion:失真,這個(gè)就是我們常說的電音,一般說唱或者搖滾,重金屬之類的曲風(fēng)會用到這個(gè)效果,同樣蘋果給我們提供了預(yù)設(shè)的幾個(gè)效果,如果不是有專業(yè)的需求我們可以直接使用。
typedef NS_ENUM(NSInteger, AVAudioUnitDistortionPreset) { AVAudioUnitDistortionPresetDrumsBitBrush = 0, AVAudioUnitDistortionPresetDrumsBufferBeats = 1, AVAudioUnitDistortionPresetDrumsLoFi = 2, AVAudioUnitDistortionPresetMultiBrokenSpeaker = 3, AVAudioUnitDistortionPresetMultiCellphoneConcert = 4, AVAudioUnitDistortionPresetMultiDecimated1 = 5, AVAudioUnitDistortionPresetMultiDecimated2 = 6, AVAudioUnitDistortionPresetMultiDecimated3 = 7, AVAudioUnitDistortionPresetMultiDecimated4 = 8, AVAudioUnitDistortionPresetMultiDistortedFunk = 9, AVAudioUnitDistortionPresetMultiDistortedCubed = 10, AVAudioUnitDistortionPresetMultiDistortedSquared = 11, AVAudioUnitDistortionPresetMultiEcho1 = 12, AVAudioUnitDistortionPresetMultiEcho2 = 13, AVAudioUnitDistortionPresetMultiEchoTight1 = 14, AVAudioUnitDistortionPresetMultiEchoTight2 = 15, AVAudioUnitDistortionPresetMultiEverythingIsBroken = 16, AVAudioUnitDistortionPresetSpeechAlienChatter = 17, AVAudioUnitDistortionPresetSpeechCosmicInterference = 18, AVAudioUnitDistortionPresetSpeechGoldenPi = 19, AVAudioUnitDistortionPresetSpeechRadioTower = 20, AVAudioUnitDistortionPresetSpeechWaves = 21
} NS_ENUM_AVAILABLE(10_10, 8_0);
其實(shí)里面有些還是比較有感覺的,比如扭曲的立方體,或者外星人的喋喋不休等。有興趣的可以說都試試 使用方式同之前的效果一樣
AVAudioUnitDistortion * dist = [[AVAudioUnitDistortion alloc] init];
[dist loadFactoryPreset:AVAudioUnitDistortionPresetDrumsBitBrush];
dist.preGain = 4;
dist.wetDryMix = 100;* 4.AVAudioUnitDelay:延遲,延遲就是 發(fā)出一個(gè)聲音之后,過段時(shí)間再次發(fā)出,一直衰減到聽不見。類似咱們的回聲。可以通過里面的屬性去細(xì)微的調(diào)節(jié)延遲的時(shí)間、速度等。
-
添加音效:
主要流程就是鏈?zhǔn)疥P(guān)系input(Mic或者音頻文件) ->效果器->output
如果是多個(gè)音效
input(Mic或者音頻文件) ->效果器1->效果器2->output
我們以AVAudioUnitReverb效果為例
AVAudioUnitReverb * reverb = [[AVAudioUnitReverb alloc] init]; [reverb loadFactoryPreset:AVAudioUnitReverbPresetLargeRoom]; reverb.wetDryMix = 100; //把混響附著到音頻引擎 [_engine attachNode:reverb]; //依次鏈接輸入-> 混響 -> 輸出 [_engine connect:_engine.inputNode to:reverb format:[_engine.inputNode inputFormatForBus:AVAudioPlayerNodeBufferLoops]]; [_engine connect:reverb to:_engine.outputNode format:[_engine.inputNode inputFormatForBus:AVAudioPlayerNodeBufferLoops]]; //啟動引擎 [_engine startAndReturnError:nil];
同理添加多個(gè)音效則需要嚴(yán)格按照 input(Mic或者音頻文件) ->效果器1->效果器2->output 順序來添加
綜上:完成了以上所有操作后你就可以實(shí)時(shí)在耳機(jī)中聽到自己經(jīng)過音效處理過的聲音了,而且這樣帶著耳機(jī)唱歌效果會非常好,聲音洪亮不易跑調(diào)。還可以針對不同的曲風(fēng)調(diào)整自己的音效。
聲音混合、寫入本地:
我們需要把我們清唱的歌曲錄制到本地,正常的錄制時(shí)使用AVAudioRecorder來進(jìn)行錄制的,像這樣
AVAudioSession * session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
[session setActive:YES error:nil];
NSString * path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
self.filePath = [path stringByAppendingPathComponent:@"SoWeak"];
self.recordFileUrl = [NSURL fileURLWithPath:self.filePath];
//設(shè)置參數(shù)
NSDictionary *recordSetting = [[NSDictionary alloc] initWithObjectsAndKeys:
//采樣率 8000/11025/22050/44100/96000(影響音頻的質(zhì)量)
[NSNumber numberWithFloat: 8000.0],AVSampleRateKey,
// 音頻格式
[NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
//采樣位數(shù) 8、16、24、32 默認(rèn)為16
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
// 音頻通道數(shù) 1 或 2
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
//錄音質(zhì)量
[NSNumber numberWithInt:AVAudioQualityHigh],AVEncoderAudioQualityKey,
nil];
self.recorder = [[AVAudioRecorder alloc] initWithURL:self.recordFileUrl settings:recordSetting error:nil];
if (self.recorder) {
_recorder.meteringEnabled = YES;
[_recorder prepareToRecord];
[_recorder record];
}
但是很明顯這樣錄制聲音需要開啟session 而聲音的session是一個(gè)單利,如果這樣開啟了那么我們后面就不能用AVAudioEngine來進(jìn)行音頻采集了,也就沒有之前的效果。所有根據(jù)以往的經(jīng)驗(yàn),AVAudioEngine在開啟引擎之后一定會有一個(gè)delegate或者是block回調(diào)出采集到的數(shù)據(jù)的。于是我們找到了AudioNode中的這個(gè)方法:
- (void)installTapOnBus:(AVAudioNodeBus)bus bufferSize:(AVAudioFrameCount)bufferSize format:(AVAudioFormat * __nullable)format block:(AVAudioNodeTapBlock)tapBlock;
其中的block的buffer 便是我們采集到的數(shù)據(jù)。
/*! @typedef AVAudioNodeTapBlock
@abstract A block that receives copies of the output of an AVAudioNode.
@param buffer
a buffer of audio captured from the output of an AVAudioNode
@param when
the time at which the buffer was captured
@discussion
CAUTION: This callback may be invoked on a thread other than the main thread.
*/
typedef void (^AVAudioNodeTapBlock)(AVAudioPCMBuffer *buffer, AVAudioTime *when);
我們需要把buffer轉(zhuǎn)成AVAudioFile然后通過AVAudioFile的write方法寫入
初始化AVAudioFile
AVAudioFile * audioFile = [[AVAudioFile alloc] initForWriting:url settings:@{} error:nil];
然后在block中實(shí)現(xiàn)
[audioFile writeFromBuffer:buffer error:nil];
這個(gè)時(shí)候?qū)懭氤晒θ缓蟛シ疟镜劁浺粑募l(fā)現(xiàn)只有自己的原生,并沒有后面添加的音效,回音等效果。
其實(shí)是因?yàn)槲覀冸m然添加了音效但是我們沒有把音效和原生混合在一起,即使我們實(shí)時(shí)聽到的是沒有問題的,但是當(dāng)保存到本地之后如果沒有做混合,系統(tǒng)會默認(rèn)將最原始的聲音寫入本地,這里我們需要用到
AVAudioMixerNode
他是繼承與AVAudioNode 也屬于一個(gè)特殊音頻處理節(jié)點(diǎn),使用方式和之前的音效節(jié)點(diǎn)一樣,添加在所有的處理之后、輸出之前即可,像這樣
input(Mic或者音頻文件) ->效果器1->效果器2->Mixer->output
不過唯一需要注意的是這個(gè)mixer最好也寫成屬性、不然會出問題。
所以一個(gè)完整的帶音效的清唱錄制為:
//創(chuàng)建音頻文件。
NSString * path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString * filePath = [path stringByAppendingPathComponent:@"123.caf"];
NSURL * url = [NSURL fileURLWithPath:filePath];
AVAudioFile * audioFile = [[AVAudioFile alloc] initForWriting:url settings:@{} error:nil];
self.recordFileUrl = url;
AVAudioUnitReverb * reverd = [[AVAudioUnitReverb alloc] init];
reverd.wetDryMix = 100;
[reverd loadFactoryPreset:AVAudioUnitReverbPresetLargeRoom];
[self.engine attachNode:reverd];
[self.engine attachNode:_mixer];
[self.engine connect:self.engine.inputNode to:reverd format:audioFile.processingFormat];
[self.engine connect:reverd to:_mixer format:audioFile.processingFormat];
[self.engine connect:_mixer to:self.engine.outputNode format:audioFile.processingFormat];
[_mixer installTapOnBus:0 bufferSize:4096 format:[_engine.inputNode inputFormatForBus:AVAudioPlayerNodeBufferLoops] block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[audioFile writeFromBuffer:buffer error:nil];
NSLog(@"我錄制到的數(shù)據(jù)是 === %@", buffer);
}];
[self.engine startAndReturnError:nil];
總結(jié)
通過如上方法可以完整的實(shí)現(xiàn)清唱功能,但是唱吧清唱使用的是AudioUnit,AudioUnit是iOS中音頻的非常底層的實(shí)現(xiàn),由C語言實(shí)現(xiàn),因?yàn)槌芍谐饲宄膺€有很多非常復(fù)雜的音頻處理功能,所以只有AudioUnit可以滿足,但是對于清唱這個(gè)功能來說,兩種實(shí)現(xiàn)方式達(dá)到了同樣的效果,本文介紹的更加輕量級,不過關(guān)于AudioUnit也正在學(xué)習(xí)過程,后續(xù)會輸出相應(yīng)的文章。