中间参考了别人的Demo,下载地址不记得了。

因为项目需要做一个语音对讲功能,其实说白了就是类似QQ的语音通话,但是资料少之又少,研究了好久,才跟同事弄出一个粗略的版本。我记性不好,所以来记录一下,也希望能够帮助其他人。

本来以为是要做语音对讲,类似微信的发送语音,我觉得这个还挺简单的,就是发送一个语音的文件,所以一开始用的是AVAudioPlayer,因为这个东西只能播放本地音频,而且非常简单。可是都快做好了,头头才说明白要的是语音通话。(小公司,别说文档了,连接口文档都没有)

后来找到AudioQueue,找了好多demo和资料,都没有直接播放从服务器端接收到的数据的例子,后来没办法,只能自己想办法咯。不过大致过程是一致的。

首先肯定是设置创建录音的音频队列,以及缓冲区,还有播放的队列和播放缓冲区,因为我们是要一起打开,所以一起创建,开始录音,并播放声音。

后面会上传demo,开始对讲的方法如下:

//开始对讲
- (IBAction)startIntercom:(id)sender {
//让udpSocket 开始接收数据
[self.udpSocket beginReceiving:nil];
//先把接收数组清空
if (receiveData) {
receiveData = nil;
}
receiveData = [[NSMutableArray alloc] init]; if (_recordAmrCode == nil) {
_recordAmrCode = [[RecordAmrCode alloc] init];
}
//设置录音的参数
[self setupAudioFormat:kAudioFormatLinearPCM SampleRate:kDefaultSampleRate];
_audioFormat.mSampleRate = kDefaultSampleRate;
//创建一个录制音频队列
AudioQueueNewInput (&(_audioFormat),GenericInputCallback,(__bridge void *)self,NULL,NULL,0,&_inputQueue);
//创建一个输出队列
AudioQueueNewOutput(&_audioFormat, GenericOutputCallback, (__bridge void *) self, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0,&_outputQueue);
//设置话筒属性等
[self initSession]; NSError *error = nil;
//设置audioSession格式 录音播放模式
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&error]; UInt32 audioRouteOverride = kAudioSessionOverrideAudioRoute_Speaker; //设置成话筒模式
AudioSessionSetProperty (kAudioSessionProperty_OverrideAudioRoute,
sizeof (audioRouteOverride),
&audioRouteOverride); //创建录制音频队列缓冲区
for (int i = 0; i < kNumberAudioQueueBuffers; i++) {
AudioQueueAllocateBuffer (_inputQueue,kDefaultInputBufferSize,&_inputBuffers[i]); AudioQueueEnqueueBuffer (_inputQueue,(_inputBuffers[i]),0,NULL);
} //创建并分配缓冲区空间 4个缓冲区
for (int i = 0; i<kNumberAudioQueueBuffers; ++i)
{
AudioQueueAllocateBuffer(_outputQueue, kDefaultOutputBufferSize, &_outputBuffers[i]);
}
for (int i=0; i < kNumberAudioQueueBuffers; ++i) {
makeSilent(_outputBuffers[i]); //改变数据
// 给输出队列完成配置
AudioQueueEnqueueBuffer(_outputQueue,_outputBuffers[i],0,NULL);
} Float32 gain = 1.0; // 1
// Optionally, allow user to override gain setting here 设置音量
AudioQueueSetParameter (_outputQueue,kAudioQueueParam_Volume,gain); //开启录制队列
AudioQueueStart(self.inputQueue, NULL);
//开启播放队列
AudioQueueStart(_outputQueue,NULL); [_startButton setEnabled:NO];
[_stopButton setEnabled:YES]; }

然后就是实现录音和播放的回调,录音回调中对PCM数据编码,打包。代码如下:

//录音回调
void GenericInputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime,
UInt32 inNumberPackets,
const AudioStreamPacketDescription *inPacketDescs
)
{
NSLog(@"录音回调方法");
RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData); if (inNumberPackets > 0) {
NSData *pcmData = [[NSData alloc] initWithBytes:inBuffer->mAudioData length:inBuffer->mAudioDataByteSize];
//pcm数据不为空时,编码为amr格式
if (pcmData && pcmData.length > 0) {
NSData *amrData = [rootCtrl.recordAmrCode encodePCMDataToAMRData:pcmData];
//这里是对编码后的数据,通过socket发送到另一个客户端
[rootCtrl.udpSocket sendData:amrData toHost:kDefaultIP port:kDefaultPort withTimeout:-1 tag:0]; } }
AudioQueueEnqueueBuffer (inAQ,inBuffer,0,NULL); }
// 输出回调
void GenericOutputCallback (
void *inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer
)
{
NSLog(@"播放回调");
RootViewController *rootCtrl = (__bridge RootViewController *)(inUserData);
NSData *pcmData = nil;
if([receiveData count] >0)
{
NSData *amrData = [receiveData objectAtIndex:0]; pcmData = [rootCtrl.recordAmrCode decodeAMRDataToPCMData:amrData]; if (pcmData) {
if(pcmData.length < 10000){
memcpy(inBuffer->mAudioData, pcmData.bytes, pcmData.length);
inBuffer->mAudioDataByteSize = (UInt32)pcmData.length;
inBuffer->mPacketDescriptionCount = 0;
}
}
[receiveData removeObjectAtIndex:0];
}
else
{
makeSilent(inBuffer);
}
AudioQueueEnqueueBuffer(rootCtrl.outputQueue,inBuffer,0,NULL);
}

然后就是socket接收数据了,是个代理方法:

#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext
{
//这里因为对录制的PCM数据编码为amr格式并添加RTP包头之后的大小,大家可以根据自己的协议,在包头中封装上数据长度再来解析。
//PS:因为socket在发送过程中会粘包,如发送数据AAA,然后再发送BBB,可能会一次收到AAABBB,也可能会一次收到AAA,另一次收到BBB,所以针对这种情况要判断接收数据大小,来拆包
if(data.length >667)
{
int num = (data.length)/667;
int sum = 0;
for (int i=0; i<num; i++)
{
NSData *receviceData = [data subdataWithRange:NSMakeRange(i*667,667)];
[receiveData addObject:receviceData];
sum = sum+667;
}
if(sum < data.length)
{
NSData *otherData = [data subdataWithRange:NSMakeRange(sum, (data.length-sum))];
[receiveData addObject:otherData];
}
}
else
{
[receiveData addObject:data];
} }

待会上传demo。但是demo是点对点发送的。

PS:附送一点思路,服务器端做一个对讲的服务器,然后所有人都用SOCKET 的TCP方式连接对讲服务器的IP和端口号,然后我们把编码后的数据发送给服务器,通过服务器转发给其他人。

上传的代码里除了有amr编码,还加了RTP包头,我等会上传一个不含RTP包头的,只是把PCM数据编码为AMR格式,把AMR格式数据解码为PCM数据的类文件。至于怎么把文件转码为AMR格式,网上的demo太多咯。

Demo和一个不含RTP包头的编码类

iOS语音通话(语音对讲)的更多相关文章

  1. ios xmpp 发送语音图片解决方案

    ios xmpp 发送语音,图片解决方案,有需要的朋友可以参考下. 目前做IM多是用的xmpp. 因为项目需求需要实现语音和图片的发送. 发送语音图片有三种方法. 1,xmpp smack.文件传输方 ...

  2. qt中采用宽带speex进行网络语音通话实验程序

    qt中采用宽带speex进行网络语音通话实验程序 本文博客链接:http://blog.csdn.NET/jdh99,作者:jdh,转载请注明.   环境: 主机:WIN8 开发环境:Qt5 3.1. ...

  3. HTML5实时语音通话聊天,MP3压缩传输3KB每秒

    目录 一.把玩方法 二.技术特性 (1)数据传输 (2)音频采集和编码 (3)音频实时接收和播放 三.应用场景 自从Recorder H5 GitHub开源库优化后,对边录边转码成小语音片段文件实时上 ...

  4. 4G LTE 网只能提供数据服务,不能承载语音通话,该怎么理解?

    转:http://www.qbiao.com/16776.html 这个问题要从移动核心网的角度来理解.我们平时说的WCDMA.TD-SCDMA.TD-LTE其实通常指空口技术,即从手机到基站的通信技 ...

  5. UI进阶 科大讯飞(1) 语音听写(语音转换成文字)

    一.科大讯飞开放平台: http://www.xfyun.cn/ 注册.登录之后创建新应用. 因为本项目只实现了语音听写,所以在SDK下载中心勾选语音听写单项SDK就可以了 开发平台选择iOS,应用选 ...

  6. Android讯飞语音云语音听写学习

    讯飞语音云语音听写学习         这几天两个舍友都买了iPhone 6S,玩起了"Hey, Siri",我依旧对我的Nexus 5喊着"OK,Google" ...

  7. iOS开发之语音功能实现

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launc ...

  8. Android || IOS录制mp3语音文件方法

    Android Android Supported Media Formats : http://developer.android.com/guide/appendix/media-formats. ...

  9. iOS语音识别,语音播报,文字变语音播报,语音变文字

    首先使用的是科大讯飞的sdk 1.语音识别部分 AppDelegate.m #import "AppDelegate.h" #import <iflyMSC/iflyMSC. ...

随机推荐

  1. Android 动态加载(防止逆向编译) jar混淆加密

    最近工作中接到了一个研究防止逆向编译的任务.研究了几天资料,最后基本实现了防破解技术,在这个工程中,也略有一些心得体会,现整理下来分享,供大家探讨参考研究.文中如有纰漏.失实之处,请大家及时给与指正. ...

  2. android 自定义ViewGroup之浪漫求婚

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 1.最终效果 有木有发现还是很小清新的感觉 2.看整体效果这是一个scrollView,滑动时每个子view都有一个或多个动画效果 ...

  3. Dynamics CRM 删除字段时检测到有组件类型为查看的依赖组件而无法删除问题

    今天在删除一个字段的时候报如下截图错误,点开详细信息会看到是一个快速查找视图,但却在视图列中没有找到我要删的那个字段,然后回过头来又看到组件类型是查看,这是啥类型?有点摸不着头脑了. 最后想到是不是查 ...

  4. Android自定义View(二、深入解析自定义属性)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51468648 本文出自:[openXu的博客] 目录: 为什么要自定义属性 怎样自定义属性 ...

  5. 在Linux上的虚拟机上启动Oracle上报ORA-00845: MEMORY_TARGET not supported on this system的问题解决

    解决办法: 1.将当前虚拟机的内容调整大一些(以下转载:http://jingyan.baidu.com/article/414eccf67b8baa6b421f0a60.html) VMware虚拟 ...

  6. How to speed up Remote Desktop Connection in Win7

    run following command in DOS window: netsh interface tcp set global autotuninglevel=disabled or nets ...

  7. java原码、补码、反码总结

    1.1. java虚拟机整数 在java虚拟机中整数有byte.short.int.long四种 分别表示 8位.16位.32位.64位有符号整数.整数使用补码表示. 所以我们先了解一下原码和反码. ...

  8. Linux下yum安装MySQL yum安装MySQL指定版本

    yum安装MySQL 1. 查看有没有安装过     yum list installed MySQL* (有存在要卸载yum remove MySQL*)     rpm -qa | grep my ...

  9. Java基础---基础加强---增强for循环、自动拆装箱及享元、枚举的作用、实现带有构造方法、透彻分析反射的基础_Class类、成员变量的反射、数组参数的成员方法进行反射、数组的反射应用

    在perference 加content Assist 可以设置快捷键 透视图与视图 透视图:Debug和java主窗口 视图:每一个小窗口就是视图 高版本的java可运行低版本的java版本 常见的 ...

  10. HTML5中 HTML表单和PHP环境搭建及与PHP交互 韩俊强的博客

    每日更新关注:http://weibo.com/hanjunqiang  新浪微博! 知识点概括:HTML表单/PHP环境搭建/表单提交数据与PHP交互 第一部分:HTML表单 <!DOCTYP ...