libVLC 提取视频帧使用OpenGL渲染

news2024/11/28 9:26:45

在上一节中,我们讲解了如何使用QWidget渲染每一帧视频数据。

由于我们不停的生成的是QImage对象,因此对 CPU 负荷较高。其实在绘制这块我们可以使用 OpenGL去绘制,利用 GPU 减轻 CPU 计算负荷,本节讲解使用OpenGL来绘制每一帧视频数据。

libVLC 提取视频帧使用QWidget渲染-CSDN博客

以下是操作流程:

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.提取视频帧数据,回调给OpenGL显示。

static void unlock(void *opaque, void *picture, void *const *planes) {
	// 这里可以释放视频帧的锁

	uint8_t *buffer = (uint8_t *)*planes; //planes即为帧数据
	//QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);
	m_this->m_videoFunc(buffer);

	g_frame->mutex.unlock();
}

声明了两个OpenGL接口回调。

//视频每一帧数据回调
typedef std::function<void(uint8_t*)> VideoDataFunc;
//视频宽、高。回调
typedef std::function<void(int, int)> VideoInfoFunc;

设置回调。

	//视频数据
	m_videoFunc = std::bind(&WOpenGLWidget::slotReceiveVideoData, ui.openGLWidget,
		std::placeholders::_1);

	//视频信息
	m_videoInfoFunc = std::bind(&WOpenGLWidget::slotOpenVideo, ui.openGLWidget,
		std::placeholders::_1, std::placeholders::_2);

使用回调。

static void unlock(void *opaque, void *picture, void *const *planes) {
	// 这里可以释放视频帧的锁

	uint8_t *buffer = (uint8_t *)*planes; //planes即为帧数据
	//QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);
	m_this->m_videoFunc(buffer);

	g_frame->mutex.unlock();
}


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;
	m_this->m_videoInfoFunc(w, h);
	return 1;
}

 opengl完全没有基础的同学,请先学习以下的几篇文章,我们只需要了解2D图像如何渲染就行。

1.OpenGL简介

2.OpenGL实现第一个窗口-三角形

3.OpenGL 纹理

首先继承QOpenGLWidget类,重写paintGL()、resizeGL()、initializeGL()方法。

本示例演示使用opengles2来渲染,渲染rgba的数据。

以下是封装好的WOpenGLWidget类,使用提升的方式,提升为以下这个类就行了。

#ifndef WOPENGLWIDGET_H
#define WOPENGLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QImage>
#include <QOpenGLShaderProgram>
#include <QOpenGLShader>
#include <QDebug>
#include <QImage>
#include <QMouseEvent>
#include <QSet>
#include <QPainter>
#include <QMutex>
#include <QOpenGLBuffer>

class OpenGLDisplayImpl
{
public:
	OpenGLDisplayImpl()
	{
		texture = NULL;
		videoW = 0;
		videoH = 0;
	}

	unsigned char *buffer = {0};

	QOpenGLTexture*         texture;

	GLsizei                 videoW, videoH;
};

class WOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions
{
    Q_OBJECT
public:
    WOpenGLWidget(QWidget* parent = Q_NULLPTR);
	~WOpenGLWidget();

public:
	void slotOpenVideo(int width,int height);
	void slotReceiveVideoData(uint8_t* buffer);
	void clear();
	void deleteBuffer();

protected:
    virtual void initializeGL();
    virtual void paintGL();
    virtual void resizeGL(int w, int h);

private:
    QOpenGLShaderProgram *m_program = nullptr;          //着色器程序

	QOpenGLBuffer VBO, EBO;

	OpenGLDisplayImpl *m_impl = nullptr;
	bool m_isShowVideo = false;
	QMutex m_mux;
};

#endif // WOPENGLWIDGET_H


#include "WOpenGLWidget.h"
#include <QDebug>
#include <QTimer>
#include <QElapsedTimer>

static float vertices[] = {
//     ---- 位置 ----    - 纹理坐标 -
     1.0f,  1.0f, 0.0f, 1.0f, 1.0f,   // 右上
     1.0f, -1.0f, 0.0f, 1.0f, 0.0f,   // 右下
    -1.0f, -1.0f, 0.0f, 0.0f, 0.0f,   // 左下
    -1.0f,  1.0f, 0.0f, 0.0f, 1.0f    // 左上
};

static unsigned int indices[] = {
    0, 1, 3,
    1, 2, 3
};


WOpenGLWidget::WOpenGLWidget(QWidget* parent)
    : QOpenGLWidget(parent)
	, m_impl(new OpenGLDisplayImpl)
	, EBO(QOpenGLBuffer::IndexBuffer)
{
}

WOpenGLWidget::~WOpenGLWidget()
{
	clear();

	if (m_impl->texture)
	{
		m_impl->texture->destroy();
	}

	delete m_impl;
	m_impl = nullptr;
}

void WOpenGLWidget::slotOpenVideo(int width, int height)
{
	qDebug() << "slotOpenVideo";
	m_mux.lock();
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	glClear(GL_COLOR_BUFFER_BIT);
	m_isShowVideo = false;
	m_impl->videoW = width;
	m_impl->videoH = height;

	deleteBuffer();

	resize(this->width(), this->height());

	m_isShowVideo = true;
	m_mux.unlock();
}

void WOpenGLWidget::slotReceiveVideoData(uint8_t* yuvBuffer)
{
	if (!m_impl)
		return;

	m_mux.lock();
	if(!m_impl->buffer)
		m_impl->buffer = new unsigned char[m_impl->videoW * m_impl->videoH * 4];//y

	memcpy(m_impl->buffer, yuvBuffer, m_impl->videoW * m_impl->videoH * 4);

	update();
    m_mux.unlock();
}


void WOpenGLWidget::clear()
{
	m_mux.lock();

	deleteBuffer();
	m_isShowVideo = false;
	m_mux.unlock();
}

void WOpenGLWidget::deleteBuffer()
{
	if (m_impl)
	{
		if (m_impl->buffer) {
			delete m_impl->buffer;
			m_impl->buffer= nullptr;
		}
	}
}

void WOpenGLWidget::initializeGL()
{
	m_mux.lock();
    initializeOpenGLFunctions();

    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceFile(QOpenGLShader::Vertex,":/shaders/shaders/shapes.vert");
    m_program->addShaderFromSourceFile(QOpenGLShader::Fragment,":/shaders/shaders/shapes.frag");
	bool success = m_program->link();
    if (!success)
        qDebug() << "ERR:" << m_program->log();


	VBO.create();
    EBO.create();

	VBO.bind();
    EBO.bind();

    VBO.allocate(vertices, 20 * sizeof(float));
    EBO.allocate(indices, 6 * sizeof(unsigned int));

	m_impl->texture = new QOpenGLTexture(QOpenGLTexture::Target2D);
	m_impl->texture->create();
	m_impl->texture->setMinificationFilter(QOpenGLTexture::Nearest);
	m_impl->texture->setMinificationFilter(QOpenGLTexture::Linear);
	m_impl->texture->setWrapMode(QOpenGLTexture::ClampToEdge);

	m_mux.unlock();

	// 启动定时器
    QTimer *ti = new QTimer(this);
    connect(ti, &QTimer::timeout, this, [=] {
        update();
    });
    ti->start(100);
}

void WOpenGLWidget::paintGL()
{
	glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	m_mux.lock();
	if (m_isShowVideo)
	{		
        VBO.bind();
        EBO.bind();
		m_program->bind();

		m_impl->texture->bind(0);

		m_program->setUniformValue("texture", 0);

        int vertexLocation = m_program->attributeLocation("position");
        m_program->enableAttributeArray(vertexLocation);
        m_program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3, 5 * sizeof(float));

        int texcoordLocation = m_program->attributeLocation("texCoord");
        m_program->enableAttributeArray(texcoordLocation);
        m_program->setAttributeBuffer(texcoordLocation, GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));

        //激活纹理单元0
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_impl->texture->textureId());
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_impl->videoW,
            m_impl->videoH, 0, GL_BGRA, GL_UNSIGNED_BYTE, m_impl->buffer);
        //设置纹理环绕方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

        glDrawElements(GL_TRIANGLES,6, GL_UNSIGNED_INT,0);

        if(m_impl->texture)
            m_impl->texture->release();

        m_program->release();
	}
	m_mux.unlock();
}

void WOpenGLWidget::resizeGL(int w, int h)
{
	// 设置视口
	//glViewport(0, 0, w, h);
}

着色器如下所示。

//shapes.vert

#ifdef GL_ES
precision mediump int;
precision mediump float;
#endif

attribute vec3 position;
attribute vec2 texCoord;

varying vec2 outTexCoord;

void main()
{
    gl_Position = vec4(position,1.0);
    outTexCoord = vec2(texCoord.x, 1.0 - texCoord.y);
}

//shapes.frag

#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif

uniform sampler2D texture;

varying vec2 outTexCoord;

void main()
{
    vec3 rgb = texture2D(texture, outTexCoord);
    gl_FragColor = vec4(rgb, 1);
}

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>

//视频每一帧数据回调
typedef std::function<void(uint8_t*)> VideoDataFunc;
//视频宽、高。回调
typedef std::function<void(int, int)> VideoInfoFunc;

enum Rate
{
	Rate2X,
	Rate1_5X,
	Rate1_25X,
	Rate1_0X,
	Rate0_75X,
	Rate0_5X
};

class showWidget : public QWidget
{
    Q_OBJECT

public:
    showWidget(QWidget *parent = nullptr);
    ~showWidget();

public:
	VideoDataFunc m_videoFunc;
	VideoInfoFunc m_videoInfoFunc;

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;
};

//=====================================================

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

static void unlock(void *opaque, void *picture, void *const *planes) {
	// 这里可以释放视频帧的锁

	uint8_t *buffer = (uint8_t *)*planes; //planes即为帧数据
	//QImage image((unsigned char*)buffer, g_frame->width, g_frame->height, QImage::Format_ARGB32);
	m_this->m_videoFunc(buffer);

	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;
	m_this->m_videoInfoFunc(w, 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);

	//视频数据
	m_videoFunc = std::bind(&WOpenGLWidget::slotReceiveVideoData, ui.openGLWidget,
		std::placeholders::_1);

	//视频信息
	m_videoInfoFunc = std::bind(&WOpenGLWidget::slotOpenVideo, ui.openGLWidget,
		std::placeholders::_1, std::placeholders::_2);

	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;
	}
}

更多参考:

Qt+FFmpeg+opengl从零制作视频播放器-7.OpenGL播放视频_qt opengl视频播放器-CSDN博客

Qt+FFmpeg+opengl从零制作视频播放器-1.项目介绍_qt opengl视频播放器-CSDN博客 

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

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

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

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

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

相关文章

2024-简单点-python中的多重继承mro和super的联系

在Python的多重继承中&#xff0c;super()函数的作用主要是确保父类的方法被正确地调用&#xff0c;同时避免了直接调用父类可能带来的问题&#xff0c;如方法覆盖或名称冲突。super()的使用是Python实现合作式多重继承的关键。 具体来说&#xff0c;当一个类从多个父类继承时…

Java数据结构-队列

目录 1. 队列概念2. 模拟实现队列2.1 链式队列2.2 循环队列 3. 双端队列4. 队列的应用4.1 用队列实现栈4.2 用栈实现队列 1. 队列概念 队列是一种只能在一端进行插入数据操作&#xff0c;另一端进行删除数据操作的数据结构&#xff0c;插入数据的叫队尾&#xff0c;删除数据的…

基于巴法云物联网云平台构建可视化控制网页(以控制LED为例)

0 前言 如今大大小小的物联网云平台非常多&#xff0c;但大部分要收取费用&#xff0c;免费的物联网云平台功能则有很多限制使用起来非常不方便。以百度云物联网云平台为例&#xff0c;它的物可视不支持发布主题&#xff0c;等于可视化界面只能作为数据监控而不具备双向通信的…

小红书广告推广如何开户及费用攻略?

随着小红书平台影响力的日益增强&#xff0c;越来越多的品牌和商家选择在此进行广告推广&#xff0c;以触达其庞大的年轻且具有高度消费力的用户群体。面对复杂的开户流程、多样化的计费模式以及激烈的竞争环境&#xff0c;许多广告主难免感到困扰。云衔科技的专业服务应运而生…

RISC-V特权架构 - 模式切换与委托

RISC-V特权架构 - 模式切换与委托 1 导致模式切换的常见动作2 异常处理规则3 异常处理时模式切换3.1 在U模式下&#xff0c;发生异常3.2 在S模式下&#xff0c;发生异常3.3 在M模式下&#xff0c;发生异常 4 系统调用时模式切换5 中断处理时模式切换 本文属于《 RISC-V指令集基…

通过网络api获取日期对应的节假日信息

网络接口获取链接&#xff1a;免费节假日API_原百度节假日API HolidayJudge.h #pragma once#include <QtWidgets/QWidget> #include "ui_HolidayJudge.h"enum DATESTATE {WORK0,//工作日DAYOFF,//休息日HOLIDAY//节假日 };class HolidayJudge : public QWidg…

隐藏在计算过程中的数据超限

【题目描述】 输入两个正整数&#xff0c;输出&#xff0c;保留5位小数。输入包含多组数据&#xff0c;结束标记为n&#xff1d;m&#xff1d;0。提示&#xff1a;本题有陷阱。 【样例输入】 2 4 65536 655360 0 0 【样例输出】 Case 1: 0.42361 Case 2: 0.00001 【题…

AI日报:北大Open Sora视频生成更强了;文心一言可以定制你自己的声音;天工 SkyMusic即将免费开放;

&#x1f916;&#x1f4f1;&#x1f4bc;AI应用 北大Open Sora视频生成更强了!时长可达10秒&#xff0c;分辨率更高 【AiBase提要:】 ⭐️ Open-Sora-Plan v1.0.0模型发布 显著提升视频生成质量和文本控制能力 ⭐️ 支持华为昇腾910b芯片&#xff0c;提升运行效率和质量。 ⭐…

如何实现OpenHarmony的OTA升级?

OTA简介 随着设备系统日新月异&#xff0c;用户如何及时获取系统的更新&#xff0c;体验新版本带来的新的体验&#xff0c;以及提升系统的稳定性和安全性成为了每个厂商都面临的严峻问题。OTA&#xff08;Over the Air&#xff09;提供对设备远程升级的能力。升级子系统对用户…

麻了,一面就这么难

总体而言&#xff0c;整个过程更看你回答的条理与深度&#xff0c;不太需要面面俱到&#xff0c;有自己的理解和思考反而会加分&#xff0c;不需要每道题都回答得百分百&#xff0c;有些问题属于面试官顺带提一嘴&#xff0c;但重点问题要答好。 一面 介绍下项目&#xff0c;因…

《最佳实践之》GPS NMEA-0183 协议, 解析 $GPRMC 协议

一&#xff0c;了解 GPS NMEA-0183 协议 需要基础物联网对接知识&#xff0c;需要对解析协议有一定认识。 如果不知道怎么连接硬件&#xff0c;请看我的另一篇博客&#xff1a;https://blog.csdn.net/Crazy_Cw/article/details/126613967 这篇文章只说明&#xff0c;如何解析协…

AI编程案例002/ 根据草图设计小红书封面

之前看OpenAI发布会&#xff0c;给个草图能设计个网站。 今天试了一下&#xff0c;给ChatGpt一个封面的设计草图&#xff0c;让ChatGpt编程实现一个拼图封面。 需求如下&#xff1a; 上传四张图片图片按草图排列成不同的布局&#xff0c;每一种布局是一个封面。下面草图的布…

Godot 常用UI+布局容器简单介绍

文章目录 前言相关链接Canvasitem&#xff1a;画布Control&#xff1a;UI布局基类Container&#xff1a;布局容器基类AspectRatioContainer&#xff1a;伸缩居中布局BoxContainer&#xff1a;盒子布局ColorPicker:取色器 CenterContainer&#xff1a;不伸缩居中FlowContainer&a…

软件设计师:下午题(试题三)历年真题

2021年下半年 2021年上半年 2020年下半年 2019年下半年 2019年上半年 2018年下半年 2018年上半年 2017年下半年 后续知识引入 2017年上半年 2016年下半年 2016年上半年 2015年下半年 2015年上半年 2014年下半年 2014年上半年 2013年下半年 2013年上半年 2012年下半年 2012年…

Flink运行机制相关概念介绍

Flink运行机制相关概念介绍 1. 流式计算和批处理2. 流式计算的状态与容错3. Flink简介及其在业务系统中的位置4. Flink模型5. Flink的架构6. Flink的重要概念7. Flink的状态、状态分区、状态缩放&#xff08;rescale&#xff09;和Key Group8. Flink数据交换9. 时间语义10. 水位…

给他个卖票的机会,他能卖出负数票. 多线程安全问题演示

文章目录 1.1 线程安全产生的原因注意 : 以上代码是有问题 , 接下来继续改进通过上述代码的执行结果 , 发现了出现了负号票 , 和相同的票 . 说明数据出现安全问题 1.2 线程的同步1.3 同步代码块1.4 同步方法1.5 Lock锁 1.1 线程安全产生的原因 多个线程在对共享数据进行读改写…

关系(三)利用python绘制相关矩阵图

关系&#xff08;三&#xff09;利用python绘制相关矩阵图 相关矩阵图&#xff08;Correlogram&#xff09;简介 相关矩阵图既可以分析每对变量之间的相关性&#xff0c;也可以分析单变量的分布情况。相关性以散点图的形式可视化&#xff0c;对角线用直方图/密度图表示每个变量…

MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用

数据结构 我们知道MySQL的存储引擎Innodb默认底层是使用B树的变种来存储数据的 下面我们来复习一下B树存储 B树存储 哈希存储的区别 哈希存储,只能使用等值查询 B树与B树存储 我们知道B树实际上就是B树的变种 那么为啥使用B树而不是使用B树呢? 我们知道效率的高低主要取决于…

【JavaScript】作用域和闭包

作用域 作用域是程序源代码中定义的范围。JavaScript采用词法作用域&#xff0c;也就是静态作用域。所谓词法作用域就是在函数定义的时候就已经确定了。 let value 1 function foo(){console.log(value) } function bar(){let value 2foo() } bar() // 1变量对象是当前代码…

2.基础乐理-唱名的来历,简谱的构造

前置内容&#xff1a;1.唱名与记住唱名的方法 唱名的来历&#xff1a; 很久很久以前&#xff08;公元前&#xff09;各个文明开始诞生和慢慢发展&#xff0c;随着文明的发展&#xff0c;各个文明都开始出现自己的音乐&#xff0c;根据考古学家的发现在 公元前1800年&#xff…