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


具体处理除了调用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)))) {undefined/* 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) {undefinedif (is->video_stream >= 0) {undefinedav_init_packet(pkt);pkt->data = https://www.isolves.com/it/cxkf/bk/2022-02-22/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) {undefinedif (loop != 1 && (!loop || --loop)) {undefinedstream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0);} else if (autoexit) {undefinedret = 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)) {undefinedpacket_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) {undefinedSDL_Delay(100);}因为之前for循环跳出条件中说明了只有两种情况下才会break出来 , 其一就是is->abort_request为true , 其二直接就goto到fail了 , 因此两种情况下该while循环都不会判断为true , 直接略过 。具体代码原因不明 。
关闭音视频流
【音视频开发-FFplay视频播放流程】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) {undefinedSDL_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队列中供主线程读取并刷新显示 。


推荐阅读