10.多媒体(Multimedia)
本章的作者:e8johan
** 注意: **
最新的构建时间:2016/03/21
这章的源代码能够在assetts folder找到。
Qt 多媒体模块中的多媒体元素使得可以播放和记录诸如声音、视频或图片的媒体。解码和编码通过平台特定的后端来处理。例如,在 Linux 平台上使用流行的 gstreamer 框架,而在 Windows 上使用 DirectShow,在 OS X 上使用 QuickTime。
多媒体元素不是 Qt Quick core API 的一部分。相反,它们需要通过导入 QtMultimedia 5.6 提供的单独的 API 提供,如下所示:
import QtMultimedia 5.6
10.1 播放媒体
QML 应用程序中最基本的多媒体集成是用于播放媒体。这可以使用 MediaPlayer 元素完成,如果源是图像或视频,则可以与 VideoOutput 元素组合使用。MediaPlayer 元素具有指向要播放的媒体的源(source)属性。媒体源被绑定后,只需调用播放(play)功能即可开始播放。
如果要播放视频媒体,即图片或视频,我们还必须设置一个 VideoOutput 元素。运行播放的 MediaPlayer 将通过 source 属性绑定到视频输出。
在下面的示例中,MediaPlayer 被给予一个视频内容作为源的文件。VideoOutput 被创建并绑定到媒体播放器。一旦主要组件被完全初始化,在 Component.onCompleted 中,播放器的播放功能会被调用。
import QtQuick 2.5
import QtMultimedia 5.6
Item {
width: 1024
height: 600
MediaPlayer {
id: player
source: "trailer_400p.ogg"
}
VideoOutput {
anchors.fill: parent
source: player
}
Component.onCompleted: {
player.play();
}
}
通过 MediaPlayer 元素的 volume 属性来控制播放媒体时改变音量的基本操作。还有其他有用的属性。例如,持续时间(duration)和位置(position)属性可用于构建进度条。如果可定位(seekable)属性为真(true),甚至可以在进度条被轻敲时更新位置(position)。下面的示例显示了如何添加到上面的基本播放示例。
Rectangle {
id: progressBar
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 100
height: 30
color: "lightGray"
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: player.duration>0?parent.width*player.position/player.duration:0
color: "darkGray"
}
MouseArea {
anchors.fill: parent
onClicked: {
if (player.seekable) {
player.position = player.duration * mouse.x/width;
}
}
}
}
在默认情况下,position 属性只能每秒更新一次。这意味着进度条将以较大的步骤进行更新,这样看起来播放进度条是一跳一跳的,除非与进度条的宽度相对应的像素数相比,媒体的持续时间足够长。这时,可以通过访问 mediaObject 属性及其 notifyInterval 属性来更改更新间隔。它可以设置为每个位置更新之间的毫秒数,我们可以根据媒体的实际时间来调整更新的时长,以此增加用户界面的平滑度。
Connections {
target: player
onMediaObjectChanged: {
if (player.mediaObject) {
player.mediaObject.notifyInterval = 50;
}
}
}
当使用 MediaPlayer 构建媒体播放器时,监视播放器的状态(status)属性是很好的选择。它是对可能的状态的枚举,范围从 MediaPlayer.Buffered 到 MediaPlayer.InvalidMedia。以下项目中总结了可能的值:
- MediaPlayer.UnknownStatus —— 状态未知。
- MediaPlayer.NoMedia —— 播放器没有分配媒体源。播放停止。
- MediaPlayer.Loading —— 播放器正在加载媒体。
- MediaPlayer.Loaded —— 媒体已经加载。播放停止。
- MediaPlayer.Stalled —— 媒体的装载停滞了。
- MediaPlayer.Buffering —— 正在缓冲媒体。
- MediaPlayer.Buffered —— 媒体被缓冲,这意味着用户可以开始播放媒体。
- MediaPlayer.EndOfMedia —— 媒体已经结束。播放停止。
- MediaPlayer.InvalidMedia —— 媒体无法播放。播放停止。
如上述枚举值所述,播放状态可随时间而变化。调用 play、pause 或 stop 更改状态,但有问题的媒体也可以有效果。例如,可以到达结束,否则可能无效,导致播放停止。当前播放状态可以通过 playbackState 属性进行跟踪。值可以是 MediaPlayer.PlayingState、MediaPlayer.PausedState 或 MediaPlayer.StoppedState。
使用 autoPlay 属性,MediaPlayer 可以在 source 属性更改后尝试进入播放状态。类似的属性是 autoLoad,导致播放器在 source 属性更改后立即尝试加载媒体。后一个属性默认是启用的。
也可以让 MediaPlayer 循环播放媒体项目。循环(loops)属性控制源播放的次数。将属性设置为 MediaPlayer.Infinite 会设置为无限循环。这对连续动画或循环的背景歌曲的播放是很有效的。
10.2 声音特效
当播放声音效果时,从请求播放到实际播放的响应时间变得很重要。在这种情况下,SoundEffect元素派上用场。通过设置源(source)属性,对播放(play)功能的简单调用立即开始播放。
这可以用于在点击屏幕时进行音频反馈,如下所示:
SoundEffect {
id: beep
source: "beep.wav"
}
Rectangle {
id: button
anchors.centerIn: parent
width: 200
height: 100
color: "red"
MouseArea {
anchors.fill: parent
onClicked: beep.play()
}
}
该元素也可以用于伴随音频的转换。要从转换触发播放,将使用 ScriptAction 元素。
SoundEffect {
id: swosh
source: "swosh.wav"
}
transitions: [
Transition {
ParallelAnimation {
ScriptAction { script: swosh.play(); }
PropertyAnimation { properties: "rotation"; duration: 200; }
}
}
]
除了 play 功能之外,还有许多类似的 MediaPlayer 提供的属性。示例是 volume 和 loops。后者可以设置为 SoundEffect.Infinite 进行无限播放。要停止播放,请调用 stop 功能。
** 注意: **
当使用 PulseAudio 后端时,stop 不会立即停止,但只能防止进一步的循环。这是由于底层 API 的限制。
10.3 视频流
VideoOutput 元素不限于与 MediaPlayer 元素组合使用。它也可以直接与视频源一起使用来显示直播视频流。使用 Camera 元素作为源,并且应用程序已完成。来自相机的视频流可以用于向用户提供直播流。此流在捕获照片时用作搜索视图。
import QtQuick 2.5
import QtMultimedia 5.6
Item {
width: 1024
height: 600
VideoOutput {
anchors.fill: parent
source: camera
}
Camera {
id: camera
}
}
10.4 捕获图像
相机(Camera)元素的主要功能之一是可用于拍摄照片。我们将在简单的停止运动应用程序中使用它。在其中,我们将学习如何显示取景器,拍摄照片和跟踪拍摄的照片。
用户界面如下所示。它由三个主要部分组成。在后台,我们将在右侧找到一个取景器,一列按钮,底部显示拍摄的图像列表。想法是拍摄一系列照片,然后点击顺序播放按钮。这将播放图像,创建一个简单的幻灯片。
相机的取景器部分只是一个 Camera 元素,用作 VideoOutput 中的源。这将向用户显示相机的直播视频流。
VideoOutput {
anchors.fill: parent
source: camera
}
Camera {
id: camera
}
照片列表是一个 ListView,横向显示来自 ListModel 的图像,名为 imagePaths。在背景中,使用半透明的黑色矩形(Rectangle)。
ListModel {
id: imagePaths
}
ListView {
id: listView
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
height: 100
orientation: ListView.Horizontal
spacing: 10
model: imagePaths
delegate: Image {
height: 100
source: path
fillMode: Image.PreserveAspectFit
}
Rectangle {
anchors.fill: parent
anchors.topMargin: -10
color: "black"
opacity: 0.5
}
}
为了拍摄图像,我们需要知道 Camera 元素包含一组用于各种任务的子元素。要捕获静态图片,使用 Camera.imageCapture 元素。当我们调用捕获(capture)方法时,将拍摄照片。这将导致 Camera.imageCapture 首先发出 imageCaptured 信号,后跟 imageSaved 信号。
Button {
id: shotButton
text: "Take Photo"
onClicked: {
camera.imageCapture.capture();
}
}
为了拦截子元素的信号,需要一个 Connections 元素。在这种情况下,我们不需要显示预览图像,而只是将生成的图像添加到屏幕底部的 ListView。 如下面的示例所示,保存的图像的路径作为带有信号的路径(path)参数提供。
Connections {
target: camera.imageCapture
onImageSaved: {
imagePaths.append({"path": path})
listView.positionViewAtEnd();
}
}
要显示预览,请连接到 imageCaptured 信号,并使用预览(preview)信号参数作为 Image 元素的源(source )。 requestId 信号参数同时发送 imageCaptured 和 imageSaved。该值从捕获(capture)方法返回。使用它,捕获图像可以在整个循环中进行跟踪。这样,预览可以先使用,然后被正确保存的图像替换。但是,这并不是我们在这个例子中所做的。
应用程序的最后一部分是实际播放。这是使用 Timer 元素和一些 JavaScript 驱动的。 _imageIndex 变量用于跟踪当前显示的图像。当显示最后一张图像时,播放停止。在该示例中,root.state 用于在播放序列时隐藏用户界面的部分。
property int _imageIndex: -1
function startPlayback()
{
root.state = "playing";
setImageIndex(0);
playTimer.start();
}
function setImageIndex(i)
{
_imageIndex = i;
if (_imageIndex >= 0 && _imageIndex < imagePaths.count)
image.source = imagePaths.get(_imageIndex).path;
else
image.source = "";
}
Timer {
id: playTimer
interval: 200
repeat: false
onTriggered: {
if (_imageIndex + 1 < imagePaths.count)
{
setImageIndex(_imageIndex + 1);
playTimer.start();
}
else
{
setImageIndex(-1);
root.state = "";
}
}
}
10.5 实用技巧
10.5.1 实现播放列表
Qt 5 多媒体 API 不支持播放列表。幸运的是,建立一个却很容易。这个想法是能够使用一个项目模型对一个 MediaPlayer 元素进行设置,如下所示。播放列表元素可用于设置 MediaPlayer 的源(source),而播放状态通过播放器进行控制。
MediaPlayer {
id: player
playlist: Playlist {
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
}
}
播放列表(Playlist)元素的上半部分,如下所示,负责在 setIndex 函数中设置给定索引的源(source)元素。它还实现了 next 和 previous 的功能来导航列表。
Item {
id: root
property int index: 0
property MediaPlayer mediaPlayer
property ListModel items: ListModel {}
function setIndex(i) {
console.log("setting index to: " + i);
index = i;
if (index < 0 || index >= items.count) {
index = -1;
mediaPlayer.source = "";
} else {
mediaPlayer.source = items.get(index).source;
}
}
function next() {
setIndex(index + 1);
}
function previous() {
setIndex(index + 1);
}
使播放列表继续到每个元素末尾的下一个元素的技巧是监视 MediaPlayer 的状态(status)属性。达到 MediaPlayer.EndOfMedia 状态后,索引增加并恢复播放,或者如果达到列表的结尾,播放停止。
Connections {
target: root.mediaPlayer
onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia) {
root.next();
if (root.index == -1) {
root.mediaPlayer.stop();
} else {
root.mediaPlayer.play();
}
}
}
}
10.6 总结
Qt 提供的多媒体 API 提供了播放和捕获视频和音频的机制。通过 VideoOutput 元素,视频源可以显示在用户界面中。通过 MediaPlayer 元素,可以操作大多数的播放,SoundEffect 可以用于低延迟的声音播放。为了捕获图像或显示实时视频流,可以使用 Camera 元素。
本章完,欢迎提出建议和指正翻译问题。