1. 程式人生 > >利用Qt Phonon框架製作音視訊播放器

利用Qt Phonon框架製作音視訊播放器

     Phonon嚴格來說其實非為Qt的library,Phonon原本就是KDE 4的開放原始碼多媒體API,後來與Qt合併與開發,所以簡單來說就是Qt使用Phonon這個多媒體框架來提供一般影音多媒體檔案的播放,而這些影音多媒體來源可以是檔案、網路串流或是指到一個檔案的QUrl。Phonon是一個跨平臺多媒體框架,能夠在Qt應用程式中使用與播放影音多媒體內容。

Phonon的架構

整體來說,Phonon的架構只需要記住以下的三東西:

  • media object
    Phonon的基礎,用於管理多媒體來源。來源可能是影音檔等,而能夠提供基本的播放控制,例如開始、暫停或結束。而提供多媒體資料給media object的則為media source,在給media object之前通常是raw data,再由media object進行轉換。
  • sinks
    輸出多媒體,例如在widget上播放影片或是輸出至音效卡(播放音樂)。通常sink是一個播放的裝置(例如音效卡等)。而sink只接受media object來的資料,由media object控制播放;而由sink來處理這些多媒體
  • paths
    用來連線Phonon的物件,意即media object與sink之間的連線。
    所以整個播放影音的流程就是首先由media object開始播放,接著把媒體串流經由path送至sink,sink會經由音效卡等裝置重新播放(play back)影音。

安裝

QtSoftware官方網站是說Phonon預設會跟在安裝Qt時一併安裝,不過我不論是從Qt SDK或從source code重新build,都沒有包含Phonon模組,目前我是用另外一種方式來安裝Phonon,就是使用套件管理工具來從套件包來安裝,只需要安裝下 列的套件:

sudo apt-get install libphonon-dev libphonon4 phononbackend-gstreamer

安裝完畢之後,就可以使用Phono模組羅。


而與其他Qt應用程式一樣,若有使用到Phonon函式庫的應用程式在build的時候都需要額外設定使用Phonon模組,必須要在qmake project file也就是.pro檔案中加入:

QT += phonon
VideoPlayer Class
Phonon有提供很多類別可供使用,其中最簡單的,莫過於VideoPlayer這個類別了。
VideoPlayer widget如它的名字一樣,就是用來播放video,而且使用起來相當簡易,而且功能也不會缺少,包括播放、暫停與停止。
而一開始早先提到的MediaObject等類別與VideoPlayer在使用上有什麼差異呢?如果不需要更復雜的功能,例如建立一個media graph,你只需要能夠播放影音檔案的話,其實使用VideoPlayer類別即可達到你的要求。
而另外值得一提的就是,VideoPlayer大部分函式都是非同步,所以載入media source並不會馬上播放多媒體檔案,只有在呼叫函式play( )之後才會播放。
要怎麼使用這個類別呢? 其實相當簡單,下面就是程式碼片段:
VideoPlayer *player = new VideoPlayer(Phonon::VideoCategory, parentWidget);
player->play(url);
實體化VideoPlayer類別物件時,可以在constructor就載入多媒體種類與要放在哪個widget中(即為 parentWidget),而media source可以利用函式load()來載入或是在play()時載入,而載入方式可直接從檔案或是從網路位址。
以下就是一個簡單的影音播放功能小程式:
#include <QApplication>
#include <QWidget>
#include <phonon>
#include <QUrl>
 
int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
 
    QWidget *widget = new QWidget;
    widget->setWindowTitle("Video Player");
    widget->resize(400,400);
 
    Phonon::VideoPlayer *player = new Phonon::VideoPlayer(Phonon::VideoCategory, widget); 
    player->load(Phonon::MediaSource("../Puppet.mpg"));//貌似只支援mpg格式
 
    player->play();
 
    widget->show();
 
    return app.exec();
}
MediaObject Class
MediaObject類別主要提供一個能夠處理媒體播放的介面。
MediaObject可說是處理多媒體檔案最基本的一部份,它接受並管理來自於MediaSource的媒體檔案。而媒體播放、暫停與停止都是由它來控制;而在此之前,media物件必須要與output node連線,如早先所講的,這個nodes主要將媒體輸出至底層的硬體,例如音效卡或顯示卡等,而所需要的output node則是根據多媒體的內容而所不同,目前Phonon有兩種output node;
  • AudioOutput-聲音播放
  • VideoWidget-影像播放
如果MediaSource包含聲音與影像的話,這兩種node都必須要連線至media物件。
就這個類別來說,有幾個函式是你必須要知道的,分別是
  • setCurrentSource():設定MediaObject的多媒體來源,而來源可以是網路上的影音檔(利用QUrl來存取)或是本機檔案(利用QString),使用上相當簡單:

          QUrl url("http://www.example.com/music.ogg");
          media->setCurrentSource(url);

  • play():開始播放多媒體資料
  • pause():暫停播放
  • stop():停止播放
以下為一個簡單的程式片段,說明如何使用:
Phonon::MediaObject *mediaObject = new Phonon::MediaObject(this);
 
Phonon::VideoWidget *videoWidget = new Phonon::VideoWidget(this);
Phonon::createPath(mediaObject, videoWidget);
 
Phonon::AudioOutput *audioOutput =
         new Phonon::AudioOutput(Phonon::VideoCategory, this);
Phonon::createPath(mediaObject, audioOutput);
 
mediaObject->play();
Phonon::createPath()

這是相當重要的一個函式,主要用於建立一個Path,連線兩個MediaNodes,就是source與sink。

其實它的主要用途是在更進階的部分,就是在使用到media graph,不過如果只是利用Phonon來播放多媒體影音檔,其實只要記得它是用來連線source與輸出裝置即可。

AudioOutput Class

AudioOutput類別主要是用來把多媒體的聲音送到聲音輸出裝置。所以它能夠經由類似喇叭等輸出裝置來播放聲音,稍早有提過,多媒體資料的來源必須要經過Phonon::createPath()由MediaObject連線。

Phonon::MediaObject *mediaObject = new Phonon::MediaObject(this);
mediaObject->setCurrentSource(Phonon::MediaSource("/mymusic/barbiegirl.wav"));
Phonon::AudioOutput *audioOutput =new Phonon::AudioOutput(Phonon::MusicCategory, this);
Phonon::Path path = Phonon::createPath(mediaObject, audioOutput);

編碼:mainwindow.h

我們已經為我們的應用程式建立了框架,現在只要新增功能即可。點選“mainwindow.h”,並在頂部新增如下程式碼行:
<div class="code">#include <QList>
#include <QFileDialog>
#include <QDesktopServices>
#include <Phonon></div>

以上程式碼的作用是,通過標頭檔案匯入我們要在程式碼中使用的Qt函式。現在我們需要新增我們的槽,它們在我們前面編輯過的ui檔案中已經定義好了。在“public:”部分中的“~MainWindow();”行正下方,新增如下程式碼:

private slots:
    void playPause();
    void addFiles();
    void nextFile();
    void aboutToFinish();
    void finished();

可以看到,這些槽對應於我們在使用者介面中建立的名稱,而且我們還添加了更多的槽來處理內部通訊。最後,在“private”部分中新增如下變數,我們將在應用程式的主要邏輯中用到這些變數:

    QList<Phonon::MediaSource> sources;
    Phonon::MediaObject *mediaObject;
    Phonon::AudioOutput *audioOutput;
    Phonon::MediaObject *metaInformationResolver;

編碼:mainwindow.cpp

現在,我們需要做的第一件事情是初始化Phonon,並建立內部的訊號和槽。這可以通過標準的初始化方法MainWindow::MainWindow來完成。如果你看一看這個方法的內容,就會發現應用程式的GUI是由“ui->setupUi(this);”行來執行的。(注意千萬不要在setupUI之前來呼叫UI上面的元件,這時候根本還沒建立)這意味著,我們需要在這之前加入我們的預執行程式碼。我們將從設定Phonon開始:

     audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
     mediaObject = new Phonon::MediaObject(this);
     metaInformationResolver = new Phonon::MediaObject(this);
     Phonon::createPath(mediaObject, audioOutput);

在Phonon術語中,我們要建立的audioOutput物件叫做音訊接收槽。它是直接與音訊驅動器通訊的層的組成部分,並充當MediaObject的虛擬音訊裝置。MediaObject位於這一層的上層,增加了諸如暫停、播放和倒帶之類的功能。順便提一句,MusicCategory不一定是必需的,但它可以對未來發展起到作用,比如可以根據正在收聽的內容自動變化的KDE均衡器

我們將使用“metaInformationResolver”來指向當前音訊檔案,而最後一行在接收槽和媒體物件之間建立了連線。Phonon使用了一種叫做“graph”的框架,這意味著物件就像是一幅圖上的節點,需要連線起來才能建立流向。這正是以上程式碼的最後一行的作用。

現在新增如下用於處理playPause()槽的新函式:

void MainWindow::playPause()
{
    switch (mediaObject->state()){
        case Phonon::PlayingState:
            mediaObject->pause();
            ui->pushButtonPlay->setChecked(false);
            break;
        case Phonon::PausedState:
            mediaObject->play();
            break;
        case Phonon::StoppedState:
            mediaObject->play();
            break;
        case Phonon::LoadingState:
            ui->pushButtonPlay->setChecked(false);
            break;
    }
}

以上程式碼應該不難理解。我們使用一條Case語句檢查播放的當前狀態。Phonon通過MediaObject為我們提供了這種功能。它實際上就是一個整數,但是每個值代表著特定的狀態。例如,如果應用程式是首次啟動,播放列表中尚未加入任何檔案,就會返回LoadingState值。播放和暫停由媒體物件進行處理,在“mediaObject->pause();”命令之後將自動從當前位置繼續。

void MainWindow::addFiles()
{
    QStringList files = QFileDialog::getOpenFileNames(this, tr("Select Music Files"),
        QDesktopServices::storageLocation(QDesktopServices::MusicLocation));
 
    ui->pushButtonPlay->setChecked(false);
    if (files.isEmpty())
        return;
    int index = sources.size();
    foreach (QString string, files) {
            Phonon::MediaSource source(string);
         sources.append(source);
    }
    if (!sources.isEmpty()){
        metaInformationResolver->setCurrentSource(sources.at(index));
        mediaObject->setCurrentSource(metaInformationResolver->currentSource());
 
    }
}

現在,讓我們開始實現新增檔案的部分。建立檔案請求器幾乎是自動的,我們可以把結果放到一個字串列表中。“QdesktopServices::MusicLocation”返回一個通常用於儲存音樂檔案的特定作業系統位置,而我們使用它作為請求器的起始位置。接下來,我們將把已經選擇的每個音樂檔案新增到我們早先建立的“metaInformationResolver”中,並使用它告訴mediaObject接下來播放哪個檔案。在所有這些工作間隙,我們進行一點清理工作,以確保佇列中有檔案,而且當處於播放狀態時出現播放按鈕。新增檔案將自動停止播放。

void MainWindow::nextFile()

{    

      int index = sources.indexOf(mediaObject->currentSource()) + 1;    

      if (sources.size() > index)

     {        

           mediaObject->stop();        

           mediaObject->setCurrentSource(sources.at(index));        

           mediaObject->play();    

     }

}

上面在音樂播放時進度條和音量控制條的顯示也是在Phonon中有自己的元件來顯示,只要將當前的mediaObject賦給它們就行啦。 在標頭檔案中:    

      Phonon::SeekSlider *seekSlider;//實現進度條

SeekSlider類別提供一個可滑動的slider來設定多媒體串流播放的位置。所以它會連線到MediaObject,並控制串流目前的位置。以下是一個使用的範例:

Phonon::MediaObject *moo = new Phonon::MediaObject;;

Phonon::AudioOutput *device = new Phonon::AudioOutput;

Phonon::createPath(moo, device);

moo->setCurrentSource(QString("/home/gvatteka/Music/Lumme-Badloop.ogg"));

Phonon::SeekSlider *slider = new Phonon::SeekSlider;

slider->setMediaObject(moo);

slider->show();

moo->play();


VolumeSlider Class

VolumeSlider widget提供可以控制聲音裝置音量的widget。用法其實與上面的SeekSlider類似,使用範例如下:honon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory);

Phonon::createPath(mediaObject, audioOutput);

Phonon::VolumeSlider *volumeSlider = new Phonon::VolumeSlider;

volumeSlider->setAudioOutput(audioOutput);

範例 
再看過上面的一些類別介紹之後,其實就可以顯一個簡單的媒體播放器了,請看範例程式碼: 
#include <QApplication>

 #include <QWidget>

 #include <phonon>

 #include <QUrl>

 #include <QObject>

 #include <QVBoxLayout> 

#include <QHBoxLayout>

 #include <QLabel>   
int main(int argc, char *argv[])

 {   

  QApplication app(argc, argv);   
    QWidget *widget = new QWidget; 
    widget->setWindowTitle("Media Player");    

 widget->resize(400,400);   
    Phonon::MediaObject *media = new Phonon::MediaObject; 
    media->setCurrentSource(Phonon::MediaSource("../Puppet.mpg"));   
    Phonon::VideoWidget *vwidget = new Phonon::VideoWidget(widget);    

 Phonon::createPath(media, vwidget); 
 vwidget->setAspectRatio(Phonon::VideoWidget::AspectRatioAuto);    

 Phonon::AudioOutput *aOutput = new 
 Phonon::AudioOutput(Phonon::VideoCategory);    

 Phonon::createPath(media, aOutput);   
 QLabel *label = new QLabel("Volume: "); 
 Phonon::VolumeSlider *volumeSlider = new Phonon::VolumeSlider;    

 volumeSlider->setAudioOutput(aOutput); 
 volumeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);   
 Phonon::SeekSlider *seekSlider = new Phonon::SeekSlider;    

 seekSlider->setMediaObject(media);   
 QHBoxLayout *hLayout = new QHBoxLayout;    

 hLayout->addWidget(label); 
 hLayout->addWidget(volumeSlider);    

 hLayout->addStretch();   
 QVBoxLayout *vLayout = new QVBoxLayout;   

 vLayout->addWidget(vwidget);     

 vLayout->addWidget(seekSlider);   

 vLayout->addLayout(hLayout);   
 widget->setLayout(vLayout);   
 widget->show();     

 media->play();   

 return app.exec(); 


上面的程式碼就完成了一個簡易的多媒體播放器,當然還有很多部分還需要改進,不過在此僅就Phonon的使用上做介紹。執行之後的畫面如下所示: 由於VideoWidget已經嵌入到QWidget了,所以你調整視窗大小的話,影片播放的視窗也會跟著調整。

VideoWidget類別提供能夠顯示出影片的widget。VideoWidget類別會在QWidget上播放多媒體串流的影像,跟AudioOutput一樣,必須使用 Phonon::createPath()來與MediaObject連線。你可以利用一些函式來控制在QWidget中的VideoWidget顯示的 大小,你可以利用setAspectRatio()或setScaleMode()來控制,而它們接收的引數可以到網站上察看,使用方式如下(預設是使用 aspect ratio):

videowidget->setAspectRatio(Phonon::VideoWidget::AspectRatioAuto);

videowidget->setScaleMode(Phonon::VideoWidget::ScaleAndCrop);
當然也有提供函式讓影片進入或退出全螢幕模式。以下為一個簡短的程式碼範例:
MediaObject *media = new MediaObject(parent);

VideoWidget *vwidget = new VideoWidget(parent);

Phonon::createPath(media, vwidget);

補充:

   多媒體檔案的播放主要過程包括:檔案讀取、分流、解碼、輸出。


    這些在不同的系統中實現方式不同,如windows下的DirectXLinux下的 gstreamer或xine及Mac下的QuickTime。 而Qt中的phonon作為誇平臺的多媒體解決方案,就因該為使用者遮蔽掉這些差異。而實際上它做得還不夠好,因為使用者還需要自己來安裝相應的後端外掛來完成播放任務。


    看完上圖,你可以知道,應用Phonon框架實質上分4個部分,你的程式,Phonon庫,Phonon後端外掛(phonon_backend),真正的後端。 Phonon其實什麼都不幹,他只是提供了一套API介面這套介面可以給你的程式呼叫,同時也是給編寫後端外掛提供一個規範。程式完全不知道最後誰來放我的Mp3,誰來解碼我的視訊,播放的又是哪個裝置。
    同樣,Phonon庫也不知道,他只管搜尋符合自己規範的外掛。並告訴這些外掛,現在程式發出的指令是什麼,從外掛返回給程式現在的媒體狀態和資訊。而後端才是實際進行讀入媒體、解碼並且播放的部分,他們和後端外掛是一一對應的。後端可以是任何形式,只要你寫出了相應的後端外掛。所以,你要使用Phonon必需要先做3件事:
(1) 編譯你的Phonon庫:通常情況下,Qt預設沒有編譯,你只需要configure後加好引數,然後單獨進入phonon的資料夾進行編譯就可以了 
(2) 編譯你的Phonon後端外掛:Qt為3大平臺分別提供了一個可用的後端外掛,放在src\plugins\phonon目錄下。進入目錄編譯即可,Qt會自動選擇你當前系統的後端外掛的。(出現phonon backend plugin could not be loaded就是這步沒有做) 
     如果你是MinGW使用者,那抱歉了,Windows下Qt只提供了這個後端外掛,而這個後端外掛因為呼叫了DX的SDK,所以只能由VC編譯。當然你其實有更好的選擇,比如這個phonon-vlc-mplayer外掛,通過他,你可以將mplayer作為你的後端,徹底拋棄臃腫的VS以及讀ID3v2都會出錯的DirectShow(其實也不能怪DirectShow,DirectShow同樣只是個框架,解碼還靠系統裡安的解碼器、濾鏡們,我用的播放器都太綠色了,於是DirectShow就很弱) 
(3) 你要保證你的後端正常運作:對於Qt提供預設提供外掛的後端來說,基本不成問題。但要是你想做嵌入式播放器?恩,很大的問題。據做過GStreamer移植的人反映,這事惱火的很。想做Qt嵌入式播放器的還是老老實實的QProcess+Mplayer slave模式吧!(新的Qt4.5說是提供了CE上的DirectShow後端外掛,有興趣的可以去試試看) 
    以上3點做完,你就可以拿demo裡那個mediaplayer去測試了,測試成功的話,你就可以非常容易的用Qt實現你自己的媒體回放了。想要更高階的操作,比如編碼、混音、儲存媒體檔案?呵呵,等Phonon慢慢更新吧,現在來說,Phonon還只是個什麼都不能幹的傳聲筒

   如上所述,如果想要執行使用Qt中的phonon寫的程式,需要滿足一下條件:Qt基本庫、Qt phonon庫、phonon_backend(後端外掛)和多媒體播放後臺。