libVLC 提取视频每一帧
1
什么是帧
DVD 电影中的场景、从 YouTube 下载的剪辑、通过网络摄像头拍摄的内容。。。无论是视频还是动画,都是由一系列静止的图像组成。然后,这些图像会一个接一个的播放,让你的眼睛误以为物体在移动。图像的播放速度越快,动作看起来越流畅,画面也越逼真。
一般来说,想要达到自然平滑的动态效果,播放速率应该在每秒 24-30 张图像之间,每个图像被称为一帧。因此,我们通常会看到 FPS(每秒帧数)这个词,它突出显示了移动速度的细节,因此而得名。
举一个栗子 - 走路,我们看到的是这样的:
其实,真实情况是这样的:
视频文件也一样,只不过它是将所有帧存储在一起并按顺序播放。对于一个典型的电影来说,存储的总帧数甚至可以达到数十万。如果要捕获其中的某一帧图像,则非常简单,只需暂停视频并按 Print Screen 键即可。
但倘若要从一个视频剪辑中提取多个连续的帧,甚至是所有帧,那么一次捕捉一个图像是非常低效和费时的。出于这个原因,可以用 libVLC 实现一个程序,用于提取想要的视频帧,并自动保存到图像文件(例如:jpg 或 png)中。
2
主要接口
要提取视频中的每一帧,主要涉及以下核心 API。
先来看第一个 - libvlc_video_set_callbacks(),用于设置回调和私有数据,将解码后的视频渲染到内存中的自定义区域:
/**
* mp:媒体播放器
* lock:回调以锁定视频内存(不能为 NULL)
* unlock:回调以解锁视频内存(如果不需要,则为 NULL)
* display:回调以显示视频(如果不需要,则为 NULL)
* opaque:这三个回调的私有指针(作为第一个参数)
*/
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" 或者 "YUYV")
* width:像素宽度
* height:像素高度
* pitch:线间距(以字节为单位)
*/
void libvlc_video_set_format( libvlc_media_player_t *mp, const char *chroma,
unsigned width, unsigned height,
unsigned pitch );
3
提取每一帧
现在,是时候一展身手了,来提取一个视频中的连续帧:
视频比较长,为了演示,只播放一段时间(这里是 10 秒),然后提取这段时间中的帧数据:
#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 将解码的数据放到缓冲区中
*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);
// 设置回调,用于提取帧或者在界面上显示。
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。该类提供了与硬件无关的图像表示,允许直接访问像素数据,并可用作绘图设备。
·END·