1.界面布局,以及相關功能
點擊中間開始錄音,點擊左上角播放或暫停播放,點擊右上角移除文件
2.定義相關屬性
#import "SendVoiceController.h"
#import#import "RecordVoiceView.h"
#import "lame.h"
#import "PlayVoiceView.h"
#define cafFilePathName @"myRecordForCaf.caf"
#define mp3FilePathName @"myRecordForMp3.mp3"
@interface SendVoiceController ()//錄音存儲路徑
@property (nonatomic, strong)NSURL *tmpFile;
//錄音
@property (nonatomic, strong)AVAudioRecorder *recorder;
//播放
@property (nonatomic, strong)AVAudioPlayer *player;
//錄音動畫
@property(nonatomic,strong)RecordVoiceView *recordview;
@property(strong,nonatomic)PlayVoiceView *playview;
//錄音計時器
@property(nonatomic,strong)NSTimer *recordTimer;
//錄音秒數(shù)
@property(strong,nonatomic)NSTimer *timer;
@property(nonatomic,assign)int index;
@property(nonatomic,assign)int duration;
@end
@implementation SendVoiceController
3.創(chuàng)建audiosession
-(void)setAVRecorder{
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
urlStr=[urlStr stringByAppendingPathComponent:cafFilePathName];
self.tmpFile = [NSURL fileURLWithPath:urlStr];
//設置后臺播放
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *sessionError;
//AVAudioSessionCategoryPlayAndRecord,這個確保了既能錄音也能播放
[session setCategory:AVAudioSessionCategoryPlayAndRecord error:&sessionError];
//判斷后臺有沒有播放
if (session == nil) {
NSLog(@"Error creating sessing:%@", [sessionError description]);
} else {
[session setActive:YES error:nil];
}
}
4.中間圖片手勢代理方法
//長按事件的實現(xiàn)方法
- (void) handleTableviewCellLongPressed:(UILongPressGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state ==
UIGestureRecognizerStateBegan) {
NSLog(@"UIGestureRecognizerStateBegan");
[self recorderPressing];
}
if (gestureRecognizer.state ==
UIGestureRecognizerStateChanged) {
NSLog(@"UIGestureRecognizerStateChanged");
}
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
NSLog(@"UIGestureRecognizerStateEnded");
[self recorderEnd];
}
}
5.創(chuàng)建AVAudioRecorder并開始錄音
-(void)recorderPressing{
NSDictionary *settingdic = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:8000],? ? ? ? ? ? ? ? ? AVSampleRateKey, // 電話所用采樣率
[NSNumber numberWithInt:kAudioFormatLinearPCM],? ? AVFormatIDKey,
[NSNumber numberWithInt:2],? ? ? ? ? ? ? ? ? ? ? AVNumberOfChannelsKey,
[NSNumber numberWithInt:16],? ? ? ? ? ? ? ? ? ? ? AVLinearPCMBitDepthKey,
[NSNumber numberWithInt:AVAudioQualityMin],? ? ? AVEncoderAudioQualityKey,
nil];
//開始錄音,將所獲取到得錄音存到文件里
//開始錄音,將所獲取到得錄音存到文件里
self.recorder = [[AVAudioRecorder alloc] initWithURL:_tmpFile settings:settingdic error:nil];
self.recorder.meteringEnabled = YES;
self.recorder.delegate=self;
//準備記錄錄音
[self.recorder prepareToRecord];
//啟動或者恢復記錄的錄音文件
[self.recorder record];
self.player = nil;
//以下是我仿照衛(wèi)星發(fā)語音的動畫效果
self.recordview.hidden=NO;
self.recordTimer=[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(recordTimerChange) userInfo:nil repeats:YES];
}
6.中間圖片長按取消的同時關閉錄音
-(void)recorderEnd{
self.duration=self.index;
self.index=0;
//停止錄音
if (self.recordTimer) {
[self.recordTimer invalidate];
self.recordTimer=nil;
}
[self.recorder stop];
self.recordview.hidden=YES;
self.recorder = nil;
self.playBtn.hidden=NO;
self.removeBtn.hidden=NO;
self.curveLineImage.hidden=NO;
NSError *playError;
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
urlStr=[urlStr stringByAppendingPathComponent:mp3FilePathName];
NSURL *url = [NSURL fileURLWithPath:urlStr];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&playError];
//當播放錄音為空, 打印錯誤信息
if (self.player == nil) {
NSLog(@"Error crenting player: %@", [playError description]);
}
self.player.delegate = self;
//? ? [self.player play];
}
7.與其同時觸發(fā)avaudiorecord代理方法
這里面的主要作用是把錄音的caf文件轉(zhuǎn)成mp3文件,用于上傳到服務器,其中的方法是來自于Libmo3lame庫
#pragma mark---錄音代理
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *cafFilePath =[urlStr stringByAppendingPathComponent:cafFilePathName];? ? //caf文件路徑
NSString *mp3FilePath = [urlStr stringByAppendingPathComponent:mp3FilePathName]; ;//存儲mp3文件的路徑
NSFileManager * fileManager=[ NSFileManager defaultManager ];
if ([fileManager fileExistsAtPath:cafFilePath]){
float msize= [[fileManager attributesOfItemAtPath:cafFilePath error:nil] fileSize]/(1024.0*1024.0);
NSLog(@"caf文件大小%lf",msize);
}else{
float msize= [[fileManager attributesOfItemAtPath:mp3FilePathName error:nil] fileSize]/(1024.0*1024.0);
NSLog(@"mp3文件大小%lf",msize);
}
if ([fileManager removeItemAtPath :mp3FilePath error : nil ])
{
NSLog ( @" 刪除 " );
}
@try {
int read, write;
FILE *pcm = fopen ([cafFilePath cStringUsingEncoding : 1 ], "rb" );? //source 被 轉(zhuǎn)換的音頻文件位置
if (pcm == NULL )
{
NSLog ( @"file not found" );
}
else
{
fseek (pcm, 4 * 1024 , SEEK_CUR );? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //skip file header
FILE *mp3 = fopen ([mp3FilePath 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];
lame_t lame = lame_init ();
lame_set_num_channels (lame, 2 ); // 設置 1 為單通道,默認為 2 雙通道
lame_set_in_samplerate (lame,? 8000.0 ); //11025.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 音 質(zhì) */
lame_init_params (lame);
do {
read = fread (pcm_buffer, 2 * sizeof ( short int ), 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);
return? ;
}
return? ;
}
@catch (NSException *exception) {
NSLog ( @"%@" ,[exception description ]);
return? ;
}
@finally {
NSLog ( @" 執(zhí)行完成 " );
}
}
8.左上角按鈕事件
-(IBAction)playerClick:(id)sender{
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString * path=[urlStr stringByAppendingPathComponent:cafFilePathName];
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:path]){
float msize= [[manager attributesOfItemAtPath:path error:nil] fileSize]/(1024.0*1024.0);
NSLog(@"文件大小%lf",msize);
}
else{
_player=nil;
}
//判斷是否正在播放,如果正在播放
if ([self.player isPlaying]) {
//暫停播放
[_player pause];
[self.playBtn? setImage:[UIImage imageNamed:@"voice_play"] forState:UIControlStateNormal];
//按鈕顯示為播放
} else {
//開始播放
[_player play];
[self.playBtn? setImage:[UIImage imageNamed:@"voice_pause"] forState:UIControlStateNormal];
}
}
9.右上角按鈕事件
-(IBAction)removeClick:(id)sender{
NSIndexPath *indexpath=[NSIndexPath indexPathForItem:6 inSection:0];
self.playBtn.hidden=YES;
self.removeBtn.hidden=YES;
self.curveLineImage.hidden=YES;
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *cafFilePath =[urlStr stringByAppendingPathComponent:cafFilePathName];? ? //caf文件路徑
NSString *mp3FilePath = [urlStr stringByAppendingPathComponent:mp3FilePathName]; ;//存儲mp3文件的路徑
NSFileManager * fileManager=[ NSFileManager defaultManager ];
if ([fileManager removeItemAtPath :mp3FilePath error : nil ])
{
NSLog ( @" 刪除 mp3" );
}
if ([fileManager removeItemAtPath :cafFilePath error : nil ])
{
NSLog ( @" 刪除 caf" );
}
}
10.上傳到服務器
主要的邏輯是把MP3文件轉(zhuǎn)成nsdata,然后通過base64string 轉(zhuǎn)成字符串傳給后臺
NSString *url=[[HJInterfaceManager sharedInstance]addRichInfo];
NSMutableDictionary *mdic=[[NSMutableDictionary alloc]initWithCapacity:0];
NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *mp3FilePath = [urlStr stringByAppendingPathComponent:mp3FilePathName]; ;//
//? ? NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"png"];
NSData *filedata=[NSData dataWithContentsOfFile:mp3FilePath];
SelfDataModel *selfModel=[SelfDataModel returnModelBySelectFMDB];
//? ? ? ? * @param familyId
//? ? ? ? * @param userId
//? ? ? ? * @param content
//? ? ? ? * @param richContent
//? ? ? ? * @param type
//? ? ? ? * @param sendTime
NSDate *date=[NSDate date];
NSDateFormatter *form=[[NSDateFormatter alloc]init];
[form setDateFormat:@"YYYY-MM-dd HH:mm:ss"];
NSString *datestr=[form stringFromDate:date ];
if (!self.isMyself) {
[mdic setObject:self.family.idNum forKey:@"familyId"];
}
[mdic setObject:selfModel.idNum forKey:@"userId"];
[mdic setObject:@"" forKey:@"content"];
[mdic setObject:[NSString stringWithFormat:@"%d",self.duration] forKey:@"duration"];
[mdic setObject:[filedata base64Encoding] forKey:@"richContents"];
[mdic setObject:@"SOUND" forKey:@"type"];
[mdic setObject:datestr forKey:@"sendTime"];
[HJHttpManager PostRequestWithUrl:url param:mdic finish:^(NSData *data) {
NSDictionary *dic=(NSDictionary *)data;
if ([dic[@"status"] isEqualToString:@"S"]) {
[MBProgressHUD showSuccess:@"發(fā)送成功"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
}else{
[MBProgressHUD showError:dic[@"message"]];
}
} failed:^(NSError *error) {
NSLog(@"請求失敗");
}];
小帖士:
以下是監(jiān)聽聲音分貝的方法
#pragma mark----監(jiān)聽聲音分貝
/* 該方法確實會隨環(huán)境音量變化而變化,但具體分貝值是否準確暫時沒有研究 */
- (void)recordTimerChange {
//? ? [self.recorder updateMeters];//刷新音量數(shù)據(jù)
//? ? double lowPassResults = pow(10, (0.05 * [self.recorder peakPowerForChannel:0]));
//? ? NSLog(@"大小:%lf",lowPassResults);
self.recordview.timeLab.text=[NSString stringWithFormat:@"%d s",self.index];
self.index++;
[self.recorder updateMeters];
float? level;? ? ? ? ? ? ? ? // The linear 0.0 .. 1.0 value we need.
float? minDecibels = -80.0f; // Or use -60dB, which I measured in a silent room.
float? decibels? ? = [self.recorder averagePowerForChannel:0];
if (decibels < minDecibels)
{
level = 0.0f;
}
else if (decibels >= 0.0f)
{
level = 1.0f;
}
else
{
float? root? ? ? ? ? ? = 2.0f;
float? minAmp? ? ? ? ? = powf(10.0f, 0.05f * minDecibels);
float? inverseAmpRange = 1.0f / (1.0f - minAmp);
float? amp? ? ? ? ? ? = powf(10.0f, 0.05f * decibels);
float? adjAmp? ? ? ? ? = (amp - minAmp) * inverseAmpRange;
level = powf(adjAmp, 1.0f / root);
}
float ben=level*120;
if (ben<20) {
[self.recordview.imageview setImage:[UIImage imageNamed:@"order_voice_1"]];
}
else if (ben<35) {
[self.recordview.imageview setImage:[UIImage imageNamed:@"order_voice_2"]];
}
else{
[self.recordview.imageview setImage:[UIImage imageNamed:@"order_voice_3"]];
}
NSLog(@"大小%lf",ben);
}