美文网首页互联网架构
对捕获的视频数据进行H264编码

对捕获的视频数据进行H264编码

作者: HelloBinary | 来源:发表于2020-08-19 15:02 被阅读0次

    H264编码是什么?

    H264编码其实就是一种视频压缩标准在更低的比特率的情况下依然能够提供良好视频质量的标准。特点:
    1)网络亲和性,即可适用于各种传输网络
    2)高的视频压缩比,当初提出的指标是比 H.263,MPEG-4,约为它们的 2 倍,现在都已基 实现;

    H.264 其编解码流程

    • 帧间和帧内预测(Estimation)
    • 变换(Transform)和反变换
    • 量化(Quantization)和反量化
    • 环路滤波(Loop Filter)
    • 熵编码(Entropy Coding)

    原理

    视频是什么

    视频组成:

    • 图像(Image)
    • 音频(Audio)
    • 元信息(Metadata)

    我们的h264 是对Image进行压缩。主要采用了以下几种方法对视频数据进行压缩。包括:

    • 帧内预测压缩,解决的是空域数据冗余问题。
    • 帧间预测压缩(运动估计与补偿),解决的是时域数据冗徐问题。
    • 整数离散余弦变换(DCT),将空间上的相关性变为频域上无关的数据然后进行量化。
    • CABAC压缩。

    经过压缩就形成三个帧:I帧,P帧和B帧:
    I帧:关键帧,采用帧内压缩技术。
    P帧:向前参考帧,在压缩时,只参考前面已经处理的帧。采用帧间压缩技术。
    B帧:双向参考帧,在压缩时,它即参考前而的帧,又参考它后面的帧。采用帧间压缩技术

    除了I/P/B帧外,还有图像序列GOP。

    在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧.所以在码率不变的前提下,GOP值越大,P、B帧的数量会越多,平均每个I、P、B帧所占用的字节数就越多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。

    image.png
    这部分的压缩我们只需要使用官方提供的VideoToolbox工具类通过进行参数配置,就可以简单进行编码工作。
    为避免主线程堵塞,编码需异步执行,这里主要申明两个同步队列,数据需要按顺序接收,一个是编码用的,一个是编码完成后进行回调的队列, 是一个管理传入视频数据压缩的会话对象。
    是一个配置类配置一些编码的信息。
    @interface SQVideoConfig : NSObject
    @property (nonatomic, assign) NSInteger width;//可选,系统支持的分辨率,采集分辨率的宽
    @property (nonatomic, assign) NSInteger height;//可选,系统支持的分辨率,采集分辨率的高
    @property (nonatomic, assign) NSInteger bitrate;//自由设置
    @property (nonatomic, assign) NSInteger fps;//自由设置 25
    + (instancetype)defaultConifg;
    @end
    #import "SQAVConfig.h"
    
    @implementation SQAudioConfig
    + (instancetype)defaultConifg {
        return  [[SQAudioConfig alloc] init];
    }
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.bitrate = 96000;
            self.channelCount = 1;
            self.sampleSize = 16;
            self.sampleRate = 44100;
        }
        return self;
    }
    @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
    
    
    //编码队列
    @property (nonatomic, strong) dispatch_queue_t encodeQueue;
    //回调队列
    @property (nonatomic, strong) dispatch_queue_t callbackQueue;
    /**编码会话*/
    @property (nonatomic) VTCompressionSessionRef encodeSesion;
    

    \color{blue}{VTCompressionSession}工作流程如下:

    • 使用 \color{red}{VTCompressionSessionCreate} 来创建一个会话
    • 通过 \color{red}{VTCompressionSessionCodeFrame} 来设置相关编码的参数 如码率,FPS 等
    • 如果对帧进行编码使用 \color{red}{VTCompressionSessionEncodeFrame} 并在会话的\color{red}{VTCompressionOutputCallback}中接收压缩视频帧。
    • 要强制完成某些或所有挂起的帧,请调用\color{red}{VTCompressionSessionCompleteFrames}
    • 完成压缩会话后,调用\color{red}{VTCompressionSessionInvalidate}使其无效,并调用CFRelease释放其内存。
    - (instancetype)initWithConfig:(SQVideoConfig*)config{
        self = [super init];
        if(self){
            _config = config;
            _encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);
            _callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);
            
            //创建编码会话
    /*
                参数1:allocator 分配器,使用null 就是用默认的
                参数2: width 视频帧的像素宽度
                参数3: height 视频帧的像素高度
                参数4: codecType 编解码器类型 这里使用 H264
                参数5:encoderSpecification 特殊的视频编码器 null就是VideoToolbox自己选择一种编码器。
                参数6:sourceImageBufferAttributes 源像素缓冲区如果不希望VideoToolbox为您创建一个,请传递NULL
                参数7:compressedDataAllocator用于压缩数据的分配器。传递NULL以使用默认分配器
                参数8: outputCallback要用压缩帧调用的回调
                参数9:outputCallbackRefCon 客户端为输出回调定义的引用值 回调是c函数不是oc方法,所以没有默认的self参数
                参数10:compressionSessionOut 要创建的会话闯入地址值。
             */
            OSStatus status  = VTCompressionSessionCreate(NULL, (int32_t)config.width, (int32_t)config.height, kCMVideoCodecType_H264, NULL, NULL, NULL, VideoEncodeCallback, (__bridge void *_Nullable)self, &_encodeSesion);
            if(status!=noErr){
                NSLog(@"VTCompressionSession create failed. status=%d", (int)status);
                return self;
            }
            //指示是否建议视频编码器实时执行压缩
            status  =  VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
            NSLog(@"VTSessionSetProperty: set RealTime return: %d", (int)status);
            //指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由于b帧带来的延时
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
            NSLog(@"VTSessionSetProperty: set profile return: %d", (int)status);
            //设置码率均值(比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。注意,比特率设置只在定时时有效)
            CFNumberRef bit = (__bridge CFNumberRef)@(_config.bitrate);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_AverageBitRate, bit);
            NSLog(@"VTSessionSetProperty: set AverageBitRate return: %d", (int)status);
            //码率限制(只在定时时起作用)*待确认
            CFArrayRef limits = (__bridge CFArrayRef)@[@(_config.bitrate / 4), @(_config.bitrate * 4)];
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);
            NSLog(@"VTSessionSetProperty: set DataRateLimits return: %d", (int)status);
            //设置关键帧间隔(GOPSize)GOP太大图像会模糊
            CFNumberRef maxKeyFrameInterval = (__bridge CFNumberRef)@(_config.fps * 2);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, maxKeyFrameInterval);
            NSLog(@"VTSessionSetProperty: set MaxKeyFrameInterval return: %d", (int)status);
            //设置fps(预期)
            CFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(_config.fps);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);
            NSLog(@"VTSessionSetProperty: set ExpectedFrameRate return: %d", (int)status);
            //准备编码
            status = VTCompressionSessionPrepareToEncodeFrames(_encodeSesion);
            NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);
        }
        return self;
    }
    

    创建和配置好编码会话后,我们设备捕获到的sampleBuffer 数据 进行H264硬编码.(捕获数据参考https://www.jianshu.com/p/5cbb8750a63f)

    /**编码*/
    -(void)encodeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer{
        CFRetain(sampleBuffer);
        dispatch_async(_encodeQueue, ^{
            CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
    ////此帧的表示时间戳,将附加到示例缓冲区。传递给会话的每个演示时间戳必须大于前一个。
            self->frameID++;
            CMTime timeStamp = CMTimeMake(self->frameID, 1000);
            //持续时间没有相关信息就kCMTimeInvalid
            CMTime duration = kCMTimeInvalid;
            VTEncodeInfoFlags flags;
            OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);
            if (status != noErr) {
                NSLog(@"VTCompression: encode failed: status=%d",(int)status);
            }
            CFRelease(sampleBuffer);
        });
    }
    

    VideoToolbox基本数据结构:

    1、CVPixelBuffer:编码前和解码后的图像数据结构;

    2、CMTime:媒体时间戳相关。时间以64-bit/32-bit的形式出现;

    3、CMBlockBuffer:编码后,结果图像的数据结构;

    4、CMVideoFormatDescription:图像存储方式,编解码器等格式描述;

    5、CMSampleBuffer:存放编解码前后的视频图像的容器数据结构。
    对于视频数据的编解码都必须通过CMSampleBuffer 来处理。
    下图为H264解码前后数据结构示意图:


    CMSampleBuffer

    现在数据已经交给编码器进行处理,编码器将会在回调函数,\color{red}{VTCompressionOutputCallback}中将编码好的数据放在\color{red}{CMSampleBufferRef}中回传给我们。我们利用\color{red}{CMSampleBufferRef}获得\color{red}{CMBlockBufferRef} 然后通过CMSampleBufferGetDataBuffer方法获取到编码好的NALU数据。

    NALU数据

    H264在提出视频分片压缩策略的同时,也提出了网络分包发送策略。其详细分包发送策略如下:


    image.png

    H.264原始码流(裸流)是由一个接一个NALU组成,它的功能分为两层,VCL(视频编码层)和 NAL(网络提取层);

    1. VCL:包括核心压缩引擎和块,宏块和片的语法级别定义,设计目标是尽可能地独立于网络进行高效的编码;
    2. NAL:负责将VCL产生的比特字符串适配到各种各样的网络和多元环境中,覆盖了所有片级以上的语法级别。
      具体可以参考NALU的数据组成

    对于我们开发者来说只要记住:

    1. 一个图像序列的组成:SPS+PPS+SEI+一个I帧+若干个P帧。SPS、PPS、SEI、一个I帧、一个P帧都可以称为一个NALU

    2. NALU结构:开始码+NALU头+NALU数据
      (开始码: 00 00 00 01分隔符)NALU数据为编码器编出来的图像信息或图像数据

    3. 在h.264中NALU头是8bit,在h.265中NALU是16bit

    4. h.264:
      (1)第1位禁止位,值为1表示语法出错
      (2)第2~3位为参考级别 (重要性)
      (3)第4~8为是nal单元类型

    5. 序列参数集 (sps帧)

    6. 图像参数集( pps帧)

    7.pps 和sps 帧作为对于该视屏流的描述信息存在视频流的前面两帧,只需要在开始时添加一次就好了,通过CMVideoFormatDescriptionGetH264ParameterSetAtIndex 来设置。

      // startCode 长度 4
    const Byte startCode[] = "\x00\x00\x00\x01";
    void VideoEncodeCallback(
    void * CM_NULLABLE outputCallbackRefCon,
    void * CM_NULLABLE sourceFrameRefCon,
    OSStatus status,
    VTEncodeInfoFlags infoFlags,
    CM_NULLABLE CMSampleBufferRef sampleBuffer ){
        if(status != noErr){
            NSLog(@"VideoEncodeCallback: encode error, status = %d", (int)status);
            return;
        }
        if(!CMSampleBufferDataIsReady(sampleBuffer)){
            NSLog(@"VideoEncodeCallback: data is not ready");
            return;
        }
        SQVideoEncoder *encoder = (__bridge SQVideoEncoder *)(outputCallbackRefCon);
        BOOL keyFrame =NO;
        CFArrayRef attachArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
        keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachArray, 0), kCMSampleAttachmentKey_NotSync);//(注意取反符号)
        if(keyFrame && !encoder->hasSpsPps){
            size_t spsSize, spsCount;
            size_t ppsSize, ppsCount;
            const uint8_t *spsData, *ppsData;
            CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
            OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0);
            OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0);
            //判断sps/pps获取成功
                   if (status1 == noErr & status2 == noErr) {
                       
                       NSLog(@"VideoEncodeCallback: get sps, pps success");
                       encoder->hasSpsPps = true;
                       //sps data
                       NSMutableData *sps = [NSMutableData dataWithCapacity:4 + spsSize];
                       [sps appendBytes:startCode length:4];
                       [sps appendBytes:spsData length:spsSize];
                       //pps data
                       NSMutableData *pps = [NSMutableData dataWithCapacity:4 + ppsSize];
                       [pps appendBytes:startCode length:4];
                       [pps appendBytes:ppsData length:ppsSize];
                       
                       dispatch_async(encoder.callbackQueue, ^{
                           //回调方法传递sps/pps
                           [encoder.delegate videoEncodeCallbacksps:sps pps:pps];
                       });
                       
                   } else {
                       NSLog(@"VideoEncodeCallback: get sps/pps failed spsStatus=%d, ppsStatus=%d", (int)status1, (int)status2);
                   }
        }
        //获取NALU数据
        size_t lengthAtOffset, totalLength;
        char *dataPoint;
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);
        if (error != kCMBlockBufferNoErr) {
            NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
            return;
        }
        size_t offet = 0;
        const int lengthInfoSize =4 ;
        while (offet < totalLength - lengthInfoSize) {
            uint32_t naluLength = 0;
            memcpy(&naluLength, dataPoint+offet, lengthInfoSize);
            naluLength = CFSwapInt32BigToHost(naluLength);
            NSMutableData *data = [NSMutableData dataWithCapacity:4 + naluLength];
            [data appendBytes:startCode length:4];
            [data appendBytes:dataPoint+offet+lengthInfoSize length:naluLength];
            dispatch_async(encoder.callbackQueue, ^{
                [encoder.delegate videoEncodeCallback:data];
            });
            offet +=naluLength +lengthInfoSize;
        }
    }
    

    最后释放资源

    - (void)dealloc
    {
        if (_encodeSesion) {
            VTCompressionSessionCompleteFrames(_encodeSesion, kCMTimeInvalid);
            VTCompressionSessionInvalidate(_encodeSesion);
            
            CFRelease(_encodeSesion);
            _encodeSesion = NULL;
        }
        
    }
    

    完整代码

    @interface SQVideoConfig : NSObject
    @property (nonatomic, assign) NSInteger width;//可选,系统支持的分辨率,采集分辨率的宽
    @property (nonatomic, assign) NSInteger height;//可选,系统支持的分辨率,采集分辨率的高
    @property (nonatomic, assign) NSInteger bitrate;//自由设置
    @property (nonatomic, assign) NSInteger fps;//自由设置 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
    
    //
    //  SQVideoEncoder.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
    /**h264编码回调代理*/
    @protocol SQVideoEncoderDelegate <NSObject>
    //Video-H264数据编码完成回调
    - (void)videoEncodeCallback:(NSData *)h264Data;
    //Video-SPS&PPS数据编码回调
    - (void)videoEncodeCallbacksps:(NSData *)sps pps:(NSData *)pps;
    @end
    @interface SQVideoEncoder : NSObject
    @property(nonatomic,strong)SQVideoConfig *config;
    @property(nonatomic,weak)id<SQVideoEncoderDelegate>delegate;
    
    - (instancetype)initWithConfig:(SQVideoConfig*)config;
    /**编码*/
    -(void)encodeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer;
    @end
    
    NS_ASSUME_NONNULL_END
    
    
    //
    //  SQVideoEncoder.m
    //  CPDemo
    //
    //  Created by Sem on 2020/8/10.
    //  Copyright © 2020 SEM. All rights reserved.
    //
    
    #import "SQVideoEncoder.h"
    #import <VideoToolbox/VideoToolbox.h>
    
    @interface SQVideoEncoder ()
    //编码队列
    @property (nonatomic, strong) dispatch_queue_t encodeQueue;
    //回调队列
    @property (nonatomic, strong) dispatch_queue_t callbackQueue;
    /**编码会话*/
    @property (nonatomic) VTCompressionSessionRef encodeSesion;
    
    @end
    @implementation SQVideoEncoder{
        long frameID;   //帧的递增序标识
        BOOL hasSpsPps;//判断是否已经获取到pps和sps
    }
    - (instancetype)initWithConfig:(SQVideoConfig*)config{
        self = [super init];
        if(self){
            _config = config;
            _encodeQueue = dispatch_queue_create("h264 hard encode queue", DISPATCH_QUEUE_SERIAL);
            _callbackQueue = dispatch_queue_create("h264 hard encode callback queue", DISPATCH_QUEUE_SERIAL);
            
            //创建编码会话
            /*
                参数1:allocator 分配器,使用null 就是用默认的
                参数2: width 视频帧的像素宽度
                参数3: height 视频帧的像素高度
                参数4: codecType 编解码器类型 这里使用 H264
                参数5:encoderSpecification 特殊的视频编码器 null就是VideoToolbox自己选择一种编码器。
                参数6:sourceImageBufferAttributes 源像素缓冲区如果不希望VideoToolbox为您创建一个,请传递NULL
                参数7:compressedDataAllocator用于压缩数据的分配器。传递NULL以使用默认分配器
                参数8: outputCallback要用压缩帧调用的回调
                参数9:outputCallbackRefCon 客户端为输出回调定义的引用值 回调是c函数不是oc方法,所以没有默认的self参数
                参数10:compressionSessionOut 要创建的会话闯入地址值。
             */
            OSStatus status  = VTCompressionSessionCreate(NULL, (int32_t)config.width, (int32_t)config.height, kCMVideoCodecType_H264, NULL, NULL, NULL, VideoEncodeCallback, (__bridge void *_Nullable)self, &_encodeSesion);
            if(status!=noErr){
                NSLog(@"VTCompressionSession create failed. status=%d", (int)status);
                return self;
            }
            //指示是否建议视频编码器实时执行压缩
            status  =  VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue);
            NSLog(@"VTSessionSetProperty: set RealTime return: %d", (int)status);
            //指定编码比特流的配置文件和级别。直播一般使用baseline,可减少由于b帧带来的延时
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ProfileLevel, kVTProfileLevel_H264_Baseline_AutoLevel);
            NSLog(@"VTSessionSetProperty: set profile return: %d", (int)status);
            //设置码率均值(比特率可以高于此。默认比特率为零,表示视频编码器。应该确定压缩数据的大小。注意,比特率设置只在定时时有效)
            CFNumberRef bit = (__bridge CFNumberRef)@(_config.bitrate);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_AverageBitRate, bit);
            NSLog(@"VTSessionSetProperty: set AverageBitRate return: %d", (int)status);
            //码率限制(只在定时时起作用)*待确认
            CFArrayRef limits = (__bridge CFArrayRef)@[@(_config.bitrate / 4), @(_config.bitrate * 4)];
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_DataRateLimits,limits);
            NSLog(@"VTSessionSetProperty: set DataRateLimits return: %d", (int)status);
            //设置关键帧间隔(GOPSize)GOP太大图像会模糊
            CFNumberRef maxKeyFrameInterval = (__bridge CFNumberRef)@(_config.fps * 2);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_MaxKeyFrameInterval, maxKeyFrameInterval);
            NSLog(@"VTSessionSetProperty: set MaxKeyFrameInterval return: %d", (int)status);
            //设置fps(预期)
            CFNumberRef expectedFrameRate = (__bridge CFNumberRef)@(_config.fps);
            status = VTSessionSetProperty(_encodeSesion, kVTCompressionPropertyKey_ExpectedFrameRate, expectedFrameRate);
            NSLog(@"VTSessionSetProperty: set ExpectedFrameRate return: %d", (int)status);
            //准备编码
            status = VTCompressionSessionPrepareToEncodeFrames(_encodeSesion);
            NSLog(@"VTSessionSetProperty: set PrepareToEncodeFrames return: %d", (int)status);
        }
        return self;
    }
    // startCode 长度 4
    const Byte startCode[] = "\x00\x00\x00\x01";
    void VideoEncodeCallback(
    void * CM_NULLABLE outputCallbackRefCon,
    void * CM_NULLABLE sourceFrameRefCon,
    OSStatus status,
    VTEncodeInfoFlags infoFlags,
    CM_NULLABLE CMSampleBufferRef sampleBuffer ){
        if(status != noErr){
            NSLog(@"VideoEncodeCallback: encode error, status = %d", (int)status);
            return;
        }
        if(!CMSampleBufferDataIsReady(sampleBuffer)){
            NSLog(@"VideoEncodeCallback: data is not ready");
            return;
        }
        SQVideoEncoder *encoder = (__bridge SQVideoEncoder *)(outputCallbackRefCon);
        BOOL keyFrame =NO;
        CFArrayRef attachArray = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, true);
        keyFrame = !CFDictionaryContainsKey(CFArrayGetValueAtIndex(attachArray, 0), kCMSampleAttachmentKey_NotSync);//(注意取反符号)
        if(keyFrame && !encoder->hasSpsPps){
            size_t spsSize, spsCount;
            size_t ppsSize, ppsCount;
            const uint8_t *spsData, *ppsData;
            CMFormatDescriptionRef formatDesc = CMSampleBufferGetFormatDescription(sampleBuffer);
            OSStatus status1 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 0, &spsData, &spsSize, &spsCount, 0);
            OSStatus status2 = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(formatDesc, 1, &ppsData, &ppsSize, &ppsCount, 0);
            //判断sps/pps获取成功
                   if (status1 == noErr & status2 == noErr) {
                       
                       NSLog(@"VideoEncodeCallback: get sps, pps success");
                       encoder->hasSpsPps = true;
                       //sps data
                       NSMutableData *sps = [NSMutableData dataWithCapacity:4 + spsSize];
                       [sps appendBytes:startCode length:4];
                       [sps appendBytes:spsData length:spsSize];
                       //pps data
                       NSMutableData *pps = [NSMutableData dataWithCapacity:4 + ppsSize];
                       [pps appendBytes:startCode length:4];
                       [pps appendBytes:ppsData length:ppsSize];
                       
                       dispatch_async(encoder.callbackQueue, ^{
                           //回调方法传递sps/pps
                           [encoder.delegate videoEncodeCallbacksps:sps pps:pps];
                       });
                       
                   } else {
                       NSLog(@"VideoEncodeCallback: get sps/pps failed spsStatus=%d, ppsStatus=%d", (int)status1, (int)status2);
                   }
        }
        //获取NALU数据
        size_t lengthAtOffset, totalLength;
        char *dataPoint;
        CMBlockBufferRef blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer);
        OSStatus error = CMBlockBufferGetDataPointer(blockBuffer, 0, &lengthAtOffset, &totalLength, &dataPoint);
        if (error != kCMBlockBufferNoErr) {
            NSLog(@"VideoEncodeCallback: get datapoint failed, status = %d", (int)error);
            return;
        }
        size_t offet = 0;
        const int lengthInfoSize =4 ;
        while (offet < totalLength - lengthInfoSize) {
            uint32_t naluLength = 0;
            memcpy(&naluLength, dataPoint+offet, lengthInfoSize);
            naluLength = CFSwapInt32BigToHost(naluLength);
            NSMutableData *data = [NSMutableData dataWithCapacity:4 + naluLength];
            [data appendBytes:startCode length:4];
            [data appendBytes:dataPoint+offet+lengthInfoSize length:naluLength];
            dispatch_async(encoder.callbackQueue, ^{
                [encoder.delegate videoEncodeCallback:data];
            });
            offet +=naluLength +lengthInfoSize;
        }
    }
    /**编码*/
    -(void)encodeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer{
        CFRetain(sampleBuffer);
        dispatch_async(_encodeQueue, ^{
            CVImageBufferRef imageBuffer = (CVImageBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
            self->frameID++;
            CMTime timeStamp = CMTimeMake(self->frameID, 1000);
            //持续时间
            CMTime duration = kCMTimeInvalid;
            VTEncodeInfoFlags flags;
            OSStatus status = VTCompressionSessionEncodeFrame(self.encodeSesion, imageBuffer, timeStamp, duration, NULL, NULL, &flags);
            if (status != noErr) {
                NSLog(@"VTCompression: encode failed: status=%d",(int)status);
            }
            CFRelease(sampleBuffer);
        });
    }
    - (void)dealloc
    {
        if(_encodeSesion){
            VTCompressionSessionCompleteFrames(_encodeSesion, kCMTimeInvalid);
            VTCompressionSessionInvalidate(_encodeSesion);
            CFRelease(_encodeSesion);
            _encodeSesion = NULL;
        }
    }
    @end
    
    ``

    相关文章

      网友评论

        本文标题:对捕获的视频数据进行H264编码

        本文链接:https://www.haomeiwen.com/subject/hojqjktx.html