iOS使用VideoToolbox硬编码录制H264视频
来源:互联网 发布:阿里云os3.2 系统优化 编辑:程序博客网 时间:2024/05/01 10:01
如今各种直播如雨后春笋般在今年冒出来了,主要的移动平台Android和iOS都相继开放了视频硬件编解码接口,以往这些接口都是系统私有的,开发者无法使用。
视频编码解码是一项繁重的工作,尤其在移动平。所以从iOS8开始,苹果将VideoToolbox.framework开放了出来,使开发者可以使用iOS设备内置的硬件设备来进行视频的编码和解码工作。硬件编解码的好处是,复杂的计算由专门的硬件电路完成,往往比使用cpu计算更高效,速度更快,功耗也更低。
VideoToolbox 工作流程大致为:
- 创建Session ->设置编码相关参数 ->开始编码
- —> 循环输入源数据(yuv类型的数据,直接从摄像头获取)
- —> 获取编码后的h264数据
- 结束编码
以下为详细代码,内有详细注释,完整工程在文章末尾。
#pragma mark - videotoolbox methods- (int)startEncodeSession:(int)width height:(int)height framerate:(int)fps bitrate:(int)bt{ OSStatus status; _frameCount = 0; VTCompressionOutputCallback cb = encodeOutputCallback; status = VTCompressionSessionCreate(kCFAllocatorDefault, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, cb, (__bridge void *)(self), &_encodeSesion); if (status != noErr) { NSLog(@"VTCompressionSessionCreate failed. ret=%d", (int)status); return -1; } // 设置实时编码输出,降低编码延迟 status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue); NSLog(@"set realtime return: %d", (int)status); // h264 profile, 直播一般使用baseline,可减少由于b帧带来的延时 status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel); NSLog(@"set profile return: %d", (int)status); // 设置编码码率(比特率),如果不设置,默认将会以很低的码率编码,导致编码出来的视频很模糊 status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_AverageBitRate, (__bridge CFTypeRef)@(bt)); // bps status += VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)@[@(bt*2/8), @1]); // Bps NSLog(@"set bitrate return: %d", (int)status); // 设置关键帧间隔,即gop size status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, (__bridge CFTypeRef)@(fps*2)); // 设置帧率,只用于初始化session,不是实际FPS status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, (__bridge CFTypeRef)@(fps)); NSLog(@"set framerate return: %d", (int)status); // 开始编码 status = VTCompressionSessionPrepareToEncodeFrames(_encodeSesion); NSLog(@"start encode return: %d", (int)status); return 0;}// 编码一帧图像,使用queue,防止阻塞系统摄像头采集线程- (void) encodeFrame:(CMSampleBufferRef )sampleBuffer{ dispatch_sync(_encodeQueue, ^{ CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer); // pts,必须设置,否则会导致编码出来的数据非常大,原因未知 CMTime pts = CMTimeMake(_frameCount, 1000); CMTime duration = kCMTimeInvalid; VTEncodeInfoFlags flags; // 送入编码器编码 OSStatus statusCode = VTCompressionSessionEncodeFrame(_encodeSesion, imageBuffer, pts, duration, NULL, NULL, &flags); if (statusCode != noErr) { NSLog(@"H264: VTCompressionSessionEncodeFrame failed with %d", (int)statusCode); [self stopEncodeSession]; return; } });}- (void) stopEncodeSession{ VTCompressionSessionCompleteFrames(_encodeSesion, kCMTimeInvalid); VTCompressionSessionInvalidate(_encodeSesion); CFRelease(_encodeSesion); _encodeSesion = NULL;}// 编码回调,每当系统编码完一帧之后,会异步掉用该方法,此为c语言方法void encodeOutputCallback(void *userData, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer ){ if (status != noErr) { NSLog(@"didCompressH264 error: with status %d, infoFlags %d", (int)status, (int)infoFlags); return; } if (!CMSampleBufferDataIsReady(sampleBuffer)) { NSLog(@"didCompressH264 data is not ready "); return; } ViewController* vc = (__bridge ViewController*)userData; // 判断当前帧是否为关键帧 bool keyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync); // 获取sps & pps数据. sps pps只需获取一次,保存在h264文件开头即可 if (keyframe && !vc->_spsppsFound) { size_t spsSize, spsCount; size_t ppsSize, ppsCount; const uint8_t *spsData, *ppsData; CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer); OSStatus err0 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0 ); OSStatus err1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0 ); if (err0==noErr && err1==noErr) { vc->_spsppsFound = 1; [vc writeH264Data:(void *)spsData length:spsSize addStartCode:YES]; [vc writeH264Data:(void *)ppsData length:ppsSize addStartCode:YES]; NSLog(@"got sps/pps data. Length: sps=%zu, pps=%zu", spsSize, ppsSize); } } size_t lengthAtOffset, totalLength; char *data; CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer); OSStatus error = CMBlockBufferGetDataPointer(dataBuffer, 0, &lengthAtOffset, &totalLength, &data); if (error == noErr) { size_t offset = 0; const int lengthInfoSize = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length // 循环获取nalu数据 while (offset < totalLength - lengthInfoSize) { uint32_t naluLength = 0; memcpy(&naluLength, data + offset, lengthInfoSize); // 获取nalu的长度, // 大端模式转化为系统端模式 naluLength = CFSwapInt32BigToHost(naluLength); NSLog(@"got nalu data, length=%d, totalLength=%zu", naluLength, totalLength); // 保存nalu数据到文件 [vc writeH264Data:data+offset+lengthInfoSize length:naluLength addStartCode:YES]; // 读取下一个nalu,一次回调可能包含多个nalu offset += lengthInfoSize + naluLength; } }}// 保存h264数据到文件- (void) writeH264Data:(void*)data length:(size_t)length addStartCode:(BOOL)b{ // 添加4字节的 h264 协议 start code const Byte bytes[] = "\x00\x00\x00\x01"; if (_h264File) { if(b) fwrite(bytes, 1, 4, _h264File); fwrite(data, 1, length, _h264File); } else { NSLog(@"_h264File null error, check if it open successed"); }}
编码保存的h264数据保存到Document目录下,在Info.plist文件中添加Application supports iTunes file sharing ,并设置为YES,即可通过iTunes直接导出来,然后使用VLC播放器即可直接播放。
Demo 源码地址
WWDC关于VideoToolbox的视频: 百度云链接 密码: uzsr
==== 直播美颜相关问题,欢迎加群讨论:496010189 ====
2 0
- iOS使用VideoToolbox硬编码录制H264视频
- iOS使用VideoToolbox硬编码录制H264视频
- iOS使用VideoToolbox硬编码录制H264视频
- 基于iOS的网络音视频实时传输系统(五)- 使用VideoToolbox硬解码H264
- iOS 视频h264硬编码
- 使用VideoToolBox硬解码MJPEG编码格式视频
- 使用VideoToolbox硬编码H.264
- VideoToolBox编码h264
- iOS h264硬编码
- 基于iOS的网络音视频实时传输系统(三)- VideoToolbox编码音视频数据为H264、AAC
- iOS 视频h264硬解码
- MediaCodec硬编码成H264视频流
- 使用VideoToolBox对获取到的视频进行编码
- ios 硬解码h264视频的坑
- android开发,通过摄像头实时采集视频并使用MediaCodec硬编码为H264
- H264--Intel硬编码
- MediaCodec 硬编码 h264
- iOS h264 硬解码
- Swift 流程控制
- Linux中安装配置ftp服务器方法
- 【模板】优先队列很好的用法
- 安装virtualenv No module named virtualenv
- 模仿今日头条的4.6版
- iOS使用VideoToolbox硬编码录制H264视频
- I2C总线协议
- stm32 定时器
- ant标签
- 华为在线练习(1)
- _beginthreadex
- JAVA 数据结构之栈实现
- JSp中 include指令标记与<jsp:include>动作标记的区别
- 自己搭建并维护一个小型网站