summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.co.uk>2011-01-18 20:07:19 +0200
committerGeorge Kiagiadakis <george.kiagiadakis@collabora.co.uk>2011-01-18 20:07:19 +0200
commit9341394004fce7e9a0f2323b65b5b5623f2f9a2c (patch)
tree7b01be8cdc7d8782e01ccde94b8c40fc2afacac6
parenta1f122cb4e29fd7e47ee175c48dde734f02ca8b2 (diff)
Add a new "recorder" example.
-rw-r--r--examples/CMakeLists.txt1
-rw-r--r--examples/examples.dox18
-rw-r--r--examples/recorder/CMakeLists.txt6
-rw-r--r--examples/recorder/main.cpp325
-rw-r--r--examples/recorder/recorder.ui257
5 files changed, 607 insertions, 0 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 27b62bb..acbf38d 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -1,2 +1,3 @@
add_subdirectory(player)
add_subdirectory(appsink-src)
+add_subdirectory(recorder)
diff --git a/examples/examples.dox b/examples/examples.dox
index f983c18..e7fb4a3 100644
--- a/examples/examples.dox
+++ b/examples/examples.dox
@@ -32,3 +32,21 @@
* and pushing it to appsrc with the pushBuffer() method. The result is a choppy
* audio player.
*/
+
+/*! \example recorder/main.cpp
+ * This is a recording application that takes audio from a microphone
+ * and video from either a camera or the X11 screen, encodes them with
+ * theora and speex and saves the result in a file.
+ *
+ * The intention of this example is to show how simple it is to perform such
+ * complex tasks with GStreamer and how easy it is to change the functionality
+ * of the program by changing just one element (with autovideosrc it will
+ * do a webcam recording, but with ximagesrc it will do a screencast).
+ *
+ * Tasks demonstrated in this example include:
+ * \li How to create and link elements manually.
+ * \li How to create and link elements using a pipeline description.
+ * \li How to use the QGst::PropertyProbe interface.
+ * \li How to handle bus messages.
+ * \li Others...
+ */
diff --git a/examples/recorder/CMakeLists.txt b/examples/recorder/CMakeLists.txt
new file mode 100644
index 0000000..8352a3f
--- /dev/null
+++ b/examples/recorder/CMakeLists.txt
@@ -0,0 +1,6 @@
+include_directories(${QTGSTREAMER_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
+add_definitions(${QTGSTREAMER_DEFINITIONS})
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${QTGSTREAMER_FLAGS}")
+qt4_wrap_ui(recorder_UI_SRCS recorder.ui)
+automoc4_add_executable(recorder main.cpp ${recorder_UI_SRCS})
+target_link_libraries(recorder ${QTGSTREAMER_LIBRARIES} ${QT_QTGUI_LIBRARY})
diff --git a/examples/recorder/main.cpp b/examples/recorder/main.cpp
new file mode 100644
index 0000000..5ec00d0
--- /dev/null
+++ b/examples/recorder/main.cpp
@@ -0,0 +1,325 @@
+/*
+ Copyright (C) 2011 Collabora Ltd.
+ @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
+
+ This library is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "ui_recorder.h"
+
+#include <QtCore/QDir>
+#include <QtGui/QApplication>
+#include <QtGui/QDialog>
+#include <QtGui/QMessageBox>
+
+#include <QGlib/Error>
+#include <QGlib/Connect>
+#include <QGst/Init>
+#include <QGst/ElementFactory>
+#include <QGst/ChildProxy>
+#include <QGst/PropertyProbe>
+#include <QGst/Pipeline>
+#include <QGst/Pad>
+#include <QGst/Event>
+#include <QGst/Message>
+#include <QGst/Bus>
+
+#ifdef Q_WS_X11
+# include <QtGui/QX11Info>
+#endif
+
+
+class Recorder : public QDialog
+{
+ Q_OBJECT
+public:
+ Recorder(QWidget *parent = 0);
+
+private:
+ enum Device { AudioSrc, VideoSrc };
+ void findDevices(Device device);
+ void probeForDevices(const QGst::PropertyProbePtr & propertyProbe);
+
+ QGst::BinPtr createAudioSrcBin();
+ QGst::BinPtr createVideoSrcBin();
+
+ void start();
+ void stop();
+
+ void onBusMessage(const QGst::MessagePtr & message);
+
+private Q_SLOTS:
+ void on_startStopButton_clicked();
+
+private:
+ Ui::Recorder m_ui;
+ QGst::PropertyProbePtr m_audioProbe;
+ QGst::PropertyProbePtr m_videoProbe;
+ QGst::PipelinePtr m_pipeline;
+};
+
+Recorder::Recorder(QWidget *parent)
+ : QDialog(parent)
+{
+ m_ui.setupUi(this);
+
+ //setup the device combo boxes
+ findDevices(AudioSrc);
+ findDevices(VideoSrc);
+
+ QGst::ElementFactoryPtr ximagesrc = QGst::ElementFactory::find("ximagesrc");
+ if (!ximagesrc) {
+ //if we don't have ximagesrc, disable the choice to use it
+ m_ui.videoSourceComboBox->removeItem(1);
+ } else {
+#ifdef Q_WS_X11
+ //setup the default screen to be the screen that this dialog is shown
+ m_ui.displayNumSpinBox->setValue(QX11Info::appScreen());
+#endif
+ }
+
+ //set default output file
+ m_ui.outputFileEdit->setText(QDir::currentPath() + QDir::separator() + "out.ogv");
+}
+
+void Recorder::findDevices(Device device)
+{
+ const char *srcElementName = (device == AudioSrc) ? "autoaudiosrc" : "autovideosrc";
+ QGst::ElementPtr src = QGst::ElementFactory::make(srcElementName);
+
+ if (!src) {
+ QMessageBox::critical(this, tr("Error"),
+ tr("Failed to create element \"%1\". Make sure you have "
+ "gstreamer-plugins-good installed").arg(srcElementName));
+ return;
+ }
+
+ QGst::PropertyProbePtr propertyProbe;
+
+ //autoaudiosrc and autovideosrc implement the child proxy interface
+ //and create the correct child element when they go to the READY state
+ src->setState(QGst::StateReady);
+ QGst::ChildProxyPtr childProxy = src.dynamicCast<QGst::ChildProxy>();
+ if (childProxy && childProxy->childrenCount() > 0) {
+ //the actual source is the first child
+ //this source usually implements the property probe interface
+ propertyProbe = childProxy->childByIndex(0).dynamicCast<QGst::PropertyProbe>();
+ }
+ //we got a reference to the underlying propertyProbe, so we don't need src anymore.
+ src->setState(QGst::StateNull);
+
+ //Most sources and sinks have a "device" property which supports probe
+ //and probing it returns all the available devices on the system.
+ //Here we try to make use of that to list the system's devices
+ //and if it fails, we just leave the source to use its default device.
+ if (propertyProbe && propertyProbe->propertySupportsProbe("device")) {
+ ((device == AudioSrc) ? m_audioProbe : m_videoProbe) = propertyProbe;
+ probeForDevices(propertyProbe);
+
+ //this signal will notify us when devices change
+ QGlib::connect(propertyProbe, "probe-needed",
+ this, &Recorder::probeForDevices, QGlib::PassSender);
+ } else {
+ QComboBox *box = (device == AudioSrc) ? m_ui.audioDeviceComboBox : m_ui.videoDeviceComboBox;
+ box->addItem(tr("Default"));
+ }
+}
+
+void Recorder::probeForDevices(const QGst::PropertyProbePtr & propertyProbe)
+{
+ QComboBox *box = (propertyProbe == m_audioProbe) ?
+ m_ui.audioDeviceComboBox : m_ui.videoDeviceComboBox;
+
+ box->clear();
+ box->addItem(tr("Default"));
+
+ //get a list of devices that the element supports
+ QList<QGlib::Value> devices = propertyProbe->probeAndGetValues("device");
+
+ Q_FOREACH(const QGlib::Value & device, devices) {
+ //set the element's device to the current device and retrieve its
+ //human-readable name through the "device-name" property
+ propertyProbe->setProperty("device", device);
+ QString deviceName = propertyProbe->property("device-name").toString();
+
+ //add the device on the combobox
+ box->addItem(QString("%1 (%2)").arg(deviceName, device.toString()),
+ device.toString());
+ }
+}
+
+QGst::BinPtr Recorder::createAudioSrcBin()
+{
+ QGst::BinPtr audioBin;
+
+ try {
+ audioBin = QGst::Bin::fromDescription("autoaudiosrc name=\"audiosrc\" ! audioconvert ! "
+ "audioresample ! audiorate ! speexenc ! queue");
+ } catch (const QGlib::Error & error) {
+ qCritical() << "Failed to create audio source bin:" << error;
+ return QGst::BinPtr();
+ }
+
+ //set the source's properties
+ QVariant device = m_ui.audioDeviceComboBox->itemData(m_ui.audioDeviceComboBox->currentIndex());
+ if (device.isValid()) {
+ QGst::ElementPtr src = audioBin->getElementByName("audiosrc");
+
+ //autoaudiosrc creates the actual source in the READY state
+ src->setState(QGst::StateReady);
+
+ QGst::ChildProxyPtr childProxy = src.dynamicCast<QGst::ChildProxy>();
+ if (childProxy && childProxy->childrenCount() > 0) {
+ //the actual source is the first child
+ QGst::ObjectPtr realSrc = childProxy->childByIndex(0);
+ realSrc->setProperty("device", device.toString());
+ }
+ }
+
+ return audioBin;
+}
+
+QGst::BinPtr Recorder::createVideoSrcBin()
+{
+ QGst::BinPtr videoBin;
+
+ try {
+ if (m_ui.videoSourceComboBox->currentIndex() == 0) { //camera
+ videoBin = QGst::Bin::fromDescription("autovideosrc name=\"videosrc\" ! "
+ "ffmpegcolorspace ! theoraenc ! queue");
+ } else { //screencast
+ videoBin = QGst::Bin::fromDescription("ximagesrc name=\"videosrc\" ! "
+ "ffmpegcolorspace ! theoraenc ! queue");
+ }
+ } catch (const QGlib::Error & error) {
+ qCritical() << "Failed to create video source bin:" << error;
+ return QGst::BinPtr();
+ }
+
+ //set the source's properties
+ if (m_ui.videoSourceComboBox->currentIndex() == 0) { //camera
+ QVariant device = m_ui.videoDeviceComboBox->itemData(m_ui.videoDeviceComboBox->currentIndex());
+ if (device.isValid()) {
+ QGst::ElementPtr src = videoBin->getElementByName("videosrc");
+
+ //autovideosrc creates the actual source in the READY state
+ src->setState(QGst::StateReady);
+
+ QGst::ChildProxyPtr childProxy = src.dynamicCast<QGst::ChildProxy>();
+ if (childProxy && childProxy->childrenCount() > 0) {
+ //the actual source is the first child
+ QGst::ObjectPtr realSrc = childProxy->childByIndex(0);
+ realSrc->setProperty("device", device.toString());
+ }
+ }
+ } else { //screencast
+ videoBin->getElementByName("videosrc")->setProperty("screen-num", m_ui.displayNumSpinBox->value());
+ }
+
+ return videoBin;
+}
+
+void Recorder::start()
+{
+ QGst::BinPtr audioSrcBin = createAudioSrcBin();
+ QGst::BinPtr videoSrcBin = createVideoSrcBin();
+ QGst::ElementPtr mux = QGst::ElementFactory::make("oggmux");
+ QGst::ElementPtr sink = QGst::ElementFactory::make("filesink");
+
+ if (!audioSrcBin || !videoSrcBin || !mux || !sink) {
+ QMessageBox::critical(this, tr("Error"), tr("One or more elements could not be created. "
+ "Verify that you have all the necessary element plugins installed."));
+ return;
+ }
+
+ sink->setProperty("location", m_ui.outputFileEdit->text());
+
+ m_pipeline = QGst::Pipeline::create();
+ m_pipeline->add(audioSrcBin);
+ m_pipeline->add(videoSrcBin);
+ m_pipeline->add(mux);
+ m_pipeline->add(sink);
+
+ //link elements
+ QGst::PadPtr audioPad = mux->getRequestPad("sink_%d");
+ audioSrcBin->getStaticPad("src")->link(audioPad);
+
+ QGst::PadPtr videoPad = mux->getRequestPad("sink_%d");
+ videoSrcBin->getStaticPad("src")->link(videoPad);
+
+ mux->link(sink);
+
+ //connect the bus
+ m_pipeline->bus()->addSignalWatch();
+ QGlib::connect(m_pipeline->bus(), "message", this, &Recorder::onBusMessage);
+
+ //go!
+ m_pipeline->setState(QGst::StatePlaying);
+ m_ui.startStopButton->setText(tr("Stop recording"));
+}
+
+void Recorder::stop()
+{
+ //stop recording
+ m_pipeline->setState(QGst::StateNull);
+
+ //clear the pointer, destroying the pipeline as its reference count drops to zero.
+ m_pipeline.clear();
+
+ //restore the button's text
+ m_ui.startStopButton->setText(tr("Start recording"));
+}
+
+void Recorder::onBusMessage(const QGst::MessagePtr & message)
+{
+ switch (message->type()) {
+ case QGst::MessageEos:
+ //got end-of-stream - stop the pipeline
+ stop();
+ break;
+ case QGst::MessageError:
+ //check if the pipeline exists before destroying it,
+ //as we might get multiple error messages
+ if (m_pipeline) {
+ stop();
+ }
+ QMessageBox::critical(this, tr("Pipeline Error"),
+ message.staticCast<QGst::ErrorMessage>()->error().message());
+ break;
+ default:
+ break;
+ }
+}
+
+void Recorder::on_startStopButton_clicked()
+{
+ if (m_pipeline) { //pipeline exists - destroy it
+ //send an end-of-stream event to flush metadata and cause an EosMessage to be delivered
+ m_pipeline->sendEvent(QGst::EosEvent::create());
+ } else { //pipeline doesn't exist - start a new one
+ start();
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ QGst::init(&argc, &argv);
+
+ Recorder r;
+ r.show();
+
+ return app.exec();
+}
+
+#include "recorder.moc"
diff --git a/examples/recorder/recorder.ui b/examples/recorder/recorder.ui
new file mode 100644
index 0000000..7e750db
--- /dev/null
+++ b/examples/recorder/recorder.ui
@@ -0,0 +1,257 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Recorder</class>
+ <widget class="QDialog" name="Recorder">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>306</width>
+ <height>284</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>QtGStreamer recorder example</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="3">
+ <widget class="QGroupBox" name="audioSourceGroupBox">
+ <property name="title">
+ <string>Audio source</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="audioSourceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Audio source:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audioSourceComboBox">
+ <item>
+ <property name="text">
+ <string>Microphone</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="audioSourcePropertiesWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="microphonePage">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="audioDeviceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audioDeviceComboBox"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="3">
+ <widget class="QGroupBox" name="videoSourceGroupBox">
+ <property name="title">
+ <string>Video source</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="videoSourceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Video source:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="videoSourceComboBox">
+ <item>
+ <property name="text">
+ <string>Camera</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Screencast (X11)</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QStackedWidget" name="videoSourcePropertiesWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="cameraPage">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="videoDeviceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="videoDeviceComboBox"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="screencastPage">
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLabel" name="displayNumLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Display number:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="displayNumSpinBox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="3">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="outputFileLabel">
+ <property name="text">
+ <string>Output file (ogg):</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="outputFileEdit"/>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>76</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="1">
+ <widget class="QPushButton" name="startStopButton">
+ <property name="text">
+ <string>Start recording</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="2">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>87</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>audioSourceComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>audioSourcePropertiesWidget</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>305</x>
+ <y>52</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>231</x>
+ <y>73</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>videoSourceComboBox</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>videoSourcePropertiesWidget</receiver>
+ <slot>setCurrentIndex(int)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>266</x>
+ <y>172</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>220</x>
+ <y>191</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>