指導7:快進快退

處理快進快退命令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提供的小的軟件縮放採樣。

相關文章
相關標籤/搜索