QT开发技术【ffmpeg + QAudioOutput】音乐播放器

一、 介绍 使用ffmpeg 4.2.2

在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音视频以其直观、生动的特性,成为信息传播与娱乐休闲的重要媒介。而在这繁华音视频世界的幕后,有一位低调而强大的“魔法工匠”——FFmpeg。

FFmpeg 是一款声名远扬的开源音视频处理工具集,凭借其跨平台、功能强大等显著优势,在音视频领域占据着不可撼动的地位。它就像一个拥有无数神奇工具的百宝箱,集音视频的录制、编解码、转换、流媒体传输等众多功能于一身,为音视频内容的创作、传播与消费提供了坚实的技术支撑。

对于开发者来说,FFmpeg 宛如一座取之不尽的宝藏。它提供了丰富且易用的 API 接口,能够无缝集成到各类应用程序中。无论是开发一个简洁实用的音视频播放器,还是打造一个功能全面的专业视频编辑软件,FFmpeg 都能凭借其强大的功能和高效的性能,助力开发者快速实现目标,大大降低开发的难度和成本。

在科研领域,FFmpeg 也发挥着重要作用。研究人员可以利用其先进的编解码算法,探索音视频质量优化的新途径;借助其流媒体传输功能,开展网络带宽利用、视频传输延迟等方面的研究,为提升音视频在不同网络环境下的播放体验提供理论依据和技术支持。

此外,FFmpeg 的开源属性更是为其发展注入了源源不断的活力。全球的开发者们汇聚于此,共同参与项目的开发与维护,不断为其增添新功能、修复漏洞,使得 FFmpeg 始终保持着与时俱进的姿态,适应不断变化的音视频市场需求。

下面为你详细介绍使用 FFmpeg 库在 C++ 里播放 MP3 文件的代码流程,整个流程主要包含初始化、打开文件、查找流信息、查找解码器、打开解码器、重采样、播放音频以及资源释放等步骤。

  1. 初始化 FFmpeg 库
    在使用 FFmpeg 库的功能之前,需要对其进行初始化,主要是注册所有可用的编解码器、格式和协议。
#include <iostream>
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}// 初始化 FFmpeg 库
avformat_network_init();
av_register_all();
  1. 打开输入文件
    使用 avformat_open_input 函数打开 MP3 文件,同时创建 AVFormatContext 结构体来保存文件的格式信息。
AVFormatContext* formatContext = nullptr;
const char* filePath = "example.mp3";
if (avformat_open_input(&formatContext, filePath, nullptr, nullptr) != 0) {std::cerr << "Could not open input file" << std::endl;return -1;
}
  1. 获取流信息
    调用 avformat_find_stream_info 函数获取文件的流信息,例如音频流、视频流等。
if (avformat_find_stream_info(formatContext, nullptr) < 0) {std::cerr << "Could not find stream information" << std::endl;avformat_close_input(&formatContext);return -1;
}
  1. 查找音频流
    通过 av_find_best_stream 函数在文件中查找音频流。
int audioStreamIndex = -1;
AVCodecParameters* codecParameters = nullptr;
for (unsigned int i = 0; i < formatContext->nb_streams; i++) {if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {audioStreamIndex = i;codecParameters = formatContext->streams[i]->codecpar;break;}
}
if (audioStreamIndex == -1) {std::cerr << "Could not find audio stream" << std::endl;avformat_close_input(&formatContext);return -1;
}
  1. 查找并打开解码器
    使用 avcodec_find_decoder 函数查找对应的音频解码器,再用 avcodec_open2 函数打开解码器。
AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id);
if (!codec) {std::cerr << "Could not find codec" << std::endl;avformat_close_input(&formatContext);return -1;
}AVCodecContext* codecContext = avcodec_alloc_context3(codec);
if (!codecContext) {std::cerr << "Could not allocate codec context" << std::endl;avformat_close_input(&formatContext);return -1;
}if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) {std::cerr << "Could not copy codec parameters to context" << std::endl;avcodec_free_context(&codecContext);avformat_close_input(&formatContext);return -1;
}if (avcodec_open2(codecContext, codec, nullptr) < 0) {std::cerr << "Could not open codec" << std::endl;avcodec_free_context(&codecContext);avformat_close_input(&formatContext);return -1;
}
  1. 初始化重采样上下文
    由于 MP3 文件的音频格式可能与系统播放设备支持的格式不一致,需要使用 swr_alloc_set_opts 函数进行重采样。
SwrContext* swrContext = swr_alloc_set_opts(nullptr,AV_CH_LAYOUT_STEREO, // 输出声道布局AV_SAMPLE_FMT_S16,   // 输出样本格式codecContext->sample_rate, // 输出采样率codecContext->channel_layout, // 输入声道布局codecContext->sample_fmt, // 输入样本格式codecContext->sample_rate, // 输入采样率0, nullptr);if (!swrContext || swr_init(swrContext) < 0) {std::cerr << "Could not initialize resampler" << std::endl;avcodec_free_context(&codecContext);avformat_close_input(&formatContext);return -1;
}
  1. 读取、解码和重采样音频数据
    使用 av_read_frame 函数读取音频数据包,再用 avcodec_send_packet 和 avcodec_receive_frame 函数进行解码,最后使用 swr_convert 函数进行重采样。
AVPacket* packet = av_packet_alloc();
AVFrame* frame = av_frame_alloc();while (av_read_frame(formatContext, packet) >= 0) {if (packet->stream_index == audioStreamIndex) {if (avcodec_send_packet(codecContext, packet) < 0) {std::cerr << "Error sending packet to decoder" << std::endl;continue;}while (avcodec_receive_frame(codecContext, frame) == 0) {// 重采样uint8_t* outputData[1];int outputSamples = swr_convert(swrContext, outputData, frame->nb_samples,(const uint8_t**)frame->data, frame->nb_samples);// 这里需要将重采样后的数据发送到音频设备进行播放// 不同系统的音频播放 API 不同,例如在 Windows 下可以使用 WaveOut 或 WASAPI}}av_packet_unref(packet);
}
  1. 释放资源
    在音频播放结束后,需要释放之前分配的所有资源。
av_frame_free(&frame);
av_packet_free(&packet);
swr_free(&swrContext);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);

整个播放 MP3 文件的流程可以概括为:初始化 FFmpeg 库 → 打开输入文件 → 获取流信息 → 查找音频流 → 查找并打开解码器 → 初始化重采样上下文 → 读取、解码和重采样音频数据 → 释放资源。需要注意的是,上述代码中重采样后的数据需要使用相应系统的音频播放 API 发送到音频设备进行播放,不同操作系统的音频播放 API 不同,例如 Windows 下的 WaveOut、WASAPI,Linux 下的 ALSA、PulseAudio 等。

二、效果

在这里插入图片描述

三、读取歌词类

#pragma once#include <QList>
#include <QString>// 歌词条目结构体
struct stLyricsEntry {int m_nTime; // 时间,单位为毫秒std::string m_strText; // 歌词文本
};class CLyricsReader {
public:CLyricsReader() = default;bool LoadLrcFile(const QString& strFilePath);std::string GetCurrentLyrics(int nCurrentTime);private:QList<stLyricsEntry> m_lstLyricsEntries; // 歌词条目列表
};#include "../Include/LyricsReader.h"
#include "QtGui/Include/Conversion.h"
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QTextCodec>
#include <QDebug>bool CLyricsReader::LoadLrcFile(const QString& strFilePath)
{QFile file(strFilePath);if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {qDebug() << "Failed to open LRC file:" << strFilePath;return false;}QByteArray data = file.readAll();QTextCodec* codec = nullptr;if (QTextCodec::codecForUtfText(data)) {codec = QTextCodec::codecForName("UTF-8");}else {// 尝试其他常见编码,如 GBKcodec = QTextCodec::codecForLocale();}QString text = codec->toUnicode(data);QTextStream in(&text);QRegularExpression timeRegex(R"(\[(\d+):(\d+\.\d+)\])");while (!in.atEnd()) {QString line = in.readLine();QRegularExpressionMatchIterator matchIterator = timeRegex.globalMatch(line);while (matchIterator.hasNext()){QRegularExpressionMatch match = matchIterator.next();int minutes = match.captured(1).toInt();double seconds = match.captured(2).toDouble();int time = static_cast<int>((minutes * 60 + seconds) * 1000);QString text = line.mid(match.capturedEnd());if (!text.isEmpty()) {stLyricsEntry entry;entry.m_nTime = time;entry.m_strText = TransUnicode2String(text);m_lstLyricsEntries.append(entry);}}}file.close();// 按时间排序std::sort(m_lstLyricsEntries.begin(), m_lstLyricsEntries.end(), [](const  stLyricsEntry& a, const stLyricsEntry& b) {return a.m_nTime < b.m_nTime;});return true;
}std::string CLyricsReader::GetCurrentLyrics(int nCurrentTime)
{for (int i = m_lstLyricsEntries.size() - 1; i >= 0; --i) {if (m_lstLyricsEntries[i].m_nTime<= nCurrentTime){return m_lstLyricsEntries[i].m_strText;}}return "";
}

三、播放类

#include "../Include/AudioPlayer.h"
#include "QtGui/Include/Conversion.h"#include <QIODevice>
#include <QBuffer>
#include <QDebug>extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
}CAudioPlayer::CAudioPlayer(QObject* parent): QThread(parent)
{m_eControlType = E_CONTROL_NONE;m_pAudioOutput = nullptr;m_bRun = false;m_nSeekMs = 0;
}CAudioPlayer::~CAudioPlayer()
{}void CAudioPlayer::Quit()
{m_bRun = false;wait();
}void CAudioPlayer::Play(const std::string& strFilePath)
{m_strFilePath = strFilePath;m_eControlType = E_CONTROL_PLAY;if (!isRunning()){m_bRun = true;start();}
}void CAudioPlayer::Stop()
{if (isRunning()){m_eControlType = E_CONTROL_STOP;}
}void CAudioPlayer::Pause()
{if (isRunning()){m_eControlType = E_CONTROL_PAUSE;}
}void CAudioPlayer::Resume()
{if (isRunning()){m_eControlType = E_CONTROL_RESUME;}
}void CAudioPlayer::Seek(int nMs)
{if (isRunning()){m_eControlType = E_CONTROL_SEEK;m_nSeekMs = nMs;}
}void CAudioPlayer::run()
{if (!InitAudioOutput(44100)){qDebug() << "InitAudioOutput failed";return;}while (m_bRun){switch (m_eControlType){case E_CONTROL_NONE:{msleep(20);}break;case E_CONTROL_PLAY:{m_eControlType = E_CONTROL_NONE;RunPlay();}break;default:m_eControlType = E_CONTROL_NONE;break;}}
}bool CAudioPlayer::InitAudioOutput(int nSampleRate)
{if (m_pAudioOutput){return false;}QAudioFormat format;format.setSampleRate(nSampleRate);format.setChannelCount(2);format.setSampleSize(16);format.setCodec("audio/pcm");format.setByteOrder(QAudioFormat::LittleEndian);format.setSampleType(QAudioFormat::SignedInt);QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());if (!info.isFormatSupported(format)){qDebug() << "Raw audio format not supported by backend, cannot play audio.";return false;}m_pAudioOutput = std::make_unique<QAudioOutput>(format);m_pAudioOutput->setBufferSize(100000);return true;
}bool CAudioPlayer::CheckControlType()
{if (!m_pAudioOutput){return false;}bool bRet = false;if (m_eControlType == E_CONTROL_PAUSE){while (m_eControlType == E_CONTROL_PAUSE){m_pAudioOutput->suspend();msleep(20);}if (m_eControlType == E_CONTROL_RESUME){m_pAudioOutput->resume();}}if (m_eControlType == E_CONTROL_PLAY){bRet = true;if (m_pAudioOutput->state() == QAudio::StoppedState){m_pAudioOutput->stop();}}if (m_eControlType == E_CONTROL_STOP){bRet = true;if (m_pAudioOutput->state() == QAudio::ActiveState){m_pAudioOutput->stop();}}return bRet;
}void CAudioPlayer::DebugError(int nError)
{char cError[1024] = { 0 };av_strerror(nError, cError, sizeof(cError));qDebug() << "Error:" << cError;
}void CAudioPlayer::RunPlay()
{int nRet = 0;int nDestMs = 0;int nCurMs = 0;if (!m_pAudioOutput){qDebug() << "m_pAudioOutput is nullptr";return;}av_log_set_level(AV_LOG_ERROR);avformat_network_init();AVFormatContext* pAvFormatContext = nullptr;if ((nRet = avformat_open_input(&pAvFormatContext, m_strFilePath.c_str(), nullptr, nullptr)) != 0){qDebug() << "Could not open input file";return;}AVDictionary* pOpts = nullptr;av_dict_set(&pOpts, "analyzeduration", "2147483647", 0); // 设置最大分析时长av_dict_set(&pOpts, "probesize", "2147483647", 0); // 设置最大探测大小if ((nRet = avformat_find_stream_info(pAvFormatContext, &pOpts)) < 0){qDebug() << "Could not find stream information";av_dict_free(&pOpts);avformat_close_input(&pAvFormatContext);return;}av_dict_free(&pOpts);int nAudioStreamIndex = -1;nAudioStreamIndex = av_find_best_stream(pAvFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);if (nAudioStreamIndex < 0){qDebug() << "Could not find audio stream";avformat_close_input(&pAvFormatContext);return;}qDebug() << "Audio stream index:" << nAudioStreamIndex;AVCodec *pCodec = avcodec_find_decoder(pAvFormatContext->streams[nAudioStreamIndex]->codecpar->codec_id);AVCodecContext* pCodecContext = avcodec_alloc_context3(pCodec);avcodec_parameters_to_context(pCodecContext, pAvFormatContext->streams[nAudioStreamIndex]->codecpar);nRet = avcodec_open2(pCodecContext, pCodec, nullptr);if (nRet < 0){qDebug() << "Could not open codec";avcodec_free_context(&pCodecContext);avformat_close_input(&pAvFormatContext);return;}SwrContext *pSwrContext = nullptr;pSwrContext = swr_alloc_set_opts(nullptr, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, pCodecContext->sample_rate,pCodecContext->channel_layout, pCodecContext->sample_fmt, pCodecContext->sample_rate, 0, nullptr);swr_init(pSwrContext);nDestMs = av_q2d(pAvFormatContext->streams[nAudioStreamIndex]->time_base) * 1000 * pAvFormatContext->streams[nAudioStreamIndex]->duration;qDebug() << TransString2Unicode("码率:") << pCodecContext->bit_rate;qDebug() << TransString2Unicode("格式:") << pCodecContext->sample_fmt;qDebug() << TransString2Unicode("通道:") << pCodecContext->channels;qDebug() << TransString2Unicode("采样率:") << pCodecContext->sample_rate;qDebug() << TransString2Unicode("时长:") << nDestMs;qDebug() << TransString2Unicode("解码器:") << pCodec->name;Q_EMIT SigDuration(nCurMs, nDestMs);AVPacket* pAvPacket = av_packet_alloc();AVFrame* pAvFrame = av_frame_alloc();m_pAudioOutput->stop();QIODevice* pAudioDevice = m_pAudioOutput->start();while (1){if (CheckControlType()){break;}if (m_eControlType == CAudioPlayer::E_CONTROL_SEEK){av_seek_frame(pAvFormatContext, nAudioStreamIndex, m_nSeekMs / (double)1000.0 / av_q2d(pAvFormatContext->streams[nAudioStreamIndex]->time_base), AVSEEK_FLAG_BACKWARD);m_eControlType = E_CONTROL_NONE;Q_EMIT SigSeekOk();}nRet = av_read_frame(pAvFormatContext, pAvPacket);if (nRet == AVERROR_EOF){// 到达文件末尾,向解码器发送刷新信号nRet = avcodec_send_packet(pCodecContext, nullptr);if (nRet < 0){DebugError(nRet);qDebug() << "Could not send flush packet";}while (nRet >= 0){nRet = avcodec_receive_frame(pCodecContext, pAvFrame);if (nRet == AVERROR(EAGAIN) || nRet == AVERROR_EOF){break;}else if (nRet < 0){DebugError(nRet);break;}// 处理剩余的帧uint8_t* pData[2] = { 0 };int nByteCnt = pAvFrame->nb_samples * 2 * 2;std::unique_ptr<uint8_t[]> pDestBuf(new uint8_t[nByteCnt]);pData[0] = pDestBuf.get();nRet = swr_convert(pSwrContext, &pData[0], pAvFrame->nb_samples,(const uint8_t**)(pAvFrame->data), pAvFrame->nb_samples);while (m_pAudioOutput->bytesFree() < nByteCnt){if (CheckControlType()){break;}msleep(10);}if (!CheckControlType()){pAudioDevice->write((const char*)pDestBuf.get(), nByteCnt);}nCurMs = av_q2d(pAvFormatContext->streams[nAudioStreamIndex]->time_base) * 1000 * pAvFrame->pts;Q_EMIT SigDuration(nCurMs, nDestMs);}qDebug() << "End of file reached";SigDuration(nDestMs, nDestMs);break;}else if (nRet < 0){DebugError(nRet);qDebug() << "Could not read frame";SigDuration(nDestMs, nDestMs);av_packet_unref(pAvPacket);break;}if (pAvPacket->stream_index == nAudioStreamIndex){if (CheckControlType()){av_packet_unref(pAvPacket);break;}// 发送 AVPacket 到解码器nRet = avcodec_send_packet(pCodecContext, pAvPacket);av_packet_unref(pAvPacket); // 发送后释放 AVPacketif (nRet < 0){DebugError(nRet);qDebug() << "Could not send packet";continue;}// 从解码器接收 AVFramewhile (true){nRet = avcodec_receive_frame(pCodecContext, pAvFrame);if (nRet == AVERROR(EAGAIN) || nRet == AVERROR_EOF){break;}else if (nRet < 0){DebugError(nRet);break;}uint8_t* pData[2] = { 0 };int nByteCnt = pAvFrame->nb_samples * 2 * 2;std::unique_ptr<uint8_t[]> pDestBuf(new uint8_t[nByteCnt]);pData[0] = pDestBuf.get();nRet = swr_convert(pSwrContext, &pData[0], pAvFrame->nb_samples,(const uint8_t**)(pAvFrame->data), pAvFrame->nb_samples);while (m_pAudioOutput->bytesFree() < nByteCnt){if (CheckControlType()){break;}msleep(10);}if (!CheckControlType()){pAudioDevice->write((const char*)pDestBuf.get(), nByteCnt);}nCurMs = av_q2d(pAvFormatContext->streams[nAudioStreamIndex]->time_base) * 1000 * pAvFrame->pts;Q_EMIT SigDuration(nCurMs, nDestMs);}}else{av_packet_unref(pAvPacket); // 非音频包,释放}}av_frame_free(&pAvFrame);av_packet_free(&pAvPacket);swr_free(&pSwrContext);avcodec_free_context(&pCodecContext);avformat_close_input(&pAvFormatContext);
}

四、主界面类

#include "../Include/EMusicMainWindow.h"
#include "ui_EMusicMainWindow.h"
#include "QtGui/Include/Conversion.h"
#include <QFileDialog>
#include <QThread>
#include <QDebug>CEMusicMainWindow::CEMusicMainWindow(QWidget* parent): QMainWindow(parent), ui(std::make_unique<Ui::CEMusicMainWindow>())
{ui->setupUi(this);InitUI();
}CEMusicMainWindow::~CEMusicMainWindow()
{m_AudioPlayer.Stop();m_AudioPlayer.Quit();
}void CEMusicMainWindow::InitUI()
{m_bIsPlaying = false;m_bSeeking = false;connect(&m_AudioPlayer, &CAudioPlayer::SigDuration, this, &CEMusicMainWindow::SlotUpdateLyricsAndTime);connect(&m_AudioPlayer, &CAudioPlayer::SigSeekOk, this, &CEMusicMainWindow::SlotSeekOk);
}void CEMusicMainWindow::on_pushButton_StopOrPlay_clicked()
{if (m_strMusicFilePath.isEmpty()){return;}if (m_bIsPlaying){m_bIsPlaying = false;m_AudioPlayer.Pause();ui->pushButton_StopOrPlay->setStyleSheet("image: url(:/Play.png);");}else{m_bIsPlaying = true;m_bSeeking = false;ui->pushButton_StopOrPlay->setStyleSheet("image: url(:/Stop.png);");if (m_strOldMusicFilePath.isEmpty()){m_strOldMusicFilePath = m_strMusicFilePath;m_AudioPlayer.Play(TransUnicode2String(m_strMusicFilePath));}else if (m_strMusicFilePath != m_strOldMusicFilePath){m_strOldMusicFilePath = m_strMusicFilePath;m_AudioPlayer.Play(TransUnicode2String(m_strMusicFilePath));}else if(m_AudioPlayer.GetControlType() == CAudioPlayer::E_CONTROL_PAUSE){m_AudioPlayer.Resume();}else {m_strOldMusicFilePath = m_strMusicFilePath;m_AudioPlayer.Play(TransUnicode2String(m_strMusicFilePath));}}
}void CEMusicMainWindow::SlotUpdateLyricsAndTime(int nCurrentTime, int nDestTime)
{std::string strLyrics = m_LyricsReader.GetCurrentLyrics(nCurrentTime);ui->label_Words->setText(TransString2Unicode(strLyrics));if (nCurrentTime == nDestTime && nCurrentTime != 0){m_bIsPlaying = false;ui->pushButton_StopOrPlay->setStyleSheet("image: url(:/Play.png);");}static int currentMs1 = -1, destMs1 = -1;if (currentMs1 == nCurrentTime && destMs1 == nDestTime){return;}currentMs1 = nCurrentTime;destMs1 = nDestTime;//qDebug() << "onDuration:" << nCurrentTime << nDestTime << m_bSeeking;QString currentTime = QString("%1:%2:%3").arg(currentMs1 / 360000 % 60, 2, 10, QChar('0')).arg(currentMs1 / 6000 % 60, 2, 10, QChar('0')).arg(currentMs1 / 1000 % 60, 2, 10, QChar('0'));QString destTime = QString("%1:%2:%3").arg(destMs1 / 360000 % 60, 2, 10, QChar('0')).arg(destMs1 / 6000 % 60, 2, 10, QChar('0')).arg(destMs1 / 1000 % 60, 2, 10, QChar('0'));ui->label_Process->setText(currentTime + "/" + destTime);if (!m_bSeeking) //未滑动{ui->slider_Seek->setMaximum(nDestTime);ui->slider_Seek->setValue(nCurrentTime);} 
}void CEMusicMainWindow::SlotSeekOk()
{m_bSeeking = false;
}void CEMusicMainWindow::on_slider_Seek_sliderPressed()
{m_bSeeking = true;
}void CEMusicMainWindow::on_slider_Seek_sliderReleased()
{m_AudioPlayer.Seek(ui->slider_Seek->value());
}void CEMusicMainWindow::on_pushButton_Select_clicked()
{m_strMusicFilePath = QFileDialog::getOpenFileName(this, TransString2Unicode("选择音乐文件"), "", "Music Files (*.mp3)");if (!m_strMusicFilePath.isEmpty()){m_LyricsReader.LoadLrcFile(m_strMusicFilePath.left(m_strMusicFilePath.lastIndexOf('.')) + ".lrc");}on_pushButton_StopOrPlay_clicked();
}

五、后续完善

完成其他功能

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

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

相关文章

[论文阅读] (38)基于大模型的威胁情报分析与知识图谱构建论文总结(读书笔记)

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

python批量解析提取word内容到excel

# 基于Python实现Word文档内容批量提取与Excel自动化存储 ## 引言 在日常办公场景中&#xff0c;常需要从大量Word文档中提取结构化数据并整理到Excel表格中。传统手动操作效率低下&#xff0c;本文介绍如何通过Python实现自动化批处理&#xff0c;使用python-docx和openpyxl…

win32相关(远程线程和远程线程注入)

远程线程和远程线程注入 CreateRemoteThread函数 作用&#xff1a;创建在另一个进程的虚拟地址空间中运行的线程 HANDLE CreateRemoteThread([in] HANDLE hProcess, // 需要在哪个进程中创建线程[in] LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全…

Flyway

Flyway 是一个强大的数据库版本控制和迁移工具&#xff0c;主要用于管理数据库结构的变更和演进。 核心作用 1. 数据库版本控制 追踪数据库变更&#xff1a;记录每次数据库结构的修改版本管理&#xff1a;为每个变更分配版本号变更历史&#xff1a;完整记录数据库演进过程 …

【深尚想】OPA855QDSGRQ1运算放大器IC德州仪器TI汽车级高速8GHz增益带宽的全面解析

1. 元器件定义与核心特性 OPA855QDSGRQ1 是德州仪器&#xff08;TI&#xff09;推出的一款 汽车级高速运算放大器&#xff0c;专为宽带跨阻放大&#xff08;TIA&#xff09;和电压放大应用优化。核心特性包括&#xff1a; 超高速性能&#xff1a;增益带宽积&#xff08;GBWP&a…

机器学习实验八--基于pca的人脸识别

基于pca的人脸识别 引言&#xff1a;pca1.pca是什么2.PCA算法的基本步骤 实例&#xff1a;人脸识别1.实验目的2.实现步骤3.代码实现4.实验结果5.实验总结 引言&#xff1a;pca 1.pca是什么 pca是一种统计方法&#xff0c;它可以通过正交变换将一组可能相关的变量转换成一组线…

【LLIE专题】NTIRE 2025 低照度图像增强第二名方案

Towards Scale-Aware Low-Light Enhancement via Structure-Guided Transformer Design&#xff08;2025&#xff0c;NTIRE&#xff09; 专题介绍一、研究背景二、SG-LLIE方法1.和Retinexformer方案对比2.总体方案及创新点3.详细方案3.1 结构先验提取3.2 网络结构3.3 损失函数 …

泊松融合的介绍和OpenCV教程

泊松融合 Poisson Blending 简介 核心思想 泊松融合的目标是在保留剪切图像的梯度(纹理)信息的同时,使融合结果在边界区域平滑过渡到目标图像中。换句话说,它在融合区域中重建一个图像,使其梯度尽可能接近源图像的梯度,并且边界贴合目标图像。 数学描述 泊松融合将问题…

Unity协程Coroutine与UniTask对比

原理对比 CoroutineUniTask本质IEnumerator 的协作调度器async/await 状态机&#xff08;IAsyncStateMachine&#xff09;调度方式Unity 内部调用 MoveNext()自建 PlayerLoopRunner 控制状态推进内存管理引用类型&#xff0c;频繁分配 GC结构体 UniTask&#xff0c;低 GC 压力…

MAC软件打开提示已损坏:“已损坏,打不开。您应将它移到废纸篓“

打开「终端.app」&#xff0c;输入以下命令并回车&#xff0c;输入开机密码回车 sudo spctl --master-disable 按照上述步骤操作完成后&#xff0c;打开「系统偏好设置」-「安全与隐私」-「通用」&#xff0c;确保已经修改为「任何来源」。 打开「终端.app」&#xff0c;输入…

JAVA之 Lambda

Java Lambda Lambda 表达式是 Java 8 的核心特性&#xff0c;通过 函数式编程 大幅简化代码。其核心思想是将行为作为参数传递&#xff0c;替代匿名内部类&#xff0c;提升代码的简洁性和可读性。以下是系统解析和完整代码示例&#xff1a; 一、Lambda 表达式基础 语法结构 (…

Starrocks中RoaringBitmap杂谈

背景 最近在阅读Starrocks源码的时候&#xff0c;遇到ColumnRefSet的RoaringBitmap使用&#xff0c;所以借此来讨论一下RoaringBitmap这个数据结构,这种思想是很值得借鉴的。 对于的实现可以参考一下 <dependency><groupId>org.roaringbitmap</groupId><…

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)

目录 &#x1f50d; 若用递归计算每一项&#xff0c;会发生什么&#xff1f; Horners Rule&#xff08;霍纳法则&#xff09; 第一步&#xff1a;我们从最原始的泰勒公式出发 第二步&#xff1a;从形式上重新观察展开式 &#x1f31f; 第三步&#xff1a;引出霍纳法则&…

从Java的Jvm的角度解释一下为什么String不可变?

从Java的Jvm的角度解释一下为什么String不可变&#xff1f; 从 JVM 的角度看&#xff0c;Java 中 String 的不可变性是由多层次的机制共同保障的&#xff0c;这些设计涉及内存管理、性能优化和安全保障&#xff1a; 1. JVM 内存模型与字符串常量池 字符串常量池&#xff08;St…

初识硬编码(x86指令描述)

硬编码 任何一个程序其实都可以看做两部分组成的&#xff0c;指令和数据 cpu并没有明确的规定哪些要当做数据&#xff0c;哪些要当做指令来执行&#xff0c;把数据给EIP只要是遵循了指定的格式&#xff08;x86 x64 ARM&#xff09;&#xff0c;cpu都会当做指令来执行 x86/x64…

3.RV1126-OPENCV 图像叠加

一.功能介绍 图像叠加&#xff1a;就是在一张图片上放上自己想要的图片&#xff0c;如LOGO&#xff0c;时间等。有点像之前提到的OSD原理一样。例如&#xff1a;下图一张图片&#xff0c;在左上角增加其他图片。 二.OPENCV中图像叠加常用的API 1. copyTo方法进行图像叠加 原理…

MySQL垂直分库(基于MyCat)

参考资料&#xff1a; 参考视频 参考博客 Mycat基本部署 视频参考资料&#xff1a;链接: https://pan.baidu.com/s/1xT_WokN_xlRv0h06b6F3yg 提取码: aag3 概要&#xff1a; 本文的垂直分库&#xff0c;全部是基于前文部署的基本架构进行的 垂直分库&#xff1a; 垂直分库…

Spitfire:Codigger 生态中的高性能、安全、分布式浏览器

Spitfire 是 Codigger 生态系统中的一款现代化浏览器&#xff0c;专为追求高效、隐私和分布式技术的用户设计。它结合了 Codigger 的分布式架构优势&#xff0c;在速度、安全性和开发者支持方面提供了独特的解决方案&#xff0c;同时确保用户对数据的完全控制。 1. 高性能浏览…

1-【源码剖析】kafka核心概念

从今天开始开始在csdn上记录学习的笔记&#xff0c;主要包括以下几个方面&#xff1a; kafkaflinkdoris 本系列笔记主要记录Kafka学习相关的内容。在进行kafka源码学习之前&#xff0c;先介绍一下Kafka的核心概念。 消息 消息是kafka中最基本的数据单元&#xff0c;由key和…

互联网大厂Java求职面试:云原生架构下的微服务网关与可观测性设计

互联网大厂Java求职面试&#xff1a;云原生架构下的微服务网关与可观测性设计 郑薪苦怀着忐忑的心情走进了会议室&#xff0c;对面坐着的是某大厂的技术总监张总&#xff0c;一位在云原生领域有着深厚积累的专家。 第一轮面试&#xff1a;微服务网关的设计挑战 张总&#xf…