目录
长风破浪会有时,直挂云帆济沧海。
项目中遇到过需要录制一段声音,然后上传到7牛(格式需要是mp3格式)。
格外注意一下:AVAudioRecorder的配置参数和lame_set配置参数。
配置不正确会导致转换的mp3文件长度不正确。
前言
音频-文件格式(后缀)
1. WAV:
音质最好(对应PCM编码)
适用:多媒体开发,保存音乐和音效素材。
2. MP3:
音质好,压缩比比较高,被广泛支持(iOS、Android都支持)。
适用:比较高要求的音乐欣赏。
3. caf:
适用: iOS中所有的编码格式。
音频-编码格式
1. PCM
脉冲编码调制,是一种非压缩音频数字化技术(未压缩的原音重现)。
数字模式下,音频的初始化信号是PCM。
2. MP3
3. AAC
advanced audio coding高级音频编码,被设计来取代MPC格式。
4. HE-AAC
HE-AAC是AAC的一个超集,专门为低比特率所优化的一种音频编码格式。
5. AMR
Adaptive Multi-Rate,另一个专门为低比特率所优化的一种音频编码格式。
2.6 ALAC
Apple Lossless,无损压缩的音频编码方式。
2.7 IMA4
在16-bit音频文件下按照4:1的压缩比来进行压缩的格式。
- 录音工具类
FQVideoRecordTool.h
@interface FQVideoRecordTool : NSObject
// 必须设置(用于没权限时弹窗)
@property (nonatomic,weak) UIViewController *currentVC;
@property (nonatomic,copy) NSString *videoRecordPath;
// 最大录制/播放时间
@property (nonatomic,assign) int maxVideoRecordTime;
// 最小录制/播放时间
@property (nonatomic,assign) int minVideoRecordTime;
// 录制时间
@property (nonatomic,assign) int videoRecordTime;
+(instancetype)fq_shared;
// 录音
-(void)fq_recordVideo;
// 暂停录音
-(void)fq_pauseRecord;
// 停止录音
-(void)fq_stopRecord;
// 取消/清除 录音
-(void)fq_cancelOrCleanRecord;
// 播放录音
-(void)fq_playVideo;
// 暂停播放录音
-(void)fq_pauseVideo;
// 继续播放录音
-(void)fq_replayVideo;
@end
FQVideoRecordTool.m
#import "FQVideoRecordTool.h"
@interface FQVideoRecordTool()
@property (nonatomic,strong) AVAudioRecorder *videoRecorder;
@property (nonatomic,strong) AVAudioPlayer *videoplayer;
@end
@implementation FQVideoRecordTool
static FQVideoRecordTool *_shared;
+(instancetype)fq_shared{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(!_shared){
_shared=[FQVideoRecordTool new];
}
});
return _shared;
}
#pragma mark 录音
// 录音
-(void)fq_recordVideo{
AVAudioSessionRecordPermission permission = AVAudioSession.sharedInstance.recordPermission;
//在此添加新的判定 undetermined,否则新安装后的第一次询问会出错。新安装后的第一次询问为 undetermined,而非 denied。
if (permission == AVAudioSessionRecordPermissionDenied || permission == AVAudioSessionRecordPermissionUndetermined) {
[AVAudioSession.sharedInstance requestRecordPermission:^(BOOL granted) {
if (!granted) {
UIAlertController *ac = [UIAlertController alertControllerWithTitle:@"无法访问麦克风" message:@"开启麦克风权限才能发送语音消息" preferredStyle:UIAlertControllerStyleAlert];
[ac addAction:[UIAlertAction actionWithTitle:@"以后再说" style:UIAlertActionStyleCancel handler:nil]];
[ac addAction:[UIAlertAction actionWithTitle:@"去开启" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
UIApplication *app = [UIApplication sharedApplication];
NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([app canOpenURL:settingsURL]) {
[app openURL:settingsURL];
}
}]];
[self.currentVC presentViewController:ac animated:YES completion:nil];
}
}];
return;
}
if(permission == AVAudioSessionRecordPermissionGranted){
[self fq_startRecord];
}
}
-(void)fq_startRecord{
if([[NSFileManager defaultManager] fileExistsAtPath:self.videoRecordPath]){
// [self.videoRecorder record];
[self fq_cancelOrCleanRecord];
}
//
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
[session setActive:YES error:&error];
if(error){
[SQGToast showWithText:@"录制出错"];
NSLog(@"%@",error.userInfo);
}else{
[self.videoRecorder prepareToRecord];
[self.videoRecorder record];
[self.videoRecorder updateMeters];
}
}
// 暂停录音
-(void)fq_pauseRecord{ //
if([self.videoRecorder isRecording]){
[self.videoRecorder pause];
}
}
// 停止录音
-(void)fq_stopRecord{ //
if([self.videoRecorder isRecording]){
[self.videoRecorder stop];
}
}
// 取消/清除 录音
-(void)fq_cancelOrCleanRecord{
//
if([self.videoRecorder isRecording]){
[self.videoRecorder stop];
}
self.videoRecorder=nil;
//
if([[NSFileManager defaultManager] fileExistsAtPath:self.videoRecordPath]){
[[NSFileManager defaultManager] removeItemAtPath:self.videoRecordPath error:nil];
}
//
self.videoplayer=nil;
}
#pragma mark 播放录音
// 播放录音
-(void)fq_playVideo{
if([self.videoRecorder isRecording]){
[self fq_stopRecord];
}
if ([self.videoplayer isPlaying])return;
//
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[self.videoplayer play];
}
// 暂停播放录音
-(void)fq_pauseVideo{
if ([self.videoplayer isPlaying]){
[self.videoplayer pause];
}
}
// 继续播放录音
-(void)fq_replayVideo{
if ([self.videoplayer isPlaying]){
return;
}
[self.videoplayer play];
}
// 没权限
-(BOOL)fq_NoPermission{
return AVAudioSession.sharedInstance.recordPermission == AVAudioSessionRecordPermissionDenied;
}
#pragma mark GET
-(AVAudioRecorder *)videoRecorder{
if(!_videoRecorder){
NSDictionary *recordSetting=[[NSDictionary alloc] initWithObjectsAndKeys:
// 采样率 8000/11025/22050/44100/96000(影响音频的质量)
[NSNumber numberWithFloat: 8000.0],AVSampleRateKey,
// 音频格式()
[NSNumber numberWithInt: kAudioFormatLinearPCM],AVFormatIDKey,
// 采样位数 8、16、24、32 默认为16
[NSNumber numberWithInt:16],AVLinearPCMBitDepthKey,
// 音频通道数 1 或 2
[NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
//
[NSNumber numberWithInt: 16],AVLinearPCMBitDepthKey,
// 录音质量
[NSNumber numberWithInt:AVAudioQualityHigh],AVEncoderAudioQualityKey,
nil];
NSError *error;
_videoRecorder=[[AVAudioRecorder alloc]initWithURL:[NSURL URLWithString:self.videoRecordPath]settings:recordSetting error:&error];
[_videoRecorder setMeteringEnabled:true];
}
return _videoRecorder;
}
-(NSString *)videoRecordPath{
if(!_videoRecordPath){
_videoRecordPath=[NSString stringWithFormat:@"%@/VideoRecord.caf",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]];
}
return _videoRecordPath;
}
-(AVAudioPlayer *)videoplayer{
if(!_videoplayer){
_videoplayer=[[AVAudioPlayer alloc]initWithContentsOfURL:[NSURL URLWithString:self.videoRecordPath] error:nil];
}
return _videoplayer;
}
-(int)maxVideoRecordTime{
if(!_maxVideoRecordTime){
_maxVideoRecordTime=20;
}
return _maxVideoRecordTime;
}
-(int)minVideoRecordTime{
if(!_minVideoRecordTime){
_minVideoRecordTime=5;
}
return _minVideoRecordTime;
}
-(void)dealloc{
//
[self fq_cancelOrCleanRecord];
}
@end
- 打包lame静态库(用来将caf转换为mp3格式)
1. 下载lame
2. 下载build脚本
- 创建一个新文件夹(存放1和2下载的内容)
- 编译(在终端输入./build-lame.sh)
- 将fat-lame中编译好的.a和.h文件拖入项目(这里要注意:勾选Create folder refrences,和平时的Create groups不太一样)
- lame使用工具类
FQLibMp3Tool.h
@interface FQLibMp3Tool : NSObject
+(NSString *)FQTransferToMP3:(NSString *)sourcePath isDeleteSourchFile:(BOOL)isDelete;
@end
FQLibMp3Tool.m
#import "FQLibMp3Tool.h"
#import "lame.h"
@implementation FQLibMp3Tool
+(NSString *)FQTransferToMP3: (NSString *)sourcePath isDeleteSourchFile:(BOOL)isDelete {
// 输入路径
NSString *inPath = sourcePath;
// 判断输入路径是否存在
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:sourcePath]) {
NSLog(@"文件不存在");
}
// 输出路径
NSString *outPath = [[sourcePath stringByDeletingPathExtension] stringByAppendingString:@".mp3"];
@try {
int read, write;
FILE *pcm = fopen([inPath cStringUsingEncoding:1], "rb"); //source 被转换的音频文件位置
fseek(pcm, 4*1024, SEEK_CUR); //skip file header
FILE *mp3 = fopen([outPath cStringUsingEncoding:1], "wb+"); //output 输出生成的Mp3文件位置
const int PCM_SIZE = 8192;
const int MP3_SIZE = 8192;
short int pcm_buffer[PCM_SIZE*2];
unsigned char mp3_buffer[MP3_SIZE];
// 配置不正确可能造成mp3转换时间长短出现错误。
lame_t lame = lame_init();
lame_set_num_channels(lame,2); // 设置1为单通道,默认为2双通道
lame_set_in_samplerate(lame, 8000.0);
// lame_set_VBR(lame, vbr_default);
lame_set_brate(lame, 16);
lame_set_mode(lame, 3);
lame_set_quality(lame, 2); /* 2=high 5 = medium 7=low 音质*/
lame_init_params(lame);
do {
size_t size = (size_t)(2 * sizeof(short int));
read = fread(pcm_buffer, size, PCM_SIZE, pcm);
if (read == 0)
write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
else
write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
fwrite(mp3_buffer, write, 1, mp3);
} while (read != 0);
lame_close(lame);
fclose(mp3);
fclose(pcm);
}
@catch (NSException *exception) {
NSLog(@"%@",[exception description]);
}
@finally {
NSLog(@"MP3生成成功:");
if (isDelete) {
NSError *error;
[fm removeItemAtPath:sourcePath error:&error];
if (error == nil) {
NSLog(@"删除源文件成功");
}
}
return outPath;
}
}
@end
- 转换为mp3格式
// var/..../test.mp3
NSString *mp3Path=[FQLibMp3Tool FQTransferToMP3:@"var/..../test.caf" isDeleteSourchFile:false];