<span style="white-space:pre"> </span>语音技术近来可是出遍了风头,从iphone4s的siri,到微信的语音聊天等等,极大地方便了人们的社交生活,也体现了当今移动科技发展的迅猛。当然,作为一位移动开发的从业人员怎能落伍呢!今天我们就来简单的实现一下语音聊天的功能。
<span style="white-space:pre"> </span>这次Demo使用的是Speex对录制的声音进行语音压缩,并且进行ogg的封装。由于本人水平有限,尝试了几次对ogg库的编译都不成功,于是终于在Code4App上找到了一个Demo,把它的speex和封装好的ogg拿了过来,如果大家对ios支持的语音格式不太了解的话可以看一下这篇文章:http://www.csdn.net/article/2012-03-16/313194
首先上图,聊天界面:
源码如下:
聊天界面头文件:
#import <UIKit/UIKit.h> #import "RecorderManager.h" #import "PlayerManager.h" @interface MainViewController : UIViewController<UITableViewDataSource, UITableViewDelegate, RecordingDelegate, PlayingDelegate, UIGestureRecognizerDelegate> - (IBAction)talk:(id)sender; @property (strong, nonatomic) IBOutlet UITableView *vTableView; @property (strong, nonatomic) IBOutlet UIButton *speekBtb; @property (assign) BOOL isSpeak; @property (strong, nonatomic) NSMutableArray *voiceArray; @property (strong, nonatomic) NSString *fileName; @property (assign) BOOL isPlaying; @end
实现:
#import "MainViewController.h" @interface MainViewController() @end @implementation MainViewController @synthesize isSpeak = _isSpeak; @synthesize voiceArray = _voiceArray; @synthesize fileName = _fileName; @synthesize isPlaying = _isPlaying; - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Custom initialization } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view from its nib. self.vTableView.delegate = self; self.voiceArray = [[NSMutableArray alloc] init]; UILongPressGestureRecognizer *guesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handSpeakBtnPressed:)]; guesture.delegate = self; guesture.minimumPressDuration = 0.01f; //录音按钮添加手势操作 [_speekBtb addGestureRecognizer:guesture]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } #pragma mark tableView+++++++++++++++++++++++++++++++++++++++ - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return 1; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; //cell选中属性修改为无 [cell setSelectionStyle:UITableViewCellSelectionStyleNone]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ static NSString *identifid = @"simpleCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifid]; if(!cell){ cell = [[UITableViewCell alloc] init]; } NSMutableDictionary *dic = [self.voiceArray objectAtIndex:indexPath.row]; //加载聊天内容 UIButton *chatView = [dic objectForKey:@"view"]; if([[dic objectForKey:@"from"] isEqualToString:@"SELF"]){ //添加录音时长显示 UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(120, 25, 30, 30)]; int time = [[dic objectForKey:@"time"] intValue]; [label setText:[NSString stringWithFormat:@"%d'", time]]; [cell addSubview:label]; //添加头像 float offset = (chatView.frame.size.height - 48)>0?(chatView.frame.size.height - 48)/2:10; UIImageView *headIcon = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 48 -5, offset, 48, 48)]; [headIcon setImage:[UIImage imageNamed:@"h2.jpg"]]; [cell addSubview:headIcon]; [chatView setTitle:@"点击播放" forState:UIControlStateNormal]; chatView.tag = 100 + indexPath.row; [chatView addTarget:self action:@selector(playVoice:) forControlEvents:UIControlEventTouchUpInside]; }else if([[dic objectForKey:@"from"] isEqualToString:@"OTHER"]){ //系统自动回复部分 float offset = (chatView.frame.size.height - 48)>0?(chatView.frame.size.height - 48)/2:10; UIImageView *headIcon = [[UIImageView alloc] initWithFrame:CGRectMake(5, offset, 48, 48)]; [headIcon setImage:[UIImage imageNamed:@"h1.jpg"]]; [cell addSubview:headIcon]; [chatView setTitle:@"hello world" forState:UIControlStateNormal]; } [cell addSubview:chatView]; return cell; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [_voiceArray count]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ UIView *chatView = [[_voiceArray objectAtIndex:[indexPath row]] objectForKey:@"view"]; return chatView.frame.size.height+30; } //添加手势操作,长按按钮 - (void)handSpeakBtnPressed:(UILongPressGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.state == UIGestureRecognizerStateBegan) { NSLog(@"UIGestureRecognizerStateBegan"); [self.speekBtb setTitle:@"松开结束" forState:UIControlStateNormal]; [self talk:nil]; } if (gestureRecognizer.state == UIGestureRecognizerStateChanged) { NSLog(@"UIGestureRecognizerStateChanged"); } if (gestureRecognizer.state == UIGestureRecognizerStateEnded) { NSLog(@"UIGestureRecognizerStateEnded"); [self.speekBtb setTitle:@"按住说话" forState:UIControlStateNormal]; [self stopRecordVoice]; } } //开始录音 - (IBAction)talk:(id)sender { //若正在播放则立即返回 if(self.isPlaying){ return; } if(!self.isSpeak){ self.isSpeak = YES; [RecorderManager sharedManager].delegate = self; [[RecorderManager sharedManager] startRecording]; } } //结束录音 - (void)stopRecordVoice{ self.isSpeak = NO; [[RecorderManager sharedManager] stopRecording]; } //播放录音 - (void)playVoice:(id)sender{ if(self.isSpeak){ return; } if(!self.isPlaying){ UIButton *btn = (UIButton *)sender; NSInteger index = btn.tag; NSMutableDictionary *dic = [_voiceArray objectAtIndex:(index - 100)]; self.fileName = [dic objectForKey:@"voice"]; [PlayerManager sharedManager].delegate = nil; self.isPlaying = YES; [[PlayerManager sharedManager] playAudioWithFileName:self.fileName delegate:self]; }else{ self.isPlaying = NO; [[PlayerManager sharedManager] stopPlaying]; } } - (void)recordingFinishedWithFileName:(NSString *)filePath time:(NSTimeInterval)interval{ //录音保存的文件地址 self.fileName = filePath; UIButton *view = [self bubbleView:@"点击播放" from:YES]; //时长 NSNumber *num = [[NSNumber alloc] initWithDouble:interval]; NSMutableDictionary *dic = [[NSMutableDictionary alloc]initWithObjectsAndKeys:@"SELF", @"from", view, @"view", self.fileName, @"voice", num, @"time", nil]; [self.voiceArray addObject:dic]; //系统默认回复消息 UIButton *otherView = [self bubbleView:@"你好!" from:NO]; NSMutableDictionary *m_dic = [[NSMutableDictionary alloc]initWithObjectsAndKeys:@"OTHER", @"from", otherView, @"view", @"", @"voice", 0, @"time",nil]; [self.voiceArray addObject:m_dic]; [_vTableView reloadData]; } //超时操作 - (void)recordingTimeout{ self.isSpeak = NO; } //录音机停止采集声音 - (void)recordingStopped{ self.isSpeak = NO; } //录制失败操作 - (void)recordingFailed:(NSString *)failureInfoString{ self.isSpeak = NO; } //播放停止 - (void)playingStoped{ self.isPlaying = NO; } //聊天气泡按钮生成 - (UIButton*)bubbleView:(NSString *)message from:(BOOL)isFromSelf { UIView *returnView = [self assembleMessageAtIndex:message from:isFromSelf]; UIButton *cellView = [[UIButton alloc] initWithFrame:CGRectZero]; cellView.backgroundColor = [UIColor clearColor]; [returnView setBackgroundColor:[UIColor clearColor]]; NSString *picName = [NSString stringWithFormat:@"%@.png", isFromSelf?@"bubble2":@"bubble1"]; UIImage *bubble = [UIImage imageNamed:picName]; UIImageView *bubbleView = [[UIImageView alloc] initWithImage:[bubble stretchableImageWithLeftCapWidth:35 topCapHeight:3]]; if(isFromSelf) { returnView.frame = CGRectMake(9.0f, 20.0f, returnView.frame.size.width, returnView.frame.size.height); bubbleView.frame = CGRectMake(0.0f, 14.0f, returnView.frame.size.width+45.0f, returnView.frame.size.height + 20.0f); cellView.frame = CGRectMake(self.view.frame.size.width - bubbleView.frame.size.width - 60, 20.0f, bubbleView.frame.size.width, bubbleView.frame.size.height + 20.0f); } else { returnView.frame = CGRectMake(88.0f, 20.0f, returnView.frame.size.width, returnView.frame.size.height); bubbleView.frame = CGRectMake(55.0f, 14.0f, returnView.frame.size.width + 45.0f, returnView.frame.size.height + 20.0f); cellView.frame = CGRectMake(50.0f, 20.0f, bubbleView.frame.size.width + 50.0f, bubbleView.frame.size.height + 20.0f); } [cellView setBackgroundImage:bubble forState:UIControlStateNormal]; [cellView setFont:[UIFont systemFontOfSize:13.0f]]; return cellView; } #pragma mark - #pragma mark assemble message at index #define BUBBLEWIDTH 18 #define BUBBLEHEIGHT 18 #define MAX_WIDTH 140 - (UIView *)assembleMessageAtIndex:(NSString *)message from:(BOOL)fromself { NSMutableArray *array = [[NSMutableArray alloc] init]; [self getImageRange:message _array:array]; UIView *returnView = [[UIView alloc] initWithFrame:CGRectZero]; CGFloat upX = 0; CGFloat upY = 0; CGFloat x = 0; CGFloat y = 0; if(array) { for(int i = 0; i < [array count]; i++) { NSString *msg = [array objectAtIndex:i]; for (int index = 0; index < msg.length; index++) { NSString *m_ch = [msg substringWithRange:NSMakeRange(index, 1)]; if(upX >= MAX_WIDTH) { upY = upY + BUBBLEHEIGHT; upX = 0; x = 140; y = upY; } UIFont *font = [UIFont systemFontOfSize:13.0f]; CGSize m_size = [m_ch sizeWithFont:font constrainedToSize:CGSizeMake(140, 40)]; UILabel *m_label = [[UILabel alloc] initWithFrame:CGRectMake(upX, upY, m_size.width, m_size.height)]; [returnView addSubview:m_label]; m_label.font = font; m_label.text = m_ch; m_label.backgroundColor = [UIColor clearColor]; upX = upX+m_size.width; if(x < 140) { x = upX; } } } } returnView.frame = CGRectMake(15.0f, 1.0f, x, y); return returnView; } - (void) getImageRange:(NSString *)message _array:(NSMutableArray *)array { if(message != nil) { [array addObject: message]; } } - (void)dealloc{ [self removeObserver:self forKeyPath:@"isSpeak"]; self.fileName = nil; } @end
好了一个简单的语音聊天程序就好了,是不是很简单,其中最重要的就是 RecorderManager以及PlayerManager两个类了,一个负责录音,一个负责播放,这两个类我准备放到下一篇博客中讲解一下,大家不妨通过去下载我的Demo自己动手试一试,下载地址:http://download.csdn.net/detail/shenjie12345678/8021263。
注意点:
1.在将Demo程序中的Classes以及Libs文件加入到工程中去的时候,请将echo_diagnostic.m文件以及以test开头的文件删掉,否则工程会报错。
2.在工程中将Preprocessor Macros选项中的内容删除。