libVLC 提取视频每一帧

共 3703字,需浏览 8分钟

 ·

2020-08-23 14:03

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_callbackslibvlc_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_formatlibvlc_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(0nullptr);
    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·
 

作者:一去、二三里
爱学习,爱编程,爱生活。
欢迎来撩,一起畅谈程序人生!

点个在看,么么哒!

浏览 897
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报