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.mzph.cn/news/800731.shtml

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

相关文章

Golang 开发实战day09 - package Scope

&#x1f3c6;个人专栏 &#x1f93a; leetcode &#x1f9d7; Leetcode Prime &#x1f3c7; Golang20天教程 &#x1f6b4;‍♂️ Java问题收集园地 &#x1f334; 成长感悟 欢迎大家观看&#xff0c;不执着于追求顶峰&#xff0c;只享受探索过程 Golang 教程09 - package Sc…

算法练习第12天|● 239. 滑动窗口最大值● 347.前 K 个高频元素

239.滑动窗口的最大值 力扣原题 题目描述&#xff1a; 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff…

【Nginx 配置详解】:动态文件名设置与正则表达式

Nginx 配置详解&#xff1a;动态文件名设置与正则表达式 Nginx 是一款轻量级的 Web 服务器/反向代理服务器&#xff0c;它的高稳定性、丰富的功能集、简单的配置和低资源消耗使其成为当今最受欢迎的服务器之一。本文将深入探讨 Nginx 配置中的一些关键概念&#xff0c;特别是如…

影响力营销与AI的结合:Kompas.ai在搭桥角色中的独特价值

在数字化营销的新时代&#xff0c;影响力营销已经成为品牌建立信任和提升市场影响力的有效手段。通过与关键意见领袖&#xff08;KOL&#xff09;的合作&#xff0c;品牌能够利用KOL的信誉和影响力来扩大其市场覆盖范围和提升品牌认知度。然而&#xff0c;寻找与品牌价值观相契…

MyBatisPlus入门用法

MyBatisPlus&#xff08;简称MP&#xff09;是MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 一、安装 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-start…

python实现网络爬虫

网络爬虫是一个自动从互联网上抓取数据的程序。Python有很多库可以帮助我们实现网络爬虫&#xff0c;其中最常用的是requests&#xff08;用于发送HTTP请求&#xff09;和BeautifulSoup&#xff08;用于解析HTML或XML文档&#xff09;。 以下是一个简单的Python网络爬虫示例&a…

学习【Java IO】这一篇就够了

目录 1. 字节流1-1. InputStream1-2. outputStream 2. 字符流2-1. Reader2-2. Wirter 3. 字节缓冲流3-1. BufferedInputStream&#xff08;字节缓冲输入流&#xff09;3-2. BufferedOutputStream&#xff08;字节缓冲输出流&#xff09; 4. 字符缓冲流5. 打印流6. 随机访问流 1…

Linux 性能分析工具大全

vmstat--虚拟内存统计 vmstat&#xff08;VirtualMeomoryStatistics&#xff0c;虚拟内存统计&#xff09;是 Linux 中监控内存的常用工具,可对操作系统的虚拟内存、进程、CPU 等的整体情况进行监视。vmstat 的常规用法&#xff1a;vmstat interval times 即每隔 interval 秒采…

rsync+inotify实时同步 和 GFS分布式文件系统概述

目录 一、rsyncinotify实时同步 1.1.实时同步的优点 1.2.Linux内核的inotify机制 1.3.发起端配置rsyncInotify 1.4.配置远程登陆 1.4.1.修改rsync源服务器配置192.168.190.101 ​编辑 1.4.2.配置server 192.168.190.102 二、GFS 2.1.GlusterFS简介 2.2.GlusterFS特点…

Flutter学习11 - Future 与 FutureBuilder

1、Future 可以利用 Future 实现异步调用 1.1、Future 的两种形式 自定义一个结果类 class Response {String _data;Response(this._data); }自定义方法实现 Future Future<Response> testFuture() {var random Random();int randomNumber random.nextInt(10);if …

ADB 操作命令及其详细用法

adb devices 用途&#xff1a;列出连接到计算机的所有 Android 设备。详解&#xff1a;执行该命令后&#xff0c;ADB 将扫描连接到计算机的所有 Android 设备&#xff0c;并列出它们的序列号。 adb connect <device> 用途&#xff1a;连接到指定 IP 地址的 Android 设备。…

DIY自己的AI

一、开源AI大语言模型 目前开源的AI大语言模型(LLM)已经非常的多了&#xff0c;以下是收集的一些LLM&#xff1a; LLaMA LLaMA&#xff08;Large Language Model Meta AI&#xff09;&#xff1a;LLaMA是由MetaAI的Facebook人工智能实验室&#xff08;FAIR&#xff09;发布的…

大鼠尾静脉注射仪和小鼠尾静脉注射仪的区别

ZL-02B大鼠可视尾静脉注射仪是用于大鼠尾注射的一款仪器&#xff0c;以往给大鼠注射都是靠盲打&#xff0c;靠经验&#xff0c;对科研新手来说极其困难&#xff0c;有了大鼠尾静脉注射仪&#xff0c;可以大大提高注射效率&#xff0c;该仪器可以显示出尾部血管位置&#xff0c;…

Jenkins 持续集成 【CICD】

持续集成 &#xff08;Continuous integration&#xff0c;简称CI&#xff09; 持续集成是一种开发实践&#xff0c;它倡导团队成员频繁的集成他们的工作&#xff0c;每次集成都通过自动化构建&#xff08;包括编译、构建、打包、部署、自动化测试&#xff09;来验证&#xff…

python中的pass关键字、断言、解包、__name__ =__main__的使用

pass关键字 在Python中&#xff0c;pass是一个空语句&#xff0c;它不做任何操作&#xff0c;只是用来占位或作为占位符使用。在Python中&#xff0c;有时候需要保持语法完整性&#xff0c;但又不需要执行任何操作&#xff0c;这时可以使用pass语句。 以下是pass语句的一些常…

【云开发笔记No.30】弹性MapReduce

弹性MapReduce的定义 弹性MapReduce&#xff08;EMR&#xff09;是一种基于云原生技术和泛Hadoop生态开源技术的安全、低成本、高可靠的开源大数据平台。它结合了云计算的弹性和MapReduce的分布式计算能力&#xff0c;使得大数据处理变得更加高效和灵活。通过EMR&#xff0c;用…

python实现OCR:pytesseract和pyddleocr(附代码)

文章目录 背景pytesseractpaddleocr百度apipaddleocr 背景 OCR是光学字符识别&#xff08;Optical Character Recognition&#xff09;的缩写&#xff0c;通过扫描等光学输入方式和文字识别将图片中的文字提取出来&#xff0c;非常适用于提取网络截图或扫描pdf等文件里的文本。…

鼎盛合方案设计——汽车轮胎气压监测方案

一、介绍 随着汽车的普及和人们对行车安全的日益重视&#xff0c;胎压监测系统&#xff08;TPMS&#xff09;已经成为现代汽车的标准配置之一。传统的胎压监测系统通常采用有线方式&#xff0c;通过传感器和线缆将轮胎的压力信息传输到车辆的控制单元。然而&#xff0c;这种方…

成都欣丰洪泰文化传媒有限公司电商服务的新锐力量

在当今电商行业风起云涌的时代&#xff0c;成都欣丰洪泰文化传媒有限公司以其独特的视角和专业的服务&#xff0c;成为了业内的佼佼者。该公司专注于电商服务&#xff0c;致力于为广大商家提供全方位、多层次的解决方案&#xff0c;助力商家在激烈的市场竞争中脱颖而出。 一、…

使用 Python 批量提取 Excel 中的图片(提供工具下载链接)

本文收录于《Python入门核心技术》专栏&#xff0c;专栏总目录&#xff1a;点这里&#xff0c;订阅后可阅读专栏内所有文章。 大家好&#xff0c;我是水滴~~ 本文主要讲解如何利用 Python 来批量提取 Excel 中的图片&#xff0c;分别保存到目录中。并将程序打包成可执行文件&am…