0
点赞
收藏
分享

微信扫一扫

ffplay源码解析1 主线程

云岭逸人 2024-09-22 阅读 17

目录


ffplay源码解析1 主线程
播放器常用功能包括:播放、停止、暂停、拖动、快进、快退、变速播放。

1.mian函数的参数

int main(int argc, char **argv)

argc: 这是一个整型参数,表示命令行中传递给程序的参数数量。
argv: 这是一个字符指针数组,存储了命令行参数的具体字符串值。**argv相当于 *argv[]。
argc 提供参数的数量,而 argv 提供每个参数的内容,便于程序根据用户输入进行相应操作。

2.初始化

包括对FFmpeg的初始化,对传递的参数进行初始化,SDL的初始化

2.1 FFmpeg初始化

#if CONFIG_AVDEVICE  //如果在编译时定义了 CONFIG_AVDEVICE,则条件为真,编译器将编译 #if 和 #endif 之间的代码;否则,将跳过这些代码。
    avdevice_register_all(); //通过调用FFmpeg的API遍历所有可用的设备驱动程序,并将它们注册到 FFmpeg 中。
#endif
    avformat_network_init(); //初始化网络
signal(SIGINT , sigterm_handler); /* Interrupt (ANSI).    */
signal(SIGTERM, sigterm_handler); /* Termination (ANSI).  */

sigterm_handler 函数是一个信号处理器,专门用于处理接收到的信号。当程序接收到中断或终止信号时(如 SIGINT 或 SIGTERM),这个函数将被调用,程序将以状态码 123 终止。

 show_banner(argc, argv, options);

show_banner 函数负责显示程序的启动信息,包括程序名称、版权和相关库的配置信息。

2.2 对传递的参数进行初始化

parse_options(NULL, argc, argv, options, opt_input_file);

首先parse_options对用户输入的参数进行解析

对参数两个判断

  1. input_filename是否为空,空的话异常退出,exit(1);
  2. display_disable是否显示视频,显示的话audio_disable = 1

2.3 SDL的初始化

flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;

其中

#define SDL_INIT_TIMER  0x00000001  // 二进制:0000 0001
#define SDL_INIT_AUDIO  0x00000010  // 二进制:0001 0000
#define SDL_INIT_VIDEO  0x00000020  // 二进制:0010 0000
所有可能的 flags 结果组合有8种
由于每个模块可以启用或关闭,这里是每种可能的组合:

无任何模块启用:
flags = 0(即所有位都为 0)
仅启用 SDL_INIT_TIMER:
flags = 0x000000010000 0001)
仅启用 SDL_INIT_AUDIO:
flags = 0x000000100001 0000)
仅启用 SDL_INIT_VIDEO:
flags = 0x000000200010 0000)
启用 SDL_INIT_TIMER 和 SDL_INIT_AUDIO:
flags = 0x000000110001 0001)
启用 SDL_INIT_TIMER 和 SDL_INIT_VIDEO:
flags = 0x000000210010 0001)
启用 SDL_INIT_AUDIO 和 SDL_INIT_VIDEO:
flags = 0x000000300011 0000)
启用 SDL_INIT_TIMER、SDL_INIT_AUDIO 和 SDL_INIT_VIDEO:
flags = 0x000000310011 0001

如果禁用音频display_disable == true,那么把音频标志从flags中去掉

if (display_disable)
        flags &= ~SDL_INIT_VIDEO;

如果禁用音频,那么把视频标志从flag中去掉

if (audio_disable)
        flags &= ~SDL_INIT_AUDIO;

Flush Packet:在 FFmpeg 中,flush_pkt 常用于刷新缓冲区(flush),特别是在处理完一个流后,发送一个这样的包可以让解码器知道流结束了,并清除其内部缓存。

av_init_packet(&flush_pkt);				// 初始化flush_packet
    flush_pkt.data = (uint8_t *)&flush_pkt; // 初始化为数据指向自己本身

2.4 创建窗口

如果没有禁用视频if (!display_disable)才往下执行

判断窗口有没有边框,把标志加到flags里面

if (borderless)
            flags |= SDL_WINDOW_BORDERLESS; //无边框,不能调整大小
        else
            flags |= SDL_WINDOW_RESIZABLE;

创建窗口

window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);

设置渲染质量

SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");

如果窗口创建成功,使用SDL 库的一个函数,给窗口创建一个渲染器renderer

renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

在 SDL (Simple DirectMedia Layer) 中,renderer 是一个用于渲染图形的对象,它负责将图像绘制到窗口上。具体来说,renderer 提供了一系列函数和功能,用于处理绘图、图像加载、纹理管理等。

2.5 开启read_thread读取线程

is = stream_open(input_filename, file_iformat);

stream_open 函数用于打开和初始化一个流(stream)
它接受两个参数:
input_filename:指定的输入文件名或路径。
file_iformat:文件格式,可能是一个指示要打开的文件类型的参数。
返回值 is 是一个指向 VideoState 结构体的指针,用于表示视频播放器的状态和控制。
VideoState 结构体是一个很大的结构体,在放在附1

stream_open函数做了很多初始化工作,例如初始化帧队列,初始化时钟,初始化音量,创建读线程
初始化结构体

	VideoState *is;//生命结构体指针
    is = av_mallocz(sizeof(VideoState));	/* 分配结构体并初始化 */
    if (!is)
        return NULL;
    is->filename = av_strdup(filename);
    if (!is->filename)
        goto fail;
    is->iformat = iformat; //成员变量赋值
    is->ytop    = 0;
    is->xleft   = 0;

初始化帧队列
init了音频,视频,字幕的包队列和帧队列,有一个创建不成功就返回fail

	if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)//1:表示队列在初始化时会分配内存
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    if (packet_queue_init(&is->videoq) < 0 ||
            packet_queue_init(&is->audioq) < 0 ||
            packet_queue_init(&is->subtitleq) < 0)
        goto fail;

初始化时钟
初始化音频时钟

init_clock(&is->audclk, &is->audioq.serial);

初始化音频
限制启动音量的范围:

startup_volume = av_clip(startup_volume, 0, 100);

将启动音量转换为 SDL 的音量范围:

startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);

相关设置:

is->audio_volume 设置为经过调整后的启动音量值。
is->muted 设置为 0,表示未静音状态。
is->av_sync_type 设置为 av_sync_type,可能是与音视频同步相关的类型或设置。

创建读线程

is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);

使用 SDL 函数 SDL_CreateThread 创建一个名为 “read_thread” 的线程,并将该线程的标识符保存在 is->read_tid 中。
read_thread 是一个函数指针,用于指定线程的入口函数。

2.6 事件响应

事件循环监控,响应相关的键盘事件(f键,p键,空格等),鼠标事件,窗口事件等

event_loop(is);

附1 VideoState 结构体

typedef struct VideoState {
    SDL_Thread	*read_tid;      // 读线程句柄
    AVInputFormat	*iformat;   // 指向demuxer
    int		abort_request;      // =1时请求退出播放
    int		force_refresh;      // =1时需要刷新画面,请求立即刷新画面的意思
    int		paused;             // =1时暂停,=0时播放
    int		last_paused;        // 暂存“暂停”/“播放”状态
    int		queue_attachments_req;
    int		seek_req;           // 标识一次seek请求
    int		seek_flags;         // seek标志,诸如AVSEEK_FLAG_BYTE等
    int64_t		seek_pos;       // 请求seek的目标位置(当前位置+增量)
    int64_t		seek_rel;       // 本次seek的位置增量
    int		read_pause_return;
    AVFormatContext *ic;        // iformat的上下文
    int		realtime;           // =1为实时流

    Clock	audclk;             // 音频时钟
    Clock	vidclk;             // 视频时钟
    Clock	extclk;             // 外部时钟

    FrameQueue	pictq;          // 视频Frame队列
    FrameQueue	subpq;          // 字幕Frame队列
    FrameQueue	sampq;          // 采样Frame队列

    Decoder auddec;             // 音频解码器
    Decoder viddec;             // 视频解码器
    Decoder subdec;             // 字幕解码器

    int audio_stream ;          // 音频流索引

    int av_sync_type;           // 音视频同步类型, 默认audio master

    double			audio_clock;            // 当前音频帧的PTS+当前帧Duration
    int             audio_clock_serial;     // 播放序列,seek可改变此值
    // 以下4个参数 非audio master同步方式使用
    double			audio_diff_cum;         // used for AV difference average computation
    double			audio_diff_avg_coef;
    double			audio_diff_threshold;
    int			audio_diff_avg_count;
    // end

    AVStream		*audio_st;              // 音频流
    PacketQueue		audioq;                 // 音频packet队列
    int			audio_hw_buf_size;          // SDL音频缓冲区的大小(字节为单位)
    // 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1,
    // 否则指向frame中的音频
    uint8_t			*audio_buf;             // 指向需要重采样的数据
    uint8_t			*audio_buf1;            // 指向重采样后的数据
    unsigned int		audio_buf_size;     // 待播放的一帧音频数据(audio_buf指向)的大小
    unsigned int		audio_buf1_size;    // 申请到的音频缓冲区audio_buf1的实际尺寸
    int			audio_buf_index;            // 更新拷贝位置 当前音频帧中已拷入SDL音频缓冲区
    // 的位置索引(指向第一个待拷贝字节)
    // 当前音频帧中尚未拷入SDL音频缓冲区的数据量:
    // audio_buf_size = audio_buf_index + audio_write_buf_size
    int			audio_write_buf_size;
    int			audio_volume;               // 音量
    int			muted;                      // =1静音,=0则正常
    struct AudioParams audio_src;           // 音频frame的参数
#if CONFIG_AVFILTER
    struct AudioParams audio_filter_src;
#endif
    struct AudioParams audio_tgt;       // SDL支持的音频参数,重采样转换:audio_src->audio_tgt
    struct SwrContext *swr_ctx;         // 音频重采样context
    int frame_drops_early;              // 丢弃视频packet计数
    int frame_drops_late;               // 丢弃视频frame计数

    enum ShowMode {
        SHOW_MODE_NONE = -1,    // 无显示
        SHOW_MODE_VIDEO = 0,    // 显示视频
        SHOW_MODE_WAVES,        // 显示波浪,音频
        SHOW_MODE_RDFT,         // 自适应滤波器
        SHOW_MODE_NB
    } show_mode;

    // 音频波形显示使用
    int16_t sample_array[SAMPLE_ARRAY_SIZE];    // 采样数组
    int sample_array_index;                     // 采样索引
    int last_i_start;                           // 上一开始
    RDFTContext *rdft;                          // 自适应滤波器上下文
    int rdft_bits;                              // 自使用比特率
    FFTSample *rdft_data;                       // 快速傅里叶采样

    int xpos;
    double last_vis_time;
    SDL_Texture *vis_texture;       // 音频Texture

    SDL_Texture *sub_texture;       // 字幕显示
    SDL_Texture *vid_texture;       // 视频显示

    int subtitle_stream;            // 字幕流索引
    AVStream *subtitle_st;          // 字幕流
    PacketQueue subtitleq;          // 字幕packet队列

    double frame_timer;             // 记录最后一帧播放的时刻
    double frame_last_returned_time;    // 上一次返回时间
    double frame_last_filter_delay;     // 上一个过滤器延时

    int video_stream;               // 视频流索引
    AVStream *video_st;             // 视频流
    PacketQueue videoq;             // 视频队列
    double max_frame_duration;      // 一帧最大间隔. above this, we consider the jump a timestamp discontinuity
    struct SwsContext *img_convert_ctx; // 视频尺寸格式变换
    struct SwsContext *sub_convert_ctx; // 字幕尺寸格式变换
    int eof;            // 是否读取结束

    char *filename;     // 文件名
    int width, height, xleft, ytop; // 宽、高,x起始坐标,y起始坐标
    int step;           // =1 步进播放模式, =0 其他模式

#if CONFIG_AVFILTER
    int vfilter_idx;
    AVFilterContext *in_video_filter;   // the first filter in the video chain
    AVFilterContext *out_video_filter;  // the last filter in the video chain
    AVFilterContext *in_audio_filter;   // the first filter in the audio chain
    AVFilterContext *out_audio_filter;  // the last filter in the audio chain
    AVFilterGraph *agraph;              // audio filter graph
#endif
    // 保留最近的相应audio、video、subtitle流的steam index
    int last_video_stream, last_audio_stream, last_subtitle_stream;

    SDL_cond *continue_read_thread; // 当读取数据队列满了后进入休眠时,可以通过该condition唤醒读线程
} VideoState;
举报

相关推荐

0 条评论