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

news2025/6/6 18:44:06

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

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.coloradmin.cn/o/2401988.html

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

相关文章

vscode 离线安装第三方库跳转库

我安装的是C/C的函数跳转 下载的离线库&#xff1a; 项目首页 - vscode代码自动补全跳转插件离线安装包:cpptools-win32.vsix是一款专为VSCode设计的离线安装插件&#xff0c;特别适合无法连接网络的电脑环境。通过安装此插件&#xff0c;您的VSCode将获得强大的代码自动跳转…

DevExpress WinForms v24.2 - 新增日程组件、电子表格组件功能扩展

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

基于机器学习的心脏病预测模型构建与可解释性分析

一、引言 心脏病是威胁人类健康的重要疾病之一&#xff0c;早期预测和诊断对防治心脏病具有重要意义。本文利用公开的心脏病数据集&#xff0c;通过机器学习算法构建预测模型&#xff0c;并使用 SHAP 值进行模型可解释性分析&#xff0c;旨在为心脏病的辅助诊断提供参考。 二、…

谷粒商城-分布式微服务项目-高级篇[三]

十五、商城业务-支付 15.1 支付宝支付 15.1.1 进入“蚂蚁金服开放平台” 支付宝开放 平台地址&#xff1a; 支付宝开放平台 15.1.2 下载支付宝官方 demo&#xff0c;进行配置和测试 开发者文档&#xff1a;支付宝开放平台文档中心 电脑网站支付文档&#xff1a;小程序文…

实现购物车微信小程序

实现一个微信小程序购物车页面&#xff0c;包含以下功能&#xff1a; 需求说明&#xff1a; 商品列表&#xff1a;显示商品名称、价格、数量加减按钮&#xff0c;支持修改商品数量&#xff08;数量≥1&#xff09;。 全选 / 反选功能&#xff1a;顶部 “全选” 复选框&#…

26考研 | 王道 | 计算机组成原理 | 四、指令系统

26考研 | 王道 | 计算机组成原理 | 四、指令系统 文章目录 26考研 | 王道 | 计算机组成原理 | 四、指令系统1.指令系统0.指令集体系结构1. 指令格式1.按地址码数目不同来分2.指令-按指令长度分类3.指令-按操作码长度分类4.指令-按操作类型分类 2. 扩展操作码指令格式 2.指令的寻…

在 Windows 系统安装 Git

前往官网下载Git - Downloads 目录 一、下载安装包 二、安装 Git 三、安装完成 四、验证安装 五、问题解决 解决步骤 一、下载安装包 点击页面右侧 “Download for Windows” 按钮。 点击页面最上方 “Click here to download” &#xff0c;下载 Git for Windows/x64 …

基于InternLM的情感调节大师FunGPT

基于书生系列大模型&#xff0c;社区用户不断创造出令人耳目一新的项目&#xff0c;从灵感萌发到落地实践&#xff0c;每一个都充满智慧与价值。“与书生共创”将陆续推出一系列文章&#xff0c;分享这些项目背后的故事与经验。欢迎订阅并积极投稿&#xff0c;一起分享经验与成…

【性能调优系列】深入解析火焰图:从基础阅读到性能优化实战

博客目录 一、火焰图基础&#xff1a;结构与阅读方法二、深入分析火焰图&#xff1a;关键观察点与性能瓶颈识别1. 识别最宽的函数块2. HTTP 请求处理分析3. 数据库操作分析4. 业务逻辑分析 三、性能优化实战&#xff1a;从火焰图到解决方案1. 线程池性能优化2. 数据库访问优化3…

Docker 与容器技术的未来:从 OCI 标准到 eBPF 的演进

Docker 的出现无疑是云计算发展史上的一个里程碑。它以其直观的打包、分发和运行方式,极大地简化了应用程序的部署和管理,从而推动了微服务架构和 DevOps 文化的普及。然而,容器技术的未来并非仅仅局限于 Docker,它正朝着更深层次的标准化和更底层的操作系统内核创新方向演…

PLC远程控制网关支持多塘口水环境数据边缘计算与远程安全传输的配置指南

一、项目背景 渔业养殖是关系到我国食物安全和海洋经济发展的重要产业&#xff0c;随着科技的不断进步&#xff0c;传统的养殖模式面临着诸多挑战&#xff0c;如养殖环境复杂、水质变化难以实时监测、设备运行状态不稳定等&#xff0c;这些问题不仅增加了养殖成本&#xff0c;还…

C++11 中 final 和 override 从入门到精通

文章目录 一、引言二、final 关键字2.1 final 关键字的基本概念2.2 final 关键字的语法2.3 final 关键字的使用示例2.3.1 防止类被继承2.3.2 防止虚函数被重写 2.4 final 关键字的使用场景2.5 final 关键字的注意事项 三、override 关键字3.1 override 关键字的基本概念3.2 ove…

大数据-275 Spark MLib - 基础介绍 机器学习算法 集成学习 随机森林 Bagging Boosting

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大模型篇章已经开始&#xff01; 目前已经更新到了第 22 篇&#xff1a;大语言模型 22 - MCP 自动操作 FigmaCursor 自动设计原型 Java篇开…

git互联GitHub 使用教程

一、下载git Git 公司 右键 git config --global user.name "name" git config --global user.email "email" ssh-keygen -t rsa -C email &#xff1a;生成的ssh密钥需要到github 网站中保存ssh 二、GitHub新建repository 三、本地git互联GitHub 找…

SpringBoot+Mysql实现的停车场收费小程序系统+文档

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

面向对象进阶 | 深入探究 Java 静态成员与继承体系

个人主页 文章专栏 文章目录 个人主页文章专栏 一、static&#xff08;静态&#xff09;1.static 静态变量代码展示内存图 2.static 静态方法工具类&#xff1a;练习&#xff1a; 3.static注意事项4.重新认识main方法 二、继承1.继承概述2.继承的特点3.子类到底能继承父类中的…

人脸识别技术成为时代需求,视频智能分析网关视频监控系统中AI算法的应用

一、应用背景&#xff1a;时代需求与技术革新的双重驱动​ 1&#xff09;传统安防系统的困境​&#xff1a;传统监控系统依赖人工逐帧筛查海量视频&#xff0c;在人流密集场所极易漏检&#xff0c;且缺乏实时锁定和主动预警能力&#xff0c;面对突发安全事件响应迟缓。​ 2&a…

pc端小卡片功能-原生JavaScript金融信息与节日日历

代码如下 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>金融信息与节日日历</title><…

Go语言学习-->第一个go程序--hello world!

Go语言学习–&#xff1e;第一个go程序–hello world! 1 写代码前的准备 1 创建编写代码的文件夹 2 使用vscode打开3 项目初始化 **go mod init*&#xff08;初始化一个go mod&#xff09;Go Module 是 Go 1.11 版本引入的官方依赖管理系统&#xff0c;用于替代传统的 GOPATH…

高雄市12岁以下身心障碍儿童口腔保健合作院所名单数据集

描述&#xff1a; 关键字&#xff1a;儿童、口腔、保健、院所、名单 字段特征&#xff1a;序号、院所分级、合作医疗院所、市话、地址 语言&#xff1a;繁体 行数/数量&#xff1a;129行&#xff0c;5列 数据量 &#xff1a;7.27KB 格式&#xff1a;CSV、JSON、XML 目录…