libVLC 提取視頻每一幀

1
什么是幀
DVD 電影中的場景、從 YouTube 下載的剪輯、通過網(wǎng)絡(luò)攝像頭拍攝的內(nèi)容。。。無論是視頻還是動畫,都是由一系列靜止的圖像組成。然后,這些圖像會一個接一個的播放,讓你的眼睛誤以為物體在移動。圖像的播放速度越快,動作看起來越流暢,畫面也越逼真。
一般來說,想要達到自然平滑的動態(tài)效果,播放速率應(yīng)該在每秒 24-30 張圖像之間,每個圖像被稱為一幀。因此,我們通常會看到 FPS(每秒幀數(shù))這個詞,它突出顯示了移動速度的細節(jié),因此而得名。
舉一個栗子 - 走路,我們看到的是這樣的:

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

視頻文件也一樣,只不過它是將所有幀存儲在一起并按順序播放。對于一個典型的電影來說,存儲的總幀數(shù)甚至可以達到數(shù)十萬。如果要捕獲其中的某一幀圖像,則非常簡單,只需暫停視頻并按 Print Screen 鍵即可。
但倘若要從一個視頻剪輯中提取多個連續(xù)的幀,甚至是所有幀,那么一次捕捉一個圖像是非常低效和費時的。出于這個原因,可以用 libVLC 實現(xiàn)一個程序,用于提取想要的視頻幀,并自動保存到圖像文件(例如:jpg 或 png)中。
2
主要接口
要提取視頻中的每一幀,主要涉及以下核心 API。
先來看第一個 -?libvlc_video_set_callbacks(),用于設(shè)置回調(diào)和私有數(shù)據(jù),將解碼后的視頻渲染到內(nèi)存中的自定義區(qū)域:
/**
?*? mp:媒體播放器
?*? lock:回調(diào)以鎖定視頻內(nèi)存(不能為 NULL)
?*? unlock:回調(diào)以解鎖視頻內(nèi)存(如果不需要,則為 NULL)
?*? display:回調(diào)以顯示視頻(如果不需要,則為 NULL)
?*? opaque:這三個回調(diào)的私有指針(作為第一個參數(shù))
?*/
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?);
這個函數(shù)包含了五個參數(shù),其中有三個是函數(shù)指針:
//?當需要解碼新的視頻幀時,就會調(diào)用 lock 回調(diào)。
typedef?void?*(*libvlc_video_lock_cb)(void?*opaque,?void?**planes);
//?當視頻幀解碼完成后,將調(diào)用 unlock 回調(diào)。
typedef?void?(*libvlc_video_unlock_cb)(void?*opaque,?void?*picture,
???????????????????????????????????????void?*const?*planes);
//?當視頻幀需要顯示時,將調(diào)用 display 回調(diào)。
typedef?void?(*libvlc_video_display_cb)(void?*opaque,?void?*picture);
此外,還可使用?libvlc_video_set_format()?或者?libvlc_video_set_format_callbacks()?配置解碼的格式。例如,設(shè)置解碼后的視頻色度和尺寸:
/**
?*? mp:媒體播放器
?*? chroma:標識色度的四個字符的字符串(例如:"RV32"?或者?"YUYV")
?*? width:像素寬度
?*? height:像素高度
?*? pitch:線間距(以字節(jié)為單位)
?*/
void?libvlc_video_set_format(?libvlc_media_player_t?*mp,?const?char?*chroma,
??????????????????????????????unsigned?width,?unsigned?height,
??????????????????????????????unsigned?pitch?);
3
提取每一幀
現(xiàn)在,是時候一展身手了,來提取一個視頻中的連續(xù)幀:

視頻比較長,為了演示,只播放一段時間(這里是 10 秒),然后提取這段時間中的幀數(shù)據(jù):
#include?
#include?
#include?
#include?
#include?
//?定義輸出視頻的分辨率
#define?VIDEO_WIDTH???640
#define?VIDEO_HEIGHT??480
struct?Context?{
????QMutex?mutex;
????uchar?*pixels;
};
static?void?*lock(void?*opaque,?void?**planes)
{
????struct?Context?*ctx?=?static_cast(opaque);
????ctx->mutex.lock();
????//?告訴?VLC?將解碼的數(shù)據(jù)放到緩沖區(qū)中
????*planes?=?ctx->pixels;
????return?nullptr;
}
//?獲取?argb?圖片并保存到文件中
static?void?unlock(void?*opaque,?void?*picture,?void?*const?*planes)
{
????Q_UNUSED(picture);
????struct?Context?*ctx?=?static_cast(opaque);
????unsigned?char?*data?=?static_cast<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()
{
????const?char?*localMrl?=?"Sample.mkv";
????struct?Context?ctx;
????ctx.pixels?=?new?uchar[VIDEO_WIDTH?*?VIDEO_HEIGHT?*?4];
????memset(ctx.pixels,?0,?VIDEO_WIDTH?*?VIDEO_HEIGHT?*?4);
????libvlc_instance_t?*instance;
????libvlc_media_player_t?*player;
????libvlc_media_t?*media;
????instance?=?libvlc_new(0,?nullptr);
????media?=?libvlc_media_new_path(instance,?localMrl);
????player?=?libvlc_media_player_new_from_media(media);
????//?設(shè)置回調(diào),用于提取幀或者在界面上顯示。
????libvlc_video_set_callbacks(player,?lock,?unlock,?display,?&ctx);
????libvlc_video_set_format(player,?"RGBA",?VIDEO_WIDTH,?VIDEO_HEIGHT,?VIDEO_WIDTH?*?4);
????libvlc_media_player_play(player);
????QThread::sleep(10);
????libvlc_media_release(media);
????libvlc_media_player_release(player);
????libvlc_release(instance);
????return?0;
}
這里為了保存圖片,我們用到了 Qt 中的一個類 - QImage。該類提供了與硬件無關(guān)的圖像表示,允許直接訪問像素數(shù)據(jù),并可用作繪圖設(shè)備。
·END·
?
點個在看,么么噠!

