ffplay代码分析(1)主体框架、主要数据结构

ffplay代码分析(1):主体框架、主要数据结构

1. 介绍

ffplay,作为FFmpeg项目的组成部分,是一个轻量级的命令行多媒体播放器。ffplay不仅支持广泛的音视频格式,还经常被用作验证多媒体文件的工具。分析其代码能够更好地理解音视频播放和流处理的复杂性。分析代码还为定制或进一步开发多媒体应用提供参考。

2. 主体框架

为了提高性能和响应速度,ffplay使用多线程技术来并行处理音频、视频和用户输入。

  • 解码线程:此线程专门处理音频和视频流的解码工作。它从文件或网络源中读取数据,然后解码为可以播放的原始帧。
  • 音频播放线程:此线程负责将解码后的音频数据送入音频输出设备。
  • 视频播放线程:与音频线程类似,但这个线程负责渲染视频帧到显示设备。

2.1. 初始化过程

在ffplay启动时,首先进行的是初始化过程。这包括:

  • ffplay初始化流程概览:在这一阶段,ffplay进行预设值的配置,预加载必要的库和组件,并为后续的解析和播放做好准备。
  • 解析命令行参数:根据用户的输入,ffplay会解析命令行参数,确定播放文件、输出格式、解码选项等。
  • 资源分配和初始化:为视频和音频流分配必要的内存和资源,初始化解码器和渲染器。

2.2. 主线程循环

一旦初始化完成,ffplay进入主循环,持续进行以下操作直至播放结束或用户中断:

  • 事件处理
    • 键盘输入:响应用户的播放控制,如暂停、跳转和停止等。
    • 鼠标事件:允许用户点击和拖拽进度条、调整音量等。
  • 音视频同步机制:确保音频和视频流同步播放,避免延迟或跳帧,为用户提供流畅的观看体验。

2.3. 清理和退出

播放完毕或收到停止命令后,ffplay开始清理过程:

  • 释放资源:关闭打开的文件和网络流,释放内存和其他资源。
  • 错误处理和日志记录:确保任何未处理的错误得到记录,同时将运行日志保存,以供后续分析或故障排查。

3. 主要数据结构分析

3.1 struct VideoState

  • struct VideoState 是在 FFplay 中使用的一个核心数据结构,用于跟踪和管理播放器的各种状态。这是播放器的心脏,其中包含了关于正在播放的媒体文件的所有重要信息。
主要属性:
  • format_ctx: 与输入文件相关的上下文,它包括了文件的所有流信息。
  • audio_stream, video_stream, subtitle_stream: 指向当前文件中的音频、视频和字幕流的指针。
  • audio_clock, video_clock: 音频和视频的时钟值,用于同步。
  • audioq, videoq, subtitleq: 音频、视频和字幕的数据包队列。
  • av_sync_type: 表示音频和视频同步的方式。
  • read_tid: 用于读取数据包的线程的线程ID。
  • audio_hw_buf_size: 音频硬件缓冲区大小。
  • audio_volume, muted: 音量控制和静音状态。
  • frame_timer, frame_last_delay: 用于视频同步的计时器。
  • frame_last_pts: 最后一个视频帧的PTS。
  • texture: 视频帧的纹理(如果使用图形硬件加速)。
  • paused, last_paused, queue_attachments_req: 播放控制标志,例如暂停状态。
  • step, seek_req, seek_flags, seek_rel: 与播放控制和搜索有关的标志。
  • read_pause_return: 用于管理读取操作的返回值。
  • continue_read_thread: 线程管理的条件变量。
  • video_stream_idx, audio_stream_idx, subtitle_stream_idx : 这些索引值确定音频、视频和字幕流在format_ctx中的位置。
  • pictq, sampq: 图像和音频样本的队列。
  • pictq_size, pictq_rindex, pictq_windex: 图像队列的大小及其读写索引。
  • audio_buf, audio_buf1: 存放解码后的音频数据的缓冲区。
  • audio_buf_size, audio_buf_index: 音频缓冲区的大小和索引。
  • av_sync_threshold: 同步阈值,当超出此阈值时,音频和视频会尝试重新同步。
  • video_current_pts_time: 当前视频帧的时间戳。
  • frame_drops_early, frame_drops_late: 早期和晚期丢弃的帧数统计。
  • realtime: 标志,指示是否是实时流。
  • eof: 文件结束标志。
  • last_video_stream, last_audio_stream, last_subtitle_stream: 上次选择的音频、视频和字幕流的索引。
  • filename: 正在播放的文件名。
  • width, height: 视频的宽度和高度。

3.2 struct Clock

struct Clock 是 FFplay 中用于管理和跟踪音视频同步的关键数据结构。当播放多媒体文件时,音频和视频流需要协同工作,以确保在播放过程中它们保持同步。这就是 struct Clock 发挥作用的地方,它帮助确保音视频之间的时间戳匹配并同步播放。

主要属性:
  • pts: 当前的显示时间戳(Presentation Timestamp)。这是最近一次更新的时间戳,通常用于音视频同步。
  • pts_drift : pts与实际时间的偏差,用于调整时间戳。
  • last_updated: 上次更新时间戳的时间。
  • speed: 播放速度。正常速度为1.0,但用户可以选择加速或减速播放。
  • serial: 用于区分数据包序列的序列号。在流切换时,它有助于避免使用过时的数据。
  • paused: 表示时钟是否已暂停。

这个数据结构为FFplay提供了一个在音视频同步过程中使用的中央时钟概念。通过使用和更新这个结构,FFplay可以确保音频和视频帧在正确的时间播放,从而为用户提供无缝的观看体验。

3.3 struct MyAVPacketListPacketQueue

struct MyAVPacketList

struct MyAVPacketList 是一个用于在内部队列中存储 AVPacket的数据结构。当 FFplay 需要从输入源读取多媒体数据(如视频或音频流)时,它会将这些数据读入 AVPacket 。而 struct MyAVPacketList 则用于为这些数据包提供一个链表结构,以便按顺序存储和访问它们。

主要属性:
  • pkt: 存储的 AVPacket。
  • next : 指向链表中的下一个 MyAVPacketList 的指针。
  • serial: 播放序列。

PacketQueue

PacketQueue 是用于存储和管理多个 MyAVPacketList(或 AVPacket)的数据结构。这是一个队列,它保证数据包的有序存储和逐个处理,这对于多媒体播放器来说非常重要,因为需要确保按正确的顺序处理每个音频和视频数据包。

主要属性:

  • first_pkt, last_pkt : 这是队列的首尾指针,指向链表的开始和结束。当新的MyAVPacketList被添加到队列时,它将被添加到last_pkt所指向的位置,并更新last_pkt指针。当数据包从队列中被取出时,first_pkt将指向下一个MyAVPacketList
  • nb_packets: 当前队列中的数据包数量。这是一个计数器,每当新的数据包被添加到队列时,它增加,每当数据包从队列中被取出时,它减少。
  • size: 队列的总大小(字节)。这是队列中所有数据包的总大小。
  • mutex: 用于同步的互斥锁。当有多个线程尝试访问或修改队列时,它确保队列的完整性和同步。
  • cond: 用于等待条件的条件变量。当队列为空且线程正在等待新数据包时,它可能会使用这个条件变量。

使用:

PacketQueue提供了一个高效、线程安全的方式来存储和管理MyAVPacketList。其设计确保了多媒体数据包的有序和同步处理。

  • 线程安全mutexcond使得PacketQueue是线程安全的。当一个线程正在读取或写入队列时,其他线程会被阻塞,直到第一个线程完成其操作。

  • 队列管理 :通过first_pktlast_pkt,队列可以快速地进行数据包的插入和删除操作。

  • 大小和数量nb_packetssize提供了关于队列状态的实时信息,如队列中有多少数据包,以及它们占用了多少内存。

  • 3.3.1 packet_queue_init(PacketQueue *q): 初始化

    用于初始化一个新的PacketQueue结构体。它确保队列的属性被正确设置,以便于后续的数据包添加、删除和处理操作。

参数说明:

  • PacketQueue *q: 一个指向PacketQueue结构体的指针,该结构体将被初始化。

结构体属性:

  • first_pkt, last_pkt : 队列的首尾指针,初始化时为NULL
  • nb_packets : 当前队列中的数据包数量,初始化为0
  • size : 队列的总大小(字节),初始化为0
  • mutex: 用于同步的互斥锁,初始化时为未锁状态。
  • cond: 用于等待条件的条件变量,初始化时为默认状态。
  • 3.3.2 packet_queue_destroy(PacketQueue *q): 销毁
    用于销毁一个已经初始化的PacketQueue结构体。它会释放与结构体关联的所有资源,包括数据包内存、互斥锁和条件变量。在调用此函数后,队列不应再被使用。

参数说明:

  • PacketQueue *q: 一个指向PacketQueue结构体的指针,该结构体将被销毁。

结构体属性:

  • first_pkt, last_pkt: 队列的首尾指针,销毁时会释放所有关联的数据包内存。
  • mutex: 用于同步的互斥锁,销毁时会被释放。
  • cond: 用于等待条件的条件变量,销毁时会被释放。
  • 3.3.3 packet_queue_start(PacketQueue *q): 开始

    启动数据包队列,准备接收和处理数据包。

  • 3.3.4 packet_queue_abort(PacketQueue *q): 中止

    中止数据包队列的操作,停止队列的所有活动。

  • 3.3.5 packet_queue_put(PacketQueue *q, AVPacket *pkt): 输入

    将一个数据包放入队列。

  • 3.3.6 packet_queue_get(PacketQueue *q, AVPacket *pkt, int block): 获取

    从队列中获取一个数据包。
    参数
    PacketQueue *q - 指向要从中获取数据包的PacketQueue结构体的指针。
    AVPacket *pkt - 用于存放从队列中获取的数据包的指针。
    int block - 指定操作是否应阻塞直到数据包可用。

  • 3.3.7 packet_queue_put_nullpacket(PacketQueue *q, int stream_index): 输入空包

    在队列中放入一个空数据包,通常用于表示流的结束。
    参数
    PacketQueue *q - 指向要添加空数据包的PacketQueue结构体的指针。
    int stream_index - 空数据包的流索引。

  • 3.3.8 packet_queue_flush(PacketQueue *q): 清空

    清空数据包队列,删除所有包含的数据包。

3.4 struct FrameFrameQueue

struct Frame是一个数据结构,用于存储解码后的视频或音频帧。

属性

  • AVFrame *frame:存储实际帧数据的指针。
  • int width, height, format:帧的宽度、高度和格式。
  • int uploaded:指示帧是否已上传到GPU(如果使用硬件加速)。
  • double pts, duration:帧的显示时间戳和持续时间。

作用:存储从数据包中解码出来的原始帧数据以及与该帧相关的信息。

FrameQueue是一个数据结构,用于存储多个Frame结构体。这个队列通常用于缓存解码后的帧,以便后续的播放和显示。

属性

  • Frame array[MAX_FRAMES]:存储帧的数组,MAX_FRAMES是队列可以容纳的最大帧数。
  • int rindex, windex:读和写索引,指示从哪里读取帧和向哪里写入新的帧。
  • int size, max_size:队列中的当前帧数和队列的最大帧数。
  • SDL_mutex *mutex:用于同步访问队列的互斥锁。
  • SDL_cond *cond:与互斥锁配合使用的条件变量。

作用

  • 存储解码后的帧,提供帧的连续播放。
  • 确保帧的正确排序和同步播放。
  • 通过缓存帧,实现流畅的播放,尤其是在解码速度变化或网络波动时。

这两个结构体是音视频播放和处理中的核心部分,它们确保音视频数据得到正确、有效和同步的处理。

  • 3.4.1 frame_queue_init(FrameQueue *fq)

    FrameQueue分配所需的内存,并初始化相关的属性和同步机制。

  • 3.4.2 frame_queue_destory(FrameQueue *fq): 销毁

    释放FrameQueue使用的所有资源,并清空队列。

  • 3.4.3 frame_queue_peek_writable(FrameQueue *fq): 查看可写Frame

    检查队列中是否有空间容纳新的帧,如果有,返回一个可以写入的Frame的引用。

  • 3.4.4 frame_queue_push(FrameQueue *fq, Frame *frame): 压入

    将解码后的帧压入队列中。

  • 3.4.5 frame_queue_peek_readable(FrameQueue *fq): 查看可读Frame

    检查队列中是否有可读取的帧,如果有,返回一个可以读取的Frame的引用。

  • 3.4.6 frame_queue_next(FrameQueue *fq): 下一个

    移动队列的读索引到下一个帧,并返回该帧的引用。

  • 3.4.7 frame_queue_nb_remaining(FrameQueue *fq): 剩余的size

    返回队列中尚未读取的帧的数量。

  • 3.4.8 frame_queue_peek(FrameQueue *fq): 查看当前帧

    返回队列中的当前帧,但不移动读取指针。这使得用户可以再次查看当前帧,而无需实际移动到下一帧。

  • 3.4.9 frame_queue_peek_next(FrameQueue *fq): 查看下一帧

    预览队列中的下一个帧,但不移动读取指针。这允许用户查看接下来的帧,以确定是否需要进行其他操作,例如跳过或解码。

  • 3.4.10 frame_queue_peek_last(FrameQueue *fq): 查看上一帧

    返回队列中上一个被读取的帧的引用。这使得用户可以回退并再次处理或显示之前的帧,而不会对队列中的其他帧产生任何影响。

相关推荐
dvlinker1 天前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
音视频牛哥6 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
音视频牛哥8 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播
音视频牛哥12 天前
电脑共享同屏的几种方法分享
音视频开发·视频编码·直播
x007xyz2 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥2 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播
哔哩哔哩技术2 个月前
自研点直播转码核心
音视频开发