音视频 FFmpeg

news2024/11/28 18:41:08

文章目录

  • 前言
  • 视频
  • 编解码
    • 硬件解码(高级)
      • 软解码(低级)
      • 软、硬解码对比
      • 视频解码有四个步骤
      • Android 系统中编解码器的命名方式
      • 查看当前设备支持的硬解码
  • 基础知识
    • RGB色彩空间
      • 常见的格式
      • 对比YUV
      • 索引格式
      • 分离RGB24像素数据中的R、G、B分量
    • BMP 文件格式
      • 格式组成
      • 像素排列顺序
      • RGB24格式像素数据封装为BMP图像
    • YUV色彩空间
      • 对比RGB
      • 采样模式
      • YUV格式
      • 文件大小
      • 文件格式
      • 像素排列顺序
    • 分离YUV
      • 分离YUVxxxP像素数据中的Y、U、V分量
      • YUV420P像素数据去掉颜色(灰度图)
      • YUV420P像素数据亮度减半
      • YUV420P像素数据的周围加边框
      • 生成YUV420P格式的灰阶测试图
      • 计算两个YUV420P像素数据的PSNR
    • RGB-YUV相互转换
      • 求Y
      • 求U
  • AVC视频码流解析
    • 类型帧
      • I帧
      • P帧
      • B帧
    • GOP
    • PTS、DTS
    • H264(AVC)
      • 背景
      • NALU
      • 结构
        • AnnexB
          • 防竞争字节序
        • avcC
          • avcC文件头部信息格式
          • NALULengthSizeMinusOne
      • 主体NALU
      • NALU的第一个字节
    • 读取SPS信息
    • slice
    • 宏块
  • HEVC视频码流解析
  • AV1视频码流解析
  • AV1、HEVC编解码格式对比
    • 未来
  • 音频
  • 常用知识
    • 音视频常见单词
    • 音视频常见名词
    • ffmpeg硬件加速
    • 常用命令
    • 注意的细节
    • H264画质级别
  • ffmpeg结构

前言

本文章是以学习笔记形式展开记录。

希望大家学成归来的时候多反馈国内的音视频社区吧

视频

编解码

解封装
音视频压缩Data
音视频同步
解协议
得到封装格式-mp4
视频压缩Data
解码
原始数据-YUV
音频驱动/设备
音频压缩Data
音频采样数据-PCM
视频驱动/设备

硬件解码(高级)

通过显卡的视频加速功能对高清视频进行解码。因此硬解码能够将CPU从繁重的视频解码运算中释放出来,使播放设备具备流畅播放高清视频的能力。

显卡的GPU/VPU要比CPU更适合这类大数据量的、低难度的重复工作。视频解码工作从处理器那里分离出来,交给显卡去做

软解码(低级)

以前纯粹依靠CPU来解码的方式则是“软解码”。

软解码是在显卡本身不支持或者部分不支持硬件解码的前提下,将解压高清编码的任务交给CPU,这是基于硬件配置本身达不到硬解压要求的前提下,属于一个折中的无奈之举

软、硬解码对比

对于一个超级电视而言,观看高清电影无疑是用户最大的诉求,而硬解码的优势就在于可以流畅的支持1080p甚至4K清晰度的电影播放,而不需要占用CPU,CPU就可以如释重负,轻松上阵,承担更多的其他任务。

如果通过软解码的方式播放高清电影,CPU的负担较重,往往会出现卡顿、不流畅的现象。

视频解码有四个步骤

  1. VLD(流处理)
  2. iDCT(频率转换)
  3. MoComp(运动补偿)
  4. PostProc(显示后处理,解码去块滤波Deblocking)

通常我们所说的硬件加速或硬件解码,就是指视频解码的这几个步骤中,用显卡专用的解码引擎替代CPU的软件计算,降低CPU的计算负荷。

Android 系统中编解码器的命名方式

  • 软编解码器通常是以 OMX.google开头
  • 硬编解码器通常是以 OMX.[hardware_vendor] 开头的

但是还有一些不遵守这个命名规范的,不以 OMX. 开头的情况,它们也会被认为是软编解码器。

查看当前设备支持的硬解码

  • 通过/system/etc/media_codecs.xml可以确定当前设备支持哪些硬解码。
  • 通过/system/etc/media_profiles.xml可以知道设备支持的具体profile和level等详细信息。

基础知识

RGB色彩空间

所有的颜色可以通过三原色产生,这 三原色 就是 Red (红),Green(绿),Blue(蓝)

缺点是占用空间大,应用用RGB封装格式的的多,下文中的BMP就是使用的RGB做封装的

常见的格式

以下是基于小端的模式架构体系中

  • RGB24 : 每个像素占3个字节,R 占 8位,G 占 8位,B 占 8 位。即格式可以混合生成 25 6 3 256^3 2563 种颜色以下的格式也是依次类推。顺序为R-G-B

  • RGB16

    • RGB565 : 每个像素占2个字节,R 占 5位,G 占 6位,B 占 5 位。顺序为B-G-R
    • RGB555 :每个像素占2个字节(还剩一个最高位不使用),R 占 5位,G 占 5位,B 占 5 位。顺序为B-G-R
  • RGB32 :每个像素占4个字节,R 占 8位,G 占 8位,B 占 8 位,最后8位保留。顺序为B-G-R-0

  • ARGB32 :本质就是带alpha通道RGB32,保留的8个bit用来表示alpha的值。顺序为B-G-R-A

  • RGB1、RGB4、RGB8

opencv默认通道是BGR的。这是opencv的入门大坑之一,BGR是个历史遗留问题,为了兼容早年的某些硬件

对比YUV

与YUV420P三个分量分开存储不同,RGB24格式的每个像素的三个分量是连续存储的,对比如下:

  • YUV444p(称为Planar方式)
yyyyuuuuvvvv
yyyyuuuuvvvv
  • RGB24(称为Packed方式)
RGBRGBRGBRGB
RGBRGBRGBRGB

索引格式

  • RGB 的值是一个索引,不是真正的颜色值
  • 例如 RGB 的值占1位,那只有两个值 0 跟 1,通常用于黑白颜色
    • 0 跟 1 到底是什么颜色,是通过 索引表(也叫调色板)来定位的,不一定是 黑白,也可以是其他的颜色,叫索引格式
  • 只占 1 位的 RGB 称为 RGB1,还有 RGB4 占 4 位,索引表有 16 种颜色, RGB8 占 8 位,索引表有 256 种颜色。

分离RGB24像素数据中的R、G、B分量

因为RGB24格式的每个像素的三个分量是连续存储的,所以只需要遍历全部文件数据,然后一次取3个字节,将3个字节分为R、G、B分量保存就可以了

	unsigned char *pic=(unsigned char *)malloc(w*h*3);
	fread(pic,1,w*h*3,fp);
	for(int j=0;j<w*h*3;j=j+3){
		fwrite(pic+j,1,1,fp1);
		fwrite(pic+j+1,1,1,fp2);
		fwrite(pic+j+2,1,1,fp3);
	}
  • 根据文件大小推算文件宽高-(只能一些推算常见、有规律的宽高)
#原始文件大小 byte单位 十六进制
$ hexdump cie1931_500x500.rgb | tail -1                   [~/D/y音视频]
00b71b0
$ python3.9 -c "print(int('0x0b71b0',16))"                [~/D/y音视频]
750000
$ expr 750000 / 3                                         [~/D/y音视频]
250000
  • 根据视频宽高推算文件大小
$ expr 500 \* 500 \* 3                                    [~/D/y音视频]
750000
$ python3.9 -c "print(hex(750000))"                       [~/D/y音视频]
0xb71b0

这里有个奇怪的现象,暂时放置,#####################

$ du -k cie1931_500x500.rgb                                  [~/D/y音视频]
736K	cie1931_500x500.rgb
$ expr 736 \* 1024                                        [~/D/y音视频]
753664

#这里的结果应该是750000才对

ffplay查看图片

ffplay -s 500x500 -pix_fmt rgb24  cie1931_500x500.rgb
#需要指定格式 不然会按照yuv420p格式打开
ffplay -s 500x500 -pix_fmt rgb24  -vf extractplanes='r' cie1931_500x500.rgb
#分量
ffplay -s 500x500 -pix_fmt rgb24  -vf extractplanes='g' cie1931_500x500.rgb
ffplay -s 500x500 -pix_fmt rgb24  -vf extractplanes='b' cie1931_500x500.rgb

BMP 文件格式

BMP 全称 Bitmap-File,是微软出的图像文件格式,目前只有 BMP 文件是用RGB 模式

格式组成

  • BMP文件头(14 bytes) ,存放一些文件相关的信息。

    • 1~2 - Magic number(除了数据文件外,基本都有,file命令就是通过它来判定文件类型的)
    • 3~6 - BMP文件大小
    • 7~10 - 扩展字节(目前是0)
    • 11~14 - 实际的像素数据的偏移值(没有调色板通常是 0x36)
  • 位图信息头,通常是 40 bytes 大小,也可以理解成 图像信息头,存放一些图像相关的信息,例如宽高之类的数据

    • 15~18 - 位图信息头大小
    • 19~22 - 图像的宽度(单位像素)
    • 23~26 - 图像的高度
    • 27~28 - color planes 的数量 ,通常是 0
    • 29~30 - 指每个像素需要多少位来存储(0x0018 -> REG24 -> 一个像素24位)
    • 31~34 - 压缩类型
    • 35~38 - 图像大小(不包含 BMP文件头跟位图信息头)
    • 39~42 - 水平分辨率
    • 43~45 - 垂直分辨率
    • 46~49 - 颜色表中的颜色索引数(没有索引表就是0 -> 真彩色)
    • 50~53 - 对图像显示有重要影响的颜色索引数码(00 -> 全都重要)
  • 调色板,大小由 颜色索引决定,本文的 juren.bmp 是 RGB24 模式,真彩色,没有用到 调色板,所以这个区域是0字节。

  • 位图数据,对于本文的 juren.bmp 来说,里面就是 RGB 数据,一个像素占 3 个字节。

像素排列顺序

默认从左下角到右上角,排列顺序是由 位图信息头 里面那个 高度是正数还是负数决定的

RGB24格式像素数据封装为BMP图像

这里我跟的时候发现代码运行后生成的bmp打不开,后来发现是结构体定义格式错了,按照上面bmp的头结构重新定义结构体后,不用进行R、B分量互换,主流电脑、笔记本中用C在写内存的时候会默认为小端模式,除非你在单片机上面写那就是大端模式

修订后的代码如下,因为这部分涉及到了结构体所有全部贴出来

#include <stdio.h>
#include <stdlib.h>

struct Head {
/*	char magic[2]; //一般是有4个字节长度的 Magic Number */
	int32_t file_size;
	int32_t extension;
	int32_t offset;
} head;

struct Info {
	int32_t		info_size;
	int32_t		width;                  /* unit pixel */
	int32_t		height;
	unsigned short	num_color_planes;
	unsigned short	bit;
	int32_t		compress_type;
	int32_t		kernel_data_size;
	int32_t		horizontal_resolution;
	short		vertical_resolution;    /* 2byte 下面需要再补充一个,如果置为0的话也可以不用写,编译器会通过字节对齐的方式跳过2个字节(下面是int_32) */
	char		_vertical_resolution;   /* 补充1byte */
	int32_t		tab_index;
	int32_t		important_idx;
} info;

int main()
{
	printf( "head byte -> %ld\n", sizeof(head) );
	printf( "info byte -> %ld\n", sizeof(info) );
	int	w	= 256, h = 256;
	char	bfType[2]	= { 'B', 'M' };
	FILE	* fp_rgb24	= fopen( "lena_256x256_rgb24.rgb", "rb" );
	FILE	* fp_bmp	= fopen( "out.bmp", "wb+" );

	if ( fp_rgb24 == NULL || fp_bmp == NULL ){
		printf( "NULL!" );
		return(-1);
	}

	unsigned char* rgb24_buf = (unsigned char *) malloc( w * h * 3 );
	unsigned char* re_rgb24_buf = (unsigned char *) malloc( w * h * 3 );
	fread( re_rgb24_buf, 1, w * h * 3, fp_rgb24 );

  //倒置字节
	for (int i = 0; i < w * h * 3; ++i) {
		rgb24_buf[i] = re_rgb24_buf[((w*h*3)-1)-i];
	}
	//不需要调整大小端
//	char tmp;
//	for ( int j = 0; j < w * h * 3; j = j + 3 ){
//		tmp	= rgb24_buf[j];
//		rgb24_buf[j] = rgb24_buf[j + 2];
//		rgb24_buf[j + 2] = tmp;
//	}

	int head_size = sizeof(bfType) + sizeof(head) + sizeof(info);
	printf( "%d\n", head_size );
	head.file_size	= head_size + w * h * 3;
	head.offset	= head_size;

	info.info_size = sizeof(info);
	info.width = w;
	info.height = h;
	info.num_color_planes = 1;
	info.bit= 3 * 8; /* rgb24 */
	info.compress_type = 0;
	info.kernel_data_size = w * h * 3;
	info.horizontal_resolution	= 0;
	info.vertical_resolution	= 0;
	info.tab_index = 0;
	info.important_idx = 0;

	fwrite( bfType, 1, sizeof(bfType), fp_bmp );
	fwrite( &head, 1, sizeof(head), fp_bmp );
	fwrite( &info, 1, sizeof(info), fp_bmp );
	fwrite( rgb24_buf, 1, w * h * 3, fp_bmp );

  free( re_rgb24_buf );
  re_rgb24_buf=NULL; //uaf
	free( rgb24_buf );
  rgb24_buf=NULL;
	fclose( fp_rgb24 );
	fclose( fp_bmp );
	return 0;
}

这里需要注意的问题 : (不知道是不是我的编译器问题 -> Apple clang version 13.1.6)

  1. 定义结构体的时候的字节对齐问题,我们知道BMP文件头是14 bytes,但是如果按照上面所述的结构来定义结构体的话,magic number会被扩展字节按int_32进行字节对齐,所以magic number需要单独进行写入

  2. 写完的bmp图片打开时,发生了图片颠倒的问题,所以需要倒置字节的操作,这里插个庄,后续再来处理

YUV色彩空间

YUV 是 指 YCbCr ,U 就是 Cb,V 就是 Cr

各分量表示含义 : Y- 亮度,U-色调,V-色饱和度(彩度)

在位深度为8bit的YUV 分量的值都是 0 ~ 255 (8位 = 2 8 2^8 28,即 [ 0 , ( 2 8 − 1 ) ] [0, (2^8 - 1)] [0,(281)]

这里的位深度就对应了PSNR的计算,挺好玩的~

简单的说就是:

  • Y亮度一张图片有了Y分量那么通过Y分量在不同局部初亮度潜深就可以描绘出这张图片的大致轮廓

  • U、V共同的决定了这张轮廓图的色调,U、V组成了一个(0~255)二维坐标,每个坐标点就是一个颜色值

  • 位深度就是只Y、U、V分量的取值范围,10bit就是0~1023的范围值,Y值的轮廓越来越明显,U、V色彩过滤就越来越缓和

对比RGB

  • RGB => 采集和显示比较好
  • YUV => 编码和存储比较好

在存储和编码之前,RGB 图像要转换为 YUV 图像,而 YUV 图像在 显示之前通常有必要转换回 RGB. 显示的时候 YUV 转成 RGB 通常是硬件或者软件内部做了,我们写代码开发的时候 这个 YUV 转 RGB 显示到屏幕这个过程通常是透明的

RGB => YUV
YUV => RGB
编码
显示

采样模式

经过大量研究实验表明,视觉系统 对 色度 的敏感度 是远小于 亮度的。所以可以对 色度 采用更小的采样率来压缩数据,对亮度采用正常的采样率即可,这样压缩数据不会对视觉体验产生太大的影响,从而就由yuv444p产生了yuv422pyuv420p格式,它们的共同点都是都缩减了u、v分量的空间

  • 4-4-4 一个像素占 3 个字节
  • 4-2-2 平均一个像素占 2 个字节
  • 4-2-0 平均一个像素占 1.5 个字节

YUV格式

  • planner :平面格式, 先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V. (比如yuvj444pp就是planner)
  • semi-Planar:半平面的YUV格式,Y分量单独存储,但是UV分量交叉存储
  • packed :每个像素点的Y,U,V是连续交错存储的,和RGB的存放方式类似(对于YUV4:2:2格式而言,用紧缩格式很合适的,因此就有了UYVY、YUYV等)

这里的连续存储,不是一行像素里面连续存储,是整张图片的连续存储,例如xx444.yuv 图片是 6075kb 大小

  • 第 1~ 2025kb 都是 Y 数据
  • 第 2026 ~ 4050kb 都是 U 数据
  • 第 4051 ~ 6075kb 都是 V 数据

文件大小

Y U V 444 = S I Z E = 1920 ∗ 1080 ∗ 3 YUV444=SIZE=1920*1080*3 YUV444=SIZE=192010803

Y U V 422 = S I Z E = 1920 ∗ 1080 ∗ 2 YUV422=SIZE=1920*1080*2 YUV422=SIZE=192010802

Y U V 420 = S I Z E = 1920 ∗ 1080 ∗ 3 / 2 YUV420=SIZE=1920*1080*3/2 YUV420=SIZE=192010803/2

比如文件lena_256x256_yuv420p.yuv因为是420p那么文件大小就是就是w*h*3/2

$ du -k lena_256x256_yuv420p.yuv                               [~/demo]
96	lena_256x256_yuv420p.yuv
$ expr $(expr 256 \* 256 \* 3 / 2) / 1024                      [~/demo]
96

为了验证结果用vim打开文件,然后执行%!xxd转换成16进制,这里截取末尾

.....
00017fb0: b5b6 b4b4 b5b3 b4b5 b5b6 b4b6 b7b6 b6b7  ................
00017fc0: b6b5 b3b4 b3b2 b1b2 b1b1 afaf aeaf adad  ................
00017fd0: aaab a8a5 a5a2 a09b 9895 9493 9292 9199  ................
00017fe0: 9f9e a09f a0a5 acb0 b1b0 afb1 b0aa a7a2  ................
00017ff0: 9e9f a0a0 a2a2 9e9b 9c9d 9c9e 9e9f a6ae  ................
00018000: 0a

可以看到文件所有的数据长度为0x18000byte => 十进制98304byte => 十进制96kb

文件格式

yuv 图片文件除了 原始的像素数据,要正确打开一个YUV图片需要指定它的宽、高、采样格式

像素排列顺序

yuv 图片的像素存储顺序 是从 左上角 到 右下角的

分离YUV

分离YUVxxxP像素数据中的Y、U、V分量

存储顺序为 planner 文件中各分量所处位置,存在以下文件

$ du lena_256x256_yuv4*                                        [~/demo]
192	lena_256x256_yuv420p.yuv
256	lena_256x256_yuv422p.yuv
384	lena_256x256_yuv444p.yuv
  • yuv444p
yyyyuuuuvvvv
yyyyuuuuvvvv
	unsigned char *pic=(unsigned char *)malloc(w*h*3);
		fread(pic,1,w*h*3,fp);
		//Y
		fwrite(pic,1,w*h,fp1);
		//U
		fwrite(pic+w*h,1,w*h,fp2);
		//V
		fwrite(pic+w*h*2,1,w*h,fp3);
  • yuv422p
yyyyuuvv----
yyyyuuvv----

第一个像素跟第二个像素 共享一个UV。

422 格式里面的 U 值是新值,是由第一个像素跟第二个像素的 U 值加起来除以 2 得到的,所以 422 格式的 UV 值 其实是平均值。

所以,422 格式,是第一第二个像素共享 一组UV,第三第四像素共享 一组 UV

	unsigned char *pic=(unsigned char *)malloc(w*h*2);
		fread(pic,1,w*h*2,fp);
		//Y
		fwrite(pic,1,w*h,fp1);
		//U
		fwrite(pic+w*h,1,w*h*1/2,fp2);
		//V
		fwrite(pic+w*h*3/2,1,w*h*1/2,fp3);
  • yuv420p
yyyyuv------
yyyyuv------

第一行的第1,第2 像素,第二行的 第1,第2 像素共用一组 UV

不是第一行的 第 1~ 4 个像素共享 一组 UV,因为这样会造成空间距离太远,容易造成体验不太好

这些 UV 值都是新值,是以前的 4 个像素的 U 值加起来 除以4 的平均值

		unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
		fread(pic,1,w*h*3/2,fp);
		//Y  这里Y分量正常取前面总文件大小的1/3
		fwrite(pic,1,w*h,fp1);
		//U  因为一个U是4个Y共享
		fwrite(pic+w*h,1,w*h/4,fp2);
		//V	同理一个V由4个Y共享
		fwrite(pic+w*h*5/4,1,w*h/4,fp3);

分离后

使用ffmpeg分离分量

ffmpeg -s 256x256 -pix_fmt yuv420p -i lena_256x256_yuv420p.yuv ff_420.y
#默认是yuv420p 这里也可不指定格式
ffmpeg -s 256x256 -pix_fmt yuv444p -i lena_256x256_yuv444p.yuv ff_444.y
#444p
ffmpeg -s 256x256 -pix_fmt yuv422p -i lena_256x256_yuv422p.yuv ff_422.y
#422p

测试结果是否准确

$ cmp output_420_y.y ff_420.y                                  [~/demo]
$ cmp output_420_u.y ff_420.U                                  [~/demo]
$ cmp output_420_v.y ff_420.V                                  [~/demo]

$ cmp output_444_y.y ff_444.y                                  [~/demo]
$ cmp output_444_u.y ff_444.U                                  [~/demo]
$ cmp output_444_v.y ff_444.V                                  [~/demo]

$ cmp output_422_y.y ff_422.y                                  [~/demo]
$ cmp output_422_u.y ff_422.u                                  [~/demo]
$ cmp output_422_v.y ff_422.v                                  [~/demo]

#没有输出不一样的地方,说明提取正确
ffplay -s 256x256 -vf extractplanes='y' lena_256x256_yuv420p.yuv
#播放y分离,u,v同理

YUV420P像素数据去掉颜色(灰度图)

各分量 Y- 亮度,U-色调,V-色饱和度(彩度),U、V是图像中的经过偏置处理的色度分量。

因为要去掉图像的颜色所以需要将U、V分量置为无色,也就是Cr、Cb构成的二维坐标中的原点值即(128,128)

    unsigned char *pic=(unsigned char *)malloc(w*h*3/2);

        fread(pic,1,w*h*3/2,fp);
        //U V分量取值为128
        memset(pic+w*h,128,w*h/2);
        fwrite(pic,1,w*h*3/2,fp1);

YUV420P像素数据亮度减半

这个简单,将前面w*hY分量读出来然后取半值再进行写入(bb 一句 c语言可以直接操作内存和无缝嵌入汇编的特性不亏是写算法的瑞士军刀,现在其他语言限制太多了,这一点又想起了Darwin、linux、Windows不得不说 Linux + C + GNU-Coreutils的组合用起来那真是一个随心所欲)

	unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
	int y = w*h;
	int u = w*h/4;
	int v = w*h/4;
	fread(pic,1,w*h*3/2,fp);
	unsigned char tmp = {0};
	for(int i=0;i<y;i++){
		tmp = pic[i]/2;
		pic[i] = tmp;
	}
	fwrite(pic,1,w*h*3/2,fp1);

YUV420P像素数据的周围加边框

这个也没什么好说的就是计算Y分量的起始值范围

	unsigned char *pic=(unsigned char *)malloc(w*h*3/2);
	int y = w*h;
	int u = w*h/4;
	int v = w*h/4;
	fread(pic,1,w*h*3/2,fp);
	unsigned char tmp = {0};
	int border = 5;
	for(int i=0;i<y;i++){
		if( i <= border*h||
				i >= (h-border)*h ||
				i%w <= border ||
				i%w >= (w-border)){
				pic[i] = tmp;
		}
	}
	fwrite(pic,1,w*h*3/2,fp1);

扩展:给bitmap图片加边框

//        int border = 50;
//        int h = (bitmapHeight * 4);
//        for (int i = 0; i < bytes.length; i++) {
//            if (i <= border * h ||
//                    i >= (bitmapHeight - border * 4) * h ||
//                    i % (bitmapWidth * 4) <= border * 4 ||
//                    i % (bitmapWidth * 4) >= (bitmapWidth * 4 - border * 4))
//                bytes[i] = 0;
//        }

生成YUV420P格式的灰阶测试图

	float lum_inc;
	int y = width*height;
	int u = width*height/4;
	int v = width*height/4;

	int barwidth=width/barnum;
	lum_inc=((float)(ymax))/((float)(barnum-1));
	unsigned char lum_temp;
	unsigned char* data_y=(unsigned char *)malloc(y);
	unsigned char* data_u=(unsigned char *)malloc(u);
	unsigned char* data_v=(unsigned char *)malloc(v);

	fp=fopen(url_out,"wb+");
	for(int j=0;j<height;j++){
		for(int i=0;i<width;i++){
			int index = i / barwidth;
			lum_temp = index*lum_inc;
			data_y[j*width+i]=(char)lum_temp;
		}
	}
	memset(data_u,128,u);
	memset(data_v,128,v);

	fwrite(data_y,y,1,fp);
	fwrite(data_u,u,1,fp);
	fwrite(data_v,v,1,fp);

计算两个YUV420P像素数据的PSNR

这里需要求一个MSE值PSNR值。这个算法打算在后面和SSIM、VMAF一起深入研究

  • M S E MSE MSE计算流程
得到对比文件的间隔值
将间隔值进行平方求和
得到其值在各分量中的比重

M S E = 1 w ⋅ h ∑ i = 0 w − 1 ∑ j = 0 h − 1 [ i m g 1 ( i , j ) − i m g 2 ( i , j ) ] 2 MSE = \frac{1}{w \cdot h } \sum_{i=0}^{w-1} \sum_{j=0}^{h-1} \left[img_1(i,j) - img_2(i,j)\right]^2 MSE=wh1i=0w1j=0h1[img1(i,j)img2(i,j)]2

  • P S N R PSNR PSNR就是基于这个值

P S N R = 10 ⋅ ln ⁡ [ ( 2 n − 1 ) 2 M S E ] PSNR = 10 \cdot \ln{\left[\frac{(2^n - 1)^2}{MSE}\right]} PSNR=10ln[MSE(2n1)2]
其中 n 就是 b i t 位值,一般情况下是 8 b i t , 也就是 n = 8 \textcolor{red}{其中n就是bit位值,一般情况下是8bit,也就是n=8} 其中n就是bit位值,一般情况下是8bit,也就是n=8

程序引入数学库就很简单了

	unsigned char *pic1=(unsigned char *)malloc(w*h);
	unsigned char *pic2=(unsigned char *)malloc(w*h);

	fread(pic1,1,w*h,fp1);
	fread(pic2,1,w*h,fp2);

	double mse_sum=0,mse=0,psnr=0;
	for(int j=0;j<w*h;j++){
		//得到两个对比文件y值的-间隔值平方之和
		mse_sum+=pow((double)(pic1[j]-pic2[j]),2);
	}
	//得到这个-间隔值平方之和-在y分量中的比重
	mse=mse_sum/(w*h);

	unsigned int bit = pow(2,8);

	psnr=10*log10(pow((bit - 1),2)/mse);
	printf("%5.3f\n",psnr);

RGB-YUV相互转换

因为RGB中的每个分量都表示颜色值,所有不同的颜色值亮度也不同使RGB三个分量都与亮度相关,根据分量占有不同比重的亮度提取存在如下公式:
Y = K r ⋅ R + K g ⋅ G + K b ⋅ B Y = Kr \cdot R + Kg \cdot G + Kb \cdot B Y=KrR+KgG+KbB

  • K K K :权重因子

  • K r Kr KrRed通道的权重

  • K g Kg KgGreen通道的权重

  • K b Kb KbBule通道的权重

既然是权重则有 K r + K g + K b = 1 Kr + Kg + Kb = 1 Kr+Kg+Kb=1

K权重因子会影响压缩率,所以K的具体取值是根据不同的标准而定的,现有如下标准 :

BTKrKgKb
601(SDTV标清)0.2990.5870.114
709(HDTV高清)0.21260.71520.0722
2020(UHDTV超高清)0.26270.67800.0593

BT.601

因为市面上的文章都是针对BT.601进行讲解,为了方便资料查阅,这里也使用该标准进行实验

求Y

按照权重比可知.在 R 通道里面有 29.9% 的亮度信息,在 G通道 有 58.7% 的亮度信息,在 B通道 有 11.4% 的亮度信息,那么可得公式如下:

Y = 0.299 R + 0.587 G + 0.114 B Y = 0.299R + 0.587G + 0.114B Y=0.299R+0.587G+0.114B

求U

为了对标专业理论知识,这里用Cr来表示U。上面YUV讲解也有讲到

Cr 的定义是 R - Y,求解就很简单了:
∵ C r = R − Y ∴ C r = R − ( 0.299 R + 0.587 G + 0.114 B ) = R − 0.299 R − 0.587 G − 0.114 B = 0.701 R − 0.587 G − 0.114 B 当 R 值最大 G 、 B 值最小时 , 有最大值 , 在不同的格式中 R 、 G 、 B 的值不同,这里采取函数表达 ∵ R 、 G 、 B 的取值范围都是一样所以设 X = m a x , 最小值都为 0 ∴ C r m a x = 0.701 X − 0 − 0 = 0.701 X ∴ C r m i n = 0 − 0.587 X − 0.114 X = − 0.701 X \because Cr = R - Y \\ \therefore Cr = R - (0.299R + 0.587G + 0.114B) \\ = R - 0.299R - 0.587G - 0.114B \\ = 0.701R - 0.587G - 0.114B\\ 当R值最大G、B值最小时,有最大值,在不同的格式中R、G、B的值不同,这里采取函数表达\\ \because R、G、B的取值范围都是一样所以设X = max ,最小值都为0 \\ \therefore Cr_{max} = 0.701X - 0 - 0 = 0.701X \\ \therefore Cr_{min} = 0 - 0.587X - 0.114X = -0.701X Cr=RYCr=R(0.299R+0.587G+0.114B)=R0.299R0.587G0.114B=0.701R0.587G0.114BR值最大GB值最小时,有最大值,在不同的格式中RGB的值不同,这里采取函数表达RGB的取值范围都是一样所以设X=max,最小值都为0Crmax=0.701X00=0.701XCrmin=00.587X0.114X=0.701X
所以上述可知Cr的值域为 [ − 0.701 X , 0.701 X ] [-0.701X,0.701X] [0.701X,0.701X],那么它的增量可以为
2 ∗ 0.701 X ∗ 10 = 1.402 X 2*0.701X*10 = 1.402X 20.701X10=1.402X

这里挖个坑,具体求值后面再来填,有点奇怪这里,需要参考一些其他资料

雷神这里的公式可以转换成功,那就先用着
Y = 0.299 R + 0.587 G + 0.114 B U = − 0.147 R + 0.289 G + 0.463 B V = 0.615 R − 0.515 G − 0.100 B Y = 0.299R+0.587G+0.114B \\ U = -0.147R +0.289G+ 0.463B\\ V = 0.615R-0.515G-0.100B Y=0.299R+0.587G+0.114BU=0.147R+0.289G+0.463BV=0.615R0.515G0.100B

//R\G\B : 1byte : unsigned : 0xff
unsigned int64 tmp = 66*r + 129*g + 25*b
//转换一下
//max = 66*0xff + 129*0xff + 26*0xff = 0xdc23
//min = 66*0 + 129*0 + 26*0 = 0
//可以看到由R、G、B转过来的Y值范围是[0,0xdc23],可能是1-2字节。最大也就是2个字节

unsigned int64 tmp2 = tmp + 128
//max = 0xdc23 + 0x80 = 0xdca3
//carry = 0xdb83 + 0x80 = 0xdc03
//min = 0 + 0x80 = 0x80
//解释一下为什么是+128,因为128是二进制的第8位,加了128就可以判断低位字节是否>=128,当>=128时则会产生进位,反之不影响高位字节

unsigned int64 tmp3 = tmp2 >> 8
//这里就是去掉了低字节
//Y = 0xdc03 >> 8 = 0xdc


//再回到公式 
//Y = 0.299R + 0.587G + 0.114B ,C=1
//Y = 66R + 129G + 25B, C=220
//它们的系数相加是1,

AVC视频码流解析

类型帧

I帧

Intra-coded picture(帧内编码图像帧

关键帧,可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成

I帧“关键帧”压缩法 : 基于离散余弦变换DCT(Discrete Cosine Transform)的压缩技术,这种算法与JPEG压缩算法类似。采用I帧压缩可达到1/6的压缩比而无明显的压缩痕迹。

当不是直播的时候一般B帧为连续2帧,从而降低码率

  • 每次收到I帧就可以清除上一个I帧

  • 在视频画面播放过程中,若I帧丢失了,则后面的P帧也就随着解不出来,就会出现视频画面黑屏的现象;若P帧丢失了,则视频画面会出现花屏、马赛克等现象。

P帧

Predictive-coded Picture(前向预测编码图像帧

P帧表示的是这一帧跟之前的一个关键帧(或P帧)的差别(基于预测值和运动矢量),解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终画面

B帧

Bidirectionally predicted picture(双向预测编码图像帧)

B帧不管在哪个位置都需要等后面一帧解完才能解码B帧,所以会带来编码延迟,因为B帧要等P帧解码完成

IBP
解码DTS132
显示PTS123

实现这个功能是通过下面的PTS、DTS的

B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确。所以比较节省内存

直播:

压缩和解码B帧时,由于要双向参考,所以它需要缓冲更多的数据,且使用的CPU也会更高,实时互动直播系统中,很少使用B帧。

GOP

编码器将多张图像进行编码后生产成一段一段的 GOP ( Group of Pictures )

解码器在播放时则是读取一段一段的 GOP 进行解码后读取画面再渲染显示。由一张 I 帧和数张 B / P 帧组成,是视频图像编码器和解码器存取的基本单位,它的排列顺序将会一直重复到影像结束

GOP可以理解为I帧间隔,一般直播GOP为1-2秒

GOP越长视频压缩效率越高,视频质量越差

直播时如果帧率为25,则一般GOP为25(1秒)、50(2秒)帧率的倍数

一帧图像可以分为多个slice通过系统线程并发编码从而加快编码速度

量化值和码率有关,量化越大图片损失越重,反之

网络好使用低码率编码进行传输,反之。所以可以使用动态修改码率,其实就是修改量化值

PTS、DTS

  • DTS(Decoding Time Stamp)解码时间戳

这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。

  • PTS(Presentation Time Stamp)显示时间戳

这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。

在没有B帧的情况下,存放帧的顺序和显示帧的顺序就是一样的,PTS和DTS的值也是一样的。

H264(AVC)

背景

  • ITU-T称为H.264
    • 该组织制定了H.120H.261H.263等标准
  • ISO称为AVC(Advanced Video Coding)
    • 该组织制定了MPEG-1MPEG-2MPEG-4 Part2等标准
  • 2003年两家组合联合起来成立JVT 2001组织
    • 2003年制定了H264/AVC
    • 2013年制定了H265/HEVC

以下简称h264是一款收费编码器,设备生产商的设备

  • <10w 免费
  • >=10w 0.2美元/台
  • >=500w 0.1美元/台(上限970w美元)

H.264和x264的区别:

  • H.264是一种编码标准,x264是符合H.264标准的一个开源项目是免费的。

    可以理解为一个社区版,但是不支持某些H.264的高级特性

    H.264是商业版

  • X264不支持硬件加速

NALU

在 H.264/AVC 视频编码标准中,NAL 单元(Network Abstraction Layer Unit)是视频编码数据的基本单位,它是一个完整的数据包,包含了视频编码数据的一部分。

NAL 单元被分为两类:

  • 一类是 VCL NAL 单元(slice下面会说)
  • 一类是非 VCL NAL 单元

VCL NAL 单元包括了编码后的视频图像数据,而非 VCL NAL 单元包含了控制信息、参数设置等其他数据。

一个NALU有很多种类型,比如在FFmpeg的h264dec.c源码中:

     switch (nal->type) {
          case H264_NAL_SLICE:
            //非 IDR 帧的 slice,也包含了完整的编码数据。
          case H264_NAL_DPA、H264_NAL_DPB、H264_NAL_DPC:
            //用于多切片编码,分别表示当前的前一个、后一个和后两个 slice 的数据。
          case H264_NAL_SEI:
            //包含了一些辅助信息,如时间戳等。
          case H264_NAL_PPS、H264_NAL_SPS_EXT:
            //包含了图像参数集和序列参数集的数据。
          case H264_NAL_AUD:
            //用于视频解码的同步,表示一个新的图像开始了。
          case H264_NAL_END_SEQUENCE、H264_NAL_END_STREAM、H264_NAL_FILLER_DATA:
            //用于结束一个视频序列或流。
          case H264_NAL_AUXILIARY_SLICE:
            //表示一个辅助的 slice,包含了一些额外的数据。
       }

结构

H264中没有音频、时间戳、只有图片信息,所以也是大家口中所说的码流、或者裸流,H264中分为两种不同的存储格式:1.AnnexB 2.avcC

注意:解码的环境下

  • ios中ViedeoToolbox只支持avcC格式
  • android中MediaCodec只支持AnnexB格式

AnnexB

它由一个个的NALU(Network Abstraction Layer Unit)组成,结构如下:

图中每个NALU之间通过startcode(起始码)进行分隔,起始码分为:

  • 00 00 01(3byte)
  • 00 00 00 01(4byte)

NALU对应的Slice为一帧数使用4byte的起始码,否则使用3byte的起始码分离出NALU

因为startcode可能会在码流中找到相同的字节,那么就会导致一个NALU被再次分割为消NALU,所以就出现了防竞争字节序

防竞争字节序

防竞争字节序就是在给NALU添加起始码之前,先对码流中的相同startcode进行字节替换

比如:码流中的000\001\002\003等先给它替换成为其他字节,最后再还原
00 ∣ 00 ∣ 00 → 00 ∣ 00 ∣ 03 ∣ 00 00 ∣ 00 ∣ 01 → 00 ∣ 00 ∣ 03 ∣ 01 00 ∣ 00 ∣ 02 → 00 ∣ 00 ∣ 03 ∣ 02 00 ∣ 00 ∣ 03 → 00 ∣ 00 ∣ 03 ∣ 03 00|00|00 \rightarrow 00|00|03|00\\ 00|00|01 \rightarrow 00|00|03|01\\ 00|00|02 \rightarrow 00|00|03|02\\ 00|00|03 \rightarrow 00|00|03|03\\ 00∣00∣0000∣00∣03∣0000∣00∣0100∣00∣03∣0100∣00∣0200∣00∣03∣0200∣00∣0300∣00∣03∣03

avcC

avcC主要用于mp4容器中。它采用的分割格式是在NALU前面的字节表示单个NALU的长度(大端模式)

在avcC格式中存在两个特殊的NALU即SPSPPS它们存放了在解码H264码流时必要的参数信息,在解码前必须获取到SPSPPS

avcC文件头部信息格式
bitsnamedescription
8version一般为1
8avc profile首个SPS的第1个字节
8avc compatibility首个SPS的第2个字节
8avc level首个SPS的第1个字节
6reserved
2NALULengthSizeMinusOneNULU长度-1(范围0~3)
3reserved
5number of SPS NALUs有几个SPS,一般为1
16SPS sizeSPS长度
SPS NALU dataSPS NALU的数据
SPS_2 size
SPS_2 NALU data
SPS_n size
SPS_n NALU data
8number of PPS NALUs有几个PPS,一般为1
16PPS sizePPS长度
PPS NALU dataPPS NALU的数据
NALULengthSizeMinusOne

NALULengthSizeMinusOne为3的时候,NALU的长度为4个字节,就是说NALU的前4个字节存放着它本身的数据长度值(大端模式、且该4个字节表示的值不包括该4个字节)

主体NALU

NALU中除去startCode的部分被称为EBSP数据比特串

NALU中除去startCode、防竞争字节的部分叫RBSP数据比特串

RBSP中除去补位bit的部分叫SODB数据比特串:

视频在编码是以bit为单位写入码流的(用户在输入时候是以字节为单位的),当写入的bit不满足8的倍数即剩余的bit不满一个byte。那么会在后续操作中不便于读取,所以在构建RBSP时会将最后不满一个byte的情况进行补齐,规则为:

先写入 1 bit 数据,数据内容是 1,然后开始补齐 0,直到补齐到一整个字节。

而当最后满了一个字节也会对它进行添加1一个字节操作00000001

最后的SODB就是裸码流数据

结构图如下:

NALU的第一个字节

nal_unit(NumButeslnNaLunit)Descriptor
forbidden_zero_bit1 bit静止位。当NALU丢失数据时为1,一般为0
nal_ref_idc2 bitNALU重要性
nal_unit_type5 bitNALU类型

nal_ref_idc存在以下值:

  • HIGHEST - 3
  • HIGH - 2
  • LOW - 1
  • DISPOSABLE - 0

当nal_ref_idc为0时则表示该NALU可有可无,即使丢失也不重要

nal_unit_type存在以下值:

nal_unit_typeNALU 类型
0未定义
1非 IDR SLICE
2非 IDR SLICE,采用 A 类数据划分片段
3非 IDR SLICE,采用 B 类数据划分片段
4非 IDR SLICE,采用 C 类数据划分片段
5IDR SLICE
6补充增强信息 SEI
7序列参数集 SPS
8图像参数集 PPS
9分隔符
10序列结束符
11码流结束符
12填充数据
13序列参数扩展集
14~18保留
19未分割的辅助编码图像的编码条带
20~23保留
24~31未指定

读取SPS信息

seq_parameter_set_data()CDescriptor
profile_idc0u(8)
constraint_set0_flag0u(1)
constraint_set1_flag0u(1)
constraint_set2_flag0u(1)
constraint_set3_flag0u(1)
constraint_set4_flag0u(1)
constraint_set5_flag0u(1)
reserved_zero_2bits0u(2)
level_idc0u(8)
seq_parameter_set_id0ue(8)

slice

H.264 视频编码标准中,slice 是 VCL NAL 单元中的一个类型,包含了编码后的视频图像数据的一部分。每个 slice 包含了一个或多个宏块(macroblock),它们是编码后的视频图像数据的最小单位。

一个 NAL 单元和一个 slice 的区别在于:

  1. NAL 单元是视频编码数据的基本单位,而 slice 是包含了视频图像数据的一部分的 NAL 单元类型
  2. 每个 slice 包含了一个或多个宏块,而一个 NAL 单元可以包含一个或多个 slice。

宏块

H.264 中,不会逐个像素去处理图像,而是按照固定的长宽划分成小块称之为宏块

当像素为176x144时,规定一个宏块为16x16像素,那么就会划分为11x9的宏块

这里的格式是 宽x高,不是矩阵中的 行x列 ,刚好相反

HEVC视频码流解析

AV1视频码流解析

全称:AOMedia Video 1

文件后缀.avif或者.av1 .而.webm文件则是将AV1视频编码器和WebM音频编码器结合使用的文件格式

类型:一个开放、免专利的视频编码格式,专为通过网络进行[流传输,即流媒体]而设计

背景:2018年取代其前身Gooole的VP9,也就是基于VP9开发的一个分支,AV1所使用的编码技术主要来源于谷歌VP10

编码器:

  • rav1e:以牺牲编码效率为代价,成为最简单、最快符合AV1标准的视频编码器

起始由Mozilla(旗下Firefox浏览器)主导,现在主要由VideoLan接管的AV1开源编码器

  • SVT-AV1

由Intel、Netflix主导的AV1开源编解码器,目前已被AOM 确定为基本代码库,致力于开发可落地实用场景的AV1软件编解码器

  • libaom->aomenc

AV1标准制定过程中的主要开源编解码代码库,由AOM联合打造、维护,是目前AV1工具实现最完整的一款开源软件编解码器

  • Aurora(微帧)

解码器:

  • libaom->aomdec
  • SVT-AV1
  • libgav1(google,特别适合Android)
  • DAV1D(videolan\ffmpeg,AOM资助)

业内公认的性能优秀的AV1开源软件解码方案

支持:

  • 硬件(2020以来)
    • Intel、AMD、NVIDIA、MediaTek、SAMSUNG
    • Apple暂不支持
  • 软件
    • Firefox浏览器,Chromium浏览器内核,微软Windows10平台,Android Q系统。FFmpeg也早已支持AV1
  • 视频
    • YouTube、Netflix、爱奇艺,爱奇艺也于今年4月上线AV1,成为国内首家应用AV1视频网站

FFmpeg:

https://www.cnblogs.com/cjdty/p/16904134.html

#coufigure : --enable-libaom

ffmpeg -i test.webm  -c:v libx264 -c:a libfdk_aac -f mp4 test.mp4 -y

AV1、HEVC编解码格式对比

https://www.bilibili.com/read/cv20079800

AV1确实有着取代HEVC的实力:

  • 质量上超越HEVC的同时,编码耗时也比HEVC短很多,甚至比H264耗时还要短一点。
  • AV1在低码率下的压缩率非常高,这也让它非常适用于流媒体。
  • 相对应的HEVC,虽然在低码率下比H264质量高很多,但是一旦码率超过一万,其对比H264的提升就微乎其微了,并且导出耗时远高于H264

AV1与HEVC的结论

  • 对于相同的图像质量,AV1可以节省高达30%的文件大小(压缩率高
  • AV1编解码器提供比H.265/HEVC更好的图像质量(质量高
  • AV1编解码器需要HEVC/H.265编码时间的3倍(编解码时间慢)
  • HEVC由AMD、英伟达、英特尔、苹果、高通等的GPU/CPU支持。对AV1解码的支持已经在这里,但到目前为止,AV1编码仅由英伟达和英特尔支持。(兼容差

未来

当HEVC发布的H.265比流行的H.264高50%时,H.264并没有立即死亡并过时

音频

音频的常见格式:

  • 原始格式: 原始格式通常不经过压缩,保留了音频的所有信息,常见的原始格式包括:

    • PCM(脉冲编码调制):是一种最基本的音频格式,是数字音频的基础,通常没有压缩,采用的是无损编码。

    • WAV(Waveform Audio File Format):一种由Microsoft开发的无损音频文件格式,基于PCM编码,广泛应用于Windows平台。

    • AIFF(Audio Interchange File Format):一种由Apple公司开发的无损音频文件格式,也基于PCM编码,主要应用于Mac平台。

  • 压缩格式: 压缩格式通常是为了减小文件大小而采用的,其中有损压缩和无损压缩两种方式,常见的压缩格式包括:

    • MP3(MPEG-1 Audio Layer III):一种有损压缩音频格式,采用的是基于FFT的压缩算法,常用于音乐和语音的存储和传输。

    • AAC(Advanced Audio Coding):一种有损压缩音频格式,采用了更为先进的压缩算法,是当前最常用的音频压缩格式之一,广泛应用于音乐、电影、手机等领域。

    • WMA(Windows Media Audio):一种由Microsoft开发的音频压缩格式,支持有损和无损压缩,主要用于Windows平台。

  • 其他格式: 除了以上两种常见格式外,还有一些其他格式,如:

    • FLAC(Free Lossless Audio Codec):一种无损压缩音频格式,可以在不降低音频质量的前提下减小文件大小,适合用于高品质音频的存储和传输。

    • OGG(Ogg Vorbis):一种免费开源的音频压缩格式,采用无损压缩方式,常用于网络音乐和在线播放。

    • DSD(Direct Stream Digital):一种超高保真度的无损音频格式,采用1比1的编码方式,常用于SACD(超级音频CD)等高端音频存储媒介。

ffplay播放一个pcm需要指定采样率、采样格式、声道数,比如从下面mov文件中提取的pcm:

$ mediainfo input.mov
//...
Audio
ID                                       : 2
Format                                   : PCM
Format settings                          : Little / Signed
Codec ID                                 : lpcm
Duration                                 : 2 s 135 ms
Source duration                          : 2 s 138 ms
Bit rate mode                            : Constant
Bit rate                                 : 705.6 kb/s
Channel(s)                               : 1 channel //声道数
Sampling rate                            : 44.1 kHz  //采样率
Bit depth                                : 16 bits   //采样格式
Stream size                              : 184 KiB (7%)
Source stream size                       : 184 KiB (7%)
Title                                    : Core Media Audio
Encoded date                             : UTC 2023-03-09 13:47:05
Tagged date                              : UTC 2023-03-09 13:47:07

可以看出该PCM文件为单声道、44.1kHz采样率、16位采样格式

$ ffplay -f s16le -ar 44100 -ac 1 output.pcm
  • -f : 指定输入文件的格式为16位有符号整数(即s16le)

  • -ar : 指定采样率为44100Hz

  • -ac : 指定声道数为1

常用知识

音视频常见单词

super-resolution
compression
master
quality

音视频常见名词

码率 : kb/s 视频文件在单位时间内使用的数据流量

帧率 : fps一秒钟显示的帧数,帧率越高画面越流畅

分辨率 : 影响视频图像的大小

#修改帧率
ffmpeg -i test_video.mp4 -r 15 output.mp4 #导致画面卡顿,ppd
#修改码率
ffmpeg -i test_video.mp4 -b:v 0.5M output2.mp4 #会导致画面模糊
#修改分辨率
ffmpeg -i test_video.mp4 -s 640x480 output.mp4 #导致窗口变小

2k指的是2048x1080,2k和1080p分辨率差不多,大一点

当年的1920x1080 比这个标准略小,只能称为准2K,后来显示器出现了2560x1440这样的分辨率,它大于2048x1080,所以可以完整显示2K分辨率,而在当时,能完整显示2K电影分辨率的只有 2560x1440 这一个主流分辨率,后来以讹传讹,2560x1440就被称为了2K。

序号图像分辨率图像容器
240p426x240
360p640x360
480p854x480
540p960x540
720P1280x720HD(高清)
1080P1920x1080FHD(全高清)
2K2048x1080QHD(四倍高清)
4K3840x2160UHD(超高清)
8k7680x4320
5k5120x2880

慢速档位 : 每秒1帧
快速档位 : 每秒30帧

常见画质对比数值 :

  • PSNR(Peak Signal-to-Noise Ratio-峰值信噪比) : 峰值信号的能量与噪声的平均能量之比,本质的是比较两张图像像素值差异,用途较广,目前仍作为对照其他指标的基线。PSNR的单位是dB,数值越大表示失真越小(传送门👆->#计算两个YUV420P像素数据的PSNR。
  • SSIM(Structural Similarity Index-结构相似性) : 一种全参考的图像质量评价指标,分别从亮度、对比度、结构三方面度量图像相似性。SSIM取值范围为[0,1],值越大,表示图像失真越小。
  • VMAF(Video Multi-method Assessment Fusion-视频多评估方法融合) : 由Netflix推出的视频质量评价工具,用来解决传统指标不能反映多种场景、多种特征的视频情况。该指标是目前互联网视频最主流的客观视频评价指标,适用于衡量大规模环境中流播视频质量的观感。

ffmpeg硬件加速

以下为提供硬件编码的框架:

Intel

  • QSV(libmfx)。从SDK到driver都己开源。同时支持Windows和Linux,Intel主推方案
  • VAAPI:Linux开源接口,可支持AMD/Nvida驱动,FFmpeg社区比较喜欢

Nvidia

  • NVENC/CUVID/NVDEC
  • VDPAU : 基本不维护

AMD

  • AM on Windows
  • VAAPI on Linux

厂商

  • Windows : D3D9 (DXVA2 ) , D3D11
  • Android : MediaCodec , OpenMax
  • Apple : VideoToolbox

因为解码只能靠CPU,而且一般硬编码压缩不如软编码好,码率相同情况下质量明显下降,比如VideoToobox实测如下:

ffmpeg -hwaccels
#显示所有可用的硬件加速器

测试视频信息:

#input.mp4:
#width=2880
#height=1800
#codec_name=h264
#pix_fmt=yuv420p
#nb_frames=4000
#bit_rate=5682608
#intput.mp4大小113M

测试命令:

ffmpeg -i intput.mp4 -pix_fmt yuv420p10le  demo_no_gpu.h265 #软编h265
ffmpeg -i intput.mp4 -pix_fmt yuv420p10le -c:v hevc_videotoolbox  demo.h265 #硬编h265

ffmpeg -i intput.mp4 -pix_fmt yuv420p10le demo_no_gpu.h264 #软编h264
ffmpeg -i intput.mp4 -pix_fmt yuv420p10le -c:v h264_videotoolbox  demo.h264 #硬编h264

#至于为什么使用yuv420p10le 是因为h265编码时会报错,264不会 
#看这里https://stackoverflow.com/questions/69777429/hevc-videotoolbox-encoding-failed

测试结果:

编码器耗时编码后大小
x265276.05 s8.3M
hevc_videotoolbox75.97 s31M
x264114.9017M
h264_videotoolbox28.08 s22M

常用命令

ffmpeg -i demo.avi -c:v libx264 -crf 22 out.mp4
#crf表示控制压缩图像质量,取之范围0-51,范围越高质量越差,即0表示无损压缩,常用范围是19-28

ffmpeg -i demo.avi -c:v libx264 -vf "scale=1024:-1,transpose=1" out.mp4
#vf(video filter)指定过滤器
#scale-修改视频尺寸(-1表示自动推算)
#transpose-旋转视频
#crop-裁剪

#对于24帧每秒的视频 128帧是第5秒的第四帧
ffmpeg -i a.mp4 -ss 00:00:05.167 -f image2 -r 1 -t 1 -s 256*256 /home/pic.jpeg
#附注 没有-r会截取25张图片,此时-ss指定时间不指定毫秒时,会截取该秒内所有帧,否则会截取同一毫秒的25张图片

ffmpeg -f image2 -i image%d.jpg video.mpg
#当前目录下的图片(名字如:image1.jpg, image2.jpg, 等...)合并成video.mpg

ffmpeg -i source_video.avi -vn -ar 44100 -ac 2 -ab 192 -f mp3 sound.mp3
#视频抽出声音

ffmpeg -ss 00:00:30 -i 666051400.mp4 -vframes 1 0.jpeg
#抽取时间段的帧

ffmpeg -loglevel quiet -i d1.mp4 -c copy -frames:v 10000 input.mp4 < /dev/null
#提取前10000帧

ffmpeg -i input.mp4 -an -vf select='eq(pict_type\,I)' -vsync 2 -f image2 image-%001d.jpeg
#提取I帧

ffmpeg -i out.mp4 -an -vf select='eq(pict_type\,I)' -vsync 2 -f image2 -qscale:v 2 image%004d.jpeg
#质量更好

ffmpeg -ss 00:12:07 -to 0:17:33 -i input.mp4 -c copy out.mp4
#截取时间段视频

ffmpeg -ss 00:12:07 -t 60 -i input.mp4 -c copy out.mp4
#截取持续时间 单位s

ffmpeg -hwaccels
#显示所有可用的硬件加速器

一般一样格式的视频不需要进行重新编码

#ffplay 默认打开文件是I420格式也就是yuv420p格式,任何文件也是这个格式,打开rbg也是默认I420
ffplay -s 1920x1080 -pixel_format rgb24 demo.rgb

注意的细节

  1. ffmpeg解析中包括了mp4的头部分装信息,而ffprobe不会解析头部信息
  • 使用ffmpeg -i xx.mp4 查看视频码率得到如下值
ffmpeg -i src2.mp4
#Duration: 00:00:03.73, start: 0.000000, bitrate: 400 kb/s
  • 使用ffprobe
ffprobe -loglevel quiet -select_streams v -show_streams src2.mp4 | grep ^bit_rate
#bit_rate=380916
  1. 在FFMpeg中,kb就是1000bit的意思,b转换到kb是/1000而不是/1024。

从 libavcodec/avcodec.c 中的 avcodec_string() 所定义的码率信息打印操作的代码可以知道,在 ffmpeg 中,1kb 的确为 1000 bit。

bitrate = get_bit_rate(enc);
if (bitrate != 0) {
    av_bprintf(&bprint, ", %"PRId64" kb/s", bitrate / 1000);
} else if (enc->rc_max_rate > 0) {
    av_bprintf(&bprint, ", max. %"PRId64" kb/s", enc->rc_max_rate / 1000);
}

具体解释 :https://wangwei1237.github.io/2022/06/19/Does-1Kbit-equal-1024bit/

  1. h265比h264、mp4的占用空间率要小

测试文件src.mp4

[STREAM]
index=0
codec_name=h264
codec_long_name=H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
profile=High
codec_type=video
codec_time_base=1/50
codec_tag_string=avc1
codec_tag=0x31637661
width=1920
height=1080
coded_width=1920
coded_height=1088
has_b_frames=2
sample_aspect_ratio=N/A
display_aspect_ratio=N/A
pix_fmt=yuv420p
level=40
chroma_location=left
field_order=unknown
timecode=N/A
refs=1
is_avc=true
nal_length_size=4
id=N/A
r_frame_rate=25/1
avg_frame_rate=25/1
time_base=1/25
start_pts=0
start_time=0.000000
duration_ts=468
duration=18.720000
bit_rate=4999281
max_bit_rate=N/A
bits_per_raw_sample=8
nb_frames=468
TAG:creation_time=2020-09-18T00:36:51.000000Z
TAG:language=und
TAG:handler_name=L-SMASH Video Handler
TAG:encoder=AVC Coding
[/STREAM]

首先将src.mp4转成tmp.yuvtmp.h264tmp.h265以及无损的tmp.mp4

$ du src.mp4                                   (master*) [~/h/ios_test]
12M	src.mp4
$ ffmpeg -i src.mp4  tmp2.yuv                  (master*) [~/h/ios_test]
$ ffmpeg -s 1920x1080 -i tmp.yuv -crf 0  tmp.h264
$ ffmpeg -s 1920x1080 -i tmp.yuv -crf 0  tmp.h265

$ du tmp.* src.mp4                             (master*) [~/h/ios_test]
159M	tmp.mp4
159M	tmp.h264
109M	tmp.h265
1.4G	tmp.yuv
 12M	src.mp4

可以看到h265编码占有空间最小

H264画质级别

//https://www.cnblogs.com/tinywan/p/6402007.html
先科普一下profile&level。(这里讨论最常用的H264) 
H.264有四种画质级别,分别是baseline, extended, main, high: 
  1、Baseline Profile:基本画质。支持I/P 帧,只支持无交错(Progressive)和CAVLC; 
  2、Extended profile:进阶画质。支持I/P/B/SP/SI 帧,只支持无交错(Progressive)和CAVLC;(用的少) 
  3、Main profile:主流画质。提供I/P/B 帧,支持无交错(Progressive)和交错(Interlaced), 
    也支持CAVLC 和CABAC 的支持; 
  4、High profile:高级画质。在main Profile 的基础上增加了8x8内部预测、自定义量化、 无损视频编码和更多的YUV 格式; 
H.264 Baseline profile、Extended profile和Main profile都是针对8位样本数据、4:2:0格式(YUV)的视频序列。在相同配置情况下,High profile(HP)可以比Main profile(MP)降低10%的码率。 
根据应用领域的不同,Baseline profile多应用于实时通信领域,Main profile多应用于流媒体领域,High profile则多应用于广电和存储领域。

下图清楚的给出不同的profile&level的性能区别。

ffmpeg结构

ffmpeg
libav_codec
libav_format
libav_filter
libav_device
libav_util
libsw_resample
libsw_scale
lib_postproc
文件格式和协议库(Protocol\Demuxer\Muxer层)
音频重采样,对数字音频声道数、数据格式、采样率等信息转换
封装了Codec层-为开发者提供第三方Codec
音视频滤镜库
输入输出设备库-比如编译了ffplay
核心代码库
图像格式转换-可YUV_2_RGB
进行后期处理-如filter会用

库里还可以包含对 H.264/MPEG-4 AVC 视频编码的 X264 库,是最常用的有损视频编码器,支持 CBR、VBR 模式,可以在编码的过程中直接改变码率的设置,在直播的场景中非常适用!可以做码率自适应的功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/474338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双指针技巧总结

一、双指针技巧——情景1 通常&#xff0c;我们只需要一个指针进行迭代&#xff0c;即从数组中的第一个元素开始&#xff0c;最后一个元素结束。然而&#xff0c;有时我们会使用两个指针进行迭代。 双指针的典型场景 (1)从两端向中间迭代数组。 (2)一个指针从头部开始&#…

Spark RDD (Resilient Distributed Datasets) 弹性分布式数据集

设计需求 Spark RDD 的设计目的是为了实现快速、可扩展和容错的数据处理。它是一个不可变的分布式数据集&#xff0c;可以在集群中分布式存储和处理数据。RDD 提供了一系列操作来处理数据&#xff0c;包括转换操作和行动操作。转换操作可以将一个 RDD 转换为另一个 RDD&#x…

Arduino学习笔记4

一.声控灯实验 1.源代码 int led2;//定义板子上数字2口控制小灯 int flag0;//定义一个变量记录小灯是亮起还是熄灭 int shengyin3;//定义声音传感器的控制口void setup() {pinMode(led,OUTPUT);//定义小灯为输出模式pinMode(shengyin,INPUT);//定义声音控制口为输入模式 } vo…

python+Django社区疫情防控系统 uniapp微信小程序

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

[jenkins自动化2]: linux自动化部署方式之流水线(下篇)

目录 1. 引言: 2. 进阶操作 流水线 -> 2.1 简介: -> 2.2 最终效果图展示: -> 2.3 有没有心动, 真的像流水线一样, 实现了一键部署启动 3. 实现方式 3.1 下载几个插件 3.2 创建流水线任务 3.3 点击配置 3.4 根据流水线语法 写一个简单的helloworld 3.5 执行…

数字化医院PACS影像系统 三维影像后处理技术应用

PACS影像存取与传输系统以实现医学影像数字化存储、诊断为核心任务&#xff0c;从医学影像设备&#xff08;如CT、CR、DR、MR、DSA、RF等&#xff09;获取影像&#xff0c;集中存储、综合管理医学影像及病人相关信息&#xff0c;建立数字化工作流程。 PACS系统可实现检查预约、…

【C++入门】内联函数

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 目录 什么是内联函数内联函数特性 什么是内联函数 内联函数概念&#xff1a; 内联函数就是以inline修饰的函数叫做内联函数&a…

No.051<软考>《(高项)备考大全》【冲刺5】《软考之 119个工具 (3)》

《软考之 119个工具 &#xff08;3&#xff09;》 41.进度计划编制工具:42.绩效审查:43.卖方投标分析:44.质量成本:45.成本汇总:46.历史关系:47.资金限制平衡:48.挣值管理:49.预测:50.完工尚需绩效指数:51.成本效益分析:52.试验设计:53.七种基本质量工具:54.统计抽样:55.其他质…

Linux拓展:链接库

一.说明 本篇博客介绍Linux操作系统下的链接库相关知识&#xff0c;由于相关概念已在Windows下链接库一文中介绍&#xff0c;本篇博客直接上操作。 二.静态链接库的创建和使用 1.提前看 这里主要介绍的是C语言的链接库技术&#xff0c;而在Linux下实现C语言程序&#xff0c…

Web入门脚本三:一键完成与dex的交互,羊毛党必备

前言 该脚本用途&#xff1a;一键可以完成与dex的所有交互&#xff0c;包括2次swap&#xff0c;添加/移除流动性&#xff0c;以及farm和提取LP。一次运行可以有6条交易记录。 无论是个人单刷还是羊毛党批量地址刷交互都完美适配。当然反女巫方案不在这次文章的讨论范围内。 一、…

Python快速入门,看这一篇就够了

大家好&#xff0c;我是老三&#xff0c;我最近在看一些人工智能相关的东西&#xff0c;大部分相关的框架&#xff0c;都是用Pyhon写的。 老三会用Python&#xff0c;但谈不上多熟练&#xff0c;最近准备拿Python作为自己的第二语言&#xff0c;那么这期我们来一起快速梳理一下…

程序员该如何学习技术

程序员该如何学习技术 前言 学习是第一生产力&#xff0c;我从来都是这么认为的&#xff0c;人只有只有不断地学习才能意识到自己的缺点和不足&#xff0c;身为程序员&#xff0c;我更认为人们应当抱着终身学习的想法实践下去&#xff0c;这是我所一直践行且相信的。 高处不胜寒…

体验 GPT-4 后,回顾 OpenAI 发展历程及感悟

从 ChatGPT Plus 发布第一天就开始重度使用&#xff0c;刚刚和新发布的 GPT-4 进行了 20 多轮对话&#xff0c;来简单介绍下这几个模型背后的技术&#xff0c;并且分享下感受。 GPT 在发展历程中&#xff0c;一共经历了 4 个阶段&#xff0c;分别是 1、2、3、4。这几个阶段分别…

【VM服务管家】VM4.x算法模块开发_4.3 联合Halcon开发

目录 4.3.1 联合开发&#xff1a;集成HALCON第三方算子到VM工具箱的方法 4.3.1 联合开发&#xff1a;集成HALCON第三方算子到VM工具箱的方法 描述 环境&#xff1a;VM4.0及以上 VS2013 问题&#xff1a;有的用户在使用VisionMaster软件在开发视觉项目时&#xff0c;可能同时也…

来了来了,我使用 ChatGPT 开发了一个 AI 应用

ChatGpt 实在太火爆了&#xff0c;很多人在问我怎么使用 chatgpt 开发一个 AI 应用程序。这不就来了吗~ 开始 你所需要准备的一个OpenAI 的密钥和一点点代码来发送提示并返回结果&#xff0c;例如下面这段代码&#xff1a; import { OpenAIApi, Configuration } from openai…

基于RAM树莓派实现智能家居:语音识别控制,Socket网络控制,火灾报警检测,实时监控

目录 一 项目说明 ① 设计框架 ② 功能说明 ③ 硬件说明 ④ 软件说明 二 项目代码 <1> mainPro.c 主函数 <2> InputCommand.h 控制设备头文件 <3> contrlDevices.h 外接设备头文件 <4> bathroomLight.c 泳池灯 <5> livin…

GraphQL(三)DataLoader 详解

DataLoader是一个通用实用程序&#xff0c;用作应用程序数据获取层的一部分&#xff0c;通过批处理和缓存为各种远程数据源&#xff08;如数据库或 Web 服务&#xff09;提供简化且一致的 API 批处理 const user await userLoader.load(1); const invitedBy await userLoade…

【C++】7. auto和nullptr(c++11)

文章目录 一、auto二、nullptr 一、auto 在C98中&#xff0c;auto是一个存储类说明符&#xff0c;表示变量具有自动存储期&#xff0c;即在函数或块的作用域内创建和销毁。 在C11中&#xff0c;auto是一个类型占位符&#xff0c;表示变量的类型由其初始化器自动推断。 使用如下…

分类和扩展与继承

文章目录 [TOC](文章目录) 分类定义分类的使用使用场景使用注意点 Extension 扩展分类和扩展的区别 继承的定义使用注意点 新建一个分类 分类基础知识 分类 分类是指为已有的类添加方法&#xff0c;也可以说是将很多很复杂的代码划分为几个分区。 定义 分类的作用是扩展已有…

第十四届蓝桥杯大赛软件赛省赛 Java 大学 B 组题解

试题 A: 阶乘求和 本题总分&#xff1a;5 分 【问题描述】 令 S 1! 2! 3! ... 202320232023!&#xff0c;求 S 的末尾 9 位数字。 提示&#xff1a;答案首位不为 0。 【答案提交】 这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一 个整数&am…