audio - significa - dinámicamente(des) elementos de enlace en una tubería en ejecución(gstreamer)?
seo imagenes wordpress (5)
hay muchos ejemplos en la documentación de gstreamer sobre la construcción y ejecución de tuberías estáticas. sin embargo, no hay mucho sobre cambiar / volver a unir elementos en una tubería en vivo , mientras que los medios realmente fluyen. Definitivamente es posible, entonces la pregunta es:
- ¿Qué conceptos / mecánicos de Gstreamer debo entender antes de intentar esto?
- ¿Hay algún escollo que vigilar?
- ¿Cuál es el procedimiento básico o un buen ejemplo?
la respuesta aceptada será alimentada con cuchara, completa y con el código fuente
En realidad estoy tratando de hacer lo mismo. No mucha suerte todavía :(
Obtuve el siguiente enlace preguntando en el canal de IRC #gstreamer: http://cgit.freedesktop.org/gstreamer/gstreamer/tree/docs/design/part-dynamic.txt
Tal vez una pista en la dirección correcta.
Por favor, hágamelo saber cuando encuentre otra documentación ...
Esta publicación apareció primero cuando busqué modificar dinámicamente cualquier tubería gstreamer. Encontramos algunos enlaces, pero ahora está bien documentado en el manual: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/section-dynamic-pipelines.html
Tiendo a usar el selector de salida o las cajas de selección de entrada dependiendo de la situación en lugar de la complejidad de bloqueo de la pastilla (he respondido el bloqueo de la pastilla en otra publicación http://gstreamer-devel.966125.n4.nabble.com/Dynamically-adding- and-removing-branches-of-a-tee-td973635.html # a4656812 ). Y conecte el selector a los contenedores fakesrc o fakesink cuando no esté en uso. En el ejemplo siguiente, si uno está utilizando GTK, entonces uno puede reemplazar la línea g_timeout_add (SWITCH_TIMEOUT, switch_cb, osel);
con gtk_toggle_button
y ponga todo el código actualmente en la función switch_cb
en la función de devolución de llamada del botón de alternancia. En este código uno puede cambiar entre los dos enlaces de imagen. Reemplazaría un receptor de imagen con fakesink para mantener la tubería en funcionamiento, en caso de que quiera agregar un tee en el futuro con un archivo donde quiero grabar video y ofrecer al reproductor una opción para encender (selector en imágenes) / apagado (selector en fakesink) la pantalla. Esto permite agregar / eliminar contenedores en tiempo de ejecución usando el selector.
#include <gst/gst.h>
#define SWITCH_TIMEOUT 1000
#define NUM_VIDEO_BUFFERS 500
static GMainLoop *loop;
/* Output selector src pads */
static GstPad *osel_src1 = NULL;
static GstPad *osel_src2 = NULL;
static gboolean
my_bus_callback (GstBus * bus, GstMessage * message, gpointer data)
{
g_print ("Got %s message/n", GST_MESSAGE_TYPE_NAME (message));
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ERROR:{
GError *err;
gchar *debug;
gst_message_parse_error (message, &err, &debug);
g_print ("Error: %s/n", err->message);
g_error_free (err);
g_free (debug);
g_main_loop_quit (loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
g_main_loop_quit (loop);
break;
default:
/* unhandled message */
break;
}
/* we want to be notified again the next time there is a message
* on the bus, so returning TRUE (FALSE means we want to stop watching
* for messages on the bus and our callback should not be called again)
*/
return TRUE;
}
static gboolean
switch_cb (gpointer user_data)
{
GstElement *sel = GST_ELEMENT (user_data);
GstPad *old_pad, *new_pad = NULL;
g_object_get (G_OBJECT (sel), "active-pad", &old_pad, NULL);
if (old_pad == osel_src1)
new_pad = osel_src2;
else
new_pad = osel_src1;
g_object_set (G_OBJECT (sel), "active-pad", new_pad, NULL);
g_print ("switched from %s:%s to %s:%s/n", GST_DEBUG_PAD_NAME (old_pad),
GST_DEBUG_PAD_NAME (new_pad));
gst_object_unref (old_pad);
return TRUE;
}
gint
main (gint argc, gchar * argv[])
{
GstElement *pipeline, *src, *toverlay, *osel, *sink1, *sink2, *convert;
GstPad *sinkpad1;
GstPad *sinkpad2;
GstBus *bus;
/* init GStreamer */
gst_init (&argc, &argv);
loop = g_main_loop_new (NULL, FALSE);
/* create elements */
pipeline = gst_element_factory_make ("pipeline", "pipeline");
src = gst_element_factory_make ("videotestsrc", "src");
toverlay = gst_element_factory_make ("timeoverlay", "timeoverlay");
osel = gst_element_factory_make ("output-selector", "osel");
convert = gst_element_factory_make ("ffmpegcolorspace", "convert");
sink1 = gst_element_factory_make ("xvimagesink", "sink1");
sink2 = gst_element_factory_make ("ximagesink", "sink2");
if (!pipeline || !src || !toverlay || !osel || !convert || !sink1 || !sink2) {
g_print ("missing element/n");
return -1;
}
/* add them to bin */
gst_bin_add_many (GST_BIN (pipeline), src, toverlay, osel, convert, sink1,
sink2, NULL);
/* set properties */
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
g_object_set (G_OBJECT (src), "do-timestamp", TRUE, NULL);
g_object_set (G_OBJECT (src), "num-buffers", NUM_VIDEO_BUFFERS, NULL);
g_object_set (G_OBJECT (sink1), "sync", FALSE, "async", FALSE, NULL);
g_object_set (G_OBJECT (sink2), "sync", FALSE, "async", FALSE, NULL);
g_object_set (G_OBJECT (osel), "resend-latest", TRUE, NULL);
/* link src ! timeoverlay ! osel */
if (!gst_element_link_many (src, toverlay, osel, NULL)) {
g_print ("linking failed/n");
return -1;
}
/* link output 1 */
sinkpad1 = gst_element_get_static_pad (sink1, "sink");
osel_src1 = gst_element_get_request_pad (osel, "src%d");
if (gst_pad_link (osel_src1, sinkpad1) != GST_PAD_LINK_OK) {
g_print ("linking output 1 failed/n");
return -1;
}
gst_object_unref (sinkpad1);
/* link output 2 */
sinkpad2 = gst_element_get_static_pad (convert, "sink");
osel_src2 = gst_element_get_request_pad (osel, "src%d");
if (gst_pad_link (osel_src2, sinkpad2) != GST_PAD_LINK_OK) {
g_print ("linking output 2 failed/n");
return -1;
}
gst_object_unref (sinkpad2);
if (!gst_element_link (convert, sink2)) {
g_print ("linking output 2 failed/n");
return -1;
}
/* add switch callback */
g_timeout_add (SWITCH_TIMEOUT, switch_cb, osel);
/* change to playing */
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
gst_bus_add_watch (bus, my_bus_callback, loop);
gst_object_unref (bus);
gst_element_set_state (pipeline, GST_STATE_PLAYING);
/* now run */
g_main_loop_run (loop);
/* also clean up */
gst_element_set_state (pipeline, GST_STATE_NULL);
gst_element_release_request_pad (osel, osel_src1);
gst_element_release_request_pad (osel, osel_src2);
gst_object_unref (GST_OBJECT (pipeline));
return 0;
}
No logré crear archivos mux completos legibles para Gstreamer 0.10 en la parte superior del multifilesink o selector de salida .
Después de analizar muchas alternativas, mi solución toma como base de código el ejemplo que se muestra en: http://gstreamer.freedesktop.org/data/doc/gstreamer/head/manual/html/section-dynamic-pipelines.html
La API de la función de sondeos ha cambiado un poco de 0.10 a 1.0, pero la siguiente solución funciona para crear cada N segundos archivos MP4 diferentes:
static GstElement *pipeline = NULL;
// Pipeline -> src -> dynamic pipeline
// Pipeline -> capsfilter(f264file) -> mp4mux(mux0) -> filesink(fsink0)
// Pipeline -> elem_before||blockpad| -> |elem_cur_sinkpad||elem_cur||elem_cur_srcpad -> |elem_after_sinkpad||elem_after
static gulong probe_id; // probe ID
static GstElement *elem_before; // SRC of dynamic pipeline
static GstElement *elem_after; // SINK of dynamic pipeline
static GstElement *elem_cur; // Main element of dynamic pipeline
static GstPad *blockpad; // SRC pad to be blocked
static GstPad *elem_cur_srcpad; // SRC pad where check EOS
static GstPad *elem_cur_sinkpad; // SINK of dynamic pipeline
static GstPad *elem_after_sinkpad; // SINK of SINK element
// Last Buffer Timestamp
static GstClockTime last_ts = 0;
typedef enum {
NO_NEW_FILE, // Keep current file destination
NEW_FILE, // Switch file destination
} NewFileStatus;
static NewFileStatus newfile = NO_NEW_FILE; // Switch File Flag
static int counter = 1; // Index filename
// EOS listener to switch to other file destination
static gboolean
event_probe_cb (GstPad * pad, GstEvent * event, gpointer user_data)
{
g_print ("INSIDE event_probe_cb:%d type:%s/n",probe_id,
GST_EVENT_TYPE (event)==GST_EVENT_EOS?"EOS":GST_EVENT_TYPE (event)==GST_EVENT_NEWSEGMENT?"NEWSEGMENT":"OTHER");
if (GST_EVENT_TYPE (event) != GST_EVENT_EOS)
{
// Push the event in the pipe flow (false DROP)
return TRUE;
}
// remove the probe first
gst_pad_remove_event_probe (pad, probe_id);
gst_object_unref (elem_cur_srcpad);
gst_object_unref (elem_after_sinkpad);
gst_element_release_request_pad(elem_cur, elem_cur_sinkpad);
gst_element_set_state (elem_cur, GST_STATE_NULL);
gst_element_set_state (elem_after, GST_STATE_NULL);
// remove unlinks automatically
GST_DEBUG_OBJECT (pipeline, "removing %" GST_PTR_FORMAT, elem_cur);
gst_bin_remove (GST_BIN (pipeline), elem_cur);
GST_DEBUG_OBJECT (pipeline, "removing %" GST_PTR_FORMAT, elem_after);
gst_bin_remove (GST_BIN (pipeline), elem_after);
GstElement * mux0 = gst_element_factory_make("mp4mux", "mux0");
GstElement * fsink0 = gst_element_factory_make("filesink", "fsink0");
elem_cur = mux0;
elem_after = fsink0;
if(!mux0 || !fsink0)
{
printf("mising elements/n");
}
GST_DEBUG_OBJECT (pipeline, "adding %" GST_PTR_FORMAT, elem_cur);
gst_bin_add (GST_BIN (pipeline), elem_cur);
GST_DEBUG_OBJECT (pipeline, "adding %" GST_PTR_FORMAT, elem_after);
gst_bin_add (GST_BIN (pipeline), elem_after);
char buffer[128];
sprintf(buffer, "test_%d.mp4", counter++);
g_print ("File Switching %s/n", buffer);
g_object_set(G_OBJECT(elem_after), "location", buffer, NULL);
GST_DEBUG_OBJECT (pipeline, "linking..");
elem_cur_srcpad = gst_element_get_static_pad (elem_cur, "src");
elem_cur_sinkpad = gst_element_get_request_pad (elem_cur, "video_%d");
elem_after_sinkpad = gst_element_get_static_pad (elem_after, "sink");
if(gst_pad_link(blockpad, elem_cur_sinkpad) != GST_PAD_LINK_OK)
{
printf("linking output 0 failed/n");
return -1;
}
if(gst_pad_link(elem_cur_srcpad, elem_after_sinkpad) != GST_PAD_LINK_OK)
{
printf("linking output 1 failed/n");
return -1;
}
g_print ("Moving to PLAYING/n");
gst_element_set_state (elem_cur, GST_STATE_PLAYING);
gst_element_set_state (elem_after, GST_STATE_PLAYING);
GST_DEBUG_OBJECT (pipeline, "done");
newfile = NO_NEW_FILE;
// Push the event in the pipe flow (false DROP)
return TRUE;
}
// Check if Buffer contains a KEY FRAME
static gboolean
is_sync_frame (GstBuffer * buffer)
{
if (GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_DELTA_UNIT))
{
return FALSE;
}
else if (!GST_BUFFER_FLAG_IS_SET(buffer, GST_BUFFER_FLAG_IN_CAPS))
{
return TRUE;
}
}
// Block source and launch EOS to MUXER to achieve a full muxed file
static gboolean
pad_probe_cb (GstPad * pad, GstBuffer * buffer, gpointer user_data)
{
g_print ("/n/tINSIDE pad_probe_cb:%d %s %s/n",probe_id, (newfile?"newfile":"thesame"),
(is_sync_frame (buffer)?"KEYframe":"frame"));
GST_DEBUG_OBJECT (pad, "pad is blocked now");
last_ts = GST_BUFFER_TIMESTAMP(buffer);
if(!GST_CLOCK_TIME_IS_VALID(last_ts))
last_ts=0;
if((newfile==NO_NEW_FILE) || !is_sync_frame (buffer))
return TRUE;
/* remove the probe first */
gst_pad_remove_buffer_probe (pad, probe_id);
/* install new probe for EOS */
probe_id = gst_pad_add_event_probe (elem_after_sinkpad, G_CALLBACK(event_probe_cb), user_data);
/* push EOS into the element, the probe will be fired when the
* EOS leaves the effect and it has thus drained all of its data */
gst_pad_send_event (elem_cur_sinkpad, gst_event_new_eos ());
// Wait til the EOS have been processed the Buffer with the Key frame will be the FIRST
while(newfile != NO_NEW_FILE)
Sleep(1);
// Push the buffer in the pipe flow (false DROP)
return TRUE;
}
// this timeout is periodically run as part of the mainloop
static gboolean timeout (gpointer user_data)
{
g_print ("TIMEOUT/n");
if(!playing)
return false;
newfile = NEW_FILE;
/* install new probe for Keyframe and New File */
probe_id = gst_pad_add_buffer_probe (blockpad, G_CALLBACK(pad_probe_cb), pipeline);
return true;
}
Mi "concepto" favorito para entender la vinculación (y el enlace dinámico) es pensar en la tubería como una tubería real con agua que fluye a través de ella. Una vez que hagas esto, algunas cosas serán muy obvias. Como, "¿configura la fuente para JUGAR antes de vincular el elemento?", Se convierte en "¿enciende el agua antes de conectar la manguera?", Y en cierto modo se responde a sí misma. Aún más con los enlaces dinámicos, ¿cómo se aseguraría de que no "gotee" el agua (que es malo, "goteos" en GStreamer es equivalente a obtener un GST_FLOW_NOT_LINKED, y detendrá su fuente y la diversión) o se obstruya (puede causar caída o congestión de paquetes).
Sí. Muchos. Con un pequeño descargo de responsabilidad de que todavía trabajo con 0.10 y algo de esto podría haber sido corregido con 1.0, desafortunadamente, es muy, muy difícil de hacer enlaces dinámicos y desvinculación con GStreamer 0.10. Déjame explicarte: digamos que estás usando una Tee, y quieres desvincular una rama. Comenzarías por soltar el Tees srcpad (sin importar desvincularlo, eso sucede como parte del lanzamiento del pad), y ahora deberías ser capaz de derribar los elementos aguas abajo de ese pad. (El equivalente de agua es que cierra una válvula después de la T, y ahora debería poder desmontar las tuberías después de la válvula, no comenzaría a desmontar las tuberías sin cerrar primero la válvula a menos que quisiera mojarse ...) funcionará la mayor parte del tiempo, pero hay una carrera aquí. Porque después de que hayas liberado el bloc, puede que aún haya un push o un pad-alloc en su camino en ese pad, y si ahora en tu código comienzas a derribar los elementos downstream, esto podría ahora colapsar debido a la raza que existe en algunos elementos si reciben un push o pad-alloc mientras se destruyen, o si obtienes un GST_FLOW_WRONG_STATE o GST_FLOW_NOT_LINKED y volverán a la fuente parando el flujo para todos ...
Hice muchos experimentos con esto, y descubrí que si necesita estabilidad y el bloqueo / congelamiento ocasionalmente no es una opción, necesita un elemento que le sirva como su red de seguridad dinámica. Un elemento que garantizará que no ocurra absolutamente ninguna actividad en un panel después de que lo suelte / desenlace. La única forma de hacerlo es romper otro paradigma de GStreamer de no presionar mientras se mantiene un bloqueo: es necesario mantener un bloqueo mientras se empujan / envían eventos / asignación de almohadillas. Hice una cosa así hace un tiempo aquí . (el caso de prueba es lo más importante, por supuesto, ya que le permite probar su propio / otros elementos para su seguridad) También podría imaginar un elemento sin bloqueo que se tragaría todos los malos FlowReturns, y solo pintaría una bonita imagen para está en sentido ascendente, pero entonces necesitaría estar absolutamente seguro de que todos sus elementos en sentido descendente serían "push o pad-alloc recibidos mientras se apaga" -seguro, ya que su elemento no podría garantizar que una vez "detenga el flujo" (liberar / desvincular) se ha ejecutado, una pequeña gota no pasaría.
Por supuesto, debes poner algo de esto en perspectiva. La ventana para estas terribles condiciones de carrera de las que estoy hablando es de hecho muy, muy pequeña, y podría suceder solo cada 1000 o 10.000 vez que ejecutas tu programa. Pero para una aplicación profesional esto por supuesto no es aceptable. Hice una charla donde cubrí algunas de estas cosas aquí