视频采集
视频采集部分,上一篇文章已经提及
iOS视频采集
视频编码
首先初始化编码器
- (void)initEncoder{
//视频宽、高、帧率、码率
int width = 1280, height = 720, FPS = 25, bitrate = 2048;
//默认H264编码,H265编码需要iOS 11
encoderType = kCMVideoCodecType_H264;
// if (@available(iOS 11.0, *)) {
// if ([[AVAssetExportSession allExportPresets] containsObject:AVAssetExportPresetHEVCHighestQuality]){
// ///是否支持H265
// encoderType = kCMVideoCodecType_HEVC;
// }
// }
lock = [[NSLock alloc] init];
[lock lock];
//创建session
VTCompressionSessionRef session;
OSStatus status = VTCompressionSessionCreate(NULL,
width,
height,
encoderType,
NULL,
NULL,
NULL,
videoEncoderCallBack,
(__bridge void*)self,
&session);
if (status == noErr) {
}else{
NSLog(@"创建session失败");
}
///设置session属性
// 设置实时编码输出(避免延迟)
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
if (encoderType == kCMVideoCodecType_H264) {
if ((YES)/*支持实时编码*/) {
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Main_AutoLevel);
}else{
//表示使用H264的Profile规格,可以设置Hight的AutoLevel规格
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
//若是支持h264该属性设置编码器是否应该使用基于CAVLC 或是 CABAC
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_H264EntropyMode, kVTH264EntropyMode_CAVLC);
}
}else if (encoderType == kCMVideoCodecType_HEVC){
///H265
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_HEVC_Main_AutoLevel);
}
// 设置关键帧(GOPsize)间隔
int frameInterval = 10;
CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);
// 设置期望帧率
CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &FPS);
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);
//设置码率,上限,单位是bps
int bitRate = width * height * 3 * 4 * 8;
CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);
//设置码率,均值,单位是byte
int bitRateLimit = width * height * 3 * 4;
CFNumberRef bitRateLimitRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRateLimit);
status = VTSessionSetProperty(session, kVTCompressionPropertyKey_DataRateLimits, bitRateLimitRef);
//告诉编码器开始编码
status = VTCompressionSessionPrepareToEncodeFrames(session);
[lock unlock];
if (status != noErr) {
NSLog(@"失败,需要排查问题和参数");
}else{
videoSession = session;
}
}
传入需要编码的数据
/// 传入需要编码的数据进行编码
/// @param sampleBuffer 需要编码的原始数据
/// @param isNeedFreeBuffer 是否需要释放,如果自己组装的,需要手动释放,系统返回的不需要
- (void)startEncodeWithSampleBuffer:(CMSampleBufferRef)sampleBuffer isNeedFreeBuffer:(BOOL)isNeedFreeBuffer{
[lock lock];
//第一帧必须是iframe,然后创建引用的时间戳
static BOOL isFirstFrame = YES;
if (isFirstFrame && encode_capture_base_time == 0) {
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
encode_capture_base_time = CMTimeGetSeconds(pts);
isFirstFrame = NO;
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CMTime presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
// 切换不同源数据会显示马赛克,因为时间戳不同步
static int64_t lastPts = 0;
int64_t currentPts = (int64_t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000);
lastPts = currentPts;
OSStatus status = noErr;
//如果编码中途进行其他操作,这时候强制添加关键帧
BOOL needForceInsertKeyFrame = NO;//是否需要强制插入关键帧
NSDictionary *properties = @{(__bridge NSString *)kVTEncodeFrameOptionKey_ForceKeyFrame:@(needForceInsertKeyFrame)};
//编码
status = VTCompressionSessionEncodeFrame(videoSession,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
(__bridge CFDictionaryRef)properties,
NULL,
NULL);
if (status != noErr) {
[self handleEncodeFailedWithIsNeedFreeBuffer:isNeedFreeBuffer sampleBuffer:sampleBuffer];
}
[lock unlock];
if (isNeedFreeBuffer) {
if (sampleBuffer != NULL) {
CFRelease(sampleBuffer);
}
}
}
编码回调
#pragma mark - callback -
static void videoEncoderCallBack(void *outputCallbackRefCon,
void *souceFrameRefCon,
OSStatus status,
VTEncodeInfoFlags infoFlags,
CMSampleBufferRef sampleBuffer) {
(当前类)*encoder = (__bridge (当前类)*)outputCallbackRefCon;
if (status != noErr) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"编码返回失败 %@", error);
return;
}
CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
CMTime dts = CMSampleBufferGetDecodeTimeStamp(sampleBuffer);
// 利用我们定义的时间。(此时间用于同步音频和视频)
int64_t ptsAfter = (int64_t)((CMTimeGetSeconds(pts) - encode_capture_base_time) * 1000);
int64_t dtsAfter = (int64_t)((CMTimeGetSeconds(dts) - encode_capture_base_time) * 1000);
dtsAfter = ptsAfter;
/*有时相对DTS为零,提供一个恢复DTS的工作区*/
static int64_t last_dts = 0;
if(dtsAfter == 0){
dtsAfter = last_dts +33;
}else if (dtsAfter == last_dts){
dtsAfter = dtsAfter + 1;
}
BOOL isKeyFrame = NO;
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) {
static uint8_t *keyParameterSetBuffer = NULL;
static size_t keyParameterSetBufferSize = 0;
// 注意:如果视频分辨率不改变,NALU头不会改变。
if (keyParameterSetBufferSize == 0 || YES == encoder->needResetKeyParamSetBuffer) {
const uint8_t *vps, *sps, *pps;
size_t vpsSize, spsSize, ppsSize;
int NALUnitHeaderLengthOut;
size_t parmCount;
if (keyParameterSetBuffer != NULL) {
free(keyParameterSetBuffer);
}
CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
if (encoder->encoderType == kCMVideoCodecType_H264) {
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut);
CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut);
keyParameterSetBufferSize = spsSize+4+ppsSize+4;
keyParameterSetBuffer = (uint8_t*)malloc(keyParameterSetBufferSize);
memcpy(keyParameterSetBuffer, "\x00\x00\x00\x01", 4);
memcpy(&keyParameterSetBuffer[4], sps, spsSize);
memcpy(&keyParameterSetBuffer[4+spsSize], "\x00\x00\x00\x01", 4);
memcpy(&keyParameterSetBuffer[4+spsSize+4], pps, ppsSize);
NSLog(@ "H264 find IDR frame, spsSize : %zu, ppsSize : %zu",spsSize, ppsSize);
}else if (encoder->encoderType == kCMVideoCodecType_HEVC) {
CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 0, &vps, &vpsSize, &parmCount, &NALUnitHeaderLengthOut);
CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 1, &sps, &spsSize, &parmCount, &NALUnitHeaderLengthOut);
CMVideoFormatDescriptionGetHEVCParameterSetAtIndex(format, 2, &pps, &ppsSize, &parmCount, &NALUnitHeaderLengthOut);
keyParameterSetBufferSize = vpsSize+4+spsSize+4+ppsSize+4;
keyParameterSetBuffer = (uint8_t*)malloc(keyParameterSetBufferSize);
memcpy(keyParameterSetBuffer, "\x00\x00\x00\x01", 4);
memcpy(&keyParameterSetBuffer[4], vps, vpsSize);
memcpy(&keyParameterSetBuffer[4+vpsSize], "\x00\x00\x00\x01", 4);
memcpy(&keyParameterSetBuffer[4+vpsSize+4], sps, spsSize);
memcpy(&keyParameterSetBuffer[4+vpsSize+4+spsSize], "\x00\x00\x00\x01", 4);
memcpy(&keyParameterSetBuffer[4+vpsSize+4+spsSize+4], pps, ppsSize);
NSLog(@ "H265 find IDR frame, vpsSize : %zu, spsSize : %zu, ppsSize : %zu",vpsSize,spsSize, ppsSize);
}
encoder->needResetKeyParamSetBuffer = NO;
}
// 是否是关键帧 NO
// 是否是额外数据 YES
// keyParameterSetBuffer 就是编码后的数据
// keyParameterSetBufferSize 就是编码后数据的size
// dtsAfter timeStamp
// 回调数据的地方 (2-1)
NSLog(@"视频编码,加载 I 帧");
}
size_t blockBufferLength;
uint8_t *bufferDataPointer = NULL;
CMBlockBufferGetDataPointer(block, 0, NULL, &blockBufferLength, (char **)&bufferDataPointer);
size_t bufferOffset = 0;
while (bufferOffset < blockBufferLength - kStartCodeLength)
{
uint32_t NALUnitLength = 0;
memcpy(&NALUnitLength, bufferDataPointer+bufferOffset, kStartCodeLength);
NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);
memcpy(bufferDataPointer+bufferOffset, kStartCode, kStartCodeLength);
bufferOffset += kStartCodeLength + NALUnitLength;
}
// 是否是关键帧 isKeyFrame
// 是否是额外数据 NO
// bufferDataPointer 就是编码后的数据
// blockBufferLength 就是编码后数据的size
// dtsAfter timeStamp
// 回调数据的地方 (2-2)
last_dts = dtsAfter;
}
其他参数:
{
VTCompressionSessionRef videoSession;
NSLock *lock;
///是否需要重置
BOOL needResetKeyParamSetBuffer;
///编码方式
CMVideoCodecType encoderType;
}
static const size_t kStartCodeLength = 4;
static const uint8_t kStartCode[] = {0x00, 0x00, 0x00, 0x01};
uint32_t encode_capture_base_time = 0;
Demo地址整理后奉上。
有其他不明白的,可以留言,看到就会回复。
如果喜欢,请帮忙点赞。支持转载,转载请附原文链接。
网友评论