1. 程式人生 > >libVLC 提取視訊每一幀

libVLC 提取視訊每一幀

什麼是幀

        DVD 電影中的場景、從 YouTube 下載的剪輯、通過網路攝像頭拍攝的內容。。。無論是視訊還是動畫,都是由一系列靜止的影象組成。然後,這些影象會一個接一個的播放,讓你的眼睛誤以為物體在移動。影象的播放速度越快,動作看起來越流暢,畫面也越逼真。

       一般來說,想要達到自然平滑的動態效果,播放速率應該在每秒 24-30 張影象之間,每個影象被稱為一幀。因此,我們通常會看到 FPS(每秒幀數)這個詞,它突出顯示了移動速度的細節,因此而得名。

       舉一個例子 - 走路,我們看到的是這樣的:  

Walk

      其實,真實情況是這樣的:

Walk

       視訊檔案也一樣,只不過它是將所有幀儲存在一起並按順序播放。對於一個典型的電影來說,儲存的總幀數甚至可以達到數十萬。如果要捕獲其中的某一幀影象,則非常簡單,只需暫停視訊並按 Print Screen 鍵即可。

       但倘若要從一個視訊剪輯中提取多個連續的幀,甚至是所有幀,那麼一次捕捉一個影象是非常低效和費時的。出於這個原因,可以用 libVLC 實現一個程式,用於提取想要的視訊幀,並自動儲存到影象檔案(例如:jpg 或 png)中。

核心API

      要提取視訊中的每一幀,主要涉及以下核心 API。

      先來看第一個 - libvlc_video_set_callbacks(),用於設定回撥和私有資料,將解碼後的視訊渲染到記憶體中的自定義區域:

/**
    mp:媒體播放器
    lock:回撥鎖定視訊記憶體(不能為 NULL)
    unlock:回撥解鎖視訊記憶體(如果不需要,則為 NULL)
    display:回撥以顯示視訊(如果不需要,則為 NULL)
    opaque:三個回撥的私有指標(作為第一個引數)
**/
LIBVLC_API void libvlc_video_set_callbacks(libvlc_media_player_t*   mp,
                                           libvlc_video_lock_cb     lock,
                                           libvlc_video_unlock_cb   unlock,
                                           libvlc_video_display_cb  display,
                                           void*                    opaque
                                           )    

      這個函式包含了五個引數,其中有三個是函式指標:

// 當需要解碼新的視訊幀時,就會呼叫 lock 回撥。
typedef void*(* libvlc_video_lock_cb) (void *opaque, void **planes)

// 當視訊幀解碼完成後,將呼叫 unlock 回撥。
typedef void(* libvlc_video_unlock_cb) (void *opaque, void *picture, void *const *planes)

// 當視訊幀需要顯示時,由媒體回放時鐘決定,將呼叫 display 回撥。
typedef void(* libvlc_video_display_cb) (void *opaque, void *picture)

      此外,還可使用 libvlc_video_set_format() 或者 libvlc_video_set_format_callbacks() 配置解碼的格式。例如,設定解碼後的視訊色度和尺寸:  

/**
    mp:媒體播放器
    chroma:標識色度的四個字元的字串(例如:RV32 或 YUV)
    width:畫素寬度
    height:畫素高度
    pitch:線間距(以位元組為單位)
**/
LIBVLC_API void libvlc_video_set_format (libvlc_media_player_t*     mp,
                                         const char*                chroma,
                                         unsigned                   width,
                                         unsigned                   height,
                                         unsigned                   pitch
                                         )

提取每一幀

#include <windows.h>
#include <vlc/vlc.h>
#include <QImage>
#include <QMutex>
#include <QCoreApplication>

// 定義輸出視訊的解析度
#define VIDEO_WIDTH   640
#define VIDEO_HEIGHT  480

struct context {
    QMutex mutex;
    uchar *pixels;
};

static void *lock(void *opaque, void **planes)
{
    struct context *ctx = (context *)opaque;
    ctx->mutex.lock();

    // 告訴 VLC 將解碼的資料放到緩衝區中
    *planes = ctx->pixels;

    return NULL;
}

// 獲取 argb 圖片並儲存到檔案中
static void unlock(void *opaque, void *picture, void *const *planes)
{
    Q_UNUSED(picture);

    struct context *ctx = (context *)opaque;
    unsigned char *data = (unsigned char *)*planes;
    static int frameCount = 1;

    QImage image(data, VIDEO_WIDTH, VIDEO_HEIGHT, QImage::Format_ARGB32);
    image.save(QString("frame_%1.png").arg(frameCount++));

    ctx->mutex.unlock();
}

static void display(void *opaque, void *picture)
{
    Q_UNUSED(picture);

    (void)opaque;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    libvlc_instance_t *vlcInstance;
    libvlc_media_player_t *mediaPlayer;
    libvlc_media_t *media;

    // 等待 20 秒
    int waitTime = 1000 * 20;

    struct context ctx;
    ctx.pixels = new uchar[VIDEO_WIDTH * VIDEO_HEIGHT * 4];
    memset(ctx.pixels, 0, VIDEO_WIDTH * VIDEO_HEIGHT * 4);

    // 建立並初始化 libvlc 例項
    vlcInstance = libvlc_new(0, NULL);

    // 建立一個 media,引數是一個媒體資源位置(例如:有效的 URL)。
    media = libvlc_media_new_location(vlcInstance, "rtsp://184.72.239.149/vod/mp4:BigBuckBunny_175k.mov");

    libvlc_media_add_option(media, ":avcodec-hw=none");

    // 建立一個 media player 播放環境
    mediaPlayer = libvlc_media_player_new_from_media(media);

    // 現在,不需要保留 media 了
    libvlc_media_release(media);

    // 設定回撥,用於提取幀或者在螢幕中顯示。
    libvlc_video_set_callbacks(mediaPlayer, lock, unlock, display, &ctx);
    libvlc_video_set_format(mediaPlayer, "RGBA", VIDEO_WIDTH, VIDEO_HEIGHT, VIDEO_WIDTH * 4);

    // 播放 media player
    libvlc_media_player_play(mediaPlayer);

    // 讓它播放一會
    Sleep(waitTime);

    // 停止播放
    libvlc_media_player_stop(mediaPlayer);

    // 釋放 media player
    libvlc_media_player_release(mediaPlayer);

    // 釋放 libvlc 例項
    libvlc_release(vlcInstance);

    return a.exec();
}