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技术的发展,基于神经网络的音频解码算法正在逐步商业化。

posted on 2025-06-30 11:58  ljbguanli  阅读(14)  评论(0)    收藏  举报