公司的直播项目一直采用的网易云的直播SDK,后来产品需求需要我们使用手机端作为音视频采集设备,使用电视端作为传输与显示媒介,把视频数据展示在电视和Windows端,音频只展示Windows端,这样一个需求,这样我这边需要做到的:
1.音视频采集已经考虑的传输数据大小问题需要对音视频进行压缩编码
2.数据传输,因为电视和手机在同一个局域网所以,我们使用socket进行数据传输
以上两点看似简单其实里面内容挺多,各种问题的出现让我在这个项目中跌了很多跟头,对于遇到的问题做以下记录
音视频采集
iOS这边处理音视频采集并不难,网上教程一大把,特别是视频采集很简单,只需有一点要注意就是像素的大小设置,苹果有两种方案:一种是你自定义画布大小,第二个你使用硬件支持的大小
自定义大小
pixW = 320;pixH = 240; //画布宽高自定义
self.captureSession.sessionPreset = AVCaptureSessionPresetHigh; //画质 采用最高 数据也比较大
使用硬件支持
这个需要你使用系统像素采集的宽高
if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) { //判断当前采集的设备 是否支持 这个像素
self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
音频这边的就有多个选择, 比如:AVCaptureDevice, AudioQueue以及Audio Unit。其中 Audio Unit是最底层的接口,它的优点是功能强大,延迟低; 而缺点是学习成本高,难度大。对于一般的iOS应用程序,AVCaptureDevice和AudioQueue完全够用了。但对于音视频直播,最好还是使用 Audio Unit 进行处理,这样可以达到最佳的效果。
因为我这边做的是直播的所以为了追求低延迟,最好的效果,就采用了Audio Unit进行处理,因为都是C语言的代码而且还要和OC进行结合所以学习起来确实很费劲 所以对里面的关键代码做了注释
// AVAudioSession的接口比较简单。APP启动的时候会自动帮激活AVAudioSession,我们可以手动 设置 开始的时候b可以播放也可以录音
AVAudioSession* session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker error:&error];
[session setActive:YES error:nil];
//启用录音功能
UInt32 inputEnableFlag = 1;
CheckError(AudioUnitSetProperty(_remoteIOUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
1,
&inputEnableFlag,
sizeof(inputEnableFlag)),
"Open input of bus 1 failed");
// Open output of bus 0(output speaker)//禁用播放功能
UInt32 outputEnableFlag = 1;
CheckError(AudioUnitSetProperty(_remoteIOUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
0,
&outputEnableFlag,
sizeof(outputEnableFlag)),
"Open output of bus 0 failed");
//Set up stream format for input and output
/**
AudioStreamBasicDescription:
mSampleRate; 采样率, eg. 44100
mFormatID; 格式, eg. kAudioFormatLinearPCM
mFormatFlags; 标签格式, eg. kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked
mBytesPerPacket; 每个Packet的Bytes数量, eg. 2
mFramesPerPacket; 每个Packet的帧数量, eg. 1
mBytesPerFrame; (mBitsPerChannel / 8 * mChannelsPerFrame) 每帧的Byte数, eg. 2
mChannelsPerFrame; 1:单声道;2:立体声, eg. 1
mBitsPerChannel; 语音每采样点占用位数[8/16/24/32], eg. 16
mReserved; 保留
*/
_streamFormat.mFormatID = kAudioFormatLinearPCM;
_streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
_streamFormat.mSampleRate = kRate;
_streamFormat.mFramesPerPacket = 1;
_streamFormat.mBytesPerFrame = 2;
_streamFormat.mBytesPerPacket = 2;
_streamFormat.mBitsPerChannel = kBits;
_streamFormat.mChannelsPerFrame = kChannels;
CheckError(AudioUnitSetProperty(_remoteIOUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
0,
&_streamFormat,
sizeof(_streamFormat)),
"kAudioUnitProperty_StreamFormat of bus 0 failed");
CheckError(AudioUnitSetProperty(_remoteIOUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
1,
&_streamFormat,
sizeof(_streamFormat)),
"kAudioUnitProperty_StreamFormat of bus 1 failed");
//音频采集结果回调
AURenderCallbackStruct recordCallback;
recordCallback.inputProc = recordCallback_xb;
recordCallback.inputProcRefCon = (__bridge void *)(self);
CheckError(AudioUnitSetProperty(_remoteIOUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Output,
1,
&recordCallback,
sizeof(recordCallback)),
"couldnt set remote i/o render callback for output");
音视频编码
视频我采用的是常用的H264编码,之前说了视频采集很简单,但视频264编码也是C语言写里面的设置还是有很多需要注意的
OSStatus status = VTCompressionSessionCreate(NULL,//分配器,如果使用NULL的话,就使用默认的. CFAllocatorRef _Nullable allocator
width,//视频帧的象素宽 int32_t width
height,//视频帧的象素高 int32_t height
kCMVideoCodecType_H264,//编码器的类型 CMVideoCodecType codecType
NULL,//如果用指定的视频编码器,就要设置这个.使用NULL就是videoToolbox自己选择一个 CFDictionaryRef _Nullable encoderSpecification
NULL,//元象素缓存,如果你不要videoToolbox给你创建,就传NULL.使用非VTB分配的缓存,可以让你有机会拷贝图片 CFDictionaryRef _Nullable sourceImageBufferAttributes,
NULL,//压缩数据分配器.传NULL可以使用默认的. CFAllocatorRef _Nullable compressedDataAllocator
didCompressH264,//回调,这个方法会在另一个线程上被异步的VTCompressionSessionEncodeFrame调用.只有在你要使VTCompressionSessionEncodeFrameWithOutputHandler去编码帧时,才可以设置为NULL. VTCompressionOutputCallback _Nullable outputCallback
(__bridge void *)(self),//回调方法所在的实例,回调方法是全局的可以设置为NULL void * _Nullable outputCallbackRefCon
&EncodingSession);//用来接收新的compression session VTCompressionSessionRef _Nullable * _Nonnull compressionSessionOut
NSLog(@"H264: VTCompressionSessionCreate %d", (int)status);
// Set the properties
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);//实时运行
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AllowFrameReordering, kCFBooleanFalse);
//设置码率,均值,单位是byte
SInt32 bitRate = width*height*3 * 4 * 8 * 2; //越高效果越屌 帧数据越大
CFNumberRef ref = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_AverageBitRate, ref);
CFRelease(ref);
//设置码率上限,均值,单位是byte
int bitRateLimit = width * height * 3 * 4 * 2;
CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRateLimit);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
int frameInterval = 10; //关键帧间隔 越低效果越屌 帧数据越大
CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
VTSessionSetProperty(EncodingSession, kVTCompressionPropertyKey_MaxKeyFrameInterval,frameIntervalRef);
CFRelease(frameIntervalRef);
设置码率这个一定要 把宽高数据放大100倍左右不然图像会很模糊,当时因为这个问题困扰了我3天,其他倒是没有太大问题的
Socket 数据传输
socket我采用的是GCDAsyncSocket 这个框架,使用方法也很简单网上教程很多,在这不再详述,只是记录一下注意的问题
比如数据传输 常量数据进行大小端转换问题 ,还有因为数据都是采用二级制进行发送所以要对数据进行组合,但是音视频发送数据要单独写一个方法,不然NSMutableData 对象会造成内存泄露无法释放,原因是视频采集的代理方法执行速度太快导致此对象释放不及时
char sign[8] = "KOCLACMD";
uint16_t data_type = 0x02;
uint16_t media_type = 0x06;
uint32_t width = pixW;
uint32_t height = pixH;
uint32_t len = (uint32_t)data.length;
// 字节序转换 小端模式转换大端 匹配网络字节序
HTONS(data_type);
HTONS(media_type);
HTONL(len);
HTONL(width);
HTONL(height);
NSMutableData *muta = [NSMutableData data];
[muta appendBytes:&sign length:8];
[muta appendBytes:&data_type length:2];
[muta appendBytes:&media_type length:2];
[muta appendBytes:&len length:4];
[muta appendBytes:&width length:4];
[muta appendBytes:&height length:4];
[muta appendData:data];
// NSLog(@"Video data (%lu): %@", (unsigned long)muta.length, muta.description);
[_clientSocket writeData:muta withTimeout:-1 tag:0];
因为你传输到其他端比如后台所以涉及到常量类型的数据要进行大小端转换,而且还要注意 short 类型转换和 long类型转换的方法不同
其他也没有什么了
网友评论