美文网首页
iOS语音录制、转码及播放

iOS语音录制、转码及播放

作者: njim3 | 来源:发表于2018-07-17 15:02 被阅读355次

    前言

    由于业务需要,在开发过程中需要使用到语音方面的知识,并且在和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
    

    相关文章

      网友评论

          本文标题:iOS语音录制、转码及播放

          本文链接:https://www.haomeiwen.com/subject/amnwpftx.html