libVLC 提取视频帧使用QWidget渲染

在前面的文章中,我们使用libvlc_media_player_set_hwnd设置了视频的显示的窗口。

libvlc_media_player_set_hwnd(vlc_mediaPlayer, (void *)ui.widgetShow->winId());

如果我们想要提取每一帧数据,将数据渲染到QWidget上,该如何操作呢?

以下是操作流程:

1.初始化 libVLC 实例。

vlc_base = libvlc_new(0, NULL);

2.创建一个媒体播放器。

	vlc_media = libvlc_media_new_path(vlc_base, filename.toUtf8().data());if (!vlc_media) {return;}// 创建libvlc实例和媒体播放器vlc_mediaPlayer = libvlc_media_player_new_from_media(vlc_media);if (!vlc_mediaPlayer) {return;}

3.设置视频回调。

	libvlc_video_set_format_callbacks(vlc_mediaPlayer, setup, NULL);// 设置自定义视频输出libvlc_video_set_callbacks(vlc_mediaPlayer, lock, unlock, display, NULL);

4.提取视频帧数据,创建QImage对象。

//保存100~110帧
static void unlock(void *opaque, void *picture, void *const *planes) {// 这里可以释放视频帧的锁char *buffer = (char *)*planes; //planes即为帧数据QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);m_this->updatePic(image);g_frame->mutex.unlock();
}

5.使用QLabel加载QImage对象,或者使用QWidget的绘图事件,绘制QImage在QWidget上。

这里使用QLabel加载。

void showWidget::slotUpdate(const QImage &image)
{ui.lbImage->setPixmap(QPixmap::fromImage(image));
}

这里介绍libvlc_video_set_callbacks的lock和unlock两个回调。更多的回调介绍请看

libVLC 提取视频帧-CSDN博客

以下是libvlc_video_lock_cb声明。

typedef void *(*libvlc_video_lock_cb)(void *opaque, void **planes);
  • opaque是一个用户数据指针,libvlc_video_set_callbacks最后一个参数会传递给lock。
  • planes是一个指向指针的指针,它指向一个指针数组,每个指针指向一个视频平面(对于 YUV 格式,通常有三个平面:Y、U 和 V)。对于 RGB 格式,通常只有一个平面。
  • 返回值传递给unlock的参数2。

如果应用程序需要在视频渲染前对视频帧进行一些处理,那么可以在libvlc_video_lock_cb中进行这些处理,并将处理后的帧数据地址赋值给 *planes。

以下是libvlc_video_unlock_cb声明。

typedef void (*libvlc_video_unlock_cb)(void *opaque, void *picture,void *const *planes);
  • opaque是一个用户数据指针,libvlc_video_set_callbacks最后一个参数会传递给lock。
  • picture是libvlc_video_lock_cb返回值。
  • planes是平面像素数据。

以下是ui界面设计。

运行结果:

代码示例:

#pragma once#include <QtWidgets/QWidget>
#include "ui_showWidget.h"
#include <QMenu>
#include <QActionGroup>
#include <vlc/vlc.h>
#include <QDebug>
#include <QFileDialog>
#include <QThread>
#include <QMouseEvent>
#include <QKeyEvent>enum Rate
{Rate2X,Rate1_5X,Rate1_25X,Rate1_0X,Rate0_75X,Rate0_5X
};class showWidget : public QWidget
{Q_OBJECTpublic:showWidget(QWidget *parent = nullptr);~showWidget();public:void updatePic(const QImage &image);private slots:void slotOpenFile();void slotPlay();void slotPause();void slotStop();void slotValueChanged(int value);void slotCurrentIndexChanged(int index);private://事件处理回调static void vlcEvents(const libvlc_event_t *ev, void *param);private:Ui::showWidgetClass ui;private:libvlc_instance_t *vlc_base = nullptr;libvlc_media_t *vlc_media = nullptr;libvlc_media_player_t *vlc_mediaPlayer = nullptr;QList<float> m_lstRate;QList<QString> m_lstAudioDevice;
};
  • 这里使用声明了static showWidget* m_this = nullptr。
  • 在构造函数中使用m_this = this赋值。
  • 在unlock回调中构造好QImage对象,使用m_this->updatePic(image)更新界面。

cpp源码。 

#include "showWidget.h"
#include <QTimer>
#include <QTime>
#include <QMutex>
#include <stdlib.h> #pragma execution_character_set("utf-8")static showWidget* m_this = nullptr;struct Frame 
{int     width;int     height;uchar * pixels;QMutex mutex;
};static Frame *g_frame = nullptr;// 自定义视频输出模块的回调函数
static void *lock(void *opaque, void **planes) {g_frame->mutex.lock();*planes = g_frame->pixels;return 0;
}//保存100~110帧
static void unlock(void *opaque, void *picture, void *const *planes) {// 这里可以释放视频帧的锁char *buffer = (char *)*planes; //planes即为帧数据QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);m_this->updatePic(image);g_frame->mutex.unlock();
}static void display(void *opaque, void *picture) {// 这里可以进行视频帧的显示或其他处理(void)opaque;
}static unsigned setup(void **opaque, char *chroma,unsigned *width, unsigned *height,unsigned *pitches,unsigned *lines)
{qDebug() << "chroma:" << QString(chroma) << "width:" << *width << ", height:" << *height;/* 开辟存放图像数据的内存块 */if (g_frame){if (g_frame->pixels){delete[] g_frame->pixels;g_frame->pixels = NULL;}delete g_frame;g_frame = NULL;}int w = *width;int h = *height;g_frame = new Frame;g_frame->pixels = new uchar[w * h * 4]; // 申请大小也为4通道的像素memset(g_frame->pixels, 0, w * h * 4);memcpy(chroma, "RV32", 4);g_frame->width = w;g_frame->height = h;*pitches = w * 4;*lines = h;return 1;
}showWidget::showWidget(QWidget *parent): QWidget(parent)
{ui.setupUi(this);m_this = this;this->setWindowTitle("视频播放器");vlc_base = libvlc_new(0, NULL);ui.cbxRate->setCurrentIndex(Rate1_0X);m_lstRate << 2.0 << 1.5 << 1.25 << 1.0 << 0.75 << 0.5;ui.btnOpen->setFocusPolicy(Qt::NoFocus);ui.btnPlay->setFocusPolicy(Qt::NoFocus);ui.btnPause->setFocusPolicy(Qt::NoFocus);ui.btnStop->setFocusPolicy(Qt::NoFocus);ui.hSliderVolumn->setFocusPolicy(Qt::NoFocus);ui.cbxRate->setFocusPolicy(Qt::NoFocus);connect(ui.btnOpen, &QPushButton::clicked, this, &showWidget::slotOpenFile);connect(ui.btnPlay, &QPushButton::clicked, this, &showWidget::slotPlay);connect(ui.btnPause, &QPushButton::clicked, this, &showWidget::slotPause);connect(ui.btnStop, &QPushButton::clicked, this, &showWidget::slotStop);connect(ui.hSliderVolumn, &QSlider::valueChanged, this, &showWidget::slotValueChanged);connect(ui.cbxRate,SIGNAL(currentIndexChanged(int)), this, SLOT(slotCurrentIndexChanged(int)));
}showWidget::~showWidget()
{libvlc_release(vlc_base); //减少libvlc实例的引用计数,并销毁
}void showWidget::updatePic(const QImage &image)
{ui.lbImage->setPixmap(QPixmap::fromImage(image));
}void showWidget::slotOpenFile()
{/*选择文件*/QString filename = QFileDialog::getOpenFileName(this, "选择打开的文件", "D:/", tr("*.*"));std::replace(filename.begin(), filename.end(), QChar('/'), QChar('\\'));vlc_media = libvlc_media_new_path(vlc_base, filename.toUtf8().data());if (!vlc_media) {return;}// 创建libvlc实例和媒体播放器vlc_mediaPlayer = libvlc_media_player_new_from_media(vlc_media);if (!vlc_mediaPlayer) {return;}libvlc_video_set_format_callbacks(vlc_mediaPlayer, setup, NULL);// 设置自定义视频输出libvlc_video_set_callbacks(vlc_mediaPlayer, lock, unlock, display, NULL);// 等待元数据加载完成libvlc_media_parse(vlc_media);// 获取各种元数据const char *title = libvlc_media_get_meta(vlc_media, libvlc_meta_Title);const char *artist = libvlc_media_get_meta(vlc_media, libvlc_meta_Artist);const char *album = libvlc_media_get_meta(vlc_media, libvlc_meta_Album);const char *url = libvlc_media_get_meta(vlc_media, libvlc_meta_URL);const char *date = libvlc_media_get_meta(vlc_media, libvlc_meta_Date);const char *lang = libvlc_media_get_meta(vlc_media, libvlc_meta_Language);int duration = libvlc_media_get_duration(vlc_media);  // 获取时长(单位:毫秒)qDebug("Title: %s", title ? title : "N/A");qDebug("Artist: %s", artist ? artist : "N/A");qDebug("Album: %s", album ? album : "N/A");qDebug("Duration: %d ms", duration);qDebug("url: %s", url ? url : "N/A");qDebug("date: %s", date ? date : "N/A");qDebug("lang: %s", lang ? lang : "N/A");libvlc_media_track_t **tracks;int track_count = libvlc_media_tracks_get(vlc_media,&tracks);for (unsigned i = 0; i < track_count; i++) {libvlc_media_track_t* track = tracks[i];// 显示轨道信息printf("Track #%u: %s\n", i, track->psz_description);// 这里可以获取到每一个轨道的信息,比如轨道类型 track->i_type// 可能是 libvlc_track_video, libvlc_track_audio 或者 libvlc_track_text (字幕)if (track->i_type == libvlc_track_video) {// 处理视频轨道信息qDebug("width = %d",track->video->i_width);qDebug("height = %d", track->video->i_height);qDebug("rate_num = %d", track->video->i_frame_rate_num);qDebug("rate_den = %d", track->video->i_frame_rate_den);}else if (track->i_type == libvlc_track_audio) {// 处理音频轨道信息qDebug("channels = %d", track->audio->i_channels);qDebug("rate = %d", track->audio->i_rate);}else if (track->i_type == libvlc_track_text) {// 处理字幕轨道信息}}//获取事件管理器libvlc_event_manager_t *em = libvlc_media_player_event_manager(vlc_mediaPlayer);// 注册事件监听器libvlc_event_attach(em, libvlc_MediaPlayerTimeChanged, vlcEvents, this);libvlc_event_attach(em, libvlc_MediaPlayerEndReached, vlcEvents, this);libvlc_event_attach(em, libvlc_MediaPlayerStopped, vlcEvents, this);libvlc_event_attach(em, libvlc_MediaPlayerPlaying, vlcEvents, this);libvlc_event_attach(em, libvlc_MediaPlayerPaused, vlcEvents, this);QTimer::singleShot(1000, this, &showWidget::slotPlay);libvlc_video_filter_list_get(vlc_base);
}void showWidget::slotPlay()
{if (vlc_mediaPlayer){libvlc_media_player_play(vlc_mediaPlayer);}
}void showWidget::slotPause()
{if (vlc_mediaPlayer)libvlc_media_player_pause(vlc_mediaPlayer);
}void showWidget::slotStop()
{if (vlc_mediaPlayer)libvlc_media_player_stop(vlc_mediaPlayer);
}void showWidget::slotValueChanged(int value)
{if (vlc_mediaPlayer)libvlc_audio_set_volume(vlc_mediaPlayer, value);
}void showWidget::slotCurrentIndexChanged(int index)
{if (vlc_mediaPlayer)libvlc_media_player_set_rate(vlc_mediaPlayer, m_lstRate[index]);
}//事件回调
void showWidget::vlcEvents(const libvlc_event_t *ev, void *param)
{showWidget *w = (showWidget*)param;//处理不同的事件switch (ev->type) {case libvlc_MediaPlayerTimeChanged:{//qDebug() << "VLC媒体播放器时间已更改";qint64 len = libvlc_media_player_get_time(w->vlc_mediaPlayer);libvlc_time_t lenSec = len / 1000;libvlc_time_t totalLen = libvlc_media_player_get_length(w->vlc_mediaPlayer);libvlc_time_t totalLenSec = totalLen / 1000;int thh, tmm, tss;thh = lenSec / 3600;tmm = (lenSec % 3600) / 60;tss = (lenSec % 60);QTime time(thh, tmm, tss);w->ui.lbCurTime->setText(time.toString("hh:mm:ss"));thh = totalLenSec / 3600;tmm = (totalLenSec % 3600) / 60;tss = (totalLenSec % 60);QTime TotalTime(thh, tmm, tss);w->ui.lbTotalTime->setText(TotalTime.toString("hh:mm:ss"));double pos = (double)lenSec / totalLenSec * 100;w->ui.horizontalSlider->setValue(pos);}break;case libvlc_MediaPlayerEndReached:qDebug() << "VLC播放完毕.";break;case libvlc_MediaPlayerStopped:qDebug() << "VLC停止播放";break;case libvlc_MediaPlayerPlaying:qDebug() << "VLC开始播放";break;case libvlc_MediaPlayerPaused:qDebug() << "VLC暂停播放";break;}
}

经过上面的步骤我们已经完成了整个视频的渲染,我们不停的生成的是 QImage 对象,因此对 CPU 负荷较高。其实在绘制这块我们可以使用 Opencv 或者 openGL 去绘制,利用 GPU 减轻 CPU 计算负荷。下一节使用openGL进行绘制,减轻CPU的压力。

更多参考:

libVLC 添加图片和文本水印-CSDN博客

libVLC 音频输出设备切换-CSDN博客

libVLC 音频立体声模式切换-CSDN博客

libVLC 提取视频帧-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/301335.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vulhub中Apache Solr Velocity 注入远程命令执行漏洞复现 (CVE-2019-17558)

Apache Solr 是一个开源的搜索服务器。 在其 5.0.0 到 8.3.1版本中&#xff0c;用户可以注入自定义模板&#xff0c;通过Velocity模板语言执行任意命令。 访问http://your-ip:8983即可查看到一个无需权限的Apache Solr服务。 1.默认情况下params.resource.loader.enabled配置…

springboot实现上传文件接口(简单版)

使用springboot实现一个最简单版本的上传文件接口 private String uploadPath "C:/imageFiles";RequestMapping(value "/upload", method RequestMethod.POST)private Result upload( RequestParam("modelName") String modelName,RequestPar…

精酿啤酒的未来:创新与传统的碰撞

随着精酿啤酒的兴起&#xff0c;越来越多的人开始关注这一领域的发展趋势。精酿啤酒作为啤酒中的一种新兴类别&#xff0c;其未来发展将受到创新与传统的碰撞和影响。在这其中&#xff0c;Fendi Club啤酒屋作为精酿啤酒的代表性场所&#xff0c;将继续发挥其重要的作用。 首先&…

Synchronized 同步锁

synchronized 它可以把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁&#xff0c;同时属于可重入锁。 Synchronized 作用范围 ● 作用于方法&#xff0c;锁住的是对象实例&#xff08;this&#xff09; ● 当作用于静态方法时&#xff0c;锁住的是 Class 实例&#xf…

鸡乐盒网页版

前端时间鸡乐盒比较火&#xff0c;当时跟着做了一款鸡乐盒&#xff0c;同时拥有聊天以及音乐播放器功能 链接&#xff1a; 鸡乐盒https://www.jaron.top/app/xiana/pages/musicBox/musicBox

每日OJ题_两个数组dp①_力扣1143. 最长公共子序列

目录 力扣1143. 最长公共子序列 解析代码 力扣1143. 最长公共子序列 1143. 最长公共子序列 难度 中等 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样…

SpringCloud Alibaba @SentinelResource 注解

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第十五篇&#xff0c;即介绍 SpringCloud Alibaba 的 SentinelResource 注解。 二、简介 这个注解用于标…

一切皆是为了交流-DDD通用语言

通用语言是什么&#xff1f; 通用语言是一种在特定领域内的沟通方式&#xff0c;可以由文字、语言、手势、图像等一切与达到沟通目的的元素组成。 比如&#xff0c;在中国内&#xff0c;方言是闽南的小王与方言是粤语的小张进行交流&#xff0c;那么&#xff0c;普通话是他们…

item_get_app在竞品分析中的应用与效果评估

item_get_app作为淘宝开放平台的重要API接口&#xff0c;为商家在竞品分析中提供了强大的数据支持。在竞争激烈的电商市场中&#xff0c;竞品分析是商家不可或缺的一环&#xff0c;而item_get_app的应用则使得这一分析过程更加高效、精准。通过调用item_get_app接口&#xff0c…

数据分析案例-牛油果价格和销量数据可视化分析与预测(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

ctfshow web入门 php特性 web108--web115

web108 ereg函数相当于而preg_match()函数 ereg函数的漏洞&#xff1a;00截断。%00截断及遇到%00则默认为字符串的结束 strrev函数就是把字符串倒过来 就是说intval处理倒过来的传参c0x36d&#xff08;877&#xff09;?ca%00778 web109 异常处理类 通过异常处理类Excepti…

心理健康教育宣传活动

为进一步加强未成年人心理健康教育&#xff0c;宣传心理健康知识&#xff0c;促进未成年人心理健康发展&#xff0c;在重庆儿童救助基金会的支持下&#xff0c;喜洋洋社工在桥头镇开展心理健康教育宣传活动。 活动中社工通过表格《我是一个怎样的人》引导青少年观察自己&#…

【LAMMPS学习】八、基本知识的讨论(1.3)从一个输入脚本运行多个模拟

8. 基本知识的讨论 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和…

SAP ERP 公有云有哪些模块?

随着全球化竞争的加剧和企业管理需求的日益复杂化&#xff0c;越来越多的企业开始采用云端企业资源计划&#xff08;ERP&#xff09;系统来优化业务流程。SAP ERP 公有云&#xff08;SAP S/4HANA Cloud, public edition&#xff09;作为一款领先的云端ERP解决方案&#xff0c;为…

jest单元测试——项目实战

jest单元测试——项目实战 一、纯函数测试二、组件测试三、接口测试四、React Hook测试&#x1f4a5; 其他的疑难杂症另&#xff1a;好用的方法 &#x1f31f; 温故而知新&#xff1a;单元测试工具——JEST 包括&#xff1a;什么是单元测试、jest的基本配置、快照测试、mock函数…

算法刷题Day24 | 回溯算法基础理论、 77. 组合

目录 0 引言1 回溯算法基础理论1.1 回溯算法模板1.2 2 组合2.1 我的解题2.2 剪枝操作 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;算法刷题Day23 | 回溯算法基础理论、 77. 组合❣️ 寄语&#xff1a;书…

2024年第七届信息管理与管理科学国际会议(IMMS 2024)即将召开!

2024年第七届信息管理与管理科学国际会议&#xff08;IMMS 2024&#xff09;将于2024年8月23-25日在中国北京举行。数字化时代&#xff0c;我们面临着诸多挑战&#xff0c;如信息安全问题、数据治理难题、管理创新需求等。IMMS 2024的召开&#xff0c;旨在让全球信息管理与管理…

Centos7 安装GitLab

安装环境: 虚拟机:Centos7 最小安装 4核8G 下载GitLab 本次实验下载的是 gitlab-ce-14.1.0-ce.0.el7.x86_64.rpm 官网截图 清华源截图 安装包下载地址(官网;下载CE版本,EE是收费版本):https://packages.gitlab.com/gitlab/gitlab-ce国内镜像源下载地址(清华源):htt…

(源码+部署+讲解)基于Spring Boot和Vue的大学志愿者服务平台的设计与实现

摘要&#xff1a; 随着互联网技术的快速发展&#xff0c;大学校园内的志愿者活动日益增多&#xff0c;传统的志愿者管理方式已难以满足现代化、信息化的需求。因此&#xff0c;设计并实现一个基于Spring Boot和Vue的大学志愿者服务平台显得尤为重要。本文详细阐述了该平台的设计…