c语言 直接运行硬编码,iOS硬编码实现

iOS硬编码实现

前言

在上一篇中,我们已经知道iOS编码的一些概念知识,从现在开始,我们可以正式对采集到的视频进行编码

这里我们重点介绍硬编码的使用方式,也就是VideoToolBox框架的使用

编码的流程:采集--> 获取到视频帧--> 对视频帧进行编码 --> 获取到视频帧信息 --> 将编码后的数据以NALU方式写入到文件

视频采集

视频采集我们已经在前面进行了介绍和学习,所有这里就直接贴代码,只是我对采集过程进行了一些简单的封装

f965aa150a2c

视频硬件编码

初始化压缩编码会话(VTCompressionSessionRef)

在VideoToolbox框架的使用过程中,基本都是C语言函数

初始化后通过VTSessionSetProperty设置对象属性

编码方式:H.264编码

帧率:每秒钟多少帧画面

码率:单位时间内保存的数据量

关键帧(GOPsize)间隔:多少帧为一个GOP

参数参考:

f965aa150a2c

准备编码

代码如下:

- (void)setupVideoSession {

// 1.用于记录当前是第几帧数据(画面帧数非常多)

self.frameID = 0;

// 2.录制视频的宽度&高度

int width = [UIScreen mainScreen].bounds.size.width;

int height = [UIScreen mainScreen].bounds.size.height;

// 3.创建CompressionSession对象,该对象用于对画面进行编码

// kCMVideoCodecType_H264 : 表示使用h.264进行编码

// didCompressH264 : 当一次编码结束会在该函数进行回调,可以在该函数中将数据,写入文件中

VTCompressionSessionCreate(NULL, width, height, kCMVideoCodecType_H264, NULL, NULL, NULL, didCompressH264, (__bridge void *)(self), &_compressionSession);

// 4.设置实时编码输出(直播必然是实时输出,否则会有延迟)

VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);

// 5.设置期望帧率(每秒多少帧,如果帧率过低,会造成画面卡顿)

int fps = 30;

CFNumberRef fpsRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &fps);

VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_ExpectedFrameRate, fpsRef);

// 6.设置码率(码率: 编码效率, 码率越高,则画面越清晰, 如果码率较低会引起马赛克 --> 码率高有利于还原原始画面,但是也不利于传输)

int bitRate = 800*1024;

CFNumberRef bitRateRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &bitRate);

VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitRateRef);

NSArray *limit = @[@(bitRate * 1.5/8), @(1)];

VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_DataRateLimits, (__bridge CFArrayRef)limit);

// 7.设置关键帧(GOPsize)间隔

int frameInterval = 30;

CFNumberRef frameIntervalRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &frameInterval);

VTSessionSetProperty(self.compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, frameIntervalRef);

// 8.基本设置结束, 准备进行编码

VTCompressionSessionPrepareToEncodeFrames(self.compressionSession);

}

将输入的帧进行编码

将CMSampleBufferRef转成CVImageBufferRef

开始对CVImageBufferRef进行编码

- (void)encodeSampleBuffer:(CMSampleBufferRef)sampleBuffer {

// 1.将sampleBuffer转成imageBuffer

CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);

// 2.根据当前的帧数,创建CMTime的时间

CMTime presentationTimeStamp = CMTimeMake(self.frameID++, 1000);

VTEncodeInfoFlags flags;

// 3.开始编码该帧数据

OSStatus statusCode = VTCompressionSessionEncodeFrame(self.compressionSession,

imageBuffer,

presentationTimeStamp,

kCMTimeInvalid,

NULL, (__bridge void * _Nullable)(self), &flags);

if (statusCode == noErr) {

NSLog(@"H264: VTCompressionSessionEncodeFrame Success");

}

}

当编码成功后,将编码后的码流写入文件

编码成功后会回调之前输入的函数

1> 先判断是否是关键帧:

如果是关键帧,则需要在写入关键帧之前,先写入PPS、SPS的NALU

取出PPS、SPS数据,并且封装成NALU单元,写入文件

2> 将I帧、P帧、B帧分别封装成NALU单元写入文件

写入后,数据存储方式:

f965aa150a2c

代码如下:

// 编码完成回调

void didCompressH264(void *outputCallbackRefCon, void *sourceFrameRefCon, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) {

// 1.判断状态是否等于没有错误

if (status != noErr) {

return;

}

// 2.根据传入的参数获取对象

VideoEncoder* encoder = (__bridge VideoEncoder*)outputCallbackRefCon;

// 3.判断是否是关键帧

bool isKeyframe = !CFDictionaryContainsKey( (CFArrayGetValueAtIndex(CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true), 0)), kCMSampleAttachmentKey_NotSync);

// 判断当前帧是否为关键帧

// 获取sps & pps数据

if (isKeyframe)

{

// 获取编码后的信息(存储于CMFormatDescriptionRef中)

CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);

// 获取SPS信息

size_t sparameterSetSize, sparameterSetCount;

const uint8_t *sparameterSet;

CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 0, &sparameterSet, &sparameterSetSize, &sparameterSetCount, 0 );

// 获取PPS信息

size_t pparameterSetSize, pparameterSetCount;

const uint8_t *pparameterSet;

CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, 1, &pparameterSet, &pparameterSetSize, &pparameterSetCount, 0 );

// 装sps/pps转成NSData,以方便写入文件

NSData *sps = [NSData dataWithBytes:sparameterSet length:sparameterSetSize];

NSData *pps = [NSData dataWithBytes:pparameterSet length:pparameterSetSize];

// 写入文件

[encoder gotSpsPps:sps pps:pps];

}

// 获取数据块

CMBlockBufferRef dataBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);

size_t length, totalLength;

char *dataPointer;

OSStatus statusCodeRet = CMBlockBufferGetDataPointer(dataBuffer, 0, &length, &totalLength, &dataPointer);

if (statusCodeRet == noErr) {

size_t bufferOffset = 0;

static const int AVCCHeaderLength = 4; // 返回的nalu数据前四个字节不是0001的startcode,而是大端模式的帧长度length

// 循环获取nalu数据

while (bufferOffset < totalLength - AVCCHeaderLength) {

uint32_t NALUnitLength = 0;

// Read the NAL unit length

memcpy(&NALUnitLength, dataPointer + bufferOffset, AVCCHeaderLength);

// 从大端转系统端

NALUnitLength = CFSwapInt32BigToHost(NALUnitLength);

NSData* data = [[NSData alloc] initWithBytes:(dataPointer + bufferOffset + AVCCHeaderLength) length:NALUnitLength];

[encoder gotEncodedData:data isKeyFrame:isKeyframe];

// 移动到写一个块,转成NALU单元

// Move to the next NAL unit in the block buffer

bufferOffset += AVCCHeaderLength + NALUnitLength;

}

}

}

- (void)gotSpsPps:(NSData*)sps pps:(NSData*)pps

{

// 1.拼接NALU的header

const char bytes[] = "\x00\x00\x00\x01";

size_t length = (sizeof bytes) - 1;

NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];

// 2.将NALU的头&NALU的体写入文件

[self.fileHandle writeData:ByteHeader];

[self.fileHandle writeData:sps];

[self.fileHandle writeData:ByteHeader];

[self.fileHandle writeData:pps];

}

- (void)gotEncodedData:(NSData*)data isKeyFrame:(BOOL)isKeyFrame

{

NSLog(@"gotEncodedData %d", (int)[data length]);

if (self.fileHandle != NULL)

{

const char bytes[] = "\x00\x00\x00\x01";

size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'

NSData *ByteHeader = [NSData dataWithBytes:bytes length:length];

[self.fileHandle writeData:ByteHeader];

[self.fileHandle writeData:data];

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值