前言
由于业务需要,在开发过程中需要使用到语音方面的知识,并且在和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
该描述要详细,否则会因模糊的语句导致被拒。
- (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)录制时钟表滚动效果
在录制时,刷新时间的同时,圆盘上有红色线按时钟表顺时针滚动,如图
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