libVLC 提取视频帧

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

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

如果我们想要提取每一帧数据,将数据保存到本地,该如何操作呢?答案肯定是有的。

默认情况下,VLC 会使用自己的渲染机制来显示视频。但如果你想要在自己的应用程序中处理视频帧(例如进行视频编辑、分析或其他自定义渲染),可以使用 libvlc_video_set_callbacks 来指定自定义的回调函数。
以下是libvlc_video_set_callbacks函数声明。

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 );
  • mp是指向libvlc_media_player_t的指针,代表一个媒体播放器实例。
  • lock是一个回调函数,当VLC需要访问视频帧时会被调用。
  • unlock是一个回调函数,当VLC完成对视频帧的处理后会被调用。
  • display是一个回调函数,用于显示视频帧。
  • opaque是一个用户数据指针,会被传递给上述回调函数。

以下是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是平面像素数据。

在打开一个新的视频文件的时候,我们不知道视频的宽和高等数据。这时候需要使用libvlc_video_set_format_callbacks来获取视频格式,函数声明如下。

LIBVLC_API
void libvlc_video_set_format_callbacks( libvlc_media_player_t *mp,libvlc_video_format_cb setup,libvlc_video_cleanup_cb cleanup );
  • mp是指向libvlc_media_player_t 的指针,代表一个媒体播放器实例。
  • setup是一个回调函数,用于设置视频像素格式和缓冲区配置。
  • cleanup是一个回调函数,用于清理视频像素格式和缓冲区配置。

 以下是libvlc_video_format_cb声明,用于在视频解码开始之前设置视频像素格式和缓冲区配置。

typedef unsigned (*libvlc_video_format_cb)(void **opaque, char *chroma,unsigned *width, unsigned *height,unsigned *pitches,unsigned *lines);
  • opaque调用libvlc_video_set_callbacks时指定,可以被传递给回调函数,用于存储应用程序特定的数据。
  • chroma是一个指向字符数组的指针,用于返回像素格式(例如 “RGBA” 或 “YUV420P”)。
  • width和height返回视频帧宽度和高度。
  • pitches返回每个视频平面(对于 YUV 格式,通常有三个平面:Y、U 和 V)的行跨度(即一行像素所占的字节数)。
  • lines返回每个视频平面的行数。

代码示例:保存指定的数据帧。

头文件。

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

cpp文件。 

#include "showWidget.h"
#include <QTimer>
#include <QTime>
#include <QMutex>
#include <stdlib.h> #pragma execution_character_set("utf-8")struct Frame 
{int     width;int     height;uchar * pixels;QMutex mutex;
};int g_frameNum = 0;
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) {// 这里可以释放视频帧的锁if (g_frameNum > 100 && g_frameNum < 110){char *buffer = (char *)*planes; //planes即为帧数据QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);QString filePath;filePath.sprintf("./img%d.jpg", g_frameNum);image.save(filePath);}g_frameNum++;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);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::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;}
}

更多参考:

libVLC 事件机制-CSDN博客

libVLC windows开发环境搭建-CSDN博客

libVLC 元数据-CSDN博客

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

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

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

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

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

相关文章

OPC UA遇见chatGPT

最近opc 基金会将召开一个会议&#xff0c;主题是”OPC UA meets IT“。由此可见&#xff0c;工业自动化行业也开始研究和评估chatGPT带来的影响了。 本文谈谈本人对OPC UA 与chatGPT结合的初步实验和思考。 构建OPC UA 信息模型 chatGPT 的确非常强大了&#xff0c;使用自然…

用户登录时md5加密源码解析

首先&#xff0c;在登录的时候&#xff0c;将页面提交的密码password加密处理&#xff0c;即password DigestUtils.md5DigestAsHex(password.getBytes()); 接着按ctrl鼠标左键&#xff0c;进入md5DigestAsHex函数中进行查看&#xff1a; 可以发现&#xff0c;md5DigestAsHex函…

Mysql底层原理六:InnoDB 数据页结构

1.行格式 1.1 Compact行格式 1.1.1 示意图 1.1.2 准备一下 1&#xff09;建表 mysql> CREATE TABLE record_format_demo (-> c1 VARCHAR(10),-> c2 VARCHAR(10) NOT NULL,-> c3 CHAR(10),-> c4 VARCHAR(10)-> ) CHARSETascii ROW_FORMATCOM…

积木-蓝桥每日真题

0积木 - 蓝桥云课 (lanqiao.cn) 题目描述 小明用积木搭了一个城堡。 为了方便&#xff0c;小明在搭的时候用的是一样大小的正方体积木&#xff0c;搭在了一个n行m列的方格图上&#xff0c;每个积木正好占据方格图的一个小方格。 当然&#xff0c;小明的城堡并不是平面的&#x…

Discord注册教程:Discord刚注册就被封怎么办?附申诉教程!

Discord如今在海外社交媒体平台中迅速崛起&#xff0c;许多社交媒体营销人员也纷纷利用其社群特性进行推广&#xff0c;Discord注册也就成为社媒营销人员必经之路。然而&#xff0c;很多人注册Discord账号时常常会想&#xff1a;“在国内使用Discord会封号吗&#xff1f;”事实…

个推助力小米汽车APP实现智能用户触达,打造智能出行新体验

4月3日&#xff0c;小米SU7首批交付仪式在北京亦庄的小米汽车工厂总装车间举行&#xff0c;全国28城交付中心也同步开启首批交付。随着小米SU7系列汽车的正式发售和交付&#xff0c;小米汽车APP迎来了用户体量的爆发式增长。 小米汽车APP是小米汽车官方推出的手机应用&#xff…

数据库面试题

文章目录 事务未提交和提交事务的4大特征事务的隔离级别并发事务的问题MVVCundo log 和 redo log记录的隐藏字段readview&#xff08;读视图&#xff09; 事务未提交和提交 事务未提交时数据存在于数据库系统的缓存中&#xff0c;而在事务提交后&#xff0c;数据才会被写入到磁…

多叉树题目:子树中标签相同的结点数

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;子树中标签相同的结点数 出处&#xff1a;1519. 子树中标签相同的结点数 难度 5 级 题目描述 要求 给你一个树&#xff08;即一个连通的无向无环图…

【Canvas技法】图解绘制圆弧的重要函数 arc(x,y,r,startAngle,endAngle,clockWise)

【一图释疑】 【绘制上图用代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>Html5/Canvas中绘制圆弧的重要函数 arc(x,y,r,startA…

虹科Pico汽车示波器 | 免拆诊断案例 | 2019款别克GL8豪华商务车前照灯水平调节故障

一、故障现象 一辆2019款别克GL8豪华商务车&#xff0c;搭载LTG发动机&#xff0c;累计行驶里程约为10.7万km。车主反映&#xff0c;车辆行驶过程中组合仪表提示前照灯水平调节故障。 二、故障诊断 接车后试车&#xff0c;起动发动机&#xff0c;组合仪表上提示“前照灯水平…

CSS常见样式

字体相关的样式 <style>div{/* 斜体 */font-style: italic;/* 加粗 100-900*/font-weight: 900;/* 字体大小 */font-size: 20px;/* 声明字体格式 */font-family: "微软雅黑";}</style> div内部文字垂直居中 只需要将行高设为其height的大小即可。 div{…

git入门教程

Git 1. Git历史 同生活中的许多伟大事件一样&#xff0c;Git 诞生于一个极富纷争大举创新的年代。Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上&#xff08;1991&#xff0d;2002年间&#xff09;。到 2002…

推荐一款很强大的SCADA工业组态软件

可以广泛应用于化工、石化、制药、冶金、建材、市政、环保、电力等几十个行业。 I官网网站:www.hcy-soft.com |体验地址:http://www.byzt.net:60/sm/ 一、产品简介 BY组态是完全自主研发的集实时数据展示、动态交互等一体的全功能可视化平台。帮助物联网、工业互联网、电力能…

css 实现排行榜向上滚动

使用动画实现无线向上滚动 复制一层dom&#xff0c;使用动画向上滚动&#xff0c;鼠标hover的时候暂停动画 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthd…

wordpress外贸独立站模板

wordpress外贸独立站模板 WordPress Direct Trade 外贸网站模板&#xff0c;适合做跨境电商的外贸公司官方网站使用。 https://www.waimaoyes.com/wangzhan/22.html

分表?分库?分库分表?实践详谈 ShardingSphere-JDBC

如果有不是很了解ShardingSphere的可以先看一下这个文章&#xff1a; 《ShardingSphere JDBC?Sharding JDBC&#xff1f;》基本小白脱坑问题 阿丹&#xff1a; 在很多开发场景下面&#xff0c;很多的技术难题都是出自于&#xff0c;大数据量级或者并发的场景下面的。这里就出…

SSL证书的作用是什么?

SSL证书让网站和用户之间安全传输信息&#xff0c;就像给网络对话加了一把密码锁。它主要做四件事&#xff1a; 1. 证明身份&#xff1a; - 像警察局一样&#xff0c;有个叫“证书颁发机构”的家伙负责检查网站是不是真的。网站要向它证明自己是谁&#xff08;比如&#xff0c;…

基于机器学习的信用卡办卡意愿模型预测项目

基于机器学习的信用卡办卡意愿模型预测项目 在金融领域&#xff0c;了解客户的信用卡办卡意愿对于银行和金融机构至关重要。借助机器学习技术&#xff0c;我们可以根据客户的历史数据和行为模式预测其是否有办理信用卡的倾向。本项目通过Python中的机器学习库&#xff0c;构建…

使用nodejs搭建脚手架工具并发布到npm中

使用nodejs搭建脚手架工具并发布到npm中 一、安装环境依赖及脚手架搭建过程二、搭建Monorepo 风格的脚手架工程三、脚手架的必备模块命令参数模块获取命令参数设置子命令用户交互模块文件拷贝模块脚手架中的路径处理目录守卫文件拷贝模块动态文件生成模块mustache简介自动安装依…

一款轻量、干净的 Vue 后台管理框架

开始之前 在开始介绍之前我想谈谈为什么要自己做一个后台管理&#xff0c;我知道很多人都用一些开源的后台管理项目&#xff0c;这些老前辈有很多亮点值得学习&#xff0c;但是存在的一些问题同样不可忽视&#xff0c;我认为很多开发者会被困扰(仅代表个人观点) 技术栈老旧不升…