{"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\/pt\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","title":{"rendered":"Estudando o motor VoIP Mediastreamer2. Parte 11","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>O conte\u00fado deste artigo foi retirado do meu <noindex><a rel=\"nofollow\" href=\"https:\/\/zen.yandex.ru\/profile\/editor\/id\/5e3f8e0751f5346faab4fc7a\">canal Zen<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Estudando o 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 movimenta\u00e7\u00e3o de dados<\/p>\n<p><\/p>\n<ul>\n<li>Bloco de dados dblk_t<\/li>\n<li>Mensagem mblk_t<\/li>\n<li>Fun\u00e7\u00f5es de opera\u00e7\u00e3o com mensagens mblk_t<\/li>\n<li>Fila queue_t<\/li>\n<li>Fun\u00e7\u00f5es de opera\u00e7\u00e3o com filas queue_t<\/li>\n<li>Conex\u00e3o de filtros<\/li>\n<li>Ponto de sinal do grafo de processamento de dados<\/li>\n<li>Atividades nos bastidores do ticker<\/li>\n<li>Bufferizador (MSBufferizer)<\/li>\n<li>Fun\u00e7\u00f5es de opera\u00e7\u00e3o com MSBufferizer<\/li>\n<\/ul>\n<p><\/p>\n<p>Na parte anterior <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/497932\/\">artigo<\/a><\/noindex> Desenvolvemos nosso pr\u00f3prio filtro. Este artigo ser\u00e1 dedicado \u00e0 estrutura do mecanismo interno de movimenta\u00e7\u00e3o de dados entre filtros de media streamer. Isso permitir\u00e1, posteriormente, criar filtros complexos com menor esfor\u00e7o.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"mehanizm-peremescheniya-dannyh\">Mecanismo de movimenta\u00e7\u00e3o de dados<\/h2>\n<p><\/p>\n<p>A movimenta\u00e7\u00e3o de dados no media streamer \u00e9 realizada atrav\u00e9s de filas descritas pela estrutura <em>queue_t<\/em>. Atrav\u00e9s das filas, movem-se sequ\u00eancias de mensagens do tipo <em>mblk_t<\/em>, que em si mesmas n\u00e3o cont\u00eam dados do sinal, mas apenas refer\u00eancias \u00e0 mensagem anterior, \u00e0 pr\u00f3xima mensagem e ao bloco de dados. Al\u00e9m disso, quero enfatizar que h\u00e1 um campo para refer\u00eancia a uma mensagem do mesmo tipo, que permite organizar uma lista encadeada de mensagens. O grupo de mensagens, unidas por essa lista, ser\u00e1 chamado de tupla. Assim, qualquer elemento da fila pode ser uma \u00fanica mensagem <em>mblk_t<\/em>, ou pode ser a cabe\u00e7a de uma tupla de mensagens <em>mblk_t<\/em>. Cada mensagem da tupla pode ter seu pr\u00f3prio bloco de dados associado. A necessidade das tuplas ser\u00e1 discutida um pouco mais adiante.<\/p>\n<p><\/p>\n<p>Como mencionado anteriormente, a mensagem em si n\u00e3o cont\u00e9m um bloco de dados, em vez disso, ela cont\u00e9m apenas um ponteiro para a \u00e1rea de mem\u00f3ria onde o bloco est\u00e1 armazenado. Nesta parte, a vis\u00e3o geral do funcionamento do media streamer lembra o armaz\u00e9m de portas do filme \"Monsters, Inc.\", onde as portas (links para dados \u2014 salas) se movem a uma velocidade insana pelos transportadores suspensos, enquanto as pr\u00f3prias salas permanecem im\u00f3veis. <\/p>\n<p><\/p>\n<p>Agora, avan\u00e7ando pela hierarquia de baixo para cima, vamos analisar detalhadamente as entidades mencionadas do mecanismo de transmiss\u00e3o de dados no media streamer.<\/p>\n<p><\/p>\n<h3 id=\"blok--dannyh-dblk_t\">Bloco de dados <em>dblk_t<\/em><\/h3>\n<p><\/p>\n<p>Um bloco de dados \u00e9 composto por um cabe\u00e7alho e um buffer de dados. O cabe\u00e7alho \u00e9 descrito pela seguinte estrutura,<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct datab\n{\nunsigned char *db_base; \/\/ Ponteiro para o in\u00edcio do buffer de dados.\nunsigned char *db_lim;  \/\/ Ponteiro para o final do buffer de dados.\nvoid (*db_freefn)(void*); \/\/ Fun\u00e7\u00e3o para liberar mem\u00f3ria ao remover o bloco.\nint db_ref; \/\/ Contador de refer\u00eancias.\n} dblk_t;<\/code><\/pre>\n<p><\/p>\n<p>Os campos da estrutura cont\u00eam ponteiros para o in\u00edcio do buffer, o fim do buffer e a fun\u00e7\u00e3o de remo\u00e7\u00e3o do buffer de dados. O \u00faltimo elemento no cabe\u00e7alho <em>db_ref<\/em> \u2014 \u00e9 um contador de refer\u00eancias, que, ao atingir zero, serve como sinal para a remo\u00e7\u00e3o desse bloco da mem\u00f3ria. Se o bloco de dados foi criado pela fun\u00e7\u00e3o <em>datab_alloc() <\/em>, o buffer de dados ser\u00e1 alocado na mem\u00f3ria logo ap\u00f3s o cabe\u00e7alho. Em todos os outros casos, o buffer pode estar localizado em um lugar separado. No buffer de dados estar\u00e3o os sinais ou outros dados que queremos processar atrav\u00e9s de filtros.<\/p>\n<p><\/p>\n<p>Uma nova inst\u00e2ncia do bloco de dados \u00e9 criada usando a fun\u00e7\u00e3o:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">dblk_t *datab_alloc(int size);<\/code><\/pre>\n<p><\/p>\n<p>Como par\u00e2metro de entrada, ela recebe o tamanho dos dados que o bloco armazenar\u00e1. A mem\u00f3ria \u00e9 alocada maior para que no in\u00edcio da mem\u00f3ria alocada seja colocado o cabe\u00e7alho \u2014 a estrutura <em>datab<\/em>. Mas ao usar outras fun\u00e7\u00f5es, isso n\u00e3o acontece sempre; em alguns casos, o buffer de dados pode estar separado do cabe\u00e7alho do bloco de dados. Os campos da estrutura s\u00e3o configurados ao criar para que seu campo <em>db_base<\/em> aponte para o in\u00edcio da \u00e1rea de dados, e <em>db_lim<\/em> para o seu fim. O contador de refer\u00eancias \u00e9 configurado para um. O ponteiro da fun\u00e7\u00e3o de limpeza de dados \u00e9 configurado para zero. <em>db_ref<\/em> Como foi dito, os elementos da fila t\u00eam o tipo<\/p>\n<p><\/p>\n<h3 id=\"soobschenie-mblk_t\">Mensagem <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>mblk_t, <em>ele \u00e9 definido da seguinte maneira:<\/em> typedef struct msgb\n{\n  struct msgb *b_prev;   \/\/ Ponteiro para o elemento anterior da lista.\n  struct msgb *b_next;   \/\/ Ponteiro para o pr\u00f3ximo elemento da lista.\n  struct msgb *b_cont;   \/\/ Ponteiro para anexar a mensagem outras mensagens, para criar uma tupla de mensagens.\n  struct datab *b_datap; \/\/ Ponteiro para a estrutura do bloco de dados.\n  unsigned char *b_rptr; \/\/ Ponteiro para o in\u00edcio da \u00e1rea de dados para ler os dados do buffer b_datap.\n  unsigned char *b_wptr; \/\/ Ponteiro para o in\u00edcio da \u00e1rea de dados para gravar os dados do buffer b_datap.\n  uint32_t reserved1;    \/\/ Campo reservado 1, o media streamer coloca informa\u00e7\u00f5es de servi\u00e7o aqui.\n  uint32_t reserved2;    \/\/ Campo reservado 2, o media streamer coloca informa\u00e7\u00f5es de servi\u00e7o aqui.\n  #if defined(ORTP_TIMESTAMP)\n  struct timeval timestamp;\n  #endif\n  ortp_recv_addr_t recv_addr;\n} mblk_t;<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">no in\u00edcio cont\u00e9m ponteiros<\/code><\/pre>\n<p><\/p>\n<p>Estrutura <em>mblk_t<\/em> b_prev <em>b_next<\/em>, <em>, que s\u00e3o necess\u00e1rios para organizar uma lista duplamente encadeada (que \u00e9 a fila<\/em>Em seguida, h\u00e1 um ponteiro <em>queue_t<\/em>). <\/p>\n<p><\/p>\n<p>b_cont <em>b_cont<\/em>, que \u00e9 usado apenas quando a mensagem entra na tupla. Para a \u00faltima mensagem na tupla, este ponteiro permanece nulo. <\/p>\n<p><\/p>\n<p>Em seguida, vemos um ponteiro para o bloco de dados <em>b_datap<\/em>, para o qual existe a mensagem. Depois seguem os ponteiros para a \u00e1rea dentro do buffer de dados do bloco. O campo <em>b_rptr<\/em> indica o local do qual os dados ser\u00e3o lidos do buffer. O campo <em>b_wptr<\/em> indica o local do qual os dados ser\u00e3o escritos no buffer. <\/p>\n<p><\/p>\n<p>Os campos restantes s\u00e3o de natureza auxiliar e n\u00e3o se relacionam com o funcionamento do mecanismo de transfer\u00eancia de dados.<\/p>\n<p><\/p>\n<p>Abaixo est\u00e1 uma mensagem \u00fanica chamada <em>m1<\/em> e um bloco de dados. <em>d1<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Estudando o motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/e6a726b13491832258c1228f03bde802.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa pr\u00f3xima figura, \u00e9 apresentado uma tupla com tr\u00eas mensagens <em>m1<\/em>, <em>m1_1<\/em>, <em>m1_2<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Estudando o 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\">As fun\u00e7\u00f5es para trabalhar com mensagens <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Uma nova mensagem <em>mblk_t<\/em> \u00e9 criada pela fun\u00e7\u00e3o:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *allocb(int size, int pri); <\/code><\/pre>\n<p><\/p>\n<p>ela aloca na mem\u00f3ria uma nova mensagem <em>mblk_t<\/em> com um bloco de dados de tamanho especificado <em>tamanho<\/em>, o segundo argumento \u2014 <em>pri<\/em> n\u00e3o \u00e9 utilizado na vers\u00e3o da biblioteca em quest\u00e3o. Ele deve permanecer nulo. Durante a execu\u00e7\u00e3o da fun\u00e7\u00e3o, mem\u00f3ria ser\u00e1 alocada para a estrutura da nova mensagem e a fun\u00e7\u00e3o <em>mblk_init()<\/em>, que zerar\u00e1 todos os campos da inst\u00e2ncia criada da estrutura e, em seguida, com o mencionado acima, <em>datab_alloc()<\/em>criar\u00e1 o buffer de dados. Depois disso, os campos na estrutura ser\u00e3o configurados: <\/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>No final, obtemos uma nova mensagem com os campos inicializados e um buffer de dados vazio. Para adicionar dados \u00e0 mensagem, \u00e9 necess\u00e1rio copi\u00e1-los para o buffer do bloco de dados:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">memcpy(msg-&gt;b_rptr, data, size);<\/code><\/pre>\n<p><\/p>\n<p>onde <em>data<\/em> \u2014 \u00e9 o ponteiro para a fonte de dados, e <em>tamanho<\/em> \u2014 \u00e9 o seu tamanho.<br \/>\nEm seguida, precisamos atualizar o ponteiro da posi\u00e7\u00e3o de grava\u00e7\u00e3o para que ele aponte novamente para o in\u00edcio da \u00e1rea livre no 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 for necess\u00e1rio criar uma mensagem a partir de um buffer j\u00e1 existente, sem c\u00f3pia, usa-se a fun\u00e7\u00e3o:<\/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>A fun\u00e7\u00e3o, ap\u00f3s criar a mensagem e a estrutura do bloco de dados, ajustar\u00e1 seus ponteiros para os dados no endere\u00e7o <em>buf<\/em>. Ou seja, neste caso, o buffer de dados n\u00e3o est\u00e1 localizado imediatamente ap\u00f3s os campos do cabe\u00e7alho do bloco de dados, como foi ao criar o bloco de dados pela fun\u00e7\u00e3o. <em>datab_alloc()<\/em>O buffer de dados passado para a fun\u00e7\u00e3o permanecer\u00e1 onde estava, mas, com a ajuda dos ponteiros, ser\u00e1 acionado para o cabe\u00e7alho de bloco de dados rec\u00e9m-criado, que, por sua vez, se conectar\u00e1 \u00e0 mensagem.<\/p>\n<p><\/p>\n<p>A uma mensagem <em>mblk_t<\/em> podem ser anexados v\u00e1rios blocos de dados sequencialmente. Isso \u00e9 feito pela fun\u00e7\u00e3o:<\/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 \u00e9 a mensagem \u00e0 qual um novo bloco de dados ser\u00e1 adicionado;<br \/>\n<em>data<\/em> \u2014 \u00e9 um ponteiro para o bloco, cuja c\u00f3pia ser\u00e1 adicionada \u00e0 mensagem;<br \/>\n<em>tamanho<\/em> \u2014 \u00e9 o tamanho dos dados;<br \/>\n<em>pad<\/em> \u2014 \u00e9 um sinalizador indicando que o tamanho da mem\u00f3ria alocada deve ser alinhado a um limite de 4 bytes (a complementa\u00e7\u00e3o ser\u00e1 feita com zeros). <\/p>\n<p><\/p>\n<p>Se houver espa\u00e7o suficiente no buffer de dados da mensagem, os novos dados ser\u00e3o anexados ap\u00f3s os dados j\u00e1 existentes. Se o espa\u00e7o livre no buffer de dados da mensagem for menor que <em>tamanho<\/em>, uma nova mensagem \u00e9 criada, com um tamanho de buffer suficiente, e os dados s\u00e3o copiados para seu buffer. Essa nova mensagem \u00e9 anexada \u00e0 original por meio de um ponteiro. <em>b_cont<\/em>Nesse caso, a mensagem se transforma em uma tupla. <\/p>\n<p><\/p>\n<p>Se for necess\u00e1rio adicionar mais um bloco de dados \u00e0 tupla, deve-se usar a fun\u00e7\u00e3o:<\/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>ela localizar\u00e1 a \u00faltima mensagem na tupla (esta ter\u00e1 <em>b_cont<\/em> como nula) e chamar\u00e1 a fun\u00e7\u00e3o <em>appendb()<\/em>.<\/p>\n<p><\/p>\n<p>Para saber o tamanho dos dados em uma mensagem ou tupla, pode-se usar a fun\u00e7\u00e3o:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>ela percorrer\u00e1 todas as mensagens da tupla e retornar\u00e1 a quantidade total de dados nos buffers de dados dessas mensagens. Para cada mensagem, a quantidade de dados \u00e9 calculada assim:<\/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 duas tuplas, usa-se a fun\u00e7\u00e3o:<\/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>ela anexa a tupla <em>newm<\/em> ao final da tupla <em>mp<\/em> e retorna um ponteiro para a \u00faltima mensagem da tupla resultante.<\/p>\n<p><\/p>\n<p>Se necess\u00e1rio, a tupla pode ser convertida em uma \u00fanica mensagem com um \u00fanico bloco de dados, isso \u00e9 feito pela fun\u00e7\u00e3o:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,int len);<\/code><\/pre>\n<p><\/p>\n<p>se o argumento <em>len<\/em> for igual a -1, o tamanho do buffer alocado \u00e9 determinado automaticamente. Se <em>len<\/em> Se um n\u00famero positivo for fornecido, um buffer desse tamanho ser\u00e1 criado e os dados das mensagens da tupla ser\u00e3o copiados para ele. Se o buffer acabar, a c\u00f3pia ser\u00e1 interrompida. A primeira mensagem da tupla receber\u00e1 um buffer do novo tamanho com os dados copiados. As demais mensagens ser\u00e3o descartadas e a mem\u00f3ria ser\u00e1 retornada ao heap.<\/p>\n<p><\/p>\n<p>Ao remover a estrutura <em>mblk_t<\/em> \u00e9 levado em conta o contador de refer\u00eancias do bloco de dados, se ao chamar <em>freeb()<\/em> ele for igual a zero, o buffer de dados \u00e9 removido junto com a inst\u00e2ncia <em>mblk_t<\/em>, que aponta para ele.<\/p>\n<p><\/p>\n<p>Inicializa\u00e7\u00e3o dos campos da nova mensagem:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void mblk_init(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Adicionando mais uma por\u00e7\u00e3o de dados \u00e0 mensagem: <\/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 os novos dados n\u00e3o couberem no espa\u00e7o livre do buffer de dados da mensagem, uma mensagem criada separadamente com um buffer do tamanho necess\u00e1rio ser\u00e1 anexada \u00e0 mensagem (o ponteiro para a mensagem adicionada \u00e9 estabelecido na primeira mensagem) e a mensagem se transforma em uma tupla.<\/p>\n<p><\/p>\n<p>Adicionando uma por\u00e7\u00e3o de dados \u00e0 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>A fun\u00e7\u00e3o chama appendb() em um loop.<\/p>\n<p><\/p>\n<p>Unindo duas tuplas em uma:<\/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>Mensagem <em>newm<\/em> ser\u00e1 anexado a <em>mp<\/em>.<\/p>\n<p><\/p>\n<p>Criando uma c\u00f3pia de uma \u00fanica mensagem:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copyb(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>C\u00f3pia completa da tupla com todos os blocos de dados:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copymsg(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Os elementos da tupla s\u00e3o copiados pela fun\u00e7\u00e3o <em>copyb()<\/em>.<\/p>\n<p><\/p>\n<p>Criando uma c\u00f3pia leve da mensagem. <em>mblk_t<\/em>Nesse caso, o bloco de dados n\u00e3o \u00e9 copiado, mas seu contador de refer\u00eancias \u00e9 incrementado. <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>Criando uma c\u00f3pia leve da tupla. Os blocos de dados n\u00e3o s\u00e3o copiados, apenas seus contadores de refer\u00eancias s\u00e3o incrementados. <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>Unindo todas as mensagens da tupla em uma s\u00f3 mensagem:<\/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 o argumento <em>len<\/em> for igual a -1, o tamanho do buffer reservado \u00e9 determinado automaticamente.<\/p>\n<p><\/p>\n<p>Removendo a mensagem, a tupla:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void freemsg(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>O contador de refer\u00eancias do bloco de dados \u00e9 decrementado em uma unidade. Se atingir zero, o bloco de dados tamb\u00e9m \u00e9 removido.<\/p>\n<p><\/p>\n<p>Contando o volume total de dados na mensagem ou tupla.<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">size_t msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Extraindo uma mensagem do final da fila:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *ms_queue_peek_last (q);<\/code><\/pre>\n<p><\/p>\n<p>Copiando o conte\u00fado dos campos reservados de uma mensagem para outra (na verdade, esses campos cont\u00eam bandeiras que s\u00e3o usadas pelo 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\">Fila <em>queue_t<\/em><\/h3>\n<p><\/p>\n<p>A fila de mensagens no media streamer \u00e9 implementada como uma lista duplamente encadeada circular. Cada elemento da lista cont\u00e9m um ponteiro para um bloco de dados com as contagens de sinal. Isso significa que apenas os ponteiros para o bloco de dados se movem, enquanto os pr\u00f3prios dados permanecem im\u00f3veis. Ou seja, apenas as refer\u00eancias a eles se movem.<br \/>\nEstrutura que descreve a fila <em>queue_t<\/em>, mostrada abaixo:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _queue\n{\n   mblk_t _q_stopper; \/* 'Elemento de parada' da fila, n\u00e3o aponta para dados, usado apenas para gerenciar a fila. Ao inicializar a fila (qinit()), seus ponteiros s\u00e3o configurados para apontar para ele pr\u00f3prio. *\/\n   int q_mcount;        \/\/ N\u00famero de elementos na fila.\n} queue_t;<\/code><\/pre>\n<p><\/p>\n<p>A estrutura cont\u00e9m um campo \u2014 um ponteiro <em>_q_stopper<\/em> do tipo *mblk_t, que aponta para o primeiro elemento (mensagem) na fila. O segundo campo da estrutura \u00e9 um contador de mensagens que est\u00e3o na fila.<br \/>\nNa figura abaixo, \u00e9 mostrada a fila com o nome q1, contendo 4 mensagens m1, m2, m3, m4.<br \/>\n<img decoding=\"async\" alt=\"Estudando o motor VoIP Mediastreamer2. Parte 11\" src=\"\/wp-content\/uploads\/2020\/04\/07fc043861edf15e5eb40e49c8ec5e3e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa pr\u00f3xima figura, \u00e9 mostrada a fila com o nome q1, contendo 4 mensagens m1, m2, m3, m4. A mensagem m2 \u00e9 o cabe\u00e7alho da tupla, que tamb\u00e9m inclui outras duas mensagens m2_1 e m2_2.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Estudando o 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\">Fun\u00e7\u00f5es de opera\u00e7\u00e3o com filas queue_t<\/h3>\n<p><\/p>\n<p>Inicializa\u00e7\u00e3o da fila:<\/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> (a partir de agora chamado de 'parada') \u00e9 inicializado pela fun\u00e7\u00e3o <em>mblk_init()<\/em>, seu ponteiro do elemento anterior e do pr\u00f3ximo elemento s\u00e3o configurados para apontar para ele mesmo. O contador de elementos da fila \u00e9 zerado.<\/p>\n<p><\/p>\n<p>Adicionando um novo elemento (mensagem):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void putq(queue_t *q, mblk_t *m);<\/code><\/pre>\n<p><\/p>\n<p>Um novo elemento <em>m<\/em> \u00e9 adicionado ao final da lista, os ponteiros do elemento s\u00e3o configurados para que o stopper se torne o pr\u00f3ximo elemento para ele, e ele para o stopper, o contador de elementos na fila \u00e9 incrementado.<\/p>\n<p><\/p>\n<p>Extraindo um elemento da fila:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * getq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>\u00e9 extra\u00edda a mensagem que est\u00e1 ap\u00f3s o stopper, o contador de elementos \u00e9 decrementado. Se n\u00e3o houver elementos na fila al\u00e9m do stopper, retorna-se 0.<\/p>\n<p><\/p>\n<p>Inserindo uma mensagem na fila:<\/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> \u00e9 inserido antes do elemento <em>emp<\/em>. Se <em>emp<\/em>=0, ent\u00e3o a mensagem \u00e9 adicionada ao final da fila.<\/p>\n<p><\/p>\n<p>Extraindo a mensagem do cabe\u00e7alho da fila:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void remq(queue_t *q, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>O contador de elementos \u00e9 decrementado.<\/p>\n<p><\/p>\n<p>Lendo o ponteiro para o primeiro elemento na fila:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * peekq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>Removendo todos os elementos da fila junto com os pr\u00f3prios elementos:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void flushq(queue_t *q, int how);<\/code><\/pre>\n<p><\/p>\n<p>Argumento <em>how<\/em> n\u00e3o usado. O contador de elementos da fila \u00e9 definido como zero.<\/p>\n<p><\/p>\n<p>Macro para ler o ponteiro do \u00faltimo elemento da fila:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * qlast(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Ao trabalhar com filas de mensagens, deve-se ter em mente que ao chamar <em>ms_queue_put(q, m)<\/em> com um ponteiro nulo para a mensagem, a fun\u00e7\u00e3o entra em loop. Seu programa ficar\u00e1 travado. O mesmo comportamento \u00e9 observado com <em>ms_queue_next(q, m)<\/em>.<\/p>\n<p><\/p>\n<h3 id=\"soedinenie-filtrov\">Conex\u00e3o de filtros<\/h3>\n<p><\/p>\n<p>A fila descrita acima \u00e9 utilizada para transmitir mensagens de um filtro para outro ou de um para v\u00e1rios filtros. Os filtros e suas conex\u00f5es entre si formam um gr\u00e1fico direcionado. A entrada ou sa\u00edda de um filtro ser\u00e1 chamada de 'pino' como termo geral. Para descrever a ordem de conex\u00e3o dos filtros entre si, o media streamer usa o conceito de 'ponto de sinal'. O ponto de sinal \u00e9 uma estrutura <em>_MSCPoint<\/em>, que cont\u00e9m um ponteiro para o filtro e o n\u00famero de um de seus pinos, descrevendo assim a conex\u00e3o de uma das entradas ou sa\u00eddas do filtro. <\/p>\n<p><\/p>\n<h4 id=\"signalnaya-tochka-grafa-obrabotki-dannyh\">Ponto de sinal do grafo de processamento de dados<\/h4>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSCPoint{\nstruct _MSFilter *filter; \/\/ Ponteiro para o filtro do mediastreamer.\nint pin;                        \/\/ N\u00famero de uma das entradas ou sa\u00eddas do filtro, ou seja, pino.\n} MSCPoint;\n<\/code><\/pre>\n<p><\/p>\n<p>Os pinos dos filtros s\u00e3o numerados a partir de zero. <\/p>\n<p><\/p>\n<p>A conex\u00e3o de dois pinos atrav\u00e9s de uma fila de mensagens \u00e9 descrita pela estrutura <em>_MSQueue<\/em>, que cont\u00e9m a fila de mensagens e ponteiros para dois pontos de sinal que ela 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>Chamaremos essa estrutura de link de sinal. Cada filtro do mediastreamer cont\u00e9m uma tabela de links de entrada e uma tabela de links de sa\u00edda (<em>MSQueue<\/em>). O tamanho das tabelas \u00e9 definido na cria\u00e7\u00e3o do filtro, como j\u00e1 fizemos com a vari\u00e1vel exportada do tipo <em>MSFilterDesc<\/em>, ao desenvolver nosso pr\u00f3prio filtro. Abaixo est\u00e1 a estrutura que descreve qualquer filtro no mediastreamer, <em>MSFilter<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">\nstruct _MSFilter{\n    MSFilterDesc *desc;    \n    \/* Pointer to the filter descriptor. *\/\n    \/* Protected attributes, they cannot be moved or removed otherwise plugin operation will be disrupted. *\/\n    ms_mutex_t lock;      \/* Semaphore. *\/\n    MSQueue **inputs;     \/* Table of input links. *\/\n    MSQueue **outputs;    \/* Table of output links. *\/\n    struct _MSFactory *factory; \/* Pointer to the factory that created this filter instance. *\/\n    void *padding;              \/* Not used, will be engaged if protected fields are added. *\/\n    void *data;                 \/* Pointer to an arbitrary structure for storing filter's internal state data and intermediate computations. *\/\n    struct _MSTicker *ticker;   \/* Pointer to the ticker object, which must not be null when process() function is called. *\/\n    \/* private attributes, they can be moved and changed at any time *\/\n    MSList *notify_callbacks; \/* List of callbacks used to handle filter events. *\/\n    uint32_t last_tick;       \/* Number of the last tick when process() was called. *\/\n    MSFilterStats *stats;     \/* Statistics of the filter's operation. *\/\n    int postponed_task; \/* Number of postponed tasks. Some filters may delay data processing (calling process()) for several ticks. *\/\n    bool_t seen;  \/* A flag that the ticker uses to mark that this filter instance has already been serviced in this tick. *\/\n};\ntypedef struct _MSFilter MSFilter;\n<\/code><\/pre>\n<p><\/p>\n<p>Depois que conectamos os filtros em nosso programa em C de acordo com nosso plano (mas n\u00e3o conectamos o ticker), criamos um gr\u00e1fico direcionado, cujos n\u00f3s s\u00e3o inst\u00e2ncias da estrutura <em>MSFilter<\/em>, e as arestas s\u00e3o inst\u00e2ncias de links <em>MSQueue<\/em>. <\/p>\n<p><\/p>\n<h3 id=\"zakulisnaya-deyatelnost-tikera\">Atividades nos bastidores do ticker<\/h3>\n<p><\/p>\n<p>Quando eu disse que o ticker \u00e9 um filtro fonte de ticks, n\u00e3o era toda a verdade sobre ele. O ticker \u00e9 um objeto que executa fun\u00e7\u00f5es em intervalos regulares <em>process()<\/em> de todos os filtros do esquema (gr\u00e1fico) ao qual est\u00e1 conectado. Quando conectamos o ticker a um filtro do gr\u00e1fico em um programa em C, damos ao ticker o gr\u00e1fico que ele gerenciar\u00e1 a partir daquele momento, at\u00e9 que o desconectemos. Depois de conectado, o ticker come\u00e7a a examinar o gr\u00e1fico sob sua responsabilidade, compilando uma lista de filtros nos quais ele est\u00e1 inclu\u00eddo. Para n\u00e3o 'contar' o mesmo filtro duas vezes, ele marca os filtros encontrados, definindo uma bandeira neles. <em>seen<\/em>. A busca \u00e9 realizada nas tabelas de links, que existem em cada filtro. <\/p>\n<p><\/p>\n<p>Durante sua excurs\u00e3o de introdu\u00e7\u00e3o ao gr\u00e1fico, o ticker verifica se h\u00e1 entre os filtros pelo menos um que funciona como fonte de blocos de dados. Se nenhum for encontrado, o gr\u00e1fico \u00e9 considerado inv\u00e1lido e o ticker encerra sua opera\u00e7\u00e3o de forma abrupta.<\/p>\n<p><\/p>\n<p>Se o gr\u00e1fico se mostrou 'correto', para cada filtro encontrado, a fun\u00e7\u00e3o \u00e9 chamada para inicializa\u00e7\u00e3o <em>preprocess()<\/em>. Assim que chega o momento do pr\u00f3ximo ciclo de processamento (por padr\u00e3o a cada 10 milissegundos), o ticker chama a fun\u00e7\u00e3o. <em>process()<\/em> para todos os filtros de origem encontrados anteriormente, e depois para os outros filtros da lista. Se um filtro tem links de entrada, o in\u00edcio da fun\u00e7\u00e3o. <em>process()<\/em> repete-se at\u00e9 que as filas de links de entrada estejam vazias. Depois disso, ele passa para o pr\u00f3ximo filtro da lista e 'roda' at\u00e9 que os links de entrada sejam liberados de mensagens. O ticker transita de filtro para filtro at\u00e9 que a lista termine. Assim, o processamento do ciclo termina. <\/p>\n<p><\/p>\n<p>Agora vamos voltar \u00e0s tuplas e discutir a raz\u00e3o pela qual tal entidade foi adicionada ao media streamer. De maneira geral, o volume de dados necess\u00e1rio para o algoritmo que opera dentro do filtro n\u00e3o coincide e n\u00e3o \u00e9 m\u00faltiplo do tamanho dos buffers de dados que entram. Por exemplo, estamos escrevendo um filtro que realiza uma r\u00e1pida transforma\u00e7\u00e3o de Fourier, que por defini\u00e7\u00e3o pode processar apenas blocos de dados cujo tamanho \u00e9 uma pot\u00eancia de dois. Vamos considerar 512 amostras. Se os dados s\u00e3o gerados por um canal telef\u00f4nico, o buffer de dados de cada mensagem de entrada nos trar\u00e1 160 amostras do sinal. H\u00e1 uma tenta\u00e7\u00e3o de n\u00e3o retirar dados da entrada at\u00e9 que haja a quantidade necess\u00e1ria de dados. Mas nesse caso, haver\u00e1 uma colis\u00e3o com o ticker, que tentar\u00e1, sem sucesso, passar o filtro at\u00e9 que a fila de entrada se esvazie. Anteriormente, nomeamos essa regra como o terceiro princ\u00edpio de opera\u00e7\u00e3o do filtro. De acordo com esse princ\u00edpio, a fun\u00e7\u00e3o process() do filtro deve retirar todos os dados das filas de entrada.<\/p>\n<p><\/p>\n<p>Al\u00e9m disso, n\u00e3o ser\u00e1 poss\u00edvel retirar apenas 512 amostras na entrada, pois a coleta deve ser feita em blocos inteiros, ou seja, o filtro dever\u00e1 coletar 640 amostras e, utilizando 512 delas, manter o restante at\u00e9 acumular uma nova por\u00e7\u00e3o de dados. Assim, nosso filtro, al\u00e9m de seu trabalho principal, deve garantir a\u00e7\u00f5es auxiliares para o armazenamento intermedi\u00e1rio dos dados de entrada. Os desenvolvedores do mediastreamer e a solu\u00e7\u00e3o dessa tarefa geral criaram um objeto especial \u2014 MSBufferizer (bufferizador), que resolve essa quest\u00e3o atrav\u00e9s de tuplas. <\/p>\n<p><\/p>\n<h3 id=\"buferizator-msbufferizer\">Bufferizador (MSBufferizer)<\/h3>\n<p><\/p>\n<p>Este \u00e9 um objeto que ir\u00e1 acumular os dados de entrada dentro do filtro e come\u00e7ar\u00e1 a envi\u00e1-los para processamento assim que a quantidade de informa\u00e7\u00f5es for suficiente para executar o algoritmo do filtro. Enquanto o bufferizador acumula dados, o filtro funcionar\u00e1 em modo ocioso, sem utilizar o poder computacional do processador. Mas assim que a fun\u00e7\u00e3o de leitura do bufferizador retornar um valor diferente de zero, a fun\u00e7\u00e3o process() do filtro come\u00e7ar\u00e1 a retirar e processar os dados do bufferizador em por\u00e7\u00f5es do tamanho necess\u00e1rio, at\u00e9 sua exaust\u00e3o.<br \/>\nOs dados que ainda n\u00e3o foram utilizados permanecem no bufferizador como o primeiro elemento da tupla, ao qual se conectam os blocos subsequentes de dados de entrada. <\/p>\n<p><\/p>\n<p>Estrutura que descreve o bufferizador: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">struct _MSBufferizer{\nqueue_t q; \/* Fila de mensagens. *\/\nint size; \/* Tamanho total dos dados atualmente no bufferizador. *\/\n};\ntypedef struct _MSBufferizer MSBufferizer;<\/code><\/pre>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-msbufferizer\">Fun\u00e7\u00f5es de opera\u00e7\u00e3o com MSBufferizer<\/h3>\n<p><\/p>\n<p>Cria\u00e7\u00e3o de uma nova inst\u00e2ncia do bufferizador:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">MSBufferizer * ms_bufferizer_new(void);<\/code><\/pre>\n<p><\/p>\n<p>Mem\u00f3ria \u00e9 alocada, inicializada em <em>ms_bufferizer_init()<\/em> e um ponteiro \u00e9 retornado.<\/p>\n<p><\/p>\n<p>Fun\u00e7\u00e3o de inicializa\u00e7\u00e3o:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_init(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>A fila <em>q<\/em>, campo <em>tamanho<\/em> \u00e9 inicializada em zero.<\/p>\n<p><\/p>\n<p>Adicionando mensagem:<\/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>A mensagem m \u00e9 adicionada \u00e0 fila. O tamanho calculado dos blocos de dados \u00e9 acrescido a <em>tamanho<\/em>.<\/p>\n<p><\/p>\n<p>Transfere todos os mensagens da fila de dados do link para o bufferizador <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>A transfer\u00eancia das mensagens do link <em>q<\/em> para o bufferizador \u00e9 realizada com a fun\u00e7\u00e3o <em>ms_bufferizer_put()<\/em>.<\/p>\n<p><\/p>\n<p>Lendo do 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>Se o tamanho dos dados acumulados no bufferizador for menor que o solicitado (<em>datalen<\/em>), a fun\u00e7\u00e3o retorna zero, a c\u00f3pia de dados em data n\u00e3o \u00e9 realizada. Caso contr\u00e1rio, a c\u00f3pia de dados \u00e9 feita sequencialmente a partir das tuplas no buffer. Ap\u00f3s a c\u00f3pia, a tupla \u00e9 removida e a mem\u00f3ria \u00e9 liberada. A c\u00f3pia \u00e9 encerrada no momento em que datalen bytes forem copiados. Se o espa\u00e7o terminar no meio de um bloco de dados, este bloco ser\u00e1 encurtado para a parte restante n\u00e3o copiada. Na pr\u00f3xima chamada, a c\u00f3pia continuar\u00e1 a partir desse ponto.<\/p>\n<p><\/p>\n<p>Lendo a quantidade de dados dispon\u00edveis no momento no buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_get_avail(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Retorna o campo <em>tamanho<\/em> do buffer.<\/p>\n<p><\/p>\n<p>Descarte de parte dos dados no 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>A quantidade especificada de bytes de dados \u00e9 extra\u00edda e descartada. Os dados mais antigos s\u00e3o descartados.<\/p>\n<p><\/p>\n<p>Remo\u00e7\u00e3o de todas as mensagens no buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_flush(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>O contador de dados \u00e9 redefinido para zero.<\/p>\n<p><\/p>\n<p>Remo\u00e7\u00e3o de todas as mensagens no buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_uninit(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>A reinicializa\u00e7\u00e3o do contador n\u00e3o \u00e9 realizada.<\/p>\n<p><\/p>\n<p>Remo\u00e7\u00e3o do buffer e libera\u00e7\u00e3o de mem\u00f3ria:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_destroy(MSBufferizer *obj);  <\/code><\/pre>\n<p><\/p>\n<p>Exemplos de uso do buffer podem ser encontrados no c\u00f3digo fonte de v\u00e1rios filtros de m\u00eddia streaming. Por exemplo, no filtro MS_L16_ENC, que realiza a reordena\u00e7\u00e3o de bytes nas amostras da ordem de rede para a ordem do 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>No pr\u00f3ximo artigo, discutiremos a quest\u00e3o da avalia\u00e7\u00e3o da carga sobre o ticker e as maneiras de combater a carga computacional excessiva no mediastreamer.<\/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.3 - 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\/pt\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\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\/pt\/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 o motor VoIP Mediastreamer2. Parte 11 | ProHoster","description":"O material do artigo foi retirado do meu canal no Zen.","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/79540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=79540"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/79540\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/79541"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=79540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=79540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=79540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}