iOS实时录音和播放功能
需求:最近公司需要做一个楼宇对讲的功能:门口机(连接WIFI)拨号对室内机(对应的WIFI)的设备进行呼叫,室内机收到呼叫之后将对收到的数据进行UDP广播的转发,手机(连接对应的WIFI)收到视频流之后,实时的展示视频数据(手机可以接听,挂断,手机接听之后,室内机不展示视频,只是进行转发。)
简单点说就是手机客户端需要做一个类似于直播平台的软件,可以实时的展示视频,实时的播放接收到的声音数据,并且实时将手机麦克风收到的声音回传给室内机,室内机负责转发给门口机。
这篇文章介绍iOS怎么进行实时的录音和播放收到的声音数据
想要使用系统的框架实时播放声音和录音数据,就得知道音频队列服务,
在AudioToolbox框架中的音频队列服务,它完全可以做到音频播放和录制,
一个音频服务队列有三个部分组成:
1.三个缓冲器Buffers:没个缓冲器都是一个存储音频数据的临时仓库。
2.一个缓冲队列BufferQueue:一个包含音频缓冲器的有序队列。
3.一个回调CallBack:一个自定义的队列回调函数。
具体怎么运转的还是百度吧!
我的简单理解:
对于播放:系统会自动从缓冲队列中循环取出每个缓冲器中的数据进行播放,我们需要做的就是将接收到的数据循环的放到缓冲器中,剩下的就交给系统去实现了。
对于录音: 系统会自动将录的声音放入队列中的每个缓冲器中,我们需要做的就是从回调函数中将数据转化我们自己的数据就OK了。
#pragmamark--实时播放
1.导入系统框架AudioToolbox.framework AVFoundation.framework
2.获取麦克风权限,在工程的Info.plist文件中加入Privacy-MicrophoneUsageDescription这个key描述:App想要访问您的麦克风
3.创建播放声音的类EYAudio
EYAudio.h
#import@interfaceEYAudio:NSObject //播放的数据流数据 -(void)playWithData:(NSData*)data; //声音播放出现问题的时候可以重置一下 -(void)resetPlay; //停止播放 -(void)stop; @end
EYAudio.m
#import"EYAudio.h"
#import#import #defineMIN_SIZE_PER_FRAME1920//每个包的大小,室内机要求为960,具体看下面的配置信息 #defineQUEUE_BUFFER_SIZE3//缓冲器个数 #defineSAMPLE_RATE16000//采样频率 @interfaceEYAudio(){ AudioQueueRefaudioQueue;//音频播放队列 AudioStreamBasicDescription_audioDescription; AudioQueueBufferRefaudioQueueBuffers[QUEUE_BUFFER_SIZE];//音频缓存 BOOLaudioQueueBufferUsed[QUEUE_BUFFER_SIZE];//判断音频缓存是否在使用 NSLock*sysnLock; NSMutableData*tempData; OSStatusosState; } @end @implementationEYAudio #pragmamark-提前设置AVAudioSessionCategoryMultiRoute播放和录音 +(void)initialize { NSError*error=nil; //只想要播放:AVAudioSessionCategoryPlayback //只想要录音:AVAudioSessionCategoryRecord //想要"播放和录音"同时进行必须设置为:AVAudioSessionCategoryMultiRoute而不是AVAudioSessionCategoryPlayAndRecord(设置这个不好使) BOOLret=[[AVAudioSessionsharedInstance]setCategory:AVAudioSessionCategoryMultiRouteerror:&error]; if(!ret){ NSLog(@"设置声音环境失败"); return; } //启用audiosession ret=[[AVAudioSessionsharedInstance]setActive:YESerror:&error]; if(!ret) { NSLog(@"启动失败"); return; } } -(void)resetPlay { if(audioQueue!=nil){ AudioQueueReset(audioQueue); } } -(void)stop { if(audioQueue!=nil){ AudioQueueStop(audioQueue,true); } audioQueue=nil; sysnLock=nil; } -(instancetype)init { self=[superinit]; if(self){ sysnLock=[[NSLockalloc]init]; //设置音频参数具体的信息需要问后台 _audioDescription.mSampleRate=SAMPLE_RATE; _audioDescription.mFormatID=kAudioFormatLinearPCM; _audioDescription.mFormatFlags=kLinearPCMFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; //1单声道 _audioDescription.mChannelsPerFrame=1; //每一个packet一侦数据,每个数据包下的桢数,即每个数据包里面有多少桢 _audioDescription.mFramesPerPacket=1; //每个采样点16bit量化语音每采样点占用位数 _audioDescription.mBitsPerChannel=16; _audioDescription.mBytesPerFrame=(_audioDescription.mBitsPerChannel/8)*_audioDescription.mChannelsPerFrame; //每个数据包的bytes总数,每桢的bytes数*每个数据包的桢数 _audioDescription.mBytesPerPacket=_audioDescription.mBytesPerFrame*_audioDescription.mFramesPerPacket; //使用player的内部线程播放新建输出 AudioQueueNewOutput(&_audioDescription,AudioPlayerAQInputCallback,(__bridgevoid*_Nullable)(self),nil,0,0,&audioQueue); //设置音量 AudioQueueSetParameter(audioQueue,kAudioQueueParam_Volume,1.0); //初始化需要的缓冲区 for(inti=0;i =QUEUE_BUFFER_SIZE){ i=0; } } } audioQueueBuffers[i]->mAudioDataByteSize=(unsignedint)len; //把bytes的头地址开始的len字节给mAudioData,向第i个缓冲器 memcpy(audioQueueBuffers[i]->mAudioData,bytes,len); //释放对象 free(bytes); //将第i个缓冲器放到队列中,剩下的都交给系统了 AudioQueueEnqueueBuffer(audioQueue,audioQueueBuffers[i],0,NULL); [sysnLockunlock]; } //**************************回调********************************** //回调回来把buffer状态设为未使用 staticvoidAudioPlayerAQInputCallback(void*inUserData,AudioQueueRefaudioQueueRef,AudioQueueBufferRefaudioQueueBufferRef){ EYAudio*audio=(__bridgeEYAudio*)inUserData; [audioresetBufferState:audioQueueRefand:audioQueueBufferRef]; } -(void)resetBufferState:(AudioQueueRef)audioQueueRefand:(AudioQueueBufferRef)audioQueueBufferRef{ //防止空数据让audioqueue后续都不播放,为了安全防护一下 if(tempData.length==0){ audioQueueBufferRef->mAudioDataByteSize=1; Byte*byte=audioQueueBufferRef->mAudioData; byte=0; AudioQueueEnqueueBuffer(audioQueueRef,audioQueueBufferRef,0,NULL); } for(inti=0;i 外界使用:不断调用下面的方法将NSData传递进来
-(void)playWithData:(NSData*)data;
#pragmamark--实时录音
1.导入系统框架AudioToolbox.framework AVFoundation.framework
2.创建录音的类EYRecord
EYRecord.h
#import@interfaceESARecord:NSObject //开始录音 -(void)startRecording; //停止录音 -(void)stopRecording; @end EYRecord.m
#import"ESARecord.h" #import#defineQUEUE_BUFFER_SIZE3//输出音频队列缓冲个数 #definekDefaultBufferDurationSeconds0.03//调整这个值使得录音的缓冲区大小为960,实际会小于或等于960,需要处理小于960的情况 #definekDefaultSampleRate16000//定义采样率为16000 externNSString*constESAIntercomNotifationRecordString; staticBOOLisRecording=NO; @interfaceESARecord(){ AudioQueueRef_audioQueue;//输出音频播放队列 AudioStreamBasicDescription_recordFormat; AudioQueueBufferRef_audioBuffers[QUEUE_BUFFER_SIZE];//输出音频缓存 } @property(nonatomic,assign)BOOLisRecording; @end @implementationESARecord -(instancetype)init { self=[superinit]; if(self){ //重置下 memset(&_recordFormat,0,sizeof(_recordFormat)); _recordFormat.mSampleRate=kDefaultSampleRate; _recordFormat.mChannelsPerFrame=1; _recordFormat.mFormatID=kAudioFormatLinearPCM; _recordFormat.mFormatFlags=kLinearPCMFormatFlagIsSignedInteger|kLinearPCMFormatFlagIsPacked; _recordFormat.mBitsPerChannel=16; _recordFormat.mBytesPerPacket=_recordFormat.mBytesPerFrame=(_recordFormat.mBitsPerChannel/8)*_recordFormat.mChannelsPerFrame; _recordFormat.mFramesPerPacket=1; //初始化音频输入队列 AudioQueueNewInput(&_recordFormat,inputBufferHandler,(__bridgevoid*)(self),NULL,NULL,0,&_audioQueue); //计算估算的缓存区大小 intframes=(int)ceil(kDefaultBufferDurationSeconds*_recordFormat.mSampleRate); intbufferByteSize=frames*_recordFormat.mBytesPerFrame; NSLog(@"缓存区大小%d",bufferByteSize); //创建缓冲器 for(inti=0;i 0){ ESARecord*recorder=(__bridgeESARecord*)inUserData; [recorderprocessAudioBuffer:inBufferwithQueue:inAQ]; } if(isRecording){ AudioQueueEnqueueBuffer(inAQ,inBuffer,0,NULL); } } -(void)processAudioBuffer:(AudioQueueBufferRef)audioQueueBufferRefwithQueue:(AudioQueueRef)audioQueueRef { NSMutableData*dataM=[NSMutableDatadataWithBytes:audioQueueBufferRef->mAudioDatalength:audioQueueBufferRef->mAudioDataByteSize]; if(dataM.length<960){//处理长度小于960的情况,此处是补00 Bytebyte[]={0x00}; NSData*zeroData=[[NSDataalloc]initWithBytes:bytelength:1]; for(NSUIntegeri=dataM.length;i<960;i++){ [dataMappendData:zeroData]; } } //NSLog(@"实时录音的数据--%@",dataM); //此处是发通知将dataM传递出去 [[NSNotificationCenterdefaultCenter]postNotificationName:@"EYRecordNotifacation"object:@{@"data":dataM}]; } -(void)stopRecording { if(isRecording) { isRecording=NO; //停止录音队列和移除缓冲区,以及关闭session,这里无需考虑成功与否 AudioQueueStop(_audioQueue,true); //移除缓冲区,true代表立即结束录制,false代表将缓冲区处理完再结束 AudioQueueDispose(_audioQueue,true); } NSLog(@"停止录音"); } @end 如果不好使尝试将EYRecord.m---->EYRecord.mm
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。