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 元素。
本章完,歡迎提出建議和指正翻譯問題。