在前面几篇文章中详细的介绍了如何利用QT自身提供的Phonon模块来进行音视频的播放和控制,这种方式是QT内部提供的音视频解码器,所以所支持的文件格式不多,而且对音视频的控制方式也是非常有限,所以在这里还是采用Mplayer作后台,QT作前端界面的方式来构建我们的播放器。
1.MPlayer 的特殊控制模式
MPlayer 本身是一个命令行的程序,但是他提供了有后台模式(Slave),可以把自己输出重定向到一个指定的X Windows窗口中,并且可以通过管道向其发送命令来控制,这样就为GUI程序提供一个简单的编程接口.让其用图形界面来控制.Gnome下的gmplayer其实就是通过这个方法来在图形界面调用MPlayer.
1.1 通过管道播放视频
- mkfifo /tmp/media // 建立管道文件
- cat xxx.mpeg > /tmp/media & // 把媒体文件数据写入管道一端
- mplayer /tmp/media –cache 3000 // mplayer在缓充3M数据后开始播放
注意必须建立缓充,以支持读取数据时的小范围索引
本例里没有用这个功能,但是很多LINUX的网络播放器也是用这个方法来这现的.有兴趣可以参考一下gtk-sopcast(http://code.google.com/p/gsopcast/)这个开源项目,这个软件相信很多人用它的WINDOWS版本看过球.
1.2 slave模式.
slave 播放模式,可以MPlayer在后台执行,并可以通专用的命令管道发送命令来控制MPlayer.
- mkfifo /tmp/cmd
- mplayer -slave -quiet -input file=/tmp/cmd xxx.mpeg &
- echo "pause" > /tmp/cmd
常用的控制命令可以用如下命令来查看
mplayer –input cmdlist
编程常用到几个命令
- seek Float [Integer] 跳到指定时间播放
- speed_incr Float 快进
- speed_set Float 设置指定时间
- quit [Integer] 退出
- pause 暂停,再一次表示重新播放
- get_percent_pos 取得百分比表示的进度
- get_time_pos 取得时间进度
1.3 在指定的窗口播放Mplayer
MPlayer是默认全屏播放,但在GUI中一般都是指定区域或者控制来播放.这样显得美观,MPlayer支持在指定的X窗口播放的功能.用-wid参数告诉窗口ID即可.
mplayer test.mpg -wid 0x00029 #0x0029是X ID
在对于正在运行图形界面窗口,可以用Linux 命令xwininfo来取到其ID,方法是运行这个程序后.屏幕鼠标变成+,将其拖到指定窗口即可看到其结果。
xwininfo 是X-Windows工具,用来查看已经打开窗口的X-Windows信息。
QT的控件都是从QWidget继承下来,因此它本身也有wid,这样只要用 QWidget::winID() 取出WID,即可实现在指定窗口播放视频的功能。
2.QT控制MPlayer分析
2.1 QProcess 简要说明
LINUX C用system 执行外部程序
QT是用QProcess(进程类)来执行。创建一个进程后。用start方法来执行.
//执行不带参数的外部命令。
void start ( const QString & program, OpenMode mode = ReadWrite )
//执行带参数
void start ( const QString & program, const QStringList & arguments, OpenMode mode = ReadWrite )
其中arguments就是参数列表。QStringList重定向了 <<,增加参数用这个重载的操作符。
QStringList arg ;
arg << "-wid"<< "0x320001f" <<" -queit" ;
QProcess 用write来实现向标准输入发送信息
readLine/read来读取信息用于分析。
IO重定向使用signal readyReadStandardOutput。
connect(mplayerProcess, SIGNAL(readyReadStandardOutput()),
this, SLOT(catchOutput()));
3.QT4下的实现
我们先通过分析网上一个比较经典的QT下调用Mplayer的例子,然后再来结合我们自己的在高清播放器中来构建播放器播放指定文件的实例学习。
#include <QApplication>
#include <QProcess>
#include <QVBoxLayout>
#include <QLayoutItem>
#include <QWidget>
#include <QPaintEvent>
#include <QPainter>
#include <QColor>
#include <QRect>
#include <QLinearGradient>
#include <QSizePolicy>
#include <QPushButton>
#include <QTextEdit>
#include <QSlider>
#include <QCloseEvent>
#include <QTimer>
#ifdef Q_OS_WIN32
const QString mplayerPath("
C:/Program
Files/MPlayer
for
Windows/mplayer/mplayer.exe");//在windows上来运行Mplayer必须先要下载Mplayer For Windows安装到机器上,然后找到安装包中的mplayer.exe,我自己的机器上对应的路径为:
C:/Program
Files/MPlayer
for
Windows/mplayer/mplayer.exe//
#else
const QString mplayerPath("/usr/bin/mplayer");//linux下的Mplayer的路径
#endif
const QString movieFile("2.avi");//最好还是通过QFileDialog来选择我们需要播放的文件路径,后面会讲到
class PlayerWidget: public QWidget
...{
Q_OBJECT
private:
QPushButton *controller;
QWidget *renderTarget;//利用一个QWidget对象来播放视频
QProcess *mplayerProcess; //main thing
bool isPlaying;
QSlider *timeLine;
QTimer *poller;
QTextEdit *log;
public:
PlayerWidget(QWidget *parent =0)
:QWidget(parent), isPlaying(false)
{
controller = new QPushButton("Play");
renderTarget = new QWidget(this);
renderTarget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
renderTarget->setAttribute(Qt::WA_OpaquePaintEvent );
renderTarget->setMinimumSize(500, 500);
timeLine = new QSlider(Qt::Horizontal);
log = new QTextEdit;
log->setReadOnly(true);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(controller);
layout->addWidget(renderTarget);
layout->addWidget(timeLine);
layout->addWidget(log);
setLayout(layout);
mplayerProcess = new QProcess(this);
poller = new QTimer(this);
connect(controller, SIGNAL(clicked()), this, SLOT(switchPlayState())); //播放与暂停之间的切换
connect(mplayerProcess, SIGNAL(readyReadStandardOutput()),
this, SLOT(catchOutput())); //当进程的标准输出上有数据可以读时,QProcess将会发出这个消息,这个消息也就是当我们给Mplayer发送命令后,Mplayer作出相应,将视频信息输出到标准输出上来,这样我们就可以在 catchOutput函数中来解析
connect(mplayerProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(mplayerEnded(int, QProcess::ExitStatus))); //当进程退出时会发出这个信号,包括了退出时的退出码和退出状态
connect(poller, SIGNAL(timeout()), this, SLOT(pollCurrentTime())); //每隔1S中定时器poller就会发出timeout信号,在响应函数中我们继续给Mplayer发送获取视频当前时间的命令get_time_pos
connect(timeLine, SIGNAL(sliderMoved(int)), this, SLOT(timeLineChanged(int))); //当我们拖动滑动条时,视频也应该跟着我们一起播放
}
protected:
virtual void closeEvent(QCloseEvent *e)
{
stopMPlayer();
e->accept();
}
private:
bool startMPlayer()
{
if(isPlaying)
return true;
QStringList args; //运行mplayer需要的参数
args << "-slave";
// Et on veut ne pas avoir trop de chose � parser :)
//And we want to not having too many things to parser:)
args << "-quiet";
#ifdef Q_WS_WIN
// reinterpret_cast<qlonglong> obligatoire, winId() ne se laissant pas convertir gentiment ;)
//reinterpret_cast <qlonglong> mandatory winId () not allowing convert nicely;)
args << "-wid" << QString::number(reinterpret_cast<qlonglong>(renderTarget->winId()));
args << "-vo" << "directx:noaccel";
#else
// Sur linux, aucun driver n'a �t� n�cessaire et pas de manip pour Wid :)
//On linux, no driver has been necessary and no manip for Wid:)
args << "-wid" << QString::number(renderTarget->winId());
log->append("Video output driver may not be necessary for your platform.
Check: http://www.mplayerhq.hu/DOCS/man/en/mplayer.1.html
at the VIDEO OUTPUT DRIVERS section.");
#endif
args << movieFile;
// On parse la stdout et stderr au m�me endroit, donc on demande � "fusionnner" les 2 flux
//parse the stdout and stderr in the same place, so we are asking "fusionnner"
mplayerProcess->setProcessChannelMode(QProcess::MergedChannels);
mplayerProcess->start(mplayerPath, args); //开始运行这个进程
if(!mplayerProcess->waitForStarted(100))
{
qDebug("allez, cherche le bug :o");
return false;
}
// On r�cup�re les infos de base
//retrieve basic information
mplayerProcess->write("get_video_resolution ");
mplayerProcess->write("get_time_length ");
poller->start(1000);
isPlaying = true;
return true;
}
bool stopMPlayer()
{
if(!isPlaying)
return true;
mplayerProcess->write("quit ");
if(!mplayerProcess->waitForFinished(100))
{
qDebug("ZOMG, �a plante :(");
return false;
}
return true;
}
private slots:
//响应readyReadStandardOutput消息
void catchOutput()
{
while(mplayerProcess->canReadLine())
{
QByteArray buffer(mplayerProcess->readLine());
log->append(QString(buffer));
// On v�rifie si on a eu des r�ponses
//It checks if we had answers
// r�ponse � get_video_resolution : ANS_VIDEO_RESOLUTION='<width> x <height>'
//response to get_video_resolution: ANS_VIDEO_RESOLUTION
if(buffer.startsWith("ANS_VIDEO_RESOLUTION"))
{
buffer.remove(0, 21); // vire ANS_VIDEO_RESOLUTION=
buffer.replace(QByteArray("'"), QByteArray(""));//将返回来的'322 x 255'中的单引号,空格和/n,/r都去掉
buffer.replace(QByteArray(" "), QByteArray(""));
buffer.replace(QByteArray("/n"), QByteArray(""));
buffer.replace(QByteArray("/r"), QByteArray(""));
int sepIndex = buffer.indexOf('x');
int resX = buffer.left(sepIndex).toInt();
int resY = buffer.mid(sepIndex+1).toInt();
renderTarget->setMinimumSize(resX, resY);
}
// r�ponse � get_time_length : ANS_LENGTH=xx.yy
//response to get_time_length: ANS_LENGTH =
else if(buffer.startsWith("ANS_LENGTH"))
...{
buffer.remove(0, 11); // vire ANS_LENGTH=
buffer.replace(QByteArray("'"), QByteArray(""));
buffer.replace(QByteArray(" "), QByteArray(""));
buffer.replace(QByteArray("/n"), QByteArray(""));
buffer.replace(QByteArray("/r"), QByteArray(""));
float maxTime = buffer.toFloat();
timeLine->setMaximum(static_cast<int>(maxTime+1));
}
// r�ponse � get_time_pos : ANS_TIME_POSITION=xx.y
//response to get_time_pos: ANS_TIME_POSITION = 2.4
else if(buffer.startsWith("ANS_TIME_POSITION"))
...{
buffer.remove(0, 18); // vire ANS_TIME_POSITION=
buffer.replace(QByteArray("'"), QByteArray(""));
buffer.replace(QByteArray(" "), QByteArray(""));
buffer.replace(QByteArray("/n"), QByteArray(""));
buffer.replace(QByteArray("/r"), QByteArray(""));
float currTime = buffer.toFloat();
timeLine->setValue(static_cast<int>(currTime+1));
}
//qApp->processEvents();
}
}
void pollCurrentTime()
{
mplayerProcess->write("get_time_pos ");
}
// Dirige la timeline
//Directs the timeline
void timeLineChanged(int pos)
{
mplayerProcess->write(QString("seek " + QString::number(pos) + " 2 ").toUtf8());
}
// Play/stop
void switchPlayState()
{
if(!isPlaying)
{
if(!startMPlayer())
return;
log->clear();
controller->setText("Stop");
isPlaying = true;
}
else
{
if(!stopMPlayer())
return;
poller->stop();
log->clear();
controller->setText("Play");
isPlaying = false;
}
}
void mplayerEnded(int exitCode, QProcess::ExitStatus exitStatus)
{
isPlaying = false;
controller->setText("Play");
poller->stop();
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
PlayerWidget *pw = new PlayerWidget;
pw->show();
return app.exec();
}
#include "main.moc" //I don't understand this.
接下来看看高清播放中播放器的构建
connect
(BtnaddFile
,
SIGNAL
(clicked
()),
this
,
SLOT
(addFile
()));
connect(Btnstop,SIGNAL(clicked()),this,SLOT(stop()));
connect(BtnplayAndPause,SIGNAL(clicked()),this,SLOT(playAndPause()));
connect(BtnSpeed,SIGNAL(clicked()),this,SLOT(Speed()));
connect(BtnLow,SIGNAL(clicked()),this,SLOT(Low()));
connect(BtnNext,SIGNAL(clicked()),this,SLOT(NextFile()));
connect(BtnPrevious,SIGNAL(clicked()),this,SLOT(PreviousFile()));
connect(BtnMute,SIGNAL(clicked()),this,SLOT(Mute()));
connect(fullScreen,SIGNAL(clicked()),this,SLOT(fullShow()));
connect(timer,SIGNAL(timeout()),this,SLOT(stepOne()));
connect(Slider,SIGNAL(sliderMoved(int)),this,SLOT(SliderChanged(int)));
connect(Vol,SIGNAL(valueChanged(int)),this,SLOT(setVol(int)));
connect(playerProcess,SIGNAL(readyReadStandardOutput()),this,SLOT(catchOutput()));
connect(playerProcess,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(mplayerEnded(int,QProcess::ExitStatus)));
void Mplayer::addFile(){
QString Path("C:/Program Files/MPlayer for Windows/mplayer/mplayer.exe");
filename = QFileDialog::getOpenFileName(this,"choose a file","C://","Video File(*.rmvb *.avi *.mpg *.rm)");
if(filename.isEmpty())
{
return;
}
//Player->resize(400,400);
//Player->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred));
//Player->show();
QStringList arguments;
arguments << "-slave";
arguments << "-quiet";
arguments << "-wid" <<QString::number(reinterpret_cast<qlonglong>(Player->winId()));
arguments << filename;
playerProcess->setProcessChannelMode(QProcess::MergedChannels);
playerProcess->start(Path,arguments,QIODevice::ReadWrite);
if(!playerProcess->waitForStarted())
{
return;
}
isPlaying = true;
Btnstop->setEnabled(true);
playerProcess->write("get_video_resolution/n");
playerProcess->write("get_time_length/n");
playerProcess->write("get_audio_codec/n");
playerProcess->write("get_audio_samples/n");
playerProcess->write("get_file_name/n");
playerProcess->write("get_meta_track/n");
timer->start(1000);
}
void Mplayer::stop()
{
playerProcess->write("quit/n");
if(!playerProcess->waitForFinished())
{
return;
}
Slider->setValue(0);
Btnstop->setEnabled(false);
}
void Mplayer::playAndPause()
{
if(isPlaying)
{
playerProcess->write("pause/n");
isPlaying = false;
BtnplayAndPause->setIcon(QIcon(":/resource/2.png"));
}
else
{
playerProcess->write("pause/n");
isPlaying = true;
BtnplayAndPause->setIcon(QIcon(":/resource/1.png"));
}
}
void Mplayer::Speed()
{
playerProcess->write(QString("speed_mult "+QString::number(2)+"/n").toUtf8());
}
void Mplayer::Low()
{
playerProcess->write(QString("speed_mult "+QString::number(0.5)+"/n").toUtf8());
}
void Mplayer::Mute(){
if(!isMute)
{
playerProcess->write("mute 1/n");
BtnMute->setIcon(QIcon(":/resource/10.png"));
isMute = true;
}
else
{
playerProcess->write("mute 0/n");
BtnMute->setIcon(QIcon(":/resource/11.png"));
isMute = false;
}
}
void Mplayer::setVol(int volum)
{
playerProcess->write(QString("volume "+QString::number(-volum)+"/n").toUtf8());
}
void Mplayer::fullShow()
{
if(!isFull)
{
Player->showFullScreen();
playerProcess->write("vo_fullscreen/n");
isFull = true;
fullScreen->setIcon(QIcon(":/resource/7.png"));
}
else
{
Player->resize(resX,resY);
Player->move(this->pos());
playerProcess->write("vo_fullscreen/n");
isFull = false;
fullScreen->setIcon(QIcon(":/resource/12.png"));
}
}
void Mplayer::stepOne()
{
if(isPlaying)
{
playerProcess->write("get_time_pos/n");
}
}
void Mplayer::catchOutput()
{
while(playerProcess->canReadLine())
{
QByteArray buffer(playerProcess->readLine());
log->append(QString(buffer));
if(buffer.startsWith("ANS_VIDEO_RESOLUTION"))
{
buffer.remove(0,21);
buffer.replace(QByteArray("'"),QByteArray(""));
buffer.replace(QByteArray(" "),QByteArray(""));
buffer.replace(QByteArray("/n"),QByteArray(""));
buffer.replace(QByteArray("/r"),QByteArray(""));
int index = buffer.indexOf('x');
resX = buffer.left(index).toInt();
resY = buffer.mid(index+1).toInt();
Player->resize(resX,resY);
Player->show();
}
if(buffer.startsWith("ANS_LENGTH"))
{
buffer.remove(0,11);
buffer.replace(QByteArray("'"),QByteArray(""));
buffer.replace(QByteArray(" "),QByteArray(""));
buffer.replace(QByteArray("/n"),QByteArray(""));
buffer.replace(QByteArray("/r"),QByteArray(""));
float totalTime = buffer.toFloat();
Slider->setMaximum(static_cast<int>(totalTime+1));
}
if(buffer.startsWith("ANS_TIME_POSITION"))
{
buffer.remove(0,18);
buffer.replace(QByteArray("'"),QByteArray(""));
buffer.replace(QByteArray(" "),QByteArray(""));
buffer.replace(QByteArray("/n"),QByteArray(""));
buffer.replace(QByteArray("/r"),QByteArray(""));
float curTime = buffer.toFloat();
Slider->setValue(static_cast<int>(curTime+1));
}
}
}
void Mplayer::SliderChanged(int pos)
{
playerProcess->write(QString("seek "+QString::number(pos)+" 2/n").toUtf8());
}