#include "playerdemowindow.h" #include #include #include #include #include #include PlayerDemoWindow::PlayerDemoWindow(QWidget* parent) : QWidget(parent) { QVBoxLayout* layout = new QVBoxLayout(this); m_videoWidget = new OpenGLVideoWidget(this); layout->addWidget(m_videoWidget); // 进度条和时间 QHBoxLayout* progressLayout = new QHBoxLayout(); m_progressSlider = new QSlider(Qt::Horizontal, this); m_progressSlider->setRange(0, 1000); m_timeLabel = new QLabel("00:00:00 / 00:00:00", this); progressLayout->addWidget(m_progressSlider, 1); progressLayout->addWidget(m_timeLabel); layout->addLayout(progressLayout); // 倍速选择和播放按钮 QHBoxLayout* controlLayout = new QHBoxLayout(); m_playBtn = new QPushButton("播放", this); m_speedCombo = new QComboBox(this); m_speedCombo->addItem("0.5x", 0.5); m_speedCombo->addItem("1.0x", 1.0); m_speedCombo->addItem("1.5x", 1.5); m_speedCombo->addItem("2.0x", 2.0); controlLayout->addWidget(m_playBtn); controlLayout->addWidget(new QLabel("倍速:", this)); controlLayout->addWidget(m_speedCombo); layout->addLayout(controlLayout); m_speedCombo->setCurrentText("1.0x"); connect(m_progressSlider, &QSlider::sliderPressed, [this]() { m_sliderPressed = true; }); connect(m_progressSlider, &QSlider::sliderReleased, this, &PlayerDemoWindow::onProgressSliderReleased); connect(m_progressSlider, &QSlider::sliderMoved, this, &PlayerDemoWindow::onProgressSliderMoved); connect(m_speedCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PlayerDemoWindow::onSpeedChanged); connect(m_playBtn, &QPushButton::clicked, this, &PlayerDemoWindow::onPlayClicked); } PlayerDemoWindow::~PlayerDemoWindow() { if (m_puller) { m_puller->stop(); delete m_puller; } if (m_audioOutput) { m_audioOutput->stop(); delete m_audioOutput; } if (m_swrCtx) { swr_free(&m_swrCtx); } if (m_swrBuffer) { av_free(m_swrBuffer); } } void PlayerDemoWindow::startPlay(const QString& url) { if (m_puller) { m_puller->stop(); delete m_puller; m_puller = nullptr; } m_puller = new FFmpegVideoPuller(); if (!m_puller->open(url, 300, 300)) { QMessageBox::critical(this, "错误", "无法打开流"); return; } m_puller->setSpeed(m_speedCombo->currentData().toFloat()); // 设置回调 m_puller->setVideoRenderCallback([this](AVFrame* frame) { m_videoWidget->Render(frame); updateProgress(); // 每次渲染视频帧时刷新进度条 }); m_puller->setAudioPlayCallback([this](AVFrame* frame) { if (!m_audioOutput) { initAudio(frame); } playAudioFrame(frame); }); m_puller->start(); // 进度条初始化 m_firstPts = m_puller->getFirstPts(); m_lastPts = m_puller->getLastPts(); m_duration = m_lastPts - m_firstPts; m_progressSlider->setValue(0); updateProgress(); } void PlayerDemoWindow::onPlayClicked() { if (!m_puller) return; float curSpeed = m_puller->getSpeed(); if (curSpeed > 0.01f) { m_puller->setSpeed(0.0f); m_playBtn->setText("播放"); } else { float speed = m_speedCombo->currentData().toFloat(); m_puller->setSpeed(speed); m_playBtn->setText("暂停"); } } void PlayerDemoWindow::initAudio(const AVFrame* frame) { if (m_audioOutput) { m_audioOutput->stop(); delete m_audioOutput; m_audioOutput = nullptr; } if (m_swrCtx) { swr_free(&m_swrCtx); m_swrCtx = nullptr; } if (m_swrBuffer) { av_free(m_swrBuffer); m_swrBuffer = nullptr; m_swrBufferSize = 0; } QAudioFormat fmt; m_audioSampleRate = frame->sample_rate; m_audioChannels = frame->ch_layout.nb_channels; // FFmpeg 7.x m_audioFormat = (AVSampleFormat) frame->format; fmt.setSampleRate(m_audioSampleRate); fmt.setChannelCount(m_audioChannels); fmt.setSampleSize(16); // 目标格式 S16 fmt.setCodec("audio/pcm"); fmt.setByteOrder(QAudioFormat::LittleEndian); fmt.setSampleType(QAudioFormat::SignedInt); QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); if (!info.isFormatSupported(fmt)) { qWarning() << "音频格式不支持,尝试最近格式"; fmt = info.nearestFormat(fmt); } m_audioOutput = new QAudioOutput(fmt, this); m_audioDevice = m_audioOutput->start(); qDebug() << "initAudio, m_audioOutput=" << m_audioOutput << " m_audioDevice=" << m_audioDevice; if (m_audioOutput) qDebug() << "QAudioOutput error:" << m_audioOutput->error(); AVChannelLayout out_ch_layout; av_channel_layout_default(&out_ch_layout, m_audioChannels); m_swrCtx = swr_alloc(); if (!m_swrCtx) { qWarning() << "swr_alloc 失败"; return; } if (swr_alloc_set_opts2(&m_swrCtx, &out_ch_layout, AV_SAMPLE_FMT_S16, m_audioSampleRate, &frame->ch_layout, (AVSampleFormat) frame->format, frame->sample_rate, 0, nullptr) < 0) { qWarning() << "swr_alloc_set_opts2 失败"; swr_free(&m_swrCtx); return; } if (swr_init(m_swrCtx) < 0) { qWarning() << "swr_init 失败"; swr_free(&m_swrCtx); m_swrCtx = nullptr; } av_channel_layout_uninit(&out_ch_layout); } void PlayerDemoWindow::playAudioFrame(AVFrame* frame) { qDebug() << "--------------" << m_audioOutput << m_audioDevice; if (!m_audioOutput || !m_audioDevice) return; int out_channels = m_audioChannels; int out_samples = frame->nb_samples; int out_bytes_per_sample = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16); // 目标缓冲区大小 int needed_bufsize = av_samples_get_buffer_size(nullptr, out_channels, out_samples, AV_SAMPLE_FMT_S16, 1); if (!m_swrBuffer || m_swrBufferSize < needed_bufsize) { if (m_swrBuffer) av_free(m_swrBuffer); m_swrBuffer = (uint8_t*) av_malloc(needed_bufsize); m_swrBufferSize = needed_bufsize; } uint8_t* out[] = {m_swrBuffer}; int converted = 0; // qDebug() << "playAudioFrame, m_audioOutput=" << m_audioOutput // << " m_audioDevice=" << m_audioDevice; if (m_swrCtx) { converted = swr_convert(m_swrCtx, out, out_samples, (const uint8_t**) frame->extended_data, frame->nb_samples); qDebug() << "swr_convert 返回" << converted; if (converted > 0) { int out_size = converted * out_channels * out_bytes_per_sample; qint64 written = m_audioDevice->write((const char*) m_swrBuffer, out_size); (void) written; // qDebug() << "写入音频数据, 大小=" << out_size << " 实际写入=" << written; } } else if (frame->format == AV_SAMPLE_FMT_S16) { // 直接写 int dataSize = av_samples_get_buffer_size(nullptr, frame->ch_layout.nb_channels, frame->nb_samples, (AVSampleFormat) frame->format, 1); m_audioDevice->write((const char*) frame->data[0], dataSize); } } void PlayerDemoWindow::onProgressSliderMoved(int value) { m_sliderPressed = true; if (m_duration <= 0) { m_timeLabel->setText("00:00:00 / 00:00:00"); return; } double seekPts = m_firstPts + (m_duration * value / 1000.0); QString cur = formatTime(seekPts - m_firstPts); QString total = formatTime(m_duration); m_timeLabel->setText(cur + " / " + total); } void PlayerDemoWindow::onProgressSliderReleased() { if (!m_puller || m_duration <= 0) return; int value = m_progressSlider->value(); double seekPts = m_firstPts + (m_duration * value / 1000.0); if (seekPts < m_firstPts) seekPts = m_firstPts; if (seekPts > m_lastPts) seekPts = m_lastPts; m_puller->seekToPts(seekPts); m_sliderPressed = false; } void PlayerDemoWindow::onSpeedChanged(int index) { if (!m_puller) return; float speed = m_speedCombo->itemData(index).toFloat(); m_puller->setSpeed(speed); } void PlayerDemoWindow::updateProgress() { if (!m_puller || m_sliderPressed) return; double duration = m_puller->getTotalDuration(); if (duration > 0) { m_duration = duration; m_firstPts = 0; m_lastPts = duration; } else { m_firstPts = m_puller->getFirstPts(); m_lastPts = m_puller->getLastPts(); m_duration = m_lastPts - m_firstPts; } // 检查有效性 if (m_firstPts < 0 || m_lastPts < 0 || m_duration <= 0) { m_progressSlider->setValue(0); m_timeLabel->setText("00:00:00 / 00:00:00"); return; } double curPts = m_puller->getCurrentPts(); if (curPts < m_firstPts) curPts = m_firstPts; if (curPts > m_lastPts) curPts = m_lastPts; int value = int((curPts - m_firstPts) / m_duration * 1000); m_progressSlider->setValue(value); QString cur = formatTime(curPts - m_firstPts); QString total = formatTime(m_duration); m_timeLabel->setText(cur + " / " + total); } QString PlayerDemoWindow::formatTime(double seconds) const { if (seconds < 0) seconds = 0; int sec = int(seconds + 0.5); int h = sec / 3600; int m = (sec % 3600) / 60; int s = sec % 60; return QString("%1:%2:%3") .arg(h, 2, 10, QChar('0')) .arg(m, 2, 10, QChar('0')) .arg(s, 2, 10, QChar('0')); }