FFMPEG解码多线程

转自:http://blog.csdn.net/bsplover/article/details/7542980

http://m.blog.csdn.net/blog/leixiaohua1020/46412023


FFMPEG多线程编码器一般以在Slice内分功能模块进行多线程编码,如h263,h263P,msmpeg(v1, v2, v3),wmv1。包含以下几个线程:(1)Pre_estimation_motion_thread运动估计前的准备;(2)Estimation_motion_thread运动估计;(3)Mb_var_thread宏块其他变量;(4)Encode_thread编码主线程。当然也有例外,如FFV1编码器按Slice为线程单位进行多线程编码。

FFMPEG多线程解码器分为Frame级和Slice级两种,Slice级多线程同时解码一帧中不同的部分。Frame级多线程同时接受多帧码流,实现并行解码,当前帧处于显示状态时,未来的几帧已经在其他线程中被解码。

1.         Slice Threading

         FFmpeg中,dvvideo_decoder, ffv1_decoder, h264_decoder, mpeg2_video_decoder和mpeg_video_decoder均支持了Slice Threading。

实现方法是:首先为codecContext注册注册多线程处理函数excute(),Codec解码过程中处理Slice时调用avctx->excute()。excute()启动Slice解码工作线程开始多线程解码,同时快速返回开始下一Slice的解析和解码。

Frame Threading主线程和解码线程的同步如图1所示。

图1 Frame Threading主线程和解码线程的同步

2.         Frame Threading

         目前为止支持Frame Threading的解码器有h264_decoder, huffyuv_decoder, ffvhuff_decoder, mdec_decoder, mimic_decoder, mpeg4_decoder, theora_decoder, vp3_decoder和vp8_decoder。

         Frame Threading有如下限制:用户函数draw_horiz_band()必须是线程安全的;为了提升性能,用户应该为codec提供线程安全的get_buffer()回调函数;用户必须能处理多线程带来的延时。另外,支持Frame Threading的codec要求每个包包含一个完整帧。Buffer内容在ff_thread_await_progress()调用之前不能读,同样,包括加边draw_edges()在内的处理,在ff_thread_report_progress()调用之后,Buffer内容不能写。

         每个线程都有以下四个状态。如图2所示,为了保证线程安全,若Codec未实现update_thread_context()和线程安全的get_buffer(),则必须在解码完成后才能将状态转换为STATUS_SETUP_FINISHED,意味着下一个线程只能在当前线程解码完成后才能开始解码。

而如图3所示,如果Codec实现update_thread_context()和线程安全的get_buffer(),线程状态可以在解码开始之前转换为STATUS_SETUP_FINISHED,这样,下一个线程就可能与当前线程并行。

图2 Codec未实现update_thread_context()和线程安全的get_buffer(),线程状态转换

图3 Codec实现update_thread_context()和线程安全的get_buffer(),线程状态转换

         解码主线程通过调用submit_packet()将码流交给对应的解码线程。主线程和解码线程的同步如图4所示。

图4 Frame Threading主线程和解码线程的同步




=====================================================

HEVC源代码分析文章列表:

【解码 -libavcodec HEVC 解码器】

FFmpeg的HEVC解码器源代码简单分析:概述

FFmpeg的HEVC解码器源代码简单分析:解析器(Parser)部分

FFmpeg的HEVC解码器源代码简单分析:解码器主干部分

FFmpeg的HEVC解码器源代码简单分析:CTU解码(CTU Decode)部分-PU

FFmpeg的HEVC解码器源代码简单分析:CTU解码(CTU Decode)部分-TU

FFmpeg的HEVC解码器源代码简单分析:环路滤波(LoopFilter)

=====================================================


从这篇文章开始,简单分析记录FFmpeg中libavcodec的HEVC(H.265)解码器(HEVC Decoder)的源代码。本文综述整个解码器的框架,后续几篇文章再对解码器的内部模块进行分析。


函数调用关系图

FFmpeg的HEVC(H.265)解码器的函数调用关系图如下所示。
 

下面解释一下图中关键标记的含义。


作为接口的结构体
FFmpeg和HEVC解码器之间作为接口的结构体有2个:
ff_hevc_parser:用于解析HEVC码流的AVCodecParser结构体。
ff_hevc_decoder:用于解码HEVC码流的AVCodec结构体。

函数背景色
函数在图中以方框的形式表现出来。不同的背景色标志了该函数不同的作用:
白色背景的函数:普通内部函数。
粉红色背景函数:解析函数(Parser)。这些函数用于解析SPS、PPS等信息。
绿色背景的函数:解码函数(Decode)。这些函数通过帧内预测、帧间预测、DCT反变换等方法解码压缩数据。
黄色背景的函数:滤波函数(Filter)。这些函数对解码后的数据进行滤波,去除方块效应。
蓝色背景函数:汇编函数(Assembly)。这些函数是做过汇编优化的函数。图中主要画出了这些函数的C语言版本,此外这些函数还包含MMX版本、SSE版本、NEON版本等。

箭头线
箭头线标志了函数的调用关系:
黑色箭头线:不加区别的调用关系。
粉红色的箭头线:解析函数(Parser)之间的调用关系。
绿色箭头线:解码函数(Decode)之间的调用关系。
黄色箭头线:环路滤波函数(Loop Filter)之间的调用关系。

函数所在的文件
每个函数标识了它所在的文件路径。



几个关键部分

下文简单记录几个关键的部分。


FFmpeg和HEVC解码器之间作为接口的结构体

FFmpeg和HEVC解码器之间作为接口的结构体有2个:ff_hevc_parser和ff_hevc_decoder。

ff_hevc_parser

ff_hevc_parser是用于解析HEVC码流的AVCodecParser结构体。AVCodecParser中包含了几个重要的函数指针:
parser_init():初始化解析器。
parser_parse():解析。
parser_close():关闭解析器。
在ff_hevc_parser结构体中,上述几个函数指针分别指向下面几个实现函数:
hevc_init():初始化HEVC解析器。
hevc_parse():解析HEVC码流。
hevc_close():关闭HEVC解析器。

ff_hevc_decoder

ff_hevc_decoder是用于解码HEVC(H.265)码流的AVCodec结构体。AVCodec中包含了几个重要的函数指针:
init():初始化解码器。
decode():解码。
close():关闭解码器。
在ff_hevc_decoder结构体中,上述几个函数指针分别指向下面几个实现函数:
hevc_decode_init():初始化HEVC解码器。
hevc_decode_frame():解码HEVC码流。
hevc_decode_free():关闭HEVC解码器。

解析函数(Parser)

解析函数(Parser)用于解析HEVC码流中的一些信息(例如SPS、PPS、Slice Header等)。在parse_nal_units()和decode_nal_units()中都调用这些解析函数完成了解析。下面举几个解析函数的例子:
ff_hevc_decode_nal_vps():解析VPS。
ff_hevc_decode_nal_sps():解析SPS。
ff_hevc_decode_nal_pps():解析PPS。
ff_hevc_decode_nal_sei():解析SEI。

普通内部函数

普通内部函数指的是HEVC解码器中还没有进行分类的函数。下面举几个例子。
ff_hevc_decoder中hevc_decode_init()调用的初始化函数:
hevc_init_context():初始化HEVC解码器上下文结构体。
hevc_decode_extradata():解析AVCodecContext中的extradata。
ff_hevc_decoder中hevc_decode_frame()逐层调用的和解码Slice相关的函数:
decode_nal_units(),decode_nal_unit(),hls_slice_data(),hls_decode_entry()等。
ff_hevc_decoder中hevc_parse()逐层调用的和解析Slice相关的函数:
hevc_find_frame_end():查找NALU的结尾。
parse_nal_units():解析一个NALU。

hls_decode_entry()

hls_decode_entry()是FFmpeg的HEVC解码器真正的解码函数。其中调用了解码函数和滤波函数HEVC中的CTU进行处理。在HEVC中CTU(Coding tree unit, 编码树单元)即对应H.264中的MB(Macroblock, 宏块)。

解码函数(Decode)

解码函数(Decode)通过帧内预测、帧间预测等方法解码CTU压缩数据。CTU解码模块对应的函数是hls_coding_quadtree()。hls_coding_quadtree()用于解析HEVC码流的四叉树结构的句法,是一个递归调用的函数。当解析到单个CU的时候,会调用CU的解码函数hls_coding_unit()。
hls_coding_unit()会调用hls_prediction_unit()和hls_transform_tree()分别对CU中的PU和TU进行处理。hls_prediction_unit()会调用luma_mc_uni()或者调用luma_mc_bi()进行预测。hls_transform_tree()用于解析TU的四叉树结构的句法,是一个递归调用的函数。当解析到单个TU的时候,会调用hls_transform_unit()对TU进行处理。

环路滤波函数(Loop Filter)

环路滤波函数(Loop Filter)对解码后的数据进行滤波,去除方块效应和振铃效应。滤波模块对应的环路滤波函数是ff_hevc_hls_filters()。ff_hevc_hls_filters()调用了ff_hevc_hls_filter()。而ff_hevc_hls_filter()调用去块效应滤波器函数deblocking_filter_CTB()去除解码过程中的块效应;调用SAO滤波器函数sao_filter_CTB()去除解码过程中的振铃效应。

汇编函数(Assembly)

汇编函数(Assembly)是做过汇编优化的函数。为了提高效率,整个HEVC解码器中包含了大量的汇编函数。实际解码的过程中,FFmpeg会根据系统的特性调用相应的汇编函数(而不是C语言函数)以提高解码的效率。如果系统不支持汇编优化的话,FFmpeg才会调用C语言版本的函数。

至此FFmpeg的HEVC解码器的结构就大致梳理完毕了。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FFmpeg解码时使用多线程可以提高解码速度,但在释放句柄时可能会遇到问题。下面是一个示例代码,展示了如何使用FFmpeg进行多线程解码,并确保句柄完全释放。 ```c // 初始化FFmpeg av_register_all(); // 打开输入文件 AVFormatContext* formatContext = NULL; if (avformat_open_input(&formatContext, inputFilePath, NULL, NULL) != 0) { // 错误处理 } // 获取流信息 if (avformat_find_stream_info(formatContext, NULL) < 0) { // 错误处理 } // 查找视频流 int videoStreamIndex = -1; for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } // 获取解码器 AVCodec* codec = avcodec_find_decoder(formatContext->streams[videoStreamIndex]->codecpar->codec_id); if (!codec) { // 错误处理 } // 创建解码器上下文 AVCodecContext* codecContext = avcodec_alloc_context3(codec); if (!codecContext) { // 错误处理 } // 设置解码器参数 if (avcodec_parameters_to_context(codecContext, formatContext->streams[videoStreamIndex]->codecpar) < 0) { // 错误处理 } // 打开解码器 if (avcodec_open2(codecContext, codec, NULL) < 0) { // 错误处理 } // 创建解码线程 AVPacket packet; AVFrame* frame = av_frame_alloc(); if (!frame) { // 错误处理 } while (av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == videoStreamIndex) { // 发送数据包到解码器 if (avcodec_send_packet(codecContext, &packet) < 0) { // 错误处理 } // 接收解码后的帧 while (avcodec_receive_frame(codecContext, frame) >= 0) { // 处理解码后的帧 } } // 释放数据包 av_packet_unref(&packet); } // 释放资源 av_frame_free(&frame); avcodec_free_context(&codecContext); avformat_close_input(&formatContext); ``` 在上述代码中,我们使用了`avcodec_free_context()`函数来释放解码器上下文,`avformat_close_input()`函数来关闭输入文件,`av_frame_free()`函数来释放帧对象。这样可以确保句柄完全释放,避免内存泄漏。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值