diff options
Diffstat (limited to 'src/3rdparty/phonon/gstreamer')
19 files changed, 654 insertions, 144 deletions
diff --git a/src/3rdparty/phonon/gstreamer/CMakeLists.txt b/src/3rdparty/phonon/gstreamer/CMakeLists.txt index 08f892a..2249ac3 100644 --- a/src/3rdparty/phonon/gstreamer/CMakeLists.txt +++ b/src/3rdparty/phonon/gstreamer/CMakeLists.txt @@ -19,7 +19,7 @@ include(ConfigureChecks.cmake) if (BUILD_PHONON_GSTREAMER) include_directories( ${CMAKE_CURRENT_BINARY_DIR} - ${GSTREAMER_INCLUDE_DIR} + ${GSTREAMER_INCLUDE_DIR} ${GLIB2_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR} ${X11_X11_INCLUDE_PATH}) @@ -34,7 +34,6 @@ if (BUILD_PHONON_GSTREAMER) set(phonon_gstreamer_SRCS audiooutput.cpp - artssink.cpp backend.cpp devicemanager.cpp effectmanager.cpp @@ -50,14 +49,20 @@ if (BUILD_PHONON_GSTREAMER) message.cpp audioeffect.cpp abstractrenderer.cpp - x11renderer.cpp widgetrenderer.cpp glrenderer.cpp volumefadereffect.cpp + audiodataoutput.cpp ) - find_package(Alsa) - macro_ensure_version("0.10.22" ${GSTREAMER_VERSION} GSTREAMER_HAS_NONBLOCKING_ALSASINK) + if(NOT WIN32) + set(phonon_gstreamer_SRCS + ${phonon_gstreamer_SRCS} + artssink.cpp + x11renderer.cpp) + macro_optional_find_package(Alsa) + macro_ensure_version("0.10.22" ${GSTREAMER_VERSION} GSTREAMER_HAS_NONBLOCKING_ALSASINK) + endif(NOT WIN32) if(ALSA_FOUND AND NOT GSTREAMER_HAS_NONBLOCKING_ALSASINK) add_definitions(-DUSE_ALSASINK2) include_directories(${ALSA_INCLUDES}) @@ -78,6 +83,9 @@ if (BUILD_PHONON_GSTREAMER) if(ALSA_FOUND) target_link_libraries(phonon_gstreamer ${ASOUND_LIBRARY}) endif(ALSA_FOUND) + if(USE_INSTALL_PLUGIN) + target_link_libraries(phonon_gstreamer ${GSTREAMER_PLUGIN_PBUTILS_LIBRARIES}) + endif(USE_INSTALL_PLUGIN) install(TARGETS phonon_gstreamer DESTINATION ${PLUGIN_INSTALL_DIR}/plugins/phonon_backend) install(FILES gstreamer.desktop DESTINATION ${SERVICES_INSTALL_DIR}/phononbackends) diff --git a/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake b/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake index f2922e1..eaf5b99 100644 --- a/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake +++ b/src/3rdparty/phonon/gstreamer/ConfigureChecks.cmake @@ -17,6 +17,7 @@ macro_log_feature(GSTREAMER_FOUND "GStreamer" "gstreamer 0.10 is required for th 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_log_feature(GSTREAMER_PLUGIN_AUDIO_LIBRARIES "GStreamer audio plugin" "The gstreamer audio 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) @@ -30,8 +31,8 @@ macro_log_feature(LIBXML2_FOUND "LibXml2" "LibXml2 is required to compile the gs 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) +if (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GSTREAMER_PLUGIN_AUDIO_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) +else (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GSTREAMER_PLUGIN_AUDIO_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) +endif (GSTREAMER_FOUND AND GSTREAMER_PLUGIN_VIDEO_LIBRARIES AND GSTREAMER_PLUGIN_AUDIO_LIBRARIES AND GLIB2_FOUND AND GOBJECT_FOUND AND LIBXML2_FOUND AND OPENGL_FOUND) diff --git a/src/3rdparty/phonon/gstreamer/audiodataoutput.cpp b/src/3rdparty/phonon/gstreamer/audiodataoutput.cpp new file mode 100644 index 0000000..30dabdf --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audiodataoutput.cpp @@ -0,0 +1,143 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Matthias Kretz <kretz@kde.org> + Copyright (C) 2009 Martin Sandsmark <sandsmark@samfundet.no> + + 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) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), Nokia Corporation + (or its successors, if any) and the KDE Free Qt Foundation, which shall + act as a proxy defined in Section 6 of version 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 "audiodataoutput.h" +#include "gsthelper.h" +#include "medianode.h" +#include "mediaobject.h" +#include <QtCore/QVector> +#include <QtCore/QMap> +#include <phonon/audiooutput.h> + +namespace Phonon +{ +namespace Gstreamer +{ +AudioDataOutput::AudioDataOutput(Backend *backend, QObject *parent) + : QObject(parent), + MediaNode(backend, AudioSink | AudioSource) +{ + static int count = 0; + m_name = "AudioDataOutput" + QString::number(count++); + + m_queue = gst_element_factory_make ("identity", NULL); + gst_object_ref(m_queue); + m_isValid = true; +} + +AudioDataOutput::~AudioDataOutput() +{ + gst_element_set_state(m_queue, GST_STATE_NULL); + gst_object_unref(m_queue); +} + +int AudioDataOutput::dataSize() const +{ + return m_dataSize; +} + +int AudioDataOutput::sampleRate() const +{ + return 44100; +} + +void AudioDataOutput::setDataSize(int size) +{ + m_dataSize = size; +} + +typedef QMap<Phonon::AudioDataOutput::Channel, QVector<float> > FloatMap; +typedef QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > IntMap; + +inline void AudioDataOutput::convertAndEmit(const QVector<qint16> &leftBuffer, const QVector<qint16> &rightBuffer) +{ + //TODO: Floats + IntMap map; + map.insert(Phonon::AudioDataOutput::LeftChannel, leftBuffer); + map.insert(Phonon::AudioDataOutput::RightChannel, rightBuffer); + emit dataReady(map); +} + +void AudioDataOutput::processBuffer(GstPad*, GstBuffer* buffer, gpointer gThat) +{ + // TODO emit endOfMedia + AudioDataOutput *that = reinterpret_cast<AudioDataOutput*>(gThat); + + // determine the number of channels + GstStructure* structure = gst_caps_get_structure (GST_BUFFER_CAPS(buffer), 0); + gst_structure_get_int (structure, "channels", &that->m_channels); + + if (that->m_channels > 2 || that->m_channels < 0) { + qWarning() << Q_FUNC_INFO << ": Number of channels not supported: " << that->m_channels; + return; + } + + gint16 *data = reinterpret_cast<gint16*>(GST_BUFFER_DATA(buffer)); + guint size = GST_BUFFER_SIZE(buffer) / sizeof(gint16); + + that->m_pendingData.reserve(that->m_pendingData.size() + size); + + for (uint i=0; i<size; i++) { + // 8 bit? interleaved? yay for lacking documentation! + that->m_pendingData.append(data[i]); + } + + while (that->m_pendingData.size() > that->m_dataSize * that->m_channels) { + if (that->m_channels == 1) { + QVector<qint16> intBuffer(that->m_dataSize); + memcpy(intBuffer.data(), that->m_pendingData.constData(), that->m_dataSize * sizeof(qint16)); + + that->convertAndEmit(intBuffer, intBuffer); + int newSize = that->m_pendingData.size() - that->m_dataSize; + memmove(that->m_pendingData.data(), that->m_pendingData.constData() + that->m_dataSize, newSize * sizeof(qint16)); + that->m_pendingData.resize(newSize); + } else { + QVector<qint16> left(that->m_dataSize), right(that->m_dataSize); + for (int i=0; i<that->m_dataSize; i++) { + left[i] = that->m_pendingData[i*2]; + right[i] = that->m_pendingData[i*2+1]; + } + that->m_pendingData.resize(that->m_pendingData.size() - that->m_dataSize*2); + that->convertAndEmit(left, right); + } + } +} + +void AudioDataOutput::mediaNodeEvent(const MediaNodeEvent *event) +{ + if (event->type() == MediaNodeEvent::MediaObjectConnected && root()) { + g_object_set(G_OBJECT(audioElement()), "sync", true, (const char*)NULL); + GstPad *audiopad = gst_element_get_pad (audioElement(), "src"); + gst_pad_add_buffer_probe (audiopad, G_CALLBACK(processBuffer), this); + gst_object_unref (audiopad); + return; + } + + MediaNode::mediaNodeEvent(event); +} + +}} //namespace Phonon::Gstreamer + +#include "moc_audiodataoutput.cpp" +// vim: sw=4 ts=4 + diff --git a/src/3rdparty/phonon/gstreamer/audiodataoutput.h b/src/3rdparty/phonon/gstreamer/audiodataoutput.h new file mode 100644 index 0000000..5e30a1d --- /dev/null +++ b/src/3rdparty/phonon/gstreamer/audiodataoutput.h @@ -0,0 +1,84 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Matthias Kretz <kretz@kde.org> + Copyright (C) 2009 Martin Sandsmark <sandsmark@samfundet.no> + + 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) version 3, or any + later version accepted by the membership of KDE e.V. (or its + successor approved by the membership of KDE e.V.), Nokia Corporation + (or its successors, if any) and the KDE Free Qt Foundation, which shall + act as a proxy defined in Section 6 of version 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_AUDIODATAOUTPUT_H +#define Phonon_GSTREAMER_AUDIODATAOUTPUT_H + +#include "abstractaudiooutput.h" +#include "backend.h" +#include "medianode.h" +#include <phonon/audiodataoutput.h> +#include <phonon/audiodataoutputinterface.h> + +namespace Phonon +{ +namespace Gstreamer +{ + /** + * \author Martin Sandsmark <sandsmark@samfundet.no> + */ + class AudioDataOutput : public QObject, + public AudioDataOutputInterface, + public MediaNode + { + Q_OBJECT + Q_INTERFACES(Phonon::AudioDataOutputInterface Phonon::Gstreamer::MediaNode) + + public: + AudioDataOutput(Backend *, QObject *); + ~AudioDataOutput(); + + public Q_SLOTS: + int dataSize() const; + int sampleRate() const; + void setDataSize(int size); + + public: + /// callback function for handling new audio data + static void processBuffer(GstPad*, GstBuffer*, gpointer); + + Phonon::AudioDataOutput* frontendObject() const { return m_frontend; } + void setFrontendObject(Phonon::AudioDataOutput *frontend) { m_frontend = frontend; } + + GstElement *audioElement() { return m_queue; } + + void mediaNodeEvent(const MediaNodeEvent *event); + + + signals: + void dataReady(const QMap<Phonon::AudioDataOutput::Channel, QVector<qint16> > &data); + void dataReady(const QMap<Phonon::AudioDataOutput::Channel, QVector<float> > &data); + void endOfMedia(int remainingSamples); + + private: + void convertAndEmit(const QVector<qint16>&, const QVector<qint16>&); + + GstElement *m_queue; + int m_dataSize; + QVector<qint16> m_pendingData; + Phonon::AudioDataOutput *m_frontend; + int m_channels; + }; +}} //namespace Phonon::Gstreamer + +// vim: sw=4 ts=4 tw=80 +#endif // Phonon_FAKE_AUDIODATAOUTPUT_H diff --git a/src/3rdparty/phonon/gstreamer/audiooutput.cpp b/src/3rdparty/phonon/gstreamer/audiooutput.cpp index 641ff6b..f3137b2 100644 --- a/src/3rdparty/phonon/gstreamer/audiooutput.cpp +++ b/src/3rdparty/phonon/gstreamer/audiooutput.cpp @@ -125,6 +125,7 @@ void AudioOutput::setVolume(qreal newVolume) bool AudioOutput::setOutputDevice(int newDevice) { m_backend->logMessage(Q_FUNC_INFO + QString::number(newDevice), Backend::Info, this); + if (newDevice == m_device) return true; @@ -135,20 +136,11 @@ bool AudioOutput::setOutputDevice(int newDevice) } bool success = false; - const QList<AudioDevice> deviceList = m_backend->deviceManager()->audioOutputDevices(); - int deviceIdx = -1; - for (int i=0; i<deviceList.size(); i++) { - if (deviceList.at(i).id == newDevice) { - deviceIdx = i; - break; - } - } - - if (m_audioSink && deviceIdx >= 0) { + if (m_audioSink && newDevice >= 0) { // Save previous state GstState oldState = GST_STATE(m_audioSink); const QByteArray oldDeviceValue = GstHelper::property(m_audioSink, "device"); - const QByteArray deviceId = deviceList.at(deviceIdx).gstId; + const QByteArray deviceId = m_backend->deviceManager()->gstId(newDevice); m_device = newDevice; // We test if the device can be opened by checking if it can go from NULL to READY state @@ -170,7 +162,7 @@ bool AudioOutput::setOutputDevice(int newDevice) deviceId, Backend::Info, this); } - // Note the stopped state should not really be neccessary, but seems to be required to + // Note the stopped state should not really be necessary, 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)); diff --git a/src/3rdparty/phonon/gstreamer/backend.cpp b/src/3rdparty/phonon/gstreamer/backend.cpp index dab6f35..729a1d3 100644 --- a/src/3rdparty/phonon/gstreamer/backend.cpp +++ b/src/3rdparty/phonon/gstreamer/backend.cpp @@ -18,6 +18,7 @@ #include "common.h" #include "backend.h" #include "audiooutput.h" +#include "audiodataoutput.h" #include "audioeffect.h" #include "mediaobject.h" #include "videowidget.h" @@ -26,6 +27,7 @@ #include "message.h" #include "volumefadereffect.h" #include <gst/interfaces/propertyprobe.h> +#include <phonon/pulsesupport.h> #include <QtCore/QSet> #include <QtCore/QVariant> @@ -49,13 +51,17 @@ Backend::Backend(QObject *parent, const QVariantList &) , m_debugLevel(Warning) , m_isValid(false) { + // Initialise PulseAudio support + PulseSupport *pulse = PulseSupport::getInstance(); + pulse->enable(); + connect(pulse, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), SIGNAL(objectDescriptionChanged(ObjectDescriptionType))); + // In order to support reloading, we only set the app name once... static bool first = true; if (first) { first = false; g_set_application_name(qApp->applicationName().toUtf8()); } - GError *err = 0; bool wasInit = gst_init_check(0, 0, &err); //init gstreamer: must be called before any gst-related functions if (err) @@ -92,6 +98,9 @@ Backend::Backend(QObject *parent, const QVariantList &) Backend::~Backend() { + delete m_effectManager; + delete m_deviceManager; + PulseSupport::shutdown(); } gboolean Backend::busCall(GstBus *bus, GstMessage *msg, gpointer data) @@ -119,18 +128,15 @@ QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const case MediaObjectClass: return new MediaObject(this, parent); - case AudioOutputClass: { - AudioOutput *ao = new AudioOutput(this, parent); - m_audioOutputs.append(ao); - return ao; - } + case AudioOutputClass: + return new AudioOutput(this, parent); + #ifndef QT_NO_PHONON_EFFECT case EffectClass: return new AudioEffect(this, args[0].toInt(), parent); #endif //QT_NO_PHONON_EFFECT case AudioDataOutputClass: - logMessage("createObject() : AudioDataOutput not implemented"); - break; + return new AudioDataOutput(this, parent); #ifndef QT_NO_PHONON_VIDEO case VideoDataOutputClass: @@ -214,14 +220,14 @@ QStringList Backend::availableMimeTypes() const GstPluginFeature *feature = GST_PLUGIN_FEATURE(iter->data); QString klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature)); - if (klass == QLatin1String("Codec/Decoder") || - klass == QLatin1String("Codec/Decoder/Audio") || - klass == QLatin1String("Codec/Decoder/Video") || - klass == QLatin1String("Codec/Demuxer") || - klass == QLatin1String("Codec/Demuxer/Audio") || - klass == QLatin1String("Codec/Demuxer/Video") || - klass == QLatin1String("Codec/Parser") || - klass == QLatin1String("Codec/Parser/Audio") || + if (klass == QLatin1String("Codec/Decoder") || + klass == QLatin1String("Codec/Decoder/Audio") || + klass == QLatin1String("Codec/Decoder/Video") || + klass == QLatin1String("Codec/Demuxer") || + klass == QLatin1String("Codec/Demuxer/Audio") || + klass == QLatin1String("Codec/Demuxer/Video") || + klass == QLatin1String("Codec/Parser") || + klass == QLatin1String("Codec/Parser/Audio") || klass == QLatin1String("Codec/Parser/Video")) { const GList *static_templates; @@ -234,16 +240,28 @@ QStringList Backend::availableMimeTypes() const 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); + for (unsigned int struct_idx = 0; struct_idx < gst_caps_get_size (caps); struct_idx++) { + + const GstStructure* capsStruct = gst_caps_get_structure (caps, struct_idx); + QString mime = QString::fromUtf8(gst_structure_get_name (capsStruct)); + if (!availableMimeTypes.contains(mime)) + availableMimeTypes.append(mime); + } } } } } } g_list_free(factoryList); + if (availableMimeTypes.contains("audio/x-vorbis") + && availableMimeTypes.contains("application/x-ogm-audio")) { + if (!availableMimeTypes.contains("audio/x-vorbis+ogg")) + availableMimeTypes.append("audio/x-vorbis+ogg"); + if (!availableMimeTypes.contains("application/ogg")) /* *.ogg */ + availableMimeTypes.append("application/ogg"); + if (!availableMimeTypes.contains("audio/ogg")) /* *.oga */ + availableMimeTypes.append("audio/ogg"); + } availableMimeTypes.sort(); return availableMimeTypes; } @@ -293,14 +311,11 @@ QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescripti switch (type) { case Phonon::AudioOutputDeviceType: { - QList<AudioDevice> audioDevices = deviceManager()->audioOutputDevices(); - foreach(const AudioDevice &device, audioDevices) { - if (device.id == index) { - ret.insert("name", device.gstId); - ret.insert("description", device.description); - ret.insert("icon", QLatin1String("audio-card")); - break; - } + AudioDevice* ad; + if ((ad = deviceManager()->audioDevice(index))) { + ret.insert("name", ad->gstId); + ret.insert("description", ad->description); + ret.insert("icon", ad->icon); } } break; @@ -429,7 +444,7 @@ EffectManager* Backend::effectManager() const /** * Returns a debuglevel that is determined by the - * PHONON_GSTREAMER_DEBUG environment variable. + * PHONON_GST_DEBUG environment variable. * * Warning - important warnings * Info - general info diff --git a/src/3rdparty/phonon/gstreamer/backend.h b/src/3rdparty/phonon/gstreamer/backend.h index 2aab6fa..d157f11 100644 --- a/src/3rdparty/phonon/gstreamer/backend.h +++ b/src/3rdparty/phonon/gstreamer/backend.h @@ -86,7 +86,6 @@ private Q_SLOTS: private: static gboolean busCall(GstBus *bus, GstMessage *msg, gpointer data); - QList<QPointer<AudioOutput> > m_audioOutputs; DeviceManager *m_deviceManager; EffectManager *m_effectManager; diff --git a/src/3rdparty/phonon/gstreamer/devicemanager.cpp b/src/3rdparty/phonon/gstreamer/devicemanager.cpp index 60e860f..c3826eb 100644 --- a/src/3rdparty/phonon/gstreamer/devicemanager.cpp +++ b/src/3rdparty/phonon/gstreamer/devicemanager.cpp @@ -24,6 +24,7 @@ #include "widgetrenderer.h" #include "x11renderer.h" #include "artssink.h" +#include "pulsesupport.h" #ifdef USE_ALSASINK2 #include "alsasink2.h" @@ -44,9 +45,12 @@ namespace Gstreamer AudioDevice::AudioDevice(DeviceManager *manager, const QByteArray &gstId) : gstId(gstId) { - //get an id - static int counter = 0; - id = counter++; + // This should never be called when PulseAudio is active. + Q_ASSERT(!PulseSupport::getInstance()->isActive()); + + id = manager->allocateDeviceId(); + icon = "audio-card"; + //get name from device if (gstId == "default") { description = "Default audio device"; @@ -71,22 +75,25 @@ AudioDevice::AudioDevice(DeviceManager *manager, const QByteArray &gstId) DeviceManager::DeviceManager(Backend *backend) : QObject(backend) , m_backend(backend) + , m_audioDeviceCounter(0) { - m_audioSink = qgetenv("PHONON_GST_AUDIOSINK"); - m_videoSinkWidget = qgetenv("PHONON_GST_VIDEOMODE"); - -#ifndef QT_NO_SETTINGS QSettings settings(QLatin1String("Trolltech")); settings.beginGroup(QLatin1String("Qt")); + PulseSupport *pulse = PulseSupport::getInstance(); + m_audioSink = qgetenv("PHONON_GST_AUDIOSINK"); if (m_audioSink.isEmpty()) { m_audioSink = settings.value(QLatin1String("audiosink"), "Auto").toByteArray().toLower(); + if (m_audioSink == "auto" && pulse->isActive()) + m_audioSink = "pulsesink"; } + if ("pulsesink" != m_audioSink) + pulse->enable(false); + m_videoSinkWidget = qgetenv("PHONON_GST_VIDEOMODE"); if (m_videoSinkWidget.isEmpty()) { m_videoSinkWidget = settings.value(QLatin1String("videomode"), "Auto").toByteArray().toLower(); } -#endif //QT_NO_SETTINGS if (m_backend->isValid()) updateDeviceList(); @@ -271,9 +278,17 @@ AbstractRenderer *DeviceManager::createVideoRenderer(VideoWidget *parent) } #endif //QT_NO_PHONON_VIDEO -/* - * Returns a positive device id or -1 if device - * does not exist +/** + * Allocate a device id for a new audio device + */ +int DeviceManager::allocateDeviceId() +{ + return m_audioDeviceCounter++; +} + + +/** + * Returns a positive device id or -1 if device does not exist * * The gstId is typically in the format hw:1,0 */ @@ -288,16 +303,30 @@ int DeviceManager::deviceId(const QByteArray &gstId) const } /** - * Get a human-readable description from a device id + * Returns a gstId or "default" if device does not exist + * + * The gstId is typically in the format hw:1,0 */ -QByteArray DeviceManager::deviceDescription(int id) const +const QByteArray DeviceManager::gstId(int deviceId) +{ + if (!PulseSupport::getInstance()->isActive()) { + AudioDevice *ad = audioDevice(deviceId); + if (ad) + return QByteArray(ad->gstId); + } + return QByteArray("default"); +} + +/** +* Get the AudioDevice for a given device id +*/ +AudioDevice* DeviceManager::audioDevice(int id) { for (int i = 0 ; i < m_audioDeviceList.size() ; ++i) { - if (m_audioDeviceList[i].id == id) { - return m_audioDeviceList[i].description; - } + if (m_audioDeviceList[i].id == id) + return &m_audioDeviceList[i]; } - return QByteArray(); + return NULL; } /** @@ -311,8 +340,11 @@ void DeviceManager::updateDeviceList() QList<QByteArray> list; if (audioSink) { - list = GstHelper::extractProperties(audioSink, "device"); - list.prepend("default"); + if (!PulseSupport::getInstance()->isActive()) { + // If we're using pulse, the PulseSupport class takes care of things for us. + list = GstHelper::extractProperties(audioSink, "device"); + list.prepend("default"); + } for (int i = 0 ; i < list.size() ; ++i) { QByteArray gstId = list.at(i); diff --git a/src/3rdparty/phonon/gstreamer/devicemanager.h b/src/3rdparty/phonon/gstreamer/devicemanager.h index a5e8289..9c6aa8d 100644 --- a/src/3rdparty/phonon/gstreamer/devicemanager.h +++ b/src/3rdparty/phonon/gstreamer/devicemanager.h @@ -42,6 +42,7 @@ public : int id; QByteArray gstId; QByteArray description; + QString icon; }; class DeviceManager : public QObject { @@ -51,8 +52,10 @@ public: virtual ~DeviceManager(); const QList<AudioDevice> audioOutputDevices() const; GstPad *requestPad(int device) const; + int allocateDeviceId(); int deviceId(const QByteArray &gstId) const; - QByteArray deviceDescription(int id) const; + const QByteArray gstId(int id); + AudioDevice* audioDevice(int id); GstElement *createGNOMEAudioSink(Category category); GstElement *createAudioSink(Category category = NoCategory); AbstractRenderer *createVideoRenderer(VideoWidget *parent); @@ -68,6 +71,7 @@ private: bool canOpenDevice(GstElement *element) const; Backend *m_backend; QList <AudioDevice> m_audioDeviceList; + int m_audioDeviceCounter; QTimer m_devicePollTimer; QByteArray m_audioSink; QByteArray m_videoSinkWidget; diff --git a/src/3rdparty/phonon/gstreamer/effectmanager.cpp b/src/3rdparty/phonon/gstreamer/effectmanager.cpp index 563e6fc..6c88148 100644 --- a/src/3rdparty/phonon/gstreamer/effectmanager.cpp +++ b/src/3rdparty/phonon/gstreamer/effectmanager.cpp @@ -54,7 +54,7 @@ EffectManager::EffectManager(Backend *backend) // "volume" not needed // "equalizer-nbands" not really useful at the moment - // These plugins simply dont work or have major stability issues: + // These plugins simply don't 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 diff --git a/src/3rdparty/phonon/gstreamer/glrenderer.cpp b/src/3rdparty/phonon/gstreamer/glrenderer.cpp index 0203028..106884f 100644 --- a/src/3rdparty/phonon/gstreamer/glrenderer.cpp +++ b/src/3rdparty/phonon/gstreamer/glrenderer.cpp @@ -266,7 +266,7 @@ GLRenderWidgetImplementation::GLRenderWidgetImplementation(VideoWidget*videoWidg palette.setColor(QPalette::Background, Qt::black); setPalette(palette); setAutoFillBackground(true); - // Videowidget allways have this property to allow hiding the mouse cursor + // Videowidget always have this property to allow hiding the mouse cursor setMouseTracking(true); } diff --git a/src/3rdparty/phonon/gstreamer/gsthelper.cpp b/src/3rdparty/phonon/gstreamer/gsthelper.cpp index 34d99fa..69bb75c 100644 --- a/src/3rdparty/phonon/gstreamer/gsthelper.cpp +++ b/src/3rdparty/phonon/gstreamer/gsthelper.cpp @@ -121,7 +121,7 @@ GstElement* GstHelper::createPluggablePlaybin() { GstElement *playbin = 0; //init playbin and add to our pipeline - playbin = gst_element_factory_make("playbin", NULL); + playbin = gst_element_factory_make("playbin2", NULL); //Create an identity element to redirect sound GstElement *audioSinkBin = gst_bin_new (NULL); diff --git a/src/3rdparty/phonon/gstreamer/gstreamer.desktop b/src/3rdparty/phonon/gstreamer/gstreamer.desktop index b62472b..0861762 100644 --- a/src/3rdparty/phonon/gstreamer/gstreamer.desktop +++ b/src/3rdparty/phonon/gstreamer/gstreamer.desktop @@ -10,28 +10,81 @@ Icon=phonon-gstreamer InitialPreference=10 Name=GStreamer +Name[bg]=GStreamer +Name[ca]=GStreamer +Name[ca@valencia]=GStreamer +Name[cs]=GStreamer +Name[da]=GStreamer +Name[de]=GStreamer +Name[el]=GStreamer +Name[en_GB]=GStreamer +Name[es]=GStreamer +Name[et]=GStreamer +Name[eu]=GStreamer +Name[fi]=GStreamer +Name[fr]=GStreamer +Name[ga]=GStreamer +Name[gl]=GStreamer +Name[hsb]=GStreamer +Name[hu]=GStreamer +Name[id]=GStreamer +Name[is]=GStreamer +Name[it]=GStreamer +Name[ja]=GStreamer +Name[ko]=GStreamer +Name[ku]=GStreamer +Name[lt]=GStreamer +Name[lv]=GStreamer +Name[nb]=GStreamer +Name[nds]=GStreamer +Name[nl]=GStreamer +Name[nn]=GStreamer Name[pa]=ਜੀਸਟੀਰਮਰ +Name[pl]=GStreamer +Name[pt]=GStreamer +Name[pt_BR]=GStreamer +Name[ru]=GStreamer +Name[se]=GStreamer +Name[sk]=GStreamer +Name[sl]=GStreamer Name[sr]=Гстример +Name[sr@ijekavian]=Гстример +Name[sr@ijekavianlatin]=GStreamer +Name[sr@latin]=GStreamer Name[sv]=Gstreamer +Name[tr]=GStreamer +Name[uk]=GStreamer Name[x-test]=xxGStreamerxx +Name[zh_CN]=GStreamer +Name[zh_TW]=GStreamer Comment=Phonon GStreamer backend Comment[bg]=Phonon GStreamer Comment[ca]=Dorsal GStreamer del Phonon +Comment[ca@valencia]=Dorsal GStreamer del Phonon +Comment[cs]=Phonon GStreamer backend Comment[da]=GStreamer-backend til Phonon Comment[de]=Phonon-Treiber für GStreamer Comment[el]=Σύστημα υποστήριξης GStreamer του Phonon +Comment[en_GB]=Phonon GStreamer backend Comment[es]=Motor GStreamer para Phonon Comment[et]=Phononi GStreameri taustaprogramm +Comment[eu]=Phonon GStreamer backend +Comment[fi]=Phonon GStreamer-taustaohjelma Comment[fr]=Système de gestion GStreamer pour Phonon Comment[ga]=Inneall GStreamer le haghaidh Phonon Comment[gl]=Infraestrutura de GStreamer para Phonon +Comment[hsb]=Phonon GStreamer backend +Comment[hu]=Phonon GStreamer modul +Comment[id]=Phonon GStreamer backend 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[lt]=Phonon GStreamer galinė sąsaja Comment[lv]=Phonon GStreamer aizmugure +Comment[nb]=Phonon-motor for GStreamer Comment[nds]=Phonon-Hülpprogramm GStreamer Comment[nl]=GStreamer-backend (Phonon) Comment[nn]=Phonon-motor for GStreamer @@ -39,9 +92,13 @@ 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[ru]=Механизм GStreamer для Phonon +Comment[se]=Phonon GStreamer duogášmohtor Comment[sk]=GStreamer podsystém Comment[sl]=Phononova hrbtenica GStreamer Comment[sr]=Гстример као позадина Фонона +Comment[sr@ijekavian]=Гстример као позадина Фонона +Comment[sr@ijekavianlatin]=GStreamer kao pozadina Phonona Comment[sr@latin]=GStreamer kao pozadina Phonona Comment[sv]=Phonon Gstreamer-gränssnitt Comment[tr]=Phonon GStreamer arka ucu diff --git a/src/3rdparty/phonon/gstreamer/medianode.cpp b/src/3rdparty/phonon/gstreamer/medianode.cpp index 7257972..1a84592 100644 --- a/src/3rdparty/phonon/gstreamer/medianode.cpp +++ b/src/3rdparty/phonon/gstreamer/medianode.cpp @@ -198,9 +198,9 @@ bool MediaNode::disconnectNode(QObject *obj) // 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); + gst_element_set_state(root()->pipeline(), GST_STATE_READY); - Q_ASSERT(sink->root()); //sink has to have a root since it is onnected + Q_ASSERT(sink->root()); //sink has to have a root since it is connected if (sink->description() & (AudioSink)) { GstPad *sinkPad = gst_element_get_pad(sink->audioElement(), "sink"); diff --git a/src/3rdparty/phonon/gstreamer/mediaobject.cpp b/src/3rdparty/phonon/gstreamer/mediaobject.cpp index b6d23ec..3e0addc 100644 --- a/src/3rdparty/phonon/gstreamer/mediaobject.cpp +++ b/src/3rdparty/phonon/gstreamer/mediaobject.cpp @@ -16,6 +16,7 @@ */ #include <cmath> #include <gst/interfaces/propertyprobe.h> +#include <gst/pbutils/install-plugins.h> #include "common.h" #include "mediaobject.h" #include "videowidget.h" @@ -53,6 +54,7 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) , m_tickTimer(new QTimer(this)) , m_prefinishMark(0) , m_transitionTime(0) + , m_isStream(false) , m_posAtSeek(-1) , m_prefinishMarkReachedNotEmitted(true) , m_aboutToFinishEmitted(false) @@ -79,6 +81,7 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) , m_autoplayTitles(true) , m_availableTitles(0) , m_currentTitle(1) + , m_pendingTitle(1) { qRegisterMetaType<GstCaps*>("GstCaps*"); qRegisterMetaType<State>("State"); @@ -95,8 +98,8 @@ MediaObject::MediaObject(Backend *backend, QObject *parent) 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))); + connect(this, SIGNAL(stateChanged(Phonon::State, Phonon::State)), + this, SLOT(notifyStateChange(Phonon::State, Phonon::State))); } @@ -136,6 +139,14 @@ QString stateString(const Phonon::State &state) return QString(); } +void +pluginInstallationDone( GstInstallPluginsReturn res, gpointer userData ) +{ + // Nothing inside yet + Q_UNUSED(res); + Q_UNUSED(userData); +} + void MediaObject::saveState() { //Only first resumeState is respected @@ -195,13 +206,35 @@ void MediaObject::noMorePadsAvailable () if (m_missingCodecs.size() > 0) { bool canPlay = (m_hasAudio || m_videoStreamFound); Phonon::ErrorType error = canPlay ? Phonon::NormalError : Phonon::FatalError; +#ifdef PLUGIN_INSTALL_API + GstInstallPluginsContext *ctx = gst_install_plugins_context_new (); + gchar *details[2]; + details[0] = m_missingCodecs[0].toLocal8Bit().data(); + details[1] = NULL; + GstInstallPluginsReturn status; + + status = gst_install_plugins_async( details, ctx, pluginInstallationDone, NULL ); + gst_install_plugins_context_free ( ctx ); + + if ( status != GST_INSTALL_PLUGINS_STARTED_OK ) + { + if( status == GST_INSTALL_PLUGINS_HELPER_MISSING ) + setError(QString(tr("Missing codec helper script assistant.")), Phonon::FatalError ); + else + setError(QString(tr("Plugin codec installation failed for codec: %0")) + .arg(m_missingCodecs[0].split("|")[3]), error); + } + m_missingCodecs.clear(); +#else + QString codecs = m_missingCodecs.join(", "); + 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(); +#endif } } @@ -248,7 +281,16 @@ void MediaObject::cb_unknown_type (GstElement *decodebin, GstPad *pad, GstCaps * value = QString::fromUtf8(gst_structure_get_name (str)); } - media->addMissingCodecName(value); + +#ifdef PLUGIN_INSTALL_API + QString plugins = QString("gstreamer|0.10|%0|%1|decoder-%2") + .arg( qApp->applicationName() ) + .arg( value ) + .arg( QString::fromUtf8(gst_caps_to_string (caps) ) ); + media->addMissingCodecName( plugins ); +#else + media->addMissingCodecName( value ); +#endif } static void notifyVideoCaps(GObject *obj, GParamSpec *, gpointer data) @@ -309,7 +351,7 @@ void MediaObject::connectVideo(GstPad *pad) 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); @@ -368,7 +410,10 @@ bool MediaObject::createPipefromURL(const QUrl &url) } // Create a new datasource based on the input URL - QByteArray encoded_cstr_url = url.toEncoded(); + // add the 'file' scheme if it's missing; the double '/' is needed! + QByteArray encoded_cstr_url = (url.scheme() == QLatin1String("") ? + "file://" + url.toEncoded() : + url.toEncoded()); m_datasource = gst_element_make_from_uri(GST_URI_SRC, encoded_cstr_url.constData(), (const char*)NULL); if (!m_datasource) return false; @@ -388,6 +433,14 @@ bool MediaObject::createPipefromURL(const QUrl &url) g_object_set (G_OBJECT (m_datasource), "read-speed", 2, (const char*)NULL); m_backend->logMessage(QString("new device speed : 2X"), Backend::Info, this); } + } + + /* make HTTP sources send extra headers so we get icecast + * metadata in case the stream is an icecast stream */ + if (encoded_cstr_url.startsWith("http://") + && g_object_class_find_property (G_OBJECT_GET_CLASS (m_datasource), "iradio-mode")) { + g_object_set (m_datasource, "iradio-mode", TRUE, NULL); + m_isStream = true; } // Link data source into pipeline @@ -442,7 +495,7 @@ void MediaObject::createPipeline() gst_object_ref (GST_OBJECT (m_pipeline)); gst_object_sink (GST_OBJECT (m_pipeline)); - m_decodebin = gst_element_factory_make ("decodebin", NULL); + m_decodebin = gst_element_factory_make ("decodebin2", 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); @@ -646,7 +699,7 @@ void MediaObject::setState(State newstate) 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) { + } else if (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); @@ -676,7 +729,7 @@ void MediaObject::changeState(State newstate) return; Phonon::State oldState = m_state; - m_state = newstate; // m_state must be set before emitting, since + 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); @@ -696,6 +749,8 @@ void MediaObject::changeState(State newstate) case Phonon::StoppedState: m_backend->logMessage("phonon state changed: Stopped", Backend::Info, this); + // We must reset the pipeline when playing again + m_resetNeeded = true; m_tickTimer->stop(); break; @@ -861,7 +916,7 @@ void MediaObject::setSource(const MediaSource &source) // 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); + gst_element_get_state(m_pipeline, &state, NULL, 2000); m_source = source; emit currentSourceChanged(m_source); @@ -871,7 +926,9 @@ void MediaObject::setSource(const MediaSource &source) // Go into to loading state changeState(Phonon::LoadingState); m_loading = true; - m_resetNeeded = false; + // IMPORTANT: Honor the m_resetNeeded flag as it currently stands. + // See https://qa.mandriva.com/show_bug.cgi?id=56807 + //m_resetNeeded = false; m_resumeState = false; m_pendingState = Phonon::StoppedState; @@ -884,8 +941,8 @@ void MediaObject::setSource(const MediaSource &source) // Clear any existing errors m_aboutToFinishEmitted = false; m_error = NoError; - m_errorString = QString(); - + m_errorString.clear(); + m_bufferPercent = 0; m_prefinishMarkReachedNotEmitted = true; m_aboutToFinishEmitted = false; @@ -894,22 +951,23 @@ void MediaObject::setSource(const MediaSource &source) setTotalTime(-1); m_atEndOfStream = false; - // Clear exising meta tags + m_availableTitles = 0; + m_pendingTitle = 1; + m_currentTitle = 1; + + // Clear existing meta tags m_metaData.clear(); + m_isStream = false; switch (source.type()) { - case MediaSource::Url: { - if (createPipefromURL(source.url())) - m_loading = true; - else + case MediaSource::Url: { + if (!createPipefromURL(source.url())) setError(tr("Could not open media source.")); } break; case MediaSource::LocalFile: { - if (createPipefromURL(QUrl::fromLocalFile(source.fileName()))) - m_loading = true; - else + if (!createPipefromURL(QUrl::fromLocalFile(source.fileName()))) setError(tr("Could not open media source.")); } break; @@ -922,17 +980,15 @@ void MediaObject::setSource(const MediaSource &source) break; case MediaSource::Stream: - if (createPipefromStream(source)) - m_loading = true; - else + if (!createPipefromStream(source)) setError(tr("Could not open media source.")); break; case MediaSource::Disc: { - QString mediaUrl; - switch (source.discType()) { - case Phonon::NoDisc: + QString mediaUrl; + switch (source.discType()) { + case Phonon::NoDisc: qWarning() << "I should never get to see a MediaSource that is a disc but doesn't specify which one"; return; case Phonon::Cd: // CD tracks can be specified by setting the url in the following way uri=cdda:4 @@ -948,9 +1004,7 @@ void MediaObject::setSource(const MediaSource &source) qWarning() << "media " << source.discType() << " not implemented"; return; } - if (!mediaUrl.isEmpty() && createPipefromURL(QUrl(mediaUrl))) - m_loading = true; - else + if (mediaUrl.isEmpty() || !createPipefromURL(QUrl(mediaUrl))) setError(tr("Could not open media source.")); } break; @@ -966,8 +1020,7 @@ void MediaObject::setSource(const MediaSource &source) // 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(); + link(); beginLoad(); } @@ -1004,22 +1057,22 @@ void MediaObject::getStreamInfo() 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)) { + if (m_source.discType() == Phonon::Cd) { + gint64 titleCount; + GstFormat format = gst_format_get_by_nick("track"); + if (gst_element_query_duration (m_pipeline, &format, &titleCount)) { //check if returned format is still "track", //gstreamer sometimes returns the total time, if tracks information is not available. - if (qstrcmp(gst_format_get_name(format), "track") == 0) { - 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); + if (qstrcmp(gst_format_get_name(format), "track") == 0) { + 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) @@ -1077,7 +1130,7 @@ void MediaObject::seek(qint64 time) } quint64 current = currentTime(); - quint64 total = totalTime(); + quint64 total = totalTime(); if (current < total - m_prefinishMark) m_prefinishMarkReachedNotEmitted = true; @@ -1098,7 +1151,7 @@ void MediaObject::emitTick() if (m_tickInterval > 0 && currentTime != m_previousTickTime) { emit tick(currentTime); - m_previousTickTime = currentTime; + m_previousTickTime = currentTime; } if (m_state == Phonon::PlayingState) { if (currentTime >= totalTime - m_prefinishMark) { @@ -1109,7 +1162,12 @@ void MediaObject::emitTick() } // Prepare load of next source if (currentTime >= totalTime - ABOUT_TO_FINNISH_TIME) { - if (!m_aboutToFinishEmitted) { + if (m_source.type() == MediaSource::Disc && + m_autoplayTitles && + m_availableTitles > 1 && + m_currentTitle < m_availableTitles) { + m_aboutToFinishEmitted = false; + } else if (!m_aboutToFinishEmitted) { m_aboutToFinishEmitted = true; // track is about to finish emit aboutToFinish(); } @@ -1213,8 +1271,8 @@ void MediaObject::handleBusMessage(const Message &message) switch (GST_MESSAGE_TYPE (gstMessage)) { - case GST_MESSAGE_EOS: - m_backend->logMessage("EOS recieved", Backend::Info, this); + case GST_MESSAGE_EOS: + m_backend->logMessage("EOS received", Backend::Info, this); handleEndOfStream(); break; @@ -1222,14 +1280,98 @@ void MediaObject::handleBusMessage(const Message &message) GstTagList* tag_list = 0; gst_message_parse_tag(gstMessage, &tag_list); if (tag_list) { + TagMap newTags; + gst_tag_list_foreach (tag_list, &foreach_tag_function, &newTags); + gst_tag_list_free(tag_list); + + // Determine if we should no fake the album/artist tags. + // This is a little confusing as we want to fake it on initial + // connection where title, album and artist are all missing. + // There are however times when we get just other information, + // e.g. codec, and so we want to only do clever stuff if we + // have a commonly available tag (ORGANIZATION) or we have a + // change in title + bool fake_it = + (m_isStream + && ((!newTags.contains("TITLE") + && newTags.contains("ORGANIZATION")) + || (newTags.contains("TITLE") + && m_metaData.value("TITLE") != newTags.value("TITLE"))) + && !newTags.contains("ALBUM") + && !newTags.contains("ARTIST")); + 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); + + // Now we've checked the new data, append any new meta tags to the existing tag list + // We cannot use TagMap::iterator as this is a multimap and when streaming data + // could in theory be lost. + QList<QString> keys = newTags.keys(); + for (QList<QString>::iterator i = keys.begin(); i != keys.end(); ++i) { + QString key = *i; + if (m_isStream) { + // If we're streaming, we need to remove data in m_metaData + // in order to stop it filling up indefinitely (as it's a multimap) + m_metaData.remove(key); + } + QList<QString> values = newTags.values(key); + for (QList<QString>::iterator j = values.begin(); j != values.end(); ++j) { + QString value = *j; + QString currVal = m_metaData.value(key); + if (!m_metaData.contains(key) || currVal != value) { + m_metaData.insert(key, value); + } + } + } + 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); - } + if (oldMap != m_metaData) { + // This is a bit of a hack to ensure that stream metadata is + // returned. We get as much as we can from the Shoutcast server's + // StreamTitle= header. If further info is decoded from the stream + // itself later, then it will overwrite this info. + if (m_isStream && fake_it) { + m_metaData.remove("ALBUM"); + m_metaData.remove("ARTIST"); + + // Detect whether we want to "fill in the blanks" + QString str; + if (m_metaData.contains("TITLE")) + { + str = m_metaData.value("TITLE"); + int splitpoint; + // Check to see if our title matches "%s - %s" + // Where neither %s are empty... + if ((splitpoint = str.indexOf(" - ")) > 0 + && str.size() > (splitpoint+3)) { + m_metaData.insert("ARTIST", str.left(splitpoint)); + m_metaData.replace("TITLE", str.mid(splitpoint+3)); + } + } else { + str = m_metaData.value("GENRE"); + if (!str.isEmpty()) + m_metaData.insert("TITLE", str); + else + m_metaData.insert("TITLE", "Streaming Data"); + } + if (!m_metaData.contains("ARTIST")) { + str = m_metaData.value("LOCATION"); + if (!str.isEmpty()) + m_metaData.insert("ARTIST", str); + else + m_metaData.insert("ARTIST", "Streaming Data"); + } + str = m_metaData.value("ORGANIZATION"); + if (!str.isEmpty()) + m_metaData.insert("ALBUM", str); + else + m_metaData.insert("ALBUM", "Streaming Data"); + } + // As we manipulate the title, we need to recompare + // oldMap and m_metaData here... + if (oldMap != m_metaData && !m_loading) + emit metaDataChanged(m_metaData); + } + } } break; @@ -1255,6 +1397,9 @@ void MediaObject::handleBusMessage(const Message &message) m_backend->logMessage("gstreamer: pipeline state set to playing", Backend::Info, this); m_tickTimer->start(); changeState(Phonon::PlayingState); + if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) { + setTrack(m_pendingTitle); + } if (m_resumeState && m_oldState == Phonon::PlayingState) { seek(m_oldPos); m_resumeState = false; @@ -1290,6 +1435,9 @@ void MediaObject::handleBusMessage(const Message &message) changeState(Phonon::StoppedState); m_backend->logMessage("gstreamer: pipeline state set to ready", Backend::Debug, this); m_tickTimer->stop(); + if ((m_source.type() == MediaSource::Disc) && (m_currentTitle != m_pendingTitle)) { + setTrack(m_pendingTitle); + } break; case GST_STATE_VOID_PENDING : @@ -1328,7 +1476,7 @@ void MediaObject::handleBusMessage(const Message &message) setError(err->message, Phonon::FatalError); gst_caps_unref (caps); gst_object_unref (sinkPad); - } + } } else { setError(QString(err->message), Phonon::FatalError); } @@ -1400,8 +1548,8 @@ void MediaObject::handleBusMessage(const Message &message) //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; + default: + break; } } @@ -1417,7 +1565,8 @@ void MediaObject::handleEndOfStream() if (!m_seekable) m_atEndOfStream = true; - if (m_autoplayTitles && + if (m_source.type() == MediaSource::Disc && + m_autoplayTitles && m_availableTitles > 1 && m_currentTitle < m_availableTitles) { _iface_setCurrentTitle(m_currentTitle + 1); @@ -1444,6 +1593,14 @@ void MediaObject::handleEndOfStream() } } +void MediaObject::invalidateGraph() +{ + m_resetNeeded = true; + if (m_state == Phonon::PlayingState || m_state == Phonon::PausedState) { + changeState(Phonon::StoppedState); + } +} + // Notifes the pipeline about state changes in the media object void MediaObject::notifyStateChange(Phonon::State newstate, Phonon::State oldstate) { @@ -1502,15 +1659,30 @@ int MediaObject::_iface_currentTitle() const 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)) + if ((title == m_currentTitle) || (title == m_pendingTitle)) + return; + + m_pendingTitle = title; + + if (m_state == Phonon::PlayingState || m_state == Phonon::StoppedState) { + setTrack(m_pendingTitle); + } else { + setState(Phonon::StoppedState); + } +} + +void MediaObject::setTrack(int title) +{ + if (((m_state != Phonon::PlayingState) && (m_state != Phonon::StoppedState)) || (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)) { + GstFormat trackFormat = gst_format_get_by_nick("track"); + m_backend->logMessage(QString("setTrack %0").arg(title), Backend::Info, this); + if (gst_element_seek_simple(m_pipeline, trackFormat, GST_SEEK_FLAG_FLUSH, title - 1)) { + m_currentTitle = title; updateTotalTime(); m_atEndOfStream = false; emit titleChanged(title); diff --git a/src/3rdparty/phonon/gstreamer/mediaobject.h b/src/3rdparty/phonon/gstreamer/mediaobject.h index 64b3510..d588ffc 100644 --- a/src/3rdparty/phonon/gstreamer/mediaobject.h +++ b/src/3rdparty/phonon/gstreamer/mediaobject.h @@ -55,6 +55,7 @@ class MediaObject : public QObject, public MediaObjectInterface , public MediaNode { friend class Stream; + friend class AudioDataOutput; Q_OBJECT Q_INTERFACES(Phonon::MediaObjectInterface #ifndef QT_NO_PHONON_MEDIACONTROLLER @@ -144,12 +145,8 @@ public: 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); - } - } + void invalidateGraph(); + 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); @@ -236,6 +233,7 @@ private: int _iface_availableTitles() const; int _iface_currentTitle() const; void _iface_setCurrentTitle(int title); + void setTrack(int title); bool m_resumeState; State m_oldState; @@ -250,6 +248,7 @@ private: MediaSource m_nextSource; qint32 m_prefinishMark; qint32 m_transitionTime; + bool m_isStream; qint64 m_posAtSeek; @@ -285,6 +284,7 @@ private: bool m_autoplayTitles; int m_availableTitles; int m_currentTitle; + int m_pendingTitle; }; } } //namespace Phonon::Gstreamer diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h index 73a494a..f83dba5 100644 --- a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h +++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.h @@ -19,6 +19,7 @@ #define Phonon_GSTREAMER_VIDEOSINK_H #include "common.h" +#include "qwidgetvideosink.h" #include <QtCore/QByteArray> #include <QtCore/QEvent> diff --git a/src/3rdparty/phonon/gstreamer/videowidget.h b/src/3rdparty/phonon/gstreamer/videowidget.h index dc0754d..8603f6a 100644 --- a/src/3rdparty/phonon/gstreamer/videowidget.h +++ b/src/3rdparty/phonon/gstreamer/videowidget.h @@ -25,6 +25,7 @@ #include "common.h" #include "medianode.h" #include "abstractrenderer.h" +#include "videowidget.h" #include <gst/gst.h> diff --git a/src/3rdparty/phonon/gstreamer/x11renderer.cpp b/src/3rdparty/phonon/gstreamer/x11renderer.cpp index 73877a8..968f3a8 100644 --- a/src/3rdparty/phonon/gstreamer/x11renderer.cpp +++ b/src/3rdparty/phonon/gstreamer/x11renderer.cpp @@ -90,7 +90,7 @@ GstElement* X11Renderer::createVideoSink() gst_object_unref(GST_OBJECT(videoSink)); videoSink = 0; } else { - // Note that this should not really be neccessary as these are + // Note that this should not really be necessary 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); @@ -138,6 +138,7 @@ void X11Renderer::scaleModeChanged(Phonon::VideoWidget::ScaleMode) void X11Renderer::movieSizeChanged(const QSize &movieSize) { Q_UNUSED(movieSize); + if (m_renderWidget) { m_renderWidget->setGeometry(m_videoWidget->calculateDrawFrameRect()); } |