音视频学习(五)——H.264视频码流

1. 概述

  • 编码是为了将数据进行压缩,这样在传输的过程中就不会使资源被浪费;
  • 用一个简单的例子来说明编码的必要性:当你此刻显示器正在播放一个视频,分辨率是1280 * 720,帧率是25,那么一秒所产生正常的数据大小为:1280 * 720(位像素)*25(张) / 8(1字节8位)(结果:B) / 1024(结果:KB) / 1024 (结果:MB) = 2.75MB。显然一秒这么大的数据你是无法接受的,需要将数据进行压缩;
  • H264在视频采集到输出中属于编解码层次的数据,如下图所示,是在采集数据后做编码压缩时通过编码标准编码后所呈现的数据;

img

  • 对于视频⽂件来说,视频由单张图⽚帧所组成,⽐如每秒25帧,但是图⽚帧的像素块之间存在相似性,因此视频帧图像可以进⾏图像压缩;H264采⽤了16*16的分块⼤⼩对,视频帧图像进⾏相似⽐较和压缩编码。如下图所示:

img

2. 名词解释

2.1 序列

  • 参照一段时间内相邻的图像中,像素、亮度与色温的差别很小。所以当面对一段时间内图像我们没必要去对每一幅图像进行完整一帧的编码,而是可以选取这段时间的第一帧图像作为完整编码,而下一幅图像可以记录与第一帧完整编码图像像素、亮度与色温等的差别即可,以此类推循环下去;
  • 什么叫序列呢?上述的这段时间内图像变化不大的图像集我们就可以称之为一个序列。序列可以理解为有相同特点的一段数据。但是如果某个图像与之前的图像变换很大,很难参考之前的帧来生成新的帧,那么就结束删一个序列,开始下一段序列。重复上一序列的做法,生成新的一段序列;

2.2 帧类型

  • H264结构中,一个视频图像编码后的数据叫做一帧,一帧由一个片(slice)或多个片组成,一个片由一个或多个宏块(MB)组成,一个宏块由16x16的yuv数据组成。宏块作为H264编码的基本单位。
  • H264使⽤帧内压缩和帧间压缩的⽅式提⾼编码压缩率;
  • H264采⽤了独特的I帧、P帧和B帧策略来实现,连续帧之间的压缩;

在这里插入图片描述

2.2.1 I帧

2.2.1.1 概念

帧内编码帧 ,I帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)。

2.2.1.2 特点
  • 是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输;
  • 解码时仅用I帧的数据就可重构完整图像;
  • I帧描述了图像背景和运动主体的详情;
  • I帧不需要参考其他画面而生成;
  • I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量);
  • I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧
  • I帧不需要考虑运动矢量;
  • I帧所占数据的信息量比较大

2.2.2 P帧

2.2.2.1 概念

前向预测编码帧。P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面。(也就是差别帧,P帧没有完整画面数据,只有与前一帧的画面差别的数据)

2.2.2.2 预测与重构

P帧是以I帧为参考帧,在I帧中找出P帧“某点”的预测值和运动矢量,取预测差值和运动矢量一起传送。在接收端根据运动矢量从I帧中找出P帧“某点”的预测值并与差值相加以得到P帧“某点”样值,从而可得到完整的P帧。

2.2.2.3 特点
  • P帧是I帧后面相隔1~2帧的编码帧;
  • P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差);
  • 解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像;
  • P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧
  • P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧;
  • 由于P帧是参考帧,它可能造成解码错误的扩散
  • 由于是差值传送,P帧的压缩比较高

2.2.3 B帧

2.2.3.1 概念

双向预测内插编码帧。B帧是双向差别帧,也就是B帧记录的是本帧与前后帧的差别(具体比较复杂,有4种情况),换言之,要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。B帧压缩率高,但是解码时CPU会比较累。

B帧以前面的I或P帧后面的P帧为参考帧,“找出”B帧“某点”的预测值和两个运动矢量,并取预测差值和运动矢量传送。接收端根据运动矢量在两个参考帧中“找出(算出)”预测值并与差值求和,得到B帧“某点”样值,从而可得到完整的B帧。

2.2.3.2 特点
  • B帧是由前面的I或P帧和后面的P帧来进行预测的;
  • B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量;
  • B帧是双向预测编码帧;
  • B帧压缩比最高,因为它只反映两个参考帧间运动主体的变化情况,预测比较准确;
  • B帧不是参考帧,不会造成解码错误的扩散。

2.3 GOP(画面组)

  • GOP即Group of picture(图像组),指两个I帧之间的距离(下图所说的视频序列就是GOP),Reference(参考周期)指两个P帧之间的距离,可以理解为跟序列差不多意思,就是一段时间内变化不大的图像集,比较说GOP为120,如果是720 p60 的话,那就是2s一次I帧。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧。所以在码率不变的前提下,GOP值越大,P、B帧的数量会越多,平均每个I、P、B帧所占用的字节数就越多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。
  • GOP结构一般有两个数字,如M=3,N=12。**M指定I帧和P帧之间的距离,N指定两个I帧之间的距离。**上面的M=3,N=12,GOP结构为:IBBPBBPBBPBBI。在一个GOP内I frame解码不依赖任何的其它帧,p frame解码则依赖前面的I frame或P frame,B frame解码依赖前最近的一个I frame或P frame 及其后最近的一个P frame。

2.4 IDR帧(关键帧)

  • IDR(Instantaneous Decoding Refresh)即时解码刷新。 在编码解码中为了方便,将GOP中首个I帧要和其他I帧区别开,把第一个I帧叫IDR,这样方便控制编码和解码流程,所以IDR帧一定是I帧,但I帧不一定是IDR帧;IDR帧的作用是立刻刷新,使错误不致传播,从IDR帧开始算新的序列开始编码。I帧有被跨帧参考的可能,IDR不会;
  • I帧不用参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样,例如:

在这里插入图片描述

  • 其核⼼作⽤是,是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤IDR之前的图像的数据来解码;

在这里插入图片描述

3. 压缩方式

  • H264采用的核心算法是帧内压缩和帧间压缩,帧内压缩是生成I帧的算法,帧间压缩是生成B帧和P帧的算法。
  • 帧内(Intraframe)压缩也称为空间压缩(Spatialcompression)。当压缩一帧图像时,仅考虑本帧的数据而不考虑相邻帧之间的冗余信息,这实际上与静态图像压缩类似。帧内一般采用有损压缩算法,由于帧内压缩是编码一个完整的图像,所以可以独立的解码、显示。帧内压缩一般达不到很高的压缩,跟编码jpeg差不多。
  • 帧间(Interframe)压缩的原理是:相邻几帧的数据有很大的相关性,或者说前后两帧信息变化很小的特点。也即连续的视频其相邻帧之间具有冗余信息,根据这一特性,压缩相邻帧之间的冗余量就可以进一步提高压缩量,减小压缩比。帧间压缩也称为时间压缩(Temporalcompression),它通过比较时间轴上不同帧之间的数据进行压缩。帧间压缩一般是无损的。**帧差值(Framedifferencing)**算法是一种典型的时间压缩法,它通过比较本帧与相邻帧之间的差异,仅记录本帧与其相邻帧的差值,这样可以大大减少数据量。

说明:

  • 分组,也就是将一系列变换不大的图像归为一个组,也就是一个序列,也可以叫GOP(画面组);

  • 定义帧,将每组的图像帧归分为I帧、P帧和B帧三种类型;

  • 预测帧, 以I帧做为基础帧,以I帧预测P帧,再由I帧和P帧预测B帧;

  • 数据传输, 最后将I帧数据与预测的差值信息进行存储和传输。

4. 分层结构

  • H264的主要目标是为了有高的视频压缩比和良好的网络亲和性,为了达成这两个目标,H264的解决方案是将系统框架分为两个层面,分别是视频编码层面(VCL:Video Coding Layer)和网络抽象层面(NAL:Network Coding Layer),H.264原始码流(裸流)是由⼀个接⼀个NALU组成,如下图:

    在这里插入图片描述

  • VLC层是对核心算法引擎、块、宏块及片的语法级别的定义,负责有效表示视频数据的内容,最终输出编码完的数据SODB

  • NAL层定义了片级以上的语法级别(如序列参数集和图像参数集,针对网络传输,后面会描述到),负责以网络所要求的恰当方式去格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。NAL层将SODB打包成RBSP然后加上NAL头组成一个NALU单元,具体NAL单元的组成也会在后面详细描述。

  • 在VCL进⾏数据传输或存储之前,这些编码的VCL数据,被映射或封装进NAL单元。(NALU)

  • ⼀个NALU = ⼀组对应于视频编码的NALU头部信息 + ⼀个原始字节序列负荷(RBSP,Raw Byte Sequence Payload).

  • NALU结构单元的主体结构如下所示;⼀个原始的H.264 NALU单元通常由[StartCode] [NALU Header] [NALU Payload]三部分组成,其中 Start Code ⽤于标示这是⼀个NALU 单元的开始,必须是"00 00 00 01" 或"00 00 01",除此之外基本相当于⼀个NAL header + RBSP;

    在这里插入图片描述

  • SODB与RBSP的关联,具体结构如图3所示:

    • SODB(String Of Data Bits): 数据比特串,是编码后的原始数据;
    • RBSP(Raw Byte Sequence Payload): 原始字节序列载荷,即在SODB的后面添加了trailing bits,即一个bit 1和若干个bit 0,以便字节对齐;
  • RBSP的形成过程
    如果SODB的内容是空的,那么RBSP的内容也是空的。否则,RBSP的第一个字节取自SODB的第1到第8个比特,RBSP字节内部按照从左到右从高到低的顺序排列。以此类推,RBSP中的每个字节都直接取自SODP的相应比特。RBSP的最后一个字节包含SODB的最后几个比特,以及trailing bits。其中,trailing bits的第一个比特为1,其余的比特为0,保证字节对齐。所以RBSP就等于,SODB在它的最后一个字节的最后一个比特后,紧跟值为1的1个比特,然后增加若干比特的0,以补齐这个字节

在这里插入图片描述

5. 码流结构

具体讲述NAL单元前,十分有必要先了解一下H264的码流结构。在经过编码后的H264的码流如下图所示,从图中我们需要得到一个概念,H264码流是由一个个的NAL单元组成,其中SPS、PPS、IDR和SLICE是NAL单元某一类型的数据。

在这里插入图片描述

6. NAL单元

6.1 NAL结构

  • 在实际的网络数据传输过程中H264的数据结构是以NALU(NAL单元)进行传输的,传输数据结构组成为[NALU Header]+[RBSP],如下图所示:

在这里插入图片描述

  • 从之前的分析我们可以知道,VCL层编码后的视频帧数据,帧有可能是I/B/P帧,这些帧也可能是属于不同的序列之中;同一序列也还有相应的序列参数集与图片参数集;综上所述,想要完成准确无误视频的解码,除了需要VCL层编码出来的视频帧数据,同时还需要传输序列参数集和图像参数集等等,所以RBSP不单纯只保存I/B/P帧的数据编码信息,还有其他信息也可能出现在里面。
  • 上面知道NAL单元是作为实际视频数据传输的基本单元,NALU头是用来标识后面RBSP是什么类型的数据,同时记录RBSP数据是否会被其他帧参考以及网络传输是否有错误,所以针对NAL头和RBSP的作用以及结构与所承载的数据需要做个简单的了解。

6.2 NAL头

6.2.1 组成

NAL单元的头部是由forbidden_bit(1bit),nal_reference_bit(2bits)(优先级),nal_unit_type(5bits)(类型)三个部分组成的,组成下图所示:

  • F(forbiden):禁止位,占用NAL头的第一个位,当禁止位值为1时表示语法错误;
  • NRI:取00~11,似乎指示这个NALU的重要性,如00的NALU解码器可以丢弃它⽽不影响图像的回放,0~3,取值越⼤,表示当前NAL越重要,需要优先受到保护。如果当前NAL是属于参考帧的⽚,或是序列参数集,或是图像参数集这些重要的单位时,本句法元素必需⼤于0。
  • Type:NAL单元数据类型,也就是标识该NAL单元的数据类型是哪种,占用NAL头的第四到第八个位;

在这里插入图片描述

6.2.2 NAL单元数据类型

  • NAL类型主要就是下面图中这些类型每个类型都有特殊的作用;

    例子:0x00 00 00 01 67 ,67:⼆进制:0110 0111,00111 = 7(⼗进制),即为序列参数集

在这里插入图片描述

在这里插入图片描述

  • 在具体介绍NAL数据类型前,有必要知道NAL分为VCL的NAL单元和非VCL的NAL单元。
  • nal_unit_type为1, 2, 3, 4, 5及12的NAL单元称为VCL的NAL单元,其他类型的NAL单元为非VCL的NAL单元。
  • 另外一个需要了解的概念就是参数集(Parameter sets),参数集是携带解码参数的NAL单元,参数集对于正确解码是非常重要的,在一个有损耗的传输场景中,传输过程中比特列或包可能丢失或损坏,在这种网络环境下,参数集可以通过高质量的服务来发送,比如向前纠错机制或优先级机制。Parameter sets与其之外的句法元素之间的关系如下图所示:

在这里插入图片描述

  • 每种类型都有代表一种数据类型,比较重要的以下几种做个简单的介绍:
    • 非VCL的NAL数据类型
      • SPS(Sequence Parameter Set:序列参数集)包含一些通用的参数,比如Profile和Level,比如视频帧的尺寸,参考帧的最大数量等,这些参数对整个Video Sequence或者Programme都是通用的。
      • PPS(Picture Parameter Set:图像参数集)包含一些通用的参数,比如熵编码类型,有效的参考图像的数目和初始化参数等,这些参数可以应用到一个Video Sequence或者一部分编码帧。
      • SEI(补充增强信息):这部分参数可作为H264的比特流数据而被传输,每一个SEI信息被封装成一个NAL单元。SEI对于解码器来说可能是有用的,但是对于基本的解码过程来说,并不是必须的。
      • 一个Parameter Set在开始的时候是不活跃的,直到被激活。一个PPS被预先传到解码器,当在一个Slice Header中涉及到的时候,就会被激活,而且直到一个不同的PPS被激活。对于SPS,当一个PPS涉及到它的时候,就会被激活。对于一个以IDR Access Unit开始的Coded Video Sequence,在整个过程中,一个SPS会一直处于活跃状态。因此,一个SPS可以有效的被IDR Slice激活。
    • VCL的NAL数据类型
      • 头信息块,包括宏块类型,量化参数,运动矢量。这些信息是最重要的,因为离开他们,被的数据块种的码元都无法使用。该数据分块称为A类数据分块。
      • 帧内编码信息数据块,称为B类数据分块。它包含帧内编码宏块类型,帧内编码系数。对应的slice来说,B类数据分块的可用性依赖于A类数据分块。和帧间编码信息数据块不同的是,帧内编码信息能防止进一步的偏差,因此比帧间编码信息更重要。
      • 帧间编码信息数据块,称为C类数据分块。它包含帧间编码宏块类型,帧间编码系数。它通常是slice种最大的一部分。帧间编码信息数据块是不重要的一部分。它所包含的信息并不提供编解码器之间的同步。C类数据分块的可用性也依赖于A类数据分块,但与B类数据分块无关。
      • 以上三种数据块每种分割被单独的存放在一个NAL单元中,因此可以被单独传输

6.2.3 NAL单元与片,宏之间的联系

  • 为什么数据NAL单元中有这么多数据类型,这个SLICE又是什么东西,为什么不直接是编码后出来的原始字节序列载荷,所以我觉得在这里再讲述帧所细分的一些片和宏的概念应该是比较合适的,也是能够参照上下文更能理解这些概念的位置,又能给这些困惑做一个合理一点的解释,所以在此做一个描述:

    • 1帧(一幅图像) = 1~N个片(slice) //也可以说1到多个片为一个片组
    • 1个片 = 1~N个宏块(Marcroblock)
    • 1个宏块 = 16*16的YUV数据(原始视频采集数据)
  • 从数据层次角度来说,一幅原始的图片可以算作广义上的一帧,帧包含片组和片,片组由片来组成,片由宏块来组成,每个宏块可以是44、88、16*16像素规模的大小,它们之间的联系如下图所示。每个片都是一个独立的编码单位。

在这里插入图片描述

  • 从容纳数据角度来说,NAL单元除了容纳Slice编码的码流外,还可以容纳其他数据,这也就是为什么有SPS、PPS等这些数据出现的原因,并且这些数据在传输H264码流的过程中起到不可或缺的作用,具体作用上面也是有讲到的。

  • 那么也就可以对下面这些概念做一个大小的排序了:序列>图像>片>宏>像素

  • 同时有几点需要说明一下,这样能便于理解NAL单元:

    • 如果不采用 FMO(灵活宏块排序) 机制,则一幅图像只有一个片组;
    • 如果不使用多个片,则一个片组只有一个片;
    • 如果不采用 DP(数据分割)机制,则一个片就是一个 NALU,一个 NALU 也就是一个片。
    • 否则,一个片的组成需要由 三个 NALU 组成,也就是上面说到的A、B、C类数据块。
  • 看下面这幅码流数据分层图就比较能理解整体的码流结构组成

在这里插入图片描述

  • 如我们所见,每个分片也包含着头和数据两部分,分片头中包含着分片类型、分片中的宏块类型、分片帧的数量以及对应的帧的设置和参数等信息,而分片数据中则是宏块,这里就是我们要找的存储像素数据的地方;宏块是视频信息的主要承载者,因为它包含着每一个像素的亮度和色度信息。视频解码最主要的工作则是提供高效的方式从码流中获得宏块中的像素阵列。宏块数据的组成如下图所示:

    在这里插入图片描述

  • 从上图中,可以看到,宏块中包含了宏块类型、预测类型、Coded Block Pattern、Quantization Parameter、像素的亮度和色度数据集等等信息。

注意:

  • H.264/AVC标准对送到解码器的NAL单元顺序是有严格要求的,如果NAL单元的顺序是混乱的,必须将其重新依照规范组织后送入解码器,否则解码器不能够正确解码。
  • 序列参数集NAL单元必须在传送所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的序列参数集NAL单元。所谓重复的详细解释为:序列参数集NAL单元都有其专门的标识,如果两个序列参数集NAL单元的标识相同,就可以认为后一个只不过是前一个的拷贝,而非新的序列参数集。
  • 图像参数集NAL单元必须在所有以此参数集为参考的其他NAL单元之前传送,不过允许这些NAL单元中间出现重复的图像参数集NAL单元,这一点与上述的序列参数集NAL单元是相同。

7. 流解析和代码实现

可参考:[视音频数据处理入门:H.264视频码流解析](https://blog.csdn.net/leixiaohua1020/article/details/50534369)

H.264原始码流(又称为“裸流”)是由一个一个的NALU组成的。他们的结构如下图所示

在这里插入图片描述

其中每个NALU之间通过startcode(起始码)进行分隔,起始码分成两种:0x000001(3Byte)或者0x00000001(4Byte)。如果NALU对应的Slice为一帧的开始就用0x00000001,否则就用0x000001。
H.264码流解析的步骤就是首先从码流中搜索0x000001和0x00000001,分离出NALU;然后再分析NALU的各个字段。

7.1 H264 annexb模式

  • H264有两种封装
    • ⼀种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中
    • ⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度很多解码器只⽀持annexb这种模式,因此需要将mp4做转换:
    • 在ffmpeg中⽤h264_mp4toannexb_filter可以做转换
  • 实现
const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
 AVBSFContext *bsf_ctx = NULL;
 // 2 初始化过滤器上下⽂
 av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
 // 3 添加解码器属性
 avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
 av_bsf_init(bsf_ctx)

7.2 解析代码

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>


static char err_buf[128] = {0};

static char *av_get_err(int errnum) {
    av_strerror(errnum, err_buf, 128);
    return err_buf;
}

/*
AvCodecContext->extradata[]中为nalu长度
*   codec_extradata:
*   1, 64, 0, 1f, ff, e1, [0, 18], 67, 64, 0, 1f, ac, c8, 60, 78, 1b, 7e,
*   78, 40, 0, 0, fa, 40, 0, 3a, 98, 3, c6, c, 66, 80,
*   1, [0, 5],68, e9, 78, bc, b0, 0,
*/

//ffmpeg -i 2018.mp4 -codec copy -bsf:h264_mp4toannexb -f h264 tmp.h264
//ffmpeg 从mp4上提取H264的nalu h
int main(int argc, char **argv) {
    AVFormatContext *ifmt_ctx = NULL;
    int videoindex = -1;
    AVPacket *pkt = NULL;
    int ret = -1;
    int file_end = 0; // 文件是否读取结束

    FILE *outfp = fopen("/Users/lijinwang/Downloads/course/study/believe.h264", "wb");

    // 分配解复用器的内存,使用avformat_close_input释放
    ifmt_ctx = avformat_alloc_context();
    if (!ifmt_ctx) {
        printf("[error] Could not allocate context.\n");
        return -1;
    }

    // 根据url打开码流,并选择匹配的解复用器
    ret = avformat_open_input(&ifmt_ctx, "/Users/lijinwang/Downloads/course/study/believe.mp4", NULL, NULL);
    if (ret != 0) {
        printf("[error]avformat_open_input: %s\n", av_get_err(ret));
        return -1;
    }

    // 读取媒体文件的部分数据包以获取码流信息
    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if (ret < 0) {
        printf("[error]avformat_find_stream_info: %s\n", av_get_err(ret));
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 查找出哪个码流是video/audio/subtitles
    videoindex = -1;
    // 推荐的方式
    videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 分配数据包
    pkt = av_packet_alloc();
    av_init_packet(pkt);

    // 1 获取相应的比特流过滤器
    //FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。
    // FLV封装时,可以把多个NALU放在一个VIDEO TAG中,结构为4B NALU长度+NALU1+4B NALU长度+NALU2+...,
    // 需要做的处理把4B长度换成00000001或者000001
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext *bsf_ctx = NULL;
    // 2 初始化过滤器上下文
    av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
    // 3 添加解码器属性
    avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
    av_bsf_init(bsf_ctx);

    file_end = 0;
    while (0 == file_end) {
        if ((ret = av_read_frame(ifmt_ctx, pkt)) < 0) {
            // 没有更多包可读
            file_end = 1;
            printf("read file end: ret:%d\n", ret);
        }
        if (ret == 0 && pkt->stream_index == videoindex) {
#if 1
            int input_size = pkt->size;
            int out_pkt_count = 0;
            if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间
            {
                av_packet_unref(pkt);   // 你不用了就把资源释放掉
                continue;       // 继续送
            }
            av_packet_unref(pkt);   // 释放资源
            while (av_bsf_receive_packet(bsf_ctx, pkt) == 0) {
                out_pkt_count++;
                // printf("fwrite size:%d\n", pkt->size);
                size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
                if (size != pkt->size) {
                    printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
                }
                av_packet_unref(pkt);
            }
            if (out_pkt_count >= 2) {
                printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n",
                       input_size, out_pkt_count);
            }
#else       // TS流可以直接写入
            size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
            if (size != pkt->size) {
                printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
            }
            av_packet_unref(pkt);
#endif
        } else {
            if (ret == 0)
                av_packet_unref(pkt);        // 释放内存
        }
    }
    if (outfp)
        fclose(outfp);
    if (bsf_ctx)
        av_bsf_free(&bsf_ctx);
    if (pkt)
        av_packet_free(&pkt);
    if (ifmt_ctx)
        avformat_close_input(&ifmt_ctx);
    printf("finish\n");

    return 0;
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值