QML Book 第十章 多媒體

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)單的幻燈片。

camera-ui

相機(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 元素。

本章完,歡迎提出建議和指正翻譯問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容