1. ffplay源码-event_loop函数、refresh_loop_wait_event函数
1.1. 源代码一览
//
// main.m
// iOSFFmpegSDLFastForwardAndBackward
//
// Created by 陈长青 on 2022/5/8.
//
#import <UIKit/UIKit.h>
#include "libavutil/avstring.h"
#include "libavutil/eval.h"
#include "libavutil/mathematics.h"
#include "libavutil/pixdesc.h"
#include "libavutil/imgutils.h"
#include "libavutil/dict.h"
#include "libavutil/parseutils.h"
#include "libavutil/samplefmt.h"
#include "libavutil/avassert.h"
#include "libavutil/time.h"
#include "libavformat/avformat.h"
#include "libavdevice/avdevice.h"
#include "libswscale/swscale.h"
#include "libavutil/opt.h"
#include "libavcodec/avfft.h"
#include "libswresample/swresample.h"
#include <SDL.h>
#include <SDL_thread.h>
const char program_name[] = "ffplay";
/* Step size for volume control in dB */
#define SDL_VOLUME_STEP (0.75)
/* polls for possible required screen refresh at least this often, should be less than 1/fps */
#define REFRESH_RATE 0.01
#define CURSOR_HIDE_DELAY 1000000
/* options specified by the user */
static AVInputFormat *file_iformat;
static const char *input_filename;
static int default_width = 640;
static int default_height = 480;
static int screen_width = 0;
static int screen_height = 0;
static int cursor_hidden = 0;
static int64_t cursor_last_shown;
/* current context */
// 命令行 -fs 指定,控制是否全屏显示
static int is_full_screen;
static AVPacket flush_pkt;
#define FF_QUIT_EVENT (SDL_USEREVENT + 2)
static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_RendererInfo renderer_info = {0};
// MARK: 视频状态
typedef struct VideoState {
int force_refresh;
int paused;
int seek_req;
int seek_flags;
int64_t seek_pos;
int64_t seek_rel;
AVFormatContext *ic;
int muted;
int width, height, xleft, ytop;
int step;
// 命令行 -showmode 指定
enum ShowMode {
SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB
} show_mode;
SDL_Texture *vis_texture;
SDL_cond *continue_read_thread;
} VideoState;
// MARK: 关闭码流
static void stream_close(VideoState *is)
{
// TODO: stream_close
}
// MARK: 打开码流
static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
{
// TODO: stream_open
VideoState *is;
is = av_mallocz(sizeof(VideoState));
if (!is)
return NULL;
return is;
}
// MARK: 切换码流
static void stream_cycle_channel(VideoState *is, int codec_type)
{
// TODO: stream_cycle_channel
}
// MARK: 退出
static void do_exit(VideoState *is)
{
if (is) {
stream_close(is);
}
if (renderer)
SDL_DestroyRenderer(renderer);
if (window)
SDL_DestroyWindow(window);
// uninit_opts();
//#if CONFIG_AVFILTER
// av_freep(&vfilters_list);
//#endif
// avformat_network_deinit();
// if (show_status)
// printf("\n");
SDL_Quit();
av_log(NULL, AV_LOG_QUIET, "%s", "");
exit(0);
}
// MARK: 刷新视频
/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
// TODO: video_refresh
// av_log(NULL, AV_LOG_INFO, "player,刷新视频\n");
}
static void toggle_full_screen(VideoState *is)
{
is_full_screen = !is_full_screen;
SDL_SetWindowFullscreen(window, is_full_screen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}
// MARK: 主时钟
/* get the current master clock value */
static double get_master_clock(VideoState *is)
{
// TODO: get_master_clock
return 0;
}
// MARK: Seek
/* 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);
}
}
// MARK: 暂停2
/* pause or resume the video */
static void stream_toggle_pause(VideoState *is)
{
// TODO: stream_toggle_pause
}
// MARK: 暂停
static void toggle_pause(VideoState *is)
{
stream_toggle_pause(is);
is->step = 0;
}
// MARK: 禁音
static void toggle_mute(VideoState *is)
{
is->muted = !is->muted;
}
// MARK: 调声音
static void update_volume(VideoState *is, int sign, double step)
{
// TODO: update_volume
}
// MARK: 进入下一帧
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;
}
// MARK: SDL事件
/**
* SDL 事件
*
* 循环检测并优先处理用户输入事件
* 内置刷新率控制,约10ms刷新一次
* https://blog.csdn.net/qq_36783046/article/details/88706162
*/
static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
double remaining_time = 0.0;
/* 从输入设备收集事件并放到事件队列中 */
SDL_PumpEvents();
/**
* SDL_PeepEvents
* 从事件队列中提取事件,由于这里使用的是SDL_GETEVENT, 所以获取事件时会从队列中移除
* 如果有事件发生,返回事件数量,则while循环不执行。
* 如果出错,返回负数的错误码,则while循环不执行。
* 如果当前没有事件发生,且没有出错,返回0,进入while循环。
*/
while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
/* 隐藏鼠标指针, CURSOR_HIDE_DELAY = 1s */
if (!cursor_hidden && av_gettime_relative() - cursor_last_shown > CURSOR_HIDE_DELAY) {
SDL_ShowCursor(0);
cursor_hidden = 1;
}
/* 默认屏幕刷新率控制,REFRESH_RATE = 10ms */
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))
video_refresh(is, &remaining_time);
/* 再次检测输入事件 */
SDL_PumpEvents();
}
}
// MARK: 事件循环
/* handle an event sent by the GUI */
static void event_loop(VideoState *cur_stream)
{
SDL_Event event;
double incr, pos, frac;
for (;;) {
double x;
refresh_loop_wait_event(cur_stream, &event);
switch (event.type) {
// 按键按下事件
case SDL_KEYDOWN:
// 按esc,q退出
if (1/**exit_on_keydown*/ || event.key.keysym.sym == SDLK_ESCAPE || event.key.keysym.sym == SDLK_q) {
do_exit(cur_stream);
break;
}
// If we don't yet have a window, skip all key events, because read_thread might still be initializing...
if (!cur_stream->width)
continue;
switch (event.key.keysym.sym) {
// 按F键,全屏
case SDLK_f:
toggle_full_screen(cur_stream);
// 调用video_refresh()刷新视频
cur_stream->force_refresh = 1;
break;
// 按P、SPACE键,暂停
case SDLK_p:
case SDLK_SPACE:
toggle_pause(cur_stream);
break;
// 按M键,静音
case SDLK_m:
toggle_mute(cur_stream);
break;
// 按+、0键,增加音量
// https://blog.csdn.net/huzhifei/article/details/112682390
case SDLK_KP_MULTIPLY:
case SDLK_0:
update_volume(cur_stream, 1, SDL_VOLUME_STEP);
break;
// 按-、9键,减小音量
case SDLK_KP_DIVIDE:
case SDLK_9:
update_volume(cur_stream, -1, SDL_VOLUME_STEP);
break;
// 按S键,下一帧
case SDLK_s: // S: Step to next frame
step_to_next_frame(cur_stream);
break;
// 按A键,切换音频流
case SDLK_a:
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);
break;
// 按V键,切换视频流
case SDLK_v:
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);
break;
// 按C键,循环切换节目(切换音频、视频、字幕流)
case SDLK_c:
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_VIDEO);
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_AUDIO);
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);
break;
// 按T键,切换字幕流
case SDLK_t:
stream_cycle_channel(cur_stream, AVMEDIA_TYPE_SUBTITLE);
break;
// 按W键,循环切换过滤器或显示模式
case SDLK_w:
//#if CONFIG_AVFILTER
// if (cur_stream->show_mode == SHOW_MODE_VIDEO && cur_stream->vfilter_idx < nb_vfilters - 1) {
// if (++cur_stream->vfilter_idx >= nb_vfilters)
// cur_stream->vfilter_idx = 0;
// } else {
// cur_stream->vfilter_idx = 0;
// toggle_audio_display(cur_stream);
// }
//#else
// toggle_audio_display(cur_stream);
//#endif
break;
// mac上好像没找到这个键
case SDLK_PAGEUP:
// 如果只有一个视频则向前10分钟
// if (cur_stream->ic->nb_chapters <= 1) {
incr = 600.0;
goto do_seek;
// }
// 有多个视频则寻找下一视频
// seek_chapter(cur_stream, 1);
break;
// mac上好像没找到这个键
case SDLK_PAGEDOWN:
// 如果只有一个视频则向后10分钟
// if (cur_stream->ic->nb_chapters <= 1) {
incr = -600.0;
goto do_seek;
// }
// 有多个视频则寻找上一视频
// seek_chapter(cur_stream, -1);
break;
// 按LEFT键,向后10秒
case SDLK_LEFT:
// seek_interval:命令行 -seek_interval 指定
incr = /**seek_interval ? -seek_interval : */-10.0;
goto do_seek;
// 按RIGHT键,向前10秒
case SDLK_RIGHT:
incr = /**seek_interval ? seek_interval :*/ 10.0;
goto do_seek;
// 按UP键,向前60秒
case SDLK_UP:
incr = 60.0;
goto do_seek;
// 按UP键,向后60秒
case SDLK_DOWN:
incr = -60.0;
do_seek:
// seek_by_bytes:命令行 -bytes 指定,默认-1
// 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;
default:
break;
}
break;
case SDL_MOUSEBUTTONDOWN:
// exit_on_mousedown:命令行 -exitonmousedown 指定,鼠标单击左键退出
// if (exit_on_mousedown) {
// do_exit(cur_stream);
// break;
// }
// 双击左键全屏
if (event.button.button == SDL_BUTTON_LEFT) {
static int64_t last_mouse_left_click = 0;
if (av_gettime_relative() - last_mouse_left_click <= 500000) {
toggle_full_screen(cur_stream);
cur_stream->force_refresh = 1;
last_mouse_left_click = 0;
} else {
last_mouse_left_click = av_gettime_relative();
}
}
// 鼠标移动事件,执行Seek(暂时不会用)
case SDL_MOUSEMOTION:
// if (cursor_hidden) {
// SDL_ShowCursor(1);
// cursor_hidden = 0;
// }
// cursor_last_shown = av_gettime_relative();
// if (event.type == SDL_MOUSEBUTTONDOWN) {
// if (event.button.button != SDL_BUTTON_RIGHT)
// break;
// x = event.button.x;
// } else {
// if (!(event.motion.state & SDL_BUTTON_RMASK))
// break;
// x = event.motion.x;
// }
// if (seek_by_bytes || cur_stream->ic->duration <= 0) {
// uint64_t size = avio_size(cur_stream->ic->pb);
// stream_seek(cur_stream, size*x/cur_stream->width, 0, 1);
// } else {
// int64_t ts;
// int ns, hh, mm, ss;
// int tns, thh, tmm, tss;
// tns = cur_stream->ic->duration / 1000000LL;
// thh = tns / 3600;
// tmm = (tns % 3600) / 60;
// tss = (tns % 60);
// frac = x / cur_stream->width;
// ns = frac * tns;
// hh = ns / 3600;
// mm = (ns % 3600) / 60;
// ss = (ns % 60);
// av_log(NULL, AV_LOG_INFO,
// "Seek to %2.0f%% (%2d:%02d:%02d) of total duration (%2d:%02d:%02d) \n", frac*100,
// hh, mm, ss, thh, tmm, tss);
// ts = frac * cur_stream->ic->duration;
// if (cur_stream->ic->start_time != AV_NOPTS_VALUE)
// ts += cur_stream->ic->start_time;
// stream_seek(cur_stream, ts, 0, 0);
// }
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_SIZE_CHANGED:
screen_width = cur_stream->width = event.window.data1;
screen_height = cur_stream->height = event.window.data2;
if (cur_stream->vis_texture) {
SDL_DestroyTexture(cur_stream->vis_texture);
cur_stream->vis_texture = NULL;
}
case SDL_WINDOWEVENT_EXPOSED:
cur_stream->force_refresh = 1;
}
break;
case SDL_QUIT:
case FF_QUIT_EVENT:
do_exit(cur_stream);
break;
default:
break;
}
}
}
// MARK: 入口函数
int main(int argc, char *argv[]) {
int flags;
VideoState *is;
// 动态加载的初始化,这是Windows平台的dll库相关处理;
// https://blog.csdn.net/ericbar/article/details/79541420
// init_dynload();
// 设置打印的标记,AV_LOG_SKIP_REPEATED表示对于重复打印的语句,不重复输出;
// https://blog.csdn.net/ericbar/article/details/79541420
av_log_set_flags(AV_LOG_SKIP_REPEATED);
// 使命令行'-loglevel'生效
// parse_loglevel(argc, argv, options);
/* register all codecs, demux and protocols */
//#if CONFIG_AVDEVICE
// 在使用libavdevice之前,必须先运行avdevice_register_all()对设备进行注册,否则就会出错
// https://blog.csdn.net/leixiaohua1020/article/details/41211121
avdevice_register_all();
//#endif
// 打开网络流的话,前面要加上函数
// avformat_network_init();
// Initialize the cmdutils option system, in particular allocate the *_opts contexts.
// 初始化 cmdutils 选项系统,特别是分配 *_opts 上下文。
// init_opts();
// signal(SIGINT , sigterm_handler); /* Interrupt (ANSI). */
// signal(SIGTERM, sigterm_handler); /* Termination (ANSI). */
// 将程序横幅打印到 stderr。 横幅内容取决于当前版本的存储库和程序使用的 libav* 库。
// Print the program banner to stderr. The banner contents depend
// on the current version of the repository and of the libav* libraries used by
// the program.
// show_banner(argc, argv, options);
// parse_options(NULL, argc, argv, options, opt_input_file);
// input_filename:命令行 -i 指定,视频路径
NSString *inPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"mov"];
input_filename = [inPath UTF8String];
if (!input_filename) {
// show_usage();
// av_log(NULL, AV_LOG_FATAL, "An input file must be specified\n");
// av_log(NULL, AV_LOG_FATAL,
// "Use -h to get full help or, even better, run 'man %s'\n", program_name);
exit(1);
}
// display_disable:命令行 -nodisp 指定,不渲染画面不播放声音
// if (display_disable) {
// video_disable = 1;
// }
flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
// audio_disable:命令行 -an 指定,渲染画面不播放声音
// if (audio_disable)
// flags &= ~SDL_INIT_AUDIO;
// else {
// /* Try to work around an occasional ALSA buffer underflow issue when the
// * period size is NPOT due to ALSA resampling by forcing the buffer size. */
// if (!SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"))
// SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE","1", 1);
// }
// if (display_disable)
// flags &= ~SDL_INIT_VIDEO;
// 指定flags,SDL初始化
if (SDL_Init (flags)) {
av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError());
av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n");
exit(1);
}
// 禁用一些事件
SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
SDL_EventState(SDL_USEREVENT, SDL_IGNORE);
av_init_packet(&flush_pkt);
flush_pkt.data = (uint8_t *)&flush_pkt;
if (1/**!display_disable*/) {
int flags = SDL_WINDOW_HIDDEN;
// if (alwaysontop)
#if SDL_VERSION_ATLEAST(2,0,5)
flags |= SDL_WINDOW_ALWAYS_ON_TOP;
#else
av_log(NULL, AV_LOG_WARNING, "Your SDL version doesn't support SDL_WINDOW_ALWAYS_ON_TOP. Feature will be inactive.\n");
#endif
// borderless:命令行 -noborder 指定,没有边框
// if (borderless)
// flags |= SDL_WINDOW_BORDERLESS;
// else
// 可以自由拉伸
flags |= SDL_WINDOW_RESIZABLE;
window = SDL_CreateWindow(program_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, default_width, default_height, flags);
// "0" or "nearest" - Nearest pixel sampling
// "1" or "linear" - Linear filtering (supported by OpenGL and Direct3D)
// "2" or "best" - Currently this is the same as "linear"
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
if (window) {
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (!renderer) {
av_log(NULL, AV_LOG_WARNING, "Failed to initialize a hardware accelerated renderer: %s\n", SDL_GetError());
renderer = SDL_CreateRenderer(window, -1, 0);
}
if (renderer) {
if (!SDL_GetRendererInfo(renderer, &renderer_info))
av_log(NULL, AV_LOG_VERBOSE, "Initialized %s renderer.\n", renderer_info.name);
}
}
if (!window || !renderer || !renderer_info.num_texture_formats) {
av_log(NULL, AV_LOG_FATAL, "Failed to create window or renderer: %s", SDL_GetError());
do_exit(NULL);
}
}
is = stream_open(input_filename, file_iformat);
if (!is) {
av_log(NULL, AV_LOG_FATAL, "Failed to initialize VideoState!\n");
do_exit(NULL);
}
event_loop(is);
/* never returns */
return 0;
}