diff options
author | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
---|---|---|
committer | axis <qt-info@nokia.com> | 2009-04-24 11:34:15 (GMT) |
commit | 8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76 (patch) | |
tree | a17e1a767a89542ab59907462206d7dcf2e504b2 /src/3rdparty/phonon/gstreamer | |
download | Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.zip Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.gz Qt-8f427b2b914d5b575a4a7c0ed65d2fb8f45acc76.tar.bz2 |
Long live Qt for S60!
Diffstat (limited to 'src/3rdparty/phonon/gstreamer')
51 files changed, 10435 insertions, 0 deletions
diff --git a/src/3rdparty/phonon/gstreamer/CMakeLists.txt b/src/3rdparty/phonon/gstreamer/CMakeLists.txt new file mode 100644 index 0000000..4946f5f --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/CMakeLists.txt @@ -0,0 +1,72 @@ +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# Copyright (C) 2008 Matthias Kretz <kretz@kde.org> +# +# 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 or 3 of the License. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +project(phonon-gstreamer) +include(ConfigureChecks.cmake) + +if (BUILD_PHONON_GSTREAMER) + include_directories(${GSTREAMER_INCLUDE_DIR} ${GLIB2_INCLUDE_DIR} + ${LIBXML2_INCLUDE_DIR}) + + set(phonon_gstreamer_SRCS + audiooutput.cpp + artssink.cpp + backend.cpp + devicemanager.cpp + effectmanager.cpp + gsthelper.cpp + mediaobject.cpp + medianode.cpp + effect.cpp + medianodeevent.cpp + videowidget.cpp + qwidgetvideosink.cpp + streamreader.cpp + phononsrc.cpp + message.cpp + audioeffect.cpp + abstractrenderer.cpp + x11renderer.cpp + widgetrenderer.cpp + glrenderer.cpp + volumefadereffect.cpp + ) + + find_package(Alsa) + if(ALSA_FOUND) + add_definitions(-DUSE_ALSASINK2) + include_directories(${ALSA_INCLUDES}) + set(phonon_gstreamer_SRCS + ${phonon_gstreamer_SRCS} + alsasink2.c + ) + endif(ALSA_FOUND) + + automoc4(phonon_gstreamer phonon_gstreamer_SRCS) + add_library(phonon_gstreamer SHARED ${phonon_gstreamer_SRCS}) + set_target_properties(phonon_gstreamer PROPERTIES PREFIX "") + target_link_libraries(phonon_gstreamer + ${QT_QTOPENGL_LIBRARY} + ${PHONON_LIBS} ${OPENGL_gl_LIBRARY} + ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARY} ${GSTREAMER_INTERFACE_LIBRARY} + ${GSTREAMER_PLUGIN_VIDEO_LIBRARIES} ${GSTREAMER_PLUGIN_AUDIO_LIBRARIES} + ${GLIB2_LIBRARIES} ${GOBJECT_LIBRARIES}) + if(ALSA_FOUND) + target_link_libraries(phonon_gstreamer ${ASOUND_LIBRARY}) + endif(ALSA_FOUND) + + install(TARGETS phonon_gstreamer DESTINATION ${PLUGIN_INSTALL_DIR}/plugins/phonon_backend) + install(FILES gstreamer.desktop DESTINATION ${SERVICES_INSTALL_DIR}/phononbackends) +endif (BUILD_PHONON_GSTREAMER) diff --git a/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake b/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake new file mode 100644 index 0000000..f2922e1 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake @@ -0,0 +1,37 @@ +# Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +# +# 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 or 3 of the License. +# +# This library 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +macro_optional_find_package(GStreamer) +macro_log_feature(GSTREAMER_FOUND "GStreamer" "gstreamer 0.10 is required for the multimedia backend" "http://gstreamer.freedesktop.org/modules/" FALSE "0.10") + +macro_optional_find_package(GStreamerPlugins) +macro_log_feature(GSTREAMER_PLUGIN_VIDEO_LIBRARIES "GStreamer video plugin" "The gstreamer video plugin (part of gstreamer-plugins-base 0.10) is required for the multimedia gstreamer backend" "http://gstreamer.freedesktop.org/modules/" FALSE "0.10") + +macro_optional_find_package(GLIB2) +macro_log_feature(GLIB2_FOUND "GLib2" "GLib 2 is required to compile the gstreamer backend for Phonon" "http://www.gtk.org/download/" FALSE) + +macro_optional_find_package(GObject) +# No log, since GObject is bundled with GLib + +macro_optional_find_package(LibXml2) +macro_log_feature(LIBXML2_FOUND "LibXml2" "LibXml2 is required to compile the gstreamer backend for Phonon" "http://xmlsoft.org/downloads.html" FALSE) + +macro_optional_find_package(OpenGL) +macro_log_feature(OPENGL_FOUND "OpenGL" "OpenGL support is required to compile the gstreamer backend for Phonon" "" FALSE) + +if (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GLIB2_FOUND AND GOBJECT_FOUND AND LIBXML2_FOUND AND OPENGL_FOUND) + set(BUILD_PHONON_GSTREAMER TRUE) +else (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GLIB2_FOUND AND GOBJECT_FOUND AND LIBXML2_FOUND AND OPENGL_FOUND) + set(BUILD_PHONON_GSTREAMER FALSE) +endif (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GLIB2_FOUND AND GOBJECT_FOUND AND LIBXML2_FOUND AND OPENGL_FOUND) diff --git a/src/3rdparty/phonon/gstreamer/Messages.sh b/src/3rdparty/phonon/gstreamer/Messages.sh new file mode 100644 index 0000000..4fdf1ef --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/Messages.sh @@ -0,0 +1,5 @@ +#! /usr/bin/env bash +find ./ -maxdepth 1 -name "*.cpp" -print > files +find ./ -maxdepth 1 -name "*.h" -print >> files +$XGETTEXT_QT --copyright-holder=This_file_is_part_of_KDE --msgid-bugs-address=http://bugs.kde.org --files-from=files -o $podir/phonon_gstreamer.pot +rm files diff --git a/src/3rdparty/phonon/gstreamer/abstractrenderer.cpp b/src/3rdparty/phonon/gstreamer/abstractrenderer.cpp new file mode 100644 index 0000000..924b611 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/abstractrenderer.cpp @@ -0,0 +1,56 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "abstractrenderer.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + + +AbstractRenderer::~AbstractRenderer() +{ + if (m_videoSink) { + gst_object_unref (GST_OBJECT (m_videoSink)); //Take ownership + m_videoSink = 0; + } +} + +void AbstractRenderer::aspectRatioChanged(Phonon::VideoWidget::AspectRatio aspectRatio) +{ + Q_UNUSED(aspectRatio); +} + +void AbstractRenderer::scaleModeChanged(Phonon::VideoWidget::ScaleMode scaleMode) +{ + Q_UNUSED(scaleMode); +} + +void AbstractRenderer::movieSizeChanged(const QSize &size) +{ + Q_UNUSED(size); +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + + diff --git a/src/3rdparty/phonon/gstreamer/abstractrenderer.h b/src/3rdparty/phonon/gstreamer/abstractrenderer.h new file mode 100644 index 0000000..140413d --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/abstractrenderer.h @@ -0,0 +1,62 @@ +/* This file is part of the KDE project. + + Copyright (C) 2 //Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).007 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_ABSTRACTRENDERER_H +#define Phonon_GSTREAMER_ABSTRACTRENDERER_H + +#include "backend.h" +#include "common.h" +#include "medianode.h" +#include <phonon/videowidget.h> + +QT_BEGIN_NAMESPACE + +class QString; +namespace Phonon +{ +namespace Gstreamer +{ + +class VideoWidget; + +class AbstractRenderer +{ +public: + AbstractRenderer(VideoWidget *video) : + m_videoWidget(video) + , m_videoSink(0) { } + virtual ~AbstractRenderer(); + virtual GstElement *videoSink() {return m_videoSink;} + virtual void aspectRatioChanged(Phonon::VideoWidget::AspectRatio aspectRatio); + virtual void scaleModeChanged(Phonon::VideoWidget::ScaleMode scaleMode); + virtual void movieSizeChanged(const QSize &movieSize); + virtual void handleMediaNodeEvent(const MediaNodeEvent *event) = 0; + virtual bool eventFilter(QEvent *) = 0; + virtual void handlePaint(QPaintEvent *) {} + virtual bool paintsOnWidget() { return true; } // Controls overlays + +protected: + VideoWidget *m_videoWidget; + GstElement *m_videoSink; +}; + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_ABSTRACTRENDERER_H diff --git a/src/3rdparty/phonon/gstreamer/alsasink2.c b/src/3rdparty/phonon/gstreamer/alsasink2.c new file mode 100644 index 0000000..4dcb140 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/alsasink2.c @@ -0,0 +1,1756 @@ +/* GStreamer + * Copyright (C) 2001 CodeFactory AB + * Copyright (C) 2001 Thomas Nyberg <thomas@codefactory.se> + * Copyright (C) 2001-2002 Andy Wingo <apwingo@eos.ncsu.edu> + * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> + * Copyright (C) 2005 Wim Taymans <wim@fluendo.com> + * Copyright (C) 2005, 2006 Tim-Philipp Müller <tim centricular net> + * Copyright (C) 2008 Matthias Kretz <kretz@kde.org> + * + * gstalsasink2.c: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:element-alsasink2 + * @short_description: play audio to an ALSA device + * @see_also: alsasrc, alsamixer + * + * <refsect2> + * <para> + * This element renders raw audio samples using the ALSA api. + * </para> + * <title>Example pipelines</title> + * <para> + * Play an Ogg/Vorbis file. + * </para> + * <programlisting> + * gst-launch -v filesrc location=sine.ogg ! oggdemux ! vorbisdec ! audioconvert ! audioresample ! alsasink2 + * </programlisting> + * </refsect2> + * + * Last reviewed on 2006-03-01 (0.10.4) + */ + +#define _XOPEN_SOURCE 600 + +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <getopt.h> +#include <alsa/asoundlib.h> + +#include "alsasink2.h" + +#include <gst/interfaces/propertyprobe.h> +#include <gst/audio/multichannel.h> + +#define _(text) (text) + +#define GST_CHECK_ALSA_VERSION(major,minor,micro) \ + (SND_LIB_MAJOR > (major) || \ + (SND_LIB_MAJOR == (major) && SND_LIB_MINOR > (minor)) || \ + (SND_LIB_MAJOR == (major) && SND_LIB_MINOR == (minor) && \ + SND_LIB_SUBMINOR >= (micro))) + +static const GList * +gst_alsa_device_property_probe_get_properties (GstPropertyProbe * probe) +{ + GObjectClass *klass = G_OBJECT_GET_CLASS (probe); + static GList *list = NULL; + + /* well, not perfect, but better than no locking at all. + * In the worst case we leak a list node, so who cares? */ + GST_CLASS_LOCK (GST_OBJECT_CLASS (klass)); + + if (!list) { + GParamSpec *pspec; + + pspec = g_object_class_find_property (klass, "device"); + list = g_list_append (NULL, pspec); + } + + GST_CLASS_UNLOCK (GST_OBJECT_CLASS (klass)); + + return list; +} + +static GList * +gst_alsa_get_device_list (snd_pcm_stream_t stream) +{ + snd_ctl_t *handle; + int card, err, dev; + snd_ctl_card_info_t *info; + snd_pcm_info_t *pcminfo; + gboolean mixer = (stream == ~0u); + GList *list = NULL; + + if (stream == ~0u) + stream = 0; + + snd_ctl_card_info_malloc (&info); + snd_pcm_info_malloc (&pcminfo); + card = -1; + + if (snd_card_next (&card) < 0 || card < 0) { + /* no soundcard found */ + return NULL; + } + + while (card >= 0) { + gchar name[32]; + + g_snprintf (name, sizeof (name), "hw:%d", card); + if ((err = snd_ctl_open (&handle, name, 0)) < 0) { + goto next_card; + } + if ((err = snd_ctl_card_info (handle, info)) < 0) { + snd_ctl_close (handle); + goto next_card; + } + + if (mixer) { + list = g_list_append (list, g_strdup (name)); + } else { + g_snprintf (name, sizeof (name), "default:CARD=%d", card); + list = g_list_append (list, g_strdup (name)); + dev = -1; + while (1) { + gchar *gst_device; + + snd_ctl_pcm_next_device (handle, &dev); + + if (dev < 0) + break; + snd_pcm_info_set_device (pcminfo, dev); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, stream); + if ((err = snd_ctl_pcm_info (handle, pcminfo)) < 0) { + continue; + } + + gst_device = g_strdup_printf ("hw:%d,%d", card, dev); + list = g_list_append (list, gst_device); + } + } + snd_ctl_close (handle); + next_card: + if (snd_card_next (&card) < 0) { + break; + } + } + + snd_ctl_card_info_free (info); + snd_pcm_info_free (pcminfo); + + return list; +} + +static void +gst_alsa_device_property_probe_probe_property (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + if (!g_str_equal (pspec->name, "device")) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + } +} + +static gboolean +gst_alsa_device_property_probe_needs_probe (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + /* don't cache probed data */ + return TRUE; +} + +static GValueArray * +gst_alsa_device_property_probe_get_values (GstPropertyProbe * probe, + guint prop_id, const GParamSpec * pspec) +{ + GstElementClass *klass; + const GList *templates; + snd_pcm_stream_t mode = -1; + GValueArray *array; + GValue value = { 0, }; + GList *l, *list; + + if (!g_str_equal (pspec->name, "device")) { + G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); + return NULL; + } + + klass = GST_ELEMENT_GET_CLASS (GST_ELEMENT (probe)); + + /* I'm pretty sure ALSA has a good way to do this. However, their cool + * auto-generated documentation is pretty much useless if you try to + * do function-wise look-ups. */ + /* we assume one pad template at max [zero=mixer] */ + templates = gst_element_class_get_pad_template_list (klass); + if (templates) { + if (GST_PAD_TEMPLATE_DIRECTION (templates->data) == GST_PAD_SRC) + mode = SND_PCM_STREAM_CAPTURE; + else + mode = SND_PCM_STREAM_PLAYBACK; + } + + list = gst_alsa_get_device_list (mode); + + if (list == NULL) { + GST_LOG_OBJECT (probe, "No devices found"); + return NULL; + } + + array = g_value_array_new (g_list_length (list)); + g_value_init (&value, G_TYPE_STRING); + for (l = list; l != NULL; l = l->next) { + GST_LOG_OBJECT (probe, "Found device: %s", (gchar *) l->data); + g_value_take_string (&value, (gchar *) l->data); + l->data = NULL; + g_value_array_append (array, &value); + } + g_value_unset (&value); + g_list_free (list); + + return array; +} + +static void +gst_alsa_property_probe_interface_init (GstPropertyProbeInterface * iface) +{ + iface->get_properties = gst_alsa_device_property_probe_get_properties; + iface->probe_property = gst_alsa_device_property_probe_probe_property; + iface->needs_probe = gst_alsa_device_property_probe_needs_probe; + iface->get_values = gst_alsa_device_property_probe_get_values; +} + +static void +gst_alsa_type_add_device_property_probe_interface (GType type) +{ + static const GInterfaceInfo probe_iface_info = { + (GInterfaceInitFunc) gst_alsa_property_probe_interface_init, + NULL, + NULL, + }; + + g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, + &probe_iface_info); +} + +static GstCaps * +gst_alsa_detect_rates (GstObject * obj, snd_pcm_hw_params_t * hw_params, + GstCaps * in_caps) +{ + GstCaps *caps; + guint min, max; + gint err, dir, min_rate, max_rate; + guint i; + + GST_LOG_OBJECT (obj, "probing sample rates ..."); + + if ((err = snd_pcm_hw_params_get_rate_min (hw_params, &min, &dir)) < 0) + goto min_rate_err; + + if ((err = snd_pcm_hw_params_get_rate_max (hw_params, &max, &dir)) < 0) + goto max_rate_err; + + min_rate = min; + max_rate = max; + + if (min_rate < 4000) + min_rate = 4000; /* random 'sensible minimum' */ + + if (max_rate <= 0) + max_rate = G_MAXINT; /* or maybe just use 192400 or so? */ + else if (max_rate > 0 && max_rate < 4000) + max_rate = MAX (4000, min_rate); + + GST_DEBUG_OBJECT (obj, "Min. rate = %u (%d)", min_rate, min); + GST_DEBUG_OBJECT (obj, "Max. rate = %u (%d)", max_rate, max); + + caps = gst_caps_make_writable (in_caps); + + for (i = 0; i < gst_caps_get_size (caps); ++i) { + GstStructure *s; + + s = gst_caps_get_structure (caps, i); + if (min_rate == max_rate) { + gst_structure_set (s, "rate", G_TYPE_INT, min_rate, NULL); + } else { + gst_structure_set (s, "rate", GST_TYPE_INT_RANGE, + min_rate, max_rate, NULL); + } + } + + return caps; + + /* ERRORS */ +min_rate_err: + { + GST_ERROR_OBJECT (obj, "failed to query minimum sample rate: %s", + snd_strerror (err)); + gst_caps_unref (in_caps); + return NULL; + } +max_rate_err: + { + GST_ERROR_OBJECT (obj, "failed to query maximum sample rate: %s", + snd_strerror (err)); + gst_caps_unref (in_caps); + return NULL; + } +} + +static const struct +{ + const int width; + const int depth; + const int sformat; + const int uformat; +} pcmformats[] = { + { + 8, 8, SND_PCM_FORMAT_S8, SND_PCM_FORMAT_U8}, { + 16, 16, SND_PCM_FORMAT_S16, SND_PCM_FORMAT_U16}, { + 32, 24, SND_PCM_FORMAT_S24, SND_PCM_FORMAT_U24}, { +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) /* no endian-unspecific enum available */ + 24, 24, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_U24_3LE}, { +#else + 24, 24, SND_PCM_FORMAT_S24_3BE, SND_PCM_FORMAT_U24_3BE}, { +#endif + 32, 32, SND_PCM_FORMAT_S32, SND_PCM_FORMAT_U32} +}; + +static GstCaps * +gst_alsa_detect_formats (GstObject * obj, snd_pcm_hw_params_t * hw_params, + GstCaps * in_caps) +{ + snd_pcm_format_mask_t *mask; + GstStructure *s; + GstCaps *caps; + guint i; + + snd_pcm_format_mask_malloc (&mask); + snd_pcm_hw_params_get_format_mask (hw_params, mask); + + caps = gst_caps_new_empty (); + + for (i = 0; i < gst_caps_get_size (in_caps); ++i) { + GstStructure *scopy; + guint w; + gint width = 0, depth = 0; + + s = gst_caps_get_structure (in_caps, i); + if (!gst_structure_has_name (s, "audio/x-raw-int")) { + GST_WARNING_OBJECT (obj, "skipping non-int format"); + continue; + } + if (!gst_structure_get_int (s, "width", &width) || + !gst_structure_get_int (s, "depth", &depth)) + continue; + if (width == 0 || (width % 8) != 0) + continue; /* Only full byte widths are valid */ + for (w = 0; w < G_N_ELEMENTS (pcmformats); w++) + if (pcmformats[w].width == width && pcmformats[w].depth == depth) + break; + if (w == G_N_ELEMENTS (pcmformats)) + continue; /* Unknown format */ + + if (snd_pcm_format_mask_test (mask, pcmformats[w].sformat) && + snd_pcm_format_mask_test (mask, pcmformats[w].uformat)) { + /* template contains { true, false } or just one, leave it as it is */ + scopy = gst_structure_copy (s); + } else if (snd_pcm_format_mask_test (mask, pcmformats[w].sformat)) { + scopy = gst_structure_copy (s); + gst_structure_set (scopy, "signed", G_TYPE_BOOLEAN, TRUE, NULL); + } else if (snd_pcm_format_mask_test (mask, pcmformats[w].uformat)) { + scopy = gst_structure_copy (s); + gst_structure_set (scopy, "signed", G_TYPE_BOOLEAN, FALSE, NULL); + } else { + scopy = NULL; + } + if (scopy) { + if (width > 8) { + /* TODO: proper endianness detection, for now it's CPU endianness only */ + gst_structure_set (scopy, "endianness", G_TYPE_INT, G_BYTE_ORDER, NULL); + } + gst_caps_append_structure (caps, scopy); + } + } + + snd_pcm_format_mask_free (mask); + gst_caps_unref (in_caps); + return caps; +} + +/* we don't have channel mappings for more than this many channels */ +#define GST_ALSA_MAX_CHANNELS 8 + +static GstStructure * +get_channel_free_structure (const GstStructure * in_structure) +{ + GstStructure *s = gst_structure_copy (in_structure); + + gst_structure_remove_field (s, "channels"); + return s; +} + +static void +caps_add_channel_configuration (GstCaps * caps, + const GstStructure * in_structure, gint min_chans, gint max_chans) +{ + GstAudioChannelPosition pos[8] = { + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_REAR_LEFT, + GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT, + GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER, + GST_AUDIO_CHANNEL_POSITION_LFE, + GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT, + GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT + }; + GstStructure *s = NULL; + gint c; + + if (min_chans == max_chans && max_chans <= 2) { + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", G_TYPE_INT, max_chans, NULL); + gst_caps_append_structure (caps, s); + return; + } + + g_assert (min_chans >= 1); + + /* mono and stereo don't need channel configurations */ + if (min_chans == 2) { + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", G_TYPE_INT, 2, NULL); + gst_caps_append_structure (caps, s); + } else if (min_chans == 1 && max_chans >= 2) { + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", GST_TYPE_INT_RANGE, 1, 2, NULL); + gst_caps_append_structure (caps, s); + } + + /* don't know whether to use 2.1 or 3.0 here - but I suspect + * alsa might work around that/fix it somehow. Can we tell alsa + * what our channel layout is like? */ + if (max_chans >= 3 && min_chans <= 3) { + GstAudioChannelPosition pos_21[3] = { + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT, + GST_AUDIO_CHANNEL_POSITION_LFE + }; + + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", G_TYPE_INT, 3, NULL); + gst_audio_set_channel_positions (s, pos_21); + gst_caps_append_structure (caps, s); + } + + /* everything else (4, 6, 8 channels) needs a channel layout */ + for (c = MAX (4, min_chans); c <= 8; c += 2) { + if (max_chans >= c) { + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", G_TYPE_INT, c, NULL); + gst_audio_set_channel_positions (s, pos); + gst_caps_append_structure (caps, s); + } + } + + for (c = MAX (9, min_chans); c <= max_chans; ++c) { + GstAudioChannelPosition *ch_layout; + gint i; + + ch_layout = g_new (GstAudioChannelPosition, c); + for (i = 0; i < c; ++i) { + ch_layout[i] = GST_AUDIO_CHANNEL_POSITION_NONE; + } + s = get_channel_free_structure (in_structure); + gst_structure_set (s, "channels", G_TYPE_INT, c, NULL); + gst_audio_set_channel_positions (s, ch_layout); + gst_caps_append_structure (caps, s); + g_free (ch_layout); + } +} + +static GstCaps * +gst_alsa_detect_channels (GstObject * obj, snd_pcm_hw_params_t * hw_params, + GstCaps * in_caps) +{ + GstCaps *caps; + guint min, max; + gint min_chans, max_chans; + gint err; + guint i; + + GST_LOG_OBJECT (obj, "probing channels ..."); + + if ((err = snd_pcm_hw_params_get_channels_min (hw_params, &min)) < 0) + goto min_chan_error; + + if ((err = snd_pcm_hw_params_get_channels_max (hw_params, &max)) < 0) + goto max_chan_error; + + /* note: the above functions may return (guint) -1 */ + min_chans = min; + max_chans = max; + + if (min_chans < 0) { + min_chans = 1; + max_chans = GST_ALSA_MAX_CHANNELS; + } else if (max_chans < 0) { + max_chans = GST_ALSA_MAX_CHANNELS; + } + + if (min_chans > max_chans) { + gint temp; + + GST_WARNING_OBJECT (obj, "minimum channels > maximum channels (%d > %d), " + "please fix your soundcard drivers", min, max); + temp = min_chans; + min_chans = max_chans; + max_chans = temp; + } + + /* pro cards seem to return large numbers for min_channels */ + if (min_chans > GST_ALSA_MAX_CHANNELS) { + GST_DEBUG_OBJECT (obj, "min_chans = %u, looks like a pro card", min_chans); + if (max_chans < min_chans) { + max_chans = min_chans; + } else { + /* only support [max_chans; max_chans] for these cards for now + * to avoid inflating the source caps with loads of structures ... */ + min_chans = max_chans; + } + } else { + min_chans = MAX (min_chans, 1); + max_chans = MIN (GST_ALSA_MAX_CHANNELS, max_chans); + } + + GST_DEBUG_OBJECT (obj, "Min. channels = %d (%d)", min_chans, min); + GST_DEBUG_OBJECT (obj, "Max. channels = %d (%d)", max_chans, max); + + caps = gst_caps_new_empty (); + + for (i = 0; i < gst_caps_get_size (in_caps); ++i) { + GstStructure *s; + GType field_type; + gint c_min = min_chans; + gint c_max = max_chans; + + s = gst_caps_get_structure (in_caps, i); + /* the template caps might limit the number of channels (like alsasrc), + * in which case we don't want to return a superset, so hack around this + * for the two common cases where the channels are either a fixed number + * or a min/max range). Example: alsasrc template has channels = [1,2] and + * the detection will claim to support 8 channels for device 'plughw:0' */ + field_type = gst_structure_get_field_type (s, "channels"); + if (field_type == G_TYPE_INT) { + gst_structure_get_int (s, "channels", &c_min); + gst_structure_get_int (s, "channels", &c_max); + } else if (field_type == GST_TYPE_INT_RANGE) { + const GValue *val; + + val = gst_structure_get_value (s, "channels"); + c_min = CLAMP (gst_value_get_int_range_min (val), min_chans, max_chans); + c_max = CLAMP (gst_value_get_int_range_max (val), min_chans, max_chans); + } else { + c_min = min_chans; + c_max = max_chans; + } + + caps_add_channel_configuration (caps, s, c_min, c_max); + } + + gst_caps_unref (in_caps); + + return caps; + + /* ERRORS */ +min_chan_error: + { + GST_ERROR_OBJECT (obj, "failed to query minimum channel count: %s", + snd_strerror (err)); + return NULL; + } +max_chan_error: + { + GST_ERROR_OBJECT (obj, "failed to query maximum channel count: %s", + snd_strerror (err)); + return NULL; + } +} + +#ifndef GST_CHECK_VERSION +#define GST_CHECK_VERSION(major,minor,micro) \ + (GST_VERSION_MAJOR > (major) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR > (minor)) || \ + (GST_VERSION_MAJOR == (major) && GST_VERSION_MINOR == (minor) && GST_VERSION_MICRO >= (micro))) +#endif + +#if GST_CHECK_VERSION(0, 10, 18) +snd_pcm_t * +gst_alsa_open_iec958_pcm (GstObject * obj) +{ + char *iec958_pcm_name = NULL; + snd_pcm_t *pcm = NULL; + int res; + char devstr[256]; /* Storage for local 'default' device string */ + + /* + * Try and open our default iec958 device. Fall back to searching on card x + * if this fails, which should only happen on older alsa setups + */ + + /* The string will be one of these: + * SPDIF_CON: Non-audio flag not set: + * spdif:{AES0 0x0 AES1 0x82 AES2 0x0 AES3 0x2} + * SPDIF_CON: Non-audio flag set: + * spdif:{AES0 0x2 AES1 0x82 AES2 0x0 AES3 0x2} + */ + sprintf (devstr, + "iec958:{AES0 0x%02x AES1 0x%02x AES2 0x%02x AES3 0x%02x}", + IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO, + IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER, + 0, IEC958_AES3_CON_FS_48000); + + GST_DEBUG_OBJECT (obj, "Generated device string \"%s\"", devstr); + iec958_pcm_name = devstr; + + res = snd_pcm_open (&pcm, iec958_pcm_name, SND_PCM_STREAM_PLAYBACK, 0); + if (G_UNLIKELY (res < 0)) { + GST_DEBUG_OBJECT (obj, "failed opening IEC958 device: %s", + snd_strerror (res)); + pcm = NULL; + } + + return pcm; +} +#endif + + +/* + * gst_alsa_probe_supported_formats: + * + * Takes the template caps and returns the subset which is actually + * supported by this device. + * + */ + +GstCaps * +gst_alsa_probe_supported_formats (GstObject * obj, snd_pcm_t * handle, + const GstCaps * template_caps) +{ + snd_pcm_hw_params_t *hw_params; + snd_pcm_stream_t stream_type; + GstCaps *caps; + gint err; + + snd_pcm_hw_params_malloc (&hw_params); + if ((err = snd_pcm_hw_params_any (handle, hw_params)) < 0) + goto error; + + stream_type = snd_pcm_stream (handle); + + caps = gst_caps_copy (template_caps); + + if (!(caps = gst_alsa_detect_formats (obj, hw_params, caps))) + goto subroutine_error; + + if (!(caps = gst_alsa_detect_rates (obj, hw_params, caps))) + goto subroutine_error; + + if (!(caps = gst_alsa_detect_channels (obj, hw_params, caps))) + goto subroutine_error; + +#if GST_CHECK_VERSION(0, 10, 18) + /* Try opening IEC958 device to see if we can support that format (playback + * only for now but we could add SPDIF capture later) */ + if (stream_type == SND_PCM_STREAM_PLAYBACK) { + snd_pcm_t *pcm = gst_alsa_open_iec958_pcm (obj); + + if (G_LIKELY (pcm)) { + gst_caps_append (caps, gst_caps_new_simple ("audio/x-iec958", NULL)); + snd_pcm_close (pcm); + } + } +#endif + + snd_pcm_hw_params_free (hw_params); + return caps; + + /* ERRORS */ +error: + { + GST_ERROR_OBJECT (obj, "failed to query formats: %s", snd_strerror (err)); + snd_pcm_hw_params_free (hw_params); + return NULL; + } +subroutine_error: + { + GST_ERROR_OBJECT (obj, "failed to query formats"); + snd_pcm_hw_params_free (hw_params); + return NULL; + } +} + +static gchar * +gst_alsa_find_device_name_no_handle (GstObject * obj, const gchar * devcard, + gint device_num, snd_pcm_stream_t stream) +{ + snd_ctl_card_info_t *info = NULL; + snd_ctl_t *ctl = NULL; + gchar *ret = NULL; + gint dev = -1; + + GST_LOG_OBJECT (obj, "[%s] device=%d", devcard, device_num); + + if (snd_ctl_open (&ctl, devcard, 0) < 0) + return NULL; + + snd_ctl_card_info_malloc (&info); + if (snd_ctl_card_info (ctl, info) < 0) + goto done; + + while (snd_ctl_pcm_next_device (ctl, &dev) == 0 && dev >= 0) { + if (dev == device_num) { + snd_pcm_info_t *pcminfo; + + snd_pcm_info_malloc (&pcminfo); + snd_pcm_info_set_device (pcminfo, dev); + snd_pcm_info_set_subdevice (pcminfo, 0); + snd_pcm_info_set_stream (pcminfo, stream); + if (snd_ctl_pcm_info (ctl, pcminfo) < 0) { + snd_pcm_info_free (pcminfo); + break; + } + + ret = g_strdup (snd_pcm_info_get_name (pcminfo)); + snd_pcm_info_free (pcminfo); + GST_LOG_OBJECT (obj, "name from pcminfo: %s", GST_STR_NULL (ret)); + } + } + + if (ret == NULL) { + char *name = NULL; + gint card; + + GST_LOG_OBJECT (obj, "no luck so far, trying backup"); + card = snd_ctl_card_info_get_card (info); + snd_card_get_name (card, &name); + ret = g_strdup (name); + free (name); + } + +done: + snd_ctl_card_info_free (info); + snd_ctl_close (ctl); + + return ret; +} + +gchar * +gst_alsa_find_device_name (GstObject * obj, const gchar * device, + snd_pcm_t * handle, snd_pcm_stream_t stream) +{ + gchar *ret = NULL; + + if (device != NULL) { + gchar *dev, *comma; + gint devnum; + + GST_LOG_OBJECT (obj, "Trying to get device name from string '%s'", device); + + /* only want name:card bit, but not devices and subdevices */ + dev = g_strdup (device); + if ((comma = strchr (dev, ','))) { + *comma = '\0'; + devnum = atoi (comma + 1); + ret = gst_alsa_find_device_name_no_handle (obj, dev, devnum, stream); + } + g_free (dev); + } + + if (ret == NULL && handle != NULL) { + snd_pcm_info_t *info; + + GST_LOG_OBJECT (obj, "Trying to get device name from open handle"); + snd_pcm_info_malloc (&info); + snd_pcm_info (handle, info); + ret = g_strdup (snd_pcm_info_get_name (info)); + snd_pcm_info_free (info); + } + + GST_LOG_OBJECT (obj, "Device name for device '%s': %s", + GST_STR_NULL (device), GST_STR_NULL (ret)); + + return ret; +} + +/* elementfactory information */ +static const GstElementDetails gst_alsasink2_details = +GST_ELEMENT_DETAILS ("Audio sink (ALSA)", + "Sink/Audio", + "Output to a sound card via ALSA", + "Wim Taymans <wim@fluendo.com>"); + +#define DEFAULT_DEVICE "default" +#define DEFAULT_DEVICE_NAME "" +#define SPDIF_PERIOD_SIZE 1536 +#define SPDIF_BUFFER_SIZE 15360 + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +static void gst_alsasink2_init_interfaces (GType type); + +GST_BOILERPLATE_FULL (_k_GstAlsaSink, gst_alsasink2, GstAudioSink, + GST_TYPE_AUDIO_SINK, gst_alsasink2_init_interfaces); + +static void gst_alsasink2_finalise (GObject * object); +static void gst_alsasink2_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); +static void gst_alsasink2_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +static GstCaps *gst_alsasink2_getcaps (GstBaseSink * bsink); + +static gboolean gst_alsasink2_open (GstAudioSink * asink); +static gboolean gst_alsasink2_prepare (GstAudioSink * asink, + GstRingBufferSpec * spec); +static gboolean gst_alsasink2_unprepare (GstAudioSink * asink); +static gboolean gst_alsasink2_close (GstAudioSink * asink); +static guint gst_alsasink2_write (GstAudioSink * asink, gpointer data, + guint length); +static guint gst_alsasink2_delay (GstAudioSink * asink); +static void gst_alsasink2_reset (GstAudioSink * asink); + +static gint output_ref; /* 0 */ +static snd_output_t *output; /* NULL */ +static GStaticMutex output_mutex = G_STATIC_MUTEX_INIT; + + +#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) +# define ALSA_SINK2_FACTORY_ENDIANNESS "LITTLE_ENDIAN, BIG_ENDIAN" +#else +# define ALSA_SINK2_FACTORY_ENDIANNESS "BIG_ENDIAN, LITTLE_ENDIAN" +#endif + +static GstStaticPadTemplate alsasink2_sink_factory = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) { " ALSA_SINK2_FACTORY_ENDIANNESS " }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) 32, " + "depth = (int) 32, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; " + "audio/x-raw-int, " + "endianness = (int) { " ALSA_SINK2_FACTORY_ENDIANNESS " }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) 24, " + "depth = (int) 24, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; " + "audio/x-raw-int, " + "endianness = (int) { " ALSA_SINK2_FACTORY_ENDIANNESS " }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) 32, " + "depth = (int) 24, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; " + "audio/x-raw-int, " + "endianness = (int) { " ALSA_SINK2_FACTORY_ENDIANNESS " }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) 16, " + "depth = (int) 16, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; " + "audio/x-raw-int, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) 8, " + "depth = (int) 8, " + "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ];" + "audio/x-iec958") + ); + +static void +gst_alsasink2_finalise (GObject * object) +{ + _k_GstAlsaSink *sink = GST_ALSA_SINK2 (object); + + g_free (sink->device); + g_mutex_free (sink->alsa_lock); + + g_static_mutex_lock (&output_mutex); + --output_ref; + if (output_ref == 0) { + snd_output_close (output); + output = NULL; + } + g_static_mutex_unlock (&output_mutex); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_alsasink2_init_interfaces (GType type) +{ + gst_alsa_type_add_device_property_probe_interface (type); +} + +static void +gst_alsasink2_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details (element_class, &gst_alsasink2_details); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&alsasink2_sink_factory)); +} +static void +gst_alsasink2_class_init (_k_GstAlsaSinkClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSinkClass *gstbasesink_class; + GstBaseAudioSinkClass *gstbaseaudiosink_class; + GstAudioSinkClass *gstaudiosink_class; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass; + gstaudiosink_class = (GstAudioSinkClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_alsasink2_finalise); + gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_alsasink2_get_property); + gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_alsasink2_set_property); + + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_alsasink2_getcaps); + + gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_alsasink2_open); + gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_alsasink2_prepare); + gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_alsasink2_unprepare); + gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_alsasink2_close); + gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_alsasink2_write); + gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_alsasink2_delay); + gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_alsasink2_reset); + + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "ALSA device, as defined in an asound configuration file", + DEFAULT_DEVICE, G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE)); +} + +static void +gst_alsasink2_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + _k_GstAlsaSink *sink; + + sink = GST_ALSA_SINK2 (object); + + switch (prop_id) { + case PROP_DEVICE: + g_free (sink->device); + sink->device = g_value_dup_string (value); + /* setting NULL restores the default device */ + if (sink->device == NULL) { + sink->device = g_strdup (DEFAULT_DEVICE); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_alsasink2_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + _k_GstAlsaSink *sink; + + sink = GST_ALSA_SINK2 (object); + + switch (prop_id) { + case PROP_DEVICE: + g_value_set_string (value, sink->device); + break; + case PROP_DEVICE_NAME: + g_value_take_string (value, + gst_alsa_find_device_name (GST_OBJECT_CAST (sink), + sink->device, sink->handle, SND_PCM_STREAM_PLAYBACK)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_alsasink2_init (_k_GstAlsaSink * alsasink2, _k_GstAlsaSinkClass * g_class) +{ + GST_DEBUG_OBJECT (alsasink2, "initializing alsasink2"); + + alsasink2->device = g_strdup (DEFAULT_DEVICE); + alsasink2->handle = NULL; + alsasink2->cached_caps = NULL; + alsasink2->alsa_lock = g_mutex_new (); + + g_static_mutex_lock (&output_mutex); + if (output_ref == 0) { + snd_output_stdio_attach (&output, stdout, 0); + ++output_ref; + } + g_static_mutex_unlock (&output_mutex); +} + +#define CHECK(call, error) \ +G_STMT_START { \ +if ((err = call) < 0) \ + goto error; \ +} G_STMT_END; + +static GstCaps * +gst_alsasink2_getcaps (GstBaseSink * bsink) +{ + GstElementClass *element_class; + GstPadTemplate *pad_template; + _k_GstAlsaSink *sink = GST_ALSA_SINK2 (bsink); + GstCaps *caps; + + if (sink->handle == NULL) { + GST_DEBUG_OBJECT (sink, "device not open, using template caps"); + return NULL; /* base class will get template caps for us */ + } + + if (sink->cached_caps) { + GST_LOG_OBJECT (sink, "Returning cached caps"); + return gst_caps_ref (sink->cached_caps); + } + + element_class = GST_ELEMENT_GET_CLASS (sink); + pad_template = gst_element_class_get_pad_template (element_class, "sink"); + g_return_val_if_fail (pad_template != NULL, NULL); + + caps = gst_alsa_probe_supported_formats (GST_OBJECT (sink), sink->handle, + gst_pad_template_get_caps (pad_template)); + + if (caps) { + sink->cached_caps = gst_caps_ref (caps); + } + + GST_INFO_OBJECT (sink, "returning caps %" GST_PTR_FORMAT, caps); + + return caps; +} + +static int +set_hwparams (_k_GstAlsaSink * alsa) +{ + guint rrate; + gint err, dir; + snd_pcm_hw_params_t *params; + guint period_time, buffer_time; + + snd_pcm_hw_params_malloc (¶ms); + + GST_DEBUG_OBJECT (alsa, "Negotiating to %d channels @ %d Hz (format = %s) " + "SPDIF (%d)", alsa->channels, alsa->rate, + snd_pcm_format_name (alsa->format), alsa->iec958); + + /* start with requested values, if we cannot configure alsa for those values, + * we set these values to -1, which will leave the default alsa values */ + buffer_time = alsa->buffer_time; + period_time = alsa->period_time; + +retry: + /* choose all parameters */ + CHECK (snd_pcm_hw_params_any (alsa->handle, params), no_config); + /* set the interleaved read/write format */ + CHECK (snd_pcm_hw_params_set_access (alsa->handle, params, alsa->access), + wrong_access); + /* set the sample format */ +#if GST_CHECK_VERSION(0, 10, 18) + if (alsa->iec958) { + /* Try to use big endian first else fallback to le and swap bytes */ + if (snd_pcm_hw_params_set_format (alsa->handle, params, alsa->format) < 0) { + alsa->format = SND_PCM_FORMAT_S16_LE; + alsa->need_swap = TRUE; + GST_DEBUG_OBJECT (alsa, "falling back to little endian with swapping"); + } else { + alsa->need_swap = FALSE; + } + } +#endif + CHECK (snd_pcm_hw_params_set_format (alsa->handle, params, alsa->format), + no_sample_format); + /* set the count of channels */ + CHECK (snd_pcm_hw_params_set_channels (alsa->handle, params, alsa->channels), + no_channels); + /* set the stream rate */ + rrate = alsa->rate; + CHECK (snd_pcm_hw_params_set_rate_near (alsa->handle, params, &rrate, NULL), + no_rate); + if (rrate != alsa->rate) + goto rate_match; + + /* get and dump some limits */ + { + guint min, max; + + snd_pcm_hw_params_get_buffer_time_min (params, &min, &dir); + snd_pcm_hw_params_get_buffer_time_max (params, &max, &dir); + + GST_DEBUG_OBJECT (alsa, "buffer time %u, min %u, max %u", + alsa->buffer_time, min, max); + + snd_pcm_hw_params_get_period_time_min (params, &min, &dir); + snd_pcm_hw_params_get_period_time_max (params, &max, &dir); + + GST_DEBUG_OBJECT (alsa, "period time %u, min %u, max %u", + alsa->period_time, min, max); + + snd_pcm_hw_params_get_periods_min (params, &min, &dir); + snd_pcm_hw_params_get_periods_max (params, &max, &dir); + + GST_DEBUG_OBJECT (alsa, "periods min %u, max %u", min, max); + } + + /* now try to configure the buffer time and period time, if one + * of those fail, we fall back to the defaults and emit a warning. */ + if (buffer_time != ~0u && !alsa->iec958) { + /* set the buffer time */ + if ((err = snd_pcm_hw_params_set_buffer_time_near (alsa->handle, params, + &buffer_time, &dir)) < 0) { + GST_ELEMENT_WARNING (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set buffer time %i for playback: %s", + buffer_time, snd_strerror (err))); + /* disable buffer_time the next round */ + buffer_time = -1; + goto retry; + } + GST_DEBUG_OBJECT (alsa, "buffer time %u", buffer_time); + } + if (period_time != ~0u && !alsa->iec958) { + /* set the period time */ + if ((err = snd_pcm_hw_params_set_period_time_near (alsa->handle, params, + &period_time, &dir)) < 0) { + GST_ELEMENT_WARNING (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set period time %i for playback: %s", + period_time, snd_strerror (err))); + /* disable period_time the next round */ + period_time = -1; + goto retry; + } + GST_DEBUG_OBJECT (alsa, "period time %u", period_time); + } + + /* Set buffer size and period size manually for SPDIF */ + if (G_UNLIKELY (alsa->iec958)) { + snd_pcm_uframes_t buffer_size = SPDIF_BUFFER_SIZE; + snd_pcm_uframes_t period_size = SPDIF_PERIOD_SIZE; + + CHECK (snd_pcm_hw_params_set_buffer_size_near (alsa->handle, params, + &buffer_size), buffer_size); + CHECK (snd_pcm_hw_params_set_period_size_near (alsa->handle, params, + &period_size, NULL), period_size); + } + + /* write the parameters to device */ + CHECK (snd_pcm_hw_params (alsa->handle, params), set_hw_params); + + /* now get the configured values */ + CHECK (snd_pcm_hw_params_get_buffer_size (params, &alsa->buffer_size), + buffer_size); + CHECK (snd_pcm_hw_params_get_period_size (params, &alsa->period_size, &dir), + period_size); + + GST_DEBUG_OBJECT (alsa, "buffer size %lu, period size %lu", alsa->buffer_size, + alsa->period_size); + + snd_pcm_hw_params_free (params); + return 0; + + /* ERRORS */ +no_config: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Broken configuration for playback: no configurations available: %s", + snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +wrong_access: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Access type not available for playback: %s", snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +no_sample_format: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Sample format not available for playback: %s", snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +no_channels: + { + gchar *msg = NULL; + + if ((alsa->channels) == 1) + msg = g_strdup (_("Could not open device for playback in mono mode.")); + if ((alsa->channels) == 2) + msg = g_strdup (_("Could not open device for playback in stereo mode.")); + if ((alsa->channels) > 2) + msg = + g_strdup_printf (_ + ("Could not open device for playback in %d-channel mode."), + alsa->channels); + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (msg), (snd_strerror (err))); + g_free (msg); + snd_pcm_hw_params_free (params); + return err; + } +no_rate: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Rate %iHz not available for playback: %s", + alsa->rate, snd_strerror (err))); + return err; + } +rate_match: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Rate doesn't match (requested %iHz, get %iHz)", alsa->rate, err)); + snd_pcm_hw_params_free (params); + return -EINVAL; + } +buffer_size: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to get buffer size for playback: %s", snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +period_size: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to get period size for playback: %s", snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +set_hw_params: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set hw params for playback: %s", snd_strerror (err))); + snd_pcm_hw_params_free (params); + return err; + } +} + +static int +set_swparams (_k_GstAlsaSink * alsa) +{ + int err; + snd_pcm_sw_params_t *params; + + snd_pcm_sw_params_malloc (¶ms); + + /* get the current swparams */ + CHECK (snd_pcm_sw_params_current (alsa->handle, params), no_config); + /* start the transfer when the buffer is almost full: */ + /* (buffer_size / avail_min) * avail_min */ + CHECK (snd_pcm_sw_params_set_start_threshold (alsa->handle, params, + (alsa->buffer_size / alsa->period_size) * alsa->period_size), + start_threshold); + + /* allow the transfer when at least period_size samples can be processed */ + CHECK (snd_pcm_sw_params_set_avail_min (alsa->handle, params, + alsa->period_size), set_avail); + +#if GST_CHECK_ALSA_VERSION(1,0,16) + /* snd_pcm_sw_params_set_xfer_align() is deprecated, alignment is always 1 */ +#else + /* align all transfers to 1 sample */ + CHECK (snd_pcm_sw_params_set_xfer_align (alsa->handle, params, 1), set_align); +#endif + + /* write the parameters to the playback device */ + CHECK (snd_pcm_sw_params (alsa->handle, params), set_sw_params); + + snd_pcm_sw_params_free (params); + return 0; + + /* ERRORS */ +no_config: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to determine current swparams for playback: %s", + snd_strerror (err))); + snd_pcm_sw_params_free (params); + return err; + } +start_threshold: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set start threshold mode for playback: %s", + snd_strerror (err))); + snd_pcm_sw_params_free (params); + return err; + } +set_avail: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set avail min for playback: %s", snd_strerror (err))); + snd_pcm_sw_params_free (params); + return err; + } +#if !GST_CHECK_ALSA_VERSION(1,0,16) +set_align: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set transfer align for playback: %s", snd_strerror (err))); + snd_pcm_sw_params_free (params); + return err; + } +#endif +set_sw_params: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Unable to set sw params for playback: %s", snd_strerror (err))); + snd_pcm_sw_params_free (params); + return err; + } +} + +static gboolean +alsasink2_parse_spec (_k_GstAlsaSink * alsa, GstRingBufferSpec * spec) +{ + /* Initialize our boolean */ + alsa->iec958 = FALSE; + + switch (spec->type) { + case GST_BUFTYPE_LINEAR: + GST_DEBUG_OBJECT (alsa, + "Linear format : depth=%d, width=%d, sign=%d, bigend=%d", spec->depth, + spec->width, spec->sign, spec->bigend); + + alsa->format = snd_pcm_build_linear_format (spec->depth, spec->width, + spec->sign ? 0 : 1, spec->bigend ? 1 : 0); + break; + case GST_BUFTYPE_FLOAT: + switch (spec->format) { + case GST_FLOAT32_LE: + alsa->format = SND_PCM_FORMAT_FLOAT_LE; + break; + case GST_FLOAT32_BE: + alsa->format = SND_PCM_FORMAT_FLOAT_BE; + break; + case GST_FLOAT64_LE: + alsa->format = SND_PCM_FORMAT_FLOAT64_LE; + break; + case GST_FLOAT64_BE: + alsa->format = SND_PCM_FORMAT_FLOAT64_BE; + break; + default: + goto error; + } + break; + case GST_BUFTYPE_A_LAW: + alsa->format = SND_PCM_FORMAT_A_LAW; + break; + case GST_BUFTYPE_MU_LAW: + alsa->format = SND_PCM_FORMAT_MU_LAW; + break; +#if GST_CHECK_VERSION(0, 10, 18) + case GST_BUFTYPE_IEC958: + alsa->format = SND_PCM_FORMAT_S16_BE; + alsa->iec958 = TRUE; + break; +#endif + default: + goto error; + + } + alsa->rate = spec->rate; + alsa->channels = spec->channels; + alsa->buffer_time = spec->buffer_time; + alsa->period_time = spec->latency_time; + alsa->access = SND_PCM_ACCESS_RW_INTERLEAVED; + + return TRUE; + + /* ERRORS */ +error: + { + return FALSE; + } +} + +static gboolean +gst_alsasink2_open (GstAudioSink * asink) +{ + _k_GstAlsaSink *alsa; + gint err; + + alsa = GST_ALSA_SINK2 (asink); + + CHECK (snd_pcm_open (&alsa->handle, alsa->device, SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK), open_error); + GST_LOG_OBJECT (alsa, "Opened device %s", alsa->device); + + return TRUE; + + /* ERRORS */ +open_error: + { + if (err == -EBUSY) { + GST_ELEMENT_ERROR (alsa, RESOURCE, BUSY, + (_("Could not open audio device for playback. " + "Device is being used by another application.")), + ("Device '%s' is busy", alsa->device)); + } else { + GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, + (_("Could not open audio device for playback.")), + ("Playback open error on device '%s': %s", alsa->device, + snd_strerror (err))); + } + return FALSE; + } +} + +static gboolean +gst_alsasink2_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) +{ + _k_GstAlsaSink *alsa; + gint err; + + alsa = GST_ALSA_SINK2 (asink); + +#if GST_CHECK_VERSION(0, 10, 18) + if (spec->format == GST_IEC958) { + snd_pcm_close (alsa->handle); + alsa->handle = gst_alsa_open_iec958_pcm (GST_OBJECT (alsa)); + if (G_UNLIKELY (!alsa->handle)) { + goto no_iec958; + } + } +#endif + + if (!alsasink2_parse_spec (alsa, spec)) + goto spec_parse; + + CHECK (set_hwparams (alsa), hw_params_failed); + CHECK (set_swparams (alsa), sw_params_failed); + + alsa->bytes_per_sample = spec->bytes_per_sample; + spec->segsize = alsa->period_size * spec->bytes_per_sample; + spec->segtotal = alsa->buffer_size / alsa->period_size; + + { + snd_output_t *out_buf = NULL; + char *msg = NULL; + + snd_output_buffer_open (&out_buf); + snd_pcm_dump_hw_setup (alsa->handle, out_buf); + snd_output_buffer_string (out_buf, &msg); + GST_DEBUG_OBJECT (alsa, "Hardware setup: \n%s", msg); + snd_output_close (out_buf); + snd_output_buffer_open (&out_buf); + snd_pcm_dump_sw_setup (alsa->handle, out_buf); + snd_output_buffer_string (out_buf, &msg); + GST_DEBUG_OBJECT (alsa, "Software setup: \n%s", msg); + snd_output_close (out_buf); + } + + return TRUE; + + /* ERRORS */ +#if GST_CHECK_VERSION(0, 10, 18) +no_iec958: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, OPEN_WRITE, (NULL), + ("Could not open IEC958 (SPDIF) device for playback")); + return FALSE; + } +#endif +spec_parse: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Error parsing spec")); + return FALSE; + } +hw_params_failed: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Setting of hwparams failed: %s", snd_strerror (err))); + return FALSE; + } +sw_params_failed: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Setting of swparams failed: %s", snd_strerror (err))); + return FALSE; + } +} + +static gboolean +gst_alsasink2_unprepare (GstAudioSink * asink) +{ + _k_GstAlsaSink *alsa; + gint err; + + alsa = GST_ALSA_SINK2 (asink); + + CHECK (snd_pcm_drop (alsa->handle), drop); + + CHECK (snd_pcm_hw_free (alsa->handle), hw_free); + + return TRUE; + + /* ERRORS */ +drop: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Could not drop samples: %s", snd_strerror (err))); + return FALSE; + } +hw_free: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, SETTINGS, (NULL), + ("Could not free hw params: %s", snd_strerror (err))); + return FALSE; + } +} + +static gboolean +gst_alsasink2_close (GstAudioSink * asink) +{ + _k_GstAlsaSink *alsa = GST_ALSA_SINK2 (asink); + gint err; + + if (alsa->handle) { + CHECK (snd_pcm_close (alsa->handle), close_error); + alsa->handle = NULL; + } + gst_caps_replace (&alsa->cached_caps, NULL); + + return TRUE; + + /* ERRORS */ +close_error: + { + GST_ELEMENT_ERROR (alsa, RESOURCE, CLOSE, (NULL), + ("Playback close error: %s", snd_strerror (err))); + return FALSE; + } +} + + +/* + * Underrun and suspend recovery + */ +static gint +xrun_recovery (_k_GstAlsaSink * alsa, snd_pcm_t * handle, gint err) +{ + GST_DEBUG_OBJECT (alsa, "xrun recovery %d", err); + + if (err == -EPIPE) { /* under-run */ + err = snd_pcm_prepare (handle); + if (err < 0) { + GST_WARNING_OBJECT (alsa, + "Can't recovery from underrun, prepare failed: %s", + snd_strerror (err)); + } + return 0; + } else if (err == -ESTRPIPE) { + while ((err = snd_pcm_resume (handle)) == -EAGAIN) + g_usleep (100); /* wait until the suspend flag is released */ + + if (err < 0) { + err = snd_pcm_prepare (handle); + if (err < 0) { + GST_WARNING_OBJECT (alsa, + "Can't recovery from suspend, prepare failed: %s", + snd_strerror (err)); + } + } + return 0; + } + return err; +} + +static guint +gst_alsasink2_write (GstAudioSink * asink, gpointer data, guint length) +{ + _k_GstAlsaSink *alsa; + gint err; + gint cptr; + gint16 *ptr = data; + + alsa = GST_ALSA_SINK2 (asink); + + if (alsa->iec958 && alsa->need_swap) { + guint i; + + GST_DEBUG_OBJECT (asink, "swapping bytes"); + for (i = 0; i < length / 2; i++) { + ptr[i] = GUINT16_SWAP_LE_BE (ptr[i]); + } + } + + GST_LOG_OBJECT (asink, "received audio samples buffer of %u bytes", length); + + cptr = length / alsa->bytes_per_sample; + + GST_ALSA_SINK2_LOCK (asink); + while (cptr > 0) { + /* start by doing a blocking wait for free space. Set the timeout + * to 4 times the period time */ + err = snd_pcm_wait (alsa->handle, (4 * alsa->period_time / 1000)); + if (err < 0) { + GST_DEBUG_OBJECT (asink, "wait timeout, %d", err); + } else { + err = snd_pcm_writei (alsa->handle, ptr, cptr); + } + + GST_DEBUG_OBJECT (asink, "written %d frames out of %d", err, cptr); + if (err < 0) { + GST_DEBUG_OBJECT (asink, "Write error: %s", snd_strerror (err)); + if (err == -EAGAIN) { + continue; + } else if (xrun_recovery (alsa, alsa->handle, err) < 0) { + goto write_error; + } + continue; + } + + ptr += snd_pcm_frames_to_bytes (alsa->handle, err); + cptr -= err; + } + GST_ALSA_SINK2_UNLOCK (asink); + + return length - (cptr * alsa->bytes_per_sample); + +write_error: + { + GST_ALSA_SINK2_UNLOCK (asink); + return length; /* skip one period */ + } +} + +static guint +gst_alsasink2_delay (GstAudioSink * asink) +{ + _k_GstAlsaSink *alsa; + snd_pcm_sframes_t delay; + int res; + + alsa = GST_ALSA_SINK2 (asink); + + res = snd_pcm_delay (alsa->handle, &delay); + if (G_UNLIKELY (res < 0)) { + /* on errors, report 0 delay */ + GST_DEBUG_OBJECT (alsa, "snd_pcm_delay returned %d", res); + delay = 0; + } + if (G_UNLIKELY (delay < 0)) { + /* make sure we never return a negative delay */ + GST_WARNING_OBJECT (alsa, "snd_pcm_delay returned negative delay"); + delay = 0; + } + + return delay; +} + +static void +gst_alsasink2_reset (GstAudioSink * asink) +{ + _k_GstAlsaSink *alsa; + gint err; + + alsa = GST_ALSA_SINK2 (asink); + + GST_ALSA_SINK2_LOCK (asink); + GST_DEBUG_OBJECT (alsa, "drop"); + CHECK (snd_pcm_drop (alsa->handle), drop_error); + GST_DEBUG_OBJECT (alsa, "prepare"); + CHECK (snd_pcm_prepare (alsa->handle), prepare_error); + GST_DEBUG_OBJECT (alsa, "reset done"); + GST_ALSA_SINK2_UNLOCK (asink); + + return; + + /* ERRORS */ +drop_error: + { + GST_ERROR_OBJECT (alsa, "alsa-reset: pcm drop error: %s", + snd_strerror (err)); + GST_ALSA_SINK2_UNLOCK (asink); + return; + } +prepare_error: + { + GST_ERROR_OBJECT (alsa, "alsa-reset: pcm prepare error: %s", + snd_strerror (err)); + GST_ALSA_SINK2_UNLOCK (asink); + return; + } +} + +static void +gst_alsa_error_wrapper (const char *file, int line, const char *function, + int err, const char *fmt, ...) +{ +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + int err; + + if (!gst_element_register (plugin, "_k_alsasink", GST_RANK_PRIMARY, + GST_TYPE_ALSA_SINK2)) + return FALSE; + + err = snd_lib_error_set_handler (gst_alsa_error_wrapper); + if (err != 0) + GST_WARNING ("failed to set alsa error handler"); + + return TRUE; +} + +#define PACKAGE "" +GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "_k_alsa", + "ALSA plugin library (hotfixed)", + plugin_init, "0.1", "LGPL", "Phonon-GStreamer", "") +#undef PACKAGE diff --git a/src/3rdparty/phonon/gstreamer/alsasink2.h b/src/3rdparty/phonon/gstreamer/alsasink2.h new file mode 100644 index 0000000..bec9933 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/alsasink2.h @@ -0,0 +1,87 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans <wim@fluendo.com> + * Copyright (C) 2008 Matthias Kretz <kretz@kde.org> + * + * gstalsasink2.h: + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + + +#ifndef ALSASINK2_H +#define ALSASINK2_H + +#include <gst/gst.h> +#include <gst/audio/gstaudiosink.h> +#include <alsa/asoundlib.h> + +G_BEGIN_DECLS + +#define GST_TYPE_ALSA_SINK2 (gst_alsasink2_get_type()) +#define GST_ALSA_SINK2(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ALSA_SINK2,_k_GstAlsaSink)) +#define GST_ALSA_SINK2_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ALSA_SINK2,_k_GstAlsaSinkClass)) +#define GST_IS_ALSA_SINK2(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ALSA_SINK2)) +#define GST_IS_ALSA_SINK2_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ALSA_SINK2)) +#define GST_ALSA_SINK2_CAST(obj) ((_k_GstAlsaSink *) (obj)) + +typedef struct _k_GstAlsaSink _k_GstAlsaSink; +typedef struct _k_GstAlsaSinkClass _k_GstAlsaSinkClass; + +#define GST_ALSA_SINK2_GET_LOCK(obj) (GST_ALSA_SINK2_CAST (obj)->alsa_lock) +#define GST_ALSA_SINK2_LOCK(obj) (g_mutex_lock (GST_ALSA_SINK2_GET_LOCK (obj))) +#define GST_ALSA_SINK2_UNLOCK(obj) (g_mutex_unlock (GST_ALSA_SINK2_GET_LOCK (obj))) + +/** + * _k_GstAlsaSink: + * + * Opaque data structure + */ +struct _k_GstAlsaSink { + GstAudioSink sink; + + gchar *device; + + snd_pcm_t *handle; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + + snd_pcm_access_t access; + snd_pcm_format_t format; + guint rate; + guint channels; + gint bytes_per_sample; + gboolean iec958; + gboolean need_swap; + + guint buffer_time; + guint period_time; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; + + GstCaps *cached_caps; + + GMutex *alsa_lock; +}; + +struct _k_GstAlsaSinkClass { + GstAudioSinkClass parent_class; +}; + +GType gst_alsasink2_get_type(void); + +G_END_DECLS + +#endif /* ALSASINK2_H */ diff --git a/src/3rdparty/phonon/gstreamer/artssink.cpp b/src/3rdparty/phonon/gstreamer/artssink.cpp new file mode 100644 index 0000000..ff56da9 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/artssink.cpp @@ -0,0 +1,277 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +/***************************************** + * + * This is an aRts plugin for GStreamer + * + ****************************************/ + +#include <gst/gst.h> +#include <gst/audio/audio.h> +#include <gst/audio/gstaudiosink.h> +#include "artssink.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +static GstStaticPadTemplate sinktemplate = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ( + "audio/x-raw-int, " + "width = (int) { 8, 16 }, " + "depth = (int) { 8, 16 }, " + "endianness = (int) BYTE_ORDER, " + "channels = (int) { 1, 2 }, " + "rate = (int) [ 8000, 96000 ]" + ) +); + +typedef int (*Ptr_arts_init)(); +typedef arts_stream_t (*Ptr_arts_play_stream)(int, int, int, const char*); +typedef int (*Ptr_arts_close_stream)(arts_stream_t); +typedef int (*Ptr_arts_stream_get)(arts_stream_t, arts_parameter_t_enum); +typedef int (*Ptr_arts_stream_set)(arts_stream_t, arts_parameter_t_enum, int value); +typedef int (*Ptr_arts_write)(arts_stream_t, const void *, int); +typedef int (*Ptr_arts_suspended)(); +typedef void (*Ptr_arts_free)(); + +static Ptr_arts_init p_arts_init = 0; +static Ptr_arts_play_stream p_arts_play_stream = 0; +static Ptr_arts_close_stream p_arts_close_stream = 0; +static Ptr_arts_stream_get p_arts_stream_get= 0; +static Ptr_arts_stream_set p_arts_stream_set= 0; +static Ptr_arts_write p_arts_write = 0; +static Ptr_arts_suspended p_arts_suspended = 0; +static Ptr_arts_free p_arts_free = 0; + +static void arts_sink_dispose (GObject * object); +static void arts_sink_reset (GstAudioSink * asink); +static void arts_sink_finalize (GObject * object); +static GstCaps *arts_sink_get_caps (GstBaseSink * bsink); +static gboolean arts_sink_open (GstAudioSink * asink); +static gboolean arts_sink_close (GstAudioSink * asink); +static gboolean arts_sink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec); +static gboolean arts_sink_unprepare (GstAudioSink * asink); +static guint arts_sink_write (GstAudioSink * asink, gpointer data, guint length); +static guint arts_sink_delay (GstAudioSink * asink); + +static gboolean connected = false; +static gboolean init = false; +static int sinkCount; + +GST_BOILERPLATE (ArtsSink, arts_sink, GstAudioSink, GST_TYPE_AUDIO_SINK) + +// ArtsSink args +enum +{ + ARG_0, + ARG_ARTSSINK +}; + +/* open the device with given specs */ +gboolean arts_sink_open(GstAudioSink *sink) +{ + Q_UNUSED(sink); + + // We already have an open connection to this device + if (!init) { + GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE, (NULL), ("Could not connect to aRts", NULL)); + return false; + } else if (connected) { + GST_ELEMENT_ERROR (sink, RESOURCE, BUSY, (NULL), ("Device is busy", NULL)); + return false; + } + + // Check if all symbols were resolved + if (!(p_arts_init && p_arts_play_stream && p_arts_close_stream + && p_arts_stream_get && p_arts_stream_set && p_arts_write && p_arts_free)) + return FALSE; + + // Check if arts_init succeeded + if (!init) + return false; + + return true; +} + +/* prepare resources and state to operate with the given specs */ +static gboolean arts_sink_prepare(GstAudioSink *sink, GstRingBufferSpec *spec) +{ + ArtsSink *asink = (ArtsSink*)sink; + + if (!init) + return false; + + asink->samplerate = spec->rate; + asink->samplebits = spec->depth; + asink->channels = spec->channels; + asink->bytes_per_sample = spec->bytes_per_sample; + + static int id = 0; + asink->stream = p_arts_play_stream(spec->rate, spec->depth, spec->channels, + QString("gstreamer-%0").arg(id++).toLatin1().constData()); + if (asink->stream) + connected = true; + + return connected; +} + +/* undo anything that was done in prepare() */ +static gboolean arts_sink_unprepare(GstAudioSink *sink) +{ + Q_UNUSED(sink); + ArtsSink *asink = (ArtsSink*)sink; + if (init && connected) { + p_arts_close_stream(asink->stream); + connected = false; + } + return true; +} + +/* close the device */ +static gboolean arts_sink_close(GstAudioSink *sink) +{ + Q_UNUSED(sink); + return true; +} + +/* write samples to the device */ +static guint arts_sink_write(GstAudioSink *sink, gpointer data, guint length) +{ + ArtsSink *asink = (ArtsSink*)sink; + + if (!init) + return 0; + + int errorcode = p_arts_write(asink->stream, (char*)data, length); + + if (errorcode < 0) + GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL), ("Could not write to device.", NULL)); + + return errorcode > 0 ? errorcode : 0; +} + +/* get number of samples queued in the device */ +static guint arts_sink_delay(GstAudioSink *sink) +{ + ArtsSink *asink = (ArtsSink*)sink; + if (!init) + return 0; + + // We get results in millisecons so we have to caculate the approximate size in samples + guint delay = p_arts_stream_get(asink->stream, ARTS_P_SERVER_LATENCY) * (asink->samplerate / 1000); + return delay; +} + +/* reset the audio device, unblock from a write */ +static void arts_sink_reset(GstAudioSink *sink) +{ + // ### We are currently unable to gracefully recover + // after artsd has been restarted or killed. + Q_UNUSED(sink); +} + +// Register element details +static void arts_sink_base_init (gpointer g_class) { + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + static gchar longname[] = "Experimental aRts sink", + klass[] = "Sink/Audio", + description[] = "aRts Audio Output Device", + author[] = "Nokia Corporation and/or its subsidiary(-ies) <qt-info@nokia.com>"; + GstElementDetails details = GST_ELEMENT_DETAILS (longname, + klass, + description, + author); + gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&sinktemplate)); + gst_element_class_set_details (gstelement_class, &details); +} + +static void arts_sink_class_init (ArtsSinkClass * klass) +{ + parent_class = (GstAudioSinkClass*)g_type_class_peek_parent(klass); + + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = GST_DEBUG_FUNCPTR (arts_sink_finalize); + gobject_class->dispose = GST_DEBUG_FUNCPTR (arts_sink_dispose); + + GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (arts_sink_get_caps); + + GstAudioSinkClass *gstaudiosink_class = (GstAudioSinkClass*)klass; + gstaudiosink_class->open = GST_DEBUG_FUNCPTR(arts_sink_open); + gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR(arts_sink_prepare); + gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR(arts_sink_unprepare); + gstaudiosink_class->close = GST_DEBUG_FUNCPTR(arts_sink_close); + gstaudiosink_class->write = GST_DEBUG_FUNCPTR(arts_sink_write); + gstaudiosink_class->delay = GST_DEBUG_FUNCPTR(arts_sink_delay); + gstaudiosink_class->reset = GST_DEBUG_FUNCPTR(arts_sink_reset); +} + +static void arts_sink_init (ArtsSink * src, ArtsSinkClass * g_class) +{ + Q_UNUSED(g_class); + GST_DEBUG_OBJECT (src, "initializing artssink"); + src->stream = 0; + + p_arts_init = (Ptr_arts_init)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_init"); + p_arts_play_stream = (Ptr_arts_play_stream)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_play_stream"); + p_arts_close_stream = (Ptr_arts_close_stream)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_close_stream"); + p_arts_stream_get = (Ptr_arts_stream_get)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_stream_get"); + p_arts_stream_set = (Ptr_arts_stream_set)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_stream_set"); + p_arts_write = (Ptr_arts_write)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_write"); + p_arts_suspended = (Ptr_arts_suspended)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_suspended"); + p_arts_free = (Ptr_arts_free)QLibrary::resolve(QLatin1String("artsc"), 0, "arts_free"); + + if (!sinkCount) { + int errorcode = p_arts_init(); + if (!errorcode) { + init = TRUE; + } + } + sinkCount ++; +} + +static void arts_sink_dispose (GObject * object) +{ + Q_UNUSED(object); + if (--sinkCount == 0) { + p_arts_free(); + } +} + +static void arts_sink_finalize (GObject * object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstCaps *arts_sink_get_caps (GstBaseSink * bsink) +{ + Q_UNUSED(bsink); + return NULL; +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/artssink.h b/src/3rdparty/phonon/gstreamer/artssink.h new file mode 100644 index 0000000..169e73e --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/artssink.h @@ -0,0 +1,91 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __ARTS_SINK_H__ +#define __ARTS_SINK_H__ + +#include <QtCore> +#include <sys/types.h> +#include <gst/gst.h> +#include <gst/audio/gstaudiosink.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +G_BEGIN_DECLS + +extern "C" { + +#define GST_TYPE_ARTS_SINK (arts_sink_get_type()) +#define GST_ARTS_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_ARTS_SINK,ArtsSink)) +#define GST_ARTS_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_ARTS_SINK,ArtsSinkClass)) +#define GST_IS_ARTS_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_ARTS_SINK)) +#define GST_IS_ARTS_SINK_CLASS(klass)(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_ARTS_SINK)) + +typedef struct _ArtsSink ArtsSink; +typedef struct _ArtsSinkClass ArtsSinkClass; + +enum arts_parameter_t_enum { + ARTS_P_BUFFER_SIZE = 1, + ARTS_P_BUFFER_TIME = 2, + ARTS_P_BUFFER_SPACE = 3, + ARTS_P_SERVER_LATENCY = 4, + ARTS_P_TOTAL_LATENCY = 5, + ARTS_P_BLOCKING = 6, + ARTS_P_PACKET_SIZE = 7, + ARTS_P_PACKET_COUNT = 8, + ARTS_P_PACKET_SETTINGS = 9 +}; + +typedef void *arts_stream_t; + +struct _ArtsSink { + GstAudioSink sink; + arts_stream_t stream; + int samplerate; + int samplebits; + int channels; + int bytes_per_sample; +}; + +struct GConfClient; +struct GError; +typedef void (*Ptr_g_type_init)(); +typedef GConfClient* (*Ptr_gconf_client_get_default)(); +typedef char* (*Ptr_gconf_client_get_string)(GConfClient*, const char*, GError **); +typedef void (*Ptr_g_object_unref)(void *); +typedef void (*Ptr_g_error_free)(GError *); +typedef void (*Ptr_g_free)(void*); + +struct _ArtsSinkClass { + GstAudioSinkClass parent_class; +}; + +GType arts_sink_get_type (void); +} +G_END_DECLS + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // __ARTS_SINK_H__ diff --git a/src/3rdparty/phonon/gstreamer/audioeffect.cpp b/src/3rdparty/phonon/gstreamer/audioeffect.cpp new file mode 100644 index 0000000..db72c8b --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audioeffect.cpp @@ -0,0 +1,78 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "common.h" +#include "backend.h" +#include "medianode.h" +#include "effectmanager.h" +#include "audioeffect.h" +#include "gsthelper.h" + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +AudioEffect::AudioEffect(Backend *backend, int effectId, QObject *parent) + : Effect(backend, parent, AudioSource | AudioSink) +{ + static int count = 0; + m_name = "AudioEffect" + QString::number(count++); + QList<EffectInfo*> audioEffects = backend->effectManager()->audioEffects(); + if (effectId >= 0 && effectId < audioEffects.size()) { + m_effectName = audioEffects[effectId]->name(); + init(); + } else { + Q_ASSERT(0); // Effect ID out of range + } +} + +GstElement* AudioEffect::createEffectBin() +{ + GstElement *audioBin = gst_bin_new(NULL); + + // We need a queue to handle tee-connections from parent node + GstElement *queue= gst_element_factory_make ("queue", NULL); + gst_bin_add(GST_BIN(audioBin), queue); + + GstElement *mconv= gst_element_factory_make ("audioconvert", NULL); + gst_bin_add(GST_BIN(audioBin), mconv); + + m_effectElement = gst_element_factory_make (qPrintable(m_effectName), NULL); + gst_bin_add(GST_BIN(audioBin), m_effectElement); + + //Link src pad + GstPad *srcPad= gst_element_get_pad (m_effectElement, "src"); + gst_element_add_pad (audioBin, gst_ghost_pad_new ("src", srcPad)); + gst_object_unref (srcPad); + + //Link sink pad + gst_element_link_many(queue, mconv, m_effectElement, (const char*)NULL); + GstPad *sinkpad = gst_element_get_pad (queue, "sink"); + gst_element_add_pad (audioBin, gst_ghost_pad_new ("sink", sinkpad)); + gst_object_unref (sinkpad); + return audioBin; +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE +#include "moc_audioeffect.cpp" diff --git a/src/3rdparty/phonon/gstreamer/audioeffect.h b/src/3rdparty/phonon/gstreamer/audioeffect.h new file mode 100644 index 0000000..3a985e5 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audioeffect.h @@ -0,0 +1,55 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_AUDIOEFFECT_H +#define Phonon_GSTREAMER_AUDIOEFFECT_H + +#include "common.h" +#include "effect.h" +#include "medianode.h" + +#include <phonon/effectparameter.h> +#include <phonon/effectinterface.h> + +#include <QtCore/QObject> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + class AudioOutput; + class EffectInfo; + + class AudioEffect : public Effect + { + Q_OBJECT + public: + AudioEffect (Backend *backend, int effectId, QObject *parent); + protected: + GstElement* createEffectBin(); + GstElement* audioElement() { return m_effectBin; } + QString m_effectName; + }; +}} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_AUDIOEFFECT_H diff --git a/src/3rdparty/phonon/gstreamer/audiooutput.cpp b/src/3rdparty/phonon/gstreamer/audiooutput.cpp new file mode 100644 index 0000000..138a7e4 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audiooutput.cpp @@ -0,0 +1,257 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + Copyright (C) 2008 Matthias Kretz <kretz@kde.org> + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "common.h" +#include "audiooutput.h" +#include "backend.h" +#include "mediaobject.h" +#include "gsthelper.h" +#include <phonon/audiooutput.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +AudioOutput::AudioOutput(Backend *backend, QObject *parent) + : QObject(parent) + , MediaNode(backend, AudioSink) + , m_volumeLevel(1.0) + , m_device(0) // ### get from backend + , m_volumeElement(0) + , m_audioBin(0) + , m_audioSink(0) + , m_conv(0) +{ + static int count = 0; + m_name = "AudioOutput" + QString::number(count++); + if (m_backend->isValid()) { + g_set_application_name(qApp->applicationName().toUtf8()); + m_audioBin = gst_bin_new (NULL); + gst_object_ref (GST_OBJECT (m_audioBin)); + gst_object_sink (GST_OBJECT (m_audioBin)); + + m_conv = gst_element_factory_make ("audioconvert", NULL); + + // Get category from parent + Phonon::Category category = Phonon::NoCategory; + if (Phonon::AudioOutput *audioOutput = qobject_cast<Phonon::AudioOutput *>(parent)) + category = audioOutput->category(); + + m_audioSink = m_backend->deviceManager()->createAudioSink(category); + m_volumeElement = gst_element_factory_make ("volume", NULL); + GstElement *queue = gst_element_factory_make ("queue", NULL); + GstElement *audioresample = gst_element_factory_make ("audioresample", NULL); + + if (queue && m_audioBin && m_conv && audioresample && m_audioSink && m_volumeElement) { + gst_bin_add_many (GST_BIN (m_audioBin), queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL); + + if (gst_element_link_many (queue, m_conv, audioresample, m_volumeElement, m_audioSink, (const char*)NULL)) { + // Add ghost sink for audiobin + GstPad *audiopad = gst_element_get_pad (queue, "sink"); + gst_element_add_pad (m_audioBin, gst_ghost_pad_new ("sink", audiopad)); + gst_object_unref (audiopad); + m_isValid = true; // Initialization ok, accept input + } + } + } +} + +void AudioOutput::mediaNodeEvent(const MediaNodeEvent *event) +{ + if (!m_audioBin) + return; + + switch (event->type()) { + + default: + break; + } +} + + +AudioOutput::~AudioOutput() +{ + if (m_audioBin) { + gst_element_set_state (m_audioBin, GST_STATE_NULL); + gst_object_unref (m_audioBin); + } +} + +qreal AudioOutput::volume() const +{ + return m_volumeLevel; +} + +int AudioOutput::outputDevice() const +{ + return m_device; +} + +void AudioOutput::setVolume(qreal newVolume) +{ + if (newVolume > 2.0 ) + newVolume = 2.0; + else if (newVolume < 0.0) + newVolume = 0.0; + + if (newVolume == m_volumeLevel) + return; + + m_volumeLevel = newVolume; + + if (m_volumeElement) { + g_object_set(G_OBJECT(m_volumeElement), "volume", newVolume, (const char*)NULL); + } + + emit volumeChanged(newVolume); +} + +bool AudioOutput::setOutputDevice(int newDevice) +{ + m_backend->logMessage(Q_FUNC_INFO + QString::number(newDevice), Backend::Info, this); + if (newDevice == m_device) + return true; + + if (root()) { + root()->saveState(); + if (gst_element_set_state(root()->pipeline(), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) + return false; + } + + bool success = false; + const QList<AudioDevice> deviceList = m_backend->deviceManager()->audioOutputDevices(); + if (m_audioSink && newDevice >= 0 && newDevice < deviceList.size()) { + // Save previous state + GstState oldState = GST_STATE(m_audioSink); + const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device"); + const QByteArray deviceId = deviceList.at(newDevice).gstId; + m_device = newDevice; + + // We test if the device can be opened by checking if it can go from NULL to READY state + gst_element_set_state(m_audioSink, GST_STATE_NULL); + success = GstHelper::setProperty(m_audioSink, "device", deviceId); + if (success) { + success = (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS); + } + if (!success) { // Revert state + m_backend->logMessage(Q_FUNC_INFO + + QLatin1String(" Failed to change device ") + + deviceId, Backend::Info, this); + + GstHelper::setProperty(m_audioSink, "device", oldDeviceValue); + gst_element_set_state(m_audioSink, oldState); + } else { + m_backend->logMessage(Q_FUNC_INFO + + QLatin1String(" Successfully changed device ") + + deviceId, Backend::Info, this); + } + + // Note the stopped state should not really be neccessary, but seems to be required to + // properly reset after changing the audio state + if (root()) { + QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); + root()->resumeState(); + } + } + return success; +} + +#if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 2, 0)) +bool AudioOutput::setOutputDevice(const AudioOutputDevice &newDevice) +{ + m_backend->logMessage(Q_FUNC_INFO, Backend::Info, this); + if (!m_audioSink || !newDevice.isValid()) { + return false; + } + const QVariant driver = newDevice.property("driver"); + if (!driver.isValid()) { + return setOutputDevice(newDevice.index()); + } + if (newDevice.index() == m_device) { + return true; + } + + if (root()) { + root()->saveState(); + if (gst_element_set_state(root()->pipeline(), GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) + return false; + } + + // Save previous state + const GstState oldState = GST_STATE(m_audioSink); + const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device"); + + const QByteArray sinkName = GstHelper::property(m_audioSink, "name"); + if (sinkName == "alsasink" || sinkName == "alsasink2") { + if (driver.toByteArray() != "alsa") { + return false; + } + } + + const QVariant deviceIdsProperty = newDevice.property("deviceIds"); + QStringList deviceIds; + if (deviceIdsProperty.type() == QVariant::StringList) { + deviceIds = deviceIdsProperty.toStringList(); + } else if (deviceIdsProperty.type() == QVariant::String) { + deviceIds += deviceIdsProperty.toString(); + } + + // We test if the device can be opened by checking if it can go from NULL to READY state + foreach (const QString &deviceId, deviceIds) { + gst_element_set_state(m_audioSink, GST_STATE_NULL); + if (GstHelper::setProperty(m_audioSink, "device", deviceId.toUtf8())) { + m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") + + deviceId + QLatin1String(") succeeded"), Backend::Info, this); + if (gst_element_set_state(m_audioSink, oldState) == GST_STATE_CHANGE_SUCCESS) { + m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") + + deviceId + QLatin1String(" succeeded"), Backend::Info, this); + m_device = newDevice.index(); + if (root()) { + QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); + root()->resumeState(); + } + return true; + } else { + m_backend->logMessage(Q_FUNC_INFO + QLatin1String("go to old state on device") + + deviceId + QLatin1String(" failed"), Backend::Info, this); + } + } else { + m_backend->logMessage(Q_FUNC_INFO + QLatin1String("setProperty(device,") + + deviceId + QLatin1String(") failed"), Backend::Info, this); + } + } + // Revert state + GstHelper::setProperty(m_audioSink, "device", oldDeviceValue); + gst_element_set_state(m_audioSink, oldState); + + if (root()) { + QMetaObject::invokeMethod(root(), "setState", Qt::QueuedConnection, Q_ARG(State, StoppedState)); + root()->resumeState(); + } + + return false; +} +#endif + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE +#include "moc_audiooutput.cpp" diff --git a/src/3rdparty/phonon/gstreamer/audiooutput.h b/src/3rdparty/phonon/gstreamer/audiooutput.h new file mode 100644 index 0000000..846fa7b --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audiooutput.h @@ -0,0 +1,82 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + Copyright (C) 2008 Matthias Kretz <kretz@kde.org> + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_AUDIOOUTPUT_H +#define Phonon_GSTREAMER_AUDIOOUTPUT_H + +#include "common.h" +#include "medianode.h" + +#include <phonon/audiooutputinterface.h> +#include <phonon/phononnamespace.h> + +#include <QtCore/QFile> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +class AudioOutput : public QObject, public AudioOutputInterface, public MediaNode +{ + Q_OBJECT + Q_INTERFACES(Phonon::AudioOutputInterface Phonon::Gstreamer::MediaNode) +public: + AudioOutput(Backend *backend, QObject *parent); + ~AudioOutput(); + + qreal volume() const; + int outputDevice() const; + void setVolume(qreal newVolume); + bool setOutputDevice(int newDevice); +#if (PHONON_VERSION >= PHONON_VERSION_CHECK(4, 2, 0)) + bool setOutputDevice(const AudioOutputDevice &newDevice); +#endif + +public: + GstElement *audioElement() + { + Q_ASSERT(m_audioBin); + return m_audioBin; + } + + void mediaNodeEvent(const MediaNodeEvent *event); + +Q_SIGNALS: + void volumeChanged(qreal newVolume); + void audioDeviceFailed(); + +private: + + qreal m_volumeLevel; + int m_device; + + GstElement *m_volumeElement; + GstElement *m_audioBin; + GstElement *m_audioSink; + GstElement *m_conv; +}; +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_AUDIOOUTPUT_H diff --git a/src/3rdparty/phonon/gstreamer/backend.cpp b/src/3rdparty/phonon/gstreamer/backend.cpp new file mode 100644 index 0000000..d05f6a6 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/backend.cpp @@ -0,0 +1,455 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "common.h" +#include "backend.h" +#include "audiooutput.h" +#include "audioeffect.h" +#include "mediaobject.h" +#include "videowidget.h" +#include "devicemanager.h" +#include "effectmanager.h" +#include "message.h" +#include "volumefadereffect.h" +#include <gst/interfaces/propertyprobe.h> + +#include <QtCore/QSet> +#include <QtCore/QVariant> +#include <QtCore/QtPlugin> + +QT_BEGIN_NAMESPACE + +Q_EXPORT_PLUGIN2(phonon_gstreamer, Phonon::Gstreamer::Backend) + +namespace Phonon +{ +namespace Gstreamer +{ + +class MediaNode; + +Backend::Backend(QObject *parent, const QVariantList &) + : QObject(parent) + , m_deviceManager(0) + , m_effectManager(0) + , m_debugLevel(Warning) + , m_isValid(false) +{ + GError *err = 0; + bool wasInit = gst_init_check(0, 0, &err); //init gstreamer: must be called before any gst-related functions + if (err) + g_error_free(err); + + qRegisterMetaType<Message>("Message"); + + setProperty("identifier", QLatin1String("phonon_gstreamer")); + setProperty("backendName", QLatin1String("Gstreamer")); + setProperty("backendComment", QLatin1String("Gstreamer plugin for Phonon")); + setProperty("backendVersion", QLatin1String("0.2")); + setProperty("backendWebsite", QLatin1String("http://qtsoftware.com/")); + + //check if we should enable debug output + QString debugLevelString = qgetenv("PHONON_GST_DEBUG"); + int debugLevel = debugLevelString.toInt(); + if (debugLevel > 3) //3 is maximum + debugLevel = 3; + m_debugLevel = (DebugLevel)debugLevel; + + if (wasInit) { + m_isValid = checkDependencies(); + gchar *versionString = gst_version_string(); + logMessage(QString("Using %0").arg(versionString)); + g_free(versionString); + } + if (!m_isValid) + qWarning("Phonon::GStreamer::Backend: Failed to initialize GStreamer"); + + m_deviceManager = new DeviceManager(this); + m_effectManager = new EffectManager(this); +} + +Backend::~Backend() +{ + gst_deinit(); +} + +gboolean Backend::busCall(GstBus *bus, GstMessage *msg, gpointer data) +{ + Q_UNUSED(bus); + Q_ASSERT(msg); + + MediaObject *mediaObject = static_cast<MediaObject*>(data); + Q_ASSERT(mediaObject); + + Message message(msg, mediaObject); + QMetaObject::invokeMethod(mediaObject->backend(), "handleBusMessage", Qt::QueuedConnection, Q_ARG(Message, message)); + + return true; +} + +/*** + * !reimp + */ +QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList<QVariant> &args) +{ + // Return nothing if dependencies are not met + + switch (c) { + case MediaObjectClass: + return new MediaObject(this, parent); + + case AudioOutputClass: { + AudioOutput *ao = new AudioOutput(this, parent); + m_audioOutputs.append(ao); + return ao; + } + case EffectClass: + return new AudioEffect(this, args[0].toInt(), parent); + + case AudioDataOutputClass: + logMessage("createObject() : AudioDataOutput not implemented"); + break; + + case VideoDataOutputClass: + logMessage("createObject() : VideoDataOutput not implemented"); + break; + + case VideoWidgetClass: { + QWidget *widget = qobject_cast<QWidget*>(parent); + return new VideoWidget(this, widget); + } + + case VolumeFaderEffectClass: + return new VolumeFaderEffect(this, parent); + + case VisualizationClass: //Fall through + default: + logMessage("createObject() : Backend object not available"); + } + return 0; +} + +// Returns true if all dependencies are met +// and gstreamer is usable, otherwise false +bool Backend::isValid() const +{ + return m_isValid; +} + +bool Backend::supportsVideo() const +{ + return isValid(); +} + +bool Backend::checkDependencies() const +{ + bool success = false; + // Verify that gst-plugins-base is installed + GstElementFactory *acFactory = gst_element_factory_find ("audioconvert"); + if (acFactory) { + gst_object_unref(acFactory); + success = true; + // Check if gst-plugins-good is installed + GstElementFactory *csFactory = gst_element_factory_find ("videobalance"); + if (csFactory) { + gst_object_unref(csFactory); + } else { + QString message = tr("Warning: You do not seem to have the package gstreamer0.10-plugins-good installed.\n" + " Some video features have been disabled."); + qDebug() << message; + } + } else { + qWarning() << tr("Warning: You do not seem to have the base GStreamer plugins installed.\n" + " All audio and video support has been disabled"); + } + return success; +} + +/*** + * !reimp + */ +QStringList Backend::availableMimeTypes() const +{ + QStringList availableMimeTypes; + + if (!isValid()) + return availableMimeTypes; + + GstElementFactory *mpegFactory; + // Add mp3 as a separate mime type as people are likely to look for it. + if ((mpegFactory = gst_element_factory_find ("ffmpeg")) || + (mpegFactory = gst_element_factory_find ("mad"))) { + availableMimeTypes << QLatin1String("audio/x-mp3"); + gst_object_unref(GST_OBJECT(mpegFactory)); + } + + // Iterate over all audio and video decoders and extract mime types from sink caps + GList* factoryList = gst_registry_get_feature_list(gst_registry_get_default (), GST_TYPE_ELEMENT_FACTORY); + for (GList* iter = g_list_first(factoryList) ; iter != NULL ; iter = g_list_next(iter)) { + GstPluginFeature *feature = GST_PLUGIN_FEATURE(iter->data); + QString klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature)); + + if (klass == QLatin1String("Codec/Decoder/Audio") || + klass == QLatin1String("Codec/Decoder/Video")) { + + const GList *static_templates; + GstElementFactory *factory = GST_ELEMENT_FACTORY(feature); + static_templates = gst_element_factory_get_static_pad_templates(factory); + + for (; static_templates != NULL ; static_templates = static_templates->next) { + GstStaticPadTemplate *padTemplate = (GstStaticPadTemplate *) static_templates->data; + if (padTemplate && padTemplate->direction == GST_PAD_SINK) { + GstCaps *caps = gst_static_pad_template_get_caps (padTemplate); + + if (caps) { + const GstStructure* capsStruct = gst_caps_get_structure (caps, 0); + QString mime = QString::fromUtf8(gst_structure_get_name (capsStruct)); + if (!availableMimeTypes.contains(mime)) + availableMimeTypes.append(mime); + } + } + } + } + } + g_list_free(factoryList); + availableMimeTypes.sort(); + return availableMimeTypes; +} + +/*** + * !reimp + */ +QList<int> Backend::objectDescriptionIndexes(ObjectDescriptionType type) const +{ + QList<int> list; + + if (!isValid()) + return list; + + switch (type) { + case Phonon::AudioOutputDeviceType: { + QList<AudioDevice> deviceList = deviceManager()->audioOutputDevices(); + for (int dev = 0 ; dev < deviceList.size() ; ++dev) + list.append(deviceList[dev].id); + break; + } + break; + + case Phonon::EffectType: { + QList<EffectInfo*> effectList = effectManager()->audioEffects(); + for (int eff = 0 ; eff < effectList.size() ; ++eff) + list.append(eff); + break; + } + break; + default: + break; + } + return list; +} + +/*** + * !reimp + */ +QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const +{ + + QHash<QByteArray, QVariant> ret; + + if (!isValid()) + return ret; + + switch (type) { + case Phonon::AudioOutputDeviceType: { + QList<AudioDevice> audioDevices = deviceManager()->audioOutputDevices(); + if (index >= 0 && index < audioDevices.size()) { + ret.insert("name", audioDevices[index].gstId); + ret.insert("description", audioDevices[index].description); + ret.insert("icon", QLatin1String("audio-card")); + } + } + break; + + case Phonon::EffectType: { + QList<EffectInfo*> effectList = effectManager()->audioEffects(); + if (index >= 0 && index <= effectList.size()) { + const EffectInfo *effect = effectList[index]; + ret.insert("name", effect->name()); + ret.insert("description", effect->description()); + ret.insert("author", effect->author()); + } else + Q_ASSERT(1); // Since we use list position as ID, this should not happen + } + default: + break; + } + return ret; +} + +/*** + * !reimp + */ +bool Backend::startConnectionChange(QSet<QObject *> objects) +{ + foreach (QObject *object, objects) { + MediaNode *sourceNode = qobject_cast<MediaNode *>(object); + MediaObject *media = sourceNode->root(); + if (media) { + media->saveState(); + return true; + } + } + return true; +} + +/*** + * !reimp + */ +bool Backend::connectNodes(QObject *source, QObject *sink) +{ + if (isValid()) { + MediaNode *sourceNode = qobject_cast<MediaNode *>(source); + MediaNode *sinkNode = qobject_cast<MediaNode *>(sink); + if (sourceNode && sinkNode) { + if (sourceNode->connectNode(sink)) { + sourceNode->root()->invalidateGraph(); + logMessage(QString("Backend connected %0 to %1").arg(source->metaObject()->className()).arg(sink->metaObject()->className())); + return true; + } + } + } + logMessage(QString("Linking %0 to %1 failed").arg(source->metaObject()->className()).arg(sink->metaObject()->className()), Warning); + return false; +} + +/*** + * !reimp + */ +bool Backend::disconnectNodes(QObject *source, QObject *sink) +{ + MediaNode *sourceNode = qobject_cast<MediaNode *>(source); + MediaNode *sinkNode = qobject_cast<MediaNode *>(sink); + + if (sourceNode && sinkNode) + return sourceNode->disconnectNode(sink); + else + return false; +} + +/*** + * !reimp + */ +bool Backend::endConnectionChange(QSet<QObject *> objects) +{ + foreach (QObject *object, objects) { + MediaNode *sourceNode = qobject_cast<MediaNode *>(object); + MediaObject *media = sourceNode->root(); + if (media) { + media->resumeState(); + return true; + } + } + return true; +} + +/*** + * Request bus messages for this mediaobject + */ +void Backend::addBusWatcher(MediaObject* node) +{ + Q_ASSERT(node); + GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE(node->pipeline())); + gst_bus_add_watch (bus, busCall, node); + gst_object_unref(bus); +} + +/*** + * Ignore bus messages for this mediaobject + */ +void Backend::removeBusWatcher(MediaObject* node) +{ + Q_ASSERT(node); + g_source_remove_by_user_data(node); +} + +/*** + * Polls each mediaobject's pipeline and delivers + * pending any pending messages + */ +void Backend::handleBusMessage(Message message) +{ + MediaObject *mediaObject = message.source(); + mediaObject->handleBusMessage(message); +} + +DeviceManager* Backend::deviceManager() const +{ + return m_deviceManager; +} + +EffectManager* Backend::effectManager() const +{ + return m_effectManager; +} + +/** + * Returns a debuglevel that is determined by the + * PHONON_GSTREAMER_DEBUG environment variable. + * + * Warning - important warnings + * Info - general info + * Debug - gives extra info + */ +Backend::DebugLevel Backend::debugLevel() const +{ + return m_debugLevel; +} + +/*** + * Prints a conditional debug message based on the current debug level + * If obj is provided, classname and objectname will be printed as well + * + * see debugLevel() + */ +void Backend::logMessage(const QString &message, int priority, QObject *obj) const +{ + if (debugLevel() > 0) { + QString output; + if (obj) { + // Strip away namespace from className + QString className(obj->metaObject()->className()); + int nameLength = className.length() - className.lastIndexOf(':') - 1; + className = className.right(nameLength); + output.sprintf("%s %s (%s %p)", message.toLatin1().constData(), + obj->objectName().toLatin1().constData(), + className.toLatin1().constData(), obj); + } + else { + output = message; + } + if (priority <= (int)debugLevel()) { + qDebug() << QString("PGST(%1): %2").arg(priority).arg(output); + } + } +} + +} +} + +QT_END_NAMESPACE + +#include "moc_backend.cpp" diff --git a/src/3rdparty/phonon/gstreamer/backend.h b/src/3rdparty/phonon/gstreamer/backend.h new file mode 100644 index 0000000..2aab6fa --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/backend.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_BACKEND_H +#define Phonon_GSTREAMER_BACKEND_H + +#include "common.h" +#include "devicemanager.h" +#include "medianode.h" + +#include <phonon/objectdescription.h> +#include <phonon/backendinterface.h> + +#include <QtCore/QList> +#include <QtCore/QPointer> +#include <QtCore/QStringList> +#include <QtCore/QTimer> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +class AudioOutput; +class MediaNode; +class MediaObject; +class EffectManager; + +class Backend : public QObject, public BackendInterface +{ + Q_OBJECT + Q_INTERFACES(Phonon::BackendInterface) + +public: + + enum DebugLevel {NoDebug, Warning, Info, Debug}; + Backend(QObject *parent = 0, const QVariantList & = QVariantList()); + virtual ~Backend(); + + DeviceManager* deviceManager() const; + EffectManager* effectManager() const; + + QObject *createObject(BackendInterface::Class, QObject *parent, const QList<QVariant> &args); + + bool isValid() const; + bool supportsVideo() const; + QStringList availableMimeTypes() const; + + QList<int> objectDescriptionIndexes(ObjectDescriptionType type) const; + QHash<QByteArray, QVariant> objectDescriptionProperties(ObjectDescriptionType type, int index) const; + + bool startConnectionChange(QSet<QObject *>); + bool connectNodes(QObject *, QObject *); + bool disconnectNodes(QObject *, QObject *); + bool endConnectionChange(QSet<QObject *>); + + DebugLevel debugLevel() const; + + void addBusWatcher(MediaObject* node); + void removeBusWatcher(MediaObject* node); + void logMessage(const QString &message, int priority = 2, QObject *obj=0) const; + bool checkDependencies() const; + +Q_SIGNALS: + void objectDescriptionChanged(ObjectDescriptionType); + +private Q_SLOTS: + void handleBusMessage(Message); + +private: + static gboolean busCall(GstBus *bus, GstMessage *msg, gpointer data); + QList<QPointer<AudioOutput> > m_audioOutputs; + + DeviceManager *m_deviceManager; + EffectManager *m_effectManager; + DebugLevel m_debugLevel; + bool m_isValid; +}; +} +} // namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_BACKEND_H diff --git a/src/3rdparty/phonon/gstreamer/common.h b/src/3rdparty/phonon/gstreamer/common.h new file mode 100644 index 0000000..355df50 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/common.h @@ -0,0 +1,51 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_COMMON_H +#define Phonon_GSTREAMER_COMMON_H + +#include <phonon/phonon_export.h> + +#include <QtCore/QDebug> + +#ifdef DEBUG_IMPLEMENTED +#define IMPLEMENTED qDebug() << "PGst:" << __FUNCTION__ << "(" << __FILE__ << "):" +#else +#define IMPLEMENTED if (1); else qDebug() +#endif + +#ifdef DEBUG_HALF_IMPLEMENTED +#define HALF_IMPLEMENTED qDebug() << "PGst: --- HALF IMPLEMENTED:" << __FUNCTION__ << "(" << __FILE__ << "):" +#else +#define HALF_IMPLEMENTED if (1); else qDebug() +#endif + +#ifdef DEBUG_NOT_IMPLEMENTED +#define NOT_IMPLEMENTED qDebug() << "PGst: *** NOT IMPLEMENTED:" << __FUNCTION__ << "(" << __FILE__ << "):" +#else +#define NOT_IMPLEMENTED if (1); else qDebug() +#endif + +#ifdef DEBUG_IMPLEMENTED_SILENT +#define IMPLEMENTED_SILENT qDebug() << "PGst: (silent)" << __FUNCTION__ << "(" << __FILE__ << "):" +#else +#define IMPLEMENTED_SILENT if (1); else qDebug() +#endif + +#define BACKEND_WARNING qDebug() << "PGst: ____ WARNING ____" << Q_FUNC_INFO << ":" + +#endif // Phonon_GSTREAMER_COMMON_H diff --git a/src/3rdparty/phonon/gstreamer/devicemanager.cpp b/src/3rdparty/phonon/gstreamer/devicemanager.cpp new file mode 100644 index 0000000..2240396 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/devicemanager.cpp @@ -0,0 +1,357 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gst/interfaces/propertyprobe.h> +#include "devicemanager.h" +#include "backend.h" +#include "gsthelper.h" +#include "videowidget.h" +#include "glrenderer.h" +#include "widgetrenderer.h" +#include "x11renderer.h" +#include "artssink.h" + +#ifdef USE_ALSASINK2 +#include "alsasink2.h" +#endif + +/* + * This class manages the list of currently + * active output devices + */ + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +AudioDevice::AudioDevice(DeviceManager *manager, const QByteArray &gstId) + : gstId(gstId) +{ + //get an id + static int counter = 0; + id = counter++; + //get name from device + if (gstId == "default") { + description = "Default audio device"; + } else { + GstElement *aSink= manager->createAudioSink(); + + if (aSink) { + gchar *deviceDescription = NULL; + + if (GST_IS_PROPERTY_PROBE(aSink) && gst_property_probe_get_property( GST_PROPERTY_PROBE(aSink), "device" ) ) { + g_object_set (G_OBJECT(aSink), "device", gstId.constData(), (const char*)NULL); + g_object_get (G_OBJECT(aSink), "device-name", &deviceDescription, (const char*)NULL); + description = QByteArray(deviceDescription); + g_free (deviceDescription); + gst_element_set_state(aSink, GST_STATE_NULL); + gst_object_unref (aSink); + } + } + } +} + +DeviceManager::DeviceManager(Backend *backend) + : QObject(backend) + , m_backend(backend) +{ + QSettings settings(QLatin1String("Trolltech")); + settings.beginGroup(QLatin1String("Qt")); + + m_audioSink = qgetenv("PHONON_GST_AUDIOSINK"); + if (m_audioSink.isEmpty()) { + m_audioSink = settings.value(QLatin1String("audiosink"), "Auto").toByteArray().toLower(); + } + + m_videoSinkWidget = qgetenv("PHONON_GST_VIDEOMODE"); + if (m_videoSinkWidget.isEmpty()) { + m_videoSinkWidget = settings.value(QLatin1String("videomode"), "Auto").toByteArray().toLower(); + } + + if (m_backend->isValid()) + updateDeviceList(); +} + +DeviceManager::~DeviceManager() +{ + m_audioDeviceList.clear(); +} + +/*** +* Returns a Gst Audiosink based on GNOME configuration settings, +* or 0 if the element is not available. +*/ +GstElement *DeviceManager::createGNOMEAudioSink(Category category) +{ + GstElement *sink = gst_element_factory_make ("gconfaudiosink", NULL); + + if (sink) { + + // set profile property on the gconfaudiosink to "music and movies" + if (g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "profile")) { + switch (category) { + case NotificationCategory: + g_object_set (G_OBJECT (sink), "profile", 0, (const char*)NULL); // 0 = 'sounds' + break; + case CommunicationCategory: + g_object_set (G_OBJECT (sink), "profile", 2, (const char*)NULL); // 2 = 'chat' + break; + default: + g_object_set (G_OBJECT (sink), "profile", 1, (const char*)NULL); // 1 = 'music and movies' + break; + } + } + } + return sink; +} + + +bool DeviceManager::canOpenDevice(GstElement *element) const +{ + if (!element) + return false; + + if (gst_element_set_state(element, GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS) + return true; + + const QList<QByteArray> &list = GstHelper::extractProperties(element, "device"); + foreach (const QByteArray &gstId, list) { + GstHelper::setProperty(element, "device", gstId); + if (gst_element_set_state(element, GST_STATE_READY) == GST_STATE_CHANGE_SUCCESS) { + return true; + } + } + // FIXME: the above can still fail for a valid alsasink because list only contains entries of + // the form "hw:X,Y". Would be better to use "default:X" or "dmix:X,Y" + + gst_element_set_state(element, GST_STATE_NULL); + return false; +} + +/* +* +* Returns a GstElement with a valid audio sink +* based on the current value of PHONON_GSTREAMER_DRIVER +* +* Allowed values are auto (default), alsa, oss, arts and ess +* does not exist +* +* If no real sound sink is available a fakesink will be returned +*/ +GstElement *DeviceManager::createAudioSink(Category category) +{ + GstElement *sink = 0; + + if (m_backend && m_backend->isValid()) + { + if (m_audioSink == "auto") //this is the default value + { + //### TODO : get equivalent KDE settings here + + if (!qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty()) { + sink = createGNOMEAudioSink(category); + if (canOpenDevice(sink)) + m_backend->logMessage("AudioOutput using gconf audio sink"); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } + +#ifdef USE_ALSASINK2 + if (!sink) { + sink = gst_element_factory_make ("_k_alsasink", NULL); + if (canOpenDevice(sink)) + m_backend->logMessage("AudioOutput using alsa2 audio sink"); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } +#endif + + if (!sink) { + sink = gst_element_factory_make ("alsasink", NULL); + if (canOpenDevice(sink)) + m_backend->logMessage("AudioOutput using alsa audio sink"); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } + + if (!sink) { + sink = gst_element_factory_make ("autoaudiosink", NULL); + if (canOpenDevice(sink)) + m_backend->logMessage("AudioOutput using auto audio sink"); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } + + if (!sink) { + sink = gst_element_factory_make ("osssink", NULL); + if (canOpenDevice(sink)) + m_backend->logMessage("AudioOutput using oss audio sink"); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } + } else if (m_audioSink == "fake") { + //do nothing as a fakesink will be created by default + } else if (m_audioSink == "artssink") { + sink = GST_ELEMENT(g_object_new(arts_sink_get_type(), NULL)); + } else if (!m_audioSink.isEmpty()) { //Use a custom sink + sink = gst_element_factory_make (m_audioSink, NULL); + if (canOpenDevice(sink)) + m_backend->logMessage(QString("AudioOutput using %0").arg(QString::fromUtf8(m_audioSink))); + else if (sink) { + gst_object_unref(sink); + sink = 0; + } + } + } + + if (!sink) { //no suitable sink found so we'll make a fake one + sink = gst_element_factory_make("fakesink", NULL); + if (sink) { + m_backend->logMessage("AudioOutput Using fake audio sink"); + //without sync the sink will pull the pipeline as fast as the CPU allows + g_object_set (G_OBJECT (sink), "sync", TRUE, (const char*)NULL); + } + } + Q_ASSERT(sink); + return sink; +} + +AbstractRenderer *DeviceManager::createVideoRenderer(VideoWidget *parent) +{ +#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES) + if (m_videoSinkWidget == "opengl") { + return new GLRenderer(parent); + } else +#endif + if (m_videoSinkWidget == "software") { + return new WidgetRenderer(parent); + } +#ifndef Q_WS_QWS + else if (m_videoSinkWidget == "xwindow") { + return new X11Renderer(parent); + } else { + GstElementFactory *srcfactory = gst_element_factory_find("ximagesink"); + if (srcfactory) { + return new X11Renderer(parent); + } + } +#endif + return new WidgetRenderer(parent); +} + +/* + * Returns a positive device id or -1 if device + * does not exist + * + * The gstId is typically in the format hw:1,0 + */ +int DeviceManager::deviceId(const QByteArray &gstId) const +{ + for (int i = 0 ; i < m_audioDeviceList.size() ; ++i) { + if (m_audioDeviceList[i].gstId == gstId) { + return m_audioDeviceList[i].id; + } + } + return -1; +} + +/** + * Get a human-readable description from a device id + */ +QByteArray DeviceManager::deviceDescription(int id) const +{ + for (int i = 0 ; i < m_audioDeviceList.size() ; ++i) { + if (m_audioDeviceList[i].id == id) { + return m_audioDeviceList[i].description; + } + } + return QByteArray(); +} + +/** + * Updates the current list of active devices + */ +void DeviceManager::updateDeviceList() +{ + //fetch list of current devices + GstElement *audioSink= createAudioSink(); + + QList<QByteArray> list; + + if (audioSink) { + list = GstHelper::extractProperties(audioSink, "device"); + list.prepend("default"); + + for (int i = 0 ; i < list.size() ; ++i) { + QByteArray gstId = list.at(i); + if (deviceId(gstId) == -1) { + // This is a new device, add it + m_audioDeviceList.append(AudioDevice(this, gstId)); + emit deviceAdded(deviceId(gstId)); + m_backend->logMessage(QString("Found new audio device %0").arg(QString::fromUtf8(gstId)), Backend::Debug, this); + } + } + + if (list.size() < m_audioDeviceList.size()) { + //a device was removed + for (int i = m_audioDeviceList.size() -1 ; i >= 0 ; --i) { + QByteArray currId = m_audioDeviceList[i].gstId; + bool found = false; + for (int k = list.size() -1 ; k >= 0 ; --k) { + if (currId == list[k]) { + found = true; + break; + } + } + if (!found) { + m_backend->logMessage(QString("Audio device lost %0").arg(QString::fromUtf8(currId)), Backend::Debug, this); + emit deviceRemoved(deviceId(currId)); + m_audioDeviceList.removeAt(i); + } + } + } + } + + gst_element_set_state (audioSink, GST_STATE_NULL); + gst_object_unref (audioSink); +} + +/** + * Returns a list of hardware id usable by gstreamer [i.e hw:1,0] + */ +const QList<AudioDevice> DeviceManager::audioOutputDevices() const +{ + return m_audioDeviceList; +} + +} +} + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/devicemanager.h b/src/3rdparty/phonon/gstreamer/devicemanager.h new file mode 100644 index 0000000..a5e8289 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/devicemanager.h @@ -0,0 +1,80 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_DEVICEMANAGER_H +#define Phonon_GSTREAMER_DEVICEMANAGER_H + +#include "common.h" + +#include <phonon/audiooutputinterface.h> + +#include <QtCore/QObject> +#include <QtCore/QTimer> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon { +namespace Gstreamer { +class Backend; +class DeviceManager; +class AbstractRenderer; +class VideoWidget; + +class AudioDevice { +public : + AudioDevice(DeviceManager *s, const QByteArray &deviceId); + int id; + QByteArray gstId; + QByteArray description; +}; + +class DeviceManager : public QObject { + Q_OBJECT +public: + DeviceManager(Backend *parent); + virtual ~DeviceManager(); + const QList<AudioDevice> audioOutputDevices() const; + GstPad *requestPad(int device) const; + int deviceId(const QByteArray &gstId) const; + QByteArray deviceDescription(int id) const; + GstElement *createGNOMEAudioSink(Category category); + GstElement *createAudioSink(Category category = NoCategory); + AbstractRenderer *createVideoRenderer(VideoWidget *parent); + +signals: + void deviceAdded(int); + void deviceRemoved(int); + +public slots: + void updateDeviceList(); + +private: + bool canOpenDevice(GstElement *element) const; + Backend *m_backend; + QList <AudioDevice> m_audioDeviceList; + QTimer m_devicePollTimer; + QByteArray m_audioSink; + QByteArray m_videoSinkWidget; +}; +} +} // namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_DEVICEMANAGER_H diff --git a/src/3rdparty/phonon/gstreamer/effect.cpp b/src/3rdparty/phonon/gstreamer/effect.cpp new file mode 100644 index 0000000..f653535 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/effect.cpp @@ -0,0 +1,246 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "effect.h" +#include "common.h" +#include "audiooutput.h" +#include "backend.h" +#include "medianode.h" +#include "effectmanager.h" +#include "gsthelper.h" + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +Effect::Effect(Backend *backend, QObject *parent, NodeDescription description) + : QObject(parent), + MediaNode(backend, description) + , m_effectBin(0) + , m_effectElement(0) +{ +} + +void Effect::init() +{ + m_effectBin = createEffectBin(); + if (m_effectBin) { + setupEffectParams(); + gst_object_ref (GST_OBJECT (m_effectBin)); // Take ownership + gst_object_sink (GST_OBJECT (m_effectBin)); + m_isValid = true; + } +} + +Effect::~Effect() +{ + if (m_effectBin) { + gst_element_set_state (m_effectBin, GST_STATE_NULL); + gst_object_unref (m_effectBin); + } +} + +void Effect::setupEffectParams() +{ + + Q_ASSERT(m_effectElement); + + //query and store parameters + if (m_effectElement) { + GParamSpec **property_specs; + guint propertyCount, i; + property_specs = g_object_class_list_properties(G_OBJECT_GET_CLASS (m_effectElement), &propertyCount); + for (i = 0; i < propertyCount; i++) { + GParamSpec *param = property_specs[i]; + if (param->flags & G_PARAM_WRITABLE) { + QString propertyName = g_param_spec_get_name (param); + + // These properties should not be exposed to the front-end + if (propertyName == "qos" || propertyName == "name" || propertyName == "async-handling") + continue; + + switch(param->value_type) { + case G_TYPE_UINT: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + 0, //hints + G_PARAM_SPEC_UINT(param)->default_value, + G_PARAM_SPEC_UINT(param)->minimum, + G_PARAM_SPEC_UINT(param)->maximum)); + break; + + case G_TYPE_STRING: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + 0, //hints + G_PARAM_SPEC_STRING(param)->default_value, + 0, + 0)); + break; + + case G_TYPE_INT: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + EffectParameter::IntegerHint, //hints + QVariant(G_PARAM_SPEC_INT(param)->default_value), + QVariant(G_PARAM_SPEC_INT(param)->minimum), + QVariant(G_PARAM_SPEC_INT(param)->maximum))); + break; + + case G_TYPE_FLOAT: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + 0, //hints + QVariant((double)G_PARAM_SPEC_FLOAT(param)->default_value), + QVariant((double)G_PARAM_SPEC_FLOAT(param)->minimum), + QVariant((double)G_PARAM_SPEC_FLOAT(param)->maximum))); + break; + + case G_TYPE_DOUBLE: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + 0, //hints + QVariant(G_PARAM_SPEC_DOUBLE(param)->default_value), + QVariant(G_PARAM_SPEC_DOUBLE(param)->minimum), + QVariant(G_PARAM_SPEC_DOUBLE(param)->maximum))); + break; + + case G_TYPE_BOOLEAN: + m_parameterList.append(Phonon::EffectParameter(i, propertyName, + Phonon::EffectParameter::ToggledHint, //hints + QVariant((bool)G_PARAM_SPEC_BOOLEAN(param)->default_value), + QVariant((bool)false), QVariant((bool)true))); + break; + + default: + break; + } + } + } + } +} + +QList<Phonon::EffectParameter> Effect::parameters() const +{ + return m_parameterList; +} + +QVariant Effect::parameterValue(const EffectParameter &p) const +{ + + Q_ASSERT(m_effectElement); + + QVariant returnVal; + switch (p.type()) { + case QVariant::Int: + { + gint val = 0; + g_object_get(G_OBJECT(m_effectElement), qPrintable(p.name()), &val, (const char*)NULL); + returnVal = val; + } + break; + + case QVariant::Bool: + { + gboolean val = 0; + g_object_get(G_OBJECT(m_effectElement), qPrintable(p.name()), &val, (const char*)NULL); + returnVal = val; + } + break; + + case QVariant::String: + { + gchar *val = 0; + g_object_get(G_OBJECT(m_effectElement), qPrintable(p.name()), &val, (const char*)NULL); + returnVal = QString::fromUtf8(val); + g_free(val); + } + break; + + case QVariant::Double: + { + GParamSpec* spec = g_object_class_find_property(G_OBJECT_GET_CLASS(m_effectElement), p.name().toLatin1().constData()); + Q_ASSERT(spec); + if (spec && spec->value_type == G_TYPE_FLOAT) { + gfloat val = 0; + g_object_get(G_OBJECT(m_effectElement), qPrintable(p.name()), &val, (const char*)NULL); + returnVal = QVariant((float)val); + } else { + gdouble val = 0; + g_object_get(G_OBJECT(m_effectElement), qPrintable(p.name()), &val, (const char*)NULL); + returnVal = QVariant((float)val); + } + } + break; + + default: + Q_ASSERT(0); //not a supported variant type + } + return returnVal; +} + + +void Effect::setParameterValue(const EffectParameter &p, const QVariant &v) +{ + Q_ASSERT(m_effectElement); + + // Note that the frontend currently calls this after creation with a null-value + // for all parameters. + + if (v.isValid()) { + + switch (p.type()) { + // ### range values should really be checked by the front end, why isnt it working? + case QVariant::Int: + if (v.toInt() >= p.minimumValue().toInt() && v.toInt() <= p.maximumValue().toInt()) + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), (gint)v.toInt(), (const char*)NULL); + break; + + case QVariant::Double: + if (v.toDouble() >= p.minimumValue().toDouble() && v.toDouble() <= p.maximumValue().toDouble()) { + GParamSpec* spec = g_object_class_find_property(G_OBJECT_GET_CLASS(m_effectElement), p.name().toLatin1().constData()); + Q_ASSERT(spec); + if (spec && spec->value_type == G_TYPE_FLOAT) + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), (gfloat)v.toDouble(), (const char*)NULL); + else + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), (gdouble)v.toDouble(), (const char*)NULL); + } + break; + + case QVariant::UInt: + if (v.toUInt() >= p.minimumValue().toUInt() && v.toUInt() <= p.maximumValue().toUInt()) + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), v.toUInt(), (const char*)NULL); + break; + + case QVariant::String: + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), v.toString().toUtf8().constData(), (const char*)NULL); + break; + + case QVariant::Bool: + g_object_set(G_OBJECT(m_effectElement), qPrintable(p.name()), (gboolean)v.toBool(), (const char*)NULL); + break; + + default: + Q_ASSERT(0); //not a supported variant type + } + } +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE +#include "moc_effect.cpp" diff --git a/src/3rdparty/phonon/gstreamer/effect.h b/src/3rdparty/phonon/gstreamer/effect.h new file mode 100644 index 0000000..dbbb457 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/effect.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_EFFECT_H +#define Phonon_GSTREAMER_EFFECT_H + +#include "common.h" +#include "medianode.h" + +#include <phonon/effectparameter.h> +#include <phonon/effectinterface.h> + +#include <QtCore/QObject> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + class EffectInfo; + + class Effect : public QObject, public Phonon::EffectInterface, public MediaNode + { + Q_OBJECT + Q_INTERFACES(Phonon::EffectInterface Phonon::Gstreamer::MediaNode) + public: + Effect (Backend *backend, QObject *parent, NodeDescription description); + virtual ~Effect (); + + virtual QList<EffectParameter> parameters() const; + virtual QVariant parameterValue(const EffectParameter &) const; + virtual void setParameterValue(const EffectParameter &, const QVariant &); + + virtual GstElement* createEffectBin() = 0; + virtual void init(); + virtual void setupEffectParams(); + + protected: + GstElement *m_effectBin; + GstElement *m_effectElement; + QList<Phonon::EffectParameter> m_parameterList; + }; +}} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_EFFECT_H diff --git a/src/3rdparty/phonon/gstreamer/effectmanager.cpp b/src/3rdparty/phonon/gstreamer/effectmanager.cpp new file mode 100644 index 0000000..563e6fc --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/effectmanager.cpp @@ -0,0 +1,105 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gst/interfaces/propertyprobe.h> +#include "effectmanager.h" +#include "backend.h" +#include "gsthelper.h" + +/* + * This class manages the list of currently + * available audio effects. + */ + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +EffectInfo::EffectInfo(const QString &name, const QString&description, const QString&author) + : m_name(name) + , m_description(description) + , m_author(author) {} + +EffectManager::EffectManager(Backend *backend) + : QObject(backend) + , m_backend(backend) +{ + GList* factoryList = gst_registry_get_feature_list(gst_registry_get_default (), GST_TYPE_ELEMENT_FACTORY); + QString name, klass, description, author; + for (GList* iter = g_list_first(factoryList) ; iter != NULL ; iter = g_list_next(iter)) { + GstPluginFeature *feature = GST_PLUGIN_FEATURE(iter->data); + klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature)); + if ( klass == "Filter/Effect/Audio" ) { + name = GST_PLUGIN_FEATURE_NAME(feature); + + // These plugins simply make no sense to the frontend: + // "audiorate" Should be internal + // "volume" not needed + // "equalizer-nbands" not really useful at the moment + + // These plugins simply dont work or have major stability issues: + // "iir" Does not seem to do much at the moment + // "audioinvert" Only works for some streams, should be invesigated + // "lpwsinc" Crashes for large values of filter kernel + // "name" Crashes for large values of filter kernel + + // Seems to be working, but not well tested: + // name == "rglimiter" Seems functional + // name == "rgvolume" Seems to be working + + QString pluginString = qgetenv("PHONON_GST_ALL_EFFECTS"); + bool acceptAll = pluginString.toInt(); + + if (acceptAll + // Plugins that have been accepted so far + || name == "audiopanorama" + || name == "audioamplify" + || name == "audiodynamic" + || name == "equalizer-10bands" + || name == "speed") + { + description = gst_element_factory_get_description (GST_ELEMENT_FACTORY(feature)); + author = gst_element_factory_get_author (GST_ELEMENT_FACTORY(feature)); + EffectInfo *effect = new EffectInfo(name, description, author); + m_audioEffectList.append(effect); + } + } + } + g_list_free(factoryList); +} + +EffectManager::~EffectManager() +{ + qDeleteAll(m_audioEffectList); + m_audioEffectList.clear(); +} + +/** + * Returns a list of available audio effects + */ +const QList<EffectInfo*> EffectManager::audioEffects() const +{ + return m_audioEffectList; +} + +} +} + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/effectmanager.h b/src/3rdparty/phonon/gstreamer/effectmanager.h new file mode 100644 index 0000000..f7d9c96 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/effectmanager.h @@ -0,0 +1,91 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_EFFECTMANAGER_H +#define Phonon_GSTREAMER_EFFECTMANAGER_H + +#include "common.h" + +#include <QtCore/QObject> +#include <QtCore/QTimer> +#include <QtCore/QStringList> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +class Backend; +class EffectManager; + +class EffectInfo +{ +public : + EffectInfo(const QString &name, + const QString &description, + const QString &author); + + QString name() const + { + return m_name; + } + QString description() const + { + return m_description; + } + QString author() const + { + return m_author; + } + QStringList properties() const + { + return m_properties; + } + void addProperty(QString propertyName) + { + m_properties.append(propertyName); + } + +private: + QString m_name; + QString m_description; + QString m_author; + QStringList m_properties; +}; + +class EffectManager : public QObject +{ + Q_OBJECT +public: + EffectManager(Backend *parent); + virtual ~EffectManager(); + const QList<EffectInfo*> audioEffects() const; + +private: + Backend *m_backend; + QList <EffectInfo*> m_audioEffectList; + QList <EffectInfo*> m_visualizationList; +}; +} +} // namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_EFFECTMANAGER_H diff --git a/src/3rdparty/phonon/gstreamer/glrenderer.cpp b/src/3rdparty/phonon/gstreamer/glrenderer.cpp new file mode 100644 index 0000000..6cf3459 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/glrenderer.cpp @@ -0,0 +1,339 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <QtGui/QPainter> +#include <QtGui/QResizeEvent> + +#ifndef QT_NO_OPENGL + +#include "common.h" +#include "message.h" +#include "mediaobject.h" +#include "qwidgetvideosink.h" +#include "glrenderer.h" +#include "qrgb.h" + +#if !defined(QT_OPENGL_ES) + +#include <gst/gst.h> + +// support old OpenGL installations (1.2) +// assume that if TEXTURE0 isn't defined, none are +#ifndef GL_TEXTURE0 +# define GL_TEXTURE0 0x84C0 +# define GL_TEXTURE1 0x84C1 +# define GL_TEXTURE2 0x84C2 +#endif + +QT_BEGIN_NAMESPACE + +static void frameRendered() +{ + static QString displayFps = qgetenv("PHONON_GST_FPS"); + if (displayFps.isEmpty()) + return; + + static int frames = 0; + static QTime lastTime = QTime::currentTime(); + QTime time = QTime::currentTime(); + + int delta = lastTime.msecsTo(time); + if (delta > 2000) { + printf("FPS: %f\n", 1000.0 * frames / qreal(delta)); + lastTime = time; + frames = 0; + } + + ++frames; +} + +namespace Phonon +{ +namespace Gstreamer +{ + +GLRenderer::GLRenderer(VideoWidget* videoWidget) : + AbstractRenderer(videoWidget) + , m_glWindow(0) +{ + videoWidget->backend()->logMessage("Creating OpenGL renderer"); + QGLFormat format = QGLFormat::defaultFormat(); + format.setSwapInterval(1); // Enable vertical sync on draw to avoid tearing + m_glWindow = new GLRenderWidgetImplementation(videoWidget, format); + + if ((m_videoSink = m_glWindow->createVideoSink())) { //if ((m_videoSink = m_glWindow->createVideoSink())) { + gst_object_ref (GST_OBJECT (m_videoSink)); //Take ownership + gst_object_sink (GST_OBJECT (m_videoSink)); + + QWidgetVideoSinkBase* sink = reinterpret_cast<QWidgetVideoSinkBase*>(m_videoSink); + // Let the videosink know which widget to direct frame updates to + sink->renderWidget = videoWidget; + } +} + +GLRenderer::~GLRenderer() +{ + if (m_videoSink) { + gst_object_unref (GST_OBJECT (m_videoSink)); + m_videoSink = 0; + } +} + + +bool GLRenderer::eventFilter(QEvent * event) +{ + if (event->type() == QEvent::User) { + NewFrameEvent *frameEvent= static_cast <NewFrameEvent *>(event); + m_glWindow->setNextFrame(frameEvent->frame, frameEvent->width, frameEvent->height); + return true; + } + else if (event->type() == QEvent::Resize) { + m_glWindow->setGeometry(m_videoWidget->geometry()); + return true; + } + return false; +} + +void GLRenderer::handleMediaNodeEvent(const MediaNodeEvent *event) +{ + switch (event->type()) { + case MediaNodeEvent::SourceChanged: + { + Q_ASSERT(m_glWindow); + m_glWindow->clearFrame(); + break; + } + default: + break; + } +} + +GstElement* GLRenderWidgetImplementation::createVideoSink() +{ + if (hasYUVSupport()) + return GST_ELEMENT(g_object_new(get_type_YUV(), NULL)); + return 0; +} + +void GLRenderWidgetImplementation::setNextFrame(const QByteArray &array, int w, int h) +{ + if (m_videoWidget->root()->state() == Phonon::LoadingState) + return; + + m_frame = QImage(); + + if (hasYUVSupport()) + updateTexture(array, w, h); + else + m_frame = QImage((uchar *)array.constData(), w, h, QImage::Format_RGB32); + + m_array = array; + m_width = w; + m_height = h; + + update(); +} + +void GLRenderWidgetImplementation::clearFrame() +{ + m_frame = QImage(); + m_array = QByteArray(); + update(); +} + +bool GLRenderWidgetImplementation::hasYUVSupport() const +{ + return m_yuvSupport; +} + +static QImage convertFromYUV(const QByteArray &array, int w, int h) +{ + QImage result(w, h, QImage::Format_RGB32); + + // TODO: bilinearly interpolate the U and V channels for better result + + for (int y = 0; y < h; ++y) { + uint *sp = (uint *)result.scanLine(y); + + const uchar *yp = (const uchar *)(array.constData() + y * w); + const uchar *up = (const uchar *)(array.constData() + w * h + (y/2)*(w/2)); + const uchar *vp = (const uchar *)(array.constData() + w * h * 5/4 + (y/2)*(w/2)); + + for (int x = 0; x < w; ++x) { + const int sy = *yp; + const int su = *up; + const int sv = *vp; + + const int R = int(1.164 * (sy - 16) + 1.596 * (sv - 128)); + const int G = int(1.164 * (sy - 16) - 0.813 * (sv - 128) - 0.391 * (su - 128)); + const int B = int(1.164 * (sy - 16) + 2.018 * (su - 128)); + + *sp = qRgb(qBound(0, R, 255), + qBound(0, G, 255), + qBound(0, B, 255)); + + ++yp; + ++sp; + if (x & 1) { + ++up; + ++vp; + } + } + } + return result; +} + +const QImage &GLRenderWidgetImplementation::currentFrame() const +{ + if (m_frame.isNull() && !m_array.isNull()) + m_frame = convertFromYUV(m_array, m_width, m_height); + + return m_frame; +} + +#ifndef GL_FRAGMENT_PROGRAM_ARB +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#endif + +// arbfp1 fragment program for converting yuv to rgb +const char *const yuvToRgb = + "!!ARBfp1.0" + "PARAM c[3] = { { 0.5, 0.0625 }," + "{ 1.164, 0, 1.596, 2.0179999 }," + "{ 1.164, -0.391, -0.81300002 } };" + "TEMP R0;" + "TEMP R1;" + "TEX R0.x, fragment.texcoord[0], texture[2], 2D;" + "ADD R1.z, R0.x, -c[0].x;" + "TEX R1.x, fragment.texcoord[0], texture[0], 2D;" + "TEX R0.x, fragment.texcoord[0], texture[1], 2D;" + "ADD R1.x, R1, -c[0].y;" + "ADD R1.y, R0.x, -c[0].x;" + "DP3 result.color.x, R1, c[1];" + "DP3 result.color.y, R1, c[2];" + "DP3 result.color.z, R1, c[1].xwyw;" + "END"; + +GLRenderWidgetImplementation::GLRenderWidgetImplementation(VideoWidget*videoWidget, const QGLFormat &format) : + QGLWidget(format, videoWidget) + , m_program(0) + , m_yuvSupport(false) + , m_videoWidget(videoWidget) +{ + makeCurrent(); + glGenTextures(3, m_texture); + + glProgramStringARB = (_glProgramStringARB) context()->getProcAddress(QLatin1String("glProgramStringARB")); + glBindProgramARB = (_glBindProgramARB) context()->getProcAddress(QLatin1String("glBindProgramARB")); + glDeleteProgramsARB = (_glDeleteProgramsARB) context()->getProcAddress(QLatin1String("glDeleteProgramsARB")); + glGenProgramsARB = (_glGenProgramsARB) context()->getProcAddress(QLatin1String("glGenProgramsARB")); + glActiveTexture = (_glActiveTexture) context()->getProcAddress(QLatin1String("glActiveTexture")); + + m_hasPrograms = glProgramStringARB && glBindProgramARB && glDeleteProgramsARB && glGenProgramsARB && glActiveTexture; + + if (m_hasPrograms) { + glGenProgramsARB(1, &m_program); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_program); + + const GLbyte *gl_src = reinterpret_cast<const GLbyte *>(yuvToRgb); + glProgramStringARB(GL_FRAGMENT_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB, + int(strlen(yuvToRgb)), gl_src); + + if (glGetError() != GL_NO_ERROR) { + glDeleteProgramsARB(1, &m_program); + m_hasPrograms = false; + } else { + m_yuvSupport = true; + } + } + + QPalette palette; + palette.setColor(QPalette::Background, Qt::black); + setPalette(palette); + setAutoFillBackground(true); + // Videowidget allways have this property to allow hiding the mouse cursor + setMouseTracking(true); +} + +void GLRenderWidgetImplementation::updateTexture(const QByteArray &array, int width, int height) +{ + m_width = width; + m_height = height; + + makeCurrent(); + + int w[3] = { width, width/2, width/2 }; + int h[3] = { height, height/2, height/2 }; + int offs[3] = { 0, width*height, width*height*5/4 }; + + for (int i = 0; i < 3; ++i) { + glBindTexture(GL_TEXTURE_2D, m_texture[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w[i], h[i], 0, + GL_LUMINANCE, GL_UNSIGNED_BYTE, array.data() + offs[i]); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + } +} + +void GLRenderWidgetImplementation::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + m_drawFrameRect = m_videoWidget->calculateDrawFrameRect(); + if (m_yuvSupport && frameIsSet()) { + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_program); + const float tx_array[] = { 0, 0, 1, 0, 1, 1, 0, 1}; + const QRectF r = drawFrameRect(); + + const float v_array[] = { r.left(), r.top(), r.right(), r.top(), r.right(), r.bottom(), r.left(), r.bottom() }; + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_texture[0]); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_texture[1]); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, m_texture[2]); + glActiveTexture(GL_TEXTURE0); + + glVertexPointer(2, GL_FLOAT, 0, v_array); + glTexCoordPointer(2, GL_FLOAT, 0, tx_array); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glDrawArrays(GL_QUADS, 0, 4); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } else { + painter.setRenderHint(QPainter::SmoothPixmapTransform); + painter.drawImage(drawFrameRect(), currentFrame()); + } + + frameRendered(); +} +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // QT_OPENGL_ES +#endif // QT_NO_OPENGL diff --git a/src/3rdparty/phonon/gstreamer/glrenderer.h b/src/3rdparty/phonon/gstreamer/glrenderer.h new file mode 100644 index 0000000..68dd141 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/glrenderer.h @@ -0,0 +1,101 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_GLRENDERER_H +#define Phonon_GSTREAMER_GLRENDERER_H + +#include "videowidget.h" +#include "common.h" + +#ifndef QT_NO_OPENGL + +#include <QtOpenGL/QGLFormat> +#include <QtOpenGL/QGLWidget> + +#ifndef QT_OPENGL_ES +QT_BEGIN_NAMESPACE + +class QString; + +namespace Phonon +{ +namespace Gstreamer +{ +class GLRenderWidgetImplementation; + +class GLRenderer : public AbstractRenderer +{ +public: + GLRenderer(VideoWidget *control); + ~GLRenderer(); + void handleMediaNodeEvent(const MediaNodeEvent *event); + bool eventFilter(QEvent * event); + bool paintsOnWidget() { return false; } +private: + GLRenderWidgetImplementation *m_glWindow; +}; + +class GLRenderWidgetImplementation : public QGLWidget +{ + Q_OBJECT + + // ARB_fragment_program + typedef void (*_glProgramStringARB) (GLenum, GLenum, GLsizei, const GLvoid *); + typedef void (*_glBindProgramARB) (GLenum, GLuint); + typedef void (*_glDeleteProgramsARB) (GLsizei, const GLuint *); + typedef void (*_glGenProgramsARB) (GLsizei, GLuint *); + typedef void (*_glActiveTexture) (GLenum); +public: + GLRenderWidgetImplementation(VideoWidget *control, const QGLFormat &format); + void paintEvent(QPaintEvent *event); + GstElement *createVideoSink(); + void updateTexture(const QByteArray &array, int width, int height); + bool hasYUVSupport() const; + const QImage& currentFrame() const; + QRect drawFrameRect() const { return m_drawFrameRect; } + bool frameIsSet() const { return !m_array.isNull(); } + void setNextFrame(const QByteArray &array, int width, int height); + void clearFrame(); +private: + _glProgramStringARB glProgramStringARB; + _glBindProgramARB glBindProgramARB; + _glDeleteProgramsARB glDeleteProgramsARB; + _glGenProgramsARB glGenProgramsARB; + _glActiveTexture glActiveTexture; + + mutable QImage m_frame; + QByteArray m_array; + int m_width; + int m_height; + QRect m_drawFrameRect; + GLuint m_texture[3]; + + bool m_hasPrograms; + GLuint m_program; + bool m_yuvSupport; + VideoWidget *m_videoWidget; +}; + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif //QT_OPENGL_ES +#endif // QT_NO_OPENGL + +#endif // Phonon_GSTREAMER_GLRENDERER_H diff --git a/src/3rdparty/phonon/gstreamer/gsthelper.cpp b/src/3rdparty/phonon/gstreamer/gsthelper.cpp new file mode 100644 index 0000000..34d99fa --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/gsthelper.cpp @@ -0,0 +1,170 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gst/interfaces/propertyprobe.h> +#include <gst/gst.h> +#include "common.h" +#include "gsthelper.h" + +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +/** + * Probes a gstElement for a list of settable string-property values + * + * @return a QStringList containing a list of allwed string values for the given + * element + */ +QList<QByteArray> GstHelper::extractProperties(GstElement *elem, const QByteArray &value) +{ + Q_ASSERT(elem); + QList<QByteArray> list; + + if (GST_IS_PROPERTY_PROBE(elem)) { + GstPropertyProbe *probe = GST_PROPERTY_PROBE(elem); + const GParamSpec *devspec = 0; + GValueArray *array = NULL; + + if ((devspec = gst_property_probe_get_property (probe, value))) { + if ((array = gst_property_probe_probe_and_get_values (probe, devspec))) { + for (unsigned int device = 0; device < array->n_values; device++) { + GValue *deviceId = g_value_array_get_nth (array, device); + list.append(g_value_get_string(deviceId)); + } + } + if (array) + g_value_array_free (array); + } + } + return list; +} + +/** + * Sets the string value of a GstElement's property + * + * @return false if the value could not be set. + */ +bool GstHelper::setProperty(GstElement *elem, const char *propertyName, const QByteArray &propertyValue) +{ + Q_ASSERT(elem); + Q_ASSERT(propertyName && strlen(propertyName)); + + if (GST_IS_PROPERTY_PROBE(elem) && gst_property_probe_get_property( GST_PROPERTY_PROBE( elem), propertyName ) ) { + g_object_set(G_OBJECT(elem), propertyName, propertyValue.constData(), (const char*)NULL); + return true; + } + return false; +} + +/** + * Queries an element for the value of an object property + */ +QByteArray GstHelper::property(GstElement *elem, const char *propertyName) +{ + Q_ASSERT(elem); + Q_ASSERT(propertyName && strlen(propertyName)); + QByteArray retVal; + + if (GST_IS_PROPERTY_PROBE(elem) && gst_property_probe_get_property( GST_PROPERTY_PROBE(elem), propertyName)) { + gchar *value = NULL; + g_object_get (G_OBJECT(elem), propertyName, &value, (const char*)NULL); + retVal = QByteArray(value); + g_free (value); + } + return retVal; +} + +/** + * Queries a GstObject for it's name + */ +QByteArray GstHelper::name(GstObject *obj) +{ + Q_ASSERT(obj); + QByteArray retVal; + gchar *value = NULL; + if ((value = gst_object_get_name (obj))) { + retVal = QByteArray(value); + g_free (value); + } + return retVal; +} + + +/*** + * Creates an instance of a playbin with "audio-src" and + * "video-src" ghost pads to allow redirected output streams. + * + * ### This function is probably not required now that MediaObject is based + * on decodebin directly. + */ +GstElement* GstHelper::createPluggablePlaybin() +{ + GstElement *playbin = 0; + //init playbin and add to our pipeline + playbin = gst_element_factory_make("playbin", NULL); + + //Create an identity element to redirect sound + GstElement *audioSinkBin = gst_bin_new (NULL); + GstElement *audioPipe = gst_element_factory_make("identity", NULL); + gst_bin_add(GST_BIN(audioSinkBin), audioPipe); + + //Create a sinkpad on the identity + GstPad *audiopad = gst_element_get_pad (audioPipe, "sink"); + gst_element_add_pad (audioSinkBin, gst_ghost_pad_new ("sink", audiopad)); + gst_object_unref (audiopad); + + //Create an "audio_src" source pad on the playbin + GstPad *audioPlaypad = gst_element_get_pad (audioPipe, "src"); + gst_element_add_pad (playbin, gst_ghost_pad_new ("audio_src", audioPlaypad)); + gst_object_unref (audioPlaypad); + + //Done with our audio redirection + g_object_set (G_OBJECT(playbin), "audio-sink", audioSinkBin, (const char*)NULL); + + // * * Redirect video to "video_src" pad : * * + + //Create an identity element to redirect sound + GstElement *videoSinkBin = gst_bin_new (NULL); + GstElement *videoPipe = gst_element_factory_make("identity", NULL); + gst_bin_add(GST_BIN(videoSinkBin), videoPipe); + + //Create a sinkpad on the identity + GstPad *videopad = gst_element_get_pad (videoPipe, "sink"); + gst_element_add_pad (videoSinkBin, gst_ghost_pad_new ("sink", videopad)); + gst_object_unref (videopad); + + //Create an "audio_src" source pad on the playbin + GstPad *videoPlaypad = gst_element_get_pad (videoPipe, "src"); + gst_element_add_pad (playbin, gst_ghost_pad_new ("video_src", videoPlaypad)); + gst_object_unref (videoPlaypad); + + //Done with our video redirection + g_object_set (G_OBJECT(playbin), "video-sink", videoSinkBin, (const char*)NULL); + return playbin; +} + + +} //namespace Gstreamer +} //namespace Phonon + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/gsthelper.h b/src/3rdparty/phonon/gstreamer/gsthelper.h new file mode 100644 index 0000000..ff342a4 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/gsthelper.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_GSTHELPER_H +#define Phonon_GSTREAMER_GSTHELPER_H + +#include "common.h" + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +template<class T> class QList; +class QByteArray; + +namespace Phonon +{ +namespace Gstreamer +{ +class GstHelper +{ +public: + static QList<QByteArray> extractProperties(GstElement *elem, const QByteArray &value); + static bool setProperty(GstElement *elem, const char *propertyName, const QByteArray &propertyValue); + static QByteArray property(GstElement *elem, const char *propertyName); + static QByteArray name(GstObject *elem); + static GstElement* createPluggablePlaybin(); +}; + +} // ns Gstreamer +} // ns Phonon + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_GSTHELPER_H diff --git a/src/3rdparty/phonon/gstreamer/gstreamer.desktop b/src/3rdparty/phonon/gstreamer/gstreamer.desktop new file mode 100644 index 0000000..b62472b --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/gstreamer.desktop @@ -0,0 +1,51 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=PhononBackend +MimeType=application/x-annodex;video/quicktime;video/x-quicktime;audio/x-m4a;application/x-quicktimeplayer;video/mkv;video/msvideo;video/x-msvideo;video/x-flic;audio/x-aiff;audio/aiff;audio/x-pn-aiff;audio/x-realaudio;audio/basic;audio/x-basic;audio/x-pn-au;audio/x-8svx;audio/8svx;audio/x-16sv;audio/168sv;image/x-ilbm;image/ilbm;video/x-anim;video/anim;image/png;image/x-png;video/mng;video/x-mng;audio/x-ogg;audio/x-speex+ogg;application/ogg;application/ogg;audio/vnd.rn-realaudio;audio/x-pn-realaudio-plugin;audio/x-real-audio;application/vnd.rn-realmedia;video/mpeg;video/x-mpeg;audio/x-wav;audio/wav;audio/x-pn-wav;audio/x-pn-windows-acm;audio/mpeg2;audio/x-mpeg2;audio/mpeg3;audio/x-mpeg3;audio/mpeg;audio/x-mpeg;x-mpegurl;audio/x-mpegurl;audio/mp3;audio/mpeg; +X-KDE-Library=phonon_gstreamer +X-KDE-PhononBackendInfo-InterfaceVersion=1 +X-KDE-PhononBackendInfo-Version=0.1 +X-KDE-PhononBackendInfo-Website=http://gstreamer.freedesktop.org/ +Icon=phonon-gstreamer +InitialPreference=10 + +Name=GStreamer +Name[pa]=ਜੀਸਟੀਰਮਰ +Name[sr]=Гстример +Name[sv]=Gstreamer +Name[x-test]=xxGStreamerxx + +Comment=Phonon GStreamer backend +Comment[bg]=Phonon GStreamer +Comment[ca]=Dorsal GStreamer del Phonon +Comment[da]=GStreamer-backend til Phonon +Comment[de]=Phonon-Treiber für GStreamer +Comment[el]=Σύστημα υποστήριξης GStreamer του Phonon +Comment[es]=Motor GStreamer para Phonon +Comment[et]=Phononi GStreameri taustaprogramm +Comment[fr]=Système de gestion GStreamer pour Phonon +Comment[ga]=Inneall GStreamer le haghaidh Phonon +Comment[gl]=Infraestrutura de GStreamer para Phonon +Comment[is]=Phonon GStreamer bakendi +Comment[it]=Motore Gstreamer di Phonon +Comment[ja]=Phonon GStreamer バックエンド +Comment[ko]=Phonon GStreamer 백엔드 +Comment[ku]=Binesaza Phonon GStreamer +Comment[lv]=Phonon GStreamer aizmugure +Comment[nds]=Phonon-Hülpprogramm GStreamer +Comment[nl]=GStreamer-backend (Phonon) +Comment[nn]=Phonon-motor for GStreamer +Comment[pa]=ਫੋਨੋਨ ਜਸਟੀਰਮਰ ਬੈਕਐਂਡ +Comment[pl]=Obsługa GStreamera przez Phonon +Comment[pt]=Infra-estrutura do GStreamer para o Phonon +Comment[pt_BR]=Infraestrutura Phonon GStreamer +Comment[sk]=GStreamer podsystém +Comment[sl]=Phononova hrbtenica GStreamer +Comment[sr]=Гстример као позадина Фонона +Comment[sr@latin]=GStreamer kao pozadina Phonona +Comment[sv]=Phonon Gstreamer-gränssnitt +Comment[tr]=Phonon GStreamer arka ucu +Comment[uk]=Сервер GStreamer для Phonon +Comment[x-test]=xxPhonon GStreamer backendxx +Comment[zh_CN]=Phonon GStreamer 后端 +Comment[zh_TW]=Phonon GStreamer 後端介面 diff --git a/src/3rdparty/phonon/gstreamer/lgpl-2.1.txt b/src/3rdparty/phonon/gstreamer/lgpl-2.1.txt new file mode 100644 index 0000000..5ab7695 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/lgpl-2.1.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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 library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/src/3rdparty/phonon/gstreamer/lgpl-3.txt b/src/3rdparty/phonon/gstreamer/lgpl-3.txt new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/lgpl-3.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/src/3rdparty/phonon/gstreamer/medianode.cpp b/src/3rdparty/phonon/gstreamer/medianode.cpp new file mode 100644 index 0000000..7257972 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/medianode.cpp @@ -0,0 +1,456 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "common.h" +#include "medianode.h" +#include "mediaobject.h" +#include "message.h" +#include "backend.h" +#include "gsthelper.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +MediaNode::MediaNode(Backend *backend, NodeDescription description) : + m_isValid(false), + m_root(0), + m_audioTee(0), + m_videoTee(0), + m_fakeAudioSink(0), + m_fakeVideoSink(0), + m_backend(backend), + m_description(description) +{ + if ((description & AudioSink) && (description & VideoSink)) { + Q_ASSERT(0); // A node cannot accept both audio and video + } + + if (description & AudioSource) { + m_audioTee = gst_element_factory_make("tee", NULL); + gst_object_ref (GST_OBJECT (m_audioTee)); + gst_object_sink (GST_OBJECT (m_audioTee)); + + // Fake audio sink to swallow unconnected audio pads + m_fakeAudioSink = gst_element_factory_make("fakesink", NULL); + g_object_set (G_OBJECT (m_fakeAudioSink), "sync", TRUE, (const char*)NULL); + gst_object_ref (GST_OBJECT (m_fakeAudioSink)); + gst_object_sink (GST_OBJECT (m_fakeAudioSink)); + } + + if (description & VideoSource) { + m_videoTee = gst_element_factory_make("tee", NULL); + gst_object_ref (GST_OBJECT (m_videoTee)); + gst_object_sink (GST_OBJECT (m_videoTee)); + + // Fake video sink to swallow unconnected video pads + m_fakeVideoSink = gst_element_factory_make("fakesink", NULL); + g_object_set (G_OBJECT (m_fakeVideoSink), "sync", TRUE, (const char*)NULL); + gst_object_ref (GST_OBJECT (m_fakeVideoSink)); + gst_object_sink (GST_OBJECT (m_fakeVideoSink)); + } +} + +MediaNode::~MediaNode() +{ + if (m_videoTee) { + gst_element_set_state(m_videoTee, GST_STATE_NULL); + gst_object_unref(m_videoTee); + } + + if (m_audioTee) { + gst_element_set_state(m_audioTee, GST_STATE_NULL); + gst_object_unref(m_audioTee); + } + + if (m_fakeAudioSink) { + gst_element_set_state(m_fakeAudioSink, GST_STATE_NULL); + gst_object_unref(m_fakeAudioSink); + } + + if (m_fakeVideoSink) { + gst_element_set_state(m_fakeVideoSink, GST_STATE_NULL); + gst_object_unref(m_fakeVideoSink); + } +} + + +/** + * Connects children recursively from a mediaobject root + */ +bool MediaNode::buildGraph() +{ + Q_ASSERT(root()); //We cannot build the graph without a root element source + + bool success = link(); + + if (success) { + // connect children recursively + for (int i=0; i< m_audioSinkList.size(); ++i) { + if (MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i])) { + node->setRoot(root()); + if (!node->buildGraph()) + success = false; + } + } + + for (int i=0; i < m_videoSinkList.size(); ++i) { + if (MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i])) { + node->setRoot(root()); + if (!node->buildGraph()) + success = false; + } + } + } + + if (!success) + unlink(); + + return success; +} + +/** + * Disconnects children recursively + */ +bool MediaNode::breakGraph() +{ + for (int i=0; i<m_audioSinkList.size(); ++i) { + MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i]); + if (!node || !node->breakGraph()) + return false; + node->setRoot(0); + } + + for (int i=0; i <m_videoSinkList.size(); ++i) { + MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i]); + if (!node || !node->breakGraph()) + return false; + node->setRoot(0); + } + unlink(); + return true; +} + +bool MediaNode::connectNode(QObject *obj) +{ + MediaNode *sink = qobject_cast<MediaNode*>(obj); + + bool success = false; + + if (sink) { + + if (!sink->isValid()) { + m_backend->logMessage(QString("Trying to link to an invalid node (%0)").arg(sink->name()), Backend::Warning); + return false; + } + + if (sink->root()) { + m_backend->logMessage("Trying to link a node that is already linked to a different mediasource ", Backend::Warning); + return false; + } + + if ((m_description & AudioSource) && (sink->m_description & AudioSink)) { + m_audioSinkList << obj; + MediaNodeEvent event(MediaNodeEvent::AudioSinkAdded, sink); + root()->mediaNodeEvent(&event); + success = true; + } + + if ((m_description & VideoSource) && (sink->m_description & VideoSink)) { + m_videoSinkList << obj; + MediaNodeEvent event(MediaNodeEvent::VideoSinkAdded, sink); + root()->mediaNodeEvent(&event); + success = true; + } + + // If we have a root source, and we are connected + // try to link the gstreamer elements + if (success && root()) { + MediaNodeEvent mediaObjectConnected(MediaNodeEvent::MediaObjectConnected, root()); + notify(&mediaObjectConnected); + root()->buildGraph(); + } + } + return success; +} + +bool MediaNode::disconnectNode(QObject *obj) +{ + MediaNode *sink = qobject_cast<MediaNode*>(obj); + if (root()) { + // Disconnecting elements while playing or paused seems to cause + // potential deadlock. Hence we force the pipeline into ready state + // before any nodes are disconnected. + gst_element_set_state(root()->pipeline(), GST_STATE_READY); + + Q_ASSERT(sink->root()); //sink has to have a root since it is onnected + + if (sink->description() & (AudioSink)) { + GstPad *sinkPad = gst_element_get_pad(sink->audioElement(), "sink"); + // Release requested src pad from tee + GstPad *requestedPad = gst_pad_get_peer(sinkPad); + if (requestedPad) { + gst_element_release_request_pad(m_audioTee, requestedPad); + gst_object_unref(requestedPad); + } + if (GST_ELEMENT_PARENT(sink->audioElement())) + gst_bin_remove(GST_BIN(root()->audioGraph()), sink->audioElement()); + gst_object_unref(sinkPad); + } + + if (sink->description() & (VideoSink)) { + GstPad *sinkPad = gst_element_get_pad(sink->videoElement(), "sink"); + // Release requested src pad from tee + GstPad *requestedPad = gst_pad_get_peer(sinkPad); + if (requestedPad) { + gst_element_release_request_pad(m_videoTee, requestedPad); + gst_object_unref(requestedPad); + } + if (GST_ELEMENT_PARENT(sink->videoElement())) + gst_bin_remove(GST_BIN(root()->videoGraph()), sink->videoElement()); + gst_object_unref(sinkPad); + } + + sink->breakGraph(); + sink->setRoot(0); + } + + m_videoSinkList.removeAll(obj); + m_audioSinkList.removeAll(obj); + + if (sink->m_description & AudioSink) { + // Remove sink from graph + MediaNodeEvent event(MediaNodeEvent::AudioSinkRemoved, sink); + mediaNodeEvent(&event); + return true; + } + + if ((m_description & VideoSource) && (sink->m_description & VideoSink)) { + // Remove sink from graph + MediaNodeEvent event(MediaNodeEvent::VideoSinkRemoved, sink); + mediaNodeEvent(&event); + return true; + } + + return false; +} + +void MediaNode::mediaNodeEvent(const MediaNodeEvent *) {} + +/** + * Propagates an event down the graph + * sender is responsible for deleting the event + */ +void MediaNode::notify(const MediaNodeEvent *event) +{ + Q_ASSERT(event); + mediaNodeEvent(event); + for (int i=0; i<m_audioSinkList.size(); ++i) { + MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i]); + node->notify(event); + } + + for (int i=0; i<m_videoSinkList.size(); ++i) { + MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i]); + node->notify(event); + } +} + +/* + * Requests a new tee pad and connects a node to it + */ +bool MediaNode::addOutput(MediaNode *output, GstElement *tee) +{ + Q_ASSERT(root()); + + bool success = true; + + GstElement *sinkElement = 0; + if (output->description() & AudioSink) + sinkElement = output->audioElement(); + else if (output->description() & VideoSink) + sinkElement = output->videoElement(); + + Q_ASSERT(sinkElement); + + if (!sinkElement) + return false; + + GstState state = GST_STATE (root()->pipeline()); + GstPad *srcPad = gst_element_get_request_pad (tee, "src%d"); + GstPad *sinkPad = gst_element_get_pad (sinkElement, "sink"); + + if (!sinkPad) { + success = false; + } else if (gst_pad_is_linked(sinkPad)) { + gst_object_unref (GST_OBJECT (sinkPad)); + gst_object_unref (GST_OBJECT (srcPad)); + return true; + } + + if (success) { + if (output->description() & AudioSink) + gst_bin_add(GST_BIN(root()->audioGraph()), sinkElement); + else if (output->description() & VideoSink) + gst_bin_add(GST_BIN(root()->videoGraph()), sinkElement); + } + + if (success) { + gst_pad_link(srcPad, sinkPad); + gst_element_set_state(sinkElement, state); + } else { + gst_element_release_request_pad(tee, srcPad); + } + + gst_object_unref (GST_OBJECT (srcPad)); + gst_object_unref (GST_OBJECT (sinkPad)); + + return success; +} + +// Used to seal up unconnected source nodes by connecting unconnected src pads to fake sinks +bool MediaNode::connectToFakeSink(GstElement *tee, GstElement *sink, GstElement *bin) +{ + bool success = true; + GstPad *sinkPad = gst_element_get_pad (sink, "sink"); + + if (GST_PAD_IS_LINKED (sinkPad)) { + //This fakesink is already connected + gst_object_unref (sinkPad); + return true; + } + + GstPad *srcPad = gst_element_get_request_pad (tee, "src%d"); + gst_bin_add(GST_BIN(bin), sink); + if (success) + success = (gst_pad_link (srcPad, sinkPad) == GST_PAD_LINK_OK); + if (success) + success = (gst_element_set_state(sink, GST_STATE(bin)) != GST_STATE_CHANGE_FAILURE); + gst_object_unref (srcPad); + gst_object_unref (sinkPad); + return success; +} + +// Used to seal up unconnected source nodes by connecting unconnected src pads to fake sinks +bool MediaNode::releaseFakeSinkIfConnected(GstElement *tee, GstElement *fakesink, GstElement *bin) +{ + if (GST_ELEMENT_PARENT(fakesink) == GST_ELEMENT(bin)) { + GstPad *sinkPad = gst_element_get_pad(fakesink, "sink"); + + // Release requested src pad from tee + GstPad *requestedPad = gst_pad_get_peer(sinkPad); + if (requestedPad) { + gst_element_release_request_pad(tee, requestedPad); + gst_object_unref(requestedPad); + } + gst_object_unref(sinkPad); + + gst_element_set_state(fakesink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(bin), fakesink); + Q_ASSERT(!GST_ELEMENT_PARENT(fakesink)); + } + return true; +} + +bool MediaNode::linkMediaNodeList(QList<QObject *> &list, GstElement *bin, GstElement *tee, GstElement *fakesink, GstElement *src) +{ + if (!GST_ELEMENT_PARENT(tee)) { + gst_bin_add(GST_BIN(bin), tee); + if (!gst_element_link_pads(src, "src", tee, "sink")) + return false; + gst_element_set_state(tee, GST_STATE(bin)); + } + if (list.isEmpty()) { + //connect node to a fake sink to avoid clogging the pipeline + if (!connectToFakeSink(tee, fakesink, bin)) + return false; + } else { + // Remove fake sink if previously connected + if (!releaseFakeSinkIfConnected(tee, fakesink, bin)) + return false; + + for (int i = 0 ; i < list.size() ; ++i) { + QObject *sink = list[i]; + if (MediaNode *output = qobject_cast<MediaNode*>(sink)) { + if (!addOutput(output, tee)) + return false; + } + } + } + return true; +} + +bool MediaNode::link() +{ + // Rewire everything + if ((description() & AudioSource)) { + if (!linkMediaNodeList(m_audioSinkList, root()->audioGraph(), m_audioTee, m_fakeAudioSink, audioElement())) + return false; + } + + if ((description() & VideoSource)) { + if (!linkMediaNodeList(m_videoSinkList, root()->videoGraph(), m_videoTee, m_fakeVideoSink, videoElement())) + return false; + } + return true; +} + +bool MediaNode::unlink() +{ + Q_ASSERT(root()); + if (description() & AudioSource) { + if (GST_ELEMENT_PARENT(m_audioTee) == GST_ELEMENT(root()->audioGraph())) { + gst_element_set_state(m_audioTee, GST_STATE_NULL); + gst_bin_remove(GST_BIN(root()->audioGraph()), m_audioTee); + } + for (int i=0; i<m_audioSinkList.size(); ++i) { + QObject *audioSink = m_audioSinkList[i]; + if (MediaNode *output = qobject_cast<MediaNode*>(audioSink)) { + GstElement *element = output->audioElement(); + if (GST_ELEMENT_PARENT(element) == GST_ELEMENT(root()->audioGraph())) { + gst_element_set_state(element, GST_STATE_NULL); + gst_bin_remove(GST_BIN(root()->audioGraph()), element); + } + } + } + } else if (description() & VideoSource) { + if (GST_ELEMENT_PARENT(m_videoTee) == GST_ELEMENT(root()->videoGraph())) { + gst_element_set_state(m_videoTee, GST_STATE_NULL); + gst_bin_remove(GST_BIN(root()->videoGraph()), m_videoTee); + } + for (int i=0; i <m_videoSinkList.size(); ++i) { + QObject *videoSink = m_videoSinkList[i]; + if (MediaNode *vw = qobject_cast<MediaNode*>(videoSink)) { + GstElement *element = vw->videoElement(); + if (GST_ELEMENT_PARENT(element) == GST_ELEMENT(root()->videoGraph())) { + gst_element_set_state(element, GST_STATE_NULL); + gst_bin_remove(GST_BIN(root()->videoGraph()), element); + } + } + } + } + return true; +} + + +} // ns Gstreamer +} // ns Phonon + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/medianode.h b/src/3rdparty/phonon/gstreamer/medianode.h new file mode 100644 index 0000000..6e3850a --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/medianode.h @@ -0,0 +1,128 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_MEDIANODE_H +#define Phonon_GSTREAMER_MEDIANODE_H + +#include "common.h" +#include "medianodeevent.h" + +#include <QtCore/QObject> +#include <QtCore/QList> +#include <QtCore/QSize> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon { +namespace Gstreamer { + +class Message; +class MediaObject; +class Backend; + +class MediaNode { +public: + enum NodeDescriptionEnum { + AudioSource = 0x1, + AudioSink = 0x2, + VideoSource = 0x4, + VideoSink = 0x8 + }; + Q_DECLARE_FLAGS(NodeDescription, NodeDescriptionEnum) + + MediaNode(Backend *backend, NodeDescription description); + + virtual ~MediaNode(); + + bool connectNode(QObject *other); + bool disconnectNode(QObject *other); + + bool buildGraph(); + bool breakGraph(); + + virtual bool link(); + virtual bool unlink(); + + NodeDescription description() const { + return m_description; + } + + bool isValid() { + return m_isValid; + } + + MediaObject *root() { + return m_root; + } + + void setRoot(MediaObject *mediaObject) { + m_root = mediaObject; + } + + void notify(const MediaNodeEvent *event); + + Backend *backend() { + return m_backend; + } + + const QString &name() { + return m_name; + } + + virtual GstElement *audioElement() { + return m_audioTee; + } + + virtual GstElement *videoElement() { + return m_videoTee; + } + +protected: + bool connectToFakeSink(GstElement *tee, GstElement *sink, GstElement *bin); + bool releaseFakeSinkIfConnected(GstElement *tee, GstElement *sink, GstElement *bin); + bool linkMediaNodeList(QList<QObject *> &list, GstElement *bin, GstElement *tee, GstElement *sink, GstElement *src); + + virtual void mediaNodeEvent(const MediaNodeEvent *event); + QList<QObject *> m_audioSinkList; + QList<QObject *> m_videoSinkList; + + bool m_isValid; + MediaObject *m_root; + GstElement *m_audioTee; + GstElement *m_videoTee; + GstElement *m_fakeAudioSink; + GstElement *m_fakeVideoSink; + Backend *m_backend; + QString m_name; + +private: + bool addOutput(MediaNode *, GstElement *tee); + NodeDescription m_description; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(MediaNode::NodeDescription) + +} // ns Gstreamer +} // ns Phonon + +Q_DECLARE_INTERFACE(Phonon::Gstreamer::MediaNode, "org.phonon.gstreamer.MediaNode") + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_MEDIANODE_H diff --git a/src/3rdparty/phonon/gstreamer/medianodeevent.cpp b/src/3rdparty/phonon/gstreamer/medianodeevent.cpp new file mode 100644 index 0000000..20560ee --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/medianodeevent.cpp @@ -0,0 +1,38 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "medianodeevent.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +MediaNodeEvent::MediaNodeEvent(Type type, const void *data) : + eventType(type), + eventData(data) +{} + +MediaNodeEvent::~MediaNodeEvent() +{} + +} +} // namespace Phonon::Gstreamer + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/medianodeevent.h b/src/3rdparty/phonon/gstreamer/medianodeevent.h new file mode 100644 index 0000000..cacf4a5 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/medianodeevent.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_MEDIANODEEVENT_H +#define Phonon_GSTREAMER_MEDIANODEEVENT_H + +#include "common.h" + +#include <QtCore> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ +class MediaNodeEvent +{ +public: + enum Type { + VideoAvailable, + AudioAvailable, + SourceChanged, + MediaObjectConnected, + StateChanged, + VideoSinkAdded, + VideoSinkRemoved, + AudioSinkAdded, + AudioSinkRemoved, + VideoHandleRequest, + VideoSizeChanged + }; + + MediaNodeEvent(Type type, const void *data = 0); + virtual ~MediaNodeEvent(); + + inline Type type() const + { + return eventType; + }; + inline const void* data() const + { + return eventData; + }; + +private: + Type eventType; + const void *eventData; +}; + +} +} // namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_MEDIANODEEVENT_H diff --git a/src/3rdparty/phonon/gstreamer/mediaobject.cpp b/src/3rdparty/phonon/gstreamer/mediaobject.cpp new file mode 100644 index 0000000..74fc1b4 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/mediaobject.cpp @@ -0,0 +1,1479 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ +#include <cmath> +#include <gst/interfaces/propertyprobe.h> +#include "common.h" +#include "mediaobject.h" +#include "videowidget.h" +#include "message.h" +#include "backend.h" +#include "streamreader.h" +#include "phononsrc.h" +#include <QtCore> +#include <QtCore/QTimer> +#include <QtCore/QVector> +#include <QtCore/QFile> +#include <QtCore/QByteRef> +#include <QtCore/QStringList> +#include <QtCore/QEvent> +#include <QApplication> + +#define ABOUT_TO_FINNISH_TIME 2000 +#define MAX_QUEUE_TIME 20 * GST_SECOND + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +MediaObject::MediaObject(Backend *backend, QObject *parent) + : QObject(parent) + , MediaNode(backend, AudioSource | VideoSource) + , m_resumeState(false) + , m_oldState(Phonon::LoadingState) + , m_oldPos(0) + , m_state(Phonon::LoadingState) + , m_pendingState(Phonon::LoadingState) + , m_tickTimer(new QTimer(this)) + , m_prefinishMark(0) + , m_transitionTime(0) + , m_posAtSeek(-1) + , m_prefinishMarkReachedNotEmitted(true) + , m_aboutToFinishEmitted(false) + , m_loading(false) + , m_capsHandler(0) + , m_datasource(0) + , m_decodebin(0) + , m_audioPipe(0) + , m_videoPipe(0) + , m_totalTime(-1) + , m_bufferPercent(0) + , m_hasVideo(false) + , m_videoStreamFound(false) + , m_hasAudio(false) + , m_seekable(false) + , m_atEndOfStream(false) + , m_atStartOfStream(false) + , m_error(Phonon::NoError) + , m_pipeline(0) + , m_audioGraph(0) + , m_videoGraph(0) + , m_previousTickTime(-1) + , m_resetNeeded(false) + , m_autoplayTitles(true) + , m_availableTitles(0) + , m_currentTitle(1) +{ + qRegisterMetaType<GstCaps*>("GstCaps*"); + qRegisterMetaType<State>("State"); + + static int count = 0; + m_name = "MediaObject" + QString::number(count++); + + if (!m_backend->isValid()) { + setError(tr("Cannot start playback. \n\nCheck your Gstreamer installation and make sure you " + "\nhave libgstreamer-plugins-base installed."), Phonon::FatalError); + } else { + m_root = this; + createPipeline(); + m_backend->addBusWatcher(this); + connect(m_tickTimer, SIGNAL(timeout()), SLOT(emitTick())); + } + connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), + this, SLOT(notifyStateChange(Phonon::State, Phonon::State))); + +} + +MediaObject::~MediaObject() +{ + m_backend->removeBusWatcher(this); + if (m_pipeline) { + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_object_unref(m_pipeline); + } + if (m_audioGraph) { + gst_element_set_state(m_audioGraph, GST_STATE_NULL); + gst_object_unref(m_audioGraph); + } + if (m_videoGraph) { + gst_element_set_state(m_videoGraph, GST_STATE_NULL); + gst_object_unref(m_videoGraph); + } +} + +QString stateString(const Phonon::State &state) +{ + switch (state) { + case Phonon::LoadingState: + return QString("LoadingState"); + case Phonon::StoppedState: + return QString("StoppedState"); + case Phonon::PlayingState: + return QString("PlayingState"); + case Phonon::BufferingState: + return QString("BufferingState"); + case Phonon::PausedState: + return QString("PausedState"); + case Phonon::ErrorState: + return QString("ErrorState"); + } + return QString(); +} + +void MediaObject::saveState() +{ + //Only first resumeState is respected + if (m_resumeState) + return; + + if (m_pendingState == Phonon::PlayingState || m_pendingState == Phonon::PausedState) { + m_resumeState = true; + m_oldState = m_pendingState; + m_oldPos = getPipelinePos(); + } +} + +void MediaObject::resumeState() +{ + if (m_resumeState) + QMetaObject::invokeMethod(this, "setState", Qt::QueuedConnection, Q_ARG(State, m_oldState)); +} + +void MediaObject::newPadAvailable (GstPad *pad) +{ + GstCaps *caps; + GstStructure *str; + caps = gst_pad_get_caps (pad); + if (caps) { + str = gst_caps_get_structure (caps, 0); + QString mediaString(gst_structure_get_name (str)); + + if (mediaString.startsWith("video")) { + connectVideo(pad); + } else if (mediaString.startsWith("audio")) { + connectAudio(pad); + } else { + m_backend->logMessage("Could not connect pad", Backend::Warning); + } + gst_caps_unref (caps); + } +} + +void MediaObject::cb_newpad (GstElement *decodebin, + GstPad *pad, + gboolean last, + gpointer data) +{ + Q_UNUSED(decodebin); + Q_UNUSED(pad); + Q_UNUSED(last); + Q_UNUSED(data); + + MediaObject *media = static_cast<MediaObject*>(data); + Q_ASSERT(media); + media->newPadAvailable(pad); +} + +void MediaObject::noMorePadsAvailable () +{ + if (m_missingCodecs.size() > 0) { + bool canPlay = (m_hasAudio || m_videoStreamFound); + Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError; + if (error == Phonon::NormalError && m_hasVideo && !m_videoStreamFound) { + m_hasVideo = false; + emit hasVideoChanged(false); + } + QString codecs = m_missingCodecs.join(", "); + setError(QString(tr("A required codec is missing. You need to install the following codec(s) to play this content: %0")).arg(codecs), error); + m_missingCodecs.clear(); + } +} + +void MediaObject::cb_no_more_pads (GstElement * decodebin, gpointer data) +{ + Q_UNUSED(decodebin); + MediaObject *media = static_cast<MediaObject*>(data); + Q_ASSERT(media); + QMetaObject::invokeMethod(media, "noMorePadsAvailable", Qt::QueuedConnection); +} + +typedef void (*Ptr_gst_pb_utils_init)(); +typedef gchar* (*Ptr_gst_pb_utils_get_codec_description)(const GstCaps *); + +void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data) +{ + Q_UNUSED(decodebin); + Q_UNUSED(pad); + MediaObject *media = static_cast<MediaObject*>(data); + Q_ASSERT(media); + + QString value = "unknown codec"; + + // These functions require GStreamer > 0.10.12 + static Ptr_gst_pb_utils_init p_gst_pb_utils_init = 0; + static Ptr_gst_pb_utils_get_codec_description p_gst_pb_utils_get_codec_description = 0; + if (!p_gst_pb_utils_init) { + p_gst_pb_utils_init = (Ptr_gst_pb_utils_init)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_init"); + p_gst_pb_utils_get_codec_description = (Ptr_gst_pb_utils_get_codec_description)QLibrary::resolve(QLatin1String("gstpbutils-0.10"), 0, "gst_pb_utils_get_codec_description"); + if (p_gst_pb_utils_init) + p_gst_pb_utils_init(); + } + if (p_gst_pb_utils_get_codec_description) { + gchar *codecName = NULL; + codecName = p_gst_pb_utils_get_codec_description (caps); + value = QString::fromUtf8(codecName); + g_free (codecName); + } else { + // For GStreamer versions < 0.10.12 + GstStructure *str = gst_caps_get_structure (caps, 0); + value = QString::fromUtf8(gst_structure_get_name (str)); + } + media->addMissingCodecName(value); +} + +static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data) +{ + GstPad *pad = GST_PAD(obj); + GstCaps *caps = gst_pad_get_caps (pad); + Q_ASSERT(caps); + MediaObject *media = static_cast<MediaObject*>(data); + + // We do not want any more notifications until the source changes + g_signal_handler_disconnect(pad, media->capsHandler()); + + // setVideoCaps calls loadingComplete(), meaning we cannot call it from + // the streaming thread + QMetaObject::invokeMethod(media, "setVideoCaps", Qt::QueuedConnection, Q_ARG(GstCaps *, caps)); +} + +void MediaObject::setVideoCaps(GstCaps *caps) +{ + GstStructure *str; + gint width, height; + + if ((str = gst_caps_get_structure (caps, 0))) { + if (gst_structure_get_int (str, "width", &width) && gst_structure_get_int (str, "height", &height)) { + gint aspectNum = 0; + gint aspectDenum = 0; + if (gst_structure_get_fraction(str, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { + if (aspectDenum > 0) + width = width*aspectNum/aspectDenum; + } + // Let child nodes know about our new video size + QSize size(width, height); + MediaNodeEvent event(MediaNodeEvent::VideoSizeChanged, &size); + notify(&event); + } + } + gst_caps_unref(caps); +} + +// Adds an element to the pipeline if not previously added +bool MediaObject::addToPipeline(GstElement *elem) +{ + bool success = true; + if (!GST_ELEMENT_PARENT(elem)) { // If not already in pipeline + success = gst_bin_add(GST_BIN(m_pipeline), elem); + } + return success; +} + +void MediaObject::connectVideo(GstPad *pad) +{ + GstState currentState = GST_STATE(m_pipeline); + if (addToPipeline(m_videoGraph)) { + GstPad *videopad = gst_element_get_pad (m_videoGraph, "sink"); + if (!GST_PAD_IS_LINKED (videopad) && (gst_pad_link (pad, videopad) == GST_PAD_LINK_OK)) { + gst_element_set_state(m_videoGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED); + m_videoStreamFound = true; + m_backend->logMessage("Video track connected", Backend::Info, this); + // Note that the notify::caps _must_ be installed after linking to work with Dapper + m_capsHandler = g_signal_connect(pad, "notify::caps", G_CALLBACK(notifyVideoCaps), this); + + if (!m_loading && !m_hasVideo) { + m_hasVideo = m_videoStreamFound; + emit hasVideoChanged(m_hasVideo); + } + } + gst_object_unref (videopad); + } else { + m_backend->logMessage("The video stream could not be plugged.", Backend::Info, this); + } +} + +void MediaObject::connectAudio(GstPad *pad) +{ + GstState currentState = GST_STATE(m_pipeline); + if (addToPipeline(m_audioGraph)) { + GstPad *audiopad = gst_element_get_pad (m_audioGraph, "sink"); + if (!GST_PAD_IS_LINKED (audiopad) && (gst_pad_link (pad, audiopad)==GST_PAD_LINK_OK)) { + gst_element_set_state(m_audioGraph, currentState == GST_STATE_PLAYING ? GST_STATE_PLAYING : GST_STATE_PAUSED); + m_hasAudio = true; + m_backend->logMessage("Audio track connected", Backend::Info, this); + } + gst_object_unref (audiopad); + } else { + m_backend->logMessage("The audio stream could not be plugged.", Backend::Info, this); + } +} + +void MediaObject::cb_pad_added(GstElement *decodebin, + GstPad *pad, + gpointer data) +{ + Q_UNUSED(decodebin); + GstPad *decodepad = static_cast<GstPad*>(data); + gst_pad_link (pad, decodepad); + gst_object_unref (decodepad); +} + +/** + * Create a media source from a given URL. + * + * returns true if successful + */ +bool MediaObject::createPipefromURL(const QUrl &url) +{ + // Remove any existing data source + if (m_datasource) { + gst_bin_remove(GST_BIN(m_pipeline), m_datasource); + // m_pipeline has the only ref to datasource + m_datasource = 0; + } + + // Verify that the uri can be parsed + if (!url.isValid()) { + m_backend->logMessage(QString("%1 is not a valid URI").arg(url.toString())); + return false; + } + + // Create a new datasource based on the input URL + QByteArray encoded_cstr_url = url.toEncoded(); + m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL); + if (!m_datasource) + return false; + + // Link data source into pipeline + gst_bin_add(GST_BIN(m_pipeline), m_datasource); + if (!gst_element_link(m_datasource, m_decodebin)) { + // For sources with dynamic pads (such as RtspSrc) we need to connect dynamically + GstPad *decodepad = gst_element_get_pad (m_decodebin, "sink"); + g_signal_connect (m_datasource, "pad-added", G_CALLBACK (&cb_pad_added), decodepad); + } + + return true; +} + +/** + * Create a media source from a media stream + * + * returns true if successful + */ +bool MediaObject::createPipefromStream(const MediaSource &source) +{ + // Remove any existing data source + if (m_datasource) { + gst_bin_remove(GST_BIN(m_pipeline), m_datasource); + // m_pipeline has the only ref to datasource + m_datasource = 0; + } + + m_datasource = GST_ELEMENT(g_object_new(phonon_src_get_type(), NULL)); + if (!m_datasource) + return false; + + StreamReader *streamReader = new StreamReader(source); + g_object_set (G_OBJECT (m_datasource), "iodevice", streamReader, (const char*)NULL); + + // Link data source into pipeline + gst_bin_add(GST_BIN(m_pipeline), m_datasource); + if (!gst_element_link(m_datasource, m_decodebin)) { + gst_bin_remove(GST_BIN(m_pipeline), m_datasource); + return false; + } + return true; +} + +void MediaObject::createPipeline() +{ + m_pipeline = gst_pipeline_new (NULL); + gst_object_ref (GST_OBJECT (m_pipeline)); + gst_object_sink (GST_OBJECT (m_pipeline)); + + m_decodebin = gst_element_factory_make ("decodebin", NULL); + g_signal_connect (m_decodebin, "new-decoded-pad", G_CALLBACK (&cb_newpad), this); + g_signal_connect (m_decodebin, "unknown-type", G_CALLBACK (&cb_unknown_type), this); + g_signal_connect (m_decodebin, "no-more-pads", G_CALLBACK (&cb_no_more_pads), this); + + gst_bin_add(GST_BIN(m_pipeline), m_decodebin); + + // Create a bin to contain the gst elements for this medianode + + // Set up audio graph + m_audioGraph = gst_bin_new(NULL); + gst_object_ref (GST_OBJECT (m_audioGraph)); + gst_object_sink (GST_OBJECT (m_audioGraph)); + + // Note that these queues are only required for streaming content + // And should ideally be created on demand as they will disable + // pull-mode access. Also note that the max-size-time are increased to + // reduce buffer overruns as these are not gracefully handled at the moment. + m_audioPipe = gst_element_factory_make("queue", NULL); + g_object_set(G_OBJECT(m_audioPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL); + gst_bin_add(GST_BIN(m_audioGraph), m_audioPipe); + GstPad *audiopad = gst_element_get_pad (m_audioPipe, "sink"); + gst_element_add_pad (m_audioGraph, gst_ghost_pad_new ("sink", audiopad)); + gst_object_unref (audiopad); + + // Set up video graph + m_videoGraph = gst_bin_new(NULL); + gst_object_ref (GST_OBJECT (m_videoGraph)); + gst_object_sink (GST_OBJECT (m_videoGraph)); + + m_videoPipe = gst_element_factory_make("queue", NULL); + g_object_set(G_OBJECT(m_videoPipe), "max-size-time", MAX_QUEUE_TIME, (const char*)NULL); + gst_bin_add(GST_BIN(m_videoGraph), m_videoPipe); + GstPad *videopad = gst_element_get_pad (m_videoPipe, "sink"); + gst_element_add_pad (m_videoGraph, gst_ghost_pad_new ("sink", videopad)); + gst_object_unref (videopad); + + if (m_pipeline && m_decodebin && m_audioGraph && m_videoGraph && m_audioPipe && m_videoPipe) + m_isValid = true; + else + m_backend->logMessage("Could not create pipeline for media object", Backend::Warning); +} + +/** + * !reimp + */ +State MediaObject::state() const +{ + return m_state; +} + +/** + * !reimp + */ +bool MediaObject::hasVideo() const +{ + return m_hasVideo; +} + +/** + * !reimp + */ +bool MediaObject::isSeekable() const +{ + return m_seekable; +} + +/** + * !reimp + */ +qint64 MediaObject::currentTime() const +{ + if (m_resumeState) + return m_oldPos; + + switch (state()) { + case Phonon::PausedState: + case Phonon::BufferingState: + case Phonon::PlayingState: + return getPipelinePos(); + case Phonon::StoppedState: + case Phonon::LoadingState: + return 0; + case Phonon::ErrorState: + break; + } + return -1; +} + +/** + * !reimp + */ +qint32 MediaObject::tickInterval() const +{ + return m_tickInterval; +} + +/** + * !reimp + */ +void MediaObject::setTickInterval(qint32 newTickInterval) +{ + m_tickInterval = newTickInterval; + if (m_tickInterval <= 0) + m_tickTimer->setInterval(50); + else + m_tickTimer->setInterval(newTickInterval); +} + +/** + * !reimp + */ +void MediaObject::play() +{ + setState(Phonon::PlayingState); + m_resumeState = false; +} + +/** + * !reimp + */ +QString MediaObject::errorString() const +{ + return m_errorString; +} + +/** + * !reimp + */ +Phonon::ErrorType MediaObject::errorType() const +{ + return m_error; +} + +/** + * Set the current state of the mediaObject. + * + * !### Note that both Playing and Paused states are set immediately + * This should obviously be done in response to actual gstreamer state changes + */ +void MediaObject::setState(State newstate) +{ + if (!isValid()) + return; + + if (m_state == newstate) + return; + + if (m_loading) { + // We are still loading. The state will be requested + // when loading has completed. + m_pendingState = newstate; + return; + } + + GstState currentState; + gst_element_get_state (m_pipeline, ¤tState, NULL, 1000); + + switch (newstate) { + case Phonon::BufferingState: + m_backend->logMessage("phonon state request: buffering", Backend::Info, this); + break; + + case Phonon::PausedState: + m_backend->logMessage("phonon state request: paused", Backend::Info, this); + if (currentState == GST_STATE_PAUSED) { + changeState(Phonon::PausedState); + } else if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) { + m_pendingState = Phonon::PausedState; + } else { + m_backend->logMessage("phonon state request failed", Backend::Info, this); + } + break; + + case Phonon::StoppedState: + m_backend->logMessage("phonon state request: Stopped", Backend::Info, this); + if (currentState == GST_STATE_READY) { + changeState(Phonon::StoppedState); + } else if (gst_element_set_state(m_pipeline, GST_STATE_READY) != GST_STATE_CHANGE_FAILURE) { + m_pendingState = Phonon::StoppedState; + } else { + m_backend->logMessage("phonon state request failed", Backend::Info, this); + } + m_atEndOfStream = false; + break; + + case Phonon::PlayingState: + if (m_resetNeeded) { + // ### Note this is a workaround and it should really be gracefully + // handled by medianode when we implement live connections. + // This generally happens if medianodes have been connected after the MediaSource was set + // Note that a side-effect of this is that we resend all meta data. + gst_element_set_state(m_pipeline, GST_STATE_NULL); + m_resetNeeded = false; + // Send a source change so the X11 renderer + // will re-set the overlay + MediaNodeEvent event(MediaNodeEvent::SourceChanged); + notify(&event); + } + m_backend->logMessage("phonon state request: Playing", Backend::Info, this); + if (m_atEndOfStream) { + m_backend->logMessage("EOS already reached", Backend::Info, this); + } else if (currentState == GST_STATE_PLAYING) { + changeState(Phonon::PlayingState); + } else if (!m_atEndOfStream && gst_element_set_state(m_pipeline, GST_STATE_PLAYING) != GST_STATE_CHANGE_FAILURE) { + m_pendingState = Phonon::PlayingState; + } else { + m_backend->logMessage("phonon state request failed", Backend::Info, this); + } + break; + + case Phonon::ErrorState: + m_backend->logMessage("phonon state request : Error", Backend::Warning, this); + m_backend->logMessage(QString("Last error : %0").arg(errorString()) , Backend::Warning, this); + changeState(Phonon::ErrorState); //immediately set error state + break; + + case Phonon::LoadingState: + m_backend->logMessage("phonon state request: Loading", Backend::Info, this); + changeState(Phonon::LoadingState); + break; + } +} + +/* + * Signals that the requested state has completed + * by emitting stateChanged and updates the internal state. + */ +void MediaObject::changeState(State newstate) +{ + if (newstate == m_state) + return; + + Phonon::State oldState = m_state; + m_state = newstate; // m_state must be set before emitting, since + // Error state requires that state() will return the new value + m_pendingState = newstate; + emit stateChanged(newstate, oldState); + + switch (newstate) { + case Phonon::PausedState: + m_backend->logMessage("phonon state changed: paused", Backend::Info, this); + break; + + case Phonon::BufferingState: + m_backend->logMessage("phonon state changed: buffering", Backend::Info, this); + break; + + case Phonon::PlayingState: + m_backend->logMessage("phonon state changed: Playing", Backend::Info, this); + break; + + case Phonon::StoppedState: + m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this); + m_tickTimer->stop(); + break; + + case Phonon::ErrorState: + m_loading = false; + m_backend->logMessage("phonon state changed : Error", Backend::Info, this); + m_backend->logMessage(errorString(), Backend::Warning, this); + break; + + case Phonon::LoadingState: + m_backend->logMessage("phonon state changed: Loading", Backend::Info, this); + break; + } +} + +void MediaObject::setError(const QString &errorString, Phonon::ErrorType error) +{ + m_errorString = errorString; + m_error = error; + m_tickTimer->stop(); + + if (error == Phonon::FatalError) { + m_hasVideo = false; + emit hasVideoChanged(false); + gst_element_set_state(m_pipeline, GST_STATE_READY); + changeState(Phonon::ErrorState); + } else { + if (m_loading) //Flag error only after loading has completed + m_pendingState = Phonon::ErrorState; + else + changeState(Phonon::ErrorState); + } +} + +qint64 MediaObject::totalTime() const +{ + return m_totalTime; +} + +qint32 MediaObject::prefinishMark() const +{ + return m_prefinishMark; +} + +qint32 MediaObject::transitionTime() const +{ + return m_transitionTime; +} + +void MediaObject::setTransitionTime(qint32 time) +{ + m_transitionTime = time; +} + +qint64 MediaObject::remainingTime() const +{ + return totalTime() - currentTime(); +} + +MediaSource MediaObject::source() const +{ + return m_source; +} + +void MediaObject::setNextSource(const MediaSource &source) +{ + if (source.type() == MediaSource::Invalid && + source.type() == MediaSource::Empty) + return; + m_nextSource = source; +} + +/** + * Update total time value from the pipeline + */ +bool MediaObject::updateTotalTime() +{ + GstFormat format = GST_FORMAT_TIME; + gint64 duration = 0; + if (gst_element_query_duration (GST_ELEMENT(m_pipeline), &format, &duration)) { + setTotalTime(duration / GST_MSECOND); + return true; + } + return false; +} + +/** + * Checks if the current source is seekable + */ +void MediaObject::updateSeekable() +{ + if (!isValid()) + return; + + GstQuery *query; + gboolean result; + gint64 start, stop; + query = gst_query_new_seeking(GST_FORMAT_TIME); + result = gst_element_query (m_pipeline, query); + if (result) { + gboolean seekable; + GstFormat format; + gst_query_parse_seeking (query, &format, &seekable, &start, &stop); + + if (m_seekable != seekable) { + m_seekable = seekable; + emit seekableChanged(m_seekable); + } + + if (m_seekable) + m_backend->logMessage("Stream is seekable", Backend::Info, this); + else + m_backend->logMessage("Stream is non-seekable", Backend::Info, this); + } else { + m_backend->logMessage("updateSeekable query failed", Backend::Info, this); + } + gst_query_unref (query); +} + +qint64 MediaObject::getPipelinePos() const +{ + Q_ASSERT(m_pipeline); + + // Note some formats (usually mpeg) do not allow us to accurately seek to the + // beginning or end of the file so we 'fake' it here rather than exposing the front end to potential issues. + if (m_atEndOfStream) + return totalTime(); + if (m_atStartOfStream) + return 0; + if (m_posAtSeek >= 0) + return m_posAtSeek; + + gint64 pos = 0; + GstFormat format = GST_FORMAT_TIME; + gst_element_query_position (GST_ELEMENT(m_pipeline), &format, &pos); + return (pos / GST_MSECOND); +} + +/* + * Internal method to set a new total time for the media object + */ +void MediaObject::setTotalTime(qint64 newTime) +{ + + if (newTime == m_totalTime) + return; + + m_totalTime = newTime; + + emit totalTimeChanged(m_totalTime); +} + +/* + * !reimp + */ +void MediaObject::setSource(const MediaSource &source) +{ + if (!isValid()) + return; + + // We have to reset the state completely here, otherwise + // remnants of the old pipeline can result in strangenes + // such as failing duration queries etc + GstState state; + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_element_get_state (m_pipeline, &state, NULL, 2000); + + m_source = source; + emit currentSourceChanged(m_source); + m_previousTickTime = -1; + m_missingCodecs.clear(); + + // Go into to loading state + changeState(Phonon::LoadingState); + m_loading = true; + m_resetNeeded = false; + m_resumeState = false; + m_pendingState = Phonon::StoppedState; + + // Make sure we start out unconnected + if (GST_ELEMENT_PARENT(m_audioGraph)) + gst_bin_remove(GST_BIN(m_pipeline), m_audioGraph); + if (GST_ELEMENT_PARENT(m_videoGraph)) + gst_bin_remove(GST_BIN(m_pipeline), m_videoGraph); + + // Clear any existing errors + m_aboutToFinishEmitted = false; + m_error = NoError; + m_errorString = QString(); + + m_bufferPercent = 0; + m_prefinishMarkReachedNotEmitted = true; + m_aboutToFinishEmitted = false; + m_hasAudio = false; + m_videoStreamFound = false; + setTotalTime(-1); + m_atEndOfStream = false; + + // Clear exising meta tags + m_metaData.clear(); + + switch (source.type()) { + case MediaSource::Url: { + if (createPipefromURL(source.url())) + m_loading = true; + else + setError(tr("Could not open media source.")); + } + break; + + case MediaSource::LocalFile: { + if (createPipefromURL(QUrl::fromLocalFile(source.fileName()))) + m_loading = true; + else + setError(tr("Could not open media source.")); + } + break; + + case MediaSource::Invalid: + setError(tr("Invalid source type."), Phonon::NormalError); + break; + + case MediaSource::Empty: + break; + + case MediaSource::Stream: + if (createPipefromStream(source)) + m_loading = true; + else + setError(tr("Could not open media source.")); + break; + + case MediaSource::Disc: // CD tracks can be specified by setting the url in the following way uri=cdda:4 + { + QUrl cdurl(QLatin1String("cdda://")); + if (createPipefromURL(cdurl)) + m_loading = true; + else + setError(tr("Could not open media source.")); + } + break; + + default: + m_backend->logMessage("Source type not currently supported", Backend::Warning, this); + setError(tr("Could not open media source."), Phonon::NormalError); + break; + } + + MediaNodeEvent event(MediaNodeEvent::SourceChanged); + notify(&event); + + // We need to link this node to ensure that fake sinks are connected + // before loading, otherwise the stream will be blocked + if (m_loading) + link(); + beginLoad(); +} + +void MediaObject::beginLoad() +{ + if (gst_element_set_state(m_pipeline, GST_STATE_PAUSED) != GST_STATE_CHANGE_FAILURE) { + m_backend->logMessage("Begin source load", Backend::Info, this); + } else { + setError(tr("Could not open media source.")); + } +} + +// Called when we are ready to leave the loading state +void MediaObject::loadingComplete() +{ + if (m_videoStreamFound) { + MediaNodeEvent event(MediaNodeEvent::VideoAvailable); + notify(&event); + } + getStreamInfo(); + m_loading = false; + + setState(m_pendingState); + emit metaDataChanged(m_metaData); +} + +void MediaObject::getStreamInfo() +{ + updateSeekable(); + updateTotalTime(); + + if (m_videoStreamFound != m_hasVideo) { + m_hasVideo = m_videoStreamFound; + emit hasVideoChanged(m_hasVideo); + } + + m_availableTitles = 1; + gint64 titleCount; + GstFormat format = gst_format_get_by_nick("track"); + if (gst_element_query_duration (m_pipeline, &format, &titleCount)) { + int oldAvailableTitles = m_availableTitles; + m_availableTitles = (int)titleCount; + if (m_availableTitles != oldAvailableTitles) { + emit availableTitlesChanged(m_availableTitles); + m_backend->logMessage(QString("Available titles changed: %0").arg(m_availableTitles), Backend::Info, this); + } + } + +} + +void MediaObject::setPrefinishMark(qint32 newPrefinishMark) +{ + m_prefinishMark = newPrefinishMark; + if (currentTime() < totalTime() - m_prefinishMark) // not about to finish + m_prefinishMarkReachedNotEmitted = true; +} + +void MediaObject::pause() +{ + m_backend->logMessage("pause()", Backend::Info, this); + if (state() != Phonon::PausedState) + setState(Phonon::PausedState); + m_resumeState = false; +} + +void MediaObject::stop() +{ + if (state() != Phonon::StoppedState) { + setState(Phonon::StoppedState); + m_prefinishMarkReachedNotEmitted = true; + } + m_resumeState = false; +} + +void MediaObject::seek(qint64 time) +{ + if (!isValid()) + return; + + if (isSeekable()) { + switch (state()) { + case Phonon::PlayingState: + case Phonon::StoppedState: + case Phonon::PausedState: + case Phonon::BufferingState: + m_backend->logMessage(QString("Seek to pos %0").arg(time), Backend::Info, this); + + if (time <= 0) + m_atStartOfStream = true; + else + m_atStartOfStream = false; + + m_posAtSeek = getPipelinePos(); + m_tickTimer->stop(); + + if (gst_element_seek(m_pipeline, 1.0, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, + time * GST_MSECOND, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) + break; + case Phonon::LoadingState: + case Phonon::ErrorState: + return; + } + + quint64 current = currentTime(); + quint64 total = totalTime(); + + if (current < total - m_prefinishMark) + m_prefinishMarkReachedNotEmitted = true; + if (current < total - ABOUT_TO_FINNISH_TIME) + m_aboutToFinishEmitted = false; + m_atEndOfStream = false; + } +} + +void MediaObject::emitTick() +{ + if (m_resumeState) { + return; + } + + qint64 currentTime = getPipelinePos(); + qint64 totalTime = m_totalTime; + + if (m_tickInterval > 0 && currentTime != m_previousTickTime) { + emit tick(currentTime); + m_previousTickTime = currentTime; + } + if (m_state == Phonon::PlayingState) { + if (currentTime >= totalTime - m_prefinishMark) { + if (m_prefinishMarkReachedNotEmitted) { + m_prefinishMarkReachedNotEmitted = false; + emit prefinishMarkReached(totalTime - currentTime); + } + } + // Prepare load of next source + if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) { + if (!m_aboutToFinishEmitted) { + m_aboutToFinishEmitted = true; // track is about to finish + emit aboutToFinish(); + } + } + } +} + + +/* + * Used to iterate through the gst_tag_list and extract values + */ +void foreach_tag_function(const GstTagList *list, const gchar *tag, gpointer user_data) +{ + TagMap *newData = static_cast<TagMap *>(user_data); + QString value; + GType type = gst_tag_get_type(tag); + switch (type) { + case G_TYPE_STRING: { + char *str = 0; + gst_tag_list_get_string(list, tag, &str); + value = QString::fromUtf8(str); + g_free(str); + } + break; + + case G_TYPE_BOOLEAN: { + int bval; + gst_tag_list_get_boolean(list, tag, &bval); + value = QString::number(bval); + } + break; + + case G_TYPE_INT: { + int ival; + gst_tag_list_get_int(list, tag, &ival); + value = QString::number(ival); + } + break; + + case G_TYPE_UINT: { + unsigned int uival; + gst_tag_list_get_uint(list, tag, &uival); + value = QString::number(uival); + } + break; + + case G_TYPE_FLOAT: { + float fval; + gst_tag_list_get_float(list, tag, &fval); + value = QString::number(fval); + } + break; + + case G_TYPE_DOUBLE: { + double dval; + gst_tag_list_get_double(list, tag, &dval); + value = QString::number(dval); + } + break; + + default: + //qDebug("Unsupported tag type: %s", g_type_name(type)); + break; + } + + QString key = QString(tag).toUpper(); + QString currVal = newData->value(key); + if (!value.isEmpty() && !(newData->contains(key) && currVal == value)) + newData->insert(key, value); +} + +/** + * Triggers playback after a song has completed in the current media queue + */ +void MediaObject::beginPlay() +{ + setSource(m_nextSource); + m_nextSource = MediaSource(); + m_pendingState = Phonon::PlayingState; +} + +/** + * Handle GStreamer bus messages + */ +void MediaObject::handleBusMessage(const Message &message) +{ + + if (!isValid()) + return; + + GstMessage *gstMessage = message.rawMessage(); + Q_ASSERT(m_pipeline); + + if (m_backend->debugLevel() >= Backend::Debug) { + int type = GST_MESSAGE_TYPE(gstMessage); + gchar* name = gst_element_get_name(gstMessage->src); + QString msgString = QString("Bus: %0 (%1)").arg(gst_message_type_get_name ((GstMessageType)type)).arg(name); + g_free(name); + m_backend->logMessage(msgString, Backend::Debug, this); + } + + switch (GST_MESSAGE_TYPE (gstMessage)) { + + case GST_MESSAGE_EOS: + m_backend->logMessage("EOS recieved", Backend::Info, this); + handleEndOfStream(); + break; + + case GST_MESSAGE_TAG: { + GstTagList* tag_list = 0; + gst_message_parse_tag(gstMessage, &tag_list); + if (tag_list) { + TagMap oldMap = m_metaData; // Keep a copy of the old one for reference + // Append any new meta tags to the existing tag list + gst_tag_list_foreach (tag_list, &foreach_tag_function, &m_metaData); + m_backend->logMessage("Meta tags found", Backend::Info, this); + if (oldMap != m_metaData && !m_loading) + emit metaDataChanged(m_metaData); + gst_tag_list_free(tag_list); + } + } + break; + + case GST_MESSAGE_STATE_CHANGED : { + + if (gstMessage->src != GST_OBJECT(m_pipeline)) + return; + + GstState oldState; + GstState newState; + GstState pendingState; + gst_message_parse_state_changed (gstMessage, &oldState, &newState, &pendingState); + + if (newState == pendingState) + return; + + m_posAtSeek = -1; + + switch (newState) { + + case GST_STATE_PLAYING : + m_atStartOfStream = false; + m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this); + m_tickTimer->start(); + changeState(Phonon::PlayingState); + if (m_resumeState && m_oldState == Phonon::PlayingState) { + seek(m_oldPos); + m_resumeState = false; + } + break; + + case GST_STATE_NULL: + m_backend->logMessage("gstreamer: pipeline state set to null", Backend::Info, this); + m_tickTimer->stop(); + break; + + case GST_STATE_PAUSED : + m_backend->logMessage("gstreamer: pipeline state set to paused", Backend::Info, this); + m_tickTimer->start(); + if (state() == Phonon::LoadingState) { + // No_more_pads is not emitted from the decodebin in older versions (0.10.4) + noMorePadsAvailable(); + loadingComplete(); + } else if (m_resumeState && m_oldState == Phonon::PausedState) { + changeState(Phonon::PausedState); + m_resumeState = false; + break; + } else { + // A lot of autotests can break if we allow all paused changes through. + if (m_pendingState == Phonon::PausedState) { + changeState(Phonon::PausedState); + } + } + break; + + case GST_STATE_READY : + if (!m_loading && m_pendingState == Phonon::StoppedState) + changeState(Phonon::StoppedState); + m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this); + m_tickTimer->stop(); + break; + + case GST_STATE_VOID_PENDING : + m_backend->logMessage("gstreamer: pipeline state set to pending (void)", Backend::Debug, this); + m_tickTimer->stop(); + break; + } + break; + } + + case GST_MESSAGE_ERROR: { + gchar *debug; + GError *err; + QString logMessage; + gst_message_parse_error (gstMessage, &err, &debug); + gchar *errorMessage = gst_error_get_message (err->domain, err->code); + logMessage.sprintf("Error: %s Message:%s (%s) Code:%d", debug, err->message, errorMessage, err->code); + m_backend->logMessage(logMessage, Backend::Warning); + g_free(errorMessage); + g_free (debug); + + if (err->domain == GST_RESOURCE_ERROR) { + if (err->code == GST_RESOURCE_ERROR_NOT_FOUND) { + setError(tr("Could not locate media source."), Phonon::FatalError); + } else if (err->code == GST_RESOURCE_ERROR_OPEN_READ) { + setError(tr("Could not open media source."), Phonon::FatalError); + } else if (err->code == GST_RESOURCE_ERROR_BUSY) { + // We need to check if this comes from an audio device by looking at sink caps + GstPad* sinkPad = gst_element_get_static_pad(GST_ELEMENT(gstMessage->src), "sink"); + if (sinkPad) { + GstCaps *caps = gst_pad_get_caps (sinkPad); + GstStructure *str = gst_caps_get_structure (caps, 0); + if (g_strrstr (gst_structure_get_name (str), "audio")) + setError(tr("Could not open audio device. The device is already in use."), Phonon::NormalError); + else + setError(err->message, Phonon::FatalError); + gst_caps_unref (caps); + gst_object_unref (sinkPad); + } + } else { + setError(QString(err->message), Phonon::FatalError); + } + } else if (err->domain == GST_STREAM_ERROR) { + switch (err->code) { + case GST_STREAM_ERROR_WRONG_TYPE: + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + setError(tr("Could not decode media source."), Phonon::FatalError); + break; + default: + setError(tr("Could not open media source."), Phonon::FatalError); + break; + } + } else { + setError(QString(err->message), Phonon::FatalError); + } + g_error_free (err); + break; + } + + case GST_MESSAGE_WARNING: { + gchar *debug; + GError *err; + gst_message_parse_warning(gstMessage, &err, &debug); + QString msgString; + msgString.sprintf("Warning: %s\nMessage:%s", debug, err->message); + m_backend->logMessage(msgString, Backend::Warning); + g_free (debug); + g_error_free (err); + break; + } + + case GST_MESSAGE_ELEMENT: { + GstMessage *gstMessage = message.rawMessage(); + const GstStructure *gstStruct = gst_message_get_structure(gstMessage); //do not free this + if (g_strrstr (gst_structure_get_name (gstStruct), "prepare-xwindow-id")) { + MediaNodeEvent videoHandleEvent(MediaNodeEvent::VideoHandleRequest); + notify(&videoHandleEvent); + } + break; + } + + case GST_MESSAGE_DURATION: { + m_backend->logMessage("GST_MESSAGE_DURATION", Backend::Debug, this); + updateTotalTime(); + break; + } + + case GST_MESSAGE_BUFFERING: { + gint percent = 0; + gst_structure_get_int (gstMessage->structure, "buffer-percent", &percent); //gst_message_parse_buffering was introduced in 0.10.11 + + if (m_bufferPercent != percent) { + emit bufferStatus(percent); + m_backend->logMessage(QString("Stream buffering %0").arg(percent), Backend::Debug, this); + m_bufferPercent = percent; + } + + if (m_state != Phonon::BufferingState) + emit stateChanged(m_state, Phonon::BufferingState); + else if (percent == 100) + emit stateChanged(Phonon::BufferingState, m_state); + break; + } + //case GST_MESSAGE_INFO: + //case GST_MESSAGE_STREAM_STATUS: + //case GST_MESSAGE_CLOCK_PROVIDE: + //case GST_MESSAGE_NEW_CLOCK: + //case GST_MESSAGE_STEP_DONE: + //case GST_MESSAGE_LATENCY: only from 0.10.12 + //case GST_MESSAGE_ASYNC_DONE: only from 0.10.13 + default: + break; + } +} + +void MediaObject::handleEndOfStream() +{ + // If the stream is not seekable ignore + // otherwise chained radio broadcasts would stop + + + if (m_atEndOfStream) + return; + + if (!m_seekable) + m_atEndOfStream = true; + + if (m_autoplayTitles && + m_availableTitles > 1 && + m_currentTitle < m_availableTitles) { + _iface_setCurrentTitle(m_currentTitle + 1); + return; + } + + if (m_nextSource.type() != MediaSource::Invalid + && m_nextSource.type() != MediaSource::Empty) { // We only emit finish when the queue is actually empty + QTimer::singleShot (qMax(0, transitionTime()), this, SLOT(beginPlay())); + } else { + m_pendingState = Phonon::PausedState; + emit finished(); + if (!m_seekable) { + setState(Phonon::StoppedState); + // Note the behavior for live streams is not properly defined + // But since we cant seek to 0, we don't have much choice other than stopping + // the stream + } else { + // Only emit paused if the finished signal + // did not result in a new state + if (m_pendingState == Phonon::PausedState) + setState(m_pendingState); + } + } +} + +// Notifes the pipeline about state changes in the media object +void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate) +{ + Q_UNUSED(oldstate); + MediaNodeEvent event(MediaNodeEvent::StateChanged, &newstate); + notify(&event); +} + +#ifndef QT_NO_PHONON_MEDIACONTROLLER +//interface management +bool MediaObject::hasInterface(Interface iface) const +{ + return iface == AddonInterface::TitleInterface; +} + +QVariant MediaObject::interfaceCall(Interface iface, int command, const QList<QVariant> ¶ms) +{ + if (hasInterface(iface)) { + + switch (iface) + { + case TitleInterface: + switch (command) + { + case availableTitles: + return _iface_availableTitles(); + case title: + return _iface_currentTitle(); + case setTitle: + _iface_setCurrentTitle(params.first().toInt()); + break; + case autoplayTitles: + return m_autoplayTitles; + case setAutoplayTitles: + m_autoplayTitles = params.first().toBool(); + break; + } + break; + default: + break; + } + } + return QVariant(); +} +#endif + +int MediaObject::_iface_availableTitles() const +{ + return m_availableTitles; +} + +int MediaObject::_iface_currentTitle() const +{ + return m_currentTitle; +} + +void MediaObject::_iface_setCurrentTitle(int title) +{ + GstFormat trackFormat = gst_format_get_by_nick("track"); + m_backend->logMessage(QString("setCurrentTitle %0").arg(title), Backend::Info, this); + if ((title == m_currentTitle) || (title < 1) || (title > m_availableTitles)) + return; + + m_currentTitle = title; + + //let's seek to the beginning of the song + if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, m_currentTitle - 1)) { + updateTotalTime(); + m_atEndOfStream = false; + emit titleChanged(title); + emit totalTimeChanged(totalTime()); + } +} + +} // ns Gstreamer +} // ns Phonon + +QT_END_NAMESPACE + +#include "moc_mediaobject.cpp" diff --git a/src/3rdparty/phonon/gstreamer/mediaobject.h b/src/3rdparty/phonon/gstreamer/mediaobject.h new file mode 100644 index 0000000..64b3510 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/mediaobject.h @@ -0,0 +1,294 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_MEDIAOBJECT_H +#define Phonon_GSTREAMER_MEDIAOBJECT_H + +#include "backend.h" +#include "common.h" +#include "medianode.h" +#include <phonon/mediaobjectinterface.h> +#include <phonon/addoninterface.h> + +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QVariant> +#include <QtCore/QObject> +#include <QtCore/QDate> +#include <QtCore/QEvent> +#include <QtCore/QUrl> +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class QTimer; +typedef QMultiMap<QString, QString> TagMap; + +namespace Phonon +{ +namespace Gstreamer +{ + +class VideoWidget; +class AudioPath; +class VideoPath; +class AudioOutput; + +class MediaObject : public QObject, public MediaObjectInterface +#ifndef QT_NO_PHONON_MEDIACONTROLLER + , public AddonInterface +#endif + , public MediaNode +{ + friend class Stream; + Q_OBJECT + Q_INTERFACES(Phonon::MediaObjectInterface +#ifndef QT_NO_PHONON_MEDIACONTROLLER + Phonon::AddonInterface +#endif + Phonon::Gstreamer::MediaNode + ) + +public: + + MediaObject(Backend *backend, QObject *parent); + ~MediaObject(); + Phonon::State state() const; + + bool hasVideo() const; + bool isSeekable() const; + + qint64 currentTime() const; + qint32 tickInterval() const; + + void setTickInterval(qint32 newTickInterval); + + void play(); + void pause(); + void stop(); + void seek(qint64 time); + + QString errorString() const; + Phonon::ErrorType errorType() const; + + QUrl url() const; + qint64 totalTime() const; + + qint32 prefinishMark() const; + void setPrefinishMark(qint32 newPrefinishMark); + + qint32 transitionTime() const; + void setTransitionTime(qint32); + qint64 remainingTime() const; + + void setSource(const MediaSource &source); + void setNextSource(const MediaSource &source); + MediaSource source() const; + + // No additional interfaces currently supported +#ifndef QT_NO_PHONON_MEDIACONTROLLER + bool hasInterface(Interface) const; + QVariant interfaceCall(Interface, int, const QList<QVariant> &); +#endif + bool isLoading() + { + return m_loading; + } + + bool audioAvailable() + { + return m_hasAudio; + } + + bool videoAvailable() + { + return m_hasVideo; + } + + GstElement *audioGraph() + { + return m_audioGraph; + } + + GstElement *videoGraph() + { + return m_videoGraph; + } + + GstElement *pipeline() + { + return m_pipeline; + }; + + gulong capsHandler() + { + return m_capsHandler; + }; + + void connectVideo(GstPad *videoPad); + void connectAudio(GstPad *audioPad); + void handleBusMessage(const Message &msg); + void handleEndOfStream(); + void addMissingCodecName(const QString &codec) { m_missingCodecs.append(codec); } + void invalidateGraph() { + m_resetNeeded = true; + if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) { + changeState(Phonon::StoppedState); + } + } + static void cb_newpad (GstElement *decodebin, GstPad *pad, gboolean last, gpointer data); + static void cb_pad_added (GstElement *decodebin, GstPad *pad, gpointer data); + static void cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps *caps, gpointer data); + static void cb_no_more_pads (GstElement * decodebin, gpointer data); + void saveState(); + void resumeState(); + +public Q_SLOTS: + void setState(State); + +Q_SIGNALS: + void currentSourceChanged(const MediaSource &newSource); + void stateChanged(Phonon::State newstate, Phonon::State oldstate); + void tick(qint64 time); + void metaDataChanged(QMultiMap<QString, QString>); + void seekableChanged(bool); + void hasVideoChanged(bool); + + void finished(); + void prefinishMarkReached(qint32); + void aboutToFinish(); + void totalTimeChanged(qint64 length); + void bufferStatus(int percentFilled); + + QMultiMap<QString, QString> metaData(); + void setMetaData(QMultiMap<QString, QString> newData); + + // AddonInterface: + void titleChanged(int); + void availableTitlesChanged(int); + + // Not implemented + void chapterChanged(int); + void availableChaptersChanged(int); + void angleChanged(int); + void availableAnglesChanged(int); + + void availableSubtitlesChanged(); + void availableAudioChannelsChanged(); + +protected: + void beginLoad(); + void loadingComplete(); + void newPadAvailable (GstPad *pad); + void changeState(State); + void setError(const QString &errorString, Phonon::ErrorType error = NormalError); + /* + * @param encodedUrl percent-encoded QString for source compat reasons. Should change to QUrl + */ + bool createPipefromURL(const QUrl &url); + bool createPipefromStream(const MediaSource &); + +private Q_SLOTS: + void noMorePadsAvailable(); + void getStreamInfo(); + void emitTick(); + void beginPlay(); + void setVideoCaps(GstCaps *caps); + void notifyStateChange(Phonon::State newstate, Phonon::State oldstate); +protected: + GstElement *audioElement() + { + Q_ASSERT(m_audioPipe); + return m_audioPipe; + } + + GstElement *videoElement() + { + Q_ASSERT(m_videoPipe); + return m_videoPipe; + } + +private: + + // GStreamer specific : + void createPipeline(); + bool addToPipeline(GstElement *elem); + void setTotalTime(qint64 newTime); + void getStreamsInfo(); + bool updateTotalTime(); + void updateSeekable(); + qint64 getPipelinePos() const; + + int _iface_availableTitles() const; + int _iface_currentTitle() const; + void _iface_setCurrentTitle(int title); + + bool m_resumeState; + State m_oldState; + quint64 m_oldPos; + + State m_state; + State m_pendingState; + QTimer *m_tickTimer; + qint32 m_tickInterval; + + MediaSource m_source; + MediaSource m_nextSource; + qint32 m_prefinishMark; + qint32 m_transitionTime; + + qint64 m_posAtSeek; + + bool m_prefinishMarkReachedNotEmitted; + bool m_aboutToFinishEmitted; + bool m_loading; + gulong m_capsHandler; + + GstElement *m_datasource; + GstElement *m_decodebin; + + GstElement *m_audioPipe; + GstElement *m_videoPipe; + + qint64 m_totalTime; + int m_bufferPercent; + bool m_hasVideo; + bool m_videoStreamFound; + bool m_hasAudio; + bool m_seekable; + bool m_atEndOfStream; + bool m_atStartOfStream; + Phonon::ErrorType m_error; + QString m_errorString; + + GstElement *m_pipeline; + GstElement *m_audioGraph; + GstElement *m_videoGraph; + int m_previousTickTime; + bool m_resetNeeded; + QStringList m_missingCodecs; + QMultiMap<QString, QString> m_metaData; + bool m_autoplayTitles; + int m_availableTitles; + int m_currentTitle; +}; +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_MEDIAOBJECT_H diff --git a/src/3rdparty/phonon/gstreamer/message.cpp b/src/3rdparty/phonon/gstreamer/message.cpp new file mode 100644 index 0000000..154e0bb --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/message.cpp @@ -0,0 +1,75 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gst/gst.h> + +#include "message.h" + + +QT_BEGIN_NAMESPACE + +static int wuchi = qRegisterMetaType<Phonon::Gstreamer::Message>(); + +namespace Phonon +{ +namespace Gstreamer +{ + +/*! + \class gstreamer::Message + \internal +*/ +Message::Message(): + m_message(0), + m_source(0) +{} + +Message::Message(GstMessage* message, MediaObject *source): + m_message(message), + m_source(source) +{ + Q_ASSERT(m_message); + gst_message_ref(m_message); +} + +Message::Message(const Message &other) +{ + m_message = other.m_message; + gst_message_ref(m_message); + m_source = other.m_source; +} + +Message::~Message() +{ + gst_message_unref(m_message); +} + +GstMessage* Message::rawMessage() const +{ + return m_message; +} + +MediaObject *Message::source() const +{ + return m_source; +} + +} // ns gstreamer +} // ns phonon + +QT_END_NAMESPACE + diff --git a/src/3rdparty/phonon/gstreamer/message.h b/src/3rdparty/phonon/gstreamer/message.h new file mode 100644 index 0000000..cadb948 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/message.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_MESSAGE_H +#define Phonon_GSTREAMER_MESSAGE_H + +#include "common.h" + +#include <QtCore/QMetaType> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +class MediaObject; +class Message +{ +public: + Message(); + Message(GstMessage* message, MediaObject *source); + ~Message(); + + GstMessage* rawMessage() const; + MediaObject *source() const; + Message(const Message &other); + +private: + GstMessage* m_message; + MediaObject *m_source; +}; + +} // ns gstreamer +} // ns phonon + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(Phonon::Gstreamer::Message) + +#endif // Phonon_GSTREAMER_MESSAGE_H diff --git a/src/3rdparty/phonon/gstreamer/phononsrc.cpp b/src/3rdparty/phonon/gstreamer/phononsrc.cpp new file mode 100644 index 0000000..f893fb5 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/phononsrc.cpp @@ -0,0 +1,257 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <gst/gst.h> +#include <gst/base/gstbasesrc.h> +#include "phononsrc.h" +#include "streamreader.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +static GstStaticPadTemplate srctemplate = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC (phonon_src_debug); + +// PhononSrc args +enum +{ + ARG_0, + ARG_PHONONSRC +}; + +static void phonon_src_finalize (GObject * object); + +static void phonon_src_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void phonon_src_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean phonon_src_start (GstBaseSrc * basesrc); +static gboolean phonon_src_stop (GstBaseSrc * basesrc); + +static gboolean phonon_src_is_seekable (GstBaseSrc * src); +static gboolean phonon_src_get_size (GstBaseSrc * src, guint64 * size); +static GstFlowReturn phonon_src_create (GstBaseSrc * src, guint64 offset, + guint length, GstBuffer ** buffer); + +static void _do_init (GType filesrc_type) +{ + Q_UNUSED(filesrc_type); + GST_DEBUG_CATEGORY_INIT (phonon_src_debug, "phononsrc", 0, "QIODevice element"); +} + +GST_BOILERPLATE_FULL (PhononSrc, phonon_src, GstBaseSrc, GST_TYPE_BASE_SRC, _do_init) + +// Register element details +static void phonon_src_base_init (gpointer g_class) { + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (g_class); + static gchar longname[] = "Phonon Stream Source", + klass[] = "Source/File", + description[] = "Read from a Phonon StreamInterface", + author[] = "Nokia Corporation and/or its subsidiary(-ies) <qt-info@nokia.com>"; + GstElementDetails details = GST_ELEMENT_DETAILS (longname, + klass, + description, + author); + gst_element_class_set_details (gstelement_class, &details); + gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&srctemplate)); +} + +static void phonon_src_class_init (PhononSrcClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstelement_class = GST_ELEMENT_CLASS (klass); + gstbasesrc_class = GST_BASE_SRC_CLASS (klass); + + gobject_class->set_property = phonon_src_set_property; + gobject_class->get_property = phonon_src_get_property; + + g_object_class_install_property (gobject_class, ARG_PHONONSRC, + g_param_spec_pointer ("iodevice", "A Phonon StreamReader", + "A Phonon::GStreamer::StreamReader to read from", GParamFlags(G_PARAM_READWRITE))); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (phonon_src_finalize); + + gstbasesrc_class->start = GST_DEBUG_FUNCPTR (phonon_src_start); + gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (phonon_src_stop); + gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (phonon_src_is_seekable); + gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (phonon_src_get_size); + gstbasesrc_class->create = GST_DEBUG_FUNCPTR (phonon_src_create); +} + +static void phonon_src_init (PhononSrc * src, PhononSrcClass * g_class) +{ + Q_UNUSED(g_class); + src->device = 0; +} + +static void phonon_src_finalize (GObject * object) +{ + PhononSrc *src; + src = GST_PHONON_SRC (object); + delete src->device; + src->device = 0; + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gboolean phonon_src_set_device(PhononSrc * src, StreamReader* device) +{ + GstState state; + // The element must be stopped in order to do this + GST_OBJECT_LOCK (src); + state = GST_STATE (src); + + if (state != GST_STATE_READY && state != GST_STATE_NULL) + goto wrong_state; + + GST_OBJECT_UNLOCK (src); + + src->device = device; + g_object_notify (G_OBJECT (src), "iodevice"); + return TRUE; + + // Error +wrong_state: + { + //GST_DEBUG_OBJECT (src, "setting location in wrong state"); + GST_OBJECT_UNLOCK (src); + return FALSE; + } +} + +static void phonon_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) +{ + PhononSrc *src; + g_return_if_fail (GST_IS_PHONON_SRC (object)); + src = GST_PHONON_SRC (object); + + switch (prop_id) { + case ARG_PHONONSRC: + { + StreamReader *dev = (StreamReader*)(g_value_get_pointer(value)); + if (dev) + phonon_src_set_device(src, dev); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void phonon_src_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + PhononSrc *src; + g_return_if_fail (GST_IS_PHONON_SRC (object)); + src = GST_PHONON_SRC (object); + + switch (prop_id) { + case ARG_PHONONSRC: + g_value_set_pointer(value, src->device); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstFlowReturn phonon_src_create_read (PhononSrc * src, guint64 offset, guint length, GstBuffer ** buffer) +{ + Q_ASSERT(src->device); + if (!src->device) + return GST_FLOW_ERROR; + + GstBuffer *buf = gst_buffer_new_and_alloc (length); + GST_BUFFER_SIZE (buf) = length; + GST_BUFFER_OFFSET (buf) = offset; + GST_BUFFER_OFFSET_END (buf) = offset + length; + + bool success = src->device->read(offset, length, (char*)GST_BUFFER_DATA (buf)); + //GST_LOG_OBJECT (src, "Reading %d bytes", length); + + if (success) { + *buffer = buf; + return GST_FLOW_OK; + } + + gst_mini_object_unref(GST_MINI_OBJECT(buf)); + return GST_FLOW_ERROR; +} + +static GstFlowReturn phonon_src_create (GstBaseSrc * basesrc, guint64 offset, guint length, GstBuffer ** buffer) +{ + PhononSrc *src; + GstFlowReturn ret; + src = GST_PHONON_SRC (basesrc); + ret = phonon_src_create_read (src, offset, length, buffer); + return ret; +} + +static gboolean phonon_src_is_seekable (GstBaseSrc * basesrc) +{ + PhononSrc *src = GST_PHONON_SRC (basesrc); + if (src->device) + return src->device->streamSeekable(); + return false; +} + +static gboolean phonon_src_get_size (GstBaseSrc * basesrc, guint64 * size) +{ + PhononSrc *src; + src = GST_PHONON_SRC (basesrc); + if (src->device && src->device->streamSeekable()) { + *size = src->device->streamSize(); + return TRUE; + } + *size = 0; + return FALSE; +} + +// Necessary to go to READY state +static gboolean phonon_src_start (GstBaseSrc * basesrc) +{ + Q_UNUSED(basesrc); + // Opening the device is handled by the frontend + // We can only assume it is already open + return TRUE; +} + +static gboolean phonon_src_stop (GstBaseSrc * basesrc) +{ + Q_UNUSED(basesrc); + // Closing the device is handled by the frontend + return TRUE; +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/phononsrc.h b/src/3rdparty/phonon/gstreamer/phononsrc.h new file mode 100644 index 0000000..a2cd8b3 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/phononsrc.h @@ -0,0 +1,69 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __PHONON_SRC_H__ +#define __PHONON_SRC_H__ + +#include <sys/types.h> +#include <gst/gst.h> +#include <gst/base/gstbasesrc.h> +#include "streamreader.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +G_BEGIN_DECLS + +#define GST_TYPE_PHONON_SRC \ + (phonon_src_get_type()) +#define GST_PHONON_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PHONON_SRC,PhononSrc)) +#define GST_PHONON_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PHONON_SRC,PhononSrcClass)) +#define GST_IS_PHONON_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PHONON_SRC)) +#define GST_IS_PHONON_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PHONON_SRC)) + +typedef struct _PhononSrc PhononSrc; +typedef struct _PhononSrcClass PhononSrcClass; + +// PhononSrc: +struct _PhononSrc { + GstBaseSrc element; + StreamReader *device; +}; + +struct _PhononSrcClass { + GstBaseSrcClass parent_class; +}; + +GType phonon_src_get_type (void); + +G_END_DECLS + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + + +#endif // __PHONON_SRC_H__ diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp new file mode 100644 index 0000000..89d5a9d --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp @@ -0,0 +1,221 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <QApplication> +#include "videowidget.h" +#include "qwidgetvideosink.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +static GstVideoSinkClass* parentClass; + +/*! + \class gstreamer::QWidgetVideoSink + \internal +*/ + +template <VideoFormat FMT> +GstCaps* QWidgetVideoSink<FMT>::get_caps(GstBaseSink* sink) +{ + Q_UNUSED(sink); + return 0; +} + +template <> +const char* QWidgetVideoSinkClass<VideoFormat_YUV>::get_name() +{ + return "QWidgetVideoSinkYUV"; +} + +template <> +const char* QWidgetVideoSinkClass<VideoFormat_RGB>::get_name() +{ + return "QWidgetVideoSinkRGB"; +} + +template <VideoFormat FMT> +gboolean QWidgetVideoSink<FMT>::set_caps(GstBaseSink* sink, GstCaps* caps) +{ + GstStructure* data; + QWidgetVideoSink<FMT> *self = G_TYPE_CHECK_INSTANCE_CAST(sink, QWidgetVideoSinkClass<FMT>::get_type(), QWidgetVideoSink<FMT>); + + data = gst_caps_get_structure(caps, 0); + + gst_structure_get_int(data, "width", &self->width); + gst_structure_get_int(data, "height", &self->height); + gst_structure_get_int(data, "bpp", &self->bpp); + gst_structure_get_int(data, "depth", &self->depth); + return TRUE; +} + +template <VideoFormat FMT> +GstStateChangeReturn QWidgetVideoSink<FMT>::change_state(GstElement* element, GstStateChange transition) +{ + return GST_ELEMENT_CLASS(parentClass)->change_state(element, transition); +} + +template <VideoFormat FMT> +GstFlowReturn QWidgetVideoSink<FMT>::render(GstBaseSink* sink, GstBuffer* buf) +{ + GstFlowReturn rc = GST_FLOW_OK; + + if (buf != 0) + { + QWidgetVideoSink<FMT> *self = G_TYPE_CHECK_INSTANCE_CAST(sink, QWidgetVideoSinkClass<FMT>::get_type(), QWidgetVideoSink<FMT>); + QByteArray frame; + frame.resize(buf->size); + memcpy(frame.data(), buf->data, buf->size); + NewFrameEvent *frameEvent = new NewFrameEvent(frame, self->width, self->height); + QApplication::postEvent(self->renderWidget, frameEvent); + } + else + rc = GST_FLOW_ERROR; + return rc; +} + +static GstStaticPadTemplate template_factory_yuv = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-raw-yuv, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]," + "bpp = (int) 32")); + +static GstStaticPadTemplate template_factory_rgb = + GST_STATIC_PAD_TEMPLATE("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-raw-rgb, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]," + "bpp = (int) 32")); + +template <VideoFormat FMT> +struct template_factory; + + +template <> +struct template_factory<VideoFormat_YUV> +{ + static GstStaticPadTemplate *getFactory() + { + return &template_factory_yuv; + } +}; + +template <> +struct template_factory<VideoFormat_RGB> +{ + static GstStaticPadTemplate *getFactory() + { + return &template_factory_rgb; + } +}; + +template <VideoFormat FMT> +void QWidgetVideoSink<FMT>::base_init(gpointer g_class) +{ + gst_element_class_add_pad_template(GST_ELEMENT_CLASS(g_class), + gst_static_pad_template_get(template_factory<FMT>::getFactory())); +} + +template <VideoFormat FMT> +void QWidgetVideoSink<FMT>::instance_init(GTypeInstance *instance, gpointer g_class) +{ + Q_UNUSED(g_class); + + QWidgetVideoSink<FMT>* self = reinterpret_cast<QWidgetVideoSink<FMT>*>(instance); + + self->renderWidget = 0; + self->width = 0; + self->height = 0; + self->bpp = 0; + self->depth = 0; +} + +// QWidgetVideoSinkClass +template <VideoFormat FMT> +void QWidgetVideoSinkClass<FMT>::class_init(gpointer g_class, gpointer class_data) +{ + Q_UNUSED(class_data); + GstBaseSinkClass* gstBaseSinkClass = (GstBaseSinkClass*)g_class; + GstElementClass* gstElementClass = (GstElementClass*)g_class; + + parentClass = reinterpret_cast<GstVideoSinkClass*>(g_type_class_peek_parent(g_class)); + + // base + gstBaseSinkClass->set_caps = QWidgetVideoSink<FMT>::set_caps; + gstBaseSinkClass->preroll = QWidgetVideoSink<FMT>::render; + gstBaseSinkClass->render = QWidgetVideoSink<FMT>::render; + + // element + gstElementClass->change_state = QWidgetVideoSink<FMT>::change_state; +} + +template <VideoFormat FMT> +GType QWidgetVideoSinkClass<FMT>::get_type() +{ + static GType type = 0; + + if (type == 0) + { + static const GTypeInfo info = + { + sizeof(QWidgetVideoSinkClass<FMT>), // class_size + QWidgetVideoSink<FMT>::base_init, // base init + NULL, // base_finalize + + QWidgetVideoSinkClass<FMT>::class_init, // class_init + NULL, // class_finalize + NULL, // class_data + + sizeof(QWidgetVideoSink<FMT>), // instance_size + 0, // n_preallocs + QWidgetVideoSink<FMT>::instance_init, // instance_init + 0 // value_table + }; + + type = g_type_register_static(GST_TYPE_VIDEO_SINK, + QWidgetVideoSinkClass<FMT>::get_name(), + &info, + GTypeFlags(0)); + } + return type; +} + +GType get_type_YUV() +{ + return QWidgetVideoSinkClass<VideoFormat_YUV>::get_type(); +} + +GType get_type_RGB() +{ + return QWidgetVideoSinkClass<VideoFormat_RGB>::get_type(); +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h new file mode 100644 index 0000000..73a494a --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h @@ -0,0 +1,97 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_VIDEOSINK_H +#define Phonon_GSTREAMER_VIDEOSINK_H + +#include "common.h" + +#include <QtCore/QByteArray> +#include <QtCore/QEvent> + +#include <gst/video/gstvideosink.h> + +QT_BEGIN_NAMESPACE + +class NewFrameEvent : public QEvent +{ +public: + NewFrameEvent(const QByteArray &newFrame, int w, int h) : + QEvent(QEvent::User), + frame(newFrame), + width(w), + height(h) + { + } + + QByteArray frame; + int width; + int height; +}; + +namespace Phonon +{ +namespace Gstreamer +{ + +enum VideoFormat { + VideoFormat_YUV, + VideoFormat_RGB +}; + +class QWidgetVideoSinkBase +{ +public: + GstVideoSink videoSink; + + QWidget * renderWidget; + gint width; + gint height; + gint bpp; + gint depth; +}; + +template <VideoFormat FMT> +class QWidgetVideoSink : public QWidgetVideoSinkBase +{ +public: + static GstCaps* get_caps(GstBaseSink* sink); + static gboolean set_caps(GstBaseSink* sink, GstCaps* caps); + static GstStateChangeReturn change_state(GstElement* element, GstStateChange transition); + static GstFlowReturn render(GstBaseSink* sink, GstBuffer* buf); + static void base_init(gpointer g_class); + static void instance_init(GTypeInstance *instance, gpointer g_class); +}; + +template <VideoFormat FMT> +struct QWidgetVideoSinkClass +{ + GstVideoSinkClass parent_class; + static void class_init(gpointer g_class, gpointer class_data); + static GType get_type(); + static const char* get_name(); +}; + +GType get_type_YUV(); +GType get_type_RGB(); + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_VIDEOSINK_H diff --git a/src/3rdparty/phonon/gstreamer/streamreader.cpp b/src/3rdparty/phonon/gstreamer/streamreader.cpp new file mode 100644 index 0000000..04fa6cc --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/streamreader.cpp @@ -0,0 +1,53 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "streamreader.h" +#include <QtCore/QMutex> +#include <phonon/streaminterface.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +bool StreamReader::read(quint64 pos, int length, char * buffer) +{ + if (currentPos() - currentBufferSize() != pos) { + if (!streamSeekable()) + return false; + setCurrentPos(pos); + } + + while (currentBufferSize() < length) { + int oldSize = currentBufferSize(); + needData(); + if (oldSize == currentBufferSize()) + return false; // We didn't get any data + } + + qMemCopy(buffer, m_buffer.data(), length); + //truncate the buffer + m_buffer = m_buffer.mid(pos); + return true; +} + +} +} + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/streamreader.h b/src/3rdparty/phonon/gstreamer/streamreader.h new file mode 100644 index 0000000..c2e61c8 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/streamreader.h @@ -0,0 +1,96 @@ +/* This file is part of the KDE project. + +Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + +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 or 3 of the License. + +This library 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef PHONON_IODEVICEREADER_H +#define PHONON_IODEVICEREADER_H + +#include <phonon/mediasource.h> +#include <phonon/streaminterface.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ + class MediaSource; + namespace Gstreamer + { + class StreamReader : public Phonon::StreamInterface + { + public: + + StreamReader(const Phonon::MediaSource &source) + : m_pos(0) + , m_size(0) + , m_seekable(false) + { + connectToSource(source); + } + + int currentBufferSize() const + { + return m_buffer.size(); + } + + void writeData(const QByteArray &data) { + m_pos += data.size(); + m_buffer += data; + } + + void setCurrentPos(qint64 pos) + { + m_pos = pos; + seekStream(pos); + m_buffer.clear(); + } + + quint64 currentPos() const + { + return m_pos; + } + + bool read(quint64 offset, int length, char * buffer); + + void endOfData() {} + + void setStreamSize(qint64 newSize) { + m_size = newSize; + } + + qint64 streamSize() const { + return m_size; + } + + void setStreamSeekable(bool s) { + m_seekable = s; + } + + bool streamSeekable() const { + return m_seekable; + } + +private: + QByteArray m_buffer; + quint64 m_pos; + quint64 m_size; + bool m_seekable; + }; + } +} + +QT_END_NAMESPACE + +#endif diff --git a/src/3rdparty/phonon/gstreamer/videowidget.cpp b/src/3rdparty/phonon/gstreamer/videowidget.cpp new file mode 100644 index 0000000..efc750a --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/videowidget.cpp @@ -0,0 +1,387 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "videowidget.h" +#include <QtCore/QEvent> +#include <QtGui/QResizeEvent> +#include <QtGui/QPalette> +#include <QtGui/QImage> +#include <QtGui/QPainter> +#include <QtGui/QBoxLayout> +#include <QApplication> +#include <gst/gst.h> +#include <gst/interfaces/propertyprobe.h> +#include "mediaobject.h" +#include "message.h" +#include "common.h" + +#include "glrenderer.h" +#include "widgetrenderer.h" +#include "x11renderer.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +VideoWidget::VideoWidget(Backend *backend, QWidget *parent) : + QWidget(parent), + MediaNode(backend, VideoSink), + m_videoBin(0), + m_renderer(0), + m_aspectRatio(Phonon::VideoWidget::AspectRatioAuto), + m_brightness(0.0), + m_hue(0.0), + m_contrast(0.0), + m_saturation(0.0), + m_scaleMode(Phonon::VideoWidget::FitInView), + m_videoBalance(0), + m_colorspace(0), + m_videoplug(0) +{ + setupVideoBin(); +} + +VideoWidget::~VideoWidget() +{ + if (m_videoBin) { + gst_element_set_state (m_videoBin, GST_STATE_NULL); + gst_object_unref (m_videoBin); + } + + if (m_renderer) + delete m_renderer; +} + + +void VideoWidget::setupVideoBin() +{ + + m_renderer = m_backend->deviceManager()->createVideoRenderer(this); + GstElement *videoSink = m_renderer->videoSink(); + + m_videoBin = gst_bin_new (NULL); + Q_ASSERT(m_videoBin); + gst_object_ref (GST_OBJECT (m_videoBin)); //Take ownership + gst_object_sink (GST_OBJECT (m_videoBin)); + + //The videoplug element is the final element before the pluggable videosink + m_videoplug = gst_element_factory_make ("identity", NULL); + + //Colorspace ensures that the output of the stream matches the input format accepted by our video sink + m_colorspace = gst_element_factory_make ("ffmpegcolorspace", NULL); + + //Video scale is used to prepare the correct aspect ratio and scale. + GstElement *videoScale = gst_element_factory_make ("videoscale", NULL); + + //We need a queue to support the tee from parent node + GstElement *queue = gst_element_factory_make ("queue", NULL); + + if (queue && m_videoBin && videoScale && m_colorspace && videoSink && m_videoplug) { + //Ensure that the bare essentials are prepared + gst_bin_add_many (GST_BIN (m_videoBin), queue, m_colorspace, m_videoplug, videoScale, videoSink, (const char*)NULL); + bool success = false; + //Video balance controls color/sat/hue in the YUV colorspace + m_videoBalance = gst_element_factory_make ("videobalance", NULL); + if (m_videoBalance) { + // For video balance to work we have to first ensure that the video is in YUV colorspace, + // then hand it off to the videobalance filter before finally converting it back to RGB. + // Hence we nede a videoFilter to convert the colorspace before and after videobalance + GstElement *m_colorspace2 = gst_element_factory_make ("ffmpegcolorspace", NULL); + gst_bin_add_many(GST_BIN(m_videoBin), m_videoBalance, m_colorspace2, (const char*)NULL); + success = gst_element_link_many(queue, m_colorspace, m_videoBalance, m_colorspace2, videoScale, m_videoplug, videoSink, (const char*)NULL); + } else { + //If video balance is not available, just connect to sink directly + success = gst_element_link_many(queue, m_colorspace, videoScale, m_videoplug, videoSink, (const char*)NULL); + } + + if (success) { + GstPad *videopad = gst_element_get_pad (queue, "sink"); + gst_element_add_pad (m_videoBin, gst_ghost_pad_new ("sink", videopad)); + gst_object_unref (videopad); + QWidget *parentWidget = qobject_cast<QWidget*>(parent()); + if (parentWidget) + parentWidget->winId(); // Due to some existing issues with alien in 4.4, + // we must currently force the creation of a parent widget. + m_isValid = true; //initialization ok, accept input + } + } +} + +void VideoWidget::paintEvent(QPaintEvent *event) +{ + Q_ASSERT(m_renderer); + m_renderer->handlePaint(event); +} + +void VideoWidget::setVisible(bool val) { + Q_ASSERT(m_renderer); + + // Disable overlays for graphics view + if (root() && window() && window()->testAttribute(Qt::WA_DontShowOnScreen) && !m_renderer->paintsOnWidget()) { + m_backend->logMessage(QString("Widget rendering forced"), Backend::Info, this); + GstElement *videoSink = m_renderer->videoSink(); + Q_ASSERT(videoSink); + + gst_element_set_state (videoSink, GST_STATE_NULL); + gst_bin_remove(GST_BIN(m_videoBin), videoSink); + delete m_renderer; + m_renderer = 0; + + // Use widgetRenderer as a fallback + m_renderer = new WidgetRenderer(this); + videoSink = m_renderer->videoSink(); + gst_bin_add(GST_BIN(m_videoBin), videoSink); + gst_element_link(m_videoplug, videoSink); + gst_element_set_state (videoSink, GST_STATE_PAUSED); + + // Request return to current state + root()->invalidateGraph(); + root()->setState(root()->state()); + } + QWidget::setVisible(val); +} + +bool VideoWidget::event(QEvent *event) +{ + if (m_renderer && m_renderer->eventFilter(event)) + return true; + return QWidget::event(event); +} + +Phonon::VideoWidget::AspectRatio VideoWidget::aspectRatio() const +{ + return m_aspectRatio; +} + +QSize VideoWidget::sizeHint() const +{ + if (!m_movieSize.isEmpty()) + return m_movieSize; + else + return QSize(640, 480); +} + +void VideoWidget::setAspectRatio(Phonon::VideoWidget::AspectRatio aspectRatio) +{ + m_aspectRatio = aspectRatio; + if (m_renderer) + m_renderer->aspectRatioChanged(aspectRatio); +} + +Phonon::VideoWidget::ScaleMode VideoWidget::scaleMode() const +{ + return m_scaleMode; +} + +QRect VideoWidget::scaleToAspect(QRect srcRect, int w, int h) const +{ + float width = srcRect.width(); + float height = srcRect.width() * (float(h) / float(w)); + if (height > srcRect.height()) { + height = srcRect.height(); + width = srcRect.height() * (float(w) / float(h)); + } + return QRect(0, 0, (int)width, (int)height); +} + +/*** + * Calculates the actual rectangle the movie will be presented with + **/ +QRect VideoWidget::calculateDrawFrameRect() const +{ + QRect widgetRect = rect(); + QRect drawFrameRect; + // Set m_drawFrameRect to be the size of the smallest possible + // rect conforming to the aspect and containing the whole frame: + switch (aspectRatio()) { + + case Phonon::VideoWidget::AspectRatioWidget: + drawFrameRect = widgetRect; + // No more calculations needed. + return drawFrameRect; + + case Phonon::VideoWidget::AspectRatio4_3: + drawFrameRect = scaleToAspect(widgetRect, 4, 3); + break; + + case Phonon::VideoWidget::AspectRatio16_9: + drawFrameRect = scaleToAspect(widgetRect, 16, 9); + break; + + case Phonon::VideoWidget::AspectRatioAuto: + default: + drawFrameRect = QRect(0, 0, movieSize().width(), movieSize().height()); + break; + } + + // Scale m_drawFrameRect to fill the widget + // without breaking aspect: + float widgetWidth = widgetRect.width(); + float widgetHeight = widgetRect.height(); + float frameWidth = widgetWidth; + float frameHeight = drawFrameRect.height() * float(widgetWidth) / float(drawFrameRect.width()); + + switch (scaleMode()) { + case Phonon::VideoWidget::ScaleAndCrop: + if (frameHeight < widgetHeight) { + frameWidth *= float(widgetHeight) / float(frameHeight); + frameHeight = widgetHeight; + } + break; + case Phonon::VideoWidget::FitInView: + default: + if (frameHeight > widgetHeight) { + frameWidth *= float(widgetHeight) / float(frameHeight); + frameHeight = widgetHeight; + } + break; + } + drawFrameRect.setSize(QSize(int(frameWidth), int(frameHeight))); + drawFrameRect.moveTo(int((widgetWidth - frameWidth) / 2.0f), + int((widgetHeight - frameHeight) / 2.0f)); + return drawFrameRect; +} + +void VideoWidget::setScaleMode(Phonon::VideoWidget::ScaleMode scaleMode) +{ + m_scaleMode = scaleMode; + if (m_renderer) + m_renderer->scaleModeChanged(scaleMode); +} + +qreal VideoWidget::brightness() const +{ + return m_brightness; +} + +qreal clampedValue(qreal val) +{ + if (val > 1.0 ) + return 1.0; + else if (val < -1.0) + return -1.0; + else return val; +} + +void VideoWidget::setBrightness(qreal newValue) +{ + newValue = clampedValue(newValue); + + if (newValue == m_brightness) + return; + + m_brightness = newValue; + + if (m_videoBalance) + g_object_set(G_OBJECT(m_videoBalance), "brightness", newValue, (const char*)NULL); //gstreamer range is [-1, 1] + +} + +qreal VideoWidget::contrast() const +{ + return m_contrast; +} + +void VideoWidget::setContrast(qreal newValue) +{ + newValue = clampedValue(newValue); + + if (newValue == m_contrast) + return; + + m_contrast = newValue; + + if (m_videoBalance) + g_object_set(G_OBJECT(m_videoBalance), "contrast", (newValue + 1.0), (const char*)NULL); //gstreamer range is [0-2] +} + +qreal VideoWidget::hue() const +{ + return m_hue; +} + +void VideoWidget::setHue(qreal newValue) +{ + if (newValue == m_hue) + return; + + newValue = clampedValue(newValue); + + m_hue = newValue; + + if (m_videoBalance) + g_object_set(G_OBJECT(m_videoBalance), "hue", newValue, (const char*)NULL); //gstreamer range is [-1, 1] +} + +qreal VideoWidget::saturation() const +{ + return m_saturation; +} + +void VideoWidget::setSaturation(qreal newValue) +{ + newValue = clampedValue(newValue); + + if (newValue == m_saturation) + return; + + m_saturation = newValue; + + if (m_videoBalance) + g_object_set(G_OBJECT(m_videoBalance), "saturation", newValue + 1.0, (const char*)NULL); //gstreamer range is [0, 2] +} + + +void VideoWidget::setMovieSize(const QSize &size) +{ + m_backend->logMessage(QString("New video size %0 x %1").arg(size.width()).arg(size.height()), Backend::Info); + if (size == m_movieSize) + return; + m_movieSize = size; + widget()->updateGeometry(); + widget()->update(); + + if (m_renderer) + m_renderer->movieSizeChanged(m_movieSize); +} + +void VideoWidget::mediaNodeEvent(const MediaNodeEvent *event) +{ + switch (event->type()) { + case MediaNodeEvent::VideoSizeChanged: { + const QSize *size = static_cast<const QSize*>(event->data()); + setMovieSize(*size); + } + break; + default: + break; + } + + // Forward events to renderer + if (m_renderer) + m_renderer->handleMediaNodeEvent(event); +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#include "moc_videowidget.cpp" diff --git a/src/3rdparty/phonon/gstreamer/videowidget.h b/src/3rdparty/phonon/gstreamer/videowidget.h new file mode 100644 index 0000000..a0ebe5f --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/videowidget.h @@ -0,0 +1,106 @@ +/* This file is part of the KDE project. + + Copyright (C) 2 //Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).007 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_VIDEOWIDGET_H +#define Phonon_GSTREAMER_VIDEOWIDGET_H + +#include <phonon/videowidget.h> +#include <phonon/videowidgetinterface.h> + +#include "backend.h" +#include "common.h" +#include "medianode.h" +#include "abstractrenderer.h" + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class QString; + +namespace Phonon +{ +namespace Gstreamer +{ + +class VideoWidget : public QWidget, public Phonon::VideoWidgetInterface, public MediaNode +{ + Q_OBJECT + Q_INTERFACES(Phonon::VideoWidgetInterface Phonon::Gstreamer::MediaNode) +public: + VideoWidget(Backend *backend, QWidget *parent = 0); + ~VideoWidget(); + + void setupVideoBin(); + void paintEvent(QPaintEvent *event); + void mediaNodeEvent(const MediaNodeEvent *event); + void setVisible(bool); + + Phonon::VideoWidget::AspectRatio aspectRatio() const; + void setAspectRatio(Phonon::VideoWidget::AspectRatio aspectRatio); + Phonon::VideoWidget::ScaleMode scaleMode() const; + void setScaleMode(Phonon::VideoWidget::ScaleMode); + qreal brightness() const; + void setBrightness(qreal); + qreal contrast() const; + void setContrast(qreal); + qreal hue() const; + void setHue(qreal); + qreal saturation() const; + void setSaturation(qreal); + void setMovieSize(const QSize &size); + QSize sizeHint() const; + QRect scaleToAspect(QRect srcRect, int w, int h) const; + QRect calculateDrawFrameRect() const; + + GstElement *videoElement() + { + Q_ASSERT(m_videoBin); + return m_videoBin; + } + + QSize movieSize() const { + return m_movieSize; + } + + bool event(QEvent *); + + QWidget *widget() { + return this; + } + +protected: + GstElement *m_videoBin; + QSize m_movieSize; + AbstractRenderer *m_renderer; + +private: + Phonon::VideoWidget::AspectRatio m_aspectRatio; + qreal m_brightness, m_hue, m_contrast, m_saturation; + Phonon::VideoWidget::ScaleMode m_scaleMode; + + GstElement *m_videoBalance; + GstElement *m_colorspace; + GstElement *m_videoplug; +}; + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_VIDEOWIDGET_H diff --git a/src/3rdparty/phonon/gstreamer/volumefadereffect.cpp b/src/3rdparty/phonon/gstreamer/volumefadereffect.cpp new file mode 100644 index 0000000..d7ee11b --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/volumefadereffect.cpp @@ -0,0 +1,162 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "volumefadereffect.h" +#include "common.h" +#include <QtCore> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +VolumeFaderEffect::VolumeFaderEffect(Backend *backend, QObject *parent) + : Effect(backend, parent, AudioSource | AudioSink) + , m_fadeCurve(Phonon::VolumeFaderEffect::Fade3Decibel) + , m_fadeTimer(0) + , m_fadeDuration(0) + , m_fadeFromVolume(0) + , m_fadeToVolume(0) +{ + m_effectElement = gst_element_factory_make ("volume", NULL); + if (m_effectElement) + init(); +} + +VolumeFaderEffect::~VolumeFaderEffect() +{ + if (m_fadeTimer) + killTimer(m_fadeTimer); +} + +GstElement* VolumeFaderEffect::createEffectBin() +{ + GstElement *audioBin = gst_bin_new(NULL); + + // We need a queue to handle tee-connections from parent node + GstElement *queue= gst_element_factory_make ("queue", NULL); + gst_bin_add(GST_BIN(audioBin), queue); + + GstElement *mconv= gst_element_factory_make ("audioconvert", NULL); + gst_bin_add(GST_BIN(audioBin), mconv); + gst_bin_add(GST_BIN(audioBin), m_effectElement); + + // Link src pad + GstPad *srcPad= gst_element_get_pad (m_effectElement, "src"); + gst_element_add_pad (audioBin, gst_ghost_pad_new ("src", srcPad)); + gst_object_unref (srcPad); + + // Link sink pad + gst_element_link_many(queue, mconv, m_effectElement, (const char*)NULL); + GstPad *sinkpad = gst_element_get_pad (queue, "sink"); + gst_element_add_pad (audioBin, gst_ghost_pad_new ("sink", sinkpad)); + gst_object_unref (sinkpad); + return audioBin; +} + +float VolumeFaderEffect::volume() const +{ + gdouble val = 0.0; + if (m_effectElement) + g_object_get(G_OBJECT(m_effectElement), "volume", &val, (const char*)NULL); + return (float)val; +} + +void VolumeFaderEffect::setVolume(float volume) +{ + g_object_set(G_OBJECT(m_effectElement), "volume", volume, (const char*)NULL); +} + +Phonon::VolumeFaderEffect::FadeCurve VolumeFaderEffect::fadeCurve() const +{ + return m_fadeCurve; +} + +void VolumeFaderEffect::setFadeCurve(Phonon::VolumeFaderEffect::FadeCurve fadeCurve) +{ + m_fadeCurve = fadeCurve; +} + +void VolumeFaderEffect::fadeTo(float targetVolume, int fadeTime) +{ + m_fadeToVolume = targetVolume; + m_fadeDuration = fadeTime; + m_fadeFromVolume = volume(); + m_fadeStartTime.start(); + + if (m_fadeTimer) + killTimer(m_fadeTimer); + m_fadeTimer = startTimer(30); +} + +void VolumeFaderEffect::updateFade() +{ + double currVal = 0.0; + float step = float(m_fadeStartTime.elapsed()) / float(m_fadeDuration); + if (step > 1){ + step = 1; + if (m_fadeTimer) { + killTimer(m_fadeTimer); + m_fadeTimer = 0; + } + } + // This is a very loose and interpretation of the API + // But in fact when fading between arbitrary values, the decibel values make no sense + // Note : seems like we will change the API to re-use names from QTimeline for this + switch (fadeCurve()) { + case Phonon::VolumeFaderEffect::Fade3Decibel: // Slow in the beginning + currVal = step * step; + break; + case Phonon::VolumeFaderEffect::Fade6Decibel: // Linear fade + currVal = step; + break; + case Phonon::VolumeFaderEffect::Fade9Decibel: // Fast in the beginning / Linear + currVal = step * 0.5 + (1.0-(1.0-step)*(1.0-step)) * 0.5; + break; + case Phonon::VolumeFaderEffect::Fade12Decibel: // Fast in the beginning + currVal = 1.0 - (1.0-step) * (1.0-step); + break; + default: + break; + } + const double volume = (1.0 - currVal) * m_fadeFromVolume + currVal * m_fadeToVolume; + setVolume(volume); +} + +bool VolumeFaderEffect::event(QEvent *event) +{ + switch (event->type()){ + case QEvent::Timer: + { + QTimerEvent *timerEvent = static_cast<QTimerEvent *>(event); + if (timerEvent->timerId() == m_fadeTimer) + updateFade(); + break; + } + default: + break; + } + return QObject::event(event); +} + +}} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#include "moc_volumefadereffect.cpp" diff --git a/src/3rdparty/phonon/gstreamer/volumefadereffect.h b/src/3rdparty/phonon/gstreamer/volumefadereffect.h new file mode 100644 index 0000000..d74014c --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/volumefadereffect.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_VOLUMEFADEREFFECT_H +#define Phonon_GSTREAMER_VOLUMEFADEREFFECT_H + +#include "medianode.h" +#include "effect.h" + +#include <phonon/effectinterface.h> +#include <phonon/effectparameter.h> +#include <phonon/volumefaderinterface.h> + +#include <QtCore> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + class VolumeFaderEffect : public Effect, public VolumeFaderInterface + { + Q_OBJECT + Q_INTERFACES(Phonon::VolumeFaderInterface) + + public: + VolumeFaderEffect(Backend *backend, QObject *parent = 0); + ~VolumeFaderEffect(); + + GstElement* createEffectBin(); + GstElement *audioElement() { return m_effectBin; } + bool event(QEvent *); + void updateFade(); + + // VolumeFaderInterface: + float volume() const; + void setVolume(float volume); + Phonon::VolumeFaderEffect::FadeCurve fadeCurve() const; + void setFadeCurve(Phonon::VolumeFaderEffect::FadeCurve fadeCurve); + void fadeTo(float volume, int fadeTime); + + Phonon::VolumeFaderEffect::FadeCurve m_fadeCurve; + int m_fadeTimer; + int m_fadeDuration; + float m_fadeFromVolume; + float m_fadeToVolume; + QTime m_fadeStartTime; + }; +}} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_VOLUMEFADEREFFECT_H diff --git a/src/3rdparty/phonon/gstreamer/widgetrenderer.cpp b/src/3rdparty/phonon/gstreamer/widgetrenderer.cpp new file mode 100644 index 0000000..d4a411f --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/widgetrenderer.cpp @@ -0,0 +1,150 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <QtGui/QPainter> +#include <gst/gst.h> +#include "common.h" +#include "message.h" +#include "mediaobject.h" +#include "qwidgetvideosink.h" +#include "widgetrenderer.h" +#include "qrgb.h" + +// support old OpenGL installations (1.2) +// assume that if TEXTURE0 isn't defined, none are +#ifndef GL_TEXTURE0 +# define GL_TEXTURE0 0x84C0 +# define GL_TEXTURE1 0x84C1 +# define GL_TEXTURE2 0x84C2 +#endif + +QT_BEGIN_NAMESPACE + +static void frameRendered() +{ + static QString displayFps = qgetenv("PHONON_GST_FPS"); + if (displayFps.isEmpty()) + return; + + static int frames = 0; + static QTime lastTime = QTime::currentTime(); + QTime time = QTime::currentTime(); + + int delta = lastTime.msecsTo(time); + if (delta > 2000) { + printf("FPS: %f\n", 1000.0 * frames / qreal(delta)); + lastTime = time; + frames = 0; + } + + ++frames; +} + +namespace Phonon +{ +namespace Gstreamer +{ + +WidgetRenderer::WidgetRenderer(VideoWidget *videoWidget) + : AbstractRenderer(videoWidget) + , m_width(0) + , m_height(0) +{ + videoWidget->backend()->logMessage("Creating QWidget renderer"); + if ((m_videoSink = GST_ELEMENT(g_object_new(get_type_RGB(), NULL)))) { + gst_object_ref (GST_OBJECT (m_videoSink)); //Take ownership + gst_object_sink (GST_OBJECT (m_videoSink)); + + QWidgetVideoSinkBase* sink = reinterpret_cast<QWidgetVideoSinkBase*>(m_videoSink); + // Let the videosink know which widget to direct frame updates to + sink->renderWidget = videoWidget; + } + + // Clear the background with black by default + QPalette palette; + palette.setColor(QPalette::Background, Qt::black); + m_videoWidget->setPalette(palette); + m_videoWidget->setAutoFillBackground(true); + m_videoWidget->setAttribute(Qt::WA_NoSystemBackground, false); + m_videoWidget->setAttribute(Qt::WA_PaintOnScreen, false); +} + +void WidgetRenderer::setNextFrame(const QByteArray &array, int w, int h) +{ + if (m_videoWidget->root()->state() == Phonon::LoadingState) + return; + + m_frame = QImage(); + { + m_frame = QImage((uchar *)array.constData(), w, h, QImage::Format_RGB32); + } + + m_array = array; + m_width = w; + m_height = h; + + m_videoWidget->update(); +} + +void WidgetRenderer::handleMediaNodeEvent(const MediaNodeEvent *event) +{ + switch (event->type()) { + case MediaNodeEvent::SourceChanged: + { + clearFrame(); + break; + } + default: + break; + } +} + +void WidgetRenderer::clearFrame() +{ + m_frame = QImage(); + m_array = QByteArray(); + m_videoWidget->update(); +} + +const QImage &WidgetRenderer::currentFrame() const +{ + return m_frame; +} + +void WidgetRenderer::handlePaint(QPaintEvent *event) +{ + Q_UNUSED(event); + QPainter painter(m_videoWidget); + m_drawFrameRect = m_videoWidget->calculateDrawFrameRect(); + painter.drawImage(drawFrameRect(), currentFrame()); + frameRendered(); +} + +bool WidgetRenderer::eventFilter(QEvent * event) +{ + if (event->type() == QEvent::User) { + NewFrameEvent *frameEvent= static_cast <NewFrameEvent *>(event); + setNextFrame(frameEvent->frame, frameEvent->width, frameEvent->height); + return true; + } + return false; +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE diff --git a/src/3rdparty/phonon/gstreamer/widgetrenderer.h b/src/3rdparty/phonon/gstreamer/widgetrenderer.h new file mode 100644 index 0000000..ff64fa7 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/widgetrenderer.h @@ -0,0 +1,63 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_WIDGETRENDERER_H +#define Phonon_GSTREAMER_WIDGETRENDERER_H + +#include "videowidget.h" +#include "common.h" + +#ifndef QT_NO_OPENGL +#include <QtOpenGL/QGLFormat> +#include <QtOpenGL/QGLWidget> +#endif + +QT_BEGIN_NAMESPACE + +class QString; + +namespace Phonon +{ +namespace Gstreamer +{ + +class WidgetRenderer : public AbstractRenderer +{ +public: + WidgetRenderer(VideoWidget *videoWidget); + bool eventFilter(QEvent * event); + void handlePaint(QPaintEvent *paintEvent); + void handleMediaNodeEvent(const MediaNodeEvent *event); + const QImage& currentFrame() const; + QRect drawFrameRect() const { return m_drawFrameRect; } + void setNextFrame(const QByteArray &array, int width, int height); + bool frameIsSet() { return !m_array.isNull(); } + void clearFrame(); +private: + mutable QImage m_frame; + QByteArray m_array; + int m_width; + int m_height; + QRect m_drawFrameRect; +}; + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_WIDGETRENDERER_H diff --git a/src/3rdparty/phonon/gstreamer/x11renderer.cpp b/src/3rdparty/phonon/gstreamer/x11renderer.cpp new file mode 100644 index 0000000..73877a8 --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/x11renderer.cpp @@ -0,0 +1,194 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "videowidget.h" +#include "x11renderer.h" + +#ifndef Q_WS_QWS + +#include <QtGui/QPalette> +#include <QtGui/QApplication> +#include <QtGui/QPainter> +#include <X11/Xlib.h> +#include <gst/gst.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/propertyprobe.h> +#include "common.h" +#include "mediaobject.h" +#include "message.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace Gstreamer +{ + +class OverlayWidget : public QWidget +{ +public: + OverlayWidget(VideoWidget *videoWidget, X11Renderer *renderer) : + QWidget(videoWidget), + m_videoWidget(videoWidget), + m_renderer(renderer) { } + void paintEvent(QPaintEvent *) { + Phonon::State state = m_videoWidget->root() ? m_videoWidget->root()->state() : Phonon::LoadingState; + if (state == Phonon::PlayingState || state == Phonon::PausedState) { + m_renderer->windowExposed(); + } else { + QPainter painter(this); + painter.fillRect(m_videoWidget->rect(), m_videoWidget->palette().background()); + } + } +private: + VideoWidget *m_videoWidget; + X11Renderer *m_renderer; +}; + +X11Renderer::X11Renderer(VideoWidget *videoWidget) + : AbstractRenderer(videoWidget) +{ + m_renderWidget = new OverlayWidget(videoWidget, this); + videoWidget->backend()->logMessage("Creating X11 overlay renderer"); + QPalette palette; + palette.setColor(QPalette::Background, Qt::black); + m_videoWidget->setPalette(palette); + m_videoWidget->setAutoFillBackground(true); + m_renderWidget->setMouseTracking(true); + m_videoSink = createVideoSink(); + aspectRatioChanged(videoWidget->aspectRatio()); + setOverlay(); +} + +X11Renderer::~X11Renderer() +{ + m_renderWidget->setAttribute(Qt::WA_PaintOnScreen, false); + m_renderWidget->setAttribute(Qt::WA_NoSystemBackground, false); + delete m_renderWidget; +} + +GstElement* X11Renderer::createVideoSink() +{ + GstElement *videoSink = gst_element_factory_make ("xvimagesink", NULL); + if (videoSink) { + // Check if the xv sink is usable + if (gst_element_set_state(videoSink, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS) { + gst_object_unref(GST_OBJECT(videoSink)); + videoSink = 0; + } else { + // Note that this should not really be neccessary as these are + // default values, though under certain conditions values are retained + // even between application instances. (reproducible on 0.10.16/Gutsy) + g_object_set(G_OBJECT(videoSink), "brightness", 0, (const char*)NULL); + g_object_set(G_OBJECT(videoSink), "contrast", 0, (const char*)NULL); + g_object_set(G_OBJECT(videoSink), "hue", 0, (const char*)NULL); + g_object_set(G_OBJECT(videoSink), "saturation", 0, (const char*)NULL); + } + } + + if (!videoSink) + videoSink = gst_element_factory_make ("ximagesink", NULL); + + gst_object_ref (GST_OBJECT (videoSink)); //Take ownership + gst_object_sink (GST_OBJECT (videoSink)); + + return videoSink; +} + +void X11Renderer::handleMediaNodeEvent(const MediaNodeEvent *event) +{ + switch (event->type()) { + case MediaNodeEvent::SourceChanged: + setOverlay(); // We need to do this whenever the pipeline is reset + break; // otherwise the videosink will open in its own window + default: + break; + } +} + + +void X11Renderer::aspectRatioChanged(Phonon::VideoWidget::AspectRatio) +{ + if (m_renderWidget) { + m_renderWidget->setGeometry(m_videoWidget->calculateDrawFrameRect()); + } +} + +void X11Renderer::scaleModeChanged(Phonon::VideoWidget::ScaleMode) +{ + if (m_renderWidget) { + m_renderWidget->setGeometry(m_videoWidget->calculateDrawFrameRect()); + } +} + +void X11Renderer::movieSizeChanged(const QSize &movieSize) +{ + Q_UNUSED(movieSize); + if (m_renderWidget) { + m_renderWidget->setGeometry(m_videoWidget->calculateDrawFrameRect()); + } +} + +bool X11Renderer::eventFilter(QEvent *e) +{ + if (e->type() == QEvent::Show) { + // Setting these values ensures smooth resizing since it + // will prevent the system from clearing the background + m_renderWidget->setAttribute(Qt::WA_NoSystemBackground, true); + m_renderWidget->setAttribute(Qt::WA_PaintOnScreen, true); + setOverlay(); + } else if (e->type() == QEvent::Resize) { + // This is a workaround for missing background repaints + // when reducing window size + m_renderWidget->setGeometry(m_videoWidget->calculateDrawFrameRect()); + windowExposed(); + } + return false; +} + +void X11Renderer::handlePaint(QPaintEvent *) +{ + QPainter painter(m_videoWidget); + painter.fillRect(m_videoWidget->rect(), m_videoWidget->palette().background()); +} + +void X11Renderer::setOverlay() +{ + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + WId windowId = m_renderWidget->winId(); + // Even if we have created a winId at this point, other X applications + // need to be aware of it. + QApplication::syncX(); + gst_x_overlay_set_xwindow_id ( GST_X_OVERLAY(m_videoSink) , windowId ); + } + windowExposed(); + m_overlaySet = true; +} + +void X11Renderer::windowExposed() +{ + QApplication::syncX(); + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) + gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink)); +} + +} +} //namespace Phonon::Gstreamer + +QT_END_NAMESPACE + +#endif // Q_WS_QWS diff --git a/src/3rdparty/phonon/gstreamer/x11renderer.h b/src/3rdparty/phonon/gstreamer/x11renderer.h new file mode 100644 index 0000000..f7140da --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/x11renderer.h @@ -0,0 +1,68 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + 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 or 3 of the License. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef Phonon_GSTREAMER_X11RENDERER_H +#define Phonon_GSTREAMER_X11RENDERER_H + +#include "videowidget.h" +#include "common.h" + +#include <QtGui/QWidget> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +#ifndef Q_WS_QWS + +class QString; + +namespace Phonon +{ +namespace Gstreamer +{ + +class OverlayWidget; +class X11Renderer : public AbstractRenderer +{ +public: + X11Renderer(VideoWidget *videoWidget); + ~X11Renderer(); + void handlePaint(QPaintEvent *event); + void aspectRatioChanged(Phonon::VideoWidget::AspectRatio aspectRatio); + void scaleModeChanged(Phonon::VideoWidget::ScaleMode scaleMode); + void movieSizeChanged(const QSize &movieSize); + void handleMediaNodeEvent(const MediaNodeEvent *event); + bool eventFilter(QEvent *); + bool paintsOnWidget() { return false; } + bool overlaySet() const { return m_overlaySet; } + void setOverlay(); + void windowExposed(); + GstElement *createVideoSink(); +private: + OverlayWidget *m_renderWidget; + bool m_overlaySet; +}; + +} +} //namespace Phonon::Gstreamer + +#endif // Q_WS_QWS + +QT_END_NAMESPACE + +#endif // Phonon_GSTREAMER_X11RENDERER_H |