H264/AVC-帧内预测相邻像素推导过程

帧内预测过程会以相邻块的像素值做参考,来预测当前块的像素值。以Intra_4x4为例,如下图所示,需要用到的13个相邻像素值,那么如何获取这13个像素值?
在这里插入图片描述
本文要主要说明如何获取帧内预测所用到的相邻像素。对应参考文档6.4.5-6.4.9小节内容。

获取相邻像素的流程如下:

  1. 找到当前块(可以为4x4、8x8、16x16大小)的左、上、右上、左上相邻块
  2. 找到左、上、右上、左上相邻块所在宏块
  3. 根据当前宏块以及相邻块所在宏块确定相邻像素

在此之前需要先知道宏块地址是否可用,如果宏块地址不可用,则在该宏块的相邻像素也都不可用。

1.宏块地址可用性推导

宏块不可用主要有以下三种情况:

  1. mbAddr<0,此时该宏块不存在
  2. mbAddr>CurrMbAddr,也就是在当前解码宏块之后的宏块,显然后面的宏块还为解码,不可用做预测
  3. mbAddr和CurrMbAddr属于不同slice

2.相邻宏块地址及其可用性的推导

分为两种情况:

  1. MbaffFlag=0
  2. MBaffFlag=1

2.1 非MBAFF宏块(帧宏块或场宏块)

mbAddrA:表示当前宏块左侧宏块的地址和可用性状态。
mbAddrB:表示当前宏块上侧宏块的地址和可用性状态。
mbAddrC:表示当前宏块右上侧宏块的地址和可用性状态。
mbAddrD:表示当前宏块左上侧宏块的地址和可用性状态。
下图表示当前宏块和四个相邻宏块的空间位置
在这里插入图片描述

2.2 MBAFF宏块

对于MBAFF格式,是以宏块对方式成对出现,一个宏块对包含顶宏块和底宏块。
帧宏块对和场宏块对数据存放方式也有差异。
一个宏块对亮度分量有32行数据,如果当前宏块对为帧宏块,则顶帧宏块由1-16行数据组成,底帧宏块由17-32行数据组成;
如果当前宏块对为场宏块,则顶场宏块为奇数行数据组成,底场宏块为偶数行数据组成。

mbAddrA:表示当前宏块左侧宏块对中顶宏块的地址和可用性状态。
mbAddrB:表示当前宏块上侧宏块对中顶宏块的地址和可用性状态。
mbAddrC:表示当前宏块右上侧宏块对中顶宏块的地址和可用性状态。
mbAddrD:表示当前宏块左上侧宏块对中顶宏块的地址和可用性状态。
下图表示当前宏块和四个相邻宏块的空间位置,不管当前宏块是顶宏块还是底宏块,四个相邻宏块都是相同的。
在这里插入图片描述

3.相邻宏块、块的推导过程

帧内预测所用的块大小有三种情况(16x16、8x8、4x4)。
以8x8块为例,如下图所示,当前宏块分为4个8x8块。

idx=0的8x8块,左相邻块为mbAddrA中idx为1的8x8块,如果mbAddrA不可用,则左相邻块也不可用,
上相邻块为mbAddrB中idx为2的8x8块,
左上相邻块为mbAddrD中idx为3的8x8块。

idx=3的8x8块,左相邻块为当前宏块中idx为2的8x8块,
上相邻块为当前宏块中idx为1的8x8块,
左上相邻块为当前宏块中idx为0的8x8块。
在这里插入图片描述
因此,相邻块的推导过程,需要先找到相邻块所在宏块,确定宏块是否可用。

4.相邻像素推导过程

本过程的输入为相对于当前宏块左上角样点位置的亮度或色度位置(xN,yN)。
本过程的输出为:
mbAddrN:等于CurrMbAddr或等于包含(xN,yN)的相邻宏块的地址,及其可用性,
( xW, yW ):表示与宏块mbAddrN左上角的相对(不是相对于当前宏块左上角)位置(xN,yN)。

比如xN=-1,yN=-1,表示获取当前宏块左上角相邻像素,显然(xN,yN)像素所在宏块为左上角宏块mbAddrD,( xW, yW )=(15, 15).

相邻像素推导根据当前宏块是否为MBAFF分为两种情况。

4.1场和非MBAFF帧中相邻像素

MbaffFlag=0时,相对简单相邻像素的推导如下表所示。
在这里插入图片描述

4.2 MBAFF帧中相邻像素

MbaffFlag=1时,要根据当前宏块帧/场属性、顶/底场属性以及相邻块帧/场属性、顶/底场属性做不同的处理,相对复杂。
以xN=-1,yN=-1为例,也就是得到左上相邻像素。
其相邻像素可以由下表获得:
在这里插入图片描述
currMbFrameFlag,表示当前宏块是否为帧宏块
mbIsTopMbFlag,表示当前宏块为顶宏块还是底宏块
mbAddrX,表示相邻宏块对,还不能确定最终相邻宏块是使用顶宏块还是底宏块
mbAddrXFrameFlag,表示相邻宏块是否为帧宏块
mbAddrN,表示最终的相邻宏块

相邻像素推导可分为四个步骤:

  1. 根据currMbFrameFlag和mbIsTopMbFlag确定当前宏块左上角像素在帧图像中对应的像素点;
  2. 根据当前宏块的属性来确定相邻像素点;
  3. 确定相邻像素点所属宏块对mbAddrX;
  4. 根据相邻宏块对的属性mbAddrXFrameFlag来确定相邻像素点所属宏块mbAddrN;

在这里插入图片描述
根据当前宏块帧、场格式以及顶、底属性分四种情况分析
1.当 currMbFrameFlag = 1,mbIsTopMbFlag = 1,也就是当前宏块为帧宏块,并且为顶宏块

  1. 确定当前宏块左上角像素在帧图像中对应的像素点:
    currMbFrameFlag = 1,mbIsTopMbFlag = 1:当前宏块为顶帧宏块,其左上角像素的对应像素点为 c
  2. 根据当前宏块的属性来确定相邻像素点:
    因为当前宏块为顶帧宏块,所以 c 的左上角相邻像素点为 a
  3. 确定相邻像素点所属宏块对:
    因为宏块对是用顶宏块的地址表示,所以像素点 a 所属宏块对为 mbAddrD,因此 mbAddrX = mbAddrD
  4. 根据相邻宏块对的属性来确定相邻像素点所属宏块:
    (1)、如果 mbAddrX 是帧宏块对(即mbAddrXFrameFlag = 1),像素点 a 属于宏块 mbAddrD+1,所以mbAddrN = mbAddrD+1
    (2)、如果 mbAddrX 是场宏块对(即mbAddrXFrameFlag = 0),像素点 a 在场图像时(即进行隔行抽取后)属于宏块mbAddrD+1
    ,因此mbAddrN = mbAddrD+1

2. 当 currMbFrameFlag = 1,mbIsTopMbFlag = 0,也就是当前宏块为帧宏块,并且为底宏块

  1. 确定当前宏块左上角像素在帧图像中对应的像素点:
    currMbFrameFlag = 1,mbIsTopMbFlag = 0:当前宏块为低帧宏块,其左上角像素的对应像素点为 f
  2. 根据当前宏块的属性来确定相邻像素点:
    因为当前宏块为底帧宏块,所以 f 的左上角相邻像素点为 e
  3. 确定相邻像素点所属宏块对:
    因为宏块对是用顶宏块的地址表示,所以像素点 e 所属宏块对为 mbAddrA,因此表 6-4 中 mbAddrX = mbAddrA
  4. 根据相邻宏块对的属性来确定相邻像素点所属宏块:
    (1)、如果 mbAddrX 是帧宏块对(即mbAddrXFrameFlag = 1),像素点 e 属于宏块 mbAddrA,因此mbAddrN = mbAddrA
    (2)、如果 mbAddrX 是场宏块对(即mbAddrXFrameFlag = 0),像素点 e 在图像的偶数行,属于底场宏块 mbAddrA+1,因此mbAddrN = mbAddrA+1

3.当 currMbFrameFlag = 0,mbIsTopMbFlag = 1,当前宏块为顶场宏块

1、确定当前宏块左上角像素在帧图像中对应的像素点:
currMbFrameFlag = 0,mbIsTopMbFlag = 1:当前宏块为顶场宏块,其左上角像素的对应像素点为 c
2、根据当前宏块对的属性来确定相邻像素点:
因为当前宏块为顶场宏块,所以 c 的左上角相邻像素点为 b
3、确定相邻像素点所属宏块对:
因为宏块对是用顶宏块的地址表示,所以像素点 b 所属宏块对为 mbAddrD,因此 mbAddrX = mbAddrD
4、根据相邻宏块对的属性来确定相邻像素点所属宏块:
(1)、如果 mbAddrX 是帧宏块对(即mbAddrXFrameFlag = 1),像素点 b 属于宏块 mbAddrD+1,因此mbAddrN = mbAddrD+1
(2)、如果 mbAddrX 是场宏块对(即mbAddrXFrameFlag = 0),像素点 b 在场图像奇数行,属于顶场宏块 mbAddrD,因此mbAddrN = mbAddrD

4.当 currMbFrameFlag = 0,mbIsTopMbFlag = 0,当前宏块为底场宏块

1、确定当前宏块左上角像素在帧图像中对应的像素点:
currMbFrameFlag = 0,mbIsTopMbFlag = 0:当前宏块为低场宏块,其左上角像素的对应像素点为 d
2、根据当前宏块对的属性来确定相邻像素点:
因为当前宏块为低场宏块,所以 d 的左上角相邻像素点为 a
3、确定相邻像素点所属宏块对:
因为宏块对是用顶宏块的地址表示,所以像素点 a 所属宏块对为 mbAddrD,因此表 6-4 中 mbAddrX = mbAddrD
4、根据相邻宏块对的属性来确定相邻像素点所属宏块:
无论 mbAddrX 是帧宏块对还是场宏块对(即无论mbAddrXFrameFlag 值为多少),像素点 a 都属于宏块 mbAddrD+1
(1)、如果 mbAddrX 是帧宏块对(即mbAddrXFrameFlag = 1),像素点 a 属于宏块 mbAddrD+1,因此mbAddrN = mbAddrD+1
(2)、如果 mbAddrX 是场宏块对(即mbAddrXFrameFlag = 0),像素点 a 在场图像偶数行,属于底场宏块 mbAddrD+1,因此mbAddrN = mbAddrD+1

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用H.264/AVC压缩格式对视频流进行压缩,你可以使用FFmpeg库来实现。下面是一个基本的示例,展示了如何使用FFmpeg库对视频流进行压缩: 1. 安装FFmpeg库:首先,确保你已经安装了FFmpeg库。你可以从官方网站下载并按照说明进行安装。 2. 编写C++代码:创建一个C++源文件,并使用以下代码来实现视频流的压缩: ```cpp #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <sstream> #include <fstream> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> } int main() { AVFormatContext* inputContext = nullptr; AVFormatContext* outputContext = nullptr; AVCodecContext* codecContext = nullptr; AVStream* stream = nullptr; AVPacket packet; const char* inputFilename = "your_input_video_file"; // 输入视频文件名 const char* outputFilename = "your_output_compressed_video_file.h264"; // 输出压缩后的视频文件名 av_register_all(); // 打开输入视频文件 if (avformat_open_input(&inputContext, inputFilename, nullptr, nullptr) != 0) { std::cerr << "Failed to open input video file!" << std::endl; return -1; } // 获取输入视频流信息 if (avformat_find_stream_info(inputContext, nullptr) < 0) { std::cerr << "Failed to retrieve input video stream information!" << std::endl; return -1; } // 查找视频流 int videoStreamIndex = -1; for (unsigned int i = 0; i < inputContext->nb_streams; i++) { if (inputContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } if (videoStreamIndex == -1) { std::cerr << "Failed to find video stream in input video file!" << std::endl; return -1; } // 获取视频编解码器参数 AVCodecParameters* codecParameters = inputContext->streams[videoStreamIndex]->codecpar; // 查找视频编解码AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id); if (codec == nullptr) { std::cerr << "Failed to find video decoder codec!" << std::endl; return -1; } // 创建编解码器上下文 codecContext = avcodec_alloc_context3(codec); if (codecContext == nullptr) { std::cerr << "Failed to allocate video decoder context!" << std::endl; return -1; } // 设置编解码器上下文参数 if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) { std::cerr << "Failed to copy video decoder parameters to context!" << std::endl; return -1; } // 打开视频编解码器 if (avcodec_open2(codecContext, codec, nullptr) < 0) { std::cerr << "Failed to open video decoder!" << std::endl; return -1; } // 创建输出视频文件 if (avformat_alloc_output_context2(&outputContext, nullptr, nullptr, outputFilename) < 0) { std::cerr << "Failed to allocate output video file context!" << std::endl; return -1; } // 添加视频流到输出视频文件 stream = avformat_new_stream(outputContext, codec); if (stream == nullptr) { std::cerr << "Failed to create output video stream!" << std::endl; return -1; } // 复制视频流参数 if (avcodec_parameters_copy(stream->codecpar, codecParameters) < 0) { std::cerr << "Failed to copy video stream parameters!" << std::endl; return -1; } // 打开输出视频文件 if (!(outputContext->oformat->flags & AVFMT_NOFILE)) { if (avio_open(&outputContext->pb, outputFilename, AVIO_FLAG_WRITE) < 0) { std::cerr << "Failed to open output video file!" << std::endl; return -1; } } // 写视频文件头部信息 if (avformat_write_header(outputContext, nullptr) < 0) { std::cerr << "Failed to write output video file header!" << std::endl; return -1; } // 初始化视频 AVFrame* frame = av_frame_alloc(); if (frame == nullptr) { std::cerr << "Failed to allocate video frame!" << std::endl; return -1; } int frameNumber = 0; // 逐读取输入视频文件并进行压缩 while (av_read_frame(inputContext, &packet) >= 0) { if (packet.stream_index == videoStreamIndex) { // 发送解码器数据包 if (avcodec_send_packet(codecContext, &packet) < 0) { std::cerr << "Failed to send packet to video decoder!" << std::endl; return -1; } // 接收解码器输出 int ret = avcodec_receive_frame(codecContext, frame); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { av_packet_unref(&packet); continue; } else if (ret < 0) { std::cerr << "Failed to receive frame from video decoder!" << std::endl; return -1; } // 压缩数据 AVPacket compressedPacket; av_init_packet(&compressedPacket); compressedPacket.data = nullptr; compressedPacket.size = 0; if (avcodec_encode_video2(codecContext, &compressedPacket, frame, nullptr) < 0) { std::cerr << "Failed to encode video frame!" << std::endl; return -1; } // 写入压缩后的数据到输出视频文件 if (av_write_frame(outputContext, &compressedPacket) < 0) { std::cerr << "Failed to write compressed video frame!" << std::endl; return -1; } av_packet_unref(&compressedPacket); frameNumber++; } av_packet_unref(&packet); } // 写视频文件尾部信息 av_write_trailer(outputContext); // 清理资源 avcodec_free_context(&codecContext); avformat_close_input(&inputContext); avformat_free_context(outputContext); av_frame_free(&frame); return 0; } ``` 请将"your_input_video_file"替换为你想要压缩的输入视频文件的路径,将"your_output_compressed_video_file.h264"替换为你想要保存的压缩后的视频文件路径。 3. 编译和运行:使用C++编译器(如g++)编译源文件,并链接FFmpeg库。 ```bash g++ your_source_file.cpp -o compress_video `pkg-config --cflags --libs libavformat libavcodec libswscale` ``` 然后运行可执行文件: ```bash ./compress_video ``` 这样,程序将读取输入视频文件并使用H.264/AVC压缩格式对视频流进行压缩,并将压缩后的视频保存到输出文件中。 请注意,上述代码仅提供了基本的功能实现,你可能需要根据具体需求进行更复杂的视频处理或参数设置。此外,确保你有适当的权限来读取输入视频文件并保存输出视频文件。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值