• 670阅读
  • 1回复

Qt音视频开发31-qmedia内核qt5/qt6播放视频 [复制链接]

上一主题 下一主题
离线liudianwu
 

只看楼主 倒序阅读 楼主  发表于: 2023-03-29


## 一、前言
qt5中的多媒体框架明显比qt4丰富了很多,使用也极其友好,提供的api接口非常简单明了,不需要像qt4中那样还需要绑定和创建路径之类的。同样也还是依赖本地解码器,qt6中的多媒体框架据说重写了,性能暴增很多,后端还支持多种解码方式,比如可以选用ffmpeg作为后端解码,这样的话就统一起来了,而且完全的跨平台,性能上主要体现在cpu的占用极低,但是也有缺点,那就是目前为止的版本比如Qt6.5及以下,还不支持视频流的播放,具体原因未知,不知道后期是否会加入支持还是架构原因不支持。

播放视频基本流程:

- 实例化视频播放控件 new QMediaPlayer
- 实例化视频显示控件 new QVideoWidget
- 设置视频输出对象 mediaPlayer->setVideoOutput(videoWidget)
- Qt6需要单独指定音频输出 new QAudioOutput/mediaPlayer->setAudioOutput(audioOutput)
- 设置播放地址 mediaPlayer->setMedia/mediaPlayer->setSource
- 开始/暂停/停止 mediaPlayer->play()/mediaPlayer->pause()/mediaPlayer->stop()

## 二、效果图



## 三、体验地址
1. 国内站点:[https://gitee.com/feiyangqingyun](https://gitee.com/feiyangqingyun)
2. 国际站点:[https://github.com/feiyangqingyun](https://github.com/feiyangqingyun)
3. 个人作品:[https://blog.csdn.net/feiyangqingyun/article/details/97565652](https://blog.csdn.net/feiyangqingyun/article/details/97565652)
4. 体验地址:[https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g](https://pan.baidu.com/s/1d7TH_GEYl5nOecuNlWJJ7g) 提取码:01jf 文件名:bin_video_demo/bin_linux_video。

## 四、相关代码
```cpp
QMediaThread::QMediaThread(QObject *parent) : VideoThread(parent)
{
    //部分版本非句柄模式需要初始化音频输出设备(这可能是Qt的bug/不这样处理会崩溃)
#if 1
    QAudioFormat format;
    format.setSampleRate(8000);
    format.setChannelCount(1);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setSampleType(QAudioFormat::SignedInt);
    format.setByteOrder(QAudioFormat::LittleEndian);
    QAudioOutput *audioOutput = new QAudioOutput(format);
    audioOutput->start();
    audioOutput->deleteLater();
#endif

    //实例化视频播放对象
    mediaPlayer = new QMediaPlayer(this);
    connect(mediaPlayer, SIGNAL(durationChanged(qint64)), this, SLOT(slot_receiveDuration(qint64)));
    connect(mediaPlayer, SIGNAL(positionChanged(qint64)), this, SLOT(slot_receivePosition(qint64)));
    connect(mediaPlayer, SIGNAL(volumeChanged(int)), this, SIGNAL(receiveVolume(int)));
    connect(mediaPlayer, SIGNAL(mutedChanged(bool)), this, SIGNAL(receiveMuted(bool)));
    connect(mediaPlayer, SIGNAL(metaDataChanged()), this, SLOT(readMediaInfo()));
    connect(mediaPlayer, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(stateChanged(QMediaPlayer::State)));
    connect(mediaPlayer, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(mediaStatusChanged(QMediaPlayer::MediaStatus)));

    //实例化视频录制对象
    mediaRecorder = new QMediaRecorder(mediaPlayer, this);
    connect(mediaRecorder, SIGNAL(stateChanged(QMediaRecorder::State)), this, SLOT(updateRecorderState(QMediaRecorder::State)));
    this->updateRecorderState(mediaRecorder->state());

    //设置录制文件音视频编码格式
    QAudioEncoderSettings audioSettings;
    audioSettings.setCodec("aac");
    audioSettings.setSampleRate(48000);
    mediaRecorder->setAudioSettings(audioSettings);
    QVideoEncoderSettings videoSettings;
    videoSettings.setCodec("h264");
    mediaRecorder->setVideoSettings(videoSettings);

    //实例化视频显示控件
    videoWidget = new QVideoWidget;
    //要加上这行不然可能会闪烁
    videoWidget->setAttribute(Qt::WA_OpaquePaintEvent);
    //设置拉伸填充
    videoWidget->setAspectRatioMode(Qt::IgnoreAspectRatio);

    //实例化视频画布控件用于回调拿到每一帧图片数据
    videoSurface = new AbstractVideoSurface;
    connect(videoSurface, SIGNAL(receiveImage(QImage, int)), this, SIGNAL(receiveImage(QImage, int)));
    connect(videoSurface, SIGNAL(receiveRgb(int, int, quint8 *, int)), this, SIGNAL(receiveFrame(int, int, quint8 *, int)));
    connect(videoSurface, SIGNAL(resolutionChanged(int, int)), this, SLOT(resolutionChanged(int, int)));
    mediaPlayer->setVideoOutput(videoSurface);

    //设置播放进度触发间隔
    mediaPlayer->setNotifyInterval(300);
}

QMediaThread::~QMediaThread()
{
    videoWidget->deleteLater();
    videoSurface->deleteLater();
}

bool QMediaThread::openVideo()
{
    //先检查地址是否正常(文件是否存在或者网络地址是否可达)
    if (!VideoHelper::checkUrl(this, videoType, videoUrl, connectTimeout)) {
        return false;
    }

    //启动计时
    timer.start();

    //句柄模式将视频显示控件放到句柄控件布局中
    if (videoMode == VideoMode_Hwnd) {
        hwndWidget->layout()->addWidget(videoWidget);
    }

    //视频需要主动显示对应显示控件
    if (!onlyAudio && videoMode == VideoMode_Hwnd) {
        QMetaObject::invokeMethod(videoWidget, "show");
    }

    //绘制模式拿到图片/GPU模式取出rgb数据用opengl绘制
    if (videoMode == VideoMode_Painter) {
        videoSurface->setType(1);
    } else if (videoMode == VideoMode_Opengl) {
        videoSurface->setType(2);
    }

    //设置地址开始播放
    if (videoType == VideoType_FileLocal) {
        mediaPlayer->setMedia(QUrl::fromLocalFile(videoUrl));
    } else {
        mediaPlayer->setMedia(QUrl(VideoHelper::getRightUrl(videoType, videoUrl)));
    }
    mediaPlayer->play();

    isOk = true;
    emit recorderStateChanged(RecorderState_Stopped, fileName);
    lastTime = QDateTime::currentDateTime();
    int time = timer.elapsed();
    debug("打开成功", QString("用时: %1 毫秒").arg(time));
    //只有获取到了宽高信息才算真正打开完成
    //emit receivePlayStart(time);
    return isOk;
}

void QMediaThread::closeVideo()
{
    //先停止录制
    recordStop();
    //隐藏视频控件
    QMetaObject::invokeMethod(videoWidget, "hide");
    //停止播放
#if (QT_VERSION >= QT_VERSION_CHECK(5,6,0))
    mediaPlayer->stop();
#else
    QMetaObject::invokeMethod(mediaPlayer, "stop");
#endif
    //调用父类关闭
    VideoThread::closeVideo();
}

void QMediaThread::slot_receiveDuration(qint64 duration)
{
    this->updateTime();
    this->duration = duration;
    this->checkVideoType();
    if (getIsFile()) {
        emit receiveDuration(duration);
    }
}

void QMediaThread::slot_receivePosition(qint64 position)
{
    //文件和视频流都有进度需要更新消息时间(只有文件才需要更新进度)
    this->updateTime();
    this->position = position;
    if (getIsFile()) {
        emit receivePosition(position);
        //如果设置了重复循环播放则快到了文件末尾重新设置位置即可
        if (this->getPlayRepeat() && (this->getDuration() - position) < 500) {
            QMetaObject::invokeMethod(this, "setPosition", Q_ARG(qint64, 0));
        }
    }
}
```

4条评分好评度+1贡献值+1金钱+10威望+1
20091001753 好评度 +1 - 2023-03-29
20091001753 贡献值 +1 - 2023-03-29
20091001753 威望 +1 - 2023-03-29
20091001753 金钱 +10 - 2023-03-29
欢迎关注微信公众号:Qt实战/Qt入门和进阶(各种开源作品、经验整理、项目实战技巧,专注Qt/C++软件开发,视频监控、物联网、工业控制、嵌入式软件、国产化系统应用软件开发) QQ:517216493  WX:feiyangqingyun  QQ群:751439350
离线wzr2008

只看该作者 1楼 发表于: 2023-06-07
请教大神: Qt6.5 QMediaPlayer播放中文名媒体文件失败,如何处理?
Qt6.5 代码:

```c++
  QString filename = QFileDialog::getOpenFileName(this, "选择", "f:");
//...

QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();

QAudioOutput * audioOutput = new QAudioOutput(defaultDevice);

audioOutput->setVolume (0.1);

QMediaPlayer * player = new QMediaPlayer();

player->setAudioOutput (audioOutput);

player->setSource(QUrl::fromLocalFile(filename));

player->play ();

   qDebug() << "文件:" << filename << " errorString " << player->errorString ();
  qDebug() << "mediaStatus " << player->mediaStatus ();
  qDebug() << "playbackState " << player->playbackState ();


```
这里 filename如果是中文(例:"f:/那些年.mp3", 则不能播放)
输出:
文件: "F:/那些年.mp3"  errorString  ""
mediaStatus  QMediaPlayer::InvalidMedia
playbackState  QMediaPlayer::StoppedState

直接将中文文件名"那些年.mp3"改为"a.mp3"(或其它非中文字母)则播放成功.

快速回复
限100 字节
 
上一个 下一个