{"id":79540,"date":"2020-04-28T07:42:01","date_gmt":"2020-04-28T05:42:01","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11"},"modified":"2020-04-28T07:42:01","modified_gmt":"2020-04-28T05:42:01","slug":"izuchaem-voip-dvizhok-mediastreamer2-chast-11","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","title":{"rendered":"Explorando el motor VoIP Mediastreamer2. Parte 11","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>El material del art\u00edculo fue tomado de mi <noindex><a rel=\"nofollow\" href=\"https:\/\/zen.yandex.ru\/profile\/editor\/id\/5e3f8e0751f5346faab4fc7a\">canal de Zen<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Explorando el motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/b66fab43eb45c626c50f0383dcfa7cca.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Mecanismo de movimiento de datos<\/p>\n<p><\/p>\n<ul>\n<li>Bloque de datos dblk_t<\/li>\n<li>Mensaje mblk_t<\/li>\n<li>Funciones para trabajar con mensajes mblk_t<\/li>\n<li>Cola queue_t<\/li>\n<li>Funciones para trabajar con colas queue_t<\/li>\n<li>Conexi\u00f3n de filtros<\/li>\n<li>Punto de se\u00f1al en el grafo de procesamiento de datos<\/li>\n<li>Actividad detr\u00e1s de escena del ticker<\/li>\n<li>MSBufferizer<\/li>\n<li>Funciones para trabajar con MSBufferizer<\/li>\n<\/ul>\n<p><\/p>\n<p>En el anterior <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/497932\/\">el art\u00edculo<\/a><\/noindex> Hemos desarrollado nuestro propio filtro. En este art\u00edculo dedicaremos atenci\u00f3n al dispositivo del mecanismo interno de movimiento de datos entre filtros del mediastreamer. Esto permitir\u00e1, en adelante, escribir filtros complejos con menos esfuerzo.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"mehanizm-peremescheniya-dannyh\">Mecanismo de movimiento de datos<\/h2>\n<p><\/p>\n<p>El movimiento de datos en el mediastreamer se realiza a trav\u00e9s de colas descritas por la estructura <em>queue_t<\/em>. A trav\u00e9s de las colas se mueven cadenas de mensajes del tipo <em>mblk_t<\/em>, que en s\u00ed mismas no contienen datos de se\u00f1al, sino solo referencias al mensaje anterior, siguiente y al bloque de datos. Adem\u00e1s, quiero destacar que hay un campo para una referencia a un mensaje de igual tipo, lo que permite organizar una lista enlazada de mensajes. Llamaremos a un grupo de mensajes unidos de esta manera un tuple. As\u00ed, cualquier elemento de la cola puede ser un mensaje \u00fanico <em>mblk_t<\/em>, o puede ser la cabeza de un tuple de mensajes <em>mblk_t<\/em>. Cada mensaje del tuple puede tener su propio bloque de datos subyacente. La utilidad de los tuples la discutiremos un poco m\u00e1s adelante.<\/p>\n<p><\/p>\n<p>Como se mencion\u00f3 anteriormente, el mensaje en s\u00ed no contiene un bloque de datos; en su lugar, solo contiene un puntero a la zona de memoria donde se almacena el bloque. En esta parte, el funcionamiento general del mediastreamer recuerda al almac\u00e9n de puertas en la pel\u00edcula \"Monstruos, S.A.\", donde las puertas (enlaces a los datos \u2014 habitaciones) se mueven a gran velocidad por las cintas transportadoras, mientras que las habitaciones mismas permanecen inm\u00f3viles. <\/p>\n<p><\/p>\n<p>Ahora, subiendo por la jerarqu\u00eda, examinemos en detalle las entidades mencionadas del mecanismo de transmisi\u00f3n de datos en el mediastreamer.<\/p>\n<p><\/p>\n<h3 id=\"blok--dannyh-dblk_t\">Bloque de datos <em>dblk_t<\/em><\/h3>\n<p><\/p>\n<p>Un bloque de datos consiste en un encabezado y un b\u00fafer de datos. El encabezado se describe mediante la siguiente estructura,<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct datab\n{\nunsigned char *db_base; \/\/ Puntero al inicio del b\u00fafer de datos.\nunsigned char *db_lim;  \/\/ Puntero al final del b\u00fafer de datos.\nvoid (*db_freefn)(void*); \/\/ Funci\u00f3n para liberar memoria al eliminar el bloque.\nint db_ref; \/\/ Contador de referencias.\n} dblk_t;<\/code><\/pre>\n<p><\/p>\n<p>Los campos de la estructura contienen punteros al inicio del b\u00fafer, al final del b\u00fafer y a la funci\u00f3n de eliminaci\u00f3n del b\u00fafer de datos. El \u00faltimo elemento en el encabezado <em>db_ref<\/em> \u2014 contador de referencias, si llega a cero, esto sirve como se\u00f1al para eliminar este bloque de la memoria. Si el bloque de datos fue creado por la funci\u00f3n <em>datab_alloc() <\/em>, entonces el b\u00fafer de datos se colocar\u00e1 en la memoria inmediatamente despu\u00e9s del encabezado. En todos los dem\u00e1s casos, el b\u00fafer puede estar ubicado en otro lugar. En el b\u00fafer de datos se encuentran las muestras de se\u00f1al u otros datos que queremos procesar con filtros.<\/p>\n<p><\/p>\n<p>Una nueva instancia del bloque de datos se crea con la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">dblk_t *datab_alloc(int size);<\/code><\/pre>\n<p><\/p>\n<p>Se le pasa como par\u00e1metro de entrada el tama\u00f1o de los datos que almacenar\u00e1 el bloque. Se asigna m\u00e1s memoria para que al principio de la memoria asignada se coloque el encabezado \u2014 la estructura <em>datab<\/em>. Pero al utilizar otras funciones, esto no siempre ocurre, en algunos casos el b\u00fafer de datos puede estar separado del encabezado del bloque de datos. Los campos de la estructura se configuran al crearla de forma que su campo <em>db_base<\/em> apunte al inicio del \u00e1rea de datos y <em>db_lim<\/em> al final. El contador de referencias <em>db_ref<\/em> se establece en uno. El puntero a la funci\u00f3n de limpieza de datos se establece en cero.<\/p>\n<p><\/p>\n<h3 id=\"soobschenie-mblk_t\">Mensaje <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Como se mencion\u00f3, los elementos de la cola tienen el tipo <em>mblk_t,<\/em> se define de la siguiente manera:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct msgb\n{\n  struct msgb *b_prev;   \\\/\\\/ Puntero al elemento anterior de la lista.\n  struct msgb *b_next;   \\\/\\\/ Puntero al siguiente elemento de la lista.\n  struct msgb *b_cont;   \\\/\\\/ Puntero para adjuntar otros mensajes a un mensaje, para crear una tupla de mensajes.\n  struct datab *b_datap; \\\/\\\/ Puntero a la estructura del bloque de datos.\n  unsigned char *b_rptr; \\\/\\\/ Puntero al inicio del \u00e1rea de datos para leer los datos del b\u00fafer b_datap.\n  unsigned char *b_wptr; \\\/\\\/ Puntero al inicio del \u00e1rea de datos para escribir los datos del b\u00fafer b_datap.\n  uint32_t reserved1;    \\\/\\\/ Campo reservado 1, el mediastreamer coloca ah\u00ed informaci\u00f3n t\u00e9cnica.\n  uint32_t reserved2;    \\\/\\\/ Campo reservado 2, el mediastreamer coloca ah\u00ed informaci\u00f3n t\u00e9cnica.\n  #if defined(ORTP_TIMESTAMP)\n  struct timeval timestamp;\n  #endif\n  ortp_recv_addr_t recv_addr;\n} mblk_t;<\/code><\/pre>\n<p><\/p>\n<p>Estructura <em>mblk_t<\/em> al principio contiene punteros <em>b_prev<\/em>, <em>b_next<\/em>, que son necesarios para organizar una lista doblemente enlazada (que es la cola <em>queue_t<\/em>). <\/p>\n<p><\/p>\n<p>Luego sigue el puntero <em>b_cont<\/em>, que se utiliza solo cuando el mensaje entra en la tupla. Para el \u00faltimo mensaje en la tupla, este puntero permanece nulo. <\/p>\n<p><\/p>\n<p>A continuaci\u00f3n, vemos un puntero al bloque de datos <em>b_datap<\/em>, para el cual existe el mensaje. Despu\u00e9s vienen punteros a la \u00e1rea dentro del b\u00fafer de datos del bloque. El campo <em>b_rptr<\/em> indica el lugar desde donde se leer\u00e1n los datos del b\u00fafer. El campo <em>b_wptr<\/em> indica el lugar desde donde se realizar\u00e1 la escritura en el b\u00fafer. <\/p>\n<p><\/p>\n<p>Los campos restantes son de car\u00e1cter auxiliar y no est\u00e1n relacionados con el funcionamiento del mecanismo de transmisi\u00f3n de datos.<\/p>\n<p><\/p>\n<p>A continuaci\u00f3n se muestra un \u00fanico mensaje llamado <em>m1<\/em> y un bloque de datos <em>d1<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Explorando el motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/e6a726b13491832258c1228f03bde802.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn la siguiente figura se muestra una tupla de tres mensajes <em>m1<\/em>, <em>m1_1<\/em>, <em>m1_2<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Explorando el motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/9613feefe9684b9551cd98115faa9a08.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-soobscheniyami--mblk_t\">Funciones para trabajar con mensajes <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Un nuevo mensaje <em>mblk_t<\/em> se crea mediante la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *allocb(int size, int pri); <\/code><\/pre>\n<p><\/p>\n<p>ella asigna en memoria un nuevo mensaje <em>mblk_t<\/em> con un bloque de datos de tama\u00f1o especificado <em>tama\u00f1o<\/em>, el segundo argumento \u2014 <em>pri<\/em> no se utiliza en la versi\u00f3n de la biblioteca considerada. Debe permanecer nulo. Durante la operaci\u00f3n de la funci\u00f3n se asignar\u00e1 memoria para la estructura del nuevo mensaje y se llamar\u00e1 a la funci\u00f3n <em>mblk_init()<\/em>, que pondr\u00e1 a cero todos los campos de la instancia creada de la estructura y luego, utilizando lo mencionado anteriormente, <em>datab_alloc()<\/em>crear\u00e1 el b\u00fafer de datos. Despu\u00e9s se configurar\u00e1n los campos en la estructura: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mp-&gt;b_datap=datab;\nmp-&gt;b_rptr=mp-&gt;b_wptr=datab-&gt;db_base;\nmp-&gt;b_next=mp-&gt;b_prev=mp-&gt;b_cont=NULL;<\/code><\/pre>\n<p><\/p>\n<p>Como resultado, obtenemos un nuevo mensaje con los campos inicializados y un b\u00fafer de datos vac\u00edo. Para agregar datos al mensaje, es necesario copiarlos al b\u00fafer del bloque de datos:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">memcpy(msg-&gt;b_rptr, data, size);<\/code><\/pre>\n<p><\/p>\n<p>donde <em>data<\/em> \u2014 puntero a la fuente de datos, y <em>tama\u00f1o<\/em> \u2014 su tama\u00f1o.<br \/>\nLuego, se debe actualizar el puntero a la posici\u00f3n de escritura para que apunte nuevamente al comienzo del \u00e1rea libre en el b\u00fafer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">msg-&gt;b_wptr = msg-&gt;b_wptr + size<\/code><\/pre>\n<p><\/p>\n<p>Si se requiere crear un mensaje a partir de un b\u00fafer que ya existe, sin copiar, se utiliza la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *esballoc(uint8_t *buf, int size, int pri, void (*freefn)(void*)); <\/code><\/pre>\n<p><\/p>\n<p>La funci\u00f3n, despu\u00e9s de crear el mensaje y la estructura del bloque de datos, configurar\u00e1 sus punteros a los datos en la direcci\u00f3n <em>buf<\/em>. Es decir, en este caso, el b\u00fafer de datos no est\u00e1 ubicado despu\u00e9s de los campos del encabezado del bloque de datos, como sucedi\u00f3 al crear el bloque de datos con la funci\u00f3n. <em>datab_alloc()<\/em>El b\u00fafer de datos pasado a la funci\u00f3n permanecer\u00e1 donde estaba, pero mediante punteros se apuntar\u00e1 al reci\u00e9n creado encabezado del bloque de datos, y este, a su vez, al mensaje.<\/p>\n<p><\/p>\n<p>A un mensaje <em>mblk_t<\/em> se pueden acoplar varios bloques de datos de manera consecutiva. Esto se hace con la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * appendb(mblk_t *mp, const char *data, int size, bool_t pad); <\/code><\/pre>\n<p><\/p>\n<p><em>mp<\/em> \u2014 mensaje al que se a\u00f1adir\u00e1 otro bloque de datos;<br \/>\n<em>data<\/em> \u2014 puntero al bloque, que ser\u00e1 copiado al mensaje;<br \/>\n<em>tama\u00f1o<\/em> \u2014 tama\u00f1o de los datos;<br \/>\n<em>pad<\/em> \u2014 indicador de que el tama\u00f1o del espacio asignado debe estar alineado a 4 bytes (el relleno se realizar\u00e1 con ceros). <\/p>\n<p><\/p>\n<p>Si en el b\u00fafer de datos del mensaje hay suficiente espacio, los nuevos datos se concatenar\u00e1n a los que ya est\u00e1n all\u00ed. Si el espacio libre en el b\u00fafer de datos del mensaje es menor que <em>tama\u00f1o<\/em>, se crea un nuevo mensaje con un tama\u00f1o de b\u00fafer suficiente y los datos se copian en su b\u00fafer. Este nuevo mensaje se acopla al original a trav\u00e9s del puntero <em>b_cont<\/em>. En este caso, el mensaje se convierte en una tupla. <\/p>\n<p><\/p>\n<p>Si se requiere agregar otro bloque de datos a la tupla, se debe usar la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgappend(mblk_t *mp, const char *data, int size, bool_t pad);<\/code><\/pre>\n<p><\/p>\n<p>esta buscar\u00e1 el \u00faltimo mensaje en la tupla (tendr\u00e1 <em>b_cont<\/em> cero) y llamar\u00e1 a la funci\u00f3n <em>appendb()<\/em>.<\/p>\n<p><\/p>\n<p>Para conocer el tama\u00f1o de los datos en el mensaje o en la tupla, se puede usar la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>esta recorrer\u00e1 todos los mensajes de la tupla y devolver\u00e1 el total de datos en los b\u00faferes de datos de esos mensajes. Para cada mensaje, la cantidad de datos se calcula as\u00ed:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\"> mp-&gt;b_wptr - mp-&gt;b_rptr<\/code><\/pre>\n<p><\/p>\n<p>Para combinar dos tuplas se aplica la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *concatb(mblk_t *mp, mblk_t *newm);<\/code><\/pre>\n<p><\/p>\n<p>esta une la tupla <em>newm<\/em> al final de la tupla <em>mp<\/em> y devuelve un puntero al \u00faltimo mensaje de la nueva tupla.<\/p>\n<p><\/p>\n<p>Si es necesario, se puede convertir la tupla en un solo mensaje con un \u00fanico bloque de datos, esto se hace con la funci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,int len);<\/code><\/pre>\n<p><\/p>\n<p>si el argumento <em>len<\/em> es igual a -1, el tama\u00f1o del b\u00fafer asignado se determina autom\u00e1ticamente. Si <em>len<\/em> si el n\u00famero es positivo, se crear\u00e1 un b\u00fafer de este tama\u00f1o y se copiar\u00e1n los datos de los mensajes de la tupla. Si se acaba el b\u00fafer, la copia se detendr\u00e1 all\u00ed. El primer mensaje de la tupla recibir\u00e1 un b\u00fafer de nuevo tama\u00f1o con los datos copiados. Los restantes mensajes ser\u00e1n eliminados y la memoria ser\u00e1 devuelta al mont\u00f3n.<\/p>\n<p><\/p>\n<p>Al eliminar la estructura <em>mblk_t<\/em> se considera el contador de referencias del bloque de datos, si al invocar <em>freeb()<\/em> resulta ser cero, el b\u00fafer de datos se elimina junto con la instancia <em>mblk_t<\/em>, a la que apunta.<\/p>\n<p><\/p>\n<p>Inicializaci\u00f3n de los campos del nuevo mensaje:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void mblk_init(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Adici\u00f3n de otro conjunto de datos al mensaje: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * appendb(mblk_t *mp, const char *data, size_t size, bool_t pad);<\/code><\/pre>\n<p><\/p>\n<p>Si los nuevos datos no caben en el espacio libre del b\u00fafer de datos del mensaje, se adjunta al mensaje un mensaje creado por separado con un b\u00fafer del tama\u00f1o necesario (en el primer mensaje se establece un puntero al mensaje a\u00f1adido) el mensaje se convierte en la tupla.<\/p>\n<p><\/p>\n<p>Adici\u00f3n de un conjunto de datos a la tupla:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgappend(mblk_t *mp, const char *data, size_t size, bool_t pad); <\/code><\/pre>\n<p><\/p>\n<p>La funci\u00f3n llama a appendb() en un ciclo.<\/p>\n<p><\/p>\n<p>Unificaci\u00f3n de dos tuplas en una:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *concatb(mblk_t *mp, mblk_t *newm);<\/code><\/pre>\n<p><\/p>\n<p>Mensaje <em>newm<\/em> se adjuntar\u00e1 a <em>mp<\/em>.<\/p>\n<p><\/p>\n<p>Creaci\u00f3n de una copia de un solo mensaje:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copyb(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Copia completa de la tupla con todos los bloques de datos:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copymsg(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Los elementos de la tupla se copian mediante la funci\u00f3n <em>copyb()<\/em>.<\/p>\n<p><\/p>\n<p>Creaci\u00f3n de una copia ligera del mensaje. <em>mblk_t<\/em>En este caso, el bloque de datos no se copia, sino que se incrementa su contador de referencias. <em>db_ref<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *dupb(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Creaci\u00f3n de una copia ligera de la tupla. Los bloques de datos no se copian, solo se incrementan sus contadores de referencias. <em>db_ref<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *dupmsg(mblk_t* m);<\/code><\/pre>\n<p><\/p>\n<p>Consolidaci\u00f3n de todos los mensajes de la tupla en un solo mensaje:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,size_t len);<\/code><\/pre>\n<p><\/p>\n<p>Si el argumento <em>len<\/em> es igual a -1, el tama\u00f1o del b\u00fafer asignado se determina autom\u00e1ticamente.<\/p>\n<p><\/p>\n<p>Eliminaci\u00f3n de un mensaje, tupla:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void freemsg(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>El contador de referencias del bloque de datos se reduce en uno. Si llega a cero, el bloque de datos tambi\u00e9n se elimina.<\/p>\n<p><\/p>\n<p>C\u00e1lculo del tama\u00f1o total de los datos en un mensaje o tupla.<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">size_t msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Extracci\u00f3n de un mensaje de la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *ms_queue_peek_last (q);<\/code><\/pre>\n<p><\/p>\n<p>Copia del contenido de los campos reservados de un mensaje a otro mensaje (en realidad, en estos campos se encuentran banderas que utiliza el transmisor de medios):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_meta_copy(const mblk_t *source, mblk *dest);<\/code><\/pre>\n<p><\/p>\n<h3 id=\"ochered-queue_t\">Cola <em>queue_t<\/em><\/h3>\n<p><\/p>\n<p>La cola de mensajes en el mediastreamer est\u00e1 implementada como una lista doblemente enlazada circular. Cada elemento de la lista contiene un puntero a un bloque de datos con las muestras de se\u00f1al. As\u00ed, solo se mueven los punteros al bloque de datos, mientras que los propios datos permanecen est\u00e1ticos. Es decir, solo se mueven las referencias a ellos.<br \/>\nEstructura que describe la cola <em>queue_t<\/em>, se muestra a continuaci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _queue\n{\n   mblk_t _q_stopper; \/* \"Elemento vac\u00edo\" de la cola, no apunta a datos, se usa solo para controlar la cola. Al inicializar la cola (qinit()), sus punteros se configuran para apuntar a s\u00ed mismo. *\/\n   int q_mcount;        \/\/ N\u00famero de elementos en la cola.\n} queue_t;<\/code><\/pre>\n<p><\/p>\n<p>La estructura contiene un campo - un puntero <em>_q_stopper<\/em> de tipo *mblk_t, que apunta al primer elemento (mensaje) en la cola. El segundo campo de la estructura es el contador de mensajes en la cola.<br \/>\nEn la figura a continuaci\u00f3n se muestra la cola con el nombre q1, que contiene 4 mensajes m1, m2, m3, m4.<br \/>\n<img decoding=\"async\" alt=\"Explorando el motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/07fc043861edf15e5eb40e49c8ec5e3e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn la siguiente figura se muestra la cola con el nombre q1, que contiene 4 mensajes m1, m2, m3, m4. El mensaje m2 es la cabeza de una tupla, que contiene dos mensajes m\u00e1s, m2_1 y m2_2.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Explorando el motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/8fa7667f3690674a2ee321b4a6c39e1d.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-ocheredyami-queue_t\">Funciones para trabajar con colas queue_t<\/h3>\n<p><\/p>\n<p>Inicializaci\u00f3n de la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void qinit(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Campo <em>_q_stopper<\/em> (en adelante lo llamaremos \"detentor\") se inicializa con la funci\u00f3n <em>mblk_init()<\/em>, su puntero al elemento anterior y al siguiente se configuran para que apunten a s\u00ed mismo. Se inicializa el contador de elementos en la cola.<\/p>\n<p><\/p>\n<p>Adici\u00f3n de un nuevo elemento (mensaje):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void putq(queue_t *q, mblk_t *m);<\/code><\/pre>\n<p><\/p>\n<p>Un nuevo elemento <em>m<\/em> se agrega al final de la lista, configurando los punteros del elemento para que el detenedor sea su siguiente elemento, y \u00e9l sea el anterior para el detenedor. Se incrementa el contador de elementos en la cola.<\/p>\n<p><\/p>\n<p>Extracci\u00f3n de un elemento de la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * getq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>se extrae el mensaje que est\u00e1 despu\u00e9s del detenedor, y el contador de elementos se decrementa. Si no hay otros elementos en la cola adem\u00e1s del detenedor, retorna 0.<\/p>\n<p><\/p>\n<p>Inserci\u00f3n de un mensaje en la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void insq(queue_t *q, mblk_t *emp, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>El elemento <em>mp<\/em> se inserta antes del elemento <em>emp<\/em>. Si <em>emp<\/em>=0, por lo que el mensaje se a\u00f1ade al final de la cola.<\/p>\n<p><\/p>\n<p>Extracci\u00f3n del mensaje de la cabeza de la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void remq(queue_t *q, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>El contador de elementos se decrementa.<\/p>\n<p><\/p>\n<p>Lectura del puntero al primer elemento en la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * peekq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>Eliminaci\u00f3n de todos los elementos de la cola con la eliminaci\u00f3n de los mismos elementos:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void flushq(queue_t *q, int how);<\/code><\/pre>\n<p><\/p>\n<p>El argumento <em>how<\/em> no se utiliza. El contador de elementos de la cola se establece en cero.<\/p>\n<p><\/p>\n<p>Macro para leer el puntero al \u00faltimo elemento de la cola:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * qlast(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Al trabajar con colas de mensajes, hay que tener en cuenta que al llamar a <em>ms_queue_put(q, m)<\/em> con un puntero nulo al mensaje, la funci\u00f3n entra en un bucle infinito. Su programa se bloquear\u00e1. Se comporta de manera similar <em>ms_queue_next(q, m)<\/em>.<\/p>\n<p><\/p>\n<h3 id=\"soedinenie-filtrov\">Conexi\u00f3n de filtros<\/h3>\n<p><\/p>\n<p>La cola descrita anteriormente se utiliza para transferir mensajes de un filtro a otro o de uno a varios filtros. Los filtros y sus conexiones entre s\u00ed forman un grafo dirigido. La entrada o salida de un filtro se denomina com\u00fanmente \"pin\". Para describir el orden de conexi\u00f3n de los filtros, el mediastreamer utiliza el concepto de \"punto de se\u00f1al\". Un punto de se\u00f1al es una estructura <em>_MSCPoint<\/em>, que contiene un puntero al filtro y el n\u00famero de uno de sus pines, describiendo as\u00ed la conexi\u00f3n de una de las entradas o salidas del filtro. <\/p>\n<p><\/p>\n<h4 id=\"signalnaya-tochka-grafa-obrabotki-dannyh\">Punto de se\u00f1al en el grafo de procesamiento de datos<\/h4>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSCPoint{\nstruct _MSFilter *filter; \/\/ Puntero al filtro del mediastreamer.\nint pin;                        \/\/ N\u00famero de una de las entradas o salidas del filtro, es decir, pin.\n} MSCPoint;\n<\/code><\/pre>\n<p><\/p>\n<p>Los pines de los filtros se numeran comenzando desde cero. <\/p>\n<p><\/p>\n<p>La conexi\u00f3n de dos pines a trav\u00e9s de una cola de mensajes se describe mediante la estructura <em>_MSQueue<\/em>, que contiene la cola de mensajes y punteros a dos puntos de se\u00f1al que conecta:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSQueue\n{\nqueue_t q;\nMSCPoint prev;\nMSCPoint next;\n}MSQueue;\n<\/code><\/pre>\n<p><\/p>\n<p>Llamaremos a esta estructura enlace de se\u00f1al. Cada filtro del mediastreamer contiene una tabla de enlaces de entrada y una tabla de enlaces de salida (<em>MSQueue<\/em>). El tama\u00f1o de las tablas se establece al crear el filtro, ya lo hicimos con la variable exportada de tipo <em>MSFilterDesc<\/em>, cuando desarrollamos nuestro propio filtro. A continuaci\u00f3n se muestra la estructura que describe cualquier filtro en el mediastreamer, <em>MSFilter<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">\nstruct _MSFilter{\n    MSFilterDesc *desc;    \n    \/* Puntero al descriptor del filtro. *\/\n    \/* Atributos protegidos, no se pueden mover ni eliminar, de lo contrario, se interrumpir\u00e1 el trabajo con los plugins. *\/\n    ms_mutex_t lock;      \/* Sem\u00e1foro. *\/\n    MSQueue **inputs;     \/* Tabla de enlaces de entrada. *\/\n    MSQueue **outputs;    \/* Tabla de enlaces de salida. *\/\n    struct _MSFactory *factory; \/* Puntero a la f\u00e1brica que cre\u00f3 esta instancia del filtro. *\/\n    void *padding;              \/* No se utiliza, se utilizar\u00e1 si se a\u00f1aden campos protegidos. *\/\n    void *data;                 \/* Puntero a una estructura arbitraria para el almacenamiento de datos del estado interno del filtro y c\u00e1lculos intermedios. *\/\n    struct _MSTicker *ticker;   \/* Puntero al objeto ticker, que no debe ser nulo cuando se llama a la funci\u00f3n process(). *\/\n    \/* atributos privados, pueden moverse y cambiarse en cualquier momento *\/\n    MSList *notify_callbacks; \/* Lista de callbacks utilizados para manejar eventos del filtro. *\/\n    uint32_t last_tick;       \/* N\u00famero del \u00faltimo pulso en que se llam\u00f3 a process(). *\/\n    MSFilterStats *stats;     \/* Estad\u00edsticas del funcionamiento del filtro. *\/\n    int postponed_task; \/* N\u00famero de tareas aplazadas. Algunos filtros pueden aplazar el procesamiento de datos (llamada a process()) durante varios pulsos. *\/\n    bool_t seen;  \/* Bandera que utiliza el ticker para marcar que esta instancia del filtro ya ha sido atendida en este pulso. *\/\n};\ntypedef struct _MSFilter MSFilter;\n<\/code><\/pre>\n<p><\/p>\n<p>Despu\u00e9s de que hemos conectado los filtros en el programa en C seg\u00fan nuestra intenci\u00f3n (pero sin conectar el ticker), hemos creado as\u00ed un gr\u00e1fico dirigido, cuyos nodos son instancias de la estructura <em>MSFilter<\/em>, y las aristas son instancias de enlaces <em>MSQueue<\/em>. <\/p>\n<p><\/p>\n<h3 id=\"zakulisnaya-deyatelnost-tikera\">Actividad detr\u00e1s de escena del ticker<\/h3>\n<p><\/p>\n<p>Cuando les dije que el ticker es un filtro fuente de pulsos, no era toda la verdad sobre \u00e9l. El ticker es un objeto que ejecuta funciones en ciclos <em>process()<\/em> de todos los filtros del esquema (grafo) a los que est\u00e1 conectado. Cuando conectamos un ticker a un filtro del grafo en un programa en C, le mostramos al ticker el grafo que a partir de ese momento gestionar\u00e1 hasta que lo desconectemos. Despu\u00e9s de la conexi\u00f3n, el ticker comienza a inspeccionar el grafo a su cuidado, confeccionando una lista de los filtros en los que se incluye. Para no \"contar\" el mismo filtro dos veces, marca los filtros detectados estableciendo un indicador <em>seen<\/em>. La b\u00fasqueda se realiza a trav\u00e9s de las tablas de enlaces que tiene cada filtro. <\/p>\n<p><\/p>\n<p>Durante su recorrido informativo por el gr\u00e1fico, el ticker verifica si hay al menos uno de los filtros que act\u00fae como fuente de bloques de datos. Si no se encuentra ninguno, el gr\u00e1fico se considera incorrecto y el ticker finaliza su operaci\u00f3n de manera forzada.<\/p>\n<p><\/p>\n<p>Si el grafo resulta ser \"correcto\", se llama a la funci\u00f3n para inicializar cada filtro encontrado <em>preprocess()<\/em>. En el momento correspondiente para el siguiente ciclo de procesamiento (de manera predeterminada cada 10 milisegundos), el ticker llama a la funci\u00f3n <em>process()<\/em> para todos los filtros de fuente encontrados previamente, y luego para los dem\u00e1s filtros de la lista. Si el filtro tiene enlaces de entrada, el inicio de la funci\u00f3n <em>process()<\/em> se repite hasta que las colas de los enlaces de entrada est\u00e9n vac\u00edas. Despu\u00e9s de eso, pasa al siguiente filtro en la lista y \"lo procesa\" hasta liberar los enlaces de entrada de los mensajes. El ticker pasa de un filtro a otro hasta que se agote la lista. As\u00ed termina el procesamiento del ciclo. <\/p>\n<p><\/p>\n<p>Ahora volvamos a las tuplas y hablemos sobre por qu\u00e9 se agreg\u00f3 tal entidad en el streamer de medios. En general, el volumen de datos necesario para el algoritmo que opera dentro del filtro no coincide ni es m\u00faltiplo del tama\u00f1o de los buffers de datos que llegan a la entrada. Por ejemplo, estamos escribiendo un filtro que realiza una r\u00e1pida transformaci\u00f3n de Fourier, que por definici\u00f3n solo puede procesar bloques de datos cuyo tama\u00f1o sea una potencia de dos. Supongamos que son 512 muestras. Si los datos se generan a trav\u00e9s de un canal telef\u00f3nico, el buffer de datos de cada mensaje en la entrada nos proporcionar\u00e1 160 muestras de se\u00f1al. Existe la tentaci\u00f3n de no extraer los datos de la entrada hasta que haya suficiente cantidad de datos. Pero en este caso, habr\u00e1 una colisi\u00f3n con el ticker, que intentar\u00e1 sin \u00e9xito rodear el filtro hasta vaciar el enlace de entrada. Anteriormente, denominamos a esta regla como el tercer principio de funcionamiento del filtro. De acuerdo con este principio, la funci\u00f3n process() del filtro debe extraer todos los datos de las colas de entrada.<\/p>\n<p><\/p>\n<p>Adem\u00e1s, no se podr\u00e1n extraer solo 512 muestras desde la entrada, ya que solo se pueden obtener en bloques completos, es decir, el filtro deber\u00e1 obtener 640 muestras y al usar 512 de ellas, el resto se acumular\u00e1 hasta que haya una nueva porci\u00f3n de datos. As\u00ed, nuestro filtro, adem\u00e1s de su funci\u00f3n principal, debe realizar acciones auxiliares para el almacenamiento intermedio de los datos de entrada. Los desarrolladores del mediastreamer y de esta tarea general han creado un objeto especial: MSBufferizer (bufferizador), que soluciona esta tarea mediante tuplas. <\/p>\n<p><\/p>\n<h3 id=\"buferizator-msbufferizer\">MSBufferizer<\/h3>\n<p><\/p>\n<p>Este es el objeto que acumular\u00e1 los datos de entrada dentro del filtro y comenzar\u00e1 a enviarlos para su procesamiento tan pronto como la cantidad de informaci\u00f3n sea suficiente para ejecutar el algoritmo del filtro. Mientras el bufferizador acumule datos, el filtro funcionar\u00e1 en modo inactivo, sin consumir capacidad de c\u00e1lculo del procesador. Pero en cuanto la funci\u00f3n de lectura del bufferizador devuelva un valor distinto de cero, la funci\u00f3n process() del filtro comenzar\u00e1 a extraer y procesar los datos del bufferizador en porciones del tama\u00f1o necesario, hasta que se agoten.<br \/>\nLos datos a\u00fan no utilizados permanecen en el bufferizador como el primer elemento de la tupla, al que se agregan los bloques posteriores de datos de entrada. <\/p>\n<p><\/p>\n<p>La estructura que describe el bufferizador: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">struct _MSBufferizer{\nqueue_t q; \/* Cola de mensajes. *\/\nint size; \/* Tama\u00f1o total de los datos que se encuentran actualmente en el bufferizador. *\/\n};\ntypedef struct _MSBufferizer MSBufferizer;<\/code><\/pre>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-msbufferizer\">Funciones para trabajar con MSBufferizer<\/h3>\n<p><\/p>\n<p>Creaci\u00f3n de una nueva instancia de bufferizador:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">MSBufferizer * ms_bufferizer_new(void);<\/code><\/pre>\n<p><\/p>\n<p>Se asigna memoria, se inicializa en <em>ms_bufferizer_init()<\/em> y se devuelve un puntero.<\/p>\n<p><\/p>\n<p>Funci\u00f3n de inicializaci\u00f3n:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_init(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Se inicializa la cola <em>q<\/em>, campo <em>tama\u00f1o<\/em> se establece en cero.<\/p>\n<p><\/p>\n<p>Adici\u00f3n de mensaje:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_put(MSBufferizer *obj, mblk_t *m); <\/code><\/pre>\n<p><\/p>\n<p>El mensaje m se agrega a la cola. El tama\u00f1o de los bloques de datos calculado se suma a <em>tama\u00f1o<\/em>.<\/p>\n<p><\/p>\n<p>Transferencia al bufferizador de todos los mensajes de la cola de datos del enlace <em>q<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_put_from_queue(MSBufferizer *obj, MSQueue *q);   <\/code><\/pre>\n<p><\/p>\n<p>La transferencia de mensajes del enlace <em>q<\/em> al bufferizador se realiza mediante la funci\u00f3n <em>ms_bufferizer_put()<\/em>.<\/p>\n<p><\/p>\n<p>Lectura del bufferizador:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_read(MSBufferizer *obj, uint8_t *data, int datalen); <\/code><\/pre>\n<p><\/p>\n<p>Si el tama\u00f1o de los datos acumulados en el bufferizador resulta ser menor que el solicitado (<em>datalen<\/em>), la funci\u00f3n devuelve cero, la copia de datos en data no se lleva a cabo. De lo contrario, se realiza la copia secuencial de datos desde las tuplas que est\u00e1n en el b\u00fafer. Despu\u00e9s de la copia, la tupla se elimina y se libera la memoria. La copia se detiene en el momento en que se han copiado datalen bytes. Si se queda sin espacio en medio de un bloque de datos, entonces en este mensaje, el bloque de datos se truncar\u00e1 hasta la parte no copiada restante. En la siguiente llamada, la copia continuar\u00e1 desde este punto.<\/p>\n<p><\/p>\n<p>Lectura de la cantidad de datos que est\u00e1n disponibles en este momento en el b\u00fafer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_get_avail(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Devuelve el campo <em>tama\u00f1o<\/em> del b\u00fafer.<\/p>\n<p><\/p>\n<p>Descartar parte de los datos que est\u00e1n en el b\u00fafer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_skip_bytes(MSBufferizer *obj, int bytes);<\/code><\/pre>\n<p><\/p>\n<p>La cantidad especificada de bytes de datos se extrae y se descarta. Se descartan los datos m\u00e1s antiguos.<\/p>\n<p><\/p>\n<p>Eliminar todos los mensajes que se encuentran en el b\u00fafer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_flush(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>El contador de datos se restablece a cero.<\/p>\n<p><\/p>\n<p>Eliminar todos los mensajes que se encuentran en el b\u00fafer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_uninit(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>La reinicializaci\u00f3n del contador no se lleva a cabo.<\/p>\n<p><\/p>\n<p>Eliminar el b\u00fafer y liberar la memoria:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_destroy(MSBufferizer *obj);  <\/code><\/pre>\n<p><\/p>\n<p>Ejemplos de uso del b\u00fafer se pueden encontrar en el c\u00f3digo fuente de varios filtros del mediastreamer. Por ejemplo, en el filtro MS_L16_ENC, que realiza una permutaci\u00f3n de bytes en las muestras del orden de red, al orden del host:<noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/BelledonneCommunications\/mediastreamer2\/blob\/1a9e8dec369a65ed10db975f5b77b92dc999b096\/src\/audiofilters\/l16.c\"> l16.c<\/a><\/noindex><\/p>\n<p><\/p>\n<p>En el siguiente art\u00edculo, abordaremos la cuesti\u00f3n de la evaluaci\u00f3n de la carga en el ticker y las formas de combatir la carga computacional excesiva en el mediastreamer.<\/p>\n<p>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/499010\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u0437\u044f\u0442 \u0441 \u043c\u043e\u0435\u0433\u043e \u0434\u0437\u0435\u043d-\u043a\u0430\u043d\u0430\u043b\u0430. \u041c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0449\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0411\u043b\u043e\u043a \u0434\u0430\u043d\u043d\u044b\u0445 dblk_t \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 mblk_t \u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f\u043c\u0438 mblk_t \u041e\u0447\u0435\u0440\u0435\u0434\u044c queue_t \u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043e\u0447\u0435\u0440\u0435\u0434\u044f\u043c\u0438 queue_t \u0421\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0421\u0438\u0433\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u0433\u0440\u0430\u0444\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0417\u0430\u043a\u0443\u043b\u0438\u0441\u043d\u0430\u044f \u0434\u0435\u044f\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0442\u0438\u043a\u0435\u0440\u0430 \u0411\u0443\u0444\u0435\u0440\u0438\u0437\u0430\u0442\u043e\u0440 (MSBufferizer) \u0424\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 MSBufferizer \u0412 \u043f\u0440\u043e\u0448\u043b\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0430\u043b\u0438 \u0441\u0432\u043e\u0439 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440. \u042d\u0442\u0443 \u0441\u0442\u0430\u0442\u044c\u044e \u043c\u044b \u043f\u043e\u0441\u0432\u0435\u0442\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":79541,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-79540","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u0437\u044f\u0442 \u0441 \u043c\u043e\u0435\u0433\u043e \u0434\u0437\u0435\u043d-\u043a\u0430\u043d\u0430\u043b\u0430.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0418\u0437\u0443\u0447\u0430\u0435\u043c VoIP-\u0434\u0432\u0438\u0436\u043e\u043a Mediastreamer2. \u0427\u0430\u0441\u0442\u044c 11 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u0437\u044f\u0442 \u0441 \u043c\u043e\u0435\u0433\u043e \u0434\u0437\u0435\u043d-\u043a\u0430\u043d\u0430\u043b\u0430.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-04-28T05:42:01+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-04-28T05:42:01+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Explorando el motor VoIP Mediastreamer2. Parte 11 | ProHoster","description":"El material del art\u00edculo proviene de mi canal de Zen.","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0418\u0437\u0443\u0447\u0430\u0435\u043c VoIP-\u0434\u0432\u0438\u0436\u043e\u043a Mediastreamer2. \u0427\u0430\u0441\u0442\u044c 11 | ProHoster","og:description":"\u041c\u0430\u0442\u0435\u0440\u0438\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 \u0432\u0437\u044f\u0442 \u0441 \u043c\u043e\u0435\u0433\u043e \u0434\u0437\u0435\u043d-\u043a\u0430\u043d\u0430\u043b\u0430.","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-04-28T05:42:01+00:00","article:modified_time":"2020-04-28T05:42:01+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"79540","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 12:40:33","updated":"2022-10-06 16:26:59","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/79540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=79540"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/79540\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/79541"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=79540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=79540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=79540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}