File indexing completed on 2024-05-12 05:53:13
0001 /* 0002 * Copyright 2019 Eike Hein <hein@kde.org> 0003 * 0004 * This program is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU General Public License as 0006 * published by the Free Software Foundation; either version 2 of 0007 * the License or (at your option) version 3 or any later version 0008 * accepted by the membership of KDE e.V. (or its successor approved 0009 * by the membership of KDE e.V.), which shall act as a proxy 0010 * defined in Section 14 of version 3 of the license. 0011 * 0012 * This program is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 * GNU General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU General Public License 0018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include "gstreamerintegration.h" 0022 #include "videosurface.h" 0023 0024 #include <QDateTime> 0025 #include <QQuickItem> 0026 #include <QQuickWindow> 0027 #include <QRunnable> 0028 #include <QStandardPaths> 0029 0030 #include <gst/gst.h> 0031 #include <gst/gstcaps.h> 0032 #include <gst/gstsample.h> 0033 0034 Q_LOGGING_CATEGORY(videoLogging, "kirogi.videosuppoert.gstreamerintegration") 0035 0036 namespace 0037 { 0038 int pipelineWatcher(GstBus *bus, GstMessage *message, gpointer user_data) 0039 { 0040 Q_UNUSED(bus) 0041 auto videoController = qobject_cast<GStreamerIntegration *>(static_cast<QObject *>(user_data)); 0042 if (videoController) { 0043 videoController->processPipelineMessage(message); 0044 } 0045 return G_SOURCE_CONTINUE; 0046 } 0047 0048 } 0049 0050 GStreamerIntegration::GStreamerIntegration(QObject *parent) 0051 : QObject(parent) 0052 , m_playing(false) 0053 , m_gstPipeline(nullptr) 0054 , m_videoSink(nullptr) 0055 , m_fallback(true) 0056 { 0057 updateGstPipeline(); 0058 } 0059 0060 GStreamerIntegration::~GStreamerIntegration() 0061 { 0062 if (m_gstPipeline) { 0063 gst_element_set_state(m_gstPipeline, GST_STATE_NULL); 0064 } 0065 } 0066 0067 bool GStreamerIntegration::playing() const 0068 { 0069 return m_playing; 0070 } 0071 0072 void GStreamerIntegration::setPlaying(bool playing) 0073 { 0074 if (m_playing != playing) { 0075 m_playing = playing; 0076 0077 if (!m_gstPipeline) { 0078 updateGstPipeline(); 0079 } else if (m_gstPipeline->current_state == GST_STATE_PLAYING && !playing) { 0080 gst_element_set_state(m_gstPipeline, GST_STATE_PAUSED); 0081 } else if (m_gstPipeline->current_state != GST_STATE_PLAYING && playing) { 0082 updateGstPipeline(); 0083 } 0084 0085 Q_EMIT playingChanged(); 0086 } 0087 } 0088 0089 QString GStreamerIntegration::stringPipeline() const 0090 { 0091 return m_stringPipeline; 0092 } 0093 0094 void GStreamerIntegration::setStringPipeline(const QString &pipeline) 0095 { 0096 if (m_stringPipeline != pipeline) { 0097 m_stringPipeline = pipeline; 0098 0099 updateGstPipeline(); 0100 0101 Q_EMIT stringPipelineChanged(); 0102 } 0103 } 0104 0105 void GStreamerIntegration::updateGstPipeline() 0106 { 0107 QString pipeline = m_stringPipeline; 0108 0109 if (pipeline.isEmpty()) { 0110 qCDebug(videoLogging) << "Empty pipeline, using snow pattern."; 0111 pipeline = QStringLiteral("videotestsrc pattern=snow ! video/x-raw,width=800,height=450 !"); 0112 } 0113 pipeline += QStringLiteral("glupload ! glcolorconvert ! qmlglsink name=sink force-aspect-ratio=false"); 0114 0115 qCDebug(videoLogging) << "Using new pipeline:" << pipeline; 0116 0117 if (m_gstPipeline) { 0118 gst_element_set_state(m_gstPipeline, GST_STATE_NULL); 0119 gst_object_unref(m_gstPipeline); 0120 } 0121 0122 // If we fail to build the pipeline, we also fail to load the QML gst plugin 0123 // and the entire application will crash after that. 0124 GError *error = nullptr; 0125 m_gstPipeline = gst_parse_launch(pipeline.toLatin1().data(), &error); 0126 Q_ASSERT_X(m_gstPipeline, "gstreamer pipeline", QStringLiteral("%0 with pipeline: %1").arg(error->message).arg(pipeline).toStdString().c_str()); 0127 0128 m_videoSink = gst_bin_get_by_name(GST_BIN(m_gstPipeline), "sink"); 0129 Q_ASSERT_X(m_videoSink, "gstreamer video sink", "Could not retrieve the video sink."); 0130 0131 // Add watcher in pipeline to detect and notify if any problem is detected 0132 gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(m_gstPipeline)), &pipelineWatcher, this); 0133 } 0134 0135 void GStreamerIntegration::takeSnapshot() 0136 { 0137 if (!m_videoSink) { 0138 qCDebug(videoLogging) << "No video, exiting"; 0139 return; 0140 } 0141 0142 GstSample *videobuffer = nullptr; 0143 g_object_get(G_OBJECT(m_videoSink), "last-sample", &videobuffer, nullptr); 0144 if (!videobuffer) { 0145 qCDebug(videoLogging) << "No video buffer, exiting"; 0146 return; 0147 } 0148 0149 GstCaps *capsStructure = gst_sample_get_caps(videobuffer); 0150 if (!capsStructure) { 0151 qCDebug(videoLogging) << "Can't get caps from video buffer, exiting"; 0152 return; 0153 } 0154 0155 GstStructure *s = gst_caps_get_structure(capsStructure, 0); 0156 if (!s) { 0157 qCDebug(videoLogging) << "Could not get the structure from the caps for the video."; 0158 return; 0159 } 0160 gint width, height; 0161 0162 gboolean res = gst_structure_get_int(s, "width", &width) && gst_structure_get_int(s, "height", &height); 0163 if (!res) { 0164 qCDebug(videoLogging) << "Can't get width or height from caps"; 0165 return; 0166 } 0167 0168 const gchar *format = gst_structure_get_string(s, "format"); 0169 if (!format) { 0170 qCDebug(videoLogging) << "Could not get the video format."; 0171 return; 0172 } 0173 0174 GstBuffer *snapbuffer = gst_sample_get_buffer(videobuffer); 0175 if (!snapbuffer) { 0176 qCDebug(videoLogging) << "Can't get the sample buffer from the video"; 0177 return; 0178 } 0179 0180 if (GST_BUFFER_FLAG_IS_SET(snapbuffer, GST_BUFFER_FLAG_CORRUPTED)) { 0181 qCDebug(videoLogging) << "Buffer is corrupted, ignoring screenshoot"; 0182 } 0183 0184 GstMapInfo map{}; 0185 gst_buffer_map(snapbuffer, &map, GST_MAP_READ); 0186 0187 uchar *bufferData = reinterpret_cast<uchar *>(map.data); 0188 QImage::Format imageFormat{}; 0189 if (g_str_equal(format, "RGBA")) { 0190 imageFormat = QImage::Format_RGB32; 0191 } else { 0192 qCDebug(videoLogging) << "Video Format unrecognized, ignoring screenshoot"; 0193 return; 0194 } 0195 0196 QImage image(bufferData, width, height, imageFormat); 0197 0198 QString savePath; // TODO: Add a setting on KConfigXT 0199 if (savePath.isEmpty()) { 0200 savePath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); 0201 } 0202 savePath += "/" + QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_hh.mm.ss")) + ".png"; 0203 0204 if (image.save(savePath)) { 0205 qCDebug(videoLogging) << "Image saved in" << savePath; 0206 } else { 0207 qCDebug(videoLogging) << "Error saving image in" << savePath << "please verify your access rights"; 0208 } 0209 } 0210 0211 void GStreamerIntegration::processPipelineMessage(GstMessage *message) 0212 { 0213 QString string; 0214 GError *gerror = nullptr; 0215 0216 switch (message->type) { 0217 case GST_MESSAGE_ERROR: 0218 gst_message_parse_error(message, &gerror, nullptr); 0219 string = gerror->message; 0220 break; 0221 0222 case GST_MESSAGE_WARNING: 0223 gst_message_parse_warning(message, &gerror, nullptr); 0224 string = gerror->message; 0225 break; 0226 0227 case GST_MESSAGE_INFO: 0228 gst_message_parse_info(message, &gerror, nullptr); 0229 string = gerror->message; 0230 break; 0231 0232 default: 0233 break; 0234 } 0235 0236 if (!string.isEmpty()) { 0237 qCDebug(videoLogging) << "From:" << GST_MESSAGE_SRC_NAME(message) << "event type" << GST_MESSAGE_TYPE_NAME(message) << "happened with" << string; 0238 } 0239 0240 if (gerror) { 0241 g_error_free(gerror); 0242 } 0243 } 0244 0245 void GStreamerIntegration::init() 0246 { 0247 // GStreamer needs the sink to be created before any Qml elements 0248 // so that the Qml elements are registered in the system. so we create 0249 // and free it. 0250 GstElement *sink = gst_element_factory_make("qmlglsink", nullptr); 0251 Q_ASSERT_X(sink, "Video Initialization", "Could not find the Qml Gl Sink GStreamer Plugin, please check your installation."); 0252 0253 gst_object_unref(sink); 0254 0255 qmlRegisterType<GStreamerIntegration>("org.kde.kirogi.video", 0, 1, "VideoReceiver"); 0256 qmlRegisterType<VideoSurface>("org.kde.kirogi.video", 0, 1, "VideoSurface"); 0257 }