ffplay源码分析(五)包缓存队列和帧缓存队列

在音视频处理流程中,ffplay的有两种队列,包缓存队列(Packet Buffer Queue)和帧缓存队列(Frame Buffer Queue)。这两个队列的存在,是为了适应音视频数据处理过程中的多线程架构——包括收包线程、解码线程和渲染线程。具体来说,收包线程负责从网络或文件中读取数据并将其放入包缓存队列中;解码线程从包缓存队列中取出数据进行解码,然后将解码后的数据放入帧缓存队列中;最后,渲染线程从帧缓存队列中取出数据进行渲染。由于每个线程的处理速度不同,缓存队列在这一过程中起到了平衡各线程工作负荷和避免数据丢失的关键作用。

音频、视频和字幕都经历了类似的处理流程,因此设计出高效且适应音视频特性的缓存队列显得尤为重要。ffplay中对于包缓存队列和帧缓存队列的设计不仅确保了音视频数据的流畅处理,还有效地提升了播放体验。这种设计通过合理的缓存策略和线程同步机制,成功地解决了音视频处理中的各种挑战。

一、包缓存队列

包缓存队列的设计需要考虑多个因素,以确保其高效性和稳定性。因为数据包本身通常较小,因此没有必要将缓存队列设计为循环队列,采用常规的入队申请内存和出队释放内存的方式即可。

ffplay中的包缓存队列设计适配了音视频的特性,和普通的队列相比有如下差异

  1. 序列号处理功能

使用serial字段来追踪数据包的顺序,在某些多路流(如音视频同步)场景中非常有用。每次队列重启或刷新时,serial都会递增,有助于区分不同的播放段。

比如说发生跳转时,又解码到了跳转之前的数据,可能会有回跳的现象,

ffplay会在发生跳转的时候,更新包的序列号,当解码到老的序列号,就把数据给丢弃掉,直到解码到新的数据。

  1. 自动增长的fifo队列

使用av_fifo_alloc2创建自动增长的FIFO队列,避免了频繁内存分配,提高了性能。

  1. 阻塞和非阻塞设置

阻塞与非阻塞模式packet_queue_get函数通过block参数实现了阻塞和非阻塞模式的灵活切换,使得队列在不同的使用场景下能够适应需求。

1.1 PacketQueue结构体

//MyAVPacketList结构体的作用就是给包加上序列号
typedef struct MyAVPacketList {
   
   
    AVPacket *pkt;
    int serial;
} MyAVPacketList;

typedef struct PacketQueue {
   
   
    AVFifo *pkt_list;//fifo队列,
    int nb_packets;//packet数量
    int size;//packet大小(字节)
    int64_t duration;//持续时长
    int abort_request;//中断请求
    int serial;//序列号
    SDL_mutex *mutex;//锁
    SDL_cond *cond;//条件变量
} PacketQueue;

1.2 初始化

static int packet_queue_init(PacketQueue *q)
{
   
   
    memset(q, 0, sizeof(PacketQueue));
    //
    q->pkt_list = av_fifo_alloc2(1, sizeof(MyAVPacketList), AV_FIFO_FLAG_AUTO_GROW);
    if (!q->pkt_list)
        return AVERROR(ENOMEM);
    q->mutex = SDL_CreateMutex();
    if (!q->mutex) {
   
   
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->cond = SDL_CreateCond();
    if (!q->cond) {
   
   
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }
    q->abort_request = 1;
    return 0;
}

1.3 开始运行

static void packet_queue_start(PacketQueue *q)
{
   
   
    SDL_LockMutex(q->mutex);
    q->abort_request = 0;
    q->serial++;
    SDL_UnlockMutex(q->mutex);
}

1.4 放入packet

//内部调用
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
{
   
   
    MyAVPacketList pkt1;
    int ret;
  
    if (q->abort_request)
       return -1;

    //给音视频包加上序列号
    pkt1.pkt = pkt;
    pkt1.serial = q->serial;

    //把包添加进队列
    ret = av_fifo_write(q->pkt_list, &pkt1, 1);
    if 
### 关于FFplay播放过程中的队列处理 在FFplay中,`VideoState` 结构体用于存储解码状态其他重要信息。其中涉及到多个队列来管理音视频数据的流动[^3]。 #### 音频视频帧队列 - **音频队列** (`frame_queue`) **视频队列** 是两个重要的组件,在 `VideoState` 中定义并初始化。这些队列为了解决不同步问题而设计,可以容纳一定数量已解码但尚未显示或播放的数据。 ```c typedef struct VideoState { // ... FrameQueue frame_queue; /* audio or video decoded data queue */ } VideoState; ``` #### 数据队列 (Packet Queue) 除了上述提到的帧队列外,还有一个专门用来缓存来自输入文件未被解析的数据队列——即 **packet queue**。此队列位于 demuxing 解析层之前,负责接收原始比特流,并将其传递给相应的编解码器进行进一步处理。 ```c static int packet_queue_put(PacketQueue *q, AVPacket *pkt){ // 将新的AVPacket放入队列末端... } ``` 当读取线程(`read_thread`)从输入源获取到一个新的数据时,会通过调用 `packet_queue_put()` 方法将它加入到这个队列里等待后续操作。与此同时,其他工作线程也会不断尝试从未满溢出的队列头部取出可用项来进行下一步骤的操作,比如解码成图像帧或是声音样本等。 #### 常见问题及解决方案 1. **死锁现象** 如果某个时刻所有的工作线程都在阻塞状态下试图访问同一个资源,则可能会发生死锁情况。为了避免这种情况的发生,通常会在实现多线程编程时采用合适的同步机制,如互斥量(mutexes),条件变量(condition variables) 来控制对共享资源的竞争访问[^4]。 2. **缓冲区溢出** 当生产者的速度远大于消费者的消费速率时,可能导致内存泄漏甚至崩溃等问题。因此需要合理设置最大允许长度以及超时策略以防止此类异常状况出现。 3. **不同步错误** 由于存在独立运作的音频与视屏两条路径,所以很容易遇到两者之间失去协调的情况。为此引入了时间戳(Timestamps)概念以便能够精确调整二者间的关系从而达到良好的视听体验效果。 ```python def synchronize_video(audio_pts, video_frame): """根据音频PTS调整视频帧的时间""" diff = get_current_audio_output_time() - audio_pts if abs(diff) >= sync_threshold: adjust_video_playback_speed(video_frame, diff) render_video_frame(video_frame) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值