ffplay播放控制代码分析
来源:互联网 发布:飞鱼网络直播 编辑:程序博客网 时间:2024/06/05 02:29
*****************************************************************************
* ffplay系列博客: *
* ffplay播放器原理剖析 *
* ffplay播放器音视频同步原理 *
* ffplay播放控制代码分析 *
* 视频主观质量对比工具(Visual comparision tool based on ffplay) *
*****************************************************************************
ffplay可以响应各种控制请求,比如快进快退,暂停,单帧播放等,具体是如何做的呢?
ffplay通过获取键盘和鼠标事情来执行各种控制逻辑,函数调用关系如下:
main() -> event_loop() --> refresh_loop_wait_event(cur_stream, &event)
|--> case key_p or SPACE: toggle_pause()
|--> case key_s: step_to_next_frame()
|--> case key_left, right, up, down: stream_seek()
main()函数完成初始化工作后,进入了事件队列的循环,循环中首先调用refresh_loop_wait_event(),该函数先获取事件队列中的事件,没有任何事件输入时,则进行video frame的渲染。如果有事件输入,则响应。
暂停
typedef struct Clock { double pts; /* clock base */ double pts_drift; /* clock base minus time at which we updated the clock */ double last_updated; double speed; int serial; /* clock is based on a packet with this serial */ int paused; int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */} Clock;
pause的相关代码和注释如下:
static void toggle_pause(VideoState *is){ stream_toggle_pause(is); is->step = 0;}static void stream_toggle_pause(VideoState *is){ if (is->paused) { //如果当前状态就是暂停,则接下来进入播放状态,需要更新vidclk is->frame_timer += av_gettime_relative() / 1000000.0 - is->vidclk.last_updated; if (is->read_pause_return != AVERROR(ENOSYS)) { is->vidclk.paused = 0; } set_clock(&is->vidclk, get_clock(&is->vidclk), is->vidclk.serial); } set_clock(&is->extclk, get_clock(&is->extclk), is->extclk.serial); is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused; // pause状态反转}static double get_clock(Clock *c){ if (*c->queue_serial != c->serial) return NAN; if (c->paused) { //如果当前是暂停状态,则返回最新的pts即可,因为暂停时时间没走 return c->pts; } else { // 如果当前正处在播放状态,则返回的时间为最新的pts + 更新pts之后流逝的时间 double time = av_gettime_relative() / 1000000.0; //这里返回的时间实际为 c->pts + time - c->last_updated return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed); //这里正常速度播放,speed=1.0 }}static void set_clock(Clock *c, double pts, int serial){ double time = av_gettime_relative() / 1000000.0; set_clock_at(c, pts, serial, time);}static void set_clock_at(Clock *c, double pts, int serial, double time){ c->pts = pts; c->last_updated = time; c->pts_drift = c->pts - time; c->serial = serial;}
// 暂停状态下的read_threadstatic int read_thread(void *arg){ ...... for (;;) { ...... /* if the queue are full, no need to read more */ // 暂停后,这个if条件满足,packet queue中有足够的包,将不再继续读包了 if (infinite_buffer<1 && (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) { /* wait 10 ms */ SDL_LockMutex(wait_mutex); SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10); SDL_UnlockMutex(wait_mutex); continue; } ...... ret = av_read_frame(ic, pkt); ...... }}对sdl_audio_callbakc的影响:sdl_audio_callback不再从audio sample queue中拿解码后的音频数据了,而是直接播放静音。
// 暂停状态下的audio_decode_framestatic int audio_decode_frame(VideoState *is){ int data_size, resampled_data_size; int64_t dec_channel_layout; av_unused double audio_clock0; int wanted_nb_samples; Frame *af; if (is->paused) // pause状态下直接返回-1,不从audio sample queue拿audio sample了 return -1; ......}// 暂停状态下的sdl_audio_callbackstatic void sdl_audio_callback(void *opaque, Uint8 *stream, int len){ ......while (len > 0) { if (is->audio_buf_index >= is->audio_buf_size) { audio_size = audio_decode_frame(is); //此时函数返回-1 if (audio_size < 0) { // 直接输入静音 /* if error, just output silence */ is->audio_buf = NULL; is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size; } else { if (is->show_mode != SHOW_MODE_VIDEO) update_sample_display(is, (int16_t *)is->audio_buf, audio_size); is->audio_buf_size = audio_size; } is->audio_buf_index = 0; } len1 = is->audio_buf_size - is->audio_buf_index; if (len1 > len) len1 = len; if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME) memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1); else { //暂停状态下直接往stream中填充0,静音 memset(stream, 0, len1); if (!is->muted && is->audio_buf) SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume); } len -= len1; stream += len1; is->audio_buf_index += len1; } ......}
static Frame *frame_queue_peek_writable(FrameQueue *f){ /* wait until we have space to put a new frame */ SDL_LockMutex(f->mutex); while (f->size >= f->max_size && !f->pktq->abort_request) { printf("frame queue peek writable: f->size %d >= f->max_size %d\n", f->size, f->max_size); SDL_CondWait(f->cond, f->mutex); } SDL_UnlockMutex(f->mutex); if (f->pktq->abort_request) return NULL; return &f->queue[f->windex];}video渲染也会停止:
static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) { double remaining_time = 0.0; SDL_PumpEvents(); while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_ALLEVENTS)) { if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) { SDL_ShowCursor(0); cursor_hidden = 1; } if (remaining_time > 0.0) av_usleep((int64_t)(remaining_time * 1000000.0)); remaining_time = REFRESH_RATE; if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh)) // pause状态不再刷新video frame video_refresh(is, &remaining_time); SDL_PumpEvents(); }}
快进快退
/* seek in the stream */static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes){ if (!is->seek_req) { is->seek_pos = pos; is->seek_rel = rel; is->seek_flags &= ~AVSEEK_FLAG_BYTE; if (seek_by_bytes) is->seek_flags |= AVSEEK_FLAG_BYTE; is->seek_req = 1; // 设置seek request = 1 SDL_CondSignal(is->continue_read_thread); }}seek对read_thread的影响:
static int read_thread(void *arg){ VideoState *is = arg; ...... for (;;) { ...... if (is->seek_req) { //执行seek请求 int64_t seek_target = is->seek_pos; int64_t seek_min = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN; int64_t seek_max = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX; ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "%s: error while seeking\n", is->ic->filename); } else { if (is->audio_stream >= 0) { packet_queue_flush(&is->audioq); // 将audio packet queue清空 packet_queue_put(&is->audioq, &flush_pkt); } if (is->subtitle_stream >= 0) { packet_queue_flush(&is->subtitleq); packet_queue_put(&is->subtitleq, &flush_pkt); } if (is->video_stream >= 0) { packet_queue_flush(&is->videoq); // 将video packet queue清空 packet_queue_put(&is->videoq, &flush_pkt); } if (is->seek_flags & AVSEEK_FLAG_BYTE) { set_clock(&is->extclk, NAN, 0); } else { set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0); } } is->seek_req = 0; is->queue_attachments_req = 1; is->eof = 0; if (is->paused) step_to_next_frame(is); } ...... ret = av_read_frame(ic, pkt); // 从源文件中读取内容到pkt结构中 /* check if packet is in play range specified by user, then queue, otherwise discard */ stream_start_time = ic->streams[pkt->stream_index]->start_time; // 下面的duration是通过命令传递给ffplay的指定播放时长的参数,所以判断pkt的时间戳是否在duration内 pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; pkt_in_play_range = duration == AV_NOPTS_VALUE || (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) * av_q2d(ic->streams[pkt->stream_index]->time_base) - (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000 <= ((double)duration / 1000000); if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { packet_queue_put(&is->audioq, pkt); // 读到的pkt为audio,放入audio queue(is->audioq) } else 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); // 读到的pkt为video,放入video queue(is->videoq) } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) { packet_queue_put(&is->subtitleq, pkt); // 读到的pkt为subtitle,放到subtitile queue中 } else { av_packet_unref(pkt); } } ......}
单帧播放
static void step_to_next_frame(VideoState *is){ /* if the stream is paused unpause it, then step */ if (is->paused) // 如果本来是pause状态,则先进入播放状态 stream_toggle_pause(is); is->step = 1; //设置标志位}
/* called to display each frame */static void video_refresh(void *opaque, double *remaining_time){ VideoState *is = opaque; double time; Frame *sp, *sp2; if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime) check_external_clock_speed(is); ...... if (is->video_st) {retry: if (frame_queue_nb_remaining(&is->pictq) == 0) { // nothing to do, no picture to display in the queue } else { double last_duration, duration, delay; Frame *vp, *lastvp; /* dequeue the picture */ lastvp = frame_queue_peek_last(&is->pictq); //取Video Frame Queue上一帧图像 vp = frame_queue_peek(&is->pictq); //取Video Frame Queue当前帧图像 ...... if (is->paused) goto display; /* compute nominal last_duration */ last_duration = vp_duration(is, lastvp, vp); //计算两帧之间的时间间隔 delay = compute_target_delay(last_duration, is); //计算当前帧与上一帧渲染的时间差 time= av_gettime_relative()/1000000.0; //is->frame_timer + delay是当前帧渲染的时刻,如果当前时间还没到帧渲染的时刻,那就要sleep了 if (time < is->frame_timer + delay) { // remaining_time为需要sleep的时间 *remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time); goto display; } is->frame_timer += delay; if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) is->frame_timer = time; SDL_LockMutex(is->pictq.mutex); if (!isnan(vp->pts)) update_video_pts(is, vp->pts, vp->pos, vp->serial); SDL_UnlockMutex(is->pictq.mutex); if (frame_queue_nb_remaining(&is->pictq) > 1) { Frame *nextvp = frame_queue_peek_next(&is->pictq); duration = vp_duration(is, vp, nextvp); // 如果当前帧显示时刻早于实际时刻,说明解码慢了,帧到的晚了,需要丢弃不能用于显示了,不然音视频不同步了。 if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){ is->frame_drops_late++; frame_queue_next(&is->pictq); goto retry; } } ...... frame_queue_next(&is->pictq); is->force_refresh = 1; //显示当前帧 if (is->step && !is->paused) //如果当前是单帧播放模式,渲染当前帧之后,马上进入暂停状态,且is->step置为0 stream_toggle_pause(is); }display: /* display picture */ if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown) video_display(is); } is->force_refresh = 0; ......}
结语
经过上面的分析,可以了解ffplay如何实现各种操作,对播放器有了更全面的理解了。
版权声明:本文为博主原创文章,未经博主允许请勿转载。
- ffplay播放控制代码分析
- 【FFplay】零基础读懂视频播放器控制原理——ffplay播放器源代码分析
- 零基础读懂视频播放器控制原理——ffplay播放器源代码分析
- 零基础读懂视频播放器控制原理: ffplay 播放器源代码分析
- 零基础读懂视频播放器控制原理——ffplay播放器源代码分析(一)
- 零基础读懂视频播放器控制原理——ffplay播放器源代码分析(二)
- ffplay中关于播放卡顿问题分析
- ffplay中关于播放卡顿问题分析
- ffplay播放视频出现segment fault问题分析和解决
- ffplay分析
- ffplay分析
- ffplay分析
- ffplay分析
- flash 播放控制代码
- Ffplay视频播放流程
- ffplay播放YUV视频
- FFplay 播放器
- FFPlay视频播放流程
- centos 安装 MySQL-python
- Linux下Redis 安装使用
- leetcode 600. Non-negative Integers without Consecutive Ones 非负整数不包括连续的1 + DP动态规划
- python 获取目录下文件(转)
- nginx日志管理
- ffplay播放控制代码分析
- liunx命令:tomcat日志分析和日志统计命令
- 学习笔记
- 最小二乘法
- Sublime Text 3 java中文乱码问题
- java finalize方法总结、GC执行finalize的过程
- ubantu17.04 编译android 遇到的问题以及解决方法
- Linux下增加Sawp分区
- 服务器更新软件