1 /* EngineUtils.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "EngineUtils.h"
22 #include "Utils/Logger/Logger.h"
23
24 #include <QString>
25 #include <QStringList>
26
27 #include <gst/base/gstbasetransform.h>
28
29 struct TeeProbeData
30 {
31 GstState state;
32 GstElement* element;
33 };
34
35 static GstPadProbeReturn
teeProbleBlocked(GstPad * pad,GstPadProbeInfo * info,gpointer p)36 teeProbleBlocked(GstPad* pad, GstPadProbeInfo* info, gpointer p)
37 {
38 TeeProbeData* data = static_cast<TeeProbeData*>(p);
39 GstElement* queue = data->element;
40
41 if(!Engine::Utils::testAndError(queue, "Connect to tee: Element is not GstElement")){
42 delete data; data = nullptr;
43 return GST_PAD_PROBE_DROP;
44 }
45
46 GstPad* queue_pad = gst_element_get_static_pad(queue, "sink");
47 if(!Engine::Utils::testAndError(queue_pad, "Connect to tee: No valid pad from GstElement")){
48 delete data; data = nullptr;
49 return GST_PAD_PROBE_DROP;
50 }
51
52 GstPadLinkReturn s = gst_pad_link(pad, queue_pad);
53 if(s != GST_PAD_LINK_OK) {
54 spLog(Log::Warning, "AbstractPipeline") << "Could not dynamically connect tee";
55 }
56
57
58 gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));
59 gst_element_set_state(queue, data->state);
60
61 delete data; data = nullptr;
62 return GST_PAD_PROBE_DROP;
63 }
64
connectTee(GstElement * tee,GstElement * queue,const QString & queue_name)65 bool Engine::Utils::connectTee(GstElement* tee, GstElement* queue, const QString& queue_name)
66 {
67 if(!testAndError(tee, "tee connect: tee is null")){
68 return false;
69 }
70
71 if(!testAndError(queue, "tee connect: queue is null")){
72 return false;
73 }
74
75 QString error_1 = QString("Engine: Tee-") + queue_name + " pad is nullptr";
76 QString error_2 = QString("Engine: ") + queue_name + " pad is nullptr";
77 QString error_3 = QString("Engine: Cannot link tee with ") + queue_name;
78
79 GstPadTemplate* tee_src_pad_template = gst_element_class_get_pad_template(GST_ELEMENT_GET_CLASS(tee), "src_%u");
80 if(!testAndError(tee_src_pad_template, "Engine: _tee_src_pad_template is nullptr")) {
81 return false;
82 }
83
84 GstPad* tee_queue_pad = gst_element_request_pad(tee, tee_src_pad_template, nullptr, nullptr);
85 if(!testAndError(tee_queue_pad, error_1)){
86 return false;
87 }
88
89 GstState state = Engine::Utils::getState(tee);
90
91 if(state == GST_STATE_PLAYING || state == GST_STATE_PAUSED)
92 {
93 TeeProbeData* data = new TeeProbeData();
94 data->state = state;
95 data->element = queue;
96
97 gulong id = gst_pad_add_probe(tee_queue_pad,
98 GST_PAD_PROBE_TYPE_IDLE,
99 teeProbleBlocked,
100 data,
101 nullptr);
102
103 Q_UNUSED(id)
104
105 return true;
106 }
107
108 GstPad* queue_pad = gst_element_get_static_pad(queue, "sink");
109 if(!testAndError(queue_pad, error_2)) {
110 return false;
111 }
112
113 GstPadLinkReturn s = gst_pad_link (tee_queue_pad, queue_pad);
114 if(!testAndErrorBool((s == GST_PAD_LINK_OK), error_3)) {
115 return false;
116 }
117
118 setState(queue, getState(tee));
119
120 gst_object_unref(tee_queue_pad);
121 gst_object_unref(queue_pad);
122 return true;
123 }
124
125
hasElement(GstBin * bin,GstElement * element)126 bool Engine::Utils::hasElement(GstBin* bin, GstElement* element)
127 {
128 if(!bin || !element){
129 return true;
130 }
131
132 if(!GST_OBJECT(element) || !GST_OBJECT(bin)){
133 return false;
134 }
135
136 Engine::Utils::GStringAutoFree element_name(gst_element_get_name(element));
137 Engine::Utils::GStringAutoFree bin_name(gst_object_get_name(GST_OBJECT(bin)));
138
139 if(element_name.data() == nullptr || bin_name.data() == nullptr)
140 {
141 return false;
142 }
143
144 if(strncmp(element_name.data(), bin_name.data(), 40) == 0){
145 return true;
146 }
147
148 GstObject* parent = gst_object_get_parent(GST_OBJECT(element));
149
150 while(parent != nullptr)
151 {
152 Engine::Utils::GStringAutoFree parent_name(gst_object_get_name(parent));
153
154 if(strncmp(bin_name.data(), parent_name.data(), 50) == 0)
155 {
156 return true;
157 }
158
159 auto* old_parent = parent;
160 parent = gst_object_get_parent(old_parent);
161 gst_object_unref(old_parent);
162 }
163
164 return false;
165 }
166
167
168
testAndError(void * element,const QString & errorstr)169 bool Engine::Utils::testAndError(void* element, const QString& errorstr)
170 {
171 if(!element) {
172 spLog(Log::Error, "Engine::Utils") << errorstr;
173 return false;
174 }
175
176 return true;
177 }
178
testAndErrorBool(bool b,const QString & errorstr)179 bool Engine::Utils::testAndErrorBool(bool b, const QString& errorstr)
180 {
181 if(!b) {
182 spLog(Log::Error, "Engine::Utils") << errorstr;
183 return false;
184 }
185
186 return true;
187 }
188
createElement(GstElement ** elem,const QString & elem_name)189 bool Engine::Utils::createElement(GstElement** elem, const QString& elem_name)
190 {
191 return createElement(elem, elem_name, QString());
192 }
193
createElement(GstElement ** elem,const QString & elem_name,const QString & prefix)194 bool Engine::Utils::createElement(GstElement** elem, const QString& elem_name, const QString& prefix)
195 {
196 gchar* g_elem_name = g_strdup(elem_name.toLocal8Bit().data());
197
198 QString error_msg;
199 if(prefix.size() > 0)
200 {
201 QString prefixed = prefix + "_" + elem_name;
202 gchar* g_prefixed = g_strdup(prefixed.toLocal8Bit().data());
203 *elem = gst_element_factory_make(g_elem_name, g_prefixed);
204
205 error_msg = QString("Engine: ") + prefixed + " creation failed";
206 g_free(g_prefixed);
207 }
208
209 else{
210 *elem = gst_element_factory_make(g_elem_name, g_elem_name);
211 error_msg = QString("Engine: ") + elem_name + " creation failed";
212 }
213
214 setState(*elem, GST_STATE_NULL);
215
216 g_free(g_elem_name);
217
218 return testAndError(*elem, error_msg);
219 }
220
getDurationMs(GstElement * element)221 MilliSeconds Engine::Utils::getDurationMs(GstElement* element)
222 {
223 if(!element){
224 return -1;
225 }
226
227 NanoSeconds pos;
228 bool success = gst_element_query_duration(element, GST_FORMAT_TIME, &pos);
229 if(!success){
230 return -1;
231 }
232
233 return GST_TIME_AS_MSECONDS(pos);
234 }
235
getPositionMs(GstElement * element)236 MilliSeconds Engine::Utils::getPositionMs(GstElement* element)
237 {
238 if(!element){
239 return -1;
240 }
241
242 NanoSeconds pos;
243 bool success = gst_element_query_position(element, GST_FORMAT_TIME, &pos);
244 if(!success){
245 return -1;
246 }
247
248 return GST_TIME_AS_MSECONDS(pos);
249 }
250
251
getTimeToGo(GstElement * element)252 MilliSeconds Engine::Utils::getTimeToGo(GstElement* element)
253 {
254 if(!element){
255 return -1;
256 }
257
258 MilliSeconds pos = getPositionMs(element);
259 if(pos < 0){
260 return getDurationMs(element);
261 }
262
263 MilliSeconds dur = getDurationMs(element);
264 if(dur < 0){
265 return -1;
266 }
267
268 if(dur < pos){
269 return -1;
270 }
271
272 return dur - pos;
273 }
274
getState(GstElement * element)275 GstState Engine::Utils::getState(GstElement* element)
276 {
277 if(!element){
278 return GST_STATE_NULL;
279 }
280
281 GstState state;
282 gst_element_get_state(element, &state, nullptr, GST_MSECOND * 10);
283 return state;
284 }
285
setState(GstElement * element,GstState state)286 bool Engine::Utils::setState(GstElement* element, GstState state)
287 {
288 if(!element){
289 return false;
290 }
291
292 GstStateChangeReturn ret = gst_element_set_state(element, state);
293 return (ret != GST_STATE_CHANGE_FAILURE);
294 }
295
296
isPluginAvailable(const gchar * str)297 bool Engine::Utils::isPluginAvailable(const gchar* str)
298 {
299 GstRegistry* reg = gst_registry_get();
300 GstPlugin* plugin = gst_registry_find_plugin(reg, str);
301
302 bool success = (plugin != nullptr);
303 gst_object_unref(plugin);
304
305 return success;
306 }
307
isPitchAvailable()308 bool Engine::Utils::isPitchAvailable()
309 {
310 return isPluginAvailable("soundtouch");
311 }
312
isLameAvailable()313 bool Engine::Utils::isLameAvailable()
314 {
315 return isPluginAvailable("lame");
316 }
317
createGhostPad(GstBin * bin,GstElement * e)318 bool Engine::Utils::createGhostPad(GstBin* bin, GstElement* e)
319 {
320 GstPad* pad = gst_element_get_static_pad(e, "sink");
321 if(!testAndError(pad, "CreateGhostPad: Cannot get static pad")){
322 return false;
323 }
324
325 GstPad* ghost_pad = gst_ghost_pad_new("sink", pad);
326 if(!testAndError(ghost_pad, "CreateGhostPad: Cannot create ghost pad")){
327 return false;
328 }
329
330 gst_pad_set_active(ghost_pad, true);
331 bool success = gst_element_add_pad(GST_ELEMENT(bin), ghost_pad);
332 if(!testAndErrorBool(success, "CreateGhostPad: Cannot add ghost pad")){
333 return false;
334 }
335
336 gst_object_unref(pad);
337 return true;
338 }
339
createBin(GstElement ** bin,const QList<GstElement * > & elements,const QString & prefix)340 bool Engine::Utils::createBin(GstElement** bin, const QList<GstElement*>& elements, const QString& prefix)
341 {
342 QString prefixed = prefix + "bin";
343 gchar* g_name = g_strdup(prefixed.toLocal8Bit().data());
344 *bin = gst_bin_new(g_name);
345 if(!testAndError(*bin, "Cannot create bin " + prefixed)){
346 return false;
347 }
348
349 addElements(GST_BIN(*bin), elements);
350 bool success = linkElements(elements);
351 if(!success) {
352 unrefElements(elements);
353 gst_object_unref(bin);
354 *bin = nullptr;
355 return false;
356 }
357
358 GstElement* e = elements.first();
359 success = createGhostPad(GST_BIN(*bin), e);
360 if(!success){
361 unrefElements(elements);
362 gst_object_unref(bin);
363 *bin = nullptr;
364 return false;
365 }
366
367 gst_object_ref(*bin);
368
369 return true;
370 }
371
linkElements(const QList<GstElement * > & elements)372 bool Engine::Utils::linkElements(const QList<GstElement*>& elements)
373 {
374 bool success = true;
375 for(int i=0; i<elements.size() - 1; i++)
376 {
377 GstElement* e1 = elements.at(i);
378 GstElement* e2 = elements.at(i+1);
379
380 if(!e2) {
381 break;
382 }
383
384 gchar* n1 = gst_element_get_name(e1);
385 gchar* n2 = gst_element_get_name(e2);
386
387 spLog(Log::Debug, "Engine::Utils") << "Try to link " << n1 << " with " << n2;
388
389 bool b = gst_element_link(e1, e2);
390 if(!b)
391 {
392 testAndErrorBool(b, QString("Cannot link element %1 with %2").arg(n1, n2));
393 g_free(n1);
394 g_free(n2);
395 success = false;
396 break;
397 }
398 }
399
400 return success;
401 }
402
unlinkElements(const Engine::Utils::Elements & elements)403 void Engine::Utils::unlinkElements(const Engine::Utils::Elements& elements)
404 {
405 for(int i=0; i<elements.size() - 1; i++)
406 {
407 GstElement* e1 = elements.at(i);
408 GstElement* e2 = elements.at(i+1);
409
410 if(!e2)
411 {
412 break;
413 }
414
415 gchar* n1 = gst_element_get_name(e1);
416 gchar* n2 = gst_element_get_name(e2);
417
418 spLog(Log::Debug, "Engine::Utils") << "Try to unlink " << n1 << " with " << n2;
419
420 gst_element_unlink(e1, e2);
421 g_free(n1);
422 g_free(n2);
423 }
424 }
425
426
addElements(GstBin * bin,const QList<GstElement * > & elements)427 bool Engine::Utils::addElements(GstBin* bin, const QList<GstElement*>& elements)
428 {
429 bool b = true;
430 for(GstElement* e : elements)
431 {
432 if(!e || hasElement(bin, e)){
433 continue;
434 }
435
436 b = (b && gst_bin_add(bin, e));
437 if(!b)
438 {
439 break;
440 }
441 }
442
443 return b;
444 }
445
446
removeElements(GstBin * bin,const Engine::Utils::Elements & elements)447 void Engine::Utils::removeElements(GstBin* bin, const Engine::Utils::Elements& elements)
448 {
449 for(GstElement* e : elements)
450 {
451 if(!e || !hasElement(bin, e))
452 {
453 continue;
454 }
455
456 bool success = gst_bin_remove(bin, e);
457 if(!success)
458 {
459 gchar* name = gst_element_get_name(e);
460 spLog(Log::Warning, "Engine::Utils") << "Could not remove element " << name;
461 g_free(name);
462 }
463 }
464 }
465
466
unrefElements(const QList<GstElement * > & elements)467 void Engine::Utils::unrefElements(const QList<GstElement*>& elements)
468 {
469 for(GstElement* e : elements){
470 gst_object_unref(e);
471 }
472 }
473
configureQueue(GstElement * queue,guint64 max_time_ms)474 void Engine::Utils::configureQueue(GstElement* queue, guint64 max_time_ms)
475 {
476 setValues(queue,
477 "flush-on-eos", true,
478 "silent", true);
479
480 setUint64Value(queue, "max-size-time", guint64(max_time_ms * GST_MSECOND));
481 }
482
configureSink(GstElement * sink)483 void Engine::Utils::configureSink(GstElement* sink)
484 {
485 setValues(sink,
486 "sync", true,
487 "async", false);
488 }
489
configureLame(GstElement * lame)490 void Engine::Utils::configureLame(GstElement* lame)
491 {
492 setValues(lame,
493 "perfect-timestamp", true,
494 "cbr", true
495 );
496
497 setIntValue(lame, "bitrate", 128);
498 setIntValue(lame, "target", 1);
499 setIntValue(lame, "encoding-engine-quality", 2);
500 }
501
502
setPassthrough(GstElement * e,bool b)503 void Engine::Utils::setPassthrough(GstElement* e, bool b)
504 {
505 if(e && GST_IS_BASE_TRANSFORM(e))
506 {
507 gst_base_transform_set_passthrough(GST_BASE_TRANSFORM(e), b);
508 gst_base_transform_set_prefer_passthrough(GST_BASE_TRANSFORM(e), b);
509 }
510 }
511
512
getInt64(gint64 value)513 GValue Engine::Utils::getInt64(gint64 value)
514 {
515 GValue ret = G_VALUE_INIT;
516 g_value_init(&ret, G_TYPE_INT64);
517 g_value_set_int64(&ret, value);
518 return ret;
519 }
520
getUint64(guint64 value)521 GValue Engine::Utils::getUint64(guint64 value)
522 {
523 GValue ret = G_VALUE_INIT;
524 g_value_init(&ret, G_TYPE_UINT64);
525 g_value_set_uint64(&ret, value);
526 return ret;
527 }
528
getUint(guint value)529 GValue Engine::Utils::getUint(guint value)
530 {
531 GValue ret = G_VALUE_INIT;
532 g_value_init(&ret, G_TYPE_UINT);
533 g_value_set_uint(&ret, value);
534 return ret;
535 }
536
getInt(gint value)537 GValue Engine::Utils::getInt(gint value)
538 {
539 GValue ret = G_VALUE_INIT;
540 g_value_init(&ret, G_TYPE_INT);
541 g_value_set_int(&ret, value);
542 return ret;
543 }
544
getUpdateInterval()545 MilliSeconds Engine::Utils::getUpdateInterval()
546 {
547 return 50;
548 }
549