{"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\/it\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","title":{"rendered":"Esploriamo il motore VoIP Mediastreamer2. Parte 11","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Il materiale dell'articolo \u00e8 tratto dal mio <noindex><a rel=\"nofollow\" href=\"https:\/\/zen.yandex.ru\/profile\/editor\/id\/5e3f8e0751f5346faab4fc7a\">canale zen<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Esploriamo il motore VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/b66fab43eb45c626c50f0383dcfa7cca.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Meccanismo di trasferimento dati<\/p>\n<p><\/p>\n<ul>\n<li>Blocco dati dblk_t<\/li>\n<li>Messaggio mblk_t<\/li>\n<li>Funzioni di gestione dei messaggi mblk_t<\/li>\n<li>Coda queue_t<\/li>\n<li>Funzioni di gestione delle code queue_t<\/li>\n<li>Connessione dei filtri<\/li>\n<li>Punto di segnalazione del grafo di elaborazione dati<\/li>\n<li>Attivit\u00e0 dietro le quinte del ticker<\/li>\n<li>Bufferizzatore (MSBufferizer)<\/li>\n<li>Funzioni di gestione con MSBufferizer<\/li>\n<\/ul>\n<p><\/p>\n<p>Nell'articolo precedente <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/497932\/\">abbiamo chiarito il corretto completamento dei programmi che utilizzano il mediastreamer.<\/a><\/noindex> Abbiamo sviluppato il nostro filtro. Questo articolo \u00e8 dedicato al funzionamento del meccanismo interno di trasferimento dati tra i filtri dello stream media. Ci\u00f2 consentir\u00e0 in futuro di scrivere filtri sofisticati con meno sforzo.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"mehanizm-peremescheniya-dannyh\">Meccanismo di trasferimento dati<\/h2>\n<p><\/p>\n<p>Il trasferimento di dati nello stream media avviene tramite code descritte dalla struttura <em>queue_t<\/em>. Le code trasferiscono sequenze di messaggi di tipo <em>mblk_t<\/em>, che di per s\u00e9 non contengono dati di segnale, ma solo riferimenti al messaggio precedente, successivo e al blocco dati. Inoltre, voglio sottolineare che c'\u00e8 anche un campo per un riferimento a un messaggio dello stesso tipo, che consente di organizzare una lista collegata di messaggi. Il gruppo di messaggi collegati in questo modo sar\u00e0 chiamato tupla. Cos\u00ec, qualsiasi elemento della coda pu\u00f2 essere un messaggio singolo <em>mblk_t<\/em>, oppure la testa di una tupla di messaggi <em>mblk_t<\/em>. Ogni messaggio della tupla pu\u00f2 avere il proprio blocco dati sottostante. Di cosa servono le tuple ne discuteremo pi\u00f9 avanti.<\/p>\n<p><\/p>\n<p>Come detto sopra, il messaggio di per s\u00e9 non contiene un blocco dati, ma contiene solo un puntatore all'area di memoria dove \u00e8 memorizzato il blocco. In questa parte, l'immagine complessiva del funzionamento dello stream media ricorda un magazzino di porte nel cartone animato \"Monsters, Inc.\", dove le porte (riferimenti ai dati \u2014 stanze) si muovono a velocit\u00e0 folle su nastri trasportatori sospesi, mentre le stanze rimangono immobile. <\/p>\n<p><\/p>\n<p>Ora, seguendo l'gerarchia dal basso verso l'alto, esaminiamo in dettaglio le entit\u00e0 elencate dell meccanismo di trasferimento dati nello stream media.<\/p>\n<p><\/p>\n<h3 id=\"blok--dannyh-dblk_t\">Blocco dati <em>dblk_t<\/em><\/h3>\n<p><\/p>\n<p>Il blocco dati consiste in un intestazione e in un buffer di dati. L'intestazione \u00e8 descritta dalla seguente struttura,<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct datab\n{\nunsigned char *db_base; \/\/ Puntatore all'inizio del buffer di dati.\nunsigned char *db_lim;  \/\/ Puntatore alla fine del buffer di dati.\nvoid (*db_freefn)(void*); \/\/ Funzione di liberazione della memoria alla rimozione del blocco.\nint db_ref; \/\/ Contatore dei riferimenti.\n} dblk_t;<\/code><\/pre>\n<p><\/p>\n<p>I campi della struttura contengono puntatori all'inizio del buffer, alla fine del buffer e alla funzione di eliminazione del buffer dati. L'ultimo elemento nell'intestazione <em>db_ref<\/em> \u2014 \u00e8 il contatore dei riferimenti; quando raggiunge zero, questo segnala la rimozione di questo blocco dalla memoria. Se il blocco dati \u00e8 stato creato dalla funzione <em>datab_alloc() <\/em>, il buffer dati sar\u00e0 posizionato nella memoria subito dopo l'intestazione. In tutti gli altri casi, il buffer pu\u00f2 trovarsi da qualche parte separato. Nel buffer dei dati saranno presenti le misurazioni del segnale o altri dati che vogliamo elaborare con i filtri.<\/p>\n<p><\/p>\n<p>Una nuova istanza del blocco dati viene creata con la funzione:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">dblk_t *datab_alloc(int size);<\/code><\/pre>\n<p><\/p>\n<p>Come parametro di ingresso, riceve la dimensione dei dati che il blocco conserver\u00e0. Viene allocata pi\u00f9 memoria per collocare all'inizio della memoria allocata l'intestazione \u2014 struttura <em>datab<\/em>. Ma quando si utilizzano altre funzioni, questo non avviene sempre; in alcuni casi, il buffer dati pu\u00f2 trovarsi separato dall'intestazione del blocco dati. I campi della struttura vengono configurati al momento della creazione in modo che il suo campo <em>db_base<\/em> indichi l'inizio dell'area dati e <em>db_lim<\/em> indichi la sua fine. Il contatore dei riferimenti <em>db_ref<\/em> viene impostato su uno. Il puntatore alla funzione di eliminazione dei dati viene impostato a zero.<\/p>\n<p><\/p>\n<h3 id=\"soobschenie-mblk_t\">Messaggio <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Come gi\u00e0 detto, gli elementi della coda sono di tipo <em>mblk_t,<\/em> definito come segue:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct msgb\n{\n  struct msgb *b_prev;   \\\/\\\/ Puntatore all'elemento precedente della lista.\n  struct msgb *b_next;   \\\/\\\/ Puntatore all'elemento successivo della lista.\n  struct msgb *b_cont;   \\\/\\\/ Puntatore per allegare ad un messaggio altri messaggi, per creare una tupla di messaggi.\n  struct datab *b_datap; \\\/\\\/ Puntatore alla struttura del blocco dati.\n  unsigned char *b_rptr; \\\/\\\/ Puntatore all'inizio dell'area dati per leggere i dati del buffer b_datap.\n  unsigned char *b_wptr; \\\/\\\/ Puntatore all'inizio dell'area dati per scrivere i dati del buffer b_datap.\n  uint32_t reserved1;    \\\/\\\/ Campo riservato1, il mediastreamer colloca informazioni di servizio qui.\n  uint32_t reserved2;    \\\/\\\/ Campo riservato2, il mediastreamer colloca informazioni di servizio qui.\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>Struttura <em>mblk_t<\/em> all'inizio contiene puntatori <em>b_prev<\/em>, <em>b_next<\/em>, necessari per organizzare una lista doppiamente collegata (che \u00e8 la coda <em>queue_t<\/em>). <\/p>\n<p><\/p>\n<p>Poi c'\u00e8 il puntatore <em>b_cont<\/em>, che viene utilizzato solo quando il messaggio entra nella tupla. Per l'ultimo messaggio nella tupla, questo puntatore rimane nullo. <\/p>\n<p><\/p>\n<p>Successivamente vediamo un puntatore al blocco dati <em>b_datap<\/em>, per il quale esiste il messaggio. Seguono i puntatori all'area all'interno del buffer dati del blocco. Il campo <em>b_rptr<\/em> indica il punto da cui i dati verranno letti dal buffer. Il campo <em>b_wptr<\/em> indica il punto da cui i dati verranno scritti nel buffer. <\/p>\n<p><\/p>\n<p>I campi rimanenti sono di natura operativa e non riguardano il funzionamento del meccanismo di trasmissione dei dati.<\/p>\n<p><\/p>\n<p>Di seguito \u00e8 mostrato un singolo messaggio con il nome <em>m1<\/em> e il blocco dati <em>d1<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Esploriamo il motore VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/e6a726b13491832258c1228f03bde802.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNell'immagine successiva \u00e8 illustrata una tupla di tre messaggi <em>m1<\/em>, <em>m1_1<\/em>, <em>m1_2<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Esploriamo il motore 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\">Le funzioni per lavorare con i messaggi <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Un nuovo messaggio <em>mblk_t<\/em> viene creato dalla funzione:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *allocb(int size, int pri); <\/code><\/pre>\n<p><\/p>\n<p>essa allocca in memoria un nuovo messaggio <em>mblk_t<\/em> con un blocco dati di dimensione specificata <em>size<\/em>, il secondo argomento \u2014 <em>pri<\/em> non \u00e8 utilizzato nella versione della libreria in esame. Deve rimanere nullo. Durante l'esecuzione della funzione verr\u00e0 allocata memoria per la struttura del nuovo messaggio e verr\u00e0 chiamata la funzione <em>mblk_init()<\/em>, che azzerer\u00e0 tutti i campi dell'istanza della struttura creata e poi, tramite la suddetta <em>datab_alloc()<\/em>, creer\u00e0 il buffer dati. Successivamente verranno configurati i campi della struttura: <\/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>In uscita otteniamo un nuovo messaggio con i campi inizializzati e un buffer dati vuoto. Per aggiungere dati al messaggio, \u00e8 necessario copiarli nel buffer del blocco dati:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">memcpy(msg-&gt;b_rptr, data, size);<\/code><\/pre>\n<p><\/p>\n<p>dove <em>data<\/em> \u2014 puntatore alla sorgente dei dati, e <em>size<\/em> \u2014 la loro dimensione.<br \/>\nSuccessivamente \u00e8 necessario aggiornare il puntatore al punto di scrittura affinch\u00e9 punti nuovamente all'inizio dell'area libera nel buffer:<\/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>Se \u00e8 necessario creare un messaggio da un buffer gi\u00e0 esistente, senza copiare, allora si utilizza la funzione:<\/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 funzione, dopo aver creato il messaggio e la struttura del blocco dati, configurer\u00e0 i suoi puntatori ai dati all'indirizzo <em>buf<\/em>. Cio\u00e8, in questo caso, il buffer dati non si trova a seguire i campi dell'intestazione del blocco dati, come avveniva nella creazione del blocco dati tramite la funzione. <em>datab_alloc()<\/em>. Il buffer di dati passato alla funzione rimarr\u00e0 dov'\u00e8, ma mediante puntatori sar\u00e0 indirizzato all'intestazione del blocco dati appena creata, e quella a sua volta al messaggio.<\/p>\n<p><\/p>\n<p>A un messaggio <em>mblk_t<\/em> possono essere concatenati pi\u00f9 blocchi di dati in modo sequenziale. Questo avviene tramite la funzione:<\/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> \u00e8 il messaggio a cui verr\u00e0 aggiunto un altro blocco di dati;<br \/>\n<em>data<\/em> \u00e8 un puntatore al blocco, di cui verr\u00e0 fatta una copia nel messaggio;<br \/>\n<em>size<\/em> \u00e8 la dimensione dei dati;<br \/>\n<em>pad<\/em> \u00e8 il flag che indica se la dimensione della memoria allocata deve essere allineata a un confine di 4 byte (il riempimento verr\u00e0 effettuato con zeri). <\/p>\n<p><\/p>\n<p>Se nel buffer di dati del messaggio c'\u00e8 abbastanza spazio, i nuovi dati verranno concatenati ai dati gi\u00e0 presenti. Se lo spazio disponibile nel buffer di dati del messaggio \u00e8 inferiore a <em>size<\/em>, verr\u00e0 creato un nuovo messaggio, con una dimensione del buffer adeguata, e i dati verranno copiati nel suo buffer. Questo nuovo messaggio viene agganciato a quello originale tramite un puntatore <em>b_cont<\/em>. In questo caso, il messaggio diventa una tupla. <\/p>\n<p><\/p>\n<p>Se nella tupla \u00e8 necessario aggiungere un altro blocco di dati, bisogna usare la funzione:<\/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>essa trover\u00e0 l'ultimo messaggio nella tupla (avr\u00e0 <em>b_cont<\/em> zero) e invoker\u00e0 per quel messaggio la funzione <em>appendb()<\/em>.<\/p>\n<p><\/p>\n<p>Per conoscere la dimensione dei dati in un messaggio o in una tupla, si pu\u00f2 usare la funzione:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>essa esaminer\u00e0 tutti i messaggi della tupla e restituir\u00e0 il numero totale di dati nei buffer di dati di questi messaggi. Per ogni messaggio, la quantit\u00e0 di dati si calcola come:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\"> mp-&gt;b_wptr - mp-&gt;b_rptr<\/code><\/pre>\n<p><\/p>\n<p>Per unire due tuple si utilizza la funzione:<\/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>essa attacca la tupla <em>newm<\/em> alla coda della tupla <em>mp<\/em> e restituisce un puntatore all'ultimo messaggio della tupla risultante.<\/p>\n<p><\/p>\n<p>Se necessario, la tupla pu\u00f2 essere trasformata in un unico messaggio con un blocco di dati unico, ci\u00f2 avviene tramite la funzione:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,int len);<\/code><\/pre>\n<p><\/p>\n<p>se l'argomento <em>len<\/em> \u00e8 uguale a -1, la dimensione del buffer allocato viene determinata automaticamente. Se <em>len<\/em> se \u00e8 un numero positivo, verr\u00e0 creato un buffer di questa dimensione e in esso verranno copiati i dati dei messaggi della tupla. Se il buffer si esaurisce, la copia verr\u00e0 interrotta. Il primo messaggio della tupla ricever\u00e0 un buffer di nuova dimensione con i dati copiati. Gli altri messaggi verranno eliminati e la memoria restituita all'heap.<\/p>\n<p><\/p>\n<p>Quando si elimina una struttura <em>mblk_t<\/em> si tiene conto del contatore dei riferimenti del blocco di dati, se durante la chiamata <em>freeb()<\/em> risulta essere zero, il buffer dei dati viene eliminato insieme all'istanza <em>mblk_t<\/em>, a cui punta.<\/p>\n<p><\/p>\n<p>Inizializzazione dei campi del nuovo messaggio:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void mblk_init(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Aggiunta a un messaggio di un'altra porzione di dati: <\/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>Se i nuovi dati non possono essere inseriti nello spazio libero del buffer dei dati del messaggio, allora al messaggio viene allegato un messaggio creato separatamente con un buffer della dimensione necessaria (nel primo messaggio viene impostato un puntatore al messaggio aggiunto) e il messaggio si trasforma in una tupla.<\/p>\n<p><\/p>\n<p>Aggiunta di una porzione di dati alla 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 funzione chiama appendb() in un ciclo.<\/p>\n<p><\/p>\n<p>Unione di due tuple in una sola:<\/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>Messaggio <em>newm<\/em> verr\u00e0 aggiunto a <em>mp<\/em>.<\/p>\n<p><\/p>\n<p>Creazione di una copia di un singolo messaggio:<\/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 della tupla con tutti i blocchi di dati:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copymsg(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Gli elementi della tupla vengono copiati dalla funzione <em>copyb()<\/em>.<\/p>\n<p><\/p>\n<p>Creazione di una copia leggera del messaggio <em>mblk_t<\/em>. In questo caso, il blocco di dati non viene copiato, ma il contatore dei suoi riferimenti viene incrementato <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>Creazione di una copia leggera della tupla. I blocchi di dati non vengono copiati, vengono solo incrementati i loro contatori di riferimenti <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>Unione di tutti i messaggi della tupla in un unico messaggio:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,size_t len);<\/code><\/pre>\n<p><\/p>\n<p>Se l'argomento <em>len<\/em> \u00e8 uguale a -1, la dimensione del buffer assegnato viene determinata automaticamente.<\/p>\n<p><\/p>\n<p>Rimozione del messaggio, della tupla:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void freemsg(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Il contatore dei riferimenti del blocco di dati viene decrementato di uno. Se raggiunge zero, anche il blocco di dati viene eliminato.<\/p>\n<p><\/p>\n<p>Calcolo del volume totale di dati nel messaggio o nella tupla.<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">size_t msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Estrazione del messaggio dalla coda:<\/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 contenuto dei campi riservati di un messaggio in un altro messaggio (in realt\u00e0 in questi campi si trovano le flag utilizzate dal media streamer):<\/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\">Coda <em>queue_t<\/em><\/h3>\n<p><\/p>\n<p>La coda di messaggi nel media streamer \u00e8 realizzata come una lista doppiamente collegata circolare. Ogni elemento della lista contiene un puntatore a un blocco di dati con le misurazioni del segnale. Ci\u00f2 significa che si spostano solo i puntatori ai blocchi di dati, mentre i dati stessi rimangono fermi. Vale a dire, si muovono solo i riferimenti ad essi.<br \/>\nStruttura che descrive la coda <em>queue_t<\/em>, mostrata di seguito:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _queue\n{\n   mblk_t _q_stopper; \/* \"Elemento vuoto\" della coda, non punta ai dati, utilizzato solo per gestire la coda. Durante l'inizializzazione della coda (qinit()) i suoi puntatori vengono configurati in modo che puntino a se stesso. *\/\n   int q_mcount;        \/\/ Numero di elementi nella coda.\n} queue_t;<\/code><\/pre>\n<p><\/p>\n<p>La struttura contiene un campo - puntatore <em>_q_stopper<\/em> di tipo *mblk_t, che punta al primo elemento (messaggio) nella coda. Il secondo campo della struttura \u00e8 un contatore dei messaggi presenti nella coda.<br \/>\nNella figura sottostante \u00e8 mostrata la coda di nome q1, contenente 4 messaggi m1, m2, m3, m4.<br \/>\n<img decoding=\"async\" alt=\"Esploriamo il motore VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/07fc043861edf15e5eb40e49c8ec5e3e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNella figura successiva \u00e8 mostrata la coda di nome q1, contenente 4 messaggi m1, m2, m3, m4. Il messaggio m2 \u00e8 la testa della tupla, a cui si collegano altri due messaggi m2_1 e m2_2.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Esploriamo il motore 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\">Funzioni di gestione delle code queue_t<\/h3>\n<p><\/p>\n<p>Inizializzazione della coda:<\/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> (d'ora in poi chiamato \"stopper\") viene inizializzato dalla funzione <em>mblk_init()<\/em>, il suo puntatore all'elemento precedente e successivo viene configurato in modo che punti a se stesso. Il contatore degli elementi nella coda viene azzerato.<\/p>\n<p><\/p>\n<p>Aggiunta di un nuovo elemento (messaggio):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void putq(queue_t *q, mblk_t *m);<\/code><\/pre>\n<p><\/p>\n<p>Il nuovo elemento <em>m<\/em> viene aggiunto alla fine della lista, i puntatori dell'elemento vengono configurati in modo che lo stopper diventi l'elemento successivo per lui, mentre lui diventa l'elemento precedente per lo stopper. Il contatore degli elementi nella coda viene incrementato.<\/p>\n<p><\/p>\n<p>Estrazione di un elemento dalla coda:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * getq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>viene estratto il messaggio che si trova dopo lo stopper, il contatore degli elementi viene decrementato. Se nella coda, oltre allo stopper, non ci sono altri elementi, viene restituito 0.<\/p>\n<p><\/p>\n<p>Inserimento di un messaggio nella coda:<\/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>Elemento <em>mp<\/em> viene inserito prima dell'elemento <em>emp<\/em>Se <em>emp<\/em>=0, quindi il messaggio viene aggiunto alla coda.<\/p>\n<p><\/p>\n<p>Estrazione del messaggio dalla testa della coda:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void remq(queue_t *q, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>Il contatore degli elementi viene decrementato.<\/p>\n<p><\/p>\n<p>Lettura del puntatore al primo elemento nella coda:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * peekq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>Cancellazione di tutti gli elementi dalla coda con eliminazione degli stessi elementi:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void flushq(queue_t *q, int how);<\/code><\/pre>\n<p><\/p>\n<p>Argomento <em>how<\/em> non utilizzato. Il contatore degli elementi della coda \u00e8 impostato a zero.<\/p>\n<p><\/p>\n<p>Macro per leggere il puntatore all'ultimo elemento della coda:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * qlast(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Quando si lavora con le code di messaggi, \u00e8 importante tenere presente che con la chiamata <em>ms_queue_put(q, m)<\/em> con un puntatore nullo al messaggio, la funzione va in loop. Il tuo programma si bloccher\u00e0. Si comporta allo stesso modo <em>ms_queue_next(q, m)<\/em>.<\/p>\n<p><\/p>\n<h3 id=\"soedinenie-filtrov\">Connessione dei filtri<\/h3>\n<p><\/p>\n<p>La coda descritta sopra viene utilizzata per trasmettere messaggi da un filtro all'altro o da un filtro a pi\u00f9 filtri. I filtri e le loro connessioni formano un grafo orientato. L'ingresso o l'uscita di un filtro vengono chiamati genericamente \"pin\". Per descrivere l'ordine delle connessioni tra i filtri, nel mediastreamer si utilizza il concetto di \"punto di segnale\". Il punto di segnale \u00e8 una struttura <em>_MSCPoint<\/em>, che contiene un puntatore al filtro e il numero di uno dei suoi pin, descrivendo cos\u00ec la connessione di uno degli ingressi o delle uscite del filtro. <\/p>\n<p><\/p>\n<h4 id=\"signalnaya-tochka-grafa-obrabotki-dannyh\">Punto di segnalazione del grafo di elaborazione dati<\/h4>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSCPoint{\nstruct _MSFilter *filter; \\\/\\\/ Puntatore al filtro del mediastreamer.\nint pin;                        \\\/\\\/ Numero di uno degli ingressi o delle uscite del filtro, ovvero un pin.\n} MSCPoint;\n<\/code><\/pre>\n<p><\/p>\n<p>I pin dei filtri sono numerati a partire da zero. <\/p>\n<p><\/p>\n<p>La connessione di due pin tramite la coda di messaggi \u00e8 descritta dalla struttura <em>_MSQueue<\/em>, che contiene la coda dei messaggi e puntatori a due punti di segnale che connette:<\/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>Ci riferiremo a questa struttura come link di segnale. Ogni filtro del mediastreamer contiene una tabella di link di ingresso e una tabella di link di uscita (<em>MSQueue<\/em>). La dimensione delle tabelle \u00e8 definita al momento della creazione del filtro, ci\u00f2 che abbiamo gi\u00e0 fatto tramite la variabile esportata di tipo <em>MSFilterDesc<\/em>, quando abbiamo sviluppato il nostro filtro. Di seguito \u00e8 mostrata la struttura che descrive qualsiasi filtro nel mediastreamer, <em>MSFilter<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">\nstruct _MSFilter{\n    MSFilterDesc *desc;    \n    \/* Puntatore al descrittore del filtro. *\/\n    \/* Attributi protetti, non possono essere spostati o rimossi altrimenti il funzionamento con i plugin sar\u00e0 compromesso. *\/\n    ms_mutex_t lock;      \n    \/* Semaforo. *\/\n    MSQueue **inputs;     \n    \/* Tabella dei collegamenti di input. *\/\n    MSQueue **outputs;    \n    \/* Tabella dei collegamenti di output. *\/\n    struct _MSFactory *factory; \n    \/* Puntatore alla fabbrica che ha creato questa istanza del filtro. *\/\n    void *padding;              \n    \/* Non utilizzato, sar\u00e0 attivato se saranno aggiunti campi protetti. *\/\n    void *data;                 \n    \/* Puntatore a una struttura arbitraria per memorizzare i dati dello stato interno del filtro e i calcoli intermedi. *\/\n    struct _MSTicker *ticker;   \n    \/* Puntatore all'oggetto ticker, che non deve essere nullo quando viene chiamata la funzione process(). *\/\n    \/* attributi privati, possono essere spostati e modificati in qualsiasi momento *\/\n    MSList *notify_callbacks; \n    \/* Elenco delle callback utilizzate per gestire gli eventi del filtro. *\/\n    uint32_t last_tick;       \n    \/* Numero dell'ultimo tick quando \u00e8 stata eseguita la chiamata process(). *\/\n    MSFilterStats *stats;     \n    \/* Statistiche sul funzionamento del filtro. *\/\n    int postponed_task; \/* Numero di attivit\u00e0 posticipate. Alcuni filtri possono posticipare l'elaborazione dei dati (chiamata process()) per diversi tick. *\/\n    bool_t seen;  \/* Flag utilizzato dal ticker per contrassegnare che questa istanza del filtro \u00e8 gi\u00e0 stata trattata in questo tick. *\/\n};\ntypedef struct _MSFilter MSFilter;\n<\/code><\/pre>\n<p><\/p>\n<p>Dopo aver collegato i filtri nel nostro programma C secondo la nostra concezione (ma senza collegare il ticker), abbiamo cos\u00ec creato un grafo orientato, i cui nodi sono istanze della struttura <em>MSFilter<\/em>, e gli archi sono istanze dei collegamenti <em>MSQueue<\/em>. <\/p>\n<p><\/p>\n<h3 id=\"zakulisnaya-deyatelnost-tikera\">Attivit\u00e0 dietro le quinte del ticker<\/h3>\n<p><\/p>\n<p>Quando ti ho detto che il ticker \u00e8 un filtro sorgente di tick, non era tutta la verit\u00e0 su di lui. Il ticker \u00e8 un oggetto che esegue l'avvio delle funzioni in base agli orari <em>process()<\/em> di tutti i filtri dello schema (grafo) a cui \u00e8 collegato. Quando colleghiamo il ticker a un filtro del grafo nel programma C, mostriamo al ticker il grafo che da quel momento gestir\u00e0, finch\u00e9 non lo disconnettiamo. Dopo il collegamento, il ticker inizia a esaminare il grafo a lui affidato, creando un elenco dei filtri di cui fa parte. Per non \"contare\" lo stesso filtro due volte, segna i filtri scoperti impostando un flag <em>seen<\/em>. La ricerca avviene nelle tabelle dei collegamenti che ha ogni filtro. <\/p>\n<p><\/p>\n<p>Durante il suo tour di orientamento tramite il grafico, il ticker verifica se c'\u00e8 almeno un filtro che svolge la funzione di sorgente dei blocchi di dati. Se tali filtri non vengono trovati, il grafico viene considerato errato e il ticker termina l'esecuzione in modo anomalo.<\/p>\n<p><\/p>\n<p>Se il grafico si \u00e8 rivelato \"corretto\", per ogni filtro trovato viene chiamata la funzione di inizializzazione <em>preprocess()<\/em>. Non appena arriva il momento dell'iterazione successiva di elaborazione (di default ogni 10 millisecondi), il ticker richiama la funzione <em>process()<\/em> per tutti i filtri sorgente trovati in precedenza, e poi per gli altri filtri nell'elenco. Se un filtro ha link di ingresso, il lancio della funzione <em>process()<\/em> viene ripetuto finch\u00e9 le code dei link di ingresso non sono vuote. Dopodich\u00e9, si passa al filtro successivo nell'elenco e lo \"scorre\" fino a liberare i link di ingresso dai messaggi. Il ticker passa da un filtro all'altro finch\u00e9 non finisce l'elenco. A questo punto termina l'elaborazione dell'iterazione. <\/p>\n<p><\/p>\n<p>Ora torniamo alle tupla e parliamo del motivo per cui \u00e8 stata aggiunta una tale entit\u00e0 nello stream di dati. In generale, la quantit\u00e0 di dati richiesta dall'algoritmo che opera all'interno del filtro non coincide e non \u00e8 un multiplo della dimensione dei buffer di dati che arrivano in ingresso. Ad esempio, scriviamo un filtro che esegue una veloce trasformazione di Fourier, la quale per definizione pu\u00f2 elaborare solo blocchi di dati la cui dimensione \u00e8 una potenza di due. Supponiamo sia 512 campioni. Se i dati vengono generati tramite un canale telefonico, il buffer di dati di ogni messaggio in ingresso ci porter\u00e0 160 campioni di segnale. C'\u00e8 la tentazione di non prelevare dati dall'ingresso finch\u00e9 non ci sar\u00e0 la quantit\u00e0 necessaria. Ma in questo caso ci sar\u00e0 una collisione con il ticker, che cercher\u00e0 invano di far avanzare il filtro fino a svuotare il link di ingresso. In precedenza abbiamo definito questa regola come il terzo principio di funzionamento del filtro. Secondo questo principio, la funzione process() del filtro deve prelevare tutti i dati dalle code di ingresso.<\/p>\n<p><\/p>\n<p>Oltre a questo, non sar\u00e0 possibile prelevare solo 512 campioni all'ingresso, poich\u00e9 i campioni possono essere prelevati solo in blocchi interi, cio\u00e8 il filtro dovr\u00e0 prelevare 640 campioni e, utilizzando 512 di essi, il resto rimarr\u00e0 in attesa di un nuovo accumulo di dati. In questo modo, il nostro filtro, oltre al suo lavoro principale, deve garantire azioni ausiliarie per l'immagazzinamento temporaneo dei dati in entrata. Gli sviluppatori dello streaming multimediale, per risolvere questo problema comune, hanno creato un oggetto speciale \u2014 MSBufferizer (buffer), che affronta questa questione tramite tuple. <\/p>\n<p><\/p>\n<h3 id=\"buferizator-msbufferizer\">Bufferizzatore (MSBufferizer)<\/h3>\n<p><\/p>\n<p>Questo oggetto accumuler\u00e0 i dati in ingresso all'interno del filtro e inizier\u00e0 a passarli in elaborazione non appena la quantit\u00e0 di informazioni sar\u00e0 sufficiente per l'esecuzione dell'algoritmo del filtro. Finch\u00e9 il buffer accumula dati, il filtro funzioner\u00e0 in modalit\u00e0 idle, senza utilizzare potenza di calcolo della CPU. Ma quando la funzione di lettura dal buffer restituir\u00e0 un valore diverso da zero, la funzione process() del filtro inizier\u00e0 a prelevare e elaborare i dati dal buffer in porzioni della dimensione necessaria, fino all'esaurimento.<br \/>\nI dati ancora non utilizzati rimangono nel buffer come primo elemento della tupla, a cui si attaccano i successivi blocchi di dati in ingresso. <\/p>\n<p><\/p>\n<p>Struttura che descrive il buffer: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">struct _MSBufferizer{\nqueue_t q; \/* Coda dei messaggi. *\/\nint size; \/* Dimensione totale dei dati attualmente presenti nel buffer. *\/\n};\ntypedef struct _MSBufferizer MSBufferizer;<\/code><\/pre>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-msbufferizer\">Funzioni di gestione con MSBufferizer<\/h3>\n<p><\/p>\n<p>Creazione di una nuova istanza del buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">MSBufferizer * ms_bufferizer_new(void);<\/code><\/pre>\n<p><\/p>\n<p>Viene allocata memoria, inizializzata in <em>ms_bufferizer_init()<\/em> e restituisce un puntatore.<\/p>\n<p><\/p>\n<p>Funzione di inizializzazione:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_init(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Si inizializza la coda <em>q<\/em>, il campo <em>size<\/em> viene impostato a zero.<\/p>\n<p><\/p>\n<p>Aggiunta di un messaggio:<\/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>Il messaggio m viene aggiunto alla coda. La dimensione calcolata dei blocchi di dati viene aggiunta a <em>size<\/em>.<\/p>\n<p><\/p>\n<p>Trasferimento nel buffer di tutti i messaggi della coda dei dati del link <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>Il trasferimento dei messaggi dal link <em>q<\/em> nel buffer avviene tramite la funzione <em>ms_bufferizer_put()<\/em>.<\/p>\n<p><\/p>\n<p>Lettura dal buffer:<\/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>Se la dimensione dei dati accumulati nel buffer risulta inferiore a quella richiesta (<em>datalen<\/em>), la funzione restituisce zero, la copia dei dati in data non viene eseguita. Altrimenti, viene eseguita una copia sequenziale dei dati dalle tuple presenti nel buffer. Dopo la copia, la tupla viene rimossa e la memoria liberata. La copia termina quando vengono copiati datalen byte. Se lo spazio finisce a met\u00e0 di un blocco di dati, in questo messaggio, il blocco di dati sar\u00e0 accorciato alla parte rimanente non copiata. Alla chiamata successiva, la copia continuer\u00e0 da questo punto.<\/p>\n<p><\/p>\n<p>Lettura della quantit\u00e0 di dati disponibili al momento nel buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_get_avail(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Restituisce il campo <em>size<\/em> del buffer.<\/p>\n<p><\/p>\n<p>Scarto di parte dei dati presenti nel buffer:<\/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>Il numero specificato di byte di dati viene estratto e scartato. Vengono scartati i dati pi\u00f9 vecchi.<\/p>\n<p><\/p>\n<p>Eliminazione di tutti i messaggi presenti nel buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_flush(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Il contatore dei dati viene azzerato.<\/p>\n<p><\/p>\n<p>Eliminazione di tutti i messaggi presenti nel buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_uninit(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>L'azzeramento del contatore non viene eseguito.<\/p>\n<p><\/p>\n<p>Eliminazione del buffer e liberazione della memoria:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_destroy(MSBufferizer *obj);  <\/code><\/pre>\n<p><\/p>\n<p>Esempi di utilizzo del buffer possono essere trovati nel codice sorgente di diversi filtri del media streamer. Ad esempio, nel filtro MS_L16_ENC, che esegue la riorganizzazione dei byte nei campioni dal formato di rete, al formato 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>Nell'articolo seguente, esamineremo la questione della valutazione del carico sul ticker e i metodi per affrontare un'eccessiva carico computazionale nel media streamer.<\/p>\n<p>Fonte: <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.1.1 - 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\/it\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"it_IT\" \/>\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\/it\/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\udd47Studiamo il motore VoIP Mediastreamer2. Parte 11 | ProHoster","description":"Il materiale dell'articolo \u00e8 tratto dal mio canale Zen.","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/79540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=79540"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/79540\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/79541"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=79540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=79540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=79540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}