1. 程式人生 > >iOS 音訊錄製、播放(本地、網路)

iOS 音訊錄製、播放(本地、網路)

文章目錄

   一、錄音機(AVAudioRecorder)

   1、簡介

   2、如何使用

   3、具體實現(開始、暫停、停止、播放 四個功能)

   4、附件實現demo

   二、播放音訊

   1、播放本地音訊檔案(AVAudioPlayer)

   2、播放網路音訊檔案(Audio Queue Servies - 音訊佇列服務)

     <1> 音訊佇列

     <2> 音訊錄製原理

     <3> 音訊播放原理

     <4> 現有的第三方實現框架

一、錄音機(AVAudioRecorder)

1、簡介

在AVFoundation.frame框架下有一個AVAudioRecorder類專門用處理錄音操作。
AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的建立有所不同,
在建立錄音機時除了指定路徑外還必須指定錄音設定資訊,
因為錄音機必須知道錄音檔案的格式、取樣率、通道數、每個取樣點的位數等資訊,
但是也並不是所有的資訊都必須設定,通常只需要幾個常用設定。

2、如何使用

(1)初始化AVAudioRecorder物件,並指定錄音檔案路徑、錄音配置資訊
(2)設定錄製屬性。例如,要監聽音波,必須設定 meteringEnabled = YES
 (3) 呼叫record方法開始錄音

3、具體實現(開始、暫停、停止、播放 四個功能)

//
//  MainViewController.m
//  3.錄製音訊
//
//  Created by cherish on 2018/4/12.
//  Copyright © 2018年 Cherish. All rights reserved.
//

#import "MainViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "Configure.h"
@interface MainViewController ()<AVAudioRecorderDelegate
,AVAudioPlayerDelegate>
//音訊錄製 @property (nonatomic,strong) AVAudioRecorder *avaudioRecorder; //本地音訊播放 @property (nonatomic,strong) AVAudioPlayer *avaudioPlayer; //背景 @property (nonatomic,strong) UIImageView *viewBg; //音量 @property (nonatomic,strong) UIProgressView *progressView; //開始錄製 @property (nonatomic,strong) UIButton *start; //暫停錄製 @property (nonatomic,strong) UIButton *pause; //停止錄製 @property (nonatomic,strong) UIButton *stop; //播放錄音 @property (nonatomic,strong) UIButton *play; //播放網路音訊 @property (nonatomic,strong) UIButton *playNetAudio; //定時器 @property (nonatomic,weak) NSTimer *timer; @end @implementation MainViewController #pragma mark - ViewController Life - (void)viewDidLoad { [super viewDidLoad]; self.title = @"音訊錄製"; [self setUI]; [[AVAudioSession sharedInstance] requestRecordPermission:^(BOOL granted) { if (granted) { NSLog(@"麥克風打開了"); } else { NSLog(@"麥克風關閉了"); } }]; }//視訊載入 #pragma mark - OverRide Method - (AVAudioRecorder*)avaudioRecorder { if (!_avaudioRecorder) { NSError *error = nil; _avaudioRecorder = [[AVAudioRecorder alloc]initWithURL:[self getSavePath] settings:[self recordConfigure] error:&error]; _avaudioRecorder.delegate = self; //如果要監控聲波則必須設定為YES _avaudioRecorder.meteringEnabled = YES; // 把錄音檔案載入到緩衝區 [_avaudioRecorder prepareToRecord]; if (error) { NSAssert(YES, @"錄音機初始化失敗,請檢查引數"); } } return _avaudioRecorder; }//錄音機 - (AVAudioPlayer*)avaudioPlayer { if (!_avaudioPlayer) { NSError *error = nil; _avaudioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:[self getSavePath] error:&error]; //設定代理 _avaudioPlayer.delegate = self; //將播放檔案載入到緩衝區 [_avaudioPlayer prepareToPlay]; } return _avaudioPlayer; } - (NSTimer*)timer { if (!_timer) { _timer = [TimerWeakTarget scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(upDataProgress) userInfo:nil repeats:YES]; } return _timer; }//定時器 #pragma mark - Private Methods - (void)setUI { //背景 self.viewBg = [[UIImageView alloc]initWithFrame:self.view.bounds]; self.viewBg.image = [UIImage imageNamed:@"viewbg"]; self.viewBg.contentMode = UIViewContentModeScaleAspectFill ; self.viewBg.layer.masksToBounds = YES; self.viewBg.userInteractionEnabled = YES; [self.view addSubview:self.viewBg]; // 功能按鈕 NSArray *titles = @[@"開始錄音",@"暫停錄音",@"播放錄音",@"停止錄音"]; CGFloat gap = (KSCreenWidth-titles.count*80)/5.0; for (int i = 0; i < 4; i++) { UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom]; [btn setTitle:titles[i] forState:UIControlStateNormal]; [btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; btn.backgroundColor = [UIColor redColor]; btn.titleLabel.textAlignment = NSTextAlignmentCenter; btn.titleLabel.font = [UIFont systemFontOfSize:15.0f]; btn.tag = 555+i; [btn addTarget:self action:@selector(btnAction:) forControlEvents:UIControlEventTouchUpInside]; [self.viewBg addSubview:btn]; btn.sd_layout .leftSpaceToView(self.viewBg, gap+i*(gap+80)) .widthIs(80) .heightIs(50) .bottomSpaceToView(self.viewBg, 50); } self.progressView = [[UIProgressView alloc]init]; self.progressView.progressTintColor = [UIColor redColor]; self.progressView.trackTintColor = [UIColor whiteColor]; self.progressView.progress = 0.5f; [self.viewBg addSubview:self.progressView]; self.progressView.sd_layout .leftSpaceToView(self.viewBg, 15) .rightSpaceToView(self.viewBg, 15) .topSpaceToView(self.viewBg, 150) .heightIs(10); }//繪製UI - (NSURL*)getSavePath { //獲取沙盒根目錄 NSString *homePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; NSString *filePath = [homePath stringByAppendingPathComponent:recordPath]; return [NSURL URLWithString:filePath]; }//設定儲存路徑 - (NSMutableDictionary*)recordConfigure { NSMutableDictionary *configure = [NSMutableDictionary dictionary]; //設定錄音格式 [configure setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey]; //設定錄音取樣率(Hz) 如:AVSampleRateKey==8000/44100/96000(影響音訊的質量) [configure setObject:@800 forKey:AVSampleRateKey]; //設定通道 [configure setObject:@1 forKey:AVNumberOfChannelsKey]; //設定取樣點位數 ,分別 8、16、24、32 [configure setObject:@8 forKey:AVLinearPCMBitDepthKey]; //是否使用浮點數取樣 [configure setObject:@(YES) forKey:AVLinearPCMIsFloatKey]; //設定錄音質量:中等質量 [configure setObject:@(AVAudioQualityMedium) forKey:AVEncoderAudioQualityKey]; // ... 其他設定 return configure; }//錄音配置 #pragma mark - Action Methods - (void)btnAction:(UIButton*)sender { switch (sender.tag) { case 555: //錄音 [self startRecord]; break; case 556: // 暫停 [self pauaseRecord]; break; case 557: // 播放 [self playRecord]; break; case 558: //停止 [self stopRecord]; break; default: break; } }//按鈕事件 - (void)startRecord { if (![self.avaudioRecorder isRecording]) { [self.avaudioRecorder record]; self.timer.fireDate = [NSDate distantPast]; } }//開始錄音 - (void)pauaseRecord { if ([self.avaudioRecorder isRecording]) { [self.avaudioRecorder pause]; self.timer.fireDate = [NSDate distantFuture]; } }//暫停錄音 - (void)playRecord { if (!self.avaudioPlayer.isPlaying) { [self.avaudioPlayer play]; } }//播放錄音 - (void)stopRecord { //呼叫這個方法後,直接執行錄製完成的代理方法 [self.avaudioRecorder stop]; self.timer.fireDate = [NSDate distantFuture]; [self.progressView setProgress:0 animated:YES]; }//停止錄音 - (void)upDataProgress { //更新測量值 [self.avaudioRecorder updateMeters]; //取得第一個通道的音訊,注意音訊強度範圍時-160到0 float power= [self.avaudioRecorder averagePowerForChannel:0]; CGFloat progress = (1.0/160.0)*(power+160.0); [self.progressView setProgress:progress animated:YES]; }//顯示音波強度 #pragma mark - AVAudioRecorder Delegate - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag { NSLog(@"錄音完成"); }//錄音完成 #pragma mark - AVAudioPlayer Delegate - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { NSLog(@"播放完成"); }//播放器完成 @end

4、附件實現demo

二、播放音訊

1、播放本地音訊檔案(AVAudioPlayer)

AVAudioPlayer使用 中我們介紹了AVFoundation.framework框架下的AVAudioPlayer可播放本地音訊。那麼如果想要播放網路音訊檔案,AVAudioPlayer 只能將音訊檔案下載到本地然後再進行播放操作了。但是這種方式最大的弊端就是必須等到整個音訊播放完成才能播放,而不能使用流式播放,這往往在實際開發中是不切實際的。

那麼在iOS中如何播放網路流媒體呢?

就是使用AudioToolbox框架中的音訊佇列服務Audio Queue Services。音訊佇列服務是純C介面。

.

2、播放網路音訊檔案(Audio Queue Servies - 音訊佇列服務)

Audio Queue Services provides a straightforward, low overhead way to record and play audio in iOS and Mac OS X. It is the recommended technology to use for adding basic recording or playback features to your iOS or Mac OS X application.

在文件中Apple推薦開發者使用AudioQueue來實現app中的播放和錄音功能。並對於支援的格式同樣也做了說明,如下

Audio Queue Services lets you record and play audio in any of the following formats:

1、Linear PCM.

2、Any compressed format supported natively on the Apple platform you are developing for.

3、Any other format for which a user has an installed codec.

它支援PCM資料、iOS/MacOSX平臺支援的壓縮格式(MP3、AAC等)、其他使用者可以自行提供解碼器的音訊資料(對於這一條,我的理解就是把音訊格式自行解碼成PCM資料後再給AudioQueue播放 )。

.

.

< 1 > 音訊佇列

這裡寫圖片描述


一個音訊服務佇列Audio Queue有三部分組成:

三個緩衝器Buffers:每個緩衝器都是一個儲存音訊資料的臨時倉庫。

一個緩衝佇列Buffer Queue:一個包含音訊緩衝器的有序佇列。

一個回撥Callback:一個自定義的佇列回撥函式。

< 2 > 音訊錄製原理

這裡寫圖片描述

  如上圖所示,音訊錄製是聲音通過輸入裝置進入緩衝佇列中,
  首先填充第一個緩衝器;當第一個緩衝器填充滿之後自動填充
  下一個緩衝器,同時會呼叫回撥函式;
  在回撥函式中需要將緩衝器中的音訊資料寫入磁碟,
  同時將緩衝器放回到緩衝佇列中以便重用。

.
.

< 3 > 音訊播放原理

這裡寫圖片描述

 如上圖所示,音訊播放過程恰好跟音訊錄製過程相反。
 音訊播放是將音訊讀取到緩衝器中,一旦一個緩衝器填充
 滿之後就放到緩衝佇列中,然後繼續填充其他緩衝器;
 當開始播放時,則從第一個緩衝器中讀取音訊進行播放;
 一旦播放完之後就會觸發回撥函式,開始播放下一個
 緩衝器中的音訊,同時填充第一個緩衝器放;
 填充滿之後再次放回到緩衝佇列。

< 4 > 現有的第三方實現框架

FreeStreamerAudioStreamer 兩者的start 差不了多少。但是,都不支援cocoapods,這個是最坑的,得手動整合到專案中。

下面是整合 FreeStreamer的流程,給下示意吧。
將FreeStreamer目錄下的檔案拖進新工程,並在Build Settings 的Header Search Paths 增加 /usr/include/libxml2。

//
//  MainViewController.m
//  4.音訊佇列服務
//
//  Created by cherish on 2018/4/16.
//  Copyright © 2018年 Cherish. All rights reserved.
//

#import "MainViewController.h"
#import "FSAudioStream.h"
@interface MainViewController ()


@property (nonatomic,strong) FSAudioStream *audioStream;

@end

@implementation MainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.title = @"音訊佇列服務";

    [self.audioStream play];




}

-(NSURL *)getNetworkUrl
{
    NSString *urlStr = @"http://tdqc-v3.oss-cn-shenzhen.aliyuncs.com/Formal/user/comments/2018/04/08/%E6%9E%97%E4%BF%8A%E6%9D%B0-Always%20Online.mp3";
    NSURL *url = [NSURL URLWithString:urlStr];
    return url;
}


-(FSAudioStream *)audioStream
{
    if (!_audioStream)
    {
        NSURL *url = [self getNetworkUrl];
        //建立FSAudioStream物件
        _audioStream = [[FSAudioStream alloc]initWithUrl:url];

        _audioStream.onFailure = ^(FSAudioStreamError error,NSString *description){
            NSLog(@"播放過程中發生錯誤,錯誤資訊:%@",description);
        };
        _audioStream.onCompletion = ^(){
            NSLog(@"播放完成!");
        };
        [_audioStream setVolume:0.5];//設定聲音
    }
    return _audioStream;
}
@end

.
.

參考文章