10.多媒體(Multimedia)
本章的作者:e8johan
** 注意: **
最新的構(gòu)建時(shí)間:2016/03/21
這章的源代碼能夠在assetts folder找到。
Qt 多媒體模塊中的多媒體元素使得可以播放和記錄諸如聲音、視頻或圖片的媒體。解碼和編碼通過平臺(tái)特定的后端來處理。例如,在 Linux 平臺(tái)上使用流行的 gstreamer 框架,而在 Windows 上使用 DirectShow,在 OS X 上使用 QuickTime。
多媒體元素不是 Qt Quick core API 的一部分。相反,它們需要通過導(dǎo)入 QtMultimedia 5.6 提供的單獨(dú)的 API 提供,如下所示:
import QtMultimedia 5.6
10.1 播放媒體
QML 應(yīng)用程序中最基本的多媒體集成是用于播放媒體。這可以使用 MediaPlayer 元素完成,如果源是圖像或視頻,則可以與 VideoOutput 元素組合使用。MediaPlayer 元素具有指向要播放的媒體的源(source)屬性。媒體源被綁定后,只需調(diào)用播放(play)功能即可開始播放。
如果要播放視頻媒體,即圖片或視頻,我們還必須設(shè)置一個(gè) VideoOutput 元素。運(yùn)行播放的 MediaPlayer 將通過 source 屬性綁定到視頻輸出。
在下面的示例中,MediaPlayer 被給予一個(gè)視頻內(nèi)容作為源的文件。VideoOutput 被創(chuàng)建并綁定到媒體播放器。一旦主要組件被完全初始化,在 Component.onCompleted 中,播放器的播放功能會(huì)被調(diào)用。
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 屬性來控制播放媒體時(shí)改變音量的基本操作。還有其他有用的屬性。例如,持續(xù)時(shí)間(duration)和位置(position)屬性可用于構(gòu)建進(jìn)度條。如果可定位(seekable)屬性為真(true),甚至可以在進(jìn)度條被輕敲時(shí)更新位置(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;
}
}
}
}
在默認(rèn)情況下,position 屬性只能每秒更新一次。這意味著進(jìn)度條將以較大的步驟進(jìn)行更新,這樣看起來播放進(jìn)度條是一跳一跳的,除非與進(jìn)度條的寬度相對(duì)應(yīng)的像素?cái)?shù)相比,媒體的持續(xù)時(shí)間足夠長(zhǎng)。這時(shí),可以通過訪問 mediaObject 屬性及其 notifyInterval 屬性來更改更新間隔。它可以設(shè)置為每個(gè)位置更新之間的毫秒數(shù),我們可以根據(jù)媒體的實(shí)際時(shí)間來調(diào)整更新的時(shí)長(zhǎng),以此增加用戶界面的平滑度。
Connections {
target: player
onMediaObjectChanged: {
if (player.mediaObject) {
player.mediaObject.notifyInterval = 50;
}
}
}
當(dāng)使用 MediaPlayer 構(gòu)建媒體播放器時(shí),監(jiān)視播放器的狀態(tài)(status)屬性是很好的選擇。它是對(duì)可能的狀態(tài)的枚舉,范圍從 MediaPlayer.Buffered 到 MediaPlayer.InvalidMedia。以下項(xiàng)目中總結(jié)了可能的值:
- MediaPlayer.UnknownStatus —— 狀態(tài)未知。
- MediaPlayer.NoMedia —— 播放器沒有分配媒體源。播放停止。
- MediaPlayer.Loading —— 播放器正在加載媒體。
- MediaPlayer.Loaded —— 媒體已經(jīng)加載。播放停止。
- MediaPlayer.Stalled —— 媒體的裝載停滯了。
- MediaPlayer.Buffering —— 正在緩沖媒體。
- MediaPlayer.Buffered —— 媒體被緩沖,這意味著用戶可以開始播放媒體。
- MediaPlayer.EndOfMedia —— 媒體已經(jīng)結(jié)束。播放停止。
- MediaPlayer.InvalidMedia —— 媒體無法播放。播放停止。
如上述枚舉值所述,播放狀態(tài)可隨時(shí)間而變化。調(diào)用 play、pause 或 stop 更改狀態(tài),但有問題的媒體也可以有效果。例如,可以到達(dá)結(jié)束,否則可能無效,導(dǎo)致播放停止。當(dāng)前播放狀態(tài)可以通過 playbackState 屬性進(jìn)行跟蹤。值可以是 MediaPlayer.PlayingState、MediaPlayer.PausedState 或 MediaPlayer.StoppedState。
使用 autoPlay 屬性,MediaPlayer 可以在 source 屬性更改后嘗試進(jìn)入播放狀態(tài)。類似的屬性是 autoLoad,導(dǎo)致播放器在 source 屬性更改后立即嘗試加載媒體。后一個(gè)屬性默認(rèn)是啟用的。
也可以讓 MediaPlayer 循環(huán)播放媒體項(xiàng)目。循環(huán)(loops)屬性控制源播放的次數(shù)。將屬性設(shè)置為 MediaPlayer.Infinite 會(huì)設(shè)置為無限循環(huán)。這對(duì)連續(xù)動(dòng)畫或循環(huán)的背景歌曲的播放是很有效的。
10.2 聲音特效
當(dāng)播放聲音效果時(shí),從請(qǐng)求播放到實(shí)際播放的響應(yīng)時(shí)間變得很重要。在這種情況下,SoundEffect元素派上用場(chǎng)。通過設(shè)置源(source)屬性,對(duì)播放(play)功能的簡(jiǎn)單調(diào)用立即開始播放。
這可以用于在點(diǎn)擊屏幕時(shí)進(jìn)行音頻反饋,如下所示:
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()
}
}
該元素也可以用于伴隨音頻的轉(zhuǎn)換。要從轉(zhuǎn)換觸發(fā)播放,將使用 ScriptAction 元素。
SoundEffect {
id: swosh
source: "swosh.wav"
}
transitions: [
Transition {
ParallelAnimation {
ScriptAction { script: swosh.play(); }
PropertyAnimation { properties: "rotation"; duration: 200; }
}
}
]
除了 play 功能之外,還有許多類似的 MediaPlayer 提供的屬性。示例是 volume 和 loops。后者可以設(shè)置為 SoundEffect.Infinite 進(jìn)行無限播放。要停止播放,請(qǐng)調(diào)用 stop 功能。
** 注意: **
當(dāng)使用 PulseAudio 后端時(shí),stop 不會(huì)立即停止,但只能防止進(jìn)一步的循環(huán)。這是由于底層 API 的限制。
10.3 視頻流
VideoOutput 元素不限于與 MediaPlayer 元素組合使用。它也可以直接與視頻源一起使用來顯示直播視頻流。使用 Camera 元素作為源,并且應(yīng)用程序已完成。來自相機(jī)的視頻流可以用于向用戶提供直播流。此流在捕獲照片時(shí)用作搜索視圖。
import QtQuick 2.5
import QtMultimedia 5.6
Item {
width: 1024
height: 600
VideoOutput {
anchors.fill: parent
source: camera
}
Camera {
id: camera
}
}
10.4 捕獲圖像
相機(jī)(Camera)元素的主要功能之一是可用于拍攝照片。我們將在簡(jiǎn)單的停止運(yùn)動(dòng)應(yīng)用程序中使用它。在其中,我們將學(xué)習(xí)如何顯示取景器,拍攝照片和跟蹤拍攝的照片。
用戶界面如下所示。它由三個(gè)主要部分組成。在后臺(tái),我們將在右側(cè)找到一個(gè)取景器,一列按鈕,底部顯示拍攝的圖像列表。想法是拍攝一系列照片,然后點(diǎn)擊順序播放按鈕。這將播放圖像,創(chuàng)建一個(gè)簡(jiǎn)單的幻燈片。

相機(jī)的取景器部分只是一個(gè) Camera 元素,用作 VideoOutput 中的源。這將向用戶顯示相機(jī)的直播視頻流。
VideoOutput {
anchors.fill: parent
source: camera
}
Camera {
id: camera
}
照片列表是一個(gè) 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 元素包含一組用于各種任務(wù)的子元素。要捕獲靜態(tài)圖片,使用 Camera.imageCapture 元素。當(dāng)我們調(diào)用捕獲(capture)方法時(shí),將拍攝照片。這將導(dǎo)致 Camera.imageCapture 首先發(fā)出 imageCaptured 信號(hào),后跟 imageSaved 信號(hào)。
Button {
id: shotButton
text: "Take Photo"
onClicked: {
camera.imageCapture.capture();
}
}
為了攔截子元素的信號(hào),需要一個(gè) Connections 元素。在這種情況下,我們不需要顯示預(yù)覽圖像,而只是將生成的圖像添加到屏幕底部的 ListView。 如下面的示例所示,保存的圖像的路徑作為帶有信號(hào)的路徑(path)參數(shù)提供。
Connections {
target: camera.imageCapture
onImageSaved: {
imagePaths.append({"path": path})
listView.positionViewAtEnd();
}
}
要顯示預(yù)覽,請(qǐng)連接到 imageCaptured 信號(hào),并使用預(yù)覽(preview)信號(hào)參數(shù)作為 Image 元素的源(source )。 requestId 信號(hào)參數(shù)同時(shí)發(fā)送 imageCaptured 和 imageSaved。該值從捕獲(capture)方法返回。使用它,捕獲圖像可以在整個(gè)循環(huán)中進(jìn)行跟蹤。這樣,預(yù)覽可以先使用,然后被正確保存的圖像替換。但是,這并不是我們?cè)谶@個(gè)例子中所做的。
應(yīng)用程序的最后一部分是實(shí)際播放。這是使用 Timer 元素和一些 JavaScript 驅(qū)動(dòng)的。 _imageIndex 變量用于跟蹤當(dāng)前顯示的圖像。當(dāng)顯示最后一張圖像時(shí),播放停止。在該示例中,root.state 用于在播放序列時(shí)隱藏用戶界面的部分。
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 實(shí)用技巧
10.5.1 實(shí)現(xiàn)播放列表
Qt 5 多媒體 API 不支持播放列表。幸運(yùn)的是,建立一個(gè)卻很容易。這個(gè)想法是能夠使用一個(gè)項(xiàng)目模型對(duì)一個(gè) MediaPlayer 元素進(jìn)行設(shè)置,如下所示。播放列表元素可用于設(shè)置 MediaPlayer 的源(source),而播放狀態(tài)通過播放器進(jìn)行控制。
MediaPlayer {
id: player
playlist: Playlist {
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
PlaylistItem { source: "trailer_400p.ogg" }
}
}
播放列表(Playlist)元素的上半部分,如下所示,負(fù)責(zé)在 setIndex 函數(shù)中設(shè)置給定索引的源(source)元素。它還實(shí)現(xiàn)了 next 和 previous 的功能來導(dǎo)航列表。
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);
}
使播放列表繼續(xù)到每個(gè)元素末尾的下一個(gè)元素的技巧是監(jiān)視 MediaPlayer 的狀態(tài)(status)屬性。達(dá)到 MediaPlayer.EndOfMedia 狀態(tài)后,索引增加并恢復(fù)播放,或者如果達(dá)到列表的結(jié)尾,播放停止。
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 總結(jié)
Qt 提供的多媒體 API 提供了播放和捕獲視頻和音頻的機(jī)制。通過 VideoOutput 元素,視頻源可以顯示在用戶界面中。通過 MediaPlayer 元素,可以操作大多數(shù)的播放,SoundEffect 可以用于低延遲的聲音播放。為了捕獲圖像或顯示實(shí)時(shí)視頻流,可以使用 Camera 元素。
本章完,歡迎提出建議和指正翻譯問題。