假想参考解码器 vbv HRD

视频编码中有个hypothetical reference decoder(HRD), 从标准H26x系列的各个协议的附录中都可以看到有专门介绍该解码器的,在MPEG2/H262的附录中,把它叫视频缓冲校验video buffer verifier(VBV),其实这两个是一个东西。vbv buffer的作用是平滑编码器的输出码流,防止码流抖动过大,通常可以和码率控制模块结合使用。在面向传输的视频编码中,vbv buffer很有用处,因此当前主流的编解码器都有这个东西或者类似的东西,比如x264/x265中的vbv,webRTC中的jitter buffer。
在这里插入图片描述

vbv buffer原理

vbv buffer其实是一个leaky bucket 漏桶模型。编码端和解码端都有这样的一个buffer,编码端的buffer是用来模拟解码的过程的,从而有效得控制好码率。我们的控制目标是为了尽量让vbv buffer不上溢(overflow)并且不下溢(underflow),上溢的后果就是会出现码流丢失,下溢的结果就是出现视频播放卡顿。
vbv buffer有三个重要的参数(B, R, F):
B: buffer 的大小,用bits来衡量
R: 传输的峰值码率,用bits/s来衡量
F: buffer的初始充盈度,用bits来衡量。
刚开始时,buffer为空,随着码流进入buffer,一直到他的初始充盈度F,这时才能开始解码(也就是说解码器从这个时候开始才能从buffer里面取数据),那么这就产生了一个解码时延的问题,又叫init delay,或者叫vbv delay,总之都是一个东西,很多实时通信技术所谓的低时延优化其实就是优化这个地方。解码器每隔一定的时钟周期就来检查一下buffer,并从中取走一帧数据。这个时钟周期和FPS有关。下面详细说明MPEG 和 H263中的buffer是怎么操作的,其他后来的协议都是基于此原理改进。

一、MPEG 的 video buffer verifier

MPEG的vbv有两种模式,分别是CBR和VBR,MPEG1和MPEG4只支持CBR,MPEG2两个都支持。
在CBR模式下:

  • R = RMAX = 平均码率
  • B的大小是在码流头部的vbv_buffer_size指定(单位是16*1024 bits)
  • vbv_delay也是码流头部指定,也就是说初始充盈度也就确定了,因为vbv_delay = F / R
  • vbv buffer的充盈度满足下面的条件
    B0 = F
    Bi+1 = Bi - bi + R/M
    解释下,Bi代表取第i帧时,buffer的充盈度,bi代表第i帧的bits大小。M是个时钟频率。编码器必须保证Bi - bi总是大于0(不下溢),Bi总是小于B(不上溢)如图所示。
    在这里插入图片描述
    在VBR模式下:
  • R = RMAX = 最大码率,也就是说总是用最大码率灌数据
  • F = B,也就是初始充盈度是整个buffer的大小
  • vbv buffer的充盈度满足下面的条件
    B0 = B
    Bi+1 = min(B,Bi - bi + R/M)
    vbr只保证缓冲器不下溢,不保证缓冲器不上溢,如图。
    在这里插入图片描述
    其实还有个低时延模式,这个模式打开后初始充盈度很小,只比第一帧多一点点,这种情况是不保证缓冲器不下溢的,也就是说可能会出现缓冲区下溢,进而造成视频卡顿,这是低时延的牺牲。从这里可以看出,初始充盈度越大,时延越长。

二、H263的HRD
H263的HRD其实和MPEG的vbv buffer有点像,区别主要有以下两点:
1、解码器定期检查buffer,只要有一个完整的帧就取出来开始解码,跳帧是允许的,也就是说下溢是允许的,下溢的发生意味着解码器来检查的时候发现没有完整的一帧,必须等完整的一帧都到达bufer之后才能开始解码。
2.检查发生在取出来一帧之后。

另外,模型稍微有一点不一样。H263的buffer的大小是B + MAXBPP,检查的时候要求第i帧的尺寸满足:
在这里插入图片描述
也就是在取走每一帧后,都摇保证剩下的充盈度小于B。
H263的模型适用于低时延,并且允许偶尔有较大的码率波动,这就是他加一个MAXBPP的原因。MAXBPP是协商出来的一帧最大bits.在这里插入图片描述

为什么需要vbv buffer

假如我们的编码时面向存储的,那么久无需这个buffer,编码出来的码率随便你怎么波动,我只存在本地就OK。现在问题是我们是面向传输的,要考虑到网络的带宽问题,以及网络的抖动,如果我们编码出来的码率变化非常大,那么很不利于传输,比如为了帧的质量,突然编出来一个很大的帧,需要传输很久,那么解码端那边就会需要等很久,体现在播放时长时间卡住。因此需要这个buffer来平滑码率,他通过码控模块来进行反馈,当码控模块检测buffer时发现buffer很满了,就通过调节QP的方式,是编码的码率降下来。另外关于时延的问题,必须要说明的是,引入vbv buffer模型并不是问了降低时延,相反他反而会引入解码时延,但是他可以有效防止时延抖动,让你看视频不卡(只要防止下溢就行),通过把初始充盈度调大一点就能做的很好。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
瑞芯微多路视频硬解码通常需要使用相关的SDK和驱动程序来实现。下面提供一个简单的代码示例,演示如何使用RK3399处理器进行H.264视频硬解码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "rockchip/rk_mpi.h" #include "rockchip/rk_type.h" #define IN_FILE "/path/to/input.h264" #define OUT_FILE "/path/to/output.yuv" #define WIDTH 1920 #define HEIGHT 1080 int main(int argc, char **argv) { MPP_RET ret; MPP_CTX mpp_ctx = NULL; MppApi *mpi = NULL; FILE *in_file = NULL, *out_file = NULL; RK_U8 *buf = NULL; MppBuffer pkt_buf = NULL; MppBuffer frm_buf = NULL; MppPacket pkt = NULL; MppFrame frm = NULL; RK_U32 pkt_eos = 0, frm_eos = 0; RK_U32 pkt_size = 0, frm_size = 0; RK_U32 width = WIDTH, height = HEIGHT; RK_U32 hor_stride = 0, ver_stride = 0; // 初始化MPP上下文 ret = mpp_create(&mpp_ctx, &mpi); if (ret != MPP_OK) { printf("mpp_create failed: %d\n", ret); return -1; } // 配置解码参数 MppCodingType type = MPP_VIDEO_CodingAVC; MppFrameFormat fmt = MPP_FMT_YUV420SP; MppPacketCoding pkt_type = MPP_VIDEO_CodingAVC; MppFrameCoding frm_type = MPP_VIDEO_CodingI420; MppEncConfig enc_conf; memset(&enc_conf, 0, sizeof(enc_conf)); enc_conf.coding = type; enc_conf.width = width; enc_conf.height = height; enc_conf.hor_stride = hor_stride; enc_conf.ver_stride = ver_stride; enc_conf.fps_in = 30; enc_conf.fps_out = 30; enc_conf.qp_init = 0; enc_conf.qp_max = 51; enc_conf.qp_min = 0; enc_conf.rc_mode = MPP_ENC_RC_MODE_CBR; enc_conf.bps_target = 1024 * 1024; enc_conf.profile = MPP_PROFILE_AVC_HIGH; enc_conf.level = MPP_LEVEL_UNKNOWN; enc_conf.gop = 30; enc_conf.skip_cnt = 0; enc_conf.max_key_interval = 30; enc_conf.ref_num = 1; enc_conf.i_frame_qp_delta = 0; enc_conf.b_frame_qp_delta = 0; enc_conf.init_vbv_buffer_size = 1024 * 1024; enc_conf.vbv_buffer_size = 1024 * 1024; enc_conf.vbv_buffer_delay = 1000; // 初始化解码器 ret = mpi->control(mpp_ctx, MPP_DEC_SET_OUTPUT_FORMAT, &fmt); if (ret != MPP_OK) { printf("mpi->control MPP_DEC_SET_OUTPUT_FORMAT failed: %d\n", ret); goto end; } ret = mpi->control(mpp_ctx, MPP_DEC_SET_CodingType, &type); if (ret != MPP_OK) { printf("mpi->control MPP_DEC_SET_CodingType failed: %d\n", ret); goto end; } ret = mpi->control(mpp_ctx, MPP_DEC_SET_FRAME_INFO, &enc_conf); if (ret != MPP_OK) { printf("mpi->control MPP_DEC_SET_FRAME_INFO failed: %d\n", ret); goto end; } // 打开输入文件和输出文件 in_file = fopen(IN_FILE, "rb"); out_file = fopen(OUT_FILE, "wb"); if (!in_file || !out_file) { printf("open file failed\n"); goto end; } // 解码循环 while (!pkt_eos || !frm_eos) { if (!pkt_eos) { // 读取输入数据 if (!pkt_buf) { ret = mpi->control(mpp_ctx, MPP_DEC_GET_PKT_BUF, &pkt_buf); if (ret != MPP_OK) { printf("mpi->control MPP_DEC_GET_PKT_BUF failed: %d\n", ret); goto end; } } buf = (RK_U8*)mpp_buffer_get_ptr(pkt_buf); pkt_size = fread(buf, 1, mpp_buffer_get_size(pkt_buf), in_file); if (pkt_size == 0) { pkt_eos = 1; } // 构造输入数据包 ret = mpp_packet_init(&pkt, buf, pkt_size); if (ret != MPP_OK) { printf("mpp_packet_init failed: %d\n", ret); goto end; } mpp_packet_set_length(pkt, pkt_size); mpp_packet_set_pts(pkt, 0); mpp_packet_set_dts(pkt, 0); } if (!frm_eos) { if (!frm_buf) { // 申请输出缓冲区 ret = mpi->control(mpp_ctx, MPP_DEC_GET_FRAME_BUF, &frm_buf); if (ret != MPP_OK) { printf("mpi->control MPP_DEC_GET_FRAME_BUF failed: %d\n", ret); goto end; } } // 解码数据包 ret = mpi->decode_put_packet(mpp_ctx, pkt); if (ret != MPP_OK) { printf("mpi->decode_put_packet failed: %d\n", ret); goto end; } ret = mpi->decode_get_frame(mpp_ctx, &frm); if (ret != MPP_OK) { if (ret == MPP_NOK_ENOMEM) { // 缓冲区不足,继续循环 usleep(1000); continue; } else if (ret == MPP_OK || ret == MPP_ERR_TIMEOUT) { // 解码成功或者超时,继续循环 usleep(1000); continue; } else { printf("mpi->decode_get_frame failed: %d\n", ret); goto end; } } // 将解码后的数据写入输出文件 buf = (RK_U8*)mpp_buffer_get_ptr(frm_buf); frm_size = mpp_frame_get_size(frm); fwrite(buf, 1, frm_size, out_file); } } end: // 释放资源 if (mpi) { mpi->reset(mpp_ctx); mpp_destroy(mpp_ctx); } if (in_file) { fclose(in_file); } if (out_file) { fclose(out_file); } if (pkt_buf) { mpp_buffer_put(pkt_buf); } if (pkt) { mpp_packet_deinit(&pkt); } if (frm_buf) { mpp_buffer_put(frm_buf); } if (frm) { mpp_frame_deinit(&frm); } return 0; } ``` 需要注意的是,这只是一个简单的示例代码,实际使用时需要根据具体的需求和硬件平台进行相应的修改和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值