- <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 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选项中的内容删除。