ffplay源码分析(七)播放控制

ffplay中的播放控制有哪些?

简单来说就是开始、结束、暂停、继续、跳转。

开始和结束前面都有说明,

所以主要分析暂停/恢复和跳转,

暂停 / 恢复

暂停恢复主要通过,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;
        //TODO
        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);
    //设置所有clock的状态
    is->paused = is->audclk.paused = is->vidclk.paused = is->extclk.paused = !is->paused;
}

使用到get_clock的地方,会发现时间仿佛静止了。。。。。

static double get_clock(Clock *c)
{
    if (*c->queue_serial != c->serial)
        return NAN;
    if (c->paused) {
        //当暂停时,返回当前pts,即时间不再流逝了
        return c->pts;
    } else {
        double time = av_gettime_relative() / 1000000.0;
        return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
    }
}

视频的暂停

static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
    ......
      //暂停状态下,不调用video_refresh
      if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
          video_refresh(is, &remaining_time);
      ......
    }
}
/* called to display each frame */
static void video_refresh(void *opaque, double *remaining_time)
{
    ......
    // 视频播放
    if (is->video_st) {
        ......
        // 暂停处理:播放上⼀帧图像
        if (is->paused)
            goto display;
        ......
    }
    ......
}

暂停时不再读取数据

 static int read_thread(void *arg) {
    ......
    //当暂停状态发生变化时
    if (is->paused != is->last_paused) {
        is->last_paused = is->paused;
        if (is->paused)
            //发送RTSP的暂停消息,让设备暂停发数据
            is->read_pause_return = av_read_pause(ic);
        else
            //发送RTSP的恢复消息,让设置继续发数据
            av_read_play(ic);
          ......
    }
       ......
 }

音频的暂停

static int audio_decode_frame(VideoState *is) {
    ......
    //直接返回-1
    if (is->paused)
    return -1;
  ......
}

暂停时,音频播放器还在取数据,但取到的都是静音数据

/* prepare a new audio buffer */
static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
{
       .......
           audio_size = audio_decode_frame(is);
           if (audio_size < 0) {
               //静音时,audio_size
               is->audio_buf = NULL;
               is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
           } 
       .......
            //判断audio_buf为空,塞入静音包
            memset(stream, 0, len1);
       .......   
}

SEEK

seek操作,就是从一个位置,跳到另外一个位置播放。

seek 的方式有两种,按字节跳转和按时间跳转,

按字节跳转一般用于特定的格式,比如ts流,每一个包都是188字节,解析器按这种方式处理可以很方便的跳到目标位置。

按时间跳转比较常见,比如在视频播放过程中快进30s,倒退30s,跳转到指定时间等。

VideoState中的seek相关标识位

typedef struct VideoState {
    ......
    int seek_req;                   // 标识一次SEEK请求
  
 /*
  #define AVSEEK_FLAG_BACKWARD 1 ///< 往指定时间戳后跳转(默认往前找)
  #define AVSEEK_FLAG_BYTE     2 ///< 按字节跳转
  #define AVSEEK_FLAG_ANY      4 ///< 可以跳转到非关键帧的读取位置
  #define AVSEEK_FLAG_FRAME    8 ///< 按帧序号跳转
*/
    int seek_flags;                 // SEEK标志,定义如上
    int64_t seek_pos;               // SEEK的目标位置(当前位置+增量)
    int64_t seek_rel;               // 本次SEEK的位置增量
    ......
} VideoState;

seek源码解析

static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes)
{
    //判断是否已有seek请求,seek过程中用户操作无效
    if (!is->seek_req) {
        //设置seek的目标位置
        is->seek_pos = pos;
        //设置seek的增量
        is->seek_rel = rel;
        //设置按字节跳转
        is->seek_flags &= ~AVSEEK_FLAG_BYTE;
        if (seek_by_bytes)
            is->seek_flags |= AVSEEK_FLAG_BYTE;
        is->seek_req = 1;
        //触发一下read线程运行
        SDL_CondSignal(is->continue_read_thread);
    }
}
static int read_thread(void *arg)
{
    ......
    for (;;) {
        //判断seek请求
        if (is->seek_req) {
            //seek的目标位置
            int64_t seek_target = is->seek_pos;
            //seek的最小位置,如果增量 > 0,那最小位置为原位,否则为INT64_MIN
            int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
            //seek的最大位置,如果增量 < 0,那最大位置为原位,否则为INT64_MAX
            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 {
                //每次seek都要把前面的包清空,这样能快速的跳转,否则用户点了seek,还要把缓存中的帧给播完,用户操作起来就不太丝滑
                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);
        }
    }
    ......
}

以上就是ffplay的播放控制的流程,用户操作主要是改变播放器的状态,然后收包线程和渲染线程根据状态做响应的处理即可,还是比较容易理解的。

ffplay源码分析(一)主函数
ffplay源码分析(二)stream_open
ffplay源码分析(六)音视频同步

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值