当前位置: 首页 > 网络应用技术

ffplay视频播放原理分析

时间:2023-03-06 12:09:30 网络应用技术

  作者|Zhao jiazhu

  FFMPEG框架由命令行工具和功能库组成。FFPlay是命令行工具之一。它提供了播放音频和视频文件的功能。它不仅可以播放本地多媒体文件,还可以播放在线流媒体文件。本文从FFPlay的整体播放过程开始,借鉴其设计思想,并学习如何设计简单的播放器。

  在学习FFPLAY源代码之前,为了促进理解,我们首先了解播放媒体文件时播放器的工作流程。

  FFPlay的使用非常简单。以ffplay -i input.mp4 -loop 2为例,这意味着ffplay player播放input.mp4文件两次。执行此命令后,相应的源代码在fftools/ffplay.c和程序输入功能是主要函数。

  注意:本文FFPLAY源代码基于FFMPEG 4.4。

  2.1环境初始化

  以下功能主要称为以下功能:

  2.2 SDL初始化

  SDL的全名是简单的直接层。它是一个支持Linux,Windows,Mac OS和其他平台的跨平台多媒体开发库。它实际上封装在DirectX,OpenGL,Xlib.Function.ffplay的播放显示屏上通过SDL实现。

  以下三个SDL函数主要在主要函数中调用:

  2.3分析媒体流

  stream_open函数是ffplay的起点开始播放该过程。该函数以两个参数介绍,即文件名input_filename和文件格式file_iformat。以下是函数内部的处理过程:

  (1)初始化Videostate:VideoState是FFPLAY中最大的结构,所有视频信息都在其中定义。初始化视频仪表板时,首先定义局部变量是视频稳定结构指针类型,分配堆内存,然后初始化结构中的变量。,例如视频流,音频流和字幕流,并将值函数分配到参数文件名和iformat中。

  (2)初始化框架:框架是解码框架队列。帧是解码数据。例如,在视频解码为YUV或RGB数据之后,音频解码后的PCM数据。初始化的框架,PICTQ(视频框架队列),SubPQ(副标题框架队列)和SAMPQ(音频框架队列)在视频仪式中为初始化。Framequeue在数组内,实现了高级和第一个OUT环缓冲区。Windex是一个指针,是通过解码线程使用的;Rindex是指针,并由播放线程使用。使用环缓冲区的优点是,在删除了缓冲区中的元素之后,其他元素不需要移动位置,这适用于知道最大值的场景提前缓冲区的容量。

  (3)初始化数据包:packetqueue是解码之前的数据包队列以保存封装的数据。当初始化的packetqueue,videoq(视频包队列),音频队列(音频队列)和subtitleq(subtitle queue)在视频中的初始化。Framequeue,Packatqueue使用一种链接的方法来实现队列。由于解码不可控制之前包装的大小,因此无法阐明缓冲区的最大容量。如果使用了环缓冲区,则很容易触发缓冲区的扩展,并且需要移动机芯中的数据。因此,更适合使用链接列表。

  (4)初始化时钟:时钟是一个时钟。在音频和视频的同步中,有三种同步方法:视频同步到音频,音频同步到视频,音频和视频同步到外部时钟。初始化时钟,vidclk(vide Clock)(视频时钟),audclk(音频时钟),和视频仪中的Extclk(外时钟)调用INIT_CLOCK函数以进行初始化。

  (5)限制体积范围:首先限制体积范围在0到100之间,然后根据SDL的体积范围进一步限制。

  (6)将音频和视频同步设置:ffplay默认为av_sync_audio_master,即视频同步到音频。

  (7)创建读取线程:调用sdl_createThread以创建读取线程,同时设置线程以创建成功的恢复read_thread函数,并且接收参数为(由stream_open创建的videostate指针类型的本地变量类型函数)。如果线程失败,请将stream_close称为破坏逻辑。

  (8)返回值:返回局部变量是作为函数返回值来处理以下SDL事件。

  2.4 SDL事件处理

  Event_loop函数是用户键盘内部的循环,鼠标点击事件,窗口事件,退出事件等。

  read_thread函数的作用是从磁盘或网络中获取流式传输,包括音频流,视频流和字幕流,然后根据可用性创建一个响应 - 编码线程。在理解协议/解锁软件包时,核心处理过程可以分为以下步骤:

  3.1创建AvformatContext

  AvformatContext是封装的上下文,描述了媒体文件或媒体流的组成和基本信息。

  获取AvformatContext对象后,您需要在调用Avformat_open_input函数之前设置中断恢复功能,以打开文件以检查是否应中断IO操作。

  decode_interrut_cb返回视频仪的abort_request变量。调用stream_close函数闭合时,该变量将放置为1。

  3.2打开输入文件

  准备了一些先前的作业后,您可以根据文件名开始打开文件。avformat_open_input函数用于打开文件并分析文件。如果文件是网络链接,则启动网络请求,并且与之相关的数据返回网络数据后,对音频流和视频流进行解析。

  3.3搜索流信息

  搜索流信息使用avformat_find_stream_info函数。应该从媒体文件中读取几个软件包,然后从中搜索。最后,将搜索的流信息放置在iC-> Streams指针数组中。数组的大小为ic-> nb_streams。

  在实际的播放过程中,用户可以指定音频流,视频流和字幕流程。因此,在解码处理流程之前,它将确定相应的流是否处于不可用的状态。如果可用,则调用AV_FIND_BEST_STREAM函数以查找相应流的索引并将其存储在ST_INDEX数组中。

  3.4设置窗口大小

  如果找到视频流的索引,则需要渲染视频屏幕。由于窗口的大小通常由640*480的默认值使用,因此视频帧的实际大小可能不同。正确显示携带视频屏幕的窗口,需要视频帧的宽度比率。CALLCALLAV_GUESS_SAMPLE_ASPECT_RATINAT函数以猜测框架样本的宽度比,调用SET_DEFAULT_WINDOW_SIZE函数以重置显示窗口的大小和高度比率。

  3.5创建解码线程

  根据音频流,视频流和字幕流的索引是否根据ST_INDEX找到,如果您发现它,请调用stream_component_open,以创建流量的解码线程。

  3.6解锁的包装处理

  接下来是一个(;;)周期:

  (1)响应中断停止,暂停/继续,寻求操作;

  (2)确定包装量队列是否已满。

  (3)调用AV_REAM_FRAME读取代码中的几个音频帧或视频帧;

  (4)从输入文件中读取AVPacket,以确定当前AVPacket是否在播放时间范围内。如果是这样,请调用packet_queue_put函数,然后根据类型将其放在音频/视频/字幕数据包中。

  3.5该部分说stream_component_open函数负责创建不同的流解码线程。那么它如何创建解码线程?

  4.1创建AvCodeCconext

  Avcodeccontext是编解码器的上下文,以保存音频和视频编解码器相关信息。使用AVCODEC_ALLOC_CONTEXT3函数为空间分配空间,并使用AVCODEC_FREE_CONTEXT函数来释放空间。

  4.2找到解码器

  根据解码器的ID,调用AVCODEC_FIND_DECODER函数以找到相应的解码器。一个相似的函数为AVCODEC_FIND_ENCODER,用于查找FFMPEG编码。两个函数返回的结构是AVCODEC。

  如果指定代码设备的名称,则需要调用avcodec_find_decoder_by_name函数以查找解码器。

  无论找到解码器,如果找不到解码器,退出过程将保持异常。

  4.3解码器初始化

  找到解码器后,您需要打开解码器并初始化解码器。相应的函数为avcodec_open2。此功能还支持编码器的初始化。

  4.4创建解码线程

  确定解码类型并创建不同的解码线程。

  该线程是在解码器函数中创建的,它仍然使用SDL创建线程方法来调用SDL_CreatEthread函数。

  视频解码线程不断读取视频视频中的Avpacket。解码完成后,将AVFrame放入视频框架中。音频的解码实现类似于视频。在这里,我们只介绍视频的解码过程。

  5.1创建AVFrame

  AVFrame解码后描述了原始音频或视频数据。通过AV_FRAME_ALLOC函数分配内存,并通过AV_FRAME_FREE函数释放内存。

  5.2视频解码

  打开for(;;)循环,并不断调用get_video_frame函数解码视频帧。此功能主要调用decoder_decode_frame函数解码。解码器_decode_frame函数已处理了音频,视频和字幕。它主要依赖FFMPEG的AVCODEC_RECEIVE_FRAME功能来获得解码器解码器输出数据。

  在获得解码视频框架后,它将确定您是否需要根据音频和视频同步和命令行的FramedRop选项丢弃同步视频框架。

  5.3放入框架

  调用queue_picture函数,并将AVFrame放入框架中。此功能调用frame_queue_push函数,该功能采用环形缓冲区的处理方法来累积写作指针Windex。

  FFPlay使用默认情况下将视频同步到音频的方法,分为以下三种情况:

  ffplay视频同步到音频逻辑是在视频播放函数video_refresh中实现的。此函数的呼叫链为:main(main() - > event_loop() - > refresh_loop_wait_event() - > video_refresh。

  6.1判断完成

  计算frame_queue_nb_remering函数以计算未显示的帧数是否等于0。如果是,则无需采取剩余的步骤。计算过程相对简单。使用Framequeue的大小-rindex_shown。大小是框架的大小。rindex_shown指示节点指向rindex是否表明,如果显示为1,则为0。

  6.2播放序列匹配****

  调用frame_queue_peek_last和Frame_queue_peek从框架函数获取上一个帧和当前帧。

  (1)比较当前帧和当前数据包播放序列序列串行是否相等:

  注意:它用于区分它是否是连续数据。如果发生寻求,将开始一个新的戏剧序列,

  (2)比较上一个帧的串行序列和当前帧是否相等:

  6.3确定是否重复上一帧

  (1)将上一个帧LASTVP和当前帧VP传递到VP_Duration函数,并通过VP-> pts-lastvp-> pts计算上一个帧的播放时间。

  注意:全名是显示时间戳的演示时间戳,指示解码后获得的框架的显示时间。

  (2)在compute_target_dlay函数中,调用get_clock函数以获取视频时钟,调用get_master_clock函数以获取同步时钟,计算两个时钟之间的差异,然后计算根据差异计算时间的时间。

  (3)如果当前的帧播放时间(IS-> frame_timer +延迟)大于当前时刻(时间),则意味着当前帧的播放时间尚未到达。

  6.4确定是否丢弃不受欢迎的框架

  如果当前队列中的帧数大于1,则需要考虑框架损失,并且只有一个帧不考虑帧损失。

  (1)调用frame_queue_peek_next函数获取下一个帧(下一个要显示的帧)。根据当前帧和下一个帧计算当前帧的播放持续时间,计算过程与6.3相同。

  (2)满足以下条件时,开始丢失框架:

  (3)丢失框架时,即iS-> frame_drops_late ++,并调用frame_queue_next函数删除上一个帧,更新框架读取指针rindex和size。

  FFPlay的最终图像渲染由SDL完成。SDL_RENDERPRESENT(RENDER)函数在Video_display中调用。渲染参数首先是在主函数中创建的。在渲染之前,需要通过解码获得的视频框架数据转换由SDL支持的图像格式。转换过程是在upload_texture函数中实现的,并且在此处未分析详细信息。

  音频相似。如果SDL无法支持通过解码获得的音频,则需要对音频进行采样,并且音频框架格式将转换为SDL支持格式。

  从整个播放过程开始,本文介绍了FFPlay Player播放媒体文件的主要过程,并且并未深深地陷入代码的详细信息中。同时,我对FFMPEG的某些常见功能有所了解,IT对我们自己的手写播放器非常有帮助。

  - - - - - 结尾 - - - - - -