当前位置: 首页 > news >正文

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

一、完善上章的功能,形成一个小工具

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

在这里插入图片描述

二、增加歌曲保存类

#include "../Include/MusicListManager.h"
#include "QtGui/Include/Conversion.h"
#include <QFile>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QDebug>CMusicListManager::CMusicListManager()
{}CMusicListManager::~CMusicListManager()
{}bool CMusicListManager::AddMusic(const std::string& strMusicFilePath)
{for (auto& itr : m_vecMusicList){if (itr.strMusicPath == strMusicFilePath){return false;}}stMusicInfo stMusicInfo;stMusicInfo.strMusicName = strMusicFilePath.substr(strMusicFilePath.find_last_of("\\/") + 1).substr(0, strMusicFilePath.find_last_of("."));stMusicInfo.strMusicPath = strMusicFilePath;m_vecMusicList.push_back(stMusicInfo);return true;
}void CMusicListManager::RemoveMusic(const std::string& strMusicFilePath)
{for (auto itr = m_vecMusicList.begin(); itr!= m_vecMusicList.end(); ++itr){if (itr->strMusicPath == strMusicFilePath){m_vecMusicList.erase(itr);return;}}
}stMusicInfo CMusicListManager::GetMusicInfoByIndex(int nIndex) const
{if (nIndex < 0){nIndex = m_vecMusicList.size() - 1;}else if (nIndex >= (int)m_vecMusicList.size()){nIndex = 0;}return m_vecMusicList[nIndex];
}void CMusicListManager::Clear()
{m_vecMusicList.clear();
}std::vector<stMusicInfo> CMusicListManager::GetMusicList() const
{return m_vecMusicList;
}void CMusicListManager::Load(const std::string& strSaveFilePath)
{m_strSaveFilePath = strSaveFilePath;QFile file(TransString2Unicode(strSaveFilePath));if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {qDebug() << TransString2Unicode("无法打开文件:" +strSaveFilePath);return;}m_vecMusicList.clear();QXmlStreamReader xmlReader(&file);while (!xmlReader.atEnd() && !xmlReader.hasError()){QXmlStreamReader::TokenType token = xmlReader.readNext();if (token == QXmlStreamReader::StartElement) {if (xmlReader.name() == "Music"){QXmlStreamAttributes attributes = xmlReader.attributes();if (attributes.hasAttribute("name") && attributes.hasAttribute("path")) {QString name = attributes.value("name").toString();QString path = attributes.value("path").toString();stMusicInfo stMusicInfo;stMusicInfo.strMusicName = TransUnicode2String(name);stMusicInfo.strMusicPath = TransUnicode2String(path);m_vecMusicList.push_back(stMusicInfo);}}}}
}void CMusicListManager::Save()
{QFile file(TransString2Unicode(m_strSaveFilePath));if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){qDebug() <<  TransString2Unicode("无法打开文件进行写入:" + m_strSaveFilePath);return;}QXmlStreamWriter xmlWriter(&file);xmlWriter.setAutoFormatting(true); // 自动格式化 XML 内容xmlWriter.writeStartDocument();xmlWriter.writeStartElement("MusicList");for (auto& itr : m_vecMusicList){xmlWriter.writeStartElement("Music");xmlWriter.writeAttribute("name", TransString2Unicode(itr.strMusicName));xmlWriter.writeAttribute("path", TransString2Unicode(itr.strMusicPath));xmlWriter.writeEndElement(); // 结束 Music 元素}xmlWriter.writeEndElement(); // 结束 MusicList 元素xmlWriter.writeEndDocument();file.close();
}

三、界面一些按钮控制实现

#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();m_pMusicLocalListManager->Save();
}void CEMusicMainWindow::InitUI()
{m_bIsPlaying = false;m_bSeeking = false;m_eCycleType = E_CYCLE_ALL;m_pMusicLocalListManager = std::make_unique<CMusicListManager>();std::string strMusicListPath = TransUnicode2String(QCoreApplication::applicationDirPath()) + "/MusicList.xml";m_pMusicLocalListManager->Load(strMusicListPath);std::vector<stMusicInfo> vecMusicList = m_pMusicLocalListManager->GetMusicList();for (auto musicInfo : vecMusicList){QListWidgetItem* item = new QListWidgetItem(QIcon(":/Music_File.png"), TransString2Unicode(musicInfo.strMusicName));ui->listWidget_All->addItem(item);}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_LyricsReader.LoadLrcFile(m_strMusicFilePath.split('.').first() + ".lrc");m_AudioPlayer.Play(TransUnicode2String(m_strMusicFilePath));}else if (m_strMusicFilePath != m_strOldMusicFilePath){m_strOldMusicFilePath = m_strMusicFilePath;m_LyricsReader.LoadLrcFile(m_strMusicFilePath.split('.').first() + ".lrc");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){if (m_eCycleType == E_CYCLE_SINGLE){m_AudioPlayer.Play(TransUnicode2String(m_strMusicFilePath));}else{on_pushButton_Next_clicked();}}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_listWidget_All_itemDoubleClicked(QListWidgetItem* item)
{int nIndex = ui->listWidget_All->row(item);if (nIndex < 0){return;}stMusicInfo musicInfo = m_pMusicLocalListManager->GetMusicInfoByIndex(nIndex);if (musicInfo.strMusicPath.empty()){return;}m_strMusicFilePath = TransString2Unicode(musicInfo.strMusicPath);m_bIsPlaying = false;on_pushButton_StopOrPlay_clicked();
}void CEMusicMainWindow::on_pushButton_CycleOrSingle_clicked()
{if (m_eCycleType == E_CYCLE_ALL){m_eCycleType = E_CYCLE_SINGLE;ui->pushButton_CycleOrSingle->setStyleSheet("image: url(:/SingleCycle.png);");}else{m_eCycleType = E_CYCLE_ALL;ui->pushButton_CycleOrSingle->setStyleSheet("image: url(:/Cycle.png);");}
}void CEMusicMainWindow::on_pushButton_Prev_clicked()
{int nIndex = ui->listWidget_All->currentRow();if (nIndex < 0){return;}stMusicInfo musicInfo = m_pMusicLocalListManager->GetMusicInfoByIndex(nIndex - 1);if (musicInfo.strMusicPath.empty()){return;}if (nIndex - 1 < 0){ui->listWidget_All->setCurrentRow(ui->listWidget_All->count() - 1);}else{ui->listWidget_All->setCurrentRow(nIndex - 1);}m_strMusicFilePath = TransString2Unicode(musicInfo.strMusicPath);m_bIsPlaying = false;on_pushButton_StopOrPlay_clicked();
}void CEMusicMainWindow::on_pushButton_Next_clicked()
{int nIndex = ui->listWidget_All->currentRow();if (nIndex < 0){return;}stMusicInfo musicInfo = m_pMusicLocalListManager->GetMusicInfoByIndex(nIndex + 1);if (musicInfo.strMusicPath.empty()){return;}if (nIndex + 1 >= ui->listWidget_All->count()){ui->listWidget_All->setCurrentRow(0);}else{ui->listWidget_All->setCurrentRow(nIndex + 1);}m_strMusicFilePath = TransString2Unicode(musicInfo.strMusicPath);m_bIsPlaying = false;on_pushButton_StopOrPlay_clicked();
}void CEMusicMainWindow::on_pushButton_DeleteAll_clicked()
{ui->listWidget_All->clear();m_pMusicLocalListManager->Clear();
}void CEMusicMainWindow::on_pushButton_Select_clicked()
{QDir dir = QFileDialog::getExistingDirectory(this, TransString2Unicode("选择音乐文件夹"), QDir::currentPath());if (dir.exists()){QStringList filters;filters << "*.mp3";QStringList files = dir.entryList(filters, QDir::Files);for (auto file : files){std::string strFilePath = TransUnicode2String(dir.absoluteFilePath(file));if (m_pMusicLocalListManager->AddMusic(strFilePath)){QListWidgetItem* item = new QListWidgetItem(QIcon(":/Music_File.png"), file.split('.').first());ui->listWidget_All->addItem(item);}}}
}

四、总结

成功利用 Qt 和 FFmpeg 实现了一个简单的音乐播放器,掌握了音频解码、播放以及用户界面设计等相关技术。

音频解码技术详解
音频解码是将压缩的数字音频数据还原为原始波形信号的过程,是现代数字音频处理的核心环节。
音频解码的基本流程

数据输入:接收压缩的音频数据流(如MP3、AAC、FLAC等格式文件)

格式解析:识别音频文件的封装格式和编码标准

解码运算:根据特定算法进行解压缩运算,常见方法包括:

频率域变换(如MP3使用的MDCT变换)
预测编码解算
熵解码(Huffman编码等)

后处理:包括重采样、抖动处理、声道映射等

输出PCM:最终产生脉冲编码调制(PCM)信号,供数模转换器使用

主要音频编码标准

有损压缩

MP3(MPEG-1 Audio Layer III):使用心理声学模型,去除人耳不敏感的频段
AAC(Advanced Audio Coding):改进的MP3算法,效率提高约30%
Opus:低延迟编码,适合实时通信

无损压缩

FLAC(Free Lossless Audio Codec):保持原始音质,压缩率约50%
ALAC(Apple Lossless):苹果设备常用无损格式

应用场景

消费电子:智能手机、数字音乐播放器
广播系统:数字广播、网络流媒体
专业音频:录音棚、现场扩声系统
车载音响:支持多格式解码的高保真系统

关键技术指标

解码延迟
资源占用率
格式兼容性
音质还原度
错误恢复能力

现代音频解码器通常集成DSP处理功能,可同时实现均衡、混响等效果处理。随着AI技术的发展,基于神经网络的音频解码算法正在逐步商业化。

http://www.lqws.cn/news/145387.html

相关文章:

  • 自定义事件wpf
  • 构建云原生安全治理体系:挑战、策略与实践路径
  • 解锁FastAPI与MongoDB聚合管道的性能奥秘
  • 动态规划(3)
  • 开关机、重启、改密、登录:图解腾讯云CVM日常管理核心操作,轻松掌控你的云主机
  • 【图片识别改名】如何批量将图片按图片上文字重命名?自动批量识别图片文字并命名,基于图片文字内容改名,WPF和京东ocr识别的解决方案
  • App使用webview套壳引入h5(二)—— app内访问h5,顶部被手机顶部菜单遮挡问题,保留顶部安全距离
  • nano编辑器的详细使用教程
  • 结合PDE反应扩散方程与物理信息神经网络(PINN)进行稀疏数据预测的技术方案
  • Spring Boot + MyBatis 集成支付宝支付流程
  • GIT - 如何从某个分支的 commit创建一个新的分支?
  • Arduino学习-按键灯
  • 智慧充电:新能源汽车智慧充电桩的发展前景受哪些因素影响?
  • ros2--图像/image
  • 各种排序算法的再整理
  • 新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
  • Java转Go日记(六十):gin其他常用知识
  • Angular报错:cann‘t bind to ngClass since it is‘t a known property of div
  • 电路图识图基础知识-自耦变压器降压启动电动机控制电路(十六)
  • 洛谷题目:P2761 软件补丁问题 (本题简单)
  • SD系列I/O接口cRBX01 2VAA008424R1
  • JavaSec-SSTI - 模板引擎注入
  • 深度学习学习率优化方法——pytorch中各类warm up策略
  • 桂花网蓝牙网关物联网医院动态血糖管理应用案例
  • Vue.js 组件:深入理解与实践
  • Spring Boot缓存组件Ehcache、Caffeine、Redis、Hazelcast
  • 使用 C/C++ 和 OpenCV 添加图片水印
  • Android协程学习
  • 负载均衡将https请求转发后端http服务报错:The plain HTTP request was sent to HTTPS port
  • 模块化架构下的前端调试体系建设:WebDebugX 与多工具协同的工程实践