本文翻译自Qt Multimedia in Qt 6 (原文发布于7月7日)
原文作者:Lars Knoll,Qt公司首席架构师
校审:Kenny Zhang
Qt 6.2第一个beta版已发布,在众多新的扩展模块中,有一个全新的Qt Multimedia模块。
Qt Multimedia在Qt 6中经历了相当大改变。尽管我们复用了Qt 5.15中的一些代码,在很多方面,它是一个全新的API和实现。
虽然我们试图在大多数模块中保持Qt 5和Qt 6之间尽可能多的源代码兼容性,但为了使API和实现在未来仍适用,我们不得不做了很大修改,最终选择以尽可能好的API而不是最大的兼容性为目标。如果您一直在Qt 5中使用Qt Multimedia,将需要对您的实现进行一些更改。
这篇博文将尝试带您一览其变,探索其API和内部实现。
目标
Qt 5中的Qt Multimedia有一个相当松散的定义范围。不同后端对API不同部分的支持并不一致,而且API本身的某些部分也不容易跨平台使用。
我们试图在Qt 6上缩小范围,并致力于打造一系列适用于所有支持平台的特性。尽管还没有完全实现该目标,但希望Qt 6.2.0的发布能填补大多数实现上的空白。
我们希望在Qt 6.2中支持的主要用例包括:
- 音频和视频播放
- 音频和视频录制(摄像头和麦克风)
- 底层(基于PCM)音频和音频解码
- 与Qt Quick和Widgets集成
- 尽可能多地使用硬件加速
据我们所知,这些功能涵盖了我们用户过去使用Qt Multimedia的一部分用例。我们的目标是首先改进这些核心用例,并确保它们在所有平台上一致工作,然后再添加新功能扩展模块。
内部架构变化
Qt5中的Qt Multimedia具有复杂的基于插件的架构,使用多个插件来实现不同的前端功能。一个完整的多媒体后端实现将包含至少四个插件。用于实现这些插件的后端API是公开的,很难调整和改进这些后端的功能。
这种架构使得模块的维护和开发变得非常困难。在Qt 6中,我们将其大幅简化,并去掉了插件框架。现在的后端会在编译时被编译成Qt Multimedia的共享库。现在只有一个后端API覆盖了所有多媒体,去掉了Qt 5中人为拆分成多个后端的做法。最后,我们将后端API设为private,以便将来可以轻松调整和扩展它。
完成后,我们可以仔细查看平台相关后端代码所需的API和接口。我们设法将实现多媒体后端所需的类集从40个减少到15个,并减少了纯虚拟方法的数量,为许多非必须功能提供了默认实现。
新的后端API在一定程度上模仿了我们在Qt Gui中用于窗口系统集成的QPA架构,新的QPlatformMediaIntegration类现在确实充当了一个公共入口点和工厂类来实例化平台相关的后端对象。在大多数情况下,我们现在的目标是在公共API中的类和实现该功能的类之间建立一对一的关系。因此,公共QMediaPlayer API有一个实现平台相关功能的QPlatformMediaPlayer类。
通过这些更改,我们还可以删除大量在前端和后端之间重复的代码,并避免它们之间的大量的转发。有了它,我们还可以将大量跨平台功能和验证转移到共享的、独立于平台的部分代码中。
总而言之,这极大简化了我们的代码库,并在不丢失大量功能的情况下大幅减少了代码大小。Qt Multimedia在5.15中大约有140,000行代码,现在Qt 6中已减少到大约74,000行。
支持的后端
我们还重新审视了Qt 6中支持的后端,并将其缩减为我们认为将来可以支持的一组集合。例如,在Qt 5中,我们在Windows上有三个完全不同的后端实现,使用DirectShow、WMF和一个单独的基于WMF的WinRT实现。
在Qt 6中,当前支持的集合为:
- Linux中使用GStreamer
- MacOS和iOS中使用AVFoundation
- Windows中使用WMF
- Android中使用MediaPlayer和Camera Java APIs
Qt 6.3计划支持QNX。我们可能还会在6.2版本的WebAssembly上提供底层音频支持。此外,我们依然保留了PulseAudio或ALSA对Linux上底层音频的支持,但目前还没有测试或支持这些代码。根据需求,我们可能会在以后的版本中恢复这些功能。
公共 API
Qt Multimedia的公共 API由 五大功能块组成。其中三个模块已存在于 Qt 5 中,但是这些模块中的API发生了重大变化。功能块包括:
- 设备发现
- 底层音频
- 播放和解码
- 快照和录像
- 视频输出管线
在做新的API的时候,我们也希望在 C++和QML之间有一个统一API。这样我们可以删除大量代码,这些代码只是简单包装了C++ API ,并以稍微不同的方式将其暴露给QML。对于大多数公共C++类,现在有一个相应的同名QML项。例如QMediaPlayer会有一个相应
QML MediaPlayer模块,具备和C++类相同的API。
让我们深入了解不同功能块的更多细节:
设备发现
让我们从设备发现开始。新的QMediaDevices类旨在为您提供可用音频和视频设备的信息。它将允许您列出可用的音频输入(通常是麦克风)、音频输出(扬声器和耳机)和摄像头。您可以检索默认设备,该类还会通知您有关配置的任何更改,例如,当用户连接外部耳机时。
QMediaDevices devices;
connect(&devices, &QMediaDevices::audioInputsChanged,
[]() { qDebug() << “available audio inputs have changed”; }
底层音频
该功能块有助于使用原始 PCM 数据处理底层音频,并直接从音频设备读取或写入该数据。
这个模块在架构上仍然与我们在Qt 5中的非常相似,但很多细节都发生了变化。最值得注意的是,用于读取或写入音频设备的底层类已改名。它们现在被命名为QAudioSource和QAudioSink。命名反映了它们的底层特性,并释放了我们在Qt 5中的旧名称(QAudioInput和QAudioOutput)以用于播放和捕获API。
QAudioFormat API已被清理和简化,现在支持四种最常用的PCM数据格式(8位无符号整型,16位和32位有符号整型和浮点型数据)。QAudioFormat还获得了新的 API 来处理音频通道的定位信息,但目前后端尚未完全支持。
我们还删除了已弃用的QSound类。QSoundEffect是它的替代,用于播放低延迟的短声音。QSoundEffect目前仍要求您使用WAV作为效果格式,但我们计划扩展此功能,在6.2之后允许播放压缩的音频数据。
播放
处理媒体文件播放的主要类是QMediaPlayer。QMediaPlayer API在Qt5的基础上进行了简化,目前我们已从模块中删除了所有播放列表功能,这是Qt5媒体播放器中内置的功能,但使其API和实现变得复杂。我们计划将6.2版本之后的播放列表功能作为一个独立的类重新引入,如果需要,您可以连接到QMediaPlayer。当前,如果您需要,可以在“Player”示例中找到一些代码来处理播放列表。
另一方面,QMediaPlayer获得了渲染字幕的能力,您现在可以使用setActiveAudioTrack()、setActiveVideoTrack()和setActiveSubtitleTrack()方法检查和选择所需的音频、视频或字幕音轨。
Qt6中的QMediaPlayer要求您使用setAudioOutput()和setVideoOutput()方法将其主动连接到音频和视频输出。不设置音频输出将意味着媒体播放器不播放音频。这与Qt 5有所不同,在Qt 5中,始终选择默认音频输出。进行此更改是为了实现音频和视频之间的对称API,并简化与QML的集成。
用C++实现的最小媒体播放器如下所示:
QMediaPlayer player;
QAudioOutput audioOutput; // chooses the default audio routing
player.setAudioOutput(&audioOutput);
QVideoWidget *videoOutput = new QVideoWidget;
player.setVideoOutput(videoOutput);
player.setSource(“mymediafile.mp4”);
player.play();
基于C++/Widgets的播放器示例具有更完整的实现,它还支持字幕和音频语言选择以及显示与媒体文件关联的元数据:
或使用QML:
Window {
MediaPlayer {
id: mediaPlayer
audioOutput: AudioOutput {} // use default audio routing
videoOutput: videoOutput
source: “mymediafile.mp4”
}
VideoOutput {
id: videoOutput
anchors.fill: parent
}
Component.onCompleted: mediaPlayer.play()
}
还有一个全新的基于QML的媒体播放器示例,基于Qt Quick Controls。您可以使用:
除了QMediaPlayer之外,Qt 6还具有跨平台支持,可以使用QAudioDecoder类将音频文件解码为原始PCM数据。该功能存在于Qt 5的某些平台上,但并未在所有平台上实现。
快照和录像
快照和录像功能在Qt 6中经历了最大的API变化。在Qt 5中,您必须神奇地将摄像头连接到录像设备上,而Qt 6现在提更了一个更明确的API来设置快照管线。
Qt 6 的中心类是QMediaCaptureSession。录制音频/视频或捕获图像时,总是需要这个类。要设置录音会话,您可以使用setAudioInput()将音频输入连接到会话,如果您想从摄像头录制,请使用setCamera()将摄像头连接到会话。
这里要注意的一件事是QAudioInput和QCamera充当两个输入通道。使用QAudioInput::setDevice()或 QCamera::setCameraDevice()选择要使用的物理设备。选择设备后,QAudioInput和QCamera允许您更改该设备的属性,例如设置音量或相机的分辨率和帧率。
QMediaCaptureSession允许将音频和视频输出连接到它以进行预览和监视。要拍摄静止图像,请使用setImageCapture()将QImageCapture对象连接到它
QMediaCaptureSession session;
QCamera camera;
session.addCamera(&camera);
QImageCapture imageCapture;
session.addImageCapture(&imageCapture);
camera.start();
imageCapture.captureToFile(“myimage.jpg”);
要录制音频和视频,请将QMediaRecorder连接到会话。QMediaRecorder允许通过指定QMediaFormat请求特定的文件格式和编解码器进行录制。在Qt6中,我们没有使用枚举为不同格式和编解码器提供跨平台API。由于编解码器支持依赖于平台,您还可以查询QMediaFormat以获得支持的文件格式和编解码器集。后端还将始终尝试将请求的格式解析为受支持的格式。因此,例如,如果您请求具有H265视频编解码器的MPEG4文件,但不支持H265,则它可能会回退到H264或其它受支持的编解码器。
QMediaRecorder recorder;
session.setRecorder(&recorder);
QMediaFormat format(QMediaFormat::MPEG4);
format.setAudioCodec(QMediaFormat::AudioCodec::AAC);
format.setVideoCodec(QMediaFormat::VideoCodec::H265);
recorder.setMediaFormat(format);
recorder.setOutputLocation(“mycapture.mp4”);
recorder.record();
除设置格式之外,您还可以在编码器上设置其它属性,例如质量、分辨率和帧率。
视频管线
视频管线已使用Qt 6完全重写,试图使其更易于实现自定义用例,并允许解码和渲染的全硬件加速,以及在软件中接收原始视频数据。
此API的大部分只能从C++访问,在QML端有一个VideoOutput QML元素,可以很容易地连接到诸如ShaderEffects之类的东西上,或者可以在Qt Quick 3D的材质SourceItem中使用:
Demo | Qt 6中的视频管线API
如果您使用Qt Widgets,则QVideoWidget类可用作那里视频的输出界面。
对于更底层的访问,C++端的中心类是QVideoSink。QVideoSink可用于从媒体播放器或快照会话中接收单个视频帧。然后将单个QVideoFrame对象映射到内存中,用户必须准备好处理各种YUV和RGB格式,可以使用QPainter渲染或转换为QImage。
下一步工作
在Qt 6.2之后,我们将研究未完成列表中的几个项目。这些想法的优先顺序尚未确定,有关您需求的反馈将对我们有所帮助。我们的想法包括:
- 支持多路视频输出
- 支持多个摄像头
- 支持多个音频输入
- 流式音频/视频
- 截屏
- 音频混音
然而,目前我们的大部分工作都集中在Bug修复和为Qt 6.2做好一切准备上。由于变化较大,在实现中仍有许多瑕疵,一些功能可能有Bug或功能缺失。我们的目标是在6.2.0版本中修复这些问题,但需要您的反馈才能做到这一点。
最近发布的Qt 6.2测试版确实有Qt Multimedia的二进制文件,您可以很轻松地尝试和使用它们。我们非常感谢任何反馈,无论是在博客上,还是在bugreports.qt.io上。