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)元素的主要功能之一是可用于拍摄照片。我们将在简单的停止运动应用程序中使用它。在其中,我们将学习如何显示取景器,拍摄照片和跟踪拍摄的照片。

用户界面如下所示。它由三个主要部分组成。在后台,我们将在右侧找到一个取景器,一列按钮,底部显示拍摄的图像列表。想法是拍摄一系列照片,然后点击顺序播放按钮。这将播放图像,创建一个简单的幻灯片。

QML Book 第十章 多媒体_第1张图片
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 元素。

本章完,欢迎提出建议和指正翻译问题。

你可能感兴趣的:(QML Book 第十章 多媒体)