libVLC 提取視訊每一幀
什麼是幀
DVD 電影中的場景、從 YouTube 下載的剪輯、通過網路攝像頭拍攝的內容。。。無論是視訊還是動畫,都是由一系列靜止的影象組成。然後,這些影象會一個接一個的播放,讓你的眼睛誤以為物體在移動。影象的播放速度越快,動作看起來越流暢,畫面也越逼真。
一般來說,想要達到自然平滑的動態效果,播放速率應該在每秒 24-30 張影象之間,每個影象被稱為一幀。因此,我們通常會看到 FPS(每秒幀數)這個詞,它突出顯示了移動速度的細節,因此而得名。
舉一個例子 - 走路,我們看到的是這樣的:
其實,真實情況是這樣的:
視訊檔案也一樣,只不過它是將所有幀儲存在一起並按順序播放。對於一個典型的電影來說,儲存的總幀數甚至可以達到數十萬。如果要捕獲其中的某一幀影象,則非常簡單,只需暫停視訊並按 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();
}