当前位置:网站首页>ffplay源码分析:播放控制
ffplay源码分析:播放控制
2022-08-06 05:26:00 【音视频开发老舅】
1. 播放控制
1.1. 暂停/继续
暂停/继续状态的切换是由用户按空格键实现的,每按一次空格键,暂停/继续的状态翻转一次。
1.1.1 暂停/继续状态切换
函数调用关系如下:
main() -->
event_loop() -->
toggle_pause() -->
stream_toggle_pause()stream_toggle_pause()实现状态翻转:
/* pause or resume the video */
static void stream_toggle_pause(VideoState *is)
{
if (is->paused) {
// 这里表示当前是暂停状态,将切换到继续播放状态。在继续播放之前,先将暂停期间流逝的时间加到frame_timer中
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;
}1.1.2 暂停状态下的视频播放
在video_refresh()函数中有如下代码:
/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
......
// 视频播放
if (is->video_st) {
......
// 暂停处理:不停播放上一帧图像
if (is->paused)
goto display;
......
}
......
}在暂停状态下,实际就是不停播放上一帧(最后一帧)图像。画面不更新。
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓
1.2 逐帧播放
逐帧播放是用户每按一次s键,播放器播放一帧画现。 逐帧播放实现的方法是:每次按了s键,就将状态切换为播放,播放一帧画面后,将状态切换为暂停。 函数调用关系如下:
main() -->
event_loop() -->
step_to_next_frame() -->
stream_toggle_pause()实现代码比较简单,如下:
static void step_to_next_frame(VideoState *is)
{
/* if the stream is paused unpause it, then step */
if (is->paused)
stream_toggle_pause(is); // 确保切换到播放状态,播放一帧画面
is->step = 1;
}/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
......
// 视频播放
if (is->video_st) {
......
if (is->step && !is->paused)
stream_toggle_pause(is); // 逐帧播放模式下,播放一帧画面后暂停
......
}
......
}1.3 播放速度控制
待补充
1.4. SEEK操作
SEEK操作就是由用户干预而改变播放进度的实现方式,比如鼠标拖动播放进度条。
1.4.1 数据结构及SEEK标志
相关数据变量定义如下:
typedef struct VideoState {
......
int seek_req; // 标识一次SEEK请求
int seek_flags; // SEEK标志,诸如AVSEEK_FLAG_BYTE等
int64_t seek_pos; // SEEK的目标位置(当前位置+增量)
int64_t seek_rel; // 本次SEEK的位置增量
......
} VideoState;“VideoState.seek_flags”表示SEEK标志。SEEK标志的类型定义如下:
#define AVSEEK_FLAG_BACKWARD 1 ///< seek backward
#define AVSEEK_FLAG_BYTE 2 ///< seeking based on position in bytes
#define AVSEEK_FLAG_ANY 4 ///< seek to any frame, even non-keyframes
#define AVSEEK_FLAG_FRAME 8 ///< seeking based on frame numberSEEK目标播放点(后文简称SEEK点)的确定,根据SEEK标志的不同,分为如下几种情况: [1]. AVSEEK_FLAG_BYTE:SEEK点对应文件中的位置(字节表示)。有些解复用器可能不支持这种情况。 [2]. AVSEEK_FLAG_FRAME:SEEK点对应stream中frame序号(?frame序号还是frame 的PTS?),stream由stream_index指定。有些解复用器可能不支持这种情况。 [3]. 如果不含上述两种标志且stream_index有效:SEEK点对应时间戳,单位是stream中的timebase,stream由stream_index指定。SEEK点的值由“目标frame中的pts(秒) × stream中的timebase”得到。 [4]. 如果不含上述两种标志且stream_index是-1:SEEK点对应时间戳,单位是AV_TIME_BASE。SEEK点的值由“目标frame中的pts(秒) × AV_TIME_BASE”得到。 [5]. AVSEEK_FLAG_ANY:SEEK点对应帧序号(待确定),播放点可停留在任意帧(包括非关键帧)。有些解复用器可能不支持这种情况。 [6]. AVSEEK_FLAG_BACKWARD:忽略。
其中AV_TIME_BASE是FFmpeg内部使用的时间基,定义如下:
/**
* Internal time base represented as integer
*/
#define AV_TIME_BASE 1000000AV_TIME_BASE表示1000000us。
1.4.2 SEEK的触发方式
当用户按下“PAGEUP”,“PAGEDOWN”,“UP”,“DOWN”,“LEFT”,“RHIGHT”按键以及用鼠标拖动进度条时,引起播放进度变化,会触发SEEK操作。 在event_loop()函数进行的SDL消息处理中有如下代码片段:
case SDLK_LEFT:
incr = seek_interval ? -seek_interval : -10.0;
goto do_seek;
case SDLK_RIGHT:
incr = seek_interval ? seek_interval : 10.0;
goto do_seek;
case SDLK_UP:
incr = 60.0;
goto do_seek;
case SDLK_DOWN:
incr = -60.0;
do_seek:
if (seek_by_bytes) {
pos = -1;
if (pos < 0 && cur_stream->video_stream >= 0)
pos = frame_queue_last_pos(&cur_stream->pictq);
if (pos < 0 && cur_stream->audio_stream >= 0)
pos = frame_queue_last_pos(&cur_stream->sampq);
if (pos < 0)
pos = avio_tell(cur_stream->ic->pb);
if (cur_stream->ic->bit_rate)
incr *= cur_stream->ic->bit_rate / 8.0;
else
incr *= 180000.0;
pos += incr;
stream_seek(cur_stream, pos, incr, 1);
} else {
pos = get_master_clock(cur_stream);
if (isnan(pos))
pos = (double)cur_stream->seek_pos / AV_TIME_BASE;
pos += incr;
if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE)
pos = cur_stream->ic->start_time / (double)AV_TIME_BASE;
stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);
}
break;seek_by_bytes生效(对应AVSEEK_FLAG_BYTE标志)时,SEEK点对应文件中的位置,上述代码中设置了对应1秒数据量的播放增量;不生效时,SEEK点对应于播放时刻。我们暂不考虑seek_by_bytes生效这种情况。
此函数实现如下功能:
[1]. 首先确定SEEK操作的播放进度增量(SEEK增量)和目标播放点(SEEK点),seek_by_bytes不生效时,将增量设为选定值,如10.0秒(用户按“RHIGHT”键的情况)。
[2]. 将同步主时钟加上进度增量,即可得到SEEK点。先将相关数值记录下来,供后续SEEK操作时使用。stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);就是记录目标播放点和播放进度增量两个参数的,精确到微秒。调用这个函数的前提是,我们只考虑8.1节中的第[4]种情况。
再看一下stream_seak()函数的实现,仅仅是变量赋值:
/* 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;
SDL_CondSignal(is->continue_read_thread);
}
}1.4.3 SEEK操作的实现
在解复用线程主循环中处理了SEEK操作。
static int read_thread(void *arg)
{
......
for (;;) {
if (is->seek_req) {
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;
// FIXME the +-2 is due to rounding being not done in the correct direction in generation
// of the seek_pos/seek_rel variables
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->url);
} else {
if (is->audio_stream >= 0) {
packet_queue_flush(&is->audioq);
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);
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);
}
}
......
}上述代码中的SEEK操作执行如下步骤:
[1]. 调用avformat_seek_file()完成解复用器中的SEEK点切换操作
// 函数原型
int avformat_seek_file(AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags);
// 调用代码
ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);这个函数会等待SEEK操作完成才返回。实际的播放点力求最接近参数ts,并确保在[min_ts, max_ts]区间内,之所以播放点不一定在ts位置,是因为ts位置未必能正常播放。
函数与SEEK点相关的三个参数(实参“seek_min”,“seek_target”,“seek_max”)取值方式与SEEK标志有关(实参“is->seek_flags”),此处“is->seek_flags”值为0,对应7.4.1节中的第[4]中情况。
[2]. 冲洗各解码器缓存帧,使当前播放序列中的帧播放完成,然后再开始新的播放序列(播放序列由各数据结构中的“serial”变量标志,此处不展开)。代码如下:
if (is->video_stream >= 0) {
packet_queue_flush(&is->videoq);
packet_queue_put(&is->videoq, &flush_pkt);
}[3]. 清除本次SEEK请求标志is->seek_req = 0;
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓
边栏推荐
- Flink 任务到底需要多少个 Slot
- 关于Warning:Implicit declaration of function “xxx” is invalid in C99警告!
- 一起了解分层架构&SOA架构
- QT自定义安装包
- QT5.14 realizes simple user login system
- [R language] This article solves the problem that the R language package cannot be installed (especially devtools)
- 九、一起学习Lua 运算符
- Cross-compilation libcurl + the openssl library
- 【图像处理】from skimage.measure import compare_psnr, compare_ssim ImportError: DLL load failed:找不到指定的模块
- 关于printf函数Warning: format string is not a string literal(potentially insecure)!
猜你喜欢

十二、一起学习Lua table(表)

A Pseudo-relevance feedback framework combining relevance matching...泛读笔记

CMU联合DeepMind发布”高质量“通用多模态表征框架HighMMT

Redis数据库(一)——Redis简介、部署及常用命令

简易数据库管理系统(DBMS)设计与实现

PointNeXt: 通过改进的训练以及模型缩放策略重新探究PointNet++

geoserver 发布 矢量切片(pbf)并用openlayers 6.14 /leaflet 1.8 加载展示 (二)(小白必备:超详细教程)

ECCV 2022 Oral | 满分文章!视频实例分割新SOTA:SeqFormer & IDOL

libcurl+openssl库交叉编译

NAACL 2022 | 字节和加州大学提出ConST模型,探讨对比学习如何助力语音翻译?
随机推荐
PCL1.12+VTK9.1+QT6编译部署
ReadTimeoutError or THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE when the pip command installs the toolkit
shp 解析的数据添加至pg空间库中
【R语言】在Jupyter Notebook中使用conda管理的R语言
Files遍历子文件、遍历目录树方法
基于ArcGIS 二次开发 使用技巧总结
R语言模糊匹配
Talk预告 | 普渡大学王虓:如何利用合作对抗学习来提升自监督学习
线程同步方法
R语言基础(数据类型,运算符,数据整理,管道操作)
七夕来袭,是时候展现专属于程序员的浪漫了。
C64725铜合金带
【Pytorch】torch.manual_seed()、torch.cuda.manual_seed() 解释
AI直接剪视频?哥本哈根大学提出基于CLIP的文本视频目标编辑模型
MacOS下 Qt6编译及链接MySQL
利用预训练语言模型ERNIE提供文本相似度(语义匹配)计算服务的简单实例
C语言和其他高级语言的最大的区别是什么?
ELK日志分析系统(一)之ELK原理
Redis高可用、持久化及性能管理
Qt教程(3) : 信号与槽