libVLC 提取视频每一帧

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011012932/article/details/81119892

什么是帧

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
                                         )

提取每一帧

现在,是时候一展身手了,来提取一个 RTSP 流中的连续帧:

ExtractFrames

视频比较长,为了演示,只播放一段时间(这里是 20 秒),然后提取这段时间中的帧数据:

#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();
}

这里为了保存图片,我们用到了 Qt 中的一个类 - QImage。该类提供了与硬件无关的图像表示,允许直接访问像素数据,并可用作绘图设备。

注意: 后面会将 libVLC 和 Qt 相结合,分享一些 GUI 相关的小程序,如果对 Qt 不太了解,建议抽空学习一下。

Github 源码地址:ExtractFrames

猜你喜欢

转载自blog.csdn.net/u011012932/article/details/81119892
今日推荐