处理快进快退命令ide
如今咱们来为咱们的播放器加入一些快进和快退的功能,由于若是你不能全局搜索一部电影是很让人讨厌的。同时,这将告诉你av_seek_frame函数是多么容易使用。函数
咱们将在电影播放中使用左方向键和右方向键来表示向后和向前一小段,使用向上和向下键来表示向前和向后一大段。这里一小段是10秒,一大段是60秒。因此咱们须要设置咱们的主循环来捕捉键盘事件。然而当咱们捕捉到键盘事件后咱们不能直接调用av_seek_frame函数。咱们要主要的解码线程decode_thread的循环中作这些。因此,咱们要添加一些变量到大结构体中,用来包含新的跳转位置和一些跳转标志:spa
int seek_req;线程 int seek_flags;code int64_t seek_pos;orm |
如今让咱们在主循环中捕捉按键:视频
for(;;) {对象 double incr, pos;队列
SDL_WaitEvent(&event);事件 switch(event.type) { case SDL_KEYDOWN: switch(event.key.keysym.sym) { case SDLK_LEFT: incr = -10.0; goto do_seek; case SDLK_RIGHT: incr = 10.0; goto do_seek; case SDLK_UP: incr = 60.0; goto do_seek; case SDLK_DOWN: incr = -60.0; goto do_seek; do_seek: if(global_video_state) { pos = get_master_clock(global_video_state); pos += incr; stream_seek(global_video_state, (int64_t)(pos * AV_TIME_BASE), incr); } break; default: break; } break; |
为了检测按键,咱们先查了一下是否有SDL_KEYDOWN事件。而后咱们使用event.key.keysym.sym来判断哪一个按键被按下。一旦咱们知道了如何来跳转,咱们就来计算新的时间,方法为把增长的时间值加到从函数get_master_clock中获得的时间值上。而后咱们调用stream_seek函数来设置seek_pos等变量。咱们把新的时间转换成为avcodec中的内部时间戳单位。在流中调用那个时间戳将使用帧而不是用秒来计算,公式为seconds = frames * time_base(fps)。默认的avcodec值为1,000,000fps(因此2秒的内部时间戳为2,000,000)。在后面咱们来看一下为何要把这个值进行一下转换。
这就是咱们的stream_seek函数。请注意咱们设置了一个标志为后退服务:
void stream_seek(VideoState *is, int64_t pos, int rel) {
if(!is->seek_req) { is->seek_pos = pos; is->seek_flags = rel < 0 ? AVSEEK_FLAG_BACKWARD : 0; is->seek_req = 1; } } |
如今让咱们看一下若是在decode_thread中实现跳转。你会注意到咱们已经在源文件中标记了一个叫作“seek stuff goes here”的部分。如今咱们将把代码写在这里。
跳转是围绕着av_seek_frame函数的。这个函数用到了一个格式上下文,一个流,一个时间戳和一组标记来做为它的参数。这个函数将会跳转到你所给的时间戳的位置。时间戳的单位是你传递给函数的流的时基time_base。然而,你并非必须要传给它一个流(流能够用-1来代替)。若是你这样作了,时基time_base将会是avcodec中的内部时间戳单位,或者是1000000fps。这就是为何咱们在设置seek_pos的时候会把位置乘以AV_TIME_BASER的缘由。
可是,若是给av_seek_frame函数的stream参数传递传-1,你有时会在播放某些文件的时候遇到问题(比较少见),因此咱们会取文件中的第一个流而且把它传递到av_seek_frame函数。不要忘记咱们也要把时间戳timestamp的单位进行转化。
if(is->seek_req) { int stream_index= -1; int64_t seek_target = is->seek_pos;
if (is->videoStream >= 0) stream_index = is->videoStream; else if(is->audioStream >= 0) stream_index = is->audioStream;
if(stream_index>=0){ seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q, pFormatCtx->streams[stream_index]->time_base); } if(av_seek_frame(is->pFormatCtx, stream_index, seek_target, is->seek_flags) < 0) { fprintf(stderr, "%s: error while seeking/n", is->pFormatCtx->filename); } else {
|
这里av_rescale_q(a,b,c)是用来把时间戳从一个时基调整到另一个时基时候用的函数。它基本的动做是计算a*b/c,可是这个函数仍是必需的,由于直接计算会有溢出的状况发生。AV_TIME_BASE_Q是AV_TIME_BASE做为分母后的版本。它们是很不相同的:AV_TIME_BASE * time_in_seconds = avcodec_timestamp而AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds(注意AV_TIME_BASE_Q其实是一个AVRational对象,因此你必需使用avcodec中特定的q函数来处理它)。
清空咱们的缓冲
咱们已经正确设定了跳转位置,可是咱们尚未结束。记住咱们有一个堆放了不少包的队列。既然咱们跳到了不一样的位置,咱们必需把队列中的内容清空不然电影是不会跳转的。不只如此,avcodec也有它本身的内部缓冲,也须要每次被清空。
要实现这个,咱们须要首先写一个函数来清空咱们的包队列。而后咱们须要一种命令声音和视频线程来清空avcodec内部缓冲的办法。咱们能够在清空队列后把特定的包放入到队列中,而后当它们检测到特定的包的时候,它们就会把本身的内部缓冲清空。
让咱们开始写清空函数。其实很简单的,因此我直接把代码写在下面:
static void packet_queue_flush(PacketQueue *q) { AVPacketList *pkt, *pkt1;
SDL_LockMutex(q->mutex); for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) { pkt1 = pkt->next; av_free_packet(&pkt->pkt); av_freep(&pkt); } q->last_pkt = NULL; q->first_pkt = NULL; q->nb_packets = 0; q->size = 0; SDL_UnlockMutex(q->mutex); } |
既然队列已经清空了,咱们放入“清空包”。可是开始咱们要定义和建立这个包:
AVPacket flush_pkt;
main() { ... av_init_packet(&flush_pkt); flush_pkt.data = "FLUSH"; ... } |
如今咱们把这个包放到队列中:
} else { if(is->audioStream >= 0) { packet_queue_flush(&is->audioq); packet_queue_put(&is->audioq, &flush_pkt); } if(is->videoStream >= 0) { packet_queue_flush(&is->videoq); packet_queue_put(&is->videoq, &flush_pkt); } } is->seek_req = 0; } |
(这些代码片断是接着前面decode_thread中的代码片断的)咱们也须要修改packet_queue_put函数才不至于直接简单复制了这个包:
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
AVPacketList *pkt1; if(pkt != &flush_pkt && av_dup_packet(pkt) < 0) { return -1; } |
而后在声音线程和视频线程中,咱们在packet_queue_get后当即调用函数avcodec_flush_buffers:
if(packet_queue_get(&is->audioq, pkt, 1) < 0) { return -1; } if(packet->data == flush_pkt.data) { avcodec_flush_buffers(is->audio_st->codec); continue; } |
上面的代码片断与视频线程中的同样,只要把“audio”换成“video”。
就这样,让咱们编译咱们的播放器:
gcc -o tutorial07 tutorial07.c -lavutil -lavformat -lavcodec -lz -lm`sdl-config --cflags --libs` |
试一下!咱们几乎已经都作完了;下次咱们只要作一点小的改动就行了,那就是检测ffmpeg提供的小的软件缩放采样。