上一篇文章介紹了如何編碼H265
欢瞪,這篇主要介紹如果通過VideoToolBox
解碼H265
愕把,以及如何在只有裸流的情況區(qū)別H265
和H264
次兆;
首先拿到一個(gè)H265
的裸流之后器贩,我們已經(jīng)知道保存的這種裸流格式是Annex-b
格式的纽帖;
解碼的步驟如下:
1.讀取媒體文件找到第一個(gè)
StartCode
(0x00 00 00 01 / 0x00 00 01)抡四;
2.找到第一個(gè)StartCode
后柜蜈,找到緊挨著的第二個(gè)StartCode
,提取NALU Unit
3.根據(jù)H265 Header
的結(jié)構(gòu)解析該NALU
是什么類型 從而找到VPS/SPS/PPS
以及P幀/I幀
4.通過VPS/SPS/PPS
創(chuàng)建編碼器指巡,將第一幀為I幀
送入編碼器淑履,后續(xù)將B/P幀
送入編碼器 得到解碼后的pixelBuffer
以上解析邏輯和關(guān)鍵代碼,如下代碼中均有體現(xiàn)藻雪,覺得不夠直觀的可以直接看demo源碼
;
解析裸流文件
判斷是否是 0x00 00 01
/ 0x00 00 00 01
起始碼秘噪,及起始碼長度
/// 判斷是否是 00 00 00 01 \ 00 00 01
static BOOL isNaluStartCode(unsigned char *data) {
BOOL isStartCode = (memcmp(data, startCode4, 4) == 0) || (memcmp(data, startCode3, 3) == 0);
return isStartCode;
}
/// 獲取startCode 長度
static int getNaluStartCodeLength(unsigned char *data) {
BOOL isStartCode = (memcmp(data, startCode4, 4) == 0) || (memcmp(data, startCode3, 3) == 0);
if (!isStartCode) return 0;
int nalu_startcode_size = 0;
if (memcmp(data, startCode4, 4) == 0) {
nalu_startcode_size = sizeof(startCode4);
} else if (memcmp(data, startCode3, 3) == 0) {
nalu_startcode_size = sizeof(startCode3);
} else {
//do nothing
}
return nalu_startcode_size;
}
定時(shí)器循環(huán)解析文件內(nèi)的 NALU Unit
- (void)tick {
dispatch_sync(_decodeQueue, ^{
packetSize = 0;
if (packetBuffer) {
free(packetBuffer);
packetBuffer = NULL;
}
BOOL isStartCode = isNaluStartCode(_inputBuffer);
unsigned int nalu_startcode_size = getNaluStartCodeLength(_inputBuffer);
if (isStartCode && (_inputSize > nalu_startcode_size)) {
uint8_t *pStart = _inputBuffer + nalu_startcode_size; //pStart 表示 NALU 的起始指針
uint8_t *pEnd = _inputBuffer + _inputSize; //pEnd 表示 NALU 的末尾指針
while (pStart != pEnd) {
BOOL isNextStartCode = isNaluStartCode(pStart);
if (isNextStartCode) {
packetSize = (pStart - _inputBuffer);
packetBuffer = malloc(packetSize);
memcpy(packetBuffer, _inputBuffer, packetSize); //復(fù)制packet內(nèi)容到新的緩沖區(qū)
memmove(_inputBuffer, _inputBuffer + packetSize, _inputSize - packetSize); //把緩沖區(qū)前移
_inputSize -= packetSize;
if (nalu_startcode_size == 3) {
/// 額外處理 startCode == 00 00 01 情況
long newPacketSize = packetSize + 1;
uint8_t *newPacketBuffer = malloc(newPacketSize);
memset(newPacketBuffer, 0, sizeof(newPacketSize));
memcpy(newPacketBuffer + 1, packetBuffer , packetSize);
free(packetBuffer);
packetBuffer = newPacketBuffer;
packetSize = newPacketSize;
}
break;
}
else {
++pStart;
}
}
if ((pStart == pEnd) && (_inputSize > sizeof(startCode4)) && (packetSize == 0) && (packetBuffer == NULL)) {
packetSize = _inputSize;//pStart - _inputBuffer - 3;
packetBuffer = malloc(packetSize);
memcpy(packetBuffer, _inputBuffer, packetSize);
memmove(_inputBuffer, _inputBuffer + packetSize, _inputSize - packetSize); //把緩沖區(qū)前移
_inputSize -= packetSize;
}
}
if (packetBuffer == NULL || packetSize == 0) {
[self endDecode];
return;
}
//2.將packet的前4個(gè)字節(jié)換成大端的長度 (有可能startCode是 00000001 / 000001兩種情況 都需要處理)
uint32_t nalSize = (uint32_t)(packetSize - 4);
uint8_t *pNalSize = (uint8_t*)(&nalSize);
packetBuffer[0] = pNalSize[3];
packetBuffer[1] = pNalSize[2];
packetBuffer[2] = pNalSize[1];
packetBuffer[3] = pNalSize[0];
//3.判斷幀類型(根據(jù)碼流結(jié)構(gòu)可知,startcode后面緊跟著就是碼流的類型)
int nalType = (packetBuffer[4] & 0x7E) >> 1;
switch (nalType) {
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
{
//IDR frame
[self _initDecodeSession];
[self decodePacket];
}
break;
case 0x27:
{
//SEI
}
break;
case 0x20:
{
//vps
if (_vps) { _vps = nil;}
size_t vpsSize = (size_t) packetSize - 4;
uint8_t *vps = malloc(vpsSize);
memcpy(vps, packetBuffer + 4, vpsSize);
_vps = [NSData dataWithBytes:vps length:vpsSize];
free(vps);
}
break;
case 0x21:
{
//sps
if (_sps) { _sps = nil;}
size_t spsSize = (size_t) packetSize - 4;
uint8_t *sps = malloc(spsSize);
memcpy(sps, packetBuffer + 4, spsSize);
_sps = [NSData dataWithBytes:sps length:spsSize];
free(sps);
}
break;
case 0x22:
{
//pps
if (_pps) { _pps = nil; }
size_t ppsSize = (size_t) packetSize - 4;
uint8_t *pps = malloc(ppsSize);
memcpy(pps, packetBuffer + 4, ppsSize);
_pps = [NSData dataWithBytes:pps length:ppsSize];
free(pps);
}
break;
default:
{
// B/P frame
[self decodePacket];
}
break;
}
});
}
獲取VPS/SPS/PPS 創(chuàng)建編碼器
//2.將packet的前4個(gè)字節(jié)換成大端的長度 (有可能startCode是 00000001 / 000001兩種情況 都需要處理)
uint32_t nalSize = (uint32_t)(packetSize - 4);
uint8_t *pNalSize = (uint8_t*)(&nalSize);
packetBuffer[0] = pNalSize[3];
packetBuffer[1] = pNalSize[2];
packetBuffer[2] = pNalSize[1];
packetBuffer[3] = pNalSize[0];
//3.判斷幀類型(根據(jù)碼流結(jié)構(gòu)可知勉耀,startcode后面緊跟著就是碼流的類型)
int nalType = (packetBuffer[4] & 0x7E) >> 1;
switch (nalType) {
case 0x10:
case 0x11:
case 0x12:
case 0x13:
case 0x14:
case 0x15:
{
//IDR frame
[self _initDecodeSession];
[self decodePacket];
}
break;
case 0x27:
{
//SEI
}
break;
case 0x20:
{
//vps
if (_vps) { _vps = nil;}
size_t vpsSize = (size_t) packetSize - 4;
uint8_t *vps = malloc(vpsSize);
memcpy(vps, packetBuffer + 4, vpsSize);
_vps = [NSData dataWithBytes:vps length:vpsSize];
free(vps);
}
break;
case 0x21:
{
//sps
if (_sps) { _sps = nil;}
size_t spsSize = (size_t) packetSize - 4;
uint8_t *sps = malloc(spsSize);
memcpy(sps, packetBuffer + 4, spsSize);
_sps = [NSData dataWithBytes:sps length:spsSize];
free(sps);
}
break;
case 0x22:
{
//pps
if (_pps) { _pps = nil; }
size_t ppsSize = (size_t) packetSize - 4;
uint8_t *pps = malloc(ppsSize);
memcpy(pps, packetBuffer + 4, ppsSize);
_pps = [NSData dataWithBytes:pps length:ppsSize];
free(pps);
}
break;
default:
{
// B/P frame
[self decodePacket];
}
break;
}
});
通過VPS/SPS/PPS
創(chuàng)建H265
類型VideoToolbox
-(void)initVideoToolBox {
if (_decodeSession) {
return;
}
CMFormatDescriptionRef formatDescriptionOut;
const uint8_t * const param[3] = {_vps.bytes,_sps.bytes,_pps.bytes};
const size_t paramSize[3] = {_vps.length,_sps.length,_pps.length};
OSStatus formateStatus =
CMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault, 3, param, paramSize, 4, NULL, &formatDescriptionOut);
_formatDescriptionOut = formatDescriptionOut;
if (formateStatus!=noErr) {
NSLog(@"FormatDescriptionCreate fail");
return;
}
//2. 創(chuàng)建VTDecompressionSessionRef
//確定編碼格式
const void *keys[] = {kCVPixelBufferPixelFormatTypeKey};
uint32_t t = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange;
const void *values[] = {CFNumberCreate(NULL, kCFNumberSInt32Type, &t)};
CFDictionaryRef att = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
VTDecompressionOutputCallbackRecord VTDecompressionOutputCallbackRecord;
VTDecompressionOutputCallbackRecord.decompressionOutputCallback = decodeCompressionOutputCallback;
VTDecompressionOutputCallbackRecord.decompressionOutputRefCon = (__bridge void * _Nullable)(self);
OSStatus sessionStatus = VTDecompressionSessionCreate(NULL,
formatDescriptionOut,
NULL,
att,
&VTDecompressionOutputCallbackRecord,
&_decodeSession);
CFRelease(att);
if (sessionStatus != noErr) {
NSLog(@"SessionCreate fail");
[self endDecode];
}
}
解碼H265類型 NALU
如何辨別裸流是 H265 還是H264
探針的作用是試探當(dāng)前的數(shù)據(jù)流是否是某一協(xié)議/編碼格式指煎,一般方法是進(jìn)行部分預(yù)解析/解碼蹋偏,觀察是否滿足指定協(xié)議/編碼的格式要求
以下代碼參考FFMPEG 源碼
/// 判斷是否是 HEVC
static int hevc_probe(unsigned char *buf, unsigned int length)
{
uint32_t code = -1;
int vps = 0, sps = 0, pps = 0, irap = 0;
int i;
for (i = 0; i < length - 1; i++) {
code = (code << 8) + buf[i];
if ((code & 0xffffff00) == 0x100) {
uint8_t nal2 = buf[i + 1];
int type = (code & 0x7E) >> 1;
if (code & 0x81) // forbidden and reserved zero bits
return 0;
if (nal2 & 0xf8) // reserved zero
return 0;
switch (type) {
case HEVC_NAL_VPS: vps++; break;
case HEVC_NAL_SPS: sps++; break;
case HEVC_NAL_PPS: pps++; break;
case HEVC_NAL_BLA_N_LP:
case HEVC_NAL_BLA_W_LP:
case HEVC_NAL_BLA_W_RADL:
case HEVC_NAL_CRA_NUT:
case HEVC_NAL_IDR_N_LP:
case HEVC_NAL_IDR_W_RADL: irap++; break;
}
}
}
if (vps && sps && pps && irap)
return 1; // 1 more than .mpg
return 0;
}
源碼地址 源碼地址: https://github.com/hunter858/OpenGL_Study/AVFoundation/VideoToolBox-decoderH265