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