概述
上篇文章我们学习了视频的相关概念及h264编解码的流程,这篇文章我们主要是做代码实现,其内容概要如下:
- 利用VideoToolBox对实时视频做h264硬编码
- ffmpeg
- 在mac平台安装ffmpeg
- 简单常用的ffmpeg命令
- 如何在mac平台编译出ios开发所用的ffmpeg库以及环境搭建
- 简单介绍ffmpeg库
- 利用ffmpeg对实时视频做h264软编码
示例代码:
代码结构:
运行截图:
如果对视频编解码相关概念不清楚,请参看上篇文章--视频的基本参数及H264编解码相关概念
利用VideoToolBox对实时视频做H264硬编码
总体步骤如下:
初始化编码器
第1步:设置视频的宽高
- (void)settingEncoderParametersWithWidth:(int)width height:(int)height fps:(int)fps
{
self.width = width;
self.height = height;
self.fps = fps;
}
复制代码
第2步:设置编码器类型为kCMVideoCodecType_H264
,通过VTSessionSetProperty
方法和 kVTCompressionPropertyKey_ExpectedFrameRate
、kVTCompressionPropertyKey_AverageBitRate
等key分别设置帧率和比特率等参数。
- (void)prepareForEncoder
{
if (self.width == 0 || self.height == 0) {
NSLog(@"AppHWH264Encoder, VTSession need width and height for init, width = %d, height = %d",self.width,self.height);
return;
}
[m_lock lock];
OSStatus status = noErr;
status = VTCompressionSessionCreate(NULL, self.width, self.height, kCMVideoCodecType_H264, NULL, NULL, NULL, miEncoderVideoCallBack, (__bridge void *)self, &compressionSession);
if (status != noErr) {
NSLog(@"AppHWH264Encoder , create encoder session failed,res=%d",status);
return;
}
if (self.fps) {
int v = self.fps;
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &v);
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, ref);
CFRelease(ref);
if (status != noErr) {
NSLog(@"AppHWH264Encoder, create encoder session failed, fps=%d,res=%d",self.fps,status);
return;
}
}
if (self.bitrate) {
int v = self.bitrate;
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &v);
status = VTSessionSetProperty(compressionSession, kVTCompressionPropertyKey_AverageBitRate,ref);
CFRelease(ref);
if (status != noErr) {
NSLog(@"AppHWH264Encoder, create encoder session failed, bitrate=%d,res=%d",self.bitrate,status);
return;
}
}
status = VTCompressionSessionPrepareToEncodeFrames(compressionSession);
if (status != noErr) {
NSLog(@"AppHWH264Encoder, create encoder session failed,res=%d",status);
return;
}
}
复制代码
利用VTCompressionSession硬编码CMSampleBufferRef
把摄像头捕获的CMSampleBuffer
直接传递给以下编码方法:
- (void)encoder:(CMSampleBufferRef)sampleBuffer
{
if (!self.isInitHWH264Encoder) {
[self prepareForEncoder];
self.isInitHWH264Encoder = YES;
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
OSStatus status = VTCompressionSessionEncodeFrame(compressionSession, imageBuffer, presentationTime, kCMTimeInvalid, NULL, NULL, NULL);
if (status != noErr) {
VTCompressionSessionInvalidate(compressionSession);
VTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);
CFRelease(compressionSession);
compressionSession = NULL;
self.isInitHWH264Encoder = NO;
NSLog(@"AppHWH264Encoder, encoder failed");
return;
}
}
复制代码
在回调函数中,将编码成功的码流转化成H264码流结构
在此处,主要是解析SPS,PPS,然后加上开始码之后组装成NALU单元(此处必须十分了解H264码流结构,如果不清楚,可翻看前面的文章)
NSLog(@"%s",__func__);
if (status != noErr) {
NSLog(@"AppHWH264Encoder, encoder failed, res=%d",status);
return;
}
if (!CMSampleBufferDataIsReady(sampleBuffer)) {
NSLog(@"AppHWH264Encoder, samplebuffer is not ready");
return;
}
MIHWH264Encoder *encoder = (__bridge MIHWH264Encoder*)outputCallbackRefCon;
CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);
BOOL isKeyframe = false;
CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, false);
if(attachments != NULL)
{
CFDictionaryRef attachment =(CFDictionaryRef)CFArrayGetValueAtIndex(attachments, 0);
CFBooleanRef dependsOnOthers = (CFBooleanRef)CFDictionaryGetValue(attachment, kCMSampleAttachmentKey_DependsOnOthers);
isKeyframe = (dependsOnOthers == kCFBooleanFalse);
}
if(isKeyframe)
{
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
size_t spsSize, ppsSize;
size_t parmCount;
const uint8_t*sps, *pps;
int NALUnitHeaderLengthOut;
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut );
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut );
uint8_t *spsppsNALBuff = (uint8_t*)malloc(spsSize+4+ppsSize+4);
memcpy(spsppsNALBuff, "\x00\x00\x00\x01", 4);
memcpy(&