音视频开发-FFplay视频播放流程( 三 )


音视频开发-FFplay视频播放流程

文章插图
 
查找音视频数据流
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函数主要就做了一件事:找符合条件的数据流 。其简单实现可以参考ffmpeg-tutorial项目中tutorial01.c的代码:
// Find the first video streamvideoStream=-1;for(i=0; i<pFormatCtx->nb_streams; i++)if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {undefinedvideoStream=i;break;}if(videoStream==-1)return -1; // Didn't find a video stream注:ffmpeg-tutorial项目是对Stephen Dranger写的7个ffmpeg tutorial做的一个update 。
打开对应的数据流
if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {undefinedret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);}通过最开始的主框架流程图 , 我们可以大概的看到stream_component_open函数中最主要的动作就是调用packet_queue_start和创建video_thread线程 。当然在这之前还有一些处理 , 其中包括:
查找解码器
avctx = ic->streams[stream_index]->codec;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_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) {undefinedif (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) {undefinedis->last_paused = is->paused;if (is->paused)is->read_pause_return = av_read_pause(ic);elseav_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) {undefinedret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);if (is->video_stream >= 0) {undefinedpacket_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标记变量来进行处理 。


推荐阅读