下面我們結(jié)合代碼重點(diǎn)介紹硬解碼:
-(void)viewDidLoad {
[super viewDidLoad];
//1.創(chuàng)建NSInputStream對(duì)象
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"abc.h264" ofType:nil];
self.inputStream = [NSInputStream inputStreamWithFileAtPath:filePath];
//2.創(chuàng)建定時(shí)器
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(readFrame)];
//2.1.設(shè)置執(zhí)行頻率(自我理解,2毫秒執(zhí)行一次,即一秒鐘執(zhí)行30次)
displayLink.frameInterval = 2;
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[displayLink setPaused:YES];
self.displayLink = displayLink;
//3.創(chuàng)建一個(gè)線程, 用于讀取被編碼后的數(shù)據(jù), 解碼數(shù)據(jù)
self.queue = dispatch_get_global_queue(0, 0);
//4.創(chuàng)建展示的layer
AAPLEAGLLayer *layer = [[AAPLEAGLLayer alloc] initWithFrame:self.view.bounds];
[self.view.layer addSublayer:layer];
self.previewLayer = layer;
}
//讀取被編碼的數(shù)據(jù)
- (void)readFrame{
dispatch_sync(self.queue, ^{
//1.讀取一個(gè)NALU數(shù)據(jù)
[self readPacket];
//2.判斷讀取的數(shù)據(jù)是否為空,如果數(shù)據(jù)未NULL,就直接結(jié)束
if (packetBuffer == NULL || packetSize == 0){
[self.displayLink setPaused:YES];
[self.displayLink invalidate];
[self.inputStream close];
//不是直接return就行了嗎,然后繼續(xù)向內(nèi)存中讀取信息
return;
}
//3.根據(jù)NALU的不同類型做出不同處理
//sps pps I幀 其他幀
//3.1.內(nèi)存地址由系統(tǒng)端模式轉(zhuǎn)化為大端模式
uint32_t nalSize = (uint32_t)(packetSize - 4);
uint32_t *pNalSize = (uint32_t *)packetBuffer;
*pNalSize = CFSwapInt32HostToBig(nalSize);
//3.2.取得NALU單元的第五個(gè)字節(jié), 然后只取后五位
int nalType = packetBuffer[4] & 0x1F;
CVImageBufferRef imageBuffer = NULL;
switch (nalType) {
case 0x07:
//開辟地址記錄sps
mSPSSize = packetSize - 4;
pSPS = malloc(mSPSSize);
memcpy(pSPS, packetBuffer + 4, mSPSSize);
break;
case 0x08:
//開辟地址記錄pps
mPPSSize = packetSize - 4;
pPPS = malloc(mPPSSize);
memcpy(pPPS, packetBuffer + 4, mPPSSize);
break;
case 0x05:
//I幀 根據(jù)SPS和PPS直接初始化Decompressionsession, 每一個(gè) Decompressionsession都是不同的, 然后解碼
[self initDecompressionsession];
imageBuffer = [self decodeFrame];
break;
//其他幀 直接解碼
default:
imageBuffer = [self decodeFrame];
break;
}
//4.將解碼數(shù)據(jù)進(jìn)行展示
if (imageBuffer != NULL){
dispatch_async(dispatch_get_main_queue(), ^{
self.previewLayer.pixelBuffer = imageBuffer;
CFRelease(imageBuffer);
});
}
});
}
//讀取一個(gè)NALU數(shù)據(jù)
- (void)readPacket{
//1.把之前的packetBuffer清空
if (packetSize != 0 || packetBuffer != NULL){
packetSize = 0;
free(packetBuffer);
packetBuffer = NULL;
}
//2.開始讀取一定長(zhǎng)度的數(shù)據(jù)(如果長(zhǎng)度沒有超過最大長(zhǎng)度,并且輸入流里面有信息)
if (leftLength < maxReadLength && self.inputStream.hasBytesAvailable){
leftLength += [self.inputStream read:dataPointer + leftLength maxLength:maxReadLength - leftLength];
}
//3.從dataPointer內(nèi)存中取出一個(gè)NALU單元到packetBuffer
uint8_t *pStart = dataPointer + 4;
uint8_t *pEnd = dataPointer + leftLength;
//是NALU單元的前提是,頭部和startCode吻合,并且除了頭標(biāo)志外是有內(nèi)容的
if (memcmp(startCode, dataPointer, 4) == 0 && leftLength > 4){
//當(dāng)起始和末尾相差不等于4的時(shí)候,就繼續(xù)向后尋找NALU的末尾
while (pStart != pEnd - 3){
//獲取NALU的長(zhǎng)度(通過對(duì)比數(shù)據(jù)是否為0x00 00 00 01)
if (memcmp(pStart, startCode, 4) == 0){
//如果和NALU開頭標(biāo)志相同了,代表找到一個(gè)完整的NALU了,此時(shí)的pStart為此NALU的結(jié)束地址的后一個(gè)地址
packetSize = pStart - dataPointer;
packetBuffer = malloc(packetSize);
memcpy(packetBuffer, dataPointer, packetSize);
//把dataPointer中信息向前移動(dòng)
memmove(dataPointer, dataPointer + packetSize, leftLength - packetSize);
leftLength -= packetSize;
break;
}else{
pStart++;
}
}
}
}
//根據(jù)SPS和PPS直接初始化VTDecompressionsession, 每一個(gè)VTDecompressionsession都是不同的
- (void)initDecompressionsession{
//1.創(chuàng)建CMVideoFormatDescriptionRef
const uint8_t *parameterSetPointers[2] = {pSPS, pPPS};
const size_t parameterSetSizes[2] = {mSPSSize, mPPSSize};
CMVideoFormatDescriptionCreateFromH264ParameterSets(NULL, 2, parameterSetPointers, parameterSetSizes, 4, &_format);
//2.
NSDictionary *attr = @{(__bridge NSString *)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)};
//3.解碼之后的回調(diào)函數(shù)
VTDecompressionOutputCallbackRecord callbackRecord;
callbackRecord.decompressionOutputCallback = decompressionCallBack;
//4.創(chuàng)建VTDecompressionsession
VTDecompressionSessionCreate(NULL, self.format, NULL, (__bridge CFDictionaryRef _Nullable)(attr), &callbackRecord, &_session);
}
//解碼:VTDecompressionSessionDecodeFrame
- (CVPixelBufferRef)decodeFrame{
//1.創(chuàng)建CMBlockBufferRef
CMBlockBufferRef blockBuffer = NULL;
CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, packetBuffer, packetSize, kCFAllocatorNull, NULL, 0, packetSize, 0, &blockBuffer);
//2.創(chuàng)建CMSampleBufferRef
CMSampleBufferRef sampleBuffer = NULL;
const size_t sampleSizeArray[] = {packetSize};
CMSampleBufferCreateReady(NULL, blockBuffer, self.format, 0, 0, NULL, 0, sampleSizeArray, &sampleBuffer);
//3.開始解碼
CVPixelBufferRef outputPixelBuffer = NULL;
VTDecompressionSessionDecodeFrame(self.session, sampleBuffer, 0, &outputPixelBuffer, NULL);
//4.釋放資源
CFRelease(sampleBuffer);
CFRelease(blockBuffer);
return outputPixelBuffer;
}
- (IBAction)play:(id)sender {
//1.對(duì)于讀取信息常量進(jìn)行賦值
maxReadLength = 1280 * 720;
leftLength = 0;
dataPointer = malloc(maxReadLength);
//2.打開輸入流
[self.inputStream open];
//3.開啟定時(shí)器來讀取信息
[self.displayLink setPaused:NO];
}
//解碼之后的回調(diào)函數(shù)
void decompressionCallBack(
void * CM_NULLABLE decompressionOutputRefCon,
void * CM_NULLABLE sourceFrameRefCon,
OSStatus status,
VTDecodeInfoFlags infoFlags,
CM_NULLABLE CVImageBufferRef imageBuffer,
CMTime presentationTimeStamp,
CMTime presentationDuration ){
CVImageBufferRef *pointer = sourceFrameRefCon;
*pointer = CVBufferRetain(imageBuffer);
}
解碼要點(diǎn):
1.創(chuàng)建定時(shí)器CADisplayLink
2.創(chuàng)建NSInputStream對(duì)象實(shí)時(shí)讀取NALU單元
3.根據(jù)NALU單元的不同類型做出不同處理
3.1.如果是sps或pps,記錄
3.2.如果是I幀,就根據(jù)SPS和PPS初始化VTDecompressionsession,然后解碼
3.3.如果是其它幀,直接解碼
4.展示解碼后的幀