ffplay源码分析3-代码框架

本文分析FFmpeg自带的简单播放器ffplay的源码结构,包括主线程、解复用线程、视频解码线程、音频解码线程的工作流程。主线程负责视频播放和SDL消息处理,解复用线程读取文件并分发packet,视频解码线程解码视频,音频解码线程处理音频,所有线程协同完成音视频同步。
摘要由CSDN通过智能技术生成

本文为作者原创,转载请注明出处:https://blog.csdn.net/leisure_chn/article/details/87926291

ffplay是FFmpeg工程自带的简单播放器,使用FFmpeg提供的解码器和SDL库进行视频播放。本文基于FFmpeg工程4.1版本进行分析,其中ffplay源码清单如下:
https://github.com/FFmpeg/FFmpeg/blob/n4.1/fftools/ffplay.c

在尝试分析源码前,可先阅读如下参考文章作为铺垫:
[1]. 雷霄骅,视音频编解码技术零基础学习方法
[2]. 视频编解码基础概念
[3]. 色彩空间与像素格式
[4]. 音频参数解析
[5]. FFmpeg基础概念

“ffplay源码分析”系列文章如下:
[1]. ffplay源码分析1-概述
[2]. ffplay源码分析2-数据结构
[3]. ffplay源码分析3-代码框架
[4]. ffplay源码分析4-音视频同步
[5]. ffplay源码分析5-图像格式转换
[6]. ffplay源码分析6-音频重采样
[7]. ffplay源码分析7-播放控制

3. 代码框架

本节简单梳理ffplay.c代码框架。一些关键问题及细节问题在后续章节探讨。

3.1 流程图

ffplay流程图

3.2 主线程

主线程主要实现三项功能:视频播放(音视频同步)、字幕播放、SDL消息处理。

主线程在进行一些必要的初始化工作、创建解复用线程后,即进入event_loop()主循环,处理视频播放和SDL消息事件:

main() -->
static void event_loop(VideoState *cur_stream)
{
   
    SDL_Event event;
    ......

    for (;;) {
   
        // SDL event队列为空,则在while循环中播放视频帧。否则从队列头部取一个event,退出当前函数,在上级函数中处理event
        refresh_loop_wait_event(cur_stream, &event);
        // SDL事件处理
        switch (event.type) {
   
        case SDL_KEYDOWN:
            switch (event.key.keysym.sym) {
   
            case SDLK_f:            // f键:强制刷新
                ......
                break;
            case SDLK_p:            // p键
            case SDLK_SPACE:        // 空格键:暂停
                ......
            case SDLK_s:            // s键:逐帧播放
                ......
                break;
            ......
        ......
        }
    }
}

3.2.1 视频播放

主要代码在**refresh_loop_wait_event()**函数中,如下:

static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
   
    double remaining_time = 0.0;
    SDL_PumpEvents();
    while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
   
        if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
   
            SDL_ShowCursor(0);
            cursor_hidden = 1;
        }
        if (remaining_time > 0.0)
            av_usleep((int64_t)(remaining_time * 1000000.0));
        remaining_time = REFRESH_RATE;
        if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
            // 立即显示当前帧,或延时remaining_time后再显示
            video_refresh(is, &remaining_time);
        SDL_PumpEvents();
    }
}

while()语句表示如果SDL event队列为空,则在while循环中播放视频帧;否则从队列头部取一个event,退出当前函数,在上级函数中处理event。
refresh_loop_wait_event()中调用了非常关键的函数video_refresh()video_refresh()函数实现音视频的同步及视频帧的显示,是ffplay.c中最核心函数之一,在“4.3节 视频同步到音频”中详细分析。

3.2.2 SDL消息处理

处理各种SDL消息,比如暂停、强制刷新等按键事件。比较简单。

main() -->
static void event_loop(VideoState *cur_stream)
{
   
    SDL_Event event;
    ......

    for (;;) {
   
        // SDL event队列为空,则在while循环中播放视频帧。否则从队列头部取一个event,退出当前函数,在上级函数中处理event
        refresh_loop_wait_event(cur_stream, &event);
        // SDL事件处理
        switch (event.type) {
   
        case SDL_KEYDOWN:
            switch (event.key.keysym.sym) {
   
            case SDLK_f:            // f键:强制刷新
                ......
                break;
            case SDLK_p:            // p键
            case SDLK_SPACE:        // 空格键:暂停
                ......
                break;
            ......
        ......
        }
    }
}

3.3 解复用线程

解复用线程读取视频文件,将取到的packet根据类型(音频、视频、字幕)存入不同是packet队列中。
为节省篇幅,如下源码中非关键内容的源码使用“…”替代。代码流程参考注释。

/* this thread gets the stream from the disk or the network */
static int read_thread(void *arg)
{
   
    VideoState *is = arg;
    AVFormatContext *ic = NULL;
    int st_i
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值