[8] ffmpeg + SDL2 实现的视频播放器「快进、快退」

前言:

实现暂停、播放的时候就在想,快进快退要如何实现呢?没想到ffmpeg提供了这个av_seek_frame()这么方便的函数。

相关知识:

  • ffmpeg的一些define:

    • AV_TIME_BASE : 1000000
    • AV_TIME_BASE_Q: (AVRational) {1, AV_TIME_BASE}
    • AVSEEK_FLAG_BACKWARD: 1 //这个是flags,表示向后seek
  • ffmpeg 的一些函数:

    • int64_t av_rescale_q(int64_t a, AVRational bq, AVRational cq)
      Returns a * bq / cq.
      作用:转换时基(time_base), 同时防止计算溢出。[ rescale a timestamp frome one base to another]

    • int av_seek_frame(AVFormatContext *s, int stream_index, int64_t timestamp, int flags)
      stream_index: 流下标,指定哪个流。如果为-1就使用默认,但是有可能出现错误。
      timestamp: 时间戳。计算:pos*timebase。「一般还需要经过 av_rescale_q调整转换」
      flags: 就是前面的AV_SEEK_FLAG_BACKWARD之类的,标识快进快退。
      快进快退就是依靠这个函数。

  • 如何检测按键(上一篇记录了两种方法):

switch(event.type)
{
    case SDL_KEYDOWN:
    {
        switch(event.key.keysym.sym)
        {
            case SDLK_RIGHT:
                increase = 10.0;            //快进10秒
                do_seek(ps);
                break;
            case SDLK_LEFt::
                increase = -10.0;           //快退10秒
                do_seek_(ps);
                break;
        }
        break;      ///不要忘记
    }
}
  • 按键代号:http://wiki.libsdl.org/SDL_ScancodeAndKeycode?highlight=%28SDLK_RIGHT%29

主要部分分析:

  • 设置跳转位置
void do_seek(PlayerState *ps, double increase)
{
    double  pos = 0.0;

    pos = get_audio_clock(ps);  //这里以什么为基准同步,就用哪个clock。
    pos += increase;

    if (ps->seek_req == 0)
    {
        ps->seek_req = 1;
        ps->seek_pos = (int64_t)(pos * AV_TIME_BASE);       
        //AVSEEK_FLAG_BACKWARD,ffmpeg定义为1
        ps->seek_flags = increase > 0 ? 0 : AVSEEK_FLAG_BACKWARD;
    }
}

get_audio_clock(): 获取时钟。这里tutorial中用的是自定义函数get_master_clock, 作用是判断用的是什么同步方式(以什么为基准),然后取不同的时钟。由于本代码用的是视频同步音频,所以用音频的时钟。

  • 进行跳转:在decode_thread()中实现。av_read_frame()之前,就判断下有没有跳转请求。有就进行跳转,并把原来的pakcet_queue清空。
void seeking(PlayerState *ps)
{
    int     stream_index = -1;
    int64_t seek_target = ps->seek_pos;

    if (ps->video_stream_index >= 0)
    {
        stream_index = ps->video_stream_index;
    }
    else if (ps->audio_stream_index >= 0)
    {
        stream_index = ps->audio_stream_index;
    }

    if (stream_index >= 0)
    {
        //AV_TIME_BASE_QAV_TIME_BASE的倒数,用AVRational结构存储
        seek_target = av_rescale_q(seek_target, AV_TIME_BASE_Q, 
                        ps->pformat_ctx->streams[stream_index]->time_base);
    }

    if (av_seek_frame(ps->pformat_ctx, stream_index,
             seek_target, ps->seek_flags) < 0)
    {
        fprintf(ERR_STREAM, "error while seeking\n");
    }
    else 
    {
        if (ps->video_stream_index >= 0)
        {
            packet_queue_flush(&ps->video_packet_queue);
            //tutorial中这里还会往队列压入一个flush_pkt
        }

        if (ps->audio_stream_index >= 0)
        {
            packet_queue_flush(&ps->audio_packet_queue);
            //tutorial中这里还会往队列压入一个flush_pkt
        }
    }

    ps->seek_req = 0;
}
  • 清空队列函数:
void packet_queue_flush(PacketQueue *queue)
{
    AVPacketList    *pkt = NULL;
    AVPacketList    *pkt1 = NULL;

    SDL_LockMutex(queue->mutex);

    //全部释放
    for(pkt = queue->first_pkt; pkt != NULL; pkt = pkt1)
    {
        pkt1 = pkt->next;
        av_free_packet(&pkt->pkt);
        av_freep(&pkt);
    }

    //packet_queue_init(queue);
    queue->first_pkt = NULL;
    queue->last_pkt = NULL;
    queue->nb_packets = 0;
    queue->size = 0;

    SDL_UnlockMutex(queue->mutex);
}

注意: tutorial中还有添加flush_packet和修改packet_queue_get/put()代码的部分。当前程序没有修改。

参考资料:

经典入门手册:http://dranger.com/ffmpeg/tutorial07.html
ffmpeg在线手册:http://ffmpeg.org/doxygen/trunk/

代码下载:

Linux版:http://download.csdn.net/detail/i_scream_/9650638

你可能感兴趣的:(「初探」ffmpeg)