QML Book 第十章 多媒體

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-ui

相機的取景器部分只是一個 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 元素。

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容