diff options
author | George Kiagiadakis <george.kiagiadakis@collabora.co.uk> | 2011-01-18 20:07:19 +0200 |
---|---|---|
committer | George Kiagiadakis <george.kiagiadakis@collabora.co.uk> | 2011-01-18 20:07:19 +0200 |
commit | 9341394004fce7e9a0f2323b65b5b5623f2f9a2c (patch) | |
tree | 7b01be8cdc7d8782e01ccde94b8c40fc2afacac6 | |
parent | a1f122cb4e29fd7e47ee175c48dde734f02ca8b2 (diff) |
Add a new "recorder" example.
-rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
-rw-r--r-- | examples/examples.dox | 18 | ||||
-rw-r--r-- | examples/recorder/CMakeLists.txt | 6 | ||||
-rw-r--r-- | examples/recorder/main.cpp | 325 | ||||
-rw-r--r-- | examples/recorder/recorder.ui | 257 |
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> |