当前位置:龙泉人才网 - 职业人才 -

seekic(大神是这样ffplay简单过滤器的)

  • 职业人才
  • 2024-03-26 12:00
  • 龙泉小编

FFplay简单介绍

1:线程主要分为:read_thread,audio_thread,video_thread,subtitle_thread,event_loop。

seekic(大神是这样ffplay简单过滤器的)

2:数据结构主要分为:PacketQueue,FrameQueue。其中PacketQueue定义的对象为videoq,subtitleq,audioq。FrameQueue定义的对象为pictq,subpq,sampq。

2.1:数据结构类型及初始化分析

seekic(大神是这样ffplay简单过滤器的)

seekic(大神是这样ffplay简单过滤器的)

通过PacketQueue的定义可以看出.PacketQueue内部将会维护一个列表.该列表存放数据类型为MyAVPacketList,由于并不是内存池操作,可以得知AVPavket的每次存放和读取都会涉及到内存的申请和释放,接下来分析FrameQueue,通过下面的定义可以看出FrameQueue内部维护了一个Frame类型的数组.而Frame结构体内部则包含了用于存放AVFrame的成员.且Frame数组在init时会根据不同类型初始化申请不同数量的数组个数.故可以得知AVFrame的每次存放和读取只会涉及到标志位的改变,并不会内存的申请和释放.

seekic(大神是这样ffplay简单过滤器的)

seekic(大神是这样ffplay简单过滤器的)

2.2:PacketQueue队列的数据存取标志位分析

首先分析AVPacket的存放流程,当我们读取出源文件一个AVPacket后,向对应的PacketQueue插入数据,内部其实就是做了一个列表的插入过程.

seekic(大神是这样ffplay简单过滤器的)

seekic(大神是这样ffplay简单过滤器的)

分析PacketQueue读取一个AVPacket流程,通过下面代码可以看出外部定义一个AVPacket,然后每次读取后释放原始列表成员.

seekic(大神是这样ffplay简单过滤器的)

上面介绍了PacketQueue的成员读取和写入流程.写入发生在read_thread线程中,每次读取出一个AVPacket都存放到对应的PacketQueue队列中,而读取发生在decoder_decode_frame方法中,每次读取出一个AVPacket,然后放入解码器解码,解码后读取出对应的AVFrame.

2.3:分析FrameQueue针对AVFrame的存放和读取,通过下面代码可以看出每次在存放AVFrame时,需要通过调用writable获取一个空闲的Frame对象,然后给Frame对象赋值,赋值完成后调用push将windex和size累加.

seekic(大神是这样ffplay简单过滤器的)

Frame的读取流程如下:通过下面的代码可以看出首先调用readable获取一个Frame对象。随后通过next方法将读取出的Fame引用计数减一,rindex累加和size减少。

seekic(大神是这样ffplay简单过滤器的)

上面介绍了FrameQueue的读取和写入。可以看出读取和写入的索引记录了待写入的位置和待读取的位置。而通过Frame内部的AVFrame引用计数的修改用于释放.以上只是简单的队列之间数据的存取。接下来分析音视频数据的同步问题。

2.3:音视频同步分析,目前同步可以看到有三种方式:以音频为标准,以视频为标准,以外部时钟为标准。由于音频的输出为有规律的被动输出(sdl_audio_callback定时回调)。故首先分析以音频为主。这里首先分析一个数据结构Clock,主要声明对象有audclk,vidclk,extclk。总的来说每次在音频输出时都会设置audclk对应的pts,而每次视频的输出也会和音频时间戳进行比对判断延时,如果视频比音频慢则减少延迟时间,如果比音频快,则增加延迟时间

seekic(大神是这样ffplay简单过滤器的)

音频时间戳设置如下:

1, set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);

,2,sync_clock_to_slave(&is->extclk, &is->audclk);

视频同步到音频流程如下:通过获取视频前后两帧数据的pts插值,通过vidclk和audclk的pts插值,来调整delay时长。

seekic(大神是这样ffplay简单过滤器的)

随后通过update_video_pts进行修改videoclk.当设置framedrop为true的话,则会选择进行丢帧。

ffplay工具

ffplay是以FFmpeg框架为基础,外加渲染音视频的库libSDL构建的媒体文件播放器。

一、命令格式

在安装了在命令行中输入如下格式的命令:

1. 主要选项

seekic(大神是这样ffplay简单过滤器的)

2. 一些高级选项

3. 一些快捷键

seekic(大神是这样ffplay简单过滤器的)

二,ffplay 播放音频

播放音频文件的命令:

这时候就会弹出来一个窗口,一边播放MP3文件,一边将播放音频的图画到该窗口上

点击该窗口的任意一个位置,ffplay会按照点击的位置计算出时间的进度,然后seek到计算出来的时间点继续播放。

按下键盘的左键默认快退10s,右键默认快进10s,上键默认快进1min,下键默认快退1min。

按ESC就退出播放进程,按W会绘制音频的波形图。

相关效果图片如下:

seekic(大神是这样ffplay简单过滤器的)

seekic(大神是这样ffplay简单过滤器的)

ffplay 播放视频

播放视频文件的命令:

这时候,就会在新弹出的窗口上播放该视频了。

  1. 如果想要同时播放多个文件,只需在多个命令行下同时执行ffplay就可以了。
  2. 如果按s键就可以进入frame-step模式,即按s键一次就会播放下一帧图像。

五、ffplay 高级使用方式

1. 循环播放

上述命令代表播放视频结束之后会从头再次播放,共循环播放10次。

2. 播放 pm.mp4 ,播放完成后自动退出

3. 以 320 x 240 的大小播放 test.mp4

4. 将窗口标题设置为 "myplayer",循环播放 2 次

5. 播放 双通道 32K 的 PCM 音频数据

六、ffplay音画同步

ffplay也是一个视频播放器,所以不得不提出来的一个问题是:音画同步。ffplay的音画同步的实现方式其实有三种,分别是:以音频为主时间轴作为同步源,以视频为主时间轴作为同步源,以外部时钟为主时间轴作为同步源。

下面就以音频为主时间轴来作为同步源来作为案例进行讲解,而且ffplay默认也是以音频为基准进行对齐的,那么以音频作为对齐基准是如何实现的呢?

首先需要说明的是,播放器接收到的视频帧或者音频帧,内部都是会有时间戳(PTS时钟)来标识它实际应该在什么时刻展示,实际的对齐策略如下:比较视频当前的播放时间和音频当前的播放时间,如果视频播放过快,则通过加大延迟或者重复播放来降低视频播放速度,如果视频播放满了,则通过减小延迟或者丢帧来追赶音频播放的时间点。关键就在于音视频时间的比较和延迟的计算,当前在比较的过程中会设置一个阈值,如果超过预设的阈值就应该作出调整(丢帧或者重复渲染),这就是整个对齐策略。

在使用ffplay的时候,我们可以明确的指定使用那种对齐方式,比如:

上面这个命令显式的指定了使用以音频为基准进行音视频同步的方式播放视频文件,当然这也是ffplay的默认播放设置。

上面这个命令显式的指定了使用以视频为基准进行音视频同步的方式播放视频文件

上面这个命令显式的指定了使用外部时钟为基准进行音视频同步的方式播放视频文件。

FFmpeg是一个开源,免费,跨平台的视频和音频流方案,它提供了一套完整的录制、转换以及流化音视频的解决方案。而ffplay是有ffmpeg官方提供的一个基于ffmpeg的简单播放器。学习ffplay对于播放器流程、ffmpeg的调用等等是一个非常好的例子。本文就是对ffplay的一个基本的流程剖析,很多细节内容还需要继续钻研。

主框架流程

下图是一个使用“gcc+eygpt+graphviz+手工调整”生成的一个ffplay函数基本调用关系图,其中只保留了视频部分,去除了音频处理、字幕处理以及一些细节处理部分。

seekic(大神是这样ffplay简单过滤器的)

从上图中我们可以了解到以下几种信息:

三个线程:主流程用于视频图像显示和刷新、read_thread用于读取数据、video_thread用于解码处理;

视频数据处理:由read_thread读取原始数据解复用后,按照packet的方式放入到队列中;由video_thread从packet队列中读取packet解码后,按照picture的方式放入到队列中;由主流程从picture队列中依次取picture进行显示;

启动流程:启动流程如上图中的数字部分

退出流程:退出流程如上图中的X?序号部分

下面将对三个线程分别加以详细描述。

read_thread线程

从read_thread开始说起而不是从main线程,主要原因是考虑按照视频数据转换的方式比较好理解。

read_thread的创建是在main-->stream_open函数中:

read_thread线程主要分为三部分:

初始化部分:主要包括SDL_mutex信号量创建、AVFormatContext创建、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。对应ffplay.c文件中的2693-2810行代码;

循环读取数据部分:主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中。对应ffplay.c文件中的2812-2946行代码;

反初始化部分:主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量。对应ffplay.c文件中的2947-2972行代码;

初始化部分

主要包括SDL_mutex信号量创建、创建avformat上下文、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。

创建wait_mutex互斥量

该互斥量主要用于在对(VideoState *)is->continue_read_thread操作时加保护,如2887行和2925行:

seekic(大神是这样ffplay简单过滤器的)

而continue_read_thread从其名字上来看,是一个控制read_thread线程是否继续阻塞的信号量,上面两次阻塞的地方分别是:packet队列已满,需要等待一会(即超时10ms)或者收到信号重新循环;读数据失败,但是并不是IO错误(ic->pb->error),如读取网络实时数据时取不到数据,此时也需要等待或者收到信号重新循环。

注:seek操作时(L1216)和音频队列为空(L2327)时,会发送continue_read_thread信号。

AVFormatContext创建

此处创建的avformat上下文,类似于一个句柄,后续所有avformat相关的函数调用第一个参数都是该上下文指针,如avformat_open_input、avformat_find_stream_info以及一些和av相关的函数接口第一个参数也是该指针,如av_find_best_stream、av_read_frame等等。

打开输入文件

创建好avformat上下文后,就打开is->filename指定的文件(或流),其中第三个和第四个参数可以传NULL,由ffmpeg自动侦测待输入流的文件格式,也可以通过is->iformat手动指定,format_opts参数表示设置的特殊属性。

通过调用avformat_open_input函数,我们可以得到输入流的一个基本信息。我们可以通过调用av_dump_format(ic, 0, is->filename, 0);来输出解析后的码流信息,可以得到如下数据:

即,可以解析出

² 封装格式是mpegts,包含两路数据流

² 流1的PID是0x68,类型是视频,编码格式是H264

² 流2的PID是0x67,类型是音频,编码格式是AAC

但是只有这些信息可定无法解码,比如视频的宽高比、图像编码格式(YUV or RGB …)、音频采样率、音频声道数量等等,以及Duration、bitrate等信息。这些信息都需要通过其他函数来解析。

解析码流信息

因为avformat_open_input函数只能解析出一些基本的码流信息,不足以满足解码的要求,因此我们调用avformat_find_stream_info函数来尽量的解析出所有的和输入流相关的信息。

解析码流的内部实现我们不在此处讨论,先看一看调用后该函数后解析出来的信息(同样采用av_dump_format来输出):

对比上一步获取的信息,我们可以看到新解析出来的信息:

² 码流信息;节目时长00:02:53.73,开始播放时间2051.276989,码率1983 kb/s

² 视频信息:色彩空间YUV420p,分辨率1280x720,帧率30,文件层的时间精度90k,视频层的时间精度180K

² 音频信息:采样率48000,立体声stereo,音频采样格式fltp(float, planar),音频比特率72 kb/s

需要注意的是,该函数是一个阻塞操作,即默认情况下会在该函数中阻塞5s。具体的实现是在avformat_open_input函数中有一个for(;;) 循环,其中的一个break条件如下:

而ic->max_analyze_duration的默认值定义在options_table.h文件中,即默认的参数表:

如果觉得这个默认的5s阻塞时间太长,或者甚至觉得完全没有必要,即我们可以手动的设置各种解码的参数,那么可以通过下面的方法将ic->max_analyze_duration的值修改为1s:

查找音视频数据流

av_find_best_stream函数主要就做了一件事:找符合条件的数据流。其简单实现可以参考ffmpeg-tutorial项目中tutorial01.c的代码:

seekic(大神是这样ffplay简单过滤器的)

注:ffmpeg-tutorial项目是对Stephen Dranger写的7个ffmpeg tutorial做的一个update。

打开对应的数据流

如果启动ffplay时通过vcodec参数指定了解码器名称,那么在通过codec_id查找到解码器后,再使用forced_codec_name查找解码avcodec_find_decoder_by_name。但是注意,如果通过解码器名称查找后会覆盖之前通过codec_id查找到解码器,即如果在参数中指定了错误的解码器会导致无法正常播放的。

设置解码参数

opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);

if (!av_dict_get(opts, "threads", NULL, 0))

av_dict_set(&opts, "threads", "auto", 0);

if (avctx->lowres)

av_dict_set(&opts, "lowres", av_asprintf("%d", avctx->lowres), AV_DICT_DONT_STRDUP_VAL);

if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO)

av_dict_set(&opts, "refcounted_frames", "1", 0);

打开解码器

if (avcodec_open2(avctx, codec, &opts) < 0)

return -1;

启动packet队列

packet_queue_start(&is->videoq);

启动packet队列时,会向队列中先放置一个flush_pkt,其中详细缘由后面再讲。

创建video_thread线程

is->video_stream = stream_index;

is->video_st = ic->streams[stream_index];

is->video_tid = SDL_CreateThread(video_thread, is);

is->queue_attachments_req = 1;

注:上述分析过程中没有考虑音频和字幕处理的部分,后续有机会再详解。

循环读取数据部分

该部分是一个for (;;)循环,循环中主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中。

for循环跳出条件

有两处是break处理的:

//代码段一

if (is->abort_request)

break; <-- Line 2814

//代码段二

ret = av_read_frame(ic, pkt);

if (ret < 0) {

if (ic->pb && ic->pb->error)

break; <-- Line 2923

}

其中条件一是调用do_exit --> stream_close中将is->abort_request置为1的,代码中有多个地方是判断该条件进行exit处理的;条件二很清晰,就是当遇到读数据失败并且是IO错误时,会退出。

pause和resume操作处理

if (is->paused != is->last_paused) {

is->last_paused = is->paused;

if (is->paused)

is->read_pause_return = av_read_pause(ic);

else

av_read_play(ic);

}

在ffplay中暂停和恢复的按键操作时p键(SDLK_p)和space键(SDLK_SPACE),会调用toggle_pause--> stream_toggle_pause来修改is->paused标记变量,然后在read_thread线程中通过对is->paused标记变量的判断进行pause和resum(play)的处理。

seek操作处理

if (is->seek_req) {

ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);

if (is->video_stream >= 0) {

packet_queue_flush(&is->videoq);

packet_queue_put(&is->videoq, &flush_pkt);

}

is->seek_req = 0;

}

注:上述代码有所删减,只保留了和视频相关的部分

同上面pause和resume的处理,is->seek_req是在按键操作(SDLK_PAGEUP、SDLK_PAGEDOWN、SDLK_LEFT、SDLK_RIGHT、SDLK_UP和SDLK_DOWN)时,调用stream_seek函数来修改is->seek_req标记变量,然后在read_thread线程中根据is->seek_req标记变量来进行处理。

具体处理除了调用ffmpeg的avformat_seek_file接口外,还向packet队列中放置了一个flush_pkt,这个在video_thread中的处理中会解决seek操作的花屏效果。

packet队列写入失败处理

/* if the queue are full, no need to read more */

if (infinite_buffer<1 &&

(is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE

|| ( (is->audioq .nb_packets > MIN_FRAMES || is->audio_stream < 0 || is->audioq.abort_request)

&& (is->videoq .nb_packets > MIN_FRAMES || is->video_stream < 0 || is->videoq.abort_request

|| (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))

&& (is->subtitleq.nb_packets > MIN_FRAMES || is->subtitle_stream < 0 || is->subtitleq.abort_request)))) {

/* wait 10 ms */

SDL_LockMutex(wait_mutex);

SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);

SDL_UnlockMutex(wait_mutex);

continue;

}

此处的各种判断条件不详细解释,重点是在播放器处理中,写数据失败时需要wait and continue的处理。

读数据结束处理

if (eof) {

if (is->video_stream >= 0) {

av_init_packet(pkt);

pkt->data = NULL;

pkt->size = 0;

pkt->stream_index = is->video_stream;

packet_queue_put(&is->videoq, pkt);

}

SDL_Delay(10);

if (is->audioq.size + is->videoq.size + is->subtitleq.size == 0) {

if (loop != 1 && (!loop || --loop)) {

stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0);

} else if (autoexit) {

ret = AVERROR_EOF;

goto fail;

}

}

eof=0;

continue;

}

当遇到eof,即end of file时,做一下几个步骤:

向packet队列中放置一个null packet,此处用于loop时使用

判断是否是loop操作,如果是就seek到开始位置重新播放

如果是autoexit模式,就goto fail退出

注意,在读数据eof时,读数据部分还有些滞后,即if (is->audioq.size + is->videoq.size + is->subtitleq.size== 0)判断不一定为true,引起在判断前先delay了10ms(SDL_Delay(10););但是仍然不一定为true,因此需要continue。当然下一步av_read_frame失败也会返回AVERROR_EOF,eof会重新赋值为1。即,eof退出会wait到真正的播放完毕。

读数据并写入到对应的音视频队列

ret = av_read_frame(ic, pkt);

if (pkt->stream_index == is->video_stream && pkt_in_play_range

&& !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {

packet_queue_put(&is->videoq, pkt);

}

注:上述代码有所删减,只保留了和视频相关的部分

此处的处理实际上比较简单,就是av_read_frame和packet_queue_put,不详解。

反初始化部分

主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量。

退出前的等待

/* wait until the end */

while (!is->abort_request) {

SDL_Delay(100);

}

因为之前for循环跳出条件中说明了只有两种情况下才会break出来,其一就是is->abort_request为true,其二直接就goto到fail了,因此两种情况下该while循环都不会判断为true,直接略过。具体代码原因不明。

关闭音视频流

if (is->video_stream >= 0)

stream_component_close(is, is->video_stream);

注:上述代码有所删减,只保留了和视频相关的部分

其中stream_component_close关闭视频流做了以下处理:

终止packet队列:packet_queue_abort(&is->videoq);

发送信号给video_thread,避免继续解码阻塞:SDL_CondSignal(is->pictq_cond);

等待vide_thread线程退出:SDL_WaitThread(is->video_tid, NULL);

清空packet队列:packet_queue_flush(&is->videoq);

给主线程发送FF_QUIT_EVENT

if (ret != 0) {

SDL_Event event;

event.type = FF_QUIT_EVENT;

event.user.data1 = is;

SDL_PushEvent(&event);

}

在主线程会接收到FF_QUIT_EVENT消息,从而会调用do_exit函数来做退出处理。

销毁SDL_mutex信号量

SDL_DestroyMutex(wait_mutex);

read_thread基本就分析到这里,下面描述以下video_thread。

video_thread线程

从主框架流程中可以看出,video_thread线程是在read_thread--> stream_component_open中创建的,负责从packet队列中读取packet并解码为picture,然后存储到picture队列中供主线程读取并刷新显示。

video_thread的创建是在read_thread --> stream_component_open函数中:

is->video_tid = SDL_CreateThread(video_thread, is);

read_thread线程同样分为三部分:

初始化部分:主要包括AVFrame创建和AVFilterGraph创建。对应ffplay.c文件中的1881-1895行代码;

循环解码部分:主要包括pause和resume操作处理、读取packet处理、AVFILTER处理、然后是将picture写入视频队列中以及每次解码后的清理动作。对应ffplay.c文件中的1897-1966行代码;

反初始化部分:主要包括刷新codec中的数据、释放AVFilterGraph、释放AVPacket以及释放AVFrame。对应ffplay.c文件中的1972-1978行代码;

初始化部分

该线程的初始化就是创建了AVFrame和AVFilterGraph,其中AVFilterGraph还是和编译宏包含,如果没有打开CONFIG_AVFILTER可以直接省略。

is->video_tid = SDL_CreateThread(video_thread, is);

… …

AVFrame *frame = av_frame_alloc();

#if CONFIG_AVFILTER

AVFilterGraph *graph = avfilter_graph_alloc();

#endif

循环解码部分

主要包括pause和resume操作处理、读取packet处理、AVFILTER处理、然后是将picture写入视频队列中以及每次解码后的清理动作。

pause和resume操作处理

video_thread中的关于pause和resume的处理比较简单,就是如果是pause状态就delay(线程sleep):

while (is->paused && !is->videoq.abort_request)

SDL_Delay(10);

读取packet处理

avcodec_get_frame_defaults(frame);

av_free_packet(&pkt);

ret = get_video_frame(is, frame, &pkt, &serial);

//关于frame的一些处理

av_frame_unref(frame);

从上述代码中可以看出,一个frame(和packet)的完整生命流程。

在ffmpeg-tutorial项目中tutorial01.c中的例子是使用avcodec_alloc_frame()来申请并设置default value的操作,但是在这里就分成了两步:av_frame_alloc()然后avcodec_get_frame_defaults(frame)。

av_free_packet实际上清空上一次get_video_frame中获取的packet数据,函数本身是有异常处理的,所以连续调用两次av_free_packet是没有问题的。

get_video_frame函数中主要部分是packet_queue_get然后avcodec_decode_video2,即从packet队列中读取数据然后进行解码,具体内容有机会另开文章进行讲解。

AVFILTER处理

AVFILTER处理是一个比较模块化很高的处理部分,大致流程包括以下几步:

释放旧的AVFilterGraph并创建一个新的:avfilter_graph_free()和avfilter_graph_alloc()

配置video filters:configure_video_filters

向buffersrc中添加frame:av_buffersrc_add_frame

情况原有的frame和packet:av_frame_unref、avcodec_get_frame_defaults和av_free_packet

从buffersink中读取处理后的frame:av_buffersink_get_frame_flags

简单的理解就是:

将picture写入视频队列

如果需要avfilter处理,那么处理完后或者不需要avfilter处理,解码完成后的frame会调用queue_picture写入到picture队列中。具体细节不详解。

解码后的清理动作

使用完packet后,必须从frame中释放出来:av_frame_unref。如api说明:Unreference allthe buffers referenced by frame and reset the frame fields.

for循环跳出条件

有以下几种情况下会break出for循环:

get_video_frame读数据失败,并且返回<0:该函数失败条件和read_thread其实是一致的,即当q->abort_request为true时;

configure_video_filters配置filter失败:该函数失败的情况下,我遇到的一种就是avfilter_graph_create_filter创建crop filter时失败,原因在于在configureffmpeg时没有把filter配置打开,导致只有默认的几个filter,其他一些特性filter都没有添加进行;

av_buffersrc_add_frame添加frame失败:该函数属于api,不详解;

queue_picture保存picture失败:该函数的失败条件是当is->videoq.abort_request为true时;

即正常情况下,有两种退出模式:

正常播放完成后退出,此时会通过get_video_frame读数据失败退出

如果是按ESCAPE和Q键退出,会直接退出,则不会等到,直接在queue_picture函数失败

反初始化部分

反初始化部分比较简单,就是先通知avcodec进行flush数据,然后依次释放AVFilterGraph、AVPacket和AVFrame。

video_thread讲解的比较粗糙,主要原因还是由于个人了解的知识有所欠缺,后续有机会会补上。

主线程

主流程用于视频图像显示和刷新,实际上还主线程是一个事件驱动的,就是一个wait_event然后switch处理,然后继续for循环。

refresh_loop_wait_event处理

该函数会从event队列中读取出event,SDL_PumpEvents、SDL_PeepEvents。同时会调用video_refresh来进行视频刷新和显示。此处会有大量和SDL API相关的操作,由于个人能力有限暂不分析。

event的switch处理

该event的处理分为以下几类:

SDL_KEYDOWN键盘按键事件

SDL_VIDEOEXPOSE屏幕重画事件

SDL_MOUSEBUTTONDOWN鼠标按下事件,如果启动ffplay时有exitonmousedown参数,会相应鼠标按下事件,然后退出播放;

SDL_MOUSEMOTION鼠标移动事件,主要seek操作

SDL_VIDEORESIZE视频大小变化事件,比如视频中间会出现大小变化,会触发该事件

SDL_QUIT、FF_QUIT_EVENT退出事件,如read_thread中出现各种异常会发送该消息

FF_ALLOC_EVENT事件比较特殊,如代码中的注释“ifthe queue is aborted, we have to pop the pending ALLOC event or wait for theallocation to complete”,该消息是video_thread中的发出的消息

总结;总结;有什么问题和需要相关资料的都可以私信‘资料'两字可MF领取相关资料,C++、linux,shell,Kali,

seekic(大神是这样ffplay简单过滤器的)

ffpaly,总结遇到的一些问题

1 如果要用vs2010进行调试,只能下载window下编译好的ffmpeg开发库而不是使用mingw编译,SDL也一样

2 ffpaly的编译问题相对而言不太多:

2.1 config.h文件可以使用mingw下configure命令生成的,编译时一些宏报错可以直接修改

2.2 opinion[]数组报错是由于vs2010编译器不支持数组定义时 {.xx=xxxx} 的方式,去掉.xx=即可,

2.3 一些提示找不到的函数(isNAN之类)可以在ffmpeg源码中找到直接copy过来

2.4 提示main函数错误时注释掉SDL_main.h文件中的 #define main SDL_main

2.5 重定义无法识别的类型

#define PRIu64 "I64u"

#define PRId64 "I64d"

2.6 ffplay.c文件注意加上#ifdef __cplusplus extern "C" .....

2.7 生成成功后控制台提示播放失败,如果提示“Could not initialize SDL ,(Did you set the DISPLAY variable?) ")”,注释下面代码

#if !defined(__MINGW32__) && !defined(__APPLE__)

// flags |= SDL_INIT_EVENTTHREAD; /* Not supported on Windows or Mac OS X */

#endif

3 windows下的SDL库说明 (ver 1.2.15)

3.1 SDL在windows下创建窗口时会先查看用户有没有设置环境变量“SDL_WINDOWID”有没有被设置,有的话就将其值作为与自己关联的窗口句柄,没有则createwindow创建窗口,所以如果你想修改ffplay代码,是sdl播放窗口与自己的mfc窗口关联,可以像这样类似的代码

<span style="font-size:18px;">//将CSTATIC控件和sdl显示窗口关联

HWND hWnd = this->GetDlgItem(IDC_PICURE_CONTROL)->GetSafeHwnd();

if( hWnd !=NULL)

{

char sdl_var[64];

sprintf(sdl_var, "SDL_WINDOWID=%d", hWnd); //主窗口句柄 //这里一定不能有空格SDL_WINDOWID=%d"

SDL_putenv(sdl_var);

char *myvalue = SDL_getenv("SDL_WINDOWID"); //让SDL取得窗口ID

}</span>

3.2 如果SDL窗口是由内部创建而不是用户关联,虽然官方不建议使用破坏跨平台性质的代码,但依然给出了解决方案,:

You can easily grab the handle of the Window using a single function inside SDL. Please note, I don't recommend you do this unless you really need to do something OS specific. This will almost certainly break cross-platform compatibility.

1

2

3

4

5

6

7

8

9

SDL_SysWMinfo SysInfo; //Will hold our Window information

SDL_VERSION(&SysInfo.version);//Set SDL version

if(SDL_GetWMInfo(&SysInfo) <= 0) {

printf("%s : %d ", SDL_GetError(), SysInfo.window);

returnfalse;

}

HWNDWindowHandle = SysInfo.window; //There it is, Win32 handle

You can check out the other possible values to grab here:

http://sdl.beuc.net/sdl.wiki/SDL_SysWMInfo

If this is something you want to do, but you still want your game/application to be cross platform, then you can use pre-processor directives:

1

2

3

4

5

#ifdef __WIN32__

HWNDWindowHandle = SysInfo.window; //Win32 window handle

#else

Window WindowHandle = SysInfo.window; //X11 window handle

#endif

Note: This might be useful if you are making an application that you want to sit in the tray, or you want the window to dock (just a few examples).

3.3 ffplay中sdl创建的窗口带有标题栏,如果想去的当然是可以的,通过给SDL_SetVideoMode的flag标志传递SDL_NOFRAME标记,在ffplay.c中你需要在两个地方修改 static int video_open(VideoState *is, int force_set_video_mode, VideoPicture *vp)函数与 static void event_loop(VideoState *cur_stream)

免责声明:本文内容来源于网络或用户投稿,龙泉人才网仅提供信息存储空间服务,不承担相关法律责任。若收录文章侵犯到您的权益/违法违规的内容,可请联系我们删除。
https://www.lqrc.cn/a/zhiye/110177.html

  • 关注微信

猜你喜欢

微信公众号