| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- #include "playerdemowindow.h"
- #include <QAudioDeviceInfo>
- #include <QAudioFormat>
- #include <QDebug>
- #include <QHBoxLayout>
- #include <QMessageBox>
- #include <QVBoxLayout>
- 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);
- // 添加保持比例复选框
- m_keepAspectCheck = new QCheckBox("保持比例", this);
- m_keepAspectCheck->setChecked(true);
- controlLayout->addWidget(m_keepAspectCheck);
- 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<int>::of(&QComboBox::currentIndexChanged),
- this,
- &PlayerDemoWindow::onSpeedChanged);
- connect(m_playBtn, &QPushButton::clicked, this, &PlayerDemoWindow::onPlayClicked);
- connect(m_keepAspectCheck, &QCheckBox::toggled, this, [this](bool checked) {
- m_videoWidget->setKeepAspectRatio(checked);
- });
- }
- 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) {
- static bool firstFrame = true;
- if (firstFrame) {
- m_videoWidget->Open(frame->width, frame->height);
- firstFrame = false;
- }
- 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'));
- }
|