{"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\/nl\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","title":{"rendered":"We bestuderen de VoIP-engine Mediastreamer2. Deel 11","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Het materiaal van dit artikel is afkomstig van mijn <noindex><a rel=\"nofollow\" href=\"https:\/\/zen.yandex.ru\/profile\/editor\/id\/5e3f8e0751f5346faab4fc7a\">zen-kanaal<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"We bestuderen de VoIP-engine Mediastreamer2. Deel 11\" src=\"\/wp-content\/uploads\/2020\/04\/b66fab43eb45c626c50f0383dcfa7cca.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Mechanisme voor het verplaatsen van gegevens<\/p>\n<p><\/p>\n<ul>\n<li>Gegevensblok dblk_t<\/li>\n<li>Bericht mblk_t<\/li>\n<li>Functies voor het werken met berichten mblk_t<\/li>\n<li>Wachtrij queue_t<\/li>\n<li>Functies voor het werken met wachtrijen queue_t<\/li>\n<li>Verbinding van filters<\/li>\n<li>Signaalpunt in de gegevensverwerkingsgrafiek<\/li>\n<li>Achtergrondactiviteiten van de ticker<\/li>\n<li>Buffer (MSBufferizer)<\/li>\n<li>Functies voor het werken met MSBufferizer<\/li>\n<\/ul>\n<p><\/p>\n<p>In het verleden <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/497932\/\">artikel<\/a><\/noindex> Wij hebben onze eigen filter ontwikkeld. Dit artikel is gewijd aan de opzet van het interne mechanisme voor het verplaatsen van gegevens tussen de filters van de mediastreamer. Dit zal in de toekomst het mogelijk maken om geavanceerdere filters te schrijven met minder inspanning.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"mehanizm-peremescheniya-dannyh\">Mechanisme voor het verplaatsen van gegevens<\/h2>\n<p><\/p>\n<p>Het verplaatsen van gegevens in de mediastreamer gebeurt met behulp van wachtrijen die worden beschreven door de structuur <em>queue_t<\/em>. Door de wachtrijen worden reeksen berichten van het type <em>mblk_t<\/em>, die zelf geen signaalgegevens bevatten, maar alleen verwijzingen naar het vorige, volgende bericht en naar het gegevensblok. Daarnaast wil ik speciaal benadrukken dat er ook een veld is voor een verwijzing naar een bericht van hetzelfde type, waarmee een enkelvoudige linklijst van berichten kan worden georganiseerd. De groep berichten die door deze lijst zijn verbonden, noemen we een tuple. Op deze manier kan elk element van de wachtrij een enkel bericht zijn <em>mblk_t<\/em>, maar kan ook de kop van de tuple van berichten zijn <em>mblk_t<\/em>. Elk bericht van de tuple kan zijn eigen ondergeschikt gegevensblok hebben. Waarom tuples nodig zijn, zullen we wat later bespreken.<\/p>\n<p><\/p>\n<p>Zoals hierboven vermeld, bevat het bericht op zichzelf geen datablok, maar alleen een aanwijzing naar het geheugengebied waar het blok is opgeslagen. In deze zin lijkt het algemene beeld van de mediastraimer op een opslagplaats van deuren in de animatiefilm \"Monsters, Inc.\", waar de deuren (de datalink \u2014 kamers) zich met een enorme snelheid over hangende transportbanden bewegen, terwijl de kamers zelf stil blijven staan. <\/p>\n<p><\/p>\n<p>Laten we nu, terwijl we de hi\u00ebrarchie van onder naar boven doorlopen, de vermelde entiteiten van het gegevensoverdrachtsmechanisme in de mediastreamer gedetailleerd bekijken.<\/p>\n<p><\/p>\n<h3 id=\"blok--dannyh-dblk_t\">Gegevensblok <em>dblk_t<\/em><\/h3>\n<p><\/p>\n<p>Het gegevensblok bestaat uit een header en een gegevensbuffer. De header wordt beschreven door de volgende structuur,<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct datab\n{\nunsigned char *db_base; \/\/ Aanwijzer naar het begin van de gegevensbuffer.\nunsigned char *db_lim;  \/\/ Aanwijzer naar het einde van de gegevensbuffer.\nvoid (*db_freefn)(void*); \/\/ Vrijgavefunctie voor geheugen bij verwijdering van het blok.\nint db_ref; \/\/ Referentieteller.\n} dblk_t;<\/code><\/pre>\n<p><\/p>\n<p>De structuurvelden bevatten aanwijzingen voor het begin van de buffer, het einde van de buffer en een functie om de gegevensbuffer te verwijderen. Het laatste element in de header <em>db_ref<\/em> is een referentieteller; wanneer deze nul bereikt, dient dit als een signaal om het betreffende blok uit het geheugen te verwijderen. Als het gegevensblok is aangemaakt met de functie <em>datab_alloc() <\/em>, dan wordt de gegevensbuffer direct achter de header in het geheugen geplaatst. In alle andere gevallen kan de buffer ergens apart liggen. In de gegevensbuffer bevinden zich signaalmetingen of andere gegevens die we willen verwerken met filters.<\/p>\n<p><\/p>\n<p>Een nieuw exemplaar van een gegevensblok wordt gemaakt met de functie:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">dblk_t *datab_alloc(int size);<\/code><\/pre>\n<p><\/p>\n<p>De grootte van de gegevens die het blok zal opslaan, wordt als invoerparameter doorgegeven. Er wordt meer geheugen toegewezen zodat in het begin van het toegewezen geheugen de header \u2014 de structuur <em>datab<\/em>. Maar dit is niet altijd het geval wanneer andere functies worden gebruikt; in sommige gevallen kan de gegevensbuffer apart van de header van het gegevensblok liggen. De velden van de structuur worden zodanig ingesteld dat het veld <em>db_base<\/em> verwijst naar het begin van het gegevensgebied, en <em>db_lim<\/em> verwijst naar het einde ervan. De referentieteller <em>db_ref<\/em> wordt ingesteld op \u00e9\u00e9n. De aanwijzer voor de functie om gegevens te wissen wordt op nul ingesteld.<\/p>\n<p><\/p>\n<h3 id=\"soobschenie-mblk_t\">Bericht <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Zoals eerder vermeld, hebben de elementen van de wachtrij het type <em>mblk_t,<\/em> dat als volgt is gedefinieerd:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct msgb\n{\n  struct msgb *b_prev;   \/\/ Aanwijzer naar het vorige element in de lijst.\n  struct msgb *b_next;   \/\/ Aanwijzer naar het volgende element in de lijst.\n  struct msgb *b_cont;   \/\/ Aanwijzer voor het onderbrengen van andere berichten aan een bericht, om een tuple van berichten te cre\u00ebren.\n  struct datab *b_datap; \/\/ Aanwijzer naar de structuur van het gegevensblok.\n  unsigned char *b_rptr; \/\/ Aanwijzer naar het begin van het gegevensgebied voor het lezen van de gegevens van de buffer b_datap.\n  unsigned char *b_wptr; \/\/ Aanwijzer naar het begin van het gegevensgebied voor het schrijven van de gegevens van de buffer b_datap.\n  uint32_t reserved1;    \/\/ Gereserveerd veld1, de mediaspeler plaatst daar service-informatie.\n  uint32_t reserved2;    \/\/ Gereserveerd veld2, de mediaspeler plaatst daar service-informatie.\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>Structuur <em>mblk_t<\/em> aan het begin bevinden zich aanwijzers <em>b_prev<\/em>, <em>b_next<\/em>, die nodig zijn voor de organisatie van een dubbel gekoppelde lijst (waaruit de wachtrij bestaat). <em>queue_t<\/em>). <\/p>\n<p><\/p>\n<p>Daarna volgt de aanwijzer <em>b_cont<\/em>, die alleen wordt gebruikt wanneer het bericht in de tuple komt. Voor het laatste bericht in de tuple blijft deze pointer nul. <\/p>\n<p><\/p>\n<p>Verder zien we een pointer naar het datablok <em>b_datap<\/em>, waarvoor het bericht bestaat. Daarna volgen pointers naar de gebieden binnen de gegevensbuffer van het blok. Het veld <em>b_rptr<\/em> geeft de plek aan waaruit gegevens uit de buffer worden gelezen. Het veld <em>b_wptr<\/em> geeft de plek aan waar de gegevens in de buffer worden geschreven. <\/p>\n<p><\/p>\n<p>De overige velden zijn administratief van aard en hebben geen betrekking op de werking van het datatransfermechanisme.<\/p>\n<p><\/p>\n<p>Hieronder ziet u een enkel bericht met de naam <em>m1<\/em> en het datablok <em>d1<\/em>.<br \/>\n<img decoding=\"async\" alt=\"We bestuderen de VoIP-engine Mediastreamer2. Deel 11\" src=\"\/wp-content\/uploads\/2020\/04\/e6a726b13491832258c1228f03bde802.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn de volgende afbeelding is een tuple van drie berichten afgebeeld <em>m1<\/em>, <em>m1_1<\/em>, <em>m1_2<\/em>.<br \/>\n<img decoding=\"async\" alt=\"We bestuderen de VoIP-engine Mediastreamer2. Deel 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\">Functies voor het werken met berichten <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Een nieuw bericht <em>mblk_t<\/em> wordt aangemaakt door de functie:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *allocb(int size, int pri); <\/code><\/pre>\n<p><\/p>\n<p>deze plaatst een nieuw bericht in het geheugen <em>mblk_t<\/em> met een datablok van de gespecificeerde grootte <em>size<\/em>, de tweede parameter \u2014 <em>pri<\/em> wordt niet gebruikt in de besproken versie van de bibliotheek. Deze moet nul blijven. Tijdens de werking van de functie wordt geheugen toegewezen voor de structuur van het nieuwe bericht en wordt de functie <em>mblk_init()<\/em>, aangeroepen, die alle velden van de gemaakte structuur instelt op nul en vervolgens, met behulp van het eerder genoemde, <em>datab_alloc()<\/em>zal een gegevensbuffer cre\u00ebren. Daarna worden de velden in de structuur ingesteld: <\/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>We krijgen een nieuw bericht met ge\u00efnitialiseerde velden en een lege gegevensbuffer. Om gegevens aan het bericht toe te voegen, moet je deze naar de buffer van het datablok kopi\u00ebren:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">memcpy(msg-&gt;b_rptr, data, size);<\/code><\/pre>\n<p><\/p>\n<p>waar <em>data<\/em> \u2014 een pointer naar de gegevensbron, en <em>size<\/em> \u2014 hun grootte.<br \/>\nVervolgens moet je de pointer naar het schrijfpunt bijwerken, zodat deze weer naar het begin van het vrije gebied in de buffer wijst:<\/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>Als je een bericht wilt maken uit een al bestaand buffer, zonder te kopi\u00ebren, dan gebruik je de functie:<\/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>De functie zal na het aanmaken van het bericht en de datablokstructuur de pointers naar de gegevens instellen op het adres <em>buf<\/em>. Dat wil zeggen, in dit geval bevindt de gegevensbuffer zich niet achter de velden van de datablokheader, zoals bij de creatie van het datablok met de functie. <em>datab_alloc()<\/em>De aan de functie doorgegeven gegevensbuffer blijft op zijn plaats, maar met behulp van pointers wordt deze verbonden met de net aangemaakte gegevensblokkentitel, die op zijn beurt naar het bericht verwijst.<\/p>\n<p><\/p>\n<p>Aan \u00e9\u00e9n bericht <em>mblk_t<\/em> kunnen meerdere gegevensblokken achtereenvolgens worden bevestigd. Dit gebeurt met de functie:<\/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 het bericht waaraan weer een gegevensblok zal worden toegevoegd;<br \/>\n<em>data<\/em> \u2014 een pointer naar het blok, waarvan een kopie aan het bericht zal worden toegevoegd;<br \/>\n<em>size<\/em> \u2014 de grootte van de gegevens;<br \/>\n<em>pad<\/em> \u2014 een vlag die aangeeft dat de grootte van de toegewezen geheugenruimte moet worden afgestemd op een grens van 4 bytes (aanvulling zal met nullen worden uitgevoerd). <\/p>\n<p><\/p>\n<p>Als er in de bestaande gegevensbuffer van het bericht voldoende ruimte is, worden de nieuwe gegevens achter de al aanwezige gegevens toegevoegd. Als er minder vrije ruimte in de gegevensbuffer van het bericht is dan <em>size<\/em>, wordt er een nieuw bericht aangemaakt met een voldoende grote buffer en worden de gegevens in zijn buffer gekopieerd. Dit nieuwe bericht wordt met behulp van de pointer <em>b_cont<\/em>aan het origineel gekoppeld. In dit geval verandert het bericht in een tuple. <\/p>\n<p><\/p>\n<p>Als er een extra gegevensblok aan de tuple moet worden toegevoegd, moet de functie worden gebruikt:<\/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>deze zal het laatste bericht in de tuple zoeken (het zal <em>b_cont<\/em> nul zijn) en voor dit bericht de functie <em>appendb() aanroepen.<\/em>.<\/p>\n<p><\/p>\n<p>De grootte van de gegevens in het bericht of de tuple kan worden bepaald met de functie:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>deze zal door alle berichten van de tuple lopen en de totale hoeveelheid gegevens in de gegevensbuffers van deze berichten retourneren. Voor elk bericht wordt de hoeveelheid gegevens als volgt berekend:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\"> mp-&gt;b_wptr - mp-&gt;b_rptr<\/code><\/pre>\n<p><\/p>\n<p>Om twee tuples te combineren, wordt de functie toegepast:<\/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>deze voegt de tuple <em>newm<\/em> aan de achterkant van de tuple <em>mp<\/em> toe en retourneert een pointer naar het laatste bericht van de resulterende tuple.<\/p>\n<p><\/p>\n<p>Indien nodig kan de tuple worden omgezet in \u00e9\u00e9n bericht met \u00e9\u00e9n gegevensblok; dit gebeurt met de functie:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,int len);<\/code><\/pre>\n<p><\/p>\n<p>als het argument <em>len<\/em> gelijk is aan -1, dan wordt de grootte van de toegewezen buffer automatisch bepaald. Als <em>len<\/em> Als het een positief getal is, wordt er een buffer van deze grootte aangemaakt en worden de gegevens van de berichten uit de tuple hierin gekopieerd. Als de buffer vol is, wordt het kopi\u00ebren stopgezet. Het eerste bericht van de tuple ontvangt een buffer van de nieuwe grootte met de gekopieerde gegevens. De overige berichten worden verwijderd en het geheugen wordt teruggegeven aan de heap.<\/p>\n<p><\/p>\n<p>Bij het verwijderen van de structuur <em>mblk_t<\/em> wordt de referentieteller van het gegevensblok in overweging genomen, als deze bij de aanroep <em>freeb()<\/em> nul blijkt te zijn, wordt de gegevensbuffer samen met het exemplaar verwijderd <em>mblk_t<\/em>, waarnaar het verwijst.<\/p>\n<p><\/p>\n<p>Initi\u00ebren van de velden van het nieuwe bericht:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void mblk_init(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Een extra hoeveelheid gegevens aan het bericht toevoegen: <\/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>Als de nieuwe gegevens niet passen in de vrije ruimte van de gegevensbuffer van het bericht, wordt er een apart gemaakt bericht met de benodigde bufferomvang aan het bericht toegevoegd (in het eerste bericht wordt een verwijzing naar het toegevoegde bericht ingesteld); het bericht verandert in een tuple.<\/p>\n<p><\/p>\n<p>Een hoeveelheid gegevens aan de tuple toevoegen:<\/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>De functie roept appendb() in een lus aan.<\/p>\n<p><\/p>\n<p>Twee tuples samenvoegen tot \u00e9\u00e9n:<\/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>Bericht <em>newm<\/em> wordt toegevoegd aan <em>mp<\/em>.<\/p>\n<p><\/p>\n<p>Een kopie van een enkel bericht maken:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copyb(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Een volledige kopie van de tuple met al zijn gegevensblokken:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copymsg(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>De elementen van de tuple worden bij deze gekopieerd door de functie <em>copyb()<\/em>.<\/p>\n<p><\/p>\n<p>Een lichte kopie van het bericht maken <em>mblk_t<\/em>. Hierbij wordt het gegevensblok niet gekopieerd, maar wordt de referentieteller verhoogd <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>Een lichte kopie van de tuple maken. Gegevensblokken worden niet gekopieerd, alleen hun referentietellers worden verhoogd <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>Alle berichten van de tuple samenvoegen tot \u00e9\u00e9n bericht:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,size_t len);<\/code><\/pre>\n<p><\/p>\n<p>Als het argument <em>len<\/em> gelijk is aan -1, wordt de grootte van de te reserveren buffer automatisch bepaald.<\/p>\n<p><\/p>\n<p>Een bericht, tuple verwijderen:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void freemsg(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>De referentieteller van het gegevensblok wordt met \u00e9\u00e9n verlaagd. Als deze nul bereikt, wordt het gegevensblok ook verwijderd.<\/p>\n<p><\/p>\n<p>Het totale volume van gegevens in een bericht of tuple tellen.<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">size_t msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Een bericht uit de staart van de wachtrij halen:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *ms_queue_peek_last (q);<\/code><\/pre>\n<p><\/p>\n<p>De inhoud van de gereserveerde velden van het ene bericht naar het andere berichten kopi\u00ebren (in feite bevinden zich in deze velden vlaggen die door de mediastreamer worden gebruikt):<\/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\">Wachtrij <em>queue_t<\/em><\/h3>\n<p><\/p>\n<p>De berichtenwachtrij in de mediastreamer is ge\u00efmplementeerd als een ringdoubly linked list. Elk element van de lijst bevat een pointer naar een gegevensblok met signalen. Dit betekent dat alleen de pointers naar de gegevensblokken verplaatst worden, terwijl de gegevens zelf onbeweeglijk blijven. Met andere woorden, alleen de verwijzingen naar deze gegevens worden verplaatst.<br \/>\nStructuur die de wachtrij beschrijft <em>queue_t<\/em>, hieronder weergegeven:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _queue\n{\n   mblk_t _q_stopper; \/* \"Lege\" queue-element, wijst niet naar gegevens, wordt alleen gebruikt voor queue-beheer. Bij het initialiseren van de queue (qinit()) worden de aanwijzers zo ingesteld dat ze naar zichzelf wijzen. *\/\n   int q_mcount;        \/\/ Aantal elementen in de queue.\n} queue_t;<\/code><\/pre>\n<p><\/p>\n<p>De structuur bevat een veld - pointer <em>_q_stopper<\/em> van het type *mblk_t, dat wijst naar het eerste element (bericht) in de wachtrij. Het tweede veld van de structuur is een teller van de berichten in de wachtrij.<br \/>\nIn de onderstaande afbeelding is de wachtrij met de naam q1 te zien, die 4 berichten m1, m2, m3, m4 bevat.<br \/>\n<img decoding=\"async\" alt=\"We bestuderen de VoIP-engine Mediastreamer2. Deel 11\" src=\"\/wp-content\/uploads\/2020\/04\/07fc043861edf15e5eb40e49c8ec5e3e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn de volgende afbeelding is de wachtrij met de naam q1 te zien, die 4 berichten m1, m2, m3, m4 bevat. Bericht m2 is de kop van het tuple, waarvan twee andere berichten m2_1 en m2_2 worden geleid.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"We bestuderen de VoIP-engine Mediastreamer2. Deel 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\">Functies voor het werken met wachtrijen queue_t<\/h3>\n<p><\/p>\n<p>Initialisatie van de wachtrij:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void qinit(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Veld <em>_q_stopper<\/em> (hierna te noemen \"stopper\") wordt ge\u00efnitialiseerd door een functie <em>mblk_init()<\/em>, zijn pointer naar het voorgaande element en het volgende element worden zo ingesteld dat ze naar zichzelf verwijzen. De teller van de elementen in de wachtrij wordt op nul gezet.<\/p>\n<p><\/p>\n<p>Toevoegen van een nieuw element (bericht):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void putq(queue_t *q, mblk_t *m);<\/code><\/pre>\n<p><\/p>\n<p>Een nieuw element <em>m<\/em> wordt aan het einde van de lijst toegevoegd, de pointers van het element worden zo ingesteld dat de stopper het volgende element voor hem wordt, en hij voor de stopper het vorige. De teller van de elementen in de wachtrij wordt verhoogd.<\/p>\n<p><\/p>\n<p>Verwijderen van een element uit de wachtrij:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * getq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>het bericht dat achter de stopper staat wordt verwijderd, de teller van de elementen wordt verlaagd. Als er geen elementen in de wachtrij zijn, behalve de stopper, wordt 0 geretourneerd.<\/p>\n<p><\/p>\n<p>Invoegen van een bericht in de wachtrij:<\/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>Element <em>mp<\/em> wordt voor het element <em>emp<\/em>. Als <em>emp<\/em>=0, dan wordt het bericht aan het einde van de wachtrij toegevoegd.<\/p>\n<p><\/p>\n<p>Verwijderen van een bericht uit de kop van de wachtrij:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void remq(queue_t *q, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>De teller van de elementen wordt verlaagd.<\/p>\n<p><\/p>\n<p>Lezen van de pointer naar het eerste element in de wachtrij:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * peekq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>Verwijderen van alle elementen uit de wachtrij met verwijdering van de elementen zelf:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void flushq(queue_t *q, int how);<\/code><\/pre>\n<p><\/p>\n<p>Het argument <em>hoe<\/em> wordt niet gebruikt. De teller van de wachtrij-elementen wordt op nul gezet.<\/p>\n<p><\/p>\n<p>Macro voor het lezen van de aanwijzer naar het laatste element in de wachtrij:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * qlast(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Bij het werken met berichtenwachtrijen moet in gedachten worden gehouden dat bij het aanroepen van <em>ms_queue_put(q, m)<\/em> met een nulpointer naar een bericht, de functie in een loop vastloopt. Uw programma zal bevroren zijn. Op dezelfde manier gedraagt zich <em>ms_queue_next(q, m)<\/em>.<\/p>\n<p><\/p>\n<h3 id=\"soedinenie-filtrov\">Verbinding van filters<\/h3>\n<p><\/p>\n<p>De hierboven beschreven queue wordt gebruikt om berichten van de ene filter naar de andere te verzenden of van \u00e9\u00e9n naar meerdere filters. Filters en hun verbindingen vormen samen een gerichte graaf. De ingang of uitgang van een filter zullen we het overkoepelende woord \"pin\" noemen. Voor de beschrijving van de volgorde van verbindingen tussen filters wordt in de mediastraimer het begrip \"signaalpunt\" gebruikt. Een signaalpunt is een structuur <em>_MSCPoint<\/em>, die een aanwijzer naar de filter bevat en het nummer van een van zijn pinnen, waardoor het de verbinding tussen een van de ingangen of uitgangen van de filter beschrijft. <\/p>\n<p><\/p>\n<h4 id=\"signalnaya-tochka-grafa-obrabotki-dannyh\">Signaalpunt in de gegevensverwerkingsgrafiek<\/h4>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSCPoint{\nstruct _MSFilter *filter; \/\/ Aanwijzer naar de mediastreamer-filter.\nint pin;                        \/\/ Het nummer van een van de ingangen of uitgangen van de filter, dus de pin.\n} MSCPoint;\n<\/code><\/pre>\n<p><\/p>\n<p>De pinnen van de filters worden genummerd vanaf nul. <\/p>\n<p><\/p>\n<p>De verbinding van twee pinnen met een berichtenwachtrij wordt beschreven door de structuur <em>_MSQueue<\/em>, die de berichtenwachtrij en aanwijzers naar twee signaalpunten bevat, die het verbindt:<\/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>Laten we deze structuur een signaalverbinding noemen. Elke mediastreamer-filter bevat een tabel van inkomende koppelingen en een tabel van uitgaande koppelingen (<em>MSQueue<\/em>). De grootte van de tabellen wordt vastgesteld bij het cre\u00ebren van de filter, wat we al deden met behulp van de ge\u00ebxporteerde variabele van het type <em>MSFilterDesc<\/em>, toen we onze eigen filter ontwikkelden. Hieronder staat de structuur die elke filter in de mediastreamer beschrijft, <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 utilized if protected fields are added. *\/\n    void *data;                 \/* Pointer to an arbitrary structure for storing the internal state data of the filter and intermediate computations. *\/\n    struct _MSTicker *ticker;   \/* Pointer to the ticker object, which must not be null when the process() function is called. *\/\n    \/* private attributes, they can be moved and changed at any time *\/\n    MSList *notify_callbacks; \/* List of callbacks used for processing filter events. *\/\n    uint32_t last_tick;       \/* The number of the last tick when the process() was called. *\/\n    MSFilterStats *stats;     \/* Filter operation statistics. *\/\n    int postponed_task; \/* Number of postponed tasks. Some filters may postpone data processing (calling process()) for several ticks. *\/\n    bool_t seen;  \/* Flag used by the ticker to mark that it has already serviced this filter instance during this tick. *\/\n};\ntypedef struct _MSFilter MSFilter;\n<\/code><\/pre>\n<p><\/p>\n<p>Nadat we de filters in ons C-programma hebben verbonden volgens ons ontwerp (maar de ticker niet hebben aangesloten), hebben we daarmee een gerichte grafiek gecre\u00eberd, waarvan de knooppunten instanties van de structuur zijn <em>MSFilter<\/em>, en de randen zijn instanties van links <em>MSQueue<\/em>. <\/p>\n<p><\/p>\n<h3 id=\"zakulisnaya-deyatelnost-tikera\">Achtergrondactiviteiten van de ticker<\/h3>\n<p><\/p>\n<p>Toen ik je zei dat de ticker een bronfilter voor tikken is, was dat niet de hele waarheid over hem. De ticker is een object dat vanuit de klok functies uitvoert <em>process()<\/em> van alle filters in de graaf (het graf) waaraan het is gekoppeld. Wanneer we in een C-programma een ticker aan een filter van de graaf koppelen, geven we de ticker de graaf aan die hij vanaf dat moment zal beheren, totdat we hem loskoppelen. Na koppeling begint de ticker zijn vertrouwelijke graaf te verkennen en een lijst van filters samen te stellen waarin hij is opgenomen. Om te voorkomen dat hij hetzelfde filter twee keer \"telt\", markeert hij de gevonden filters door een vlag in te stellen. <em>seen<\/em>. De zoektocht wordt uitgevoerd via de tabellen van de links die elke filter heeft. <\/p>\n<p><\/p>\n<p>Tijdens zijn introductietour controleert de ticker of er onder de filters ten minste \u00e9\u00e9n is die fungeert als bron van gegevensblokken. Als er geen is, wordt de grafiek als ongeldig beschouwd en stopt de ticker met de uitvoering.<\/p>\n<p><\/p>\n<p>Als de graaf \"correct\" bleek te zijn, wordt voor elke gevonden filter de initialisatiefunctie aangeroepen. <em>preprocess()<\/em>. Zodra het tijd is voor de volgende verwerkingscyclus (standaard om de 10 milliseconden), roept de ticker de functie aan <em>process()<\/em> voor alle eerder gevonden bronfilters en daarna voor de overige filters in de lijst. Als de filter inkomende links heeft, wordt de functie <em>process()<\/em> Dit herhaalt zich totdat de queues van de ingangslinks leeg zijn. Daarna gaat hij verder naar de volgende filter op de lijst en \"rollt\" deze door tot de ingangslinks van berichten zijn vrijgemaakt. De ticker gaat van filter naar filter totdat de lijst is afgelopen. Hiermee eindigt de verwerking van de cyclus. <\/p>\n<p><\/p>\n<p>Laten we nu terugkeren naar de tuples en bespreken waarom zo'n entiteit aan de media-streamer is toegevoegd. In het algemeen komt de hoeveelheid gegevens die nodig is voor de algoritme dat in de filter draait, niet overeen met en is niet een veelvoud van de grootte van de gegevensbuffers die binnenkomen. Stel dat we een filter schrijven dat snelle Fourier-transformaties uitvoert, welke per definitie alleen blokken gegevens kan verwerken waarvan de grootte een macht van twee is. Laten we zeggen dat dit 512 steekproeven is. Als de gegevens via een telefoonkanaal worden gegenereerd, komt de gegevensbuffer van elk bericht bij de ingang met 160 steekproeven van het signaal. Er is de verleiding om geen gegevens van de invoer te halen totdat er een benodigde hoeveelheid gegevens is. Maar in dit geval ontstaat er een conflict met de ticker, die vergeefs zal proberen de filter te draaien totdat de inkomende link is geleegd. Eerder hebben we deze regel aangeduid als de derde principe van de filterwerking. Volgens dit principe moet de functie process() van de filter alle gegevens uit de invoerwachtrijen ophalen.<\/p>\n<p><\/p>\n<p>Daarnaast kunnen er vanaf de invoer niet meer dan 512 metingen worden opgehaald, omdat deze alleen in hele blokken kunnen worden verzameld, dat wil zeggen dat de filter 640 metingen moet ophalen en 512 daarvan gebruiken, met de rest tot de accumulatie van een nieuwe gegevensbatch. Zo moet onze filter, naast zijn hoofdfunctie, ook ondersteunende acties voor de tussentijdse opslag van de invoergegevens bieden. De ontwikkelaars van de mediastreamer hebben hiervoor een speciaal object ontwikkeld - MSBufferizer (buffer), dat deze taak oplost met behulp van tuples. <\/p>\n<p><\/p>\n<h3 id=\"buferizator-msbufferizer\">Buffer (MSBufferizer)<\/h3>\n<p><\/p>\n<p>Dit is een object dat de invoergegevens binnen de filter accumuleert en begint deze te verwerken zodra de hoeveelheid informatie voldoende is om het filteralgoritme uit te voeren. Terwijl de buffer gegevens verzamelt, werkt de filter in een idle-modus en verbruikt geen rekenkracht van de processor. Maar zodra de leesfunctie van de buffer een waarde teruggeeft die verschillend is van nul, begint de functie process() van de filter de gegevens in de juiste hoeveelheden uit de buffer te halen en te verwerken tot deze uitgeput zijn.<br \/>\nDe nog niet gebruikte gegevens blijven in de buffer als het eerste element van de tuple, waaraan de volgende blokken invoergegevens worden toegevoegd. <\/p>\n<p><\/p>\n<p>De structuur die de buffer beschrijft: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">struct _MSBufferizer{\nqueue_t q; \/* Berichtqueue. *\/\nint size; \/* Totale grootte van de gegevens in de buffer op dit moment. *\/\n};\ntypedef struct _MSBufferizer MSBufferizer;<\/code><\/pre>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-msbufferizer\">Functies voor het werken met MSBufferizer<\/h3>\n<p><\/p>\n<p>Een nieuw exemplaar van de buffer aanmaken:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">MSBufferizer * ms_bufferizer_new(void);<\/code><\/pre>\n<p><\/p>\n<p>Geheugen wordt toegewezen en ge\u00efnitieerd in <em>ms_bufferizer_init()<\/em> en retourneert een pointer.<\/p>\n<p><\/p>\n<p>De initialisatiefunctie:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_init(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>De queue <em>q<\/em>, veld <em>size<\/em> wordt ingesteld op nul.<\/p>\n<p><\/p>\n<p>Bericht toevoegen:<\/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>Bericht m wordt toegevoegd aan de queue. De berekende grootte van de gegevensblokken wordt opgeteld bij <em>size<\/em>.<\/p>\n<p><\/p>\n<p>Overbrengen naar de buffer van alle berichten in de gegevensqueue van de 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>Berichten overbrengen van de link <em>q<\/em> naar de buffer gebeurt met behulp van de functie <em>ms_bufferizer_put()<\/em>.<\/p>\n<p><\/p>\n<p>Lezen uit de 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>Als de grootte van de gegevens die in de buffer zijn verzameld kleiner is dan het aangevraagde (<em>datalen<\/em>), de functie retourneert nul, gegevenskopie naar data wordt niet uitgevoerd. Anders wordt er een sequenti\u00eble kopie van gegevens uit de tuples in de buffer uitgevoerd. Na het kopi\u00ebren wordt de tuple verwijderd en wordt het geheugen vrijgegeven. Het kopi\u00ebren stopt wanneer datalen bytes zijn gekopieerd. Als de ruimte halverwege een gegevensblok opraakt, wordt in dit bericht het gegevensblok ingekort tot het resterende niet-gekopieerde deel. Bij de volgende aanroep gaat het kopi\u00ebren verder vanaf deze locatie.<\/p>\n<p><\/p>\n<p>Lezen van de hoeveelheid gegevens die op dit moment beschikbaar zijn in de buffer:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_get_avail(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Geeft het veld terug <em>size<\/em> van de buffer.<\/p>\n<p><\/p>\n<p>Overslaan van een deel van de gegevens in de 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>Het opgegeven aantal bytes gegevens wordt ge\u00ebxtraheerd en overgeslagen. De oudste gegevens worden overgeslagen.<\/p>\n<p><\/p>\n<p>Verwijderen van alle berichten die in de buffer zitten:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_flush(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>De gegevens teller wordt op nul gereset.<\/p>\n<p><\/p>\n<p>Verwijderen van alle berichten die in de buffer zitten:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_uninit(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Het resetten van de teller wordt niet uitgevoerd.<\/p>\n<p><\/p>\n<p>Verwijderen van de buffer en vrijgeven van het geheugen:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_destroy(MSBufferizer *obj);  <\/code><\/pre>\n<p><\/p>\n<p>Voorbeelden van het gebruik van de buffer zijn te vinden in de broncode van verschillende mediastreamerfilters. Bijvoorbeeld in de filter MS_L16_ENC, die de bytevolgorde in netwerkomgevingen omzet naar de hostvolgorde:<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>In het volgende artikel zullen we de vraag bespreken over de belastingsevaluatie voor de ticker en manieren om overmatige rekenbelasting in de mediastreamer te bestrijden.<\/p>\n<p>Bron: <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\/nl\/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=\"nl_NL\" \/>\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\/nl\/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\udd47 Leren over de VoIP-engine Mediastreamer2. Deel 11 | ProHoster","description":"Het materiaal van dit artikel is afkomstig van mijn Zen-kanaal.","canonical_url":"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"nl_NL","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\/nl\/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\/nl\/wp-json\/wp\/v2\/posts\/79540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/comments?post=79540"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/79540\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media\/79541"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media?parent=79540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/categories?post=79540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/tags?post=79540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}