ffplay会把所有的流都.zip.001怎么解压出来来吗

ffplay播放rtsp视频流时,播放过程中随机出现花屏现象。
针对上述问题,首先学习了ffplay源码,了解其播放rtsp流的过程,然后进行错误测试与验证,定位问题位置,最后修改源代码,解决问题。
基本流程学习
阅读ffplay源码,熟悉其播放rtsp视频流的基本流程。
在ffplay源码阅读和分析的基础上,初步得出播放rtsp的函数调用关系,如下图所示。
avformat_open_input函数根据输入的文件名,与rtsp_read_packet关联。
rtsp_read_packet完成每个rtp包的读取和解析,读取主要是利用rtp_read从缓冲区获取数据,解析主要是根据rtp协议,解析rtp包,得到h264码流数据,由rtp_parse_packet完成。
av_read_frame读取一帧数据的avpacket包,主要是调用rtsp_read_packet读取h264码流数据包,然后由av_parser_parse2组成h264 码流包,最终组成一帧数据的avpacket。
发布不同分辨率的rtsp视频流,测试错误产生的原因。
利用VLC发布视频的rtsp服务,经测试,同一种视频封装格式,分辨率越小,花屏现象越少。
分辨率越小,服务端发送给客户端的数据越小,其花屏现象越少,说明花屏现象与服务端发送的数据量有关。
可能的原因是服务端发送的数据量较大时,客户端缓冲区不足,导致数据丢失的问题,从而引起花屏现象。
修改ffmpeg源码,输出客户端接收的数据包信息,验证是否存在数据丢失的问题。
源码修改如下图所示,主要是输出RTP包的序号,根据序号判断是否存在丢包问题。
if(len &0)
av_log(h,AV_LOG_DEBUG,&rtp len %d,time usec %llu&,len,av_gettime());
int16_t seq = AV_RB16(buf+2);
av_log(s-&ic,AV_LOG_DEBUG,&seq %d\n&,seq);
信息输出结果如下图所示,正常情况下,RTP的序号是连续的,而由输出信息可知RTP序号不连续,因而存在丢包的问题。
增加客户端接收数据的缓冲区,避免丢包现象的产生。
源码修改如下图所示,主要是将UDP_MAX_PKT_SIZE增大了10倍。
#define UDP_MAX_PKT_SIZE (65536*10)
s-&buffer_size = is_output ? UDP_TX_BUF_SIZE : UDP_MAX_PKT_SIZE;
tmp = s-buffer_size;
if(setsockopt(udp_fd,SOL_SOCKET,SO_REVBUF,&tmp,sizeof(tmp)) & 0)
av_log(h,AV_LOG_WARNING,&setsockopt(SO_REVBUF):%s\n&,strerror(errno));ffmpeg(3)
运行tutorial03及以后包含音频播放的示例程序,发现播出的声音全是噪声,而ffplay却没出现这个问题,于是决定将ffplay的代码大致看一遍,看看有关音频这部分的处理有何不同。这里只简单介绍ffplay的流程,怎么解决噪音问题将会在下一章说明。
整个ffplay包括6个数据队列,每种数据各两个,包含已解码和未解码:
1、视频数据:未解码的is-&viddec-&queue(is-&videoq),已解码的is-&pictq
2、音频数据:未解码的is-&auddec-&queue(is-&audioq),已解码的is-&sampq
3、字幕数据:未解码的is-&auddec-&queue(is-&subtitleq),已解码的is-&subpq
线程包括以下几个:
1、read_thread,负责读取文件(流)的数据存到相应的未解码数据队列中
2、video_thread、audio_thread、subtitle_thread负责从对应的未解码数据队列中取数据并解码,然后存到已解码的数据队列中
3、event_loop,事件循环,捕获按键、刷新等事件并做相应处理
4、sdl_audio_callback,这个是sdl的音频回调(姑且把它当做一个单独的线程),在这个方法中需要做的是给sdl填充音频数据
总的来说,就是一个线程负责读数据并保存到队列,三个线程负责解码并保存到队列,其余的与事件循环、sdl相关。废话不多说,直接上图:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:58149次
排名:千里之外
原创:29篇
评论:60条
(1)(1)(1)(1)(2)(1)(1)(3)(1)(1)(1)(3)(2)(2)(1)(2)(5)static uint64_t global_video_pkt_pts= AV_NOPTS_VALUE;
还重写了分配帧和销毁帧的方法:C/C++ code
static int my_get_buffer(struct AVCodecContext *c, AVFrame *pic){
int ret= avcodec_default_get_buffer(c, pic);
uint64_t *pts= av_malloc(sizeof(uint64_t));
*pts= global_video_pkt_
pic-&opaque=
static void my_release_buffer(struct AVCodecContext *c, AVFrame *pic){
if(pic) av_freep(&pic-&opaque);
avcodec_default_release_buffer(c, pic);
在使用这个全局时间戳的时候,是这样使用的:C/C++ code
/* NOTE: ipts is the PTS of the _first_ picture beginning in
this packet, if any */
global_video_pkt_pts= pkt-&
len1 = avcodec_decode_video(is-&video_st-&codec,
frame, &got_picture,
pkt-&data, pkt-&size);
(decoder_reorder_pts || pkt-&dts == AV_NOPTS_VALUE)
&& frame-&opaque && *(uint64_t*)frame-&opaque != AV_NOPTS_VALUE)
pts= *(uint64_t*)frame-&
else if(pkt-&dts != AV_NOPTS_VALUE)
pts= pkt-&
pts *= av_q2d(is-&video_st-&time_base);
if (len1 & 0)
if (got_picture) {
if (output_picture2(is, frame, pts) & 0)
av_free_packet(pkt);
if (cur_stream)
stream_pause(cur_stream);
请问,这里为什么要使用一个全局变量,而不是用临时变量呢?而且,一般是不到万不得已,是不是用全局变量的啊。------解决方案--------------------查看代码,如果不需要暂存信息,就可以不用。它这里用,应该是有道理的。
------解决方案--------------------
之所以用全局变量,是因为在my_get_buffer里要用到
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有Ffplay视频播放流程
Ffplay视频播放流程
主框架流程下图是一个使用“gcc+eygpt+graphviz+手工调整”生成的一个ffplay函数基本调用关系图,其中只保留了视频部分,去除了音频处理、字幕处理以及一些细节处理部分。注:图中的数字表示了播放中的一次基本调用流程,X?序号表示退出流程。从上图中我们可以了解到以下几种信息:三个线程:主流程用于视频图像显示和刷新、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函数中:&&& is-&read_tid&&&& = SDL_CreateThread(read_thread, is);read_thread线程主要分为三部分:初始化部分:主要包括SDL_mutex信号量创建、AVFormatContext创建、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。对应ffplay.c文件中的行代码;循环读取数据部分:主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中。对应ffplay.c文件中的行代码;反初始化部分:主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量。对应ffplay.c文件中的行代码;初始化部分主要包括SDL_mutex信号量创建、创建avformat上下文、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流。创建wait_mutex互斥量&&& SDL_mutex *wait_mutex = SDL_CreateMutex();该互斥量主要用于在对(VideoState *)is-&continue_read_thread操作时加保护,如2887行和2925行://代码段一/* if the queue are full, no need to read more */if (infinite_buffer&1 &&&&&&& ……) {&&& /* wait 10 ms */&&& SDL_LockMutex(wait_mutex);&&& SDL_CondWaitTimeout(is-&continue_read_thread, wait_mutex, 10); &&-- line 2887&&& SDL_UnlockMutex(wait_mutex);&&&}&//代码段二ret = av_read_frame(ic, pkt);if (ret & 0) {&&& if (ret == AVERROR_EOF || url_feof(ic-&pb))&&&&&&& eof = 1;&&& if (ic-&pb && ic-&pb-&error)&&&&&&&&&& SDL_LockMutex(wait_mutex);&&& SDL_CondWaitTimeout(is-&continue_read_thread, wait_mutex, 10); &&-- line 2925&&& SDL_UnlockMutex(wait_mutex);&&&}而continue_read_thread从其名字上来看,是一个控制read_thread线程是否继续阻塞的信号量,上面两次阻塞的地方分别 是:packet队列已满,需要等待一会(即超时10ms)或者收到信号重新循环;读数据失败,但是并不是IO错误 (ic-&pb-&error),如读取网络实时数据时取不到数据,此时也需要等待或者收到信号重新循环。注:seek操作时(L1216)和音频队列为空(L2327)时,会发送continue_read_thread信号。AVFormatContext创建(AVFormatContext *)ic = avformat_alloc_context();&&&&&&&& 此处创建的avformat上下文,类似于一个句柄,后续所有avformat相关的函数调用第一个参数都是该上下文指针,如 avformat_open_input、avformat_find_stream_info以及一些和av相关的函数接口第一个参数也是该指针,如 av_find_best_stream、av_read_frame等等。打开输入文件err = avformat_open_input(&ic, is-&filename, is-&iformat, &format_opts);&&&&&&&& 创建好avformat上下文后,就打开is-&filename指定的文件(或流),其中第三个和第四个参数可以传NULL,由ffmpeg自动 侦测待输入流的文件格式,也可以通过is-&iformat手动指定,format_opts参数表示设置的特殊属性。通过调用avformat_open_input函数,我们可以得到输入流的一个基本信息。我们可以通过调用av_dump_format(ic, 0, is-&filename, 0);来输出解析后的码流信息,可以得到如下数据:Input #0,&mpegts, from '/home/nfer/bak/cw880-latency.ts':0B f=0/0&&& Duration: N/A, bitrate: N/A& Program 1&&&&Stream #0:0[0x68]:Video:h264 ([27][0][0][0] / 0x001B), 90k tbn&&&&Stream #0:1[0x67]:Audio:aac([15][0][0][0] / 0x000F), 0 channels即,可以解析出2& 封装格式是mpegts,包含两路数据流2& 流1的PID是0x68,类型是视频,编码格式是H2642& 流2的PID是0x67,类型是音频,编码格式是AAC但是只有这些信息可定无法解码,比如视频的宽高比、图像编码格式(YUV or RGB …)、音频采样率、音频声道数量等等,以及Duration、bitrate等信息。这些信息都需要通过其他函数来解析。解析码流信息err = avformat_find_stream_info(ic, opts);因为avformat_open_input函数只能解析出一些基本的码流信息,不足以满足解码的要求,因此我们调用avformat_find_stream_info函数来尽量的解析出所有的和输入流相关的信息。解析码流的内部实现我们不在此处讨论,先看一看调用后该函数后解析出来的信息(同样采用av_dump_format来输出):Input #0, mpegts, from '/home/nfer/bak/cw880-latency.ts':0B f=0/0&&& Duration:&00:02:53.73, start:&, bitrate:&1983 kb/s& Program 1&&& Stream #0:0[0x68]: Video: h264 (Baseline) ([27][0][0][0] / 0x001B),&yuv420p,&,&30 tbr, 90k tbn,&180k tbc&&& Stream #0:1[0x67]: Audio: aac ([15][0][0][0] / 0x000F),&48000 Hz,&stereo,&fltp,72 kb/s对比上一步获取的信息,我们可以看到新解析出来的信息:2& 码流信息;节目时长00:02:53.73,开始播放时间,码率1983 kb/s2& 视频信息:色彩空间YUV420p,分辨率,帧率30,文件层的时间精度90k,视频层的时间精度180K2& 音频信息:采样率48000,立体声stereo,音频采样格式fltp(float, planar),音频比特率72 kb/s需要注意的是,该函数是一个阻塞操作,即默认情况下会在该函数中阻塞5s。具体的实现是在avformat_open_input函数中有一个for(;;) 循环,其中的一个break条件如下:if (t &= ic-&max_analyze_duration) {&&& av_log(ic, AV_LOG_VERBOSE, "max_analyze_duration %d reached at %"PRId64" microseconds\n", ic-&max_analyze_duration, t);&&&}而ic-&max_analyze_duration的默认值定义在options_table.h文件中,即默认的参数表:{"analyzeduration", "specify how many microseconds are analyzed to probe the input", OFFSET(max_analyze_duration), AV_OPT_TYPE_INT, {.i64 = 5*AV_TIME_BASE }, 0, INT_MAX, D},&#define AV_TIME_BASE&&&&&&&&&&& 1000000&&&&&&&&&&& &--file: avutil.h, line: 229如果觉得这个默认的5s阻塞时间太长,或者甚至觉得完全没有必要,即我们可以手动的设置各种解码的参数,那么可以通过下面的方法将ic-&max_analyze_duration的值修改为1s:ic = avformat_alloc_context();ic-&interrupt_callback.callback = decode_interrupt_ic-&interrupt_callback.opaque =//add by Nferic-&max_analyze_duration =1*;av_log(NULL, AV_LOG_ERROR, "ic-&max_analyze_duration %d.\n", ic-&max_analyze_duration);err = avformat_open_input(&ic, is-&filename, is-&iformat, &ffp-&format_opts);注:红色部分为添加的代码查找音视频数据流if (!video_disable)&&& st_index[AVMEDIA_TYPE_VIDEO] =&&&&&&& av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,&&&&&&&&&&&&&&&&&&&&&&&&&&& wanted_stream[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);av_find_best_stream函数主要就做了一件事:找符合条件的数据流。其简单实现可以参考项目中tutorial01.c的代码:// Find the first video streamvideoStream=-1;for(i=0; i&pFormatCtx-&nb_ i++)& if(pFormatCtx-&streams[i]-&codec-&codec_type==AVMEDIA_TYPE_VIDEO) {&&& videoStream=i;&&&& }if(videoStream==-1)& return -1; // Didn't find a video stream注:项目是对Stephen Dranger写的7个做的一个update。打开对应的数据流if (st_index[AVMEDIA_TYPE_VIDEO] &= 0) {&&& ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);}&通过最开始的主框架流程图,我们可以大概的看到stream_component_open函数中最主要的动作就是调用packet_queue_start和创建video_thread线程。当然在这之前还有一些处理,其中包括:查找解码器&&& avctx = ic-&streams[stream_index]-&&&& codec = avcodec_find_decoder(avctx-&codec_id);如果启动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_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)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &-- Line 2814&//代码段二ret = av_read_frame(ic, pkt);if (ret & 0) {&&& if (ic-&pb && ic-&pb-&error)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &-- Line 2923}其中条件一是调用do_exit --& stream_close中将is-&abort_request置为1的,代码中有多个地方是判断该条件进行exit处理的;条件二很清晰,就是当遇到读数据失败并且是IO错误时,会退出。pause和resume操作处理if (is-&paused != is-&last_paused) {&&& is-&last_paused = is-&&&& 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);&&&}此处的各种判断条件不详细解释,重点是在播放器处理中,写数据失败时需要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_&&&&&&& 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;&&&&&&&&&&&&&&&&&& }&&& }&&& eof=0;&&&}当遇到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);}因为之前中说明了只有两种情况下才会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_EVENTif (ret != 0) {&&& SDL_E&&& event.type = FF_QUIT_EVENT;&&& event.user.data1 =&&& 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文件中的行代码;循环解码部分:主要包括pause和resume操作处理、读取packet处理、AVFILTER处理、然后是将picture写入视频队列中以及每次解码后的清理动作。对应ffplay.c文件中的行代码;反初始化部分:主要包括刷新codec中的数据、释放AVFilterGraph、释放AVPacket以及释放AVFrame。对应ffplay.c文件中的行代码;初始化部分该线程的初始化就是创建了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)的完整生命流程。在项 目中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中的发出的消息
发表评论:
TA的最新馆藏}

我要回帖

更多关于 文件解压不出来 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信