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

news2025/7/27 5:08:34

一、 介绍 使用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 {
        // 尝试其他常见编码,如 GBK
        codec = 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); // 发送后释放 AVPacket
            if (nRet < 0)
            {
                DebugError(nRet);
                qDebug() << "Could not send packet";
                continue;
            }

            // 从解码器接收 AVFrame
            while (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.coloradmin.cn/o/2406867.html

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

相关文章

算术操作符与类型转换:从基础到精通

目录 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符&#xff1a;、-、*、/、% 赋值操作符&#xff1a;和复合赋值 单⽬操作符&#xff1a;、--、、- 前言&#xff1a;从基础到实践——探索运算符与类型转换的奥秘 在先前的文…

jdbc查询mysql数据库时,出现id顺序错误的情况

我在repository中的查询语句如下所示&#xff0c;即传入一个List<intager>的数据&#xff0c;返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致&#xff0c;会导致返回的id是从小到大排列的&#xff0c;但我不希望这样。 Query("SELECT NEW com…

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…

前端开发者常用网站

Can I use网站&#xff1a;一个查询网页技术兼容性的网站 一个查询网页技术兼容性的网站Can I use&#xff1a;Can I use... Support tables for HTML5, CSS3, etc (查询浏览器对HTML5的支持情况) 权威网站&#xff1a;MDN JavaScript权威网站&#xff1a;JavaScript | MDN

如何在Windows本机安装Python并确保与Python.NET兼容

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

快速排序算法改进:随机快排-荷兰国旗划分详解

随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…

篇章二 论坛系统——系统设计

目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…

热门Chrome扩展程序存在明文传输风险,用户隐私安全受威胁

赛门铁克威胁猎手团队最新报告披露&#xff0c;数款拥有数百万活跃用户的Chrome扩展程序正在通过未加密的HTTP连接静默泄露用户敏感数据&#xff0c;严重威胁用户隐私安全。 知名扩展程序存在明文传输风险 尽管宣称提供安全浏览、数据分析或便捷界面等功能&#xff0c;但SEMR…

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…

《信号与系统》第 6 章 信号与系统的时域和频域特性

目录 6.0 引言 6.1 傅里叶变换的模和相位表示 6.2 线性时不变系统频率响应的模和相位表示 6.2.1 线性与非线性相位 6.2.2 群时延 6.2.3 对数模和相位图 6.3 理想频率选择性滤波器的时域特性 6.4 非理想滤波器的时域和频域特性讨论 6.5 一阶与二阶连续时间系统 6.5.1 …

一些实用的chrome扩展0x01

简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序&#xff0c;无论是测试应用程序、搜寻漏洞还是收集情报&#xff0c;它们都能提升工作流程。 FoxyProxy 代理管理工具&#xff0c;此扩展简化了使用代理&#xff08;如 Burp…

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)

Name&#xff1a;3ddown Serial&#xff1a;FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名&#xff1a;Axure 序列号&#xff1a;8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP

02.运算符

目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&&#xff1a;逻辑与 ||&#xff1a;逻辑或 &#xff01;&#xff1a;逻辑非 短路求值 位运算符 按位与&&#xff1a; 按位或 | 按位取反~ …

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 原创笔记&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 上一篇&#xff1a;《数据结构第4章 数组和广义表》…

Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程

鸿蒙电脑版操作系统来了&#xff0c;很多小伙伴想体验鸿蒙电脑版操作系统&#xff0c;可惜&#xff0c;鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机&#xff0c;来体验大家心心念念的鸿蒙系统啦&#xff01;注意&#xff1a;虚拟…