ffplay 的事件处理依赖于SDL。在 main 函数中:
...
flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER;
...
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的初始化工作,其中包含SDL_EVENT的初始化。
事件处理都是在 event_loop 中进行:
/* 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:
if (exit_on_keydown) {
do_exit(cur_stream);
break;
}
switch (event.key.keysym.sym) {
case SDLK_ESCAPE:
case SDLK_q:
do_exit(cur_stream);
break;
case SDLK_f:
toggle_full_screen(cur_stream);
cur_stream->force_refresh = 1;
break;
...
break;
case SDL_VIDEOEXPOSE:
cur_stream->force_refresh = 1;
break;
case SDL_MOUSEBUTTONDOWN:
...
case SDL_MOUSEMOTION:
...
break;
case SDL_VIDEORESIZE:
...
break;
case SDL_QUIT:
case FF_QUIT_EVENT:
do_exit(cur_stream);
break;
case FF_ALLOC_EVENT:
alloc_picture(event.user.data1);
break;
default:
break;
}
}
}
event_loop 中有循环不停的获取事件,具体的获取事件及刷新视频的操作在函数 refresh_loop_wait_event 中完成。
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))
video_refresh(is, &remaining_time);
SDL_PumpEvents();
}
}
其中,SDL_PumpEvents 函数主动收集来自输入设备的事件,填入事件循环中,从而更新事件队列。
SDL_PeepEvents 会检查事件队列,如果队列中有事件,回取出事件进行处理;如果没有则会按照REFRESH_RATE的延迟去刷新视频。
具体的SDL事件机制可以参考 https://my.oschina.net/u/735973/blog/832117 。