ffplay source code analysis---play control

ffplay is a simple player that comes with the FFmpeg project. It uses the decoder and SDL library provided by FFmpeg for video playback. This article is based on the analysis of the FFmpeg project version 4.1. The ffplay source code list is as follows:
https://github.com/FFmpeg/FFmpeg/blob/n4.1/fftools/ffplay.c

Play control

Pause/continue

The pause/resume state switch is realized by the user pressing the space bar. Each time the space bar is pressed, the pause/resume state is reversed once.

7.1.1 Pause/continue state switching The
function call relationship is as follows:

main() -->
event_loop() -->
toggle_pause() -->
stream_toggle_pause()

stream_toggle_pause() realizes state rollover:

/* 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;
}

Video playback in paused state

There is the following code in the video_refresh() function:

/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
    
    
    ......
    
    // 视频播放
    if (is->video_st) {
    
    
        ......
        // 暂停处理:不停播放上一帧图像
        if (is->paused)
            goto display;
        
        ......
    }
    
    ......
}

In the paused state, the last frame (the last frame) of the image is actually played continuously. The screen is not updated.

Play frame by frame

Frame-by-frame playback means that every time the user presses the s key, the player plays a frame of the picture.
The method of frame-by-frame playback is: each time you press the s key, the state is switched to play, and after one frame is played, the state is switched to pause.
The function call relationship is as follows:

main() -->
event_loop() -->
step_to_next_frame() -->
stream_toggle_pause()

The implementation code is relatively simple, as follows:

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);    // 逐帧播放模式下,播放一帧画面后暂停
        ......
    }
    
    ......
}

SEEK operation

SEEK operation is the way to change the playback progress by user intervention, such as dragging the playback progress bar with the mouse.

Data structure and SEEK logo

The relevant data variables are defined as follows:

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" represents the SEEK flag. The type of SEEK logo is defined as follows:

#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 number

The determination of the SEEK target playback point (hereinafter referred to as the SEEK point) is divided into the following situations according to the different SEEK marks:

  • AVSEEK_FLAG_BYTE: SEEK point corresponds to the position in the file (in bytes). Some demultiplexers may not support this situation.
  • AVSEEK_FLAG_FRAME: The SEEK point corresponds to the frame serial number in the stream (?frame serial number or frame
    PTS?), and the stream is specified by stream_index. Some demultiplexers may not support this situation.
  • If the above two flags are not included and the stream_index is valid: the SEEK point corresponds to the timestamp, the unit is the timebase in the stream, and the stream is specified by the stream_index. The value of the SEEK point is obtained by "pts (seconds) in the target frame x timebase in the stream".
  • If the above two flags are not included and the stream_index is -1: the SEEK point corresponds to the timestamp, and the unit is AV_TIME_BASE. The value of SEEK point is obtained by "pts (second) in the target frame × AV_TIME_BASE".
  • AVSEEK_FLAG_ANY: SEEK point corresponds to the frame number (to be determined), the playback point can stay in any frame (including non-key frames). Some demultiplexers may not support this situation.
  • AVSEEK_FLAG_BACKWARD:忽略。

Among them, AV_TIME_BASE is the time base used internally by FFmpeg, which is defined as follows:

/**
 * Internal time base represented as integer
 */

#define AV_TIME_BASE            1000000

AV_TIME_BASE means 1000000us.

SEEK trigger method

When the user presses the "PAGEUP", "PAGEDOWN", "UP", "DOWN", "LEFT", "RHIGHT" buttons and drags the progress bar with the mouse, the playback progress will change and the SEEK operation will be triggered.

There are the following code snippets in the SDL message processing performed by the event_loop() function:

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;

When seek_by_bytes is valid (corresponding to the AVSEEK_FLAG_BYTE flag), the SEEK point corresponds to the position in the file, and the above code sets a playback increment corresponding to 1 second of data; when it is not valid, the SEEK point corresponds to the playback time. We do not consider seek_by_bytes to take effect for the time being.

This function implements the following functions:

  • First determine the playback progress increment (SEEK increment) and target playback point (SEEK point) of the SEEK operation. When seek_by_bytes is not effective, set the increment to the selected value, such as 10.0 seconds (when the user presses the "RHIGHT" key) .
  • Add the progress increment to the synchronized master clock to get SEEK points. Record the relevant values ​​first for use in subsequent SEEK operations. stream_seek(cur_stream,(int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE),0); is to record two parameters of target playback point and playback progress increment, accurate to microseconds. The premise of calling this function is that we only consider the case [4] in Section 8.1.

Look at the implementation of the stream_seak() function, which is just variable assignment:

/* 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);
    }
}

Implementation of SEEK operation

The SEEK operation is processed in the main loop of the demultiplexing thread.

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);
        }
    }
    ......
}

The SEEK operation in the above code performs the following steps:

  • Call avformat_seek_file() to complete the SEEK point switching operation in the demultiplexer
// 函数原型
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);

This function will wait for the SEEK operation to complete before returning. The actual playback point strives to be the closest to the parameter ts, and ensures that within the interval of [min_ts, max_ts], the playback point is not necessarily at the ts position, because the ts position may not be able to play normally.
The three parameters (actual parameters "seek_min", "seek_target", "seek_max") related to the SEEK point of the function are related to the SEEK flag (actual parameter "is->seek_flags"), here "is->seek_flags" The value is 0, which corresponds to the situation in section [4] in Section 7.4.1.

  • Flush each decoder buffer frame to complete the frame playback in the current playback sequence, and then start a new playback sequence (the playback sequence is marked by the "serial" variable in each data structure, which is not expanded here). code show as below:
if (is->video_stream >= 0) {
    
    
    packet_queue_flush(&is->videoq);
    packet_queue_put(&is->videoq, &flush_pkt);
}
  • Clear this SEEK request flag is->seek_req = 0;

Article source: https://www.cnblogs.com/leisure_chn/p/10316225.html

C/C++ Technology Exchange Group: [960994558] I have compiled some good study books, interview questions from major companies, and popular technology teaching video materials that I think are better. You can add them if you need them! ~
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_52622200/article/details/114028436