iOS语音录制、转码及播放

2018-07-17  本文已影响355人  njim3

前言

由于业务需要,在开发过程中需要使用到语音方面的知识,并且在和Android同步开发时,需要用到转码。因此,语音的录制、播放及转码在APP中需要实现。

准备工作

1. 界面设计

由于本例是语音demo,因此不要求界面多么美观,功能齐全即可,使用autolayout构建的界面如下图。 界面设计图

(1)NavVC作为Initial View,由导航控制整个页面跳转;
(2)根视图中包括了录制和播放功能的实现;
(3)Audios页面中对所录制的音频文件进行查看和删除;
(4)PlayAudio页面是对音频文件进行播放,播放时带有动画效果。

2. 类库

Demo中使用的类库有AmrVoiceConverter和UIView+FrameEx类库,前者是wav和amr互转的类库,后者是UIView的分类类库,旨在更好操作UIView的frame。另外,使用AVFoundation类库进行音频的录制和播放。

说明:wav是iOS上录制生成的文件格式,其体积较大,录制10秒生成的文件在100k左右,而amr格式是Android平台上使用的音频格式,其体积较小。因此为保证和Android平台的一致,在iOS平台上需要将wav转化为amr文件,这也同样节省了流量消耗。

详细实现

在这里不针对某一个页面进行具体实现,对其中的重要功能进行说明。如需要查看所有的实现,请移步文章底部。

1. 录制功能的实现

录制功能使用AVFoundation类库中的API,

(1)APP请求许可

首先需要请求APP的许可,使用语音设备。前提是在Info.plist中配置了键值对。
Privacy - Microphone Usage Description
该描述要详细,否则会因模糊的语句导致被拒。

请求APP的许可,调用后会在APP中弹出alert。 1.png 其实现代码如下
- (void)requestRecordingPermission: (void(^) (BOOL))callback {
    AVAudioSession* audioSession = [AVAudioSession sharedInstance];
    
    if ([audioSession respondsToSelector: @selector(requestRecordPermission:)]) {
        [audioSession performSelector: @selector(requestRecordPermission:)
                           withObject: ^(BOOL granted) {
                               callback(granted);
                           }];
    }
}

(2)录音

主要分为设置AVAudioSession、设置文件路径和设置AVAudioRecorder。
a. AVAudioSession的设置

    AVAudioSession* audioSession = [AVAudioSession sharedInstance];
    NSError* error;
    
    [audioSession setCategory: AVAudioSessionCategoryPlayAndRecord
                        error: &error];
    
    if (audioSession == nil) {
        // 弹出Alert
        return ;
    }
    
    [audioSession setActive: YES
                      error: nil];

b. 文件路径的设置
语音文件需要单独创建一个文件夹用于存储,将其存入DOCUMENT_PATH下面的audios文件夹中亦可。

#define DOCUMENT_PATH                   [NSSearchPathForDirectoriesInDomains(   \
                                            NSDocumentDirectory, NSUserDomainMask, \
                                            YES) objectAtIndex: 0]
#define AUDIO_FOLDER_NAME               @"audios"
#define AUDIO_FOLDER_PATH               [DOCUMENT_PATH stringByAppendingPathComponent:  \
                                            AUDIO_FOLDER_NAME]

c.设置AVAudioRecorder
AVAudioRecorder中录音类,需要设置录音的settings,如采样频率、音频格式、采样位数、音频通道和录音质量。

NSDictionary* recordSettings = @{
             AVSampleRateKey: @8000.0f,                         // 采样率
             AVFormatIDKey: @(kAudioFormatLinearPCM),           // 音频格式
             AVLinearPCMBitDepthKey: @16,                       // 采样位数
             AVNumberOfChannelsKey: @1,                         // 音频通道
             AVEncoderAudioQualityKey: @(AVAudioQualityHigh)    // 录音质量
             };
    
    _avAudioRecorder = [[AVAudioRecorder alloc] initWithURL: _curWavFileUrl
                                                   settings: recordSettings
                                                      error: nil];
    
    if (!_avAudioRecorder) {
        // 弹出Alert
        return ;
    }
    
    _avAudioRecorder.meteringEnabled = YES;
    [_avAudioRecorder prepareToRecord];
    [_avAudioRecorder record];

2. 播放功能的实现

播放功能相对较简单,只需要设置AVAudioPlayer即可。

- (IBAction)playBBIAction:(UIBarButtonItem *)sender {
    if (_avAudioPlayer && _avAudioPlayer.isPlaying) {
        [_avAudioPlayer stop];
        
        return ;
    }
    
    if ([_avAudioRecorder isRecording])
        return ;
    
    if ([[FileManager manager] isFileExistsAtPath: _curWavFilePath]) {
        _avAudioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:
                          [NSURL fileURLWithPath: _curWavFilePath]
                                                                error: nil];
        [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback
                                               error: nil];
        [_avAudioPlayer play];
    }
}

3. 转码功能的实现

添加类库时,无论以cocoapods添加或者直接拖拽至工程,若使用SVN或git文件,需要保证其ignore文件中对.a文件的忽略,否则在上传或者下载时忽略掉.a文件,造成编译时报丢失链接库的错误。
转码使用VoiceConverter类库,其提供了API如下

@interface VoiceConverter : NSObject

+ (BOOL)amrToWav:(NSString*)_amrPath wavSavePath:(NSString*)_savePath;
+ (BOOL)wavToAmr:(NSString*)_wavPath amrSavePath:(NSString*)_savePath;
+ (NSData *)convertToRawAmrDataWithData:(NSData *)data;
+ (NSData *)amrToWavWithAmrData:(NSData *)amrData;
+ (NSData *)wavToAmrWithWavData:(NSData *)wavData;

@end

在实际的业务需要中,使用到的是wav->amr,再由amr的NSData转为wav的NSData,即使用API中的第二个和第四个方法。

4. 界面效果的实现

(1)录制时钟表滚动效果

在录制时,刷新时间的同时,圆盘上有红色线按时钟表顺时针滚动,如图

圆盘滚动效果 使用NSTimer来控制:时间label使用NSTimer来进行刷新,每隔1s刷新一次;滚动效果的时间间隔若设置为1s,1s画的区域为1/60,这样得出的效果不是连续的。由于人的视觉暂留时间是0.1s,因此设置为0.1s或者小于0.1s间隔,超出人眼辨别范围,便是连续的效果了。
a. 滚动layer
滚动的圆圈线是添加一个CAShapeLayer至UIView上,使用Beizier曲线画出来。
- (CAShapeLayer*)shapeLayer {
    if (!_shapeLayer) {
        _shapeLayer = [[CAShapeLayer alloc] init];
        
        _shapeLayer.fillColor = [UIColor clearColor].CGColor;
        _shapeLayer.lineWidth = 3.0f;
        _shapeLayer.strokeColor = [UIColor orangeColor].CGColor;
        
        UIBezierPath* path = [[UIBezierPath alloc] init];
        
        [path moveToPoint: CGPointMake(self.speakerView.width / 2, 0)];
        [path addArcWithCenter: CGPointMake(self.speakerView.width / 2,
                                            self.speakerView.height / 2)
                        radius: self.speakerView.width / 2
                    startAngle: - M_PI / 2
                      endAngle: 3 * M_PI / 2
                     clockwise: YES];
        
        _shapeLayer.path = path.CGPath;
        
        _shapeLayer.strokeStart = 0;
        _shapeLayer.strokeEnd = 0;
    }
    
    return _shapeLayer;
}

需要注意旋转的区域是从-π/2 -> 3π/2。
b.NSTimer的控制

    _strokeTimer = [NSTimer scheduledTimerWithTimeInterval: 0.05f
                                                    target: self
                                                  selector: @selector(strokeCircle)
                                                  userInfo: nil
                                                   repeats: YES];
    [[NSRunLoop currentRunLoop] addTimer: _strokeTimer
                                 forMode: NSRunLoopCommonModes];

- (void)strokeCircle {
    self.shapeLayer.strokeEnd += (0.05f / 60);
}

设置的间隔为0.05s

(2)播放效果

播放效果为PlayAudio页面中,点击播放后中间三个横线依次闪现的效果,类似于微信的语音播放效果。
其原理是帧动画。三张图片分别为一横线、二横线和三横线,大小一致。使用UIImageView进行设置。

- (void)setPlayVoiceIVStyle {
    self.playVoiceIV.animationImages = @[UIImageNamed(BG_PLAYVOICE_1),
                                         UIImageNamed(BG_PLAYVOICE_2),
                                         UIImageNamed(BG_PLAYVOICE_3)];
    self.playVoiceIV.animationDuration = 0.8f;
    self.playVoiceIV.animationRepeatCount = 0;
}

在播放和暂停时调用startAnimating和stopAnimating方法进行开始和暂停动画效果。

结束语

文中介绍的较为笼统,在实际的实现过程中更复杂一些,要考虑到用户的操作,如录制过程中退到后台,录制过程中接入电话等情况。另外,语音和文字的同时输入需要界面之间的转换,也要添加相应的逻辑判断。根据实际的需求进行实现。
代码托管至github中。

https://github.com/njim3/AudioDemo
上一篇下一篇

猜你喜欢

热点阅读