前言:
实现暂停、播放的时候就在想,快进快退要如何实现呢?没想到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; ///不要忘记
}
}
主要部分分析:
- 设置跳转位置
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_Q是AV_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/