對(duì)視頻數(shù)據(jù)進(jìn)行H264編碼參考http://www.itdecent.cn/p/07c7ad30b70a
接受到的數(shù)據(jù)前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
幀類型有:
NAL_SLICE = 1 非關(guān)鍵幀
NAL_SLICE_DPA = 2
NAL_SLICE_DPB = 3
NAL_SLICE_DPC =4
NAL_SLICE_IDR =5 關(guān)鍵幀
NAL_SEI = 6
NAL_SPS = 7 SPS幀
NAL_PPS = 8 PPS幀
NAL_AUD = 9
NAL_FILLER = 12
解密的時(shí)候需要先獲取sps 和 pps ,這兩個(gè)數(shù)據(jù)是不需要解碼的(之前壓縮的時(shí)候也沒(méi)編碼)。
獲取到sps和pps就可以創(chuàng)建解碼會(huì)話了,我們可以通過(guò)
來(lái)創(chuàng)建會(huì)話,其工作流如下:
- 創(chuàng)建一個(gè)會(huì)話通過(guò)
- 根據(jù)需要配置會(huì)話屬性通過(guò)調(diào)用
- 解碼視頻幀通過(guò)使用
4.當(dāng)你結(jié)束解碼的時(shí)候調(diào)用
VTDecompressionSessionCreate需要兩個(gè)重要參數(shù)
,
和回調(diào)函數(shù)
-
:是關(guān)于源視頻的描述,也就是對(duì)應(yīng)壓縮方式的解壓參數(shù)配置,將獲取的sps,pps放入其中。通過(guò)
來(lái)創(chuàng)建
-
:用于設(shè)置像素緩沖器的要求,如攝像頭的輸出數(shù)據(jù)格式,視頻源的分辨率,是否開啟零拷貝通道等。
case 0x07: //sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
memcpy(_sps, &frame[4], _spsSize);
break;
case 0x08: //pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
memcpy(_pps, &frame[4], _ppsSize);
break;
初始化解碼器
/**解碼會(huì)話*/
@property (nonatomic) VTDecompressionSessionRef decodeSesion;
- (BOOL)initDecoder {
if (_decodeSesion) return true;
//定義一個(gè)指針數(shù)組 ,里面存放的是sps pps的指針變量
const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
//定義一個(gè)數(shù)組 ,里面存放的是sps pps的容量大小
const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
int naluHeaderLen = 4;
/**
根據(jù)sps pps設(shè)置解碼參數(shù)
param kCFAllocatorDefault 分配器
param 2 參數(shù)個(gè)數(shù)
param parameterSetPointers 參數(shù)集指針
param parameterSetSizes 參數(shù)集大小
param naluHeaderLen nalu nalu start code 的長(zhǎng)度 4
param _decodeDesc 解碼器描述
return 狀態(tài)
*/
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);
if (status != noErr) {
NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
return false;
}
/*
解碼參數(shù):
* kCVPixelBufferPixelFormatTypeKey:攝像頭的輸出數(shù)據(jù)格式
kCVPixelBufferPixelFormatTypeKey,已測(cè)可用值為
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,即420v
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,即420f
kCVPixelFormatType_32BGRA,iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
YUV420一般用于標(biāo)清視頻,YUV422用于高清視頻,這里的限制讓人感到意外。但是,在相同條件下,YUV420計(jì)算耗時(shí)和傳輸壓力比YUV422都小。
* kCVPixelBufferWidthKey/kCVPixelBufferHeightKey: 視頻源的分辨率 width*height
* kCVPixelBufferOpenGLCompatibilityKey : 它允許在 OpenGL 的上下文中直接繪制解碼后的圖像,而不是從總線和 CPU 之間復(fù)制數(shù)據(jù)。這有時(shí)候被稱為零拷貝通道,因?yàn)樵诶L制過(guò)程中沒(méi)有解碼的圖像被拷貝.
*/
NSDictionary *destinationPixBufferAttrs =
@{
(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //iOS上 nv12(uvuv排布) 而不是nv21(vuvu排布)
(id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
(id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
(id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
};
//解碼回調(diào)設(shè)置
/*
VTDecompressionOutputCallbackRecord 是一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,它帶有一個(gè)指針 (decompressionOutputCallback),指向幀解壓完成后的回調(diào)方法。你需要提供可以找到這個(gè)回調(diào)方法的實(shí)例 (decompressionOutputRefCon)。VTDecompressionOutputCallback 回調(diào)方法包括七個(gè)參數(shù):
參數(shù)1: 回調(diào)的引用
參數(shù)2: 幀的引用
參數(shù)3: 一個(gè)狀態(tài)標(biāo)識(shí) (包含未定義的代碼)
參數(shù)4: 指示同步/異步解碼,或者解碼器是否打算丟幀的標(biāo)識(shí)
參數(shù)5: 實(shí)際圖像的緩沖
參數(shù)6: 出現(xiàn)的時(shí)間戳
參數(shù)7: 出現(xiàn)的持續(xù)時(shí)間
*/
VTDecompressionOutputCallbackRecord callbackRecord;
callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
//創(chuàng)建session
/*!
@function VTDecompressionSessionCreate
@abstract 創(chuàng)建用于解壓縮視頻幀的會(huì)話。
@discussion 解壓后的幀將通過(guò)調(diào)用OutputCallback發(fā)出
@param allocator 內(nèi)存的會(huì)話。通過(guò)使用默認(rèn)的kCFAllocatorDefault的分配器。
@param videoFormatDescription 描述源視頻幀
@param videoDecoderSpecification 指定必須使用的特定視頻解碼器.NULL
@param destinationImageBufferAttributes 描述源像素緩沖區(qū)的要求 NULL
@param outputCallback 使用已解壓縮的幀調(diào)用的回調(diào)
@param decompressionSessionOut 指向一個(gè)變量以接收新的解壓會(huì)話
*/
status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decodeSesion);
//判斷一下status
if (status != noErr) {
NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
return false;
}
//設(shè)置解碼會(huì)話屬性(實(shí)時(shí)編碼)
status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", (int)status);
return true;
}
初始化完會(huì)話后,要處理外部傳入的數(shù)據(jù)進(jìn)行解碼處理,這里要注意傳送給解碼會(huì)話的數(shù)據(jù)要重新包裝成CMBlockBufferRef-》CMSampleBufferRef然后通過(guò)VTDecompressionSessionDecodeFrame進(jìn)行解碼。解碼成功后會(huì)回調(diào)之前設(shè)置的VTDecompressionOutputCallbackRecord
-(void)decodeNaluData:(NSData *)frame{
dispatch_async(_decodeQueue, ^{
uint8_t *nalu =(uint8_t *) frame.bytes;
[self decodeNaluData:nalu size:(uint32_t)frame.length];
});
}
- (void)decodeNaluData:(uint8_t *)frame size:(uint32_t)size {
//數(shù)據(jù)類型:frame的前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
// 第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
int type = (frame[4] & 0x1F);
// 將NALU的開始碼轉(zhuǎn)為4字節(jié)大端NALU的長(zhǎng)度信息
uint32_t naluSize = size - 4;
uint8_t *pNaluSize = (uint8_t *)(&naluSize);
CVPixelBufferRef pixelBuffer = NULL;
frame[0] = *(pNaluSize + 3);
frame[1] = *(pNaluSize + 2);
frame[2] = *(pNaluSize + 1);
frame[3] = *(pNaluSize);
//第一次解析時(shí): 初始化解碼器initDecoder
/*
關(guān)鍵幀/其他幀數(shù)據(jù): 調(diào)用[self decode:frame withSize:size] 方法
sps/pps數(shù)據(jù):則將sps/pps數(shù)據(jù)賦值到_sps/_pps中.
*/
switch (type) {
case 0x05: //關(guān)鍵幀
if ([self initDecoder]) {
pixelBuffer= [self decode:frame withSize:size];
}
break;
case 0x06:
//NSLog(@"SEI");//增強(qiáng)信息
break;
case 0x07: //sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
memcpy(_sps, &frame[4], _spsSize);
break;
case 0x08: //pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
memcpy(_pps, &frame[4], _ppsSize);
break;
default: //其他幀(1-5)
if ([self initDecoder]) {
pixelBuffer = [self decode:frame withSize:size];
}
break;
}
}
/**解碼函數(shù)(private)*/
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
CVPixelBufferRef outputPixelBuffer = NULL;
CMBlockBufferRef blockBuffer = NULL;
CMBlockBufferFlags flag0 = 0;
//創(chuàng)建blockBuffer
/*!
參數(shù)1: structureAllocator kCFAllocatorDefault
參數(shù)2: memoryBlock frame
參數(shù)3: frame size
參數(shù)4: blockAllocator: Pass NULL
參數(shù)5: customBlockSource Pass NULL
參數(shù)6: offsetToData 數(shù)據(jù)偏移
參數(shù)7: dataLength 數(shù)據(jù)長(zhǎng)度
參數(shù)8: flags 功能和控制標(biāo)志
參數(shù)9: newBBufOut blockBuffer地址,不能為空
*/
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
if (status != kCMBlockBufferNoErr) {
NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
return outputPixelBuffer;
}
//創(chuàng)建sampleBuffer
/*
參數(shù)1: allocator 分配器,使用默認(rèn)內(nèi)存分配, kCFAllocatorDefault
參數(shù)2: blockBuffer.需要編碼的數(shù)據(jù)blockBuffer.不能為NULL
參數(shù)3: formatDescription,視頻輸出格式
參數(shù)4: numSamples.CMSampleBuffer 個(gè)數(shù).
參數(shù)5: numSampleTimingEntries 必須為0,1,numSamples
參數(shù)6: sampleTimingArray. 數(shù)組.為空
參數(shù)7: numSampleSizeEntries 默認(rèn)為1
參數(shù)8: sampleSizeArray
參數(shù)9: sampleBuffer對(duì)象
*/
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {frameSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
if (status != noErr || !sampleBuffer) {
NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
CFRelease(blockBuffer);
return outputPixelBuffer;
}
VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
//異步解碼
VTDecodeInfoFlags infoFlag = kVTDecodeInfo_Asynchronous;
//解碼數(shù)據(jù)
/*
參數(shù)1: 解碼session
參數(shù)2: 源數(shù)據(jù) 包含一個(gè)或多個(gè)視頻幀的CMsampleBuffer
參數(shù)3: 解碼標(biāo)志
參數(shù)4: 解碼后數(shù)據(jù)outputPixelBuffer
參數(shù)5: 同步/異步解碼標(biāo)識(shí)
*/
status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, flag1, &outputPixelBuffer, &infoFlag);
if (status == kVTInvalidSessionErr) {
NSLog(@"Video hard decode InvalidSessionErr status =%d", (int)status);
} else if (status == kVTVideoDecoderBadDataErr) {
NSLog(@"Video hard decode BadData status =%d", (int)status);
} else if (status != noErr) {
NSLog(@"Video hard decode failed status =%d", (int)status);
}
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
return outputPixelBuffer;
// return outputPixelBuffer;
}
解碼成功后我們?cè)诨卣{(diào)函數(shù) VTDecompressionOutputCallbackRecord 】中處理解碼后的數(shù)據(jù)
/*
1.參數(shù)1:decompressionOutputRefCon
這個(gè)是之前傳人方法的參數(shù)也就是self,這個(gè)是c方法所以沒(méi)有self參數(shù)需要自己傳。
2.參數(shù)2:sourceFrameRefCon
回調(diào)函數(shù)會(huì)引用你設(shè)置的這個(gè)幀的參考值
3.status
noErr if decompression was successful; an error code if decompression was not successful.
4.infoFlags
Information about the decode operation.
指向一個(gè)VTEncodeInfoFlags來(lái)接受一個(gè)編碼操作.如果使用異步運(yùn)行,kVTEncodeInfo_Asynchronous被設(shè)置;同步運(yùn)行,kVTEncodeInfo_FrameDropped被設(shè)置;設(shè)置NULL為不想接受這個(gè)信息.
5.imageBuffer
解壓的幀如果不成功是null
6.presentationTimeStamp
幀的表示時(shí)間戳
7.presentationDuration
幀的表示持續(xù)時(shí)間
*/
void videoDecompressionOutputCallback(
void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ){
if (status != noErr) {
NSLog(@"Video hard decode callback error status=%d", (int)status);
return;
}
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
*outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
//獲取self
SQVideoDecoder *decoder = (__bridge SQVideoDecoder *)(decompressionOutputRefCon);
//調(diào)用回調(diào)隊(duì)列
dispatch_async(decoder.callbackQueue, ^{
//將解碼后的數(shù)據(jù)給decoder代理.viewController
[decoder.delegate videoDecodeCallback:imageBuffer];
//釋放數(shù)據(jù)
CVPixelBufferRelease(imageBuffer);
});
//解碼后的數(shù)據(jù)sourceFrameRefCon -> CVPixelBufferRef
}
全部源碼
//
// SQVideoDecoder.h
// CPDemo
//
// Created by Sem on 2020/8/10.
// Copyright ? 2020 SEM. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SQAVConfig.h"
NS_ASSUME_NONNULL_BEGIN
@protocol SQVideoDecoderDelegate <NSObject>
//解碼后H264數(shù)據(jù)回調(diào)
- (void)videoDecodeCallback:(CVPixelBufferRef)imageBuffer;
@end
@interface SQVideoDecoder : NSObject
@property (nonatomic, strong) SQVideoConfig *config;
@property (nonatomic, weak) id<SQVideoDecoderDelegate> delegate;
/**初始化解碼器**/
- (instancetype)initWithConfig:(SQVideoConfig*)config;
-(void)decodeNaluData:(NSData *)frame;
@end
NS_ASSUME_NONNULL_END
//
// SQVideoDecoder.m
// CPDemo
//
// Created by Sem on 2020/8/10.
// Copyright ? 2020 SEM. All rights reserved.
//
#import "SQVideoDecoder.h"
#import <AVFoundation/AVFoundation.h>
#import <VideoToolbox/VideoToolbox.h>
@interface SQVideoDecoder ()
@property (nonatomic, strong) dispatch_queue_t decodeQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
/**解碼會(huì)話*/
@property (nonatomic) VTDecompressionSessionRef decodeSesion;
@end
@implementation SQVideoDecoder{
uint8_t *_sps;
NSUInteger _spsSize;
uint8_t *_pps;
NSUInteger _ppsSize;
CMVideoFormatDescriptionRef _decodeDesc;
}
- (instancetype)initWithConfig:(SQVideoConfig*)config{
self = [super init];
if(self){
_config = config;
_decodeQueue = dispatch_queue_create("h264 hard decode queue", DISPATCH_QUEUE_SERIAL);
_callbackQueue =dispatch_queue_create("h264 hard decode callback queue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
/*初始化解碼器**/
/*初始化解碼器**/
-(void)decodeNaluData:(NSData *)frame{
dispatch_async(_decodeQueue, ^{
uint8_t *nalu =(uint8_t *) frame.bytes;
[self decodeNaluData:nalu size:(uint32_t)frame.length];
});
}
- (void)decodeNaluData:(uint8_t *)frame size:(uint32_t)size {
//數(shù)據(jù)類型:frame的前4個(gè)字節(jié)是NALU數(shù)據(jù)的開始碼,也就是00 00 00 01,
// 第5個(gè)字節(jié)是表示數(shù)據(jù)類型,轉(zhuǎn)為10進(jìn)制后,7是sps, 8是pps, 5是IDR(I幀)信息
int type = (frame[4] & 0x1F);
// 將NALU的開始碼轉(zhuǎn)為4字節(jié)大端NALU的長(zhǎng)度信息
uint32_t naluSize = size - 4;
uint8_t *pNaluSize = (uint8_t *)(&naluSize);
CVPixelBufferRef pixelBuffer = NULL;
frame[0] = *(pNaluSize + 3);
frame[1] = *(pNaluSize + 2);
frame[2] = *(pNaluSize + 1);
frame[3] = *(pNaluSize);
//第一次解析時(shí): 初始化解碼器initDecoder
/*
關(guān)鍵幀/其他幀數(shù)據(jù): 調(diào)用[self decode:frame withSize:size] 方法
sps/pps數(shù)據(jù):則將sps/pps數(shù)據(jù)賦值到_sps/_pps中.
*/
switch (type) {
case 0x05: //關(guān)鍵幀
if ([self initDecoder]) {
pixelBuffer= [self decode:frame withSize:size];
}
break;
case 0x06:
//NSLog(@"SEI");//增強(qiáng)信息
break;
case 0x07: //sps
_spsSize = naluSize;
_sps = malloc(_spsSize);
memcpy(_sps, &frame[4], _spsSize);
break;
case 0x08: //pps
_ppsSize = naluSize;
_pps = malloc(_ppsSize);
memcpy(_pps, &frame[4], _ppsSize);
break;
default: //其他幀(1-5)
if ([self initDecoder]) {
pixelBuffer = [self decode:frame withSize:size];
}
break;
}
}
/**解碼函數(shù)(private)*/
- (CVPixelBufferRef)decode:(uint8_t *)frame withSize:(uint32_t)frameSize {
CVPixelBufferRef outputPixelBuffer = NULL;
CMBlockBufferRef blockBuffer = NULL;
CMBlockBufferFlags flag0 = 0;
//創(chuàng)建blockBuffer
/*!
參數(shù)1: structureAllocator kCFAllocatorDefault
參數(shù)2: memoryBlock frame
參數(shù)3: frame size
參數(shù)4: blockAllocator: Pass NULL
參數(shù)5: customBlockSource Pass NULL
參數(shù)6: offsetToData 數(shù)據(jù)偏移
參數(shù)7: dataLength 數(shù)據(jù)長(zhǎng)度
參數(shù)8: flags 功能和控制標(biāo)志
參數(shù)9: newBBufOut blockBuffer地址,不能為空
*/
OSStatus status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, frame, frameSize, kCFAllocatorNull, NULL, 0, frameSize, flag0, &blockBuffer);
if (status != kCMBlockBufferNoErr) {
NSLog(@"Video hard decode create blockBuffer error code=%d", (int)status);
return outputPixelBuffer;
}
//創(chuàng)建sampleBuffer
/*
參數(shù)1: allocator 分配器,使用默認(rèn)內(nèi)存分配, kCFAllocatorDefault
參數(shù)2: blockBuffer.需要編碼的數(shù)據(jù)blockBuffer.不能為NULL
參數(shù)3: formatDescription,視頻輸出格式
參數(shù)4: numSamples.CMSampleBuffer 個(gè)數(shù).
參數(shù)5: numSampleTimingEntries 必須為0,1,numSamples
參數(shù)6: sampleTimingArray. 數(shù)組.為空
參數(shù)7: numSampleSizeEntries 默認(rèn)為1
參數(shù)8: sampleSizeArray
參數(shù)9: sampleBuffer對(duì)象
*/
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {frameSize};
status = CMSampleBufferCreateReady(kCFAllocatorDefault, blockBuffer, _decodeDesc, 1, 0, NULL, 1, sampleSizeArray, &sampleBuffer);
if (status != noErr || !sampleBuffer) {
NSLog(@"Video hard decode create sampleBuffer failed status=%d", (int)status);
CFRelease(blockBuffer);
return outputPixelBuffer;
}
VTDecodeFrameFlags flag1 = kVTDecodeFrame_1xRealTimePlayback;
//異步解碼
VTDecodeInfoFlags infoFlag = kVTDecodeInfo_Asynchronous;
//解碼數(shù)據(jù)
/*
參數(shù)1: 解碼session
參數(shù)2: 源數(shù)據(jù) 包含一個(gè)或多個(gè)視頻幀的CMsampleBuffer
參數(shù)3: 解碼標(biāo)志
參數(shù)4: 解碼后數(shù)據(jù)outputPixelBuffer
參數(shù)5: 同步/異步解碼標(biāo)識(shí)
*/
status = VTDecompressionSessionDecodeFrame(_decodeSesion, sampleBuffer, flag1, &outputPixelBuffer, &infoFlag);
if (status == kVTInvalidSessionErr) {
NSLog(@"Video hard decode InvalidSessionErr status =%d", (int)status);
} else if (status == kVTVideoDecoderBadDataErr) {
NSLog(@"Video hard decode BadData status =%d", (int)status);
} else if (status != noErr) {
NSLog(@"Video hard decode failed status =%d", (int)status);
}
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
return outputPixelBuffer;
// return outputPixelBuffer;
}
- (BOOL)initDecoder {
if (_decodeSesion) return true;
//定義一個(gè)指針數(shù)組 ,里面存放的是sps pps的指針變量
const uint8_t * const parameterSetPointers[2] = {_sps, _pps};
//定義一個(gè)數(shù)組 ,里面存放的是sps pps的容量大小
const size_t parameterSetSizes[2] = {_spsSize, _ppsSize};
int naluHeaderLen = 4;
/**
根據(jù)sps pps設(shè)置解碼參數(shù)
param kCFAllocatorDefault 分配器
param 2 參數(shù)個(gè)數(shù)
param parameterSetPointers 參數(shù)集指針
param parameterSetSizes 參數(shù)集大小
param naluHeaderLen nalu nalu start code 的長(zhǎng)度 4
param _decodeDesc 解碼器描述
return 狀態(tài)
*/
OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, naluHeaderLen, &_decodeDesc);
if (status != noErr) {
NSLog(@"Video hard DecodeSession create H264ParameterSets(sps, pps) failed status= %d", (int)status);
return false;
}
/*
解碼參數(shù):
* kCVPixelBufferPixelFormatTypeKey:攝像頭的輸出數(shù)據(jù)格式
kCVPixelBufferPixelFormatTypeKey,已測(cè)可用值為
kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,即420v
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,即420f
kCVPixelFormatType_32BGRA,iOS在內(nèi)部進(jìn)行YUV至BGRA格式轉(zhuǎn)換
YUV420一般用于標(biāo)清視頻,YUV422用于高清視頻,這里的限制讓人感到意外。但是,在相同條件下,YUV420計(jì)算耗時(shí)和傳輸壓力比YUV422都小。
* kCVPixelBufferWidthKey/kCVPixelBufferHeightKey: 視頻源的分辨率 width*height
* kCVPixelBufferOpenGLCompatibilityKey : 它允許在 OpenGL 的上下文中直接繪制解碼后的圖像,而不是從總線和 CPU 之間復(fù)制數(shù)據(jù)。這有時(shí)候被稱為零拷貝通道,因?yàn)樵诶L制過(guò)程中沒(méi)有解碼的圖像被拷貝.
*/
NSDictionary *destinationPixBufferAttrs =
@{
(id)kCVPixelBufferPixelFormatTypeKey: [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange], //iOS上 nv12(uvuv排布) 而不是nv21(vuvu排布)
(id)kCVPixelBufferWidthKey: [NSNumber numberWithInteger:_config.width],
(id)kCVPixelBufferHeightKey: [NSNumber numberWithInteger:_config.height],
(id)kCVPixelBufferOpenGLCompatibilityKey: [NSNumber numberWithBool:true]
};
//解碼回調(diào)設(shè)置
/*
VTDecompressionOutputCallbackRecord 是一個(gè)簡(jiǎn)單的結(jié)構(gòu)體,它帶有一個(gè)指針 (decompressionOutputCallback),指向幀解壓完成后的回調(diào)方法。你需要提供可以找到這個(gè)回調(diào)方法的實(shí)例 (decompressionOutputRefCon)。VTDecompressionOutputCallback 回調(diào)方法包括七個(gè)參數(shù):
參數(shù)1: 回調(diào)的引用
參數(shù)2: 幀的引用
參數(shù)3: 一個(gè)狀態(tài)標(biāo)識(shí) (包含未定義的代碼)
參數(shù)4: 指示同步/異步解碼,或者解碼器是否打算丟幀的標(biāo)識(shí)
參數(shù)5: 實(shí)際圖像的緩沖/Users/sem/Desktop/CPDemo/CPDemo/AudioEncoder/SQAudioEncoder.m
參數(shù)6: 出現(xiàn)的時(shí)間戳
參數(shù)7: 出現(xiàn)的持續(xù)時(shí)間
*/
VTDecompressionOutputCallbackRecord callbackRecord;
callbackRecord.decompressionOutputCallback = videoDecompressionOutputCallback;
callbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
//創(chuàng)建session
/*!
@function VTDecompressionSessionCreate
@abstract 創(chuàng)建用于解壓縮視頻幀的會(huì)話。
@discussion 解壓后的幀將通過(guò)調(diào)用OutputCallback發(fā)出
@param allocator 內(nèi)存的會(huì)話。通過(guò)使用默認(rèn)的kCFAllocatorDefault的分配器。
@param videoFormatDescription 描述源視頻幀
@param videoDecoderSpecification 指定必須使用的特定視頻解碼器.NULL
@param destinationImageBufferAttributes 描述源像素緩沖區(qū)的要求 NULL
@param outputCallback 使用已解壓縮的幀調(diào)用的回調(diào)
@param decompressionSessionOut 指向一個(gè)變量以接收新的解壓會(huì)話
*/
status = VTDecompressionSessionCreate(kCFAllocatorDefault, _decodeDesc, NULL, (__bridge CFDictionaryRef _Nullable)(destinationPixBufferAttrs), &callbackRecord, &_decodeSesion);
//判斷一下status
if (status != noErr) {
NSLog(@"Video hard DecodeSession create failed status= %d", (int)status);
return false;
}
//設(shè)置解碼會(huì)話屬性(實(shí)時(shí)編碼)
status = VTSessionSetProperty(_decodeSesion, kVTDecompressionPropertyKey_RealTime,kCFBooleanTrue);
NSLog(@"Vidoe hard decodeSession set property RealTime status = %d", (int)status);
return true;
}
/*
1.參數(shù)1:decompressionOutputRefCon
這個(gè)是之前傳人方法的參數(shù)也就是self,這個(gè)是c方法所以沒(méi)有self參數(shù)需要自己傳。
2.參數(shù)2:sourceFrameRefCon
回調(diào)函數(shù)會(huì)引用你設(shè)置的這個(gè)幀的參考值
3.status
noErr if decompression was successful; an error code if decompression was not successful.
4.infoFlags
Information about the decode operation.
指向一個(gè)VTEncodeInfoFlags來(lái)接受一個(gè)編碼操作.如果使用異步運(yùn)行,kVTEncodeInfo_Asynchronous被設(shè)置;同步運(yùn)行,kVTEncodeInfo_FrameDropped被設(shè)置;設(shè)置NULL為不想接受這個(gè)信息.
5.imageBuffer
解壓的幀如果不成功是null
6.presentationTimeStamp
幀的表示時(shí)間戳
7.presentationDuration
幀的表示持續(xù)時(shí)間
*/
void videoDecompressionOutputCallback(
void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ){
if (status != noErr) {
NSLog(@"Video hard decode callback error status=%d", (int)status);
return;
}
CVPixelBufferRef *outputPixelBuffer = (CVPixelBufferRef *)sourceFrameRefCon;
*outputPixelBuffer = CVPixelBufferRetain(imageBuffer);
//獲取self
SQVideoDecoder *decoder = (__bridge SQVideoDecoder *)(decompressionOutputRefCon);
//調(diào)用回調(diào)隊(duì)列
dispatch_async(decoder.callbackQueue, ^{
//將解碼后的數(shù)據(jù)給decoder代理.viewController
[decoder.delegate videoDecodeCallback:imageBuffer];
//釋放數(shù)據(jù)
CVPixelBufferRelease(imageBuffer);
});
//解碼后的數(shù)據(jù)sourceFrameRefCon -> CVPixelBufferRef
}
//銷毀
- (void)dealloc
{
if (_decodeSesion) {
VTDecompressionSessionInvalidate(_decodeSesion);
CFRelease(_decodeSesion);
_decodeSesion = NULL;
}
}
@end
@interface SQVideoConfig : NSObject
@property (nonatomic, assign) NSInteger width;//可選,系統(tǒng)支持的分辨率,采集分辨率的寬
@property (nonatomic, assign) NSInteger height;//可選,系統(tǒng)支持的分辨率,采集分辨率的高
@property (nonatomic, assign) NSInteger bitrate;//自由設(shè)置
@property (nonatomic, assign) NSInteger fps;//自由設(shè)置 25
+ (instancetype)defaultConifg;
@end
@implementation SQVideoConfig
+ (instancetype)defaultConifg {
return [[SQVideoConfig alloc] init];
}
- (instancetype)init
{
self = [super init];
if (self) {
self.width = 480;
self.height = 640;
self.bitrate = 640*1000;
self.fps = 25;
}
return self;
}
@end