iOS中編碼視頻數據,通常狀況而言一個項目僅須要一個編碼器,不過有時特殊需求可能須要兩個編碼器同時工做.本例中實現了編碼器類.僅經過指定不一樣編碼器的枚舉值就能夠快速生成須要的編碼器,且支持兩個編碼器一塊兒工做.ios
iOS中利用VideoToolBox框架完成視頻硬編碼操做,支持H.264,H.265編碼器.git
軟編碼:使用CPU進行編碼。github
硬編碼:不使用CPU進行編碼,使用顯卡GPU,專用的DSP、FPGA、ASIC芯片等硬件進行編碼。macos
本例經過將編碼後的文件寫成.mov文件, 來測試h264, h265編碼效率, 錄製時間相同,場景基本相同,結果顯示h265僅須要h264一半的內存就能夠完成一樣的畫質.注意,錄製出來的文件只能用ffmpeg相關工具播放.bash
本例中的編碼器類不是單例,由於咱們能夠生成出h264編碼器,h265編碼器,以及讓生成兩個不一樣類型編碼器對象同時工做.這裏指定的寬高幀率須要與相機保持一致. 比特率即播放過程當中平均碼率,是否支持實時編碼,若是支持實時編碼碼率則沒法控制.最後咱們僅僅能夠經過指定編碼器的類型來決定建立h264編碼器仍是h265編碼器.網絡
判斷是否支持hevc編碼器,並非全部的設備都支持h265編碼器,這由硬件決定,可是沒有直接的API去判斷是否支持h265編碼器,在這裏藉助AVAssetExportPresetHEVCHighestQuality
屬性來間接判斷是否支持h265編碼.session
注意: h265編碼的軟件API須要在iOS 11以上的操做系統才能使用. 目前全部流行的iPhone已都支持h264編碼器.數據結構
// You could select h264 / h265 encoder.
self.videoEncoder = [[XDXVideoEncoder alloc] initWithWidth:1280
height:720
fps:30
bitrate:2048
isSupportRealTimeEncode:NO
encoderType:XDXH265Encoder]; // XDXH264Encoder
-(instancetype)initWithWidth:(int)width height:(int)height fps:(int)fps bitrate:(int)bitrate isSupportRealTimeEncode:(BOOL)isSupportRealTimeEncode encoderType:(XDXVideoEncoderType)encoderType {
if (self = [super init]) {
mSession = NULL;
mVideoFile = NULL;
_width = width;
_height = height;
_fps = fps;
_bitrate = bitrate << 10; //convert to bps
_errorCount = 0;
_isSupportEncoder = NO;
_encoderType = encoderType;
_lock = [[NSLock alloc] init];
_isSupportRealTimeEncode = isSupportRealTimeEncode;
_needResetKeyParamSetBuffer = YES;
if (encoderType == XDXH265Encoder) {
if (@available(iOS 11.0, *)) {
if ([[AVAssetExportSession allExportPresets] containsObject:AVAssetExportPresetHEVCHighestQuality]) {
_isSupportEncoder = YES;
}
}
}else if (encoderType == XDXH264Encoder){
_isSupportEncoder = YES;
}
log4cplus_info("Video Encoder:","Init encoder width:%d, height:%d, fps:%d, bitrate:%d, is support encoder:%d, encoder type:H%lu", width, height, fps, bitrate, isSupportRealTimeEncode, (unsigned long)encoderType);
}
return self;
}
複製代碼
初始化一個編碼器分爲如下三個步驟, 首先新建一個VTCompressionSessionRef
引用對象管理編碼器, 而後將編碼器全部屬性賦值給該對象.最後在編碼前預先分配一些資源(即爲要編碼的數據預先分配內存)以便編碼buffer使用.框架
- (void)configureEncoderWithWidth:(int)width height:(int)height {
log4cplus_info("Video Encoder:", "configure encoder with and height for init,with = %d,height = %d",width, height);
if(width == 0 || height == 0) {
log4cplus_error("Video Encoder:", "encoder param can't is null. width:%d, height:%d",width, height);
return;
}
self.width = width;
self.height = height;
mSession = [self configureEncoderWithEncoderType:self.encoderType
callback:EncodeCallBack
width:self.width
height:self.height
fps:self.fps
bitrate:self.bitrate
isSupportRealtimeEncode:self.isSupportRealTimeEncode
iFrameDuration:30
lock:self.lock];
}
- (VTCompressionSessionRef)configureEncoderWithEncoderType:(XDXVideoEncoderType)encoderType callback:(VTCompressionOutputCallback)callback width:(int)width height:(int)height fps:(int)fps bitrate:(int)bitrate isSupportRealtimeEncode:(BOOL)isSupportRealtimeEncode iFrameDuration:(int)iFrameDuration lock:(NSLock *)lock {
log4cplus_info("Video Encoder:","configure encoder width:%d, height:%d, fps:%d, bitrate:%d, is support realtime encode:%d, I frame duration:%d", width, height, fps, bitrate, isSupportRealtimeEncode, iFrameDuration);
[lock lock];
// Create compression session
VTCompressionSessionRef session = [self createCompressionSessionWithEncoderType:encoderType
width:width
height:height
callback:callback];
// Set compresssion property
[self setCompressionSessionPropertyWithSession:session
fps:fps
bitrate:bitrate
isSupportRealtimeEncode:isSupportRealtimeEncode
iFrameDuration:iFrameDuration
EncoderType:encoderType];
// Prepare to encode
OSStatus status = VTCompressionSessionPrepareToEncodeFrames(session);
[lock unlock];
if(status != noErr) {
log4cplus_error("Video Encoder:", "create encoder failed, status: %d",(int)status);
return NULL;
}else {
log4cplus_info("Video Encoder:","create encoder success");
return session;
}
}
複製代碼
VTCompressionSessionRef
對象VTCompressionSessionCreate
: 建立視頻編碼器session, 即管理編碼器上下文的對象.異步
a pixel buffer pool
.VTCompressionSessionEncodeFrame
函數線程保持一致,若是用異步會新建一條線程接收.該參數也可傳NULL不過當且僅當咱們使用VTCompressionSessionEncodeFrameWithOutputHandler
函數做編碼時.VT_EXPORT OSStatus
VTCompressionSessionCreate(
CM_NULLABLE CFAllocatorRef allocator,
int32_t width,
int32_t height,
CMVideoCodecType codecType,
CM_NULLABLE CFDictionaryRef encoderSpecification,
CM_NULLABLE CFDictionaryRef sourceImageBufferAttributes,
CM_NULLABLE CFAllocatorRef compressedDataAllocator,
CM_NULLABLE VTCompressionOutputCallback outputCallback,
void * CM_NULLABLE outputCallbackRefCon,
CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTCompressionSessionRef * CM_NONNULL compressionSessionOut) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
複製代碼
下面是具體用法.注意若是相機採集的分辨率改變,須要銷燬當前編碼器session從新建立.
- (VTCompressionSessionRef)createCompressionSessionWithEncoderType:(XDXVideoEncoderType)encoderType width:(int)width height:(int)height callback:(VTCompressionOutputCallback)callback {
CMVideoCodecType codecType;
if (encoderType == XDXH264Encoder) {
codecType = kCMVideoCodecType_H264;
}else if (encoderType == XDXH265Encoder) {
codecType = kCMVideoCodecType_HEVC;
}else {
return nil;
}
VTCompressionSessionRef session;
OSStatus status = VTCompressionSessionCreate(NULL,
width,
height,
codecType,
NULL,
NULL,
NULL,
callback,
(__bridge void *)self,
&session);
if (status != noErr) {
log4cplus_error("Video Encoder:", "%s: Create session failed:%d",__func__,(int)status);
return nil;
}else {
return session;
}
}
複製代碼
建立好session後,調用VTSessionCopySupportedPropertyDictionary
函數能夠將當前session支持的全部屬性拷貝到指定的字典中,之後在設置屬性前先在字典中查詢是否支持便可.
- (BOOL)isSupportPropertyWithSession:(VTCompressionSessionRef)session key:(CFStringRef)key {
OSStatus status;
static CFDictionaryRef supportedPropertyDictionary;
if (!supportedPropertyDictionary) {
status = VTSessionCopySupportedPropertyDictionary(session, &supportedPropertyDictionary);
if (status != noErr) {
return NO;
}
}
BOOL isSupport = [NSNumber numberWithBool:CFDictionaryContainsKey(supportedPropertyDictionary, key)].intValue;
return isSupport;
}
複製代碼
使用VTSessionSetProperty
函數指定key, value便可設置屬性.
- (OSStatus)setSessionPropertyWithSession:(VTCompressionSessionRef)session key:(CFStringRef)key value:(CFTypeRef)value {
if (value == nil || value == NULL || value == 0x0) {
return noErr;
}
OSStatus status = VTSessionSetProperty(session, key, value);
if (status != noErr) {
log4cplus_error("Video Encoder:", "Set session of %s Failed, status = %d",CFStringGetCStringPtr(key, kCFStringEncodingUTF8),status);
}
return status;
}
複製代碼
kVTCompressionPropertyKey\_MaxKeyFrameInterval
一塊兒設置,而且將強制執行這兩個限制 - 每X幀或每Y秒須要一個關鍵幀,以先到者爲準。// Set compresssion property
[self setCompressionSessionPropertyWithSession:session
fps:fps
bitrate:bitrate
isSupportRealtimeEncode:isSupportRealtimeEncode
iFrameDuration:iFrameDuration
EncoderType:encoderType];
- (void)setCompressionSessionPropertyWithSession:(VTCompressionSessionRef)session fps:(int)fps bitrate:(int)bitrate isSupportRealtimeEncode:(BOOL)isSupportRealtimeEncode iFrameDuration:(int)iFrameDuration EncoderType:(XDXVideoEncoderType)encoderType {
int maxCount = 3;
if (!isSupportRealtimeEncode) {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_MaxFrameDelayCount]) {
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &maxCount);
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_MaxFrameDelayCount value:ref];
CFRelease(ref);
}
}
if(fps) {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_ExpectedFrameRate]) {
int value = fps;
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_ExpectedFrameRate value:ref];
CFRelease(ref);
}
}else {
log4cplus_error("Video Encoder:", "Current fps is 0");
return;
}
if(bitrate) {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_AverageBitRate]) {
int value = bitrate << 10;
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_AverageBitRate value:ref];
CFRelease(ref);
}
}else {
log4cplus_error("Video Encoder:", "Current bitrate is 0");
return;
}
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_RealTime]) {
log4cplus_info("Video Encoder:", "use realTimeEncoder");
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_RealTime value:isSupportRealtimeEncode ? kCFBooleanTrue : kCFBooleanFalse];
}
// Ban B frame.
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_AllowFrameReordering]) {
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_AllowFrameReordering value:kCFBooleanFalse];
}
if (encoderType == XDXH264Encoder) {
if (isSupportRealtimeEncode) {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel]) {
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel value:kVTProfileLevel_H264_Main_AutoLevel];
}
}else {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel]) {
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel value:kVTProfileLevel_H264_Baseline_AutoLevel];
}
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_H264EntropyMode]) {
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_H264EntropyMode value:kVTH264EntropyMode_CAVLC];
}
}
}else if (encoderType == XDXH265Encoder) {
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel]) {
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_ProfileLevel value:kVTProfileLevel_HEVC_Main_AutoLevel];
}
}
if([self isSupportPropertyWithSession:session key:kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration]) {
int value = iFrameDuration;
CFNumberRef ref = CFNumberCreate(NULL, kCFNumberSInt32Type, &value);
[self setSessionPropertyWithSession:session key:kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration value:ref];
CFRelease(ref);
}
log4cplus_info("Video Encoder:", "The compression session max frame delay count = %d, expected frame rate = %d, average bitrate = %d, is support realtime encode = %d, I frame duration = %d",maxCount, fps, bitrate, isSupportRealtimeEncode,iFrameDuration);
}
複製代碼
您能夠選擇調用此函數,以便爲編碼器提供在開始編碼幀以前執行任何須要資源分配的機會。此可選調用可用於爲編碼器提供在開始編碼幀以前分配所需資源的機會。若是未調用此方法,則將在第一個VTCompressionSessionEncodeFrame調用上分配任何須要的資源。額外調用此函數將不起做用。
// Prepare to encode
OSStatus status = VTCompressionSessionPrepareToEncodeFrames(session);
[lock unlock];
if(status != noErr) {
log4cplus_error("Video Encoder:", "create encoder failed, status: %d",(int)status);
return NULL;
}else {
log4cplus_info("Video Encoder:","create encoder success");
return session;
}
複製代碼
執行到這裏,初始化編碼器的工做已經作完,接下來咱們須要將視頻幀數據進行編碼. 本例中使用AVCaptureSession採集視頻幀以傳給編碼器編碼.
注意,由於編碼線程與建立,銷燬編碼器過程屬於異步操做,因此須要加鎖.
首先咱們取第一幀視頻數據爲基準點,取系統當前時間,做爲編碼第一幀數據的基準時間. 此操做主要用於後期的音視頻同步,本例中不做過多說明,另外,時間戳同步生成機制也不像本例中這麼簡單.能夠自行制定生成規則.
判斷當前編碼的視頻幀中的時間戳是否大於前一幀, 由於視頻是嚴格按時間戳排序播放的,因此時間戳應該是一直遞增的,可是考慮到傳給編碼器的可能不是一個視頻源,好比一開始是攝像頭採集的,後面換成從網絡流解碼的視頻原始數據,此時時間戳一定不一樣步,若是強行將其傳給編碼器,則畫面會出現卡頓.
VT_EXPORT OSStatus
VTCompressionSessionEncodeFrame(
CM_NONNULL VTCompressionSessionRef session,
CM_NONNULL CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime duration, // may be kCMTimeInvalid
CM_NULLABLE CFDictionaryRef frameProperties,
void * CM_NULLABLE sourceFrameRefcon,
VTEncodeInfoFlags * CM_NULLABLE infoFlagsOut ) API_AVAILABLE(macosx(10.8), ios(8.0), tvos(10.2));
複製代碼
-(void)startEncodeWithBuffer:(CMSampleBufferRef)sampleBuffer session:(VTCompressionSessionRef)session isNeedFreeBuffer:(BOOL)isNeedFreeBuffer isDrop:(BOOL)isDrop needForceInsertKeyFrame:(BOOL)needForceInsertKeyFrame lock:(NSLock *)lock {
[lock lock];
if(session == NULL) {
log4cplus_error("Video Encoder:", "%s,session is empty",__func__);
[self handleEncodeFailedWithIsNeedFreeBuffer:isNeedFreeBuffer sampleBuffer:sampleBuffer];
return;
}
//the first frame must be iframe then create the reference timeStamp;
static BOOL isFirstFrame = YES;
if(isFirstFrame && g_capture_base_time == 0) {
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
g_capture_base_time = CMTimeGetSeconds(pts);// system absolutly time(s)
// g_capture_base_time = g_tvustartcaptureTime - (ntp_time_offset/1000);
isFirstFrame = NO;
log4cplus_error("Video Encoder:","start capture time = %u",g_capture_base_time);
}
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CMTime presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
// Switch different source data will show mosaic because timestamp not sync.
static int64_t lastPts = 0;
int64_t currentPts = (int64_t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) * 1000);
if (currentPts - lastPts < 0) {
log4cplus_error("Video Encoder:","Switch different source data the timestamp < last timestamp, currentPts = %lld, lastPts = %lld, duration = %lld",currentPts, lastPts, currentPts - lastPts);
[self handleEncodeFailedWithIsNeedFreeBuffer:isNeedFreeBuffer sampleBuffer:sampleBuffer];
return;
}
lastPts = currentPts;
OSStatus status = noErr;
NSDictionary *properties = @{(__bridge NSString *)kVTEncodeFrameOptionKey_ForceKeyFrame:@(needForceInsertKeyFrame)};
status = VTCompressionSessionEncodeFrame(session,
imageBuffer,
presentationTimeStamp,
kCMTimeInvalid,
(__bridge CFDictionaryRef)properties,
NULL,
NULL);
if(status != noErr) {
log4cplus_error("Video Encoder:", "encode frame failed");
[self handleEncodeFailedWithIsNeedFreeBuffer:isNeedFreeBuffer sampleBuffer:sampleBuffer];
}
[lock unlock];
if (isNeedFreeBuffer) {
if (sampleBuffer != NULL) {
CFRelease(sampleBuffer);
log4cplus_debug("Video Encoder:", "release the sample buffer");
}
}
}
複製代碼
如下關於碼流部分的代碼若是看不懂,建議必定要先看下標題推薦的連接,裏面是瞭解編解碼器的基礎知識以及iOS中VideoToolbox框架中數據結構的解析.
若是status中有錯誤信息,表示編碼失敗.能夠作一些特殊處理.
咱們須要爲編碼後的數據填充時間戳,這裏咱們能夠根據本身的規則制定一套時間戳生成規則,咱們這裏僅僅用最簡單的偏移量,即用第一幀視頻數據編碼前系統時間爲基準點,而後每幀編碼後的時間取採集到的時間戳減去基準時間獲得的值做爲編碼後數據的時間戳.
原始視頻數據通過編碼後分爲I幀,B幀,P幀.iOS端通常不開啓B幀,B幀須要從新排序,咱們拿到編碼後的數據首先經過kCMSampleAttachmentKey_DependsOnOthers
屬性判斷是否爲I幀,若是是I幀,要從I幀中讀取NALU頭部關鍵信息,即vps,sps,pps. vps僅在h265編碼器中才有.沒有這些編碼的視頻沒法在另外一端播放,也沒法錄製成文件.
從I幀中能夠讀取到vps,sps,pps數據具體的內容.若是是h264編碼器調用CMVideoFormatDescriptionGetH264ParameterSetAtIndex
函數,若是是h265編碼器調用CMVideoFormatDescriptionGetHEVCParameterSetAtIndex
函數,其中第二個參數的索引值0,1,2就分別表明這些數據的索引值.
找到這些數據後咱們須要將它們拼接起來,由於它們是獨立的NALU,即以0x00, 0x00, 0x00, 0x01
做爲隔斷符以區分sps,pps.
因此,咱們按照規則將拿到的vps,sps,pps中間分別以00 00 00 01
做爲隔斷符以拼接成一個完整連續的buffer.本例以寫文件爲例,咱們首先要將NALU頭信息寫入文件,也就是將I幀先寫進去,由於I幀表明一個完整圖像,P幀須要依賴I幀才能產生圖像,因此咱們文件的讀取開頭必須是一個I幀數據.
一幀圖片通過 H.264 編碼器以後,就被編碼爲一個或多個片(slice),而裝載着這些片(slice)的載體,就是 NALU 了。
注意:片(slice)的概念不一樣與幀(frame),幀(frame)是用做描述一張圖片的,一幀(frame)對應一張圖片,而片(slice),是 H.264 中提出的新概念,是經過編碼圖片後切分經過高效的方式整合出來的概念,一張圖片至少有一個或多個片(slice)。片(slice)都是又 NALU 裝載並進行網絡傳輸的,可是這並不表明 NALU 內就必定是切片,這是充分沒必要要條件,由於 NALU 還有可能裝載着其餘用做描述視頻的信息。
首先經過CMBlockBufferGetDataPointer
獲取視頻幀數據.該幀表示一段H264/H265碼流,其中可能包含多個NALU,咱們須要找出每一個NALU並用00 00 00 01
做爲隔斷符. 即while循環就是尋找碼流中的NALU,由於裸流中不含有start code.咱們要將start code拷貝進去.
CFSwapInt32BigToHost
: 從h264編碼的數據的大端模式(字節序)轉系統端模式
static void EncodeCallBack(void *outputCallbackRefCon,void *souceFrameRefCon,OSStatus status,VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {
XDXVideoEncoder *encoder = (__bridge XDXVideoEncoder*)outputCallbackRefCon;
if(status != noErr) {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil];
NSLog(@"H264: vtCallBack failed with %@", error);
log4cplus_error("TVUEncoder", "encode frame failured! %s" ,error.debugDescription.UTF8String);
return;
}
if (!encoder.isSupportEncoder) {
return;
}
CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sampleBuffer);
CMTime pts = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
CMTime dts = CMSampleBufferGetDecodeTimeStamp(sampleBuffer);
// Use our define time. (the time is used to sync audio and video)
int64_t ptsAfter = (int64_t)((CMTimeGetSeconds(pts) - g_capture_base_time) * 1000);
int64_t dtsAfter = (int64_t)((CMTimeGetSeconds(dts) - g_capture_base_time) * 1000);
dtsAfter = ptsAfter;
/*sometimes relative dts is zero, provide a workground to restore 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;
// Note: the NALU header will not change if video resolution not change.
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 == XDXH264Encoder) {
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);
log4cplus_info("Video Encoder:", "H264 find IDR frame, spsSize : %zu, ppsSize : %zu",spsSize, ppsSize);
}else if (encoder.encoderType == XDXH265Encoder) {
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);
log4cplus_info("Video Encoder:", "H265 find IDR frame, vpsSize : %zu, spsSize : %zu, ppsSize : %zu",vpsSize,spsSize, ppsSize);
}
encoder.needResetKeyParamSetBuffer = NO;
}
if (encoder.isNeedRecord) {
if (encoder->mVideoFile == NULL) {
[encoder initSaveVideoFile];
log4cplus_info("Video Encoder:", "Start video record.");
}
fwrite(keyParameterSetBuffer, 1, keyParameterSetBufferSize, encoder->mVideoFile);
}
log4cplus_info("Video Encoder:", "Load a I frame.");
}
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;
}
if (encoder.isNeedRecord && encoder->mVideoFile != NULL) {
fwrite(bufferDataPointer, 1, blockBufferLength, encoder->mVideoFile);
}else {
if (encoder->mVideoFile != NULL) {
fclose(encoder->mVideoFile);
encoder->mVideoFile = NULL;
log4cplus_info("Video Encoder:", "Stop video record.");
}
}
// log4cplus_debug("Video Encoder:","H265 encoded video:%lld, size:%lu, interval:%lld", dtsAfter,blockBufferLength, dtsAfter - last_dts);
last_dts = dtsAfter;
}
複製代碼