{"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\/de\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","title":{"rendered":"Wir lernen den VoIP-Engine Mediastreamer2. Teil 11","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Der Artikelinhalt stammt von meinem <noindex><a rel=\"nofollow\" href=\"https:\/\/zen.yandex.ru\/profile\/editor\/id\/5e3f8e0751f5346faab4fc7a\">Zen-Kanal<\/a><\/noindex>.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Wir lernen den VoIP-Engine Mediastreamer2. Teil 11\" src=\"\/wp-content\/uploads\/2020\/04\/b66fab43eb45c626c50f0383dcfa7cca.png\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Datenverschiebungsmechanismus<\/p>\n<p><\/p>\n<ul>\n<li>Datenblock dblk_t<\/li>\n<li>Nachricht mblk_t<\/li>\n<li>Funktionen zur Arbeit mit Nachrichten mblk_t<\/li>\n<li>Warteschlange queue_t<\/li>\n<li>Funktionen zur Arbeit mit Warteschlangen queue_t<\/li>\n<li>Filterverbindungen<\/li>\n<li>Signalpunkt im Datenverarbeitungsgraphen<\/li>\n<li>Hintergrundaktivit\u00e4ten des Tickers<\/li>\n<li>Puffer (MSBufferizer)<\/li>\n<li>Funktionen zur Arbeit mit MSBufferizer<\/li>\n<\/ul>\n<p><\/p>\n<p>Letzte Woche <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/497932\/\">Artikel<\/a><\/noindex> Wir haben unseren eigenen Filter entwickelt. In diesem Artikel widmen wir uns dem internen Mechanismus zur Verschiebung von Daten zwischen den Filtern des Mediastreamers. Dies wird es uns in Zukunft erm\u00f6glichen, ausgekl\u00fcgelte Filter mit weniger Aufwand zu schreiben.<\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2 id=\"mehanizm-peremescheniya-dannyh\">Datenverschiebungsmechanismus<\/h2>\n<p><\/p>\n<p>Die Datenverschiebung im Mediastreamer erfolgt \u00fcber Warteschlangen, die durch die Struktur <em>queue_t<\/em>beschrieben werden. \u00dcber die Warteschlangen werden Serien von Nachrichten des Typs <em>mblk_t<\/em>\u00fcbertragen, die selbst keine Signaldaten enthalten, sondern lediglich Verweise auf die vorherige, folgende Nachricht und auf den Datenblock. Au\u00dferdem m\u00f6chte ich besonders betonen, dass es noch ein Feld f\u00fcr einen Verweis auf eine Nachricht desselben Typs gibt, das es erm\u00f6glicht, eine einfach verkettete Liste von Nachrichten zu organisieren. Eine Gruppe von Nachrichten, die durch solch eine Liste verbunden sind, nennen wir Tuppel. Somit kann jedes Element der Warteschlange eine einzelne Nachricht sein <em>mblk_t<\/em>, oder es kann der Kopf eines Tuppels von Nachrichten sein <em>mblk_t<\/em>. Jede Nachricht des Tuppels kann ihren eigenen zugeh\u00f6rigen Datenblock haben. Warum Tuplen ben\u00f6tigt werden, werden wir etwas sp\u00e4ter besprechen.<\/p>\n<p><\/p>\n<p>Wie oben erw\u00e4hnt, enth\u00e4lt die Nachricht selbst keinen Datenblock, sondern nur einen Verweis auf den Speicherbereich, in dem der Block gespeichert ist. In diesem Teil \u00e4hnelt das gesamte Konzept des Media-Streamers einem Lagerhaus voller T\u00fcren aus dem Cartoon \"Die Monster AG\", in dem T\u00fcren (Datenverweise \u2014 R\u00e4ume) mit irrsinniger Geschwindigkeit auf F\u00f6rderb\u00e4ndern bewegt werden, w\u00e4hrend die R\u00e4ume selbst unbewegt bleiben. <\/p>\n<p><\/p>\n<p>Nun, lassen Sie uns die hierarchische Struktur von unten nach oben betrachten und die genannten Entit\u00e4ten des Daten\u00fcbertragungsmechanismus im Mediastreamer im Detail untersuchen.<\/p>\n<p><\/p>\n<h3 id=\"blok--dannyh-dblk_t\">Datenblock <em>dblk_t<\/em><\/h3>\n<p><\/p>\n<p>Ein Datenblock besteht aus einem Header und einem Datenpuffer. Der Header wird durch die folgende Struktur beschrieben,<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct datab\n{\nunsigned char *db_base; \/\/ Zeiger auf den Anfang des Datenpuffers.\nunsigned char *db_lim;  \/\/ Zeiger auf das Ende des Datenpuffers.\nvoid (*db_freefn)(void*); \/\/ Funktion zur Freigabe des Speichers beim L\u00f6schen des Blocks.\nint db_ref; \/\/ Referenzz\u00e4hler.\n} dblk_t;<\/code><\/pre>\n<p><\/p>\n<p>Die Strukturfelder enthalten Zeiger auf den Anfang des Puffers, das Ende des Puffers und die Funktion zum L\u00f6schen des Datenpuffers. Das letzte Element im Header <em>db_ref<\/em> \u2014 ist ein Referenzz\u00e4hler; wenn er null erreicht, ist das ein Signal zum Entfernen dieses Blocks aus dem Speicher. Wenn der Datenblock mit der Funktion <em>datab_alloc() <\/em>erstellt wurde, wird der Datenpuffer sofort nach dem Header im Speicher angeordnet. In allen anderen F\u00e4llen kann sich der Puffer separat befinden. Im Datenpuffer werden die Signalwerte oder andere Daten gespeichert, die wir durch Filter verarbeiten m\u00f6chten.<\/p>\n<p><\/p>\n<p>Eine neue Instanz des Datenblocks wird mit der Funktion erstellt:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">dblk_t *datab_alloc(int size);<\/code><\/pre>\n<p><\/p>\n<p>Als Eingabeparameter wird die Gr\u00f6\u00dfe der Daten \u00fcbergeben, die der Block speichern wird. Es wird zus\u00e4tzlicher Speicher reserviert, um am Anfang des reservierten Speichers den Header \u2014 die Struktur <em>datab<\/em>unterzubringen. Aber bei der Verwendung anderer Funktionen ist dies nicht immer der Fall; in einigen F\u00e4llen kann der Datenpuffer getrennt vom Header des Datenblocks liegen. Die Strukturfelder werden bei der Erstellung so konfiguriert, dass ihr Feld <em>db_base<\/em> auf den Anfang des Datenbereichs und <em>db_lim<\/em> auf dessen Ende zeigt. Der Referenzz\u00e4hler <em>db_ref<\/em> wird auf Eins gesetzt. Der Zeiger auf die Funktion zur Datenbereinigung wird auf null gesetzt.<\/p>\n<p><\/p>\n<h3 id=\"soobschenie-mblk_t\">Nachricht <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Wie bereits erw\u00e4hnt, haben die Elemente der Warteschlange den Typ <em>mblk_t,<\/em> der wie folgt definiert ist:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct msgb\n{\n  struct msgb *b_prev;   \/\/ Zeiger auf das vorherige Element der Liste.\n  struct msgb *b_next;   \/\/ Zeiger auf das n\u00e4chste Element der Liste.\n  struct msgb *b_cont;   \/\/ Zeiger, um an die Nachricht andere Nachrichten anzuh\u00e4ngen, um ein Nachrichten-Tuplex zu erstellen.\n  struct datab *b_datap; \/\/ Zeiger auf die Struktur des Datenblocks.\n  unsigned char *b_rptr; \/\/ Zeiger auf den Anfang des Datenbereichs zum Lesen der Daten des Puffers b_datap.\n  unsigned char *b_wptr; \/\/ Zeiger auf den Anfang des Datenbereichs zum Schreiben der Daten des Puffers b_datap.\n  uint32_t reserved1;    \/\/ Reserviertes Feld 1, Mediastreamer platziert dort Verwaltungsinformationen. \n  uint32_t reserved2;    \/\/ Reserviertes Feld 2, Mediastreamer platziert dort Verwaltungsinformationen.\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>Struktur <em>mblk_t<\/em> enth\u00e4lt zu Beginn die Zeiger <em>b_prev<\/em>, <em>b_next<\/em>, die erforderlich sind, um eine doppelt verkettete Liste (die Warteschlange) zu organisieren. <em>queue_t<\/em>). <\/p>\n<p><\/p>\n<p>Dann folgt der Zeiger <em>b_cont<\/em>, der nur verwendet wird, wenn die Nachricht in das Tuple eingeht. F\u00fcr die letzte Nachricht im Tuple bleibt dieser Zeiger null. <\/p>\n<p><\/p>\n<p>Als N\u00e4chstes sehen wir einen Zeiger auf einen Datenblock <em>b_datap<\/em>, wegen dem die Nachricht existiert. Darauf folgen Zeiger auf den Bereich innerhalb des Datenblocks. Das Feld <em>b_rptr<\/em> zeigt den Ort an, von dem die Daten aus dem Puffer gelesen werden. Das Feld <em>b_wptr<\/em> zeigt den Ort an, von dem in den Puffer geschrieben wird. <\/p>\n<p><\/p>\n<p>Die verbleibenden Felder sind dienstlicher Natur und beziehen sich nicht auf die Funktionsweise des Daten\u00fcbertragungsmechanismus.<\/p>\n<p><\/p>\n<p>Nachfolgend ist eine einzelne Nachricht mit dem Namen <em>m1<\/em> und einem Datenblock dargestellt. <em>d1<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Wir lernen den VoIP-Engine Mediastreamer2. Teil 11\" src=\"\/wp-content\/uploads\/2020\/04\/e6a726b13491832258c1228f03bde802.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIm n\u00e4chsten Bild ist ein Tuple aus drei Nachrichten dargestellt <em>m1<\/em>, <em>m1_1<\/em>, <em>m1_2<\/em>.<br \/>\n<img decoding=\"async\" alt=\"Wir lernen den VoIP-Engine Mediastreamer2. Teil 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\">Funktionen zur Arbeit mit Nachrichten <em>mblk_t<\/em><\/h3>\n<p><\/p>\n<p>Eine neue Nachricht <em>mblk_t<\/em> wird durch die Funktion erstellt:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *allocb(int size, int pri); <\/code><\/pre>\n<p><\/p>\n<p>sie reserviert im Speicher eine neue Nachricht <em>mblk_t<\/em> mit einem Datenblock der angegebenen Gr\u00f6\u00dfe <em>Gr\u00f6\u00dfe<\/em>, das zweite Argument \u2014 <em>pri<\/em> wird in der betrachteten Version der Bibliothek nicht verwendet. Es sollte null bleiben. W\u00e4hrend der Ausf\u00fchrung der Funktion wird Speicher f\u00fcr die Struktur der neuen Nachricht zugewiesen und die Funktion <em>mblk_init()<\/em>, die alle Felder der erstellten Struktur auf null setzt, wird aufgerufen und dann, mithilfe des oben genannten <em>datab_alloc()<\/em>, wird ein Datenpuffer erstellt. Danach erfolgt die Konfiguration der Felder in der Struktur: <\/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>Am Ende erhalten wir eine neue Nachricht mit initialisierten Feldern und einem leeren Datenpuffer. Um Daten zur Nachricht hinzuzuf\u00fcgen, m\u00fcssen sie in den Datenblockpuffer kopiert werden:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">memcpy(msg-&gt;b_rptr, data, size);<\/code><\/pre>\n<p><\/p>\n<p>wo <em>data<\/em> \u2014 ein Zeiger auf die Datenquelle, und <em>Gr\u00f6\u00dfe<\/em> \u2014 deren Gr\u00f6\u00dfe.<br \/>\nDann muss der Zeiger auf den Schreibpunkt aktualisiert werden, damit er wieder auf den Anfang des freien Bereichs im Puffer zeigt:<\/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>Wenn eine Nachricht aus einem bereits vorhandenen Puffer erstellt werden soll, ohne Kopieren, wird daf\u00fcr die Funktion verwendet:<\/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>Die Funktion wird nach der Erstellung der Nachricht und der Struktur des Datenblocks die Zeiger auf die Daten an die Adresse <em>buf<\/em>. Das hei\u00dft, in diesem Fall befindet sich der Datenpuffer nicht unmittelbar nach den Feldern des Datenblock-Headers, wie es bei der Erstellung des Datenblocks durch die Funktion der Fall war. <em>datab_alloc()<\/em>Der an die Funktion \u00fcbergebene Datenpuffer bleibt an der Stelle, wo er war, wird aber mithilfe von Zeigern auf den gerade erstellten Blockheader gelenkt, und dieser wiederum auf die Nachricht.<\/p>\n<p><\/p>\n<p>Zu einer Nachricht <em>mblk_t<\/em> k\u00f6nnen mehrere Datenbl\u00f6cke sequenziell angeh\u00e4ngt werden. Dies geschieht durch die Funktion:<\/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 die Nachricht, zu der ein weiterer Datenblock hinzugef\u00fcgt werden soll;<br \/>\n<em>data<\/em> \u2014 ein Zeiger auf den Block, dessen Kopie zur Nachricht hinzugef\u00fcgt wird;<br \/>\n<em>Gr\u00f6\u00dfe<\/em> \u2014 die Gr\u00f6\u00dfe der Daten;<br \/>\n<em>pad<\/em> \u2014 ein Flag, das anzeigt, dass die Gr\u00f6\u00dfe des zugewiesenen Speichers auf eine Grenze von 4 Bytes ausgerichtet werden soll (die Auff\u00fcllung erfolgt mit Nullen). <\/p>\n<p><\/p>\n<p>Wenn im vorhandenen Datenpuffer der Nachricht gen\u00fcgend Platz ist, werden die neuen Daten an die bereits dort vorhandenen Daten angeh\u00e4ngt. Sollte der freie Platz im Datenpuffer der Nachricht geringer sein als <em>Gr\u00f6\u00dfe<\/em>, wird eine neue Nachricht erstellt, mit ausreichend gro\u00dfem Puffer, und die Daten werden in ihren Puffer kopiert. Diese neue Nachricht wird mit der urspr\u00fcnglichen \u00fcber einen Zeiger verbunden <em>b_cont<\/em>. In diesem Fall verwandelt sich die Nachricht in ein Tupel. <\/p>\n<p><\/p>\n<p>Wenn in das Tupel ein weiterer Datenblock hinzugef\u00fcgt werden soll, muss die Funktion verwendet werden:<\/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>Sie sucht die letzte Nachricht im Tupel (die hat <em>b_cont<\/em> wird null sein) und ruft f\u00fcr diese Nachricht die Funktion <em>appendb()<\/em>.<\/p>\n<p><\/p>\n<p>Um die Gr\u00f6\u00dfe der Daten in der Nachricht oder dem Tupel zu ermitteln, kann die Funktion verwendet werden:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Diese Durchl\u00e4uft alle Nachrichten des Tupels und gibt die Gesamtanzahl der Daten in den Datenpuffern dieser Nachrichten zur\u00fcck. F\u00fcr jede Nachricht wird die Datenanzahl so berechnet:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\"> mp-&gt;b_wptr - mp-&gt;b_rptr<\/code><\/pre>\n<p><\/p>\n<p>Um zwei Tupel zu verbinden, wird die Funktion verwendet:<\/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>Sie f\u00fcgt das Tupel <em>newm<\/em> an das Ende des Tupels <em>mp<\/em> an und gibt einen Zeiger auf die letzte Nachricht des entstandenen Tupels zur\u00fcck.<\/p>\n<p><\/p>\n<p>Falls n\u00f6tig, kann das Tupel in eine einzige Nachricht mit einem zusammenh\u00e4ngenden Datenblock verwandelt werden, dies geschieht durch die Funktion:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,int len);<\/code><\/pre>\n<p><\/p>\n<p>wenn das Argument <em>len<\/em> gleich -1 ist, wird die Gr\u00f6\u00dfe des zugewiesenen Puffers automatisch bestimmt. Wenn <em>len<\/em> Eine positive Zahl wird ein Puffer dieser Gr\u00f6\u00dfe erstellen, in den die Daten der Nachrichten des Tuples kopiert werden. Wenn der Puffer voll ist, wird das Kopieren eingestellt. Die erste Nachricht des Tuples erh\u00e4lt einen Puffer neuer Gr\u00f6\u00dfe mit den kopierten Daten. Die anderen Nachrichten werden gel\u00f6scht und der Speicher wird in den Heap zur\u00fcckgegeben.<\/p>\n<p><\/p>\n<p>Beim L\u00f6schen der Struktur <em>mblk_t<\/em> wird der Referenzz\u00e4hler des Datenblocks ber\u00fccksichtigt, wenn bei der Aufforderung <em>freeb()<\/em> er gleich Null ist, werden die Datenpuffer zusammen mit dem zuweisenden Exemplar gel\u00f6scht. <em>mblk_t<\/em>, auf das er zeigt.<\/p>\n<p><\/p>\n<p>Initialisierung der Felder der neuen Nachricht:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void mblk_init(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Hinzuf\u00fcgen einer weiteren Datenportion zur Nachricht: <\/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>Wenn die neuen Daten nicht in den freien Platz des Datenpuffers der Nachricht passen, wird der Nachricht ein separat erstelltes Nachricht mit einem Puffer der passenden Gr\u00f6\u00dfe angeh\u00e4ngt (im ersten Nachricht wird ein Zeiger auf die hinzugef\u00fcgte Nachricht gesetzt), die Nachricht wird zu einem Tuple.<\/p>\n<p><\/p>\n<p>Hinzuf\u00fcgen einer Datenportion zum Tuple:<\/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>Die Funktion ruft appendb() in einer Schleife auf.<\/p>\n<p><\/p>\n<p>Vereinigen von zwei Tuples in ein einzelnes:<\/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>Nachricht <em>newm<\/em> wird angef\u00fcgt an <em>mp<\/em>.<\/p>\n<p><\/p>\n<p>Erstellen einer Kopie einer einzelnen Nachricht:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copyb(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Vollst\u00e4ndige Kopierung des Tuples mit allen Datenbl\u00f6cken:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *copymsg(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Dabei werden die Elemente des Tuples von der Funktion <em>copyb()<\/em>.<\/p>\n<p><\/p>\n<p>Erstellen einer leichten Kopie der Nachricht <em>mblk_t<\/em>. Dabei wird der Datenblock nicht kopiert, sondern der Referenzz\u00e4hler erh\u00f6ht. <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>Erstellen einer leichten Kopie des Tuples. Die Datenbl\u00f6cke werden nicht kopiert, nur ihre Referenzz\u00e4hler werden erh\u00f6ht. <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>Zusammenf\u00fcgen aller Nachrichten des Tuples zu einer Nachricht:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void msgpullup(mblk_t *mp,size_t len);<\/code><\/pre>\n<p><\/p>\n<p>Wenn das Argument <em>len<\/em> gleich -1 ist, wird die Gr\u00f6\u00dfe des zugewiesenen Puffers automatisch bestimmt.<\/p>\n<p><\/p>\n<p>L\u00f6schen einer Nachricht, des Tuples:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void freemsg(mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Der Referenzz\u00e4hler des Datenblocks wird um eins verringert. Wenn er dabei Null erreicht, wird auch der Datenblock gel\u00f6scht.<\/p>\n<p><\/p>\n<p>Z\u00e4hlen des gesamten Datenvolumens in der Nachricht oder im Tuple.<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">size_t msgdsize(const mblk_t *mp);<\/code><\/pre>\n<p><\/p>\n<p>Extrahieren der Nachricht aus dem Ende der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t *ms_queue_peek_last (q);<\/code><\/pre>\n<p><\/p>\n<p>Kopieren des Inhalts der reservierten Felder einer Nachricht in eine andere Nachricht (in diesen Feldern befinden sich tats\u00e4chlich Flags, die vom Medienstreamer verwendet werden):<\/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\">Warteschlange <em>queue_t<\/em><\/h3>\n<p><\/p>\n<p>Die Warteschlange in dem Medienstreamer ist als ringf\u00f6rmige doppeltverkettete Liste implementiert. Jedes Element der Liste enth\u00e4lt einen Zeiger auf einen Datenblock mit Signalabtastungen. Das bedeutet, dass nur die Zeiger auf den Datenblock nacheinander verschoben werden, w\u00e4hrend die Daten selbst station\u00e4r bleiben. Das hei\u00dft, es werden nur die Links auf diese verschoben.<br \/>\nDie Struktur, die die Warteschlange beschreibt <em>queue_t<\/em>, ist unten dargestellt:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _queue\n{\n   mblk_t _q_stopper; \/* \"Leeres\" Element der Warteschlange, zeigt nicht auf Daten, wird nur zur Steuerung der Warteschlange verwendet. Bei der Initialisierung der Warteschlange (qinit()) werden seine Zeiger so eingestellt, dass sie auf sich selbst zeigen. *\/\n   int q_mcount;        \/\/ Anzahl der Elemente in der Warteschlange.\n} queue_t;<\/code><\/pre>\n<p><\/p>\n<p>Die Struktur enth\u00e4lt ein Feld \u2013 einen Zeiger <em>_q_stopper<\/em> vom Typ *mblk_t, der auf das erste Element (Nachricht) in der Warteschlange zeigt. Das zweite Feld der Struktur ist der Z\u00e4hler der in der Warteschlange befindlichen Nachrichten.<br \/>\nIm folgenden Bild ist die Warteschlange mit dem Namen q1 dargestellt, die 4 Nachrichten m1, m2, m3, m4 enth\u00e4lt.<br \/>\n<img decoding=\"async\" alt=\"Wir lernen den VoIP-Engine Mediastreamer2. Teil 11\" src=\"\/wp-content\/uploads\/2020\/04\/07fc043861edf15e5eb40e49c8ec5e3e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIm n\u00e4chsten Bild ist die Warteschlange mit dem Namen q1 dargestellt, die 4 Nachrichten m1, m2, m3, m4 enth\u00e4lt. Die Nachricht m2 ist der Kopf des Tupels, in das noch zwei Nachrichten m2_1 und m2_2 eingef\u00fcgt werden.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"Wir lernen den VoIP-Engine Mediastreamer2. Teil 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\">Funktionen zur Arbeit mit Warteschlangen queue_t<\/h3>\n<p><\/p>\n<p>Initialisierung der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void qinit(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Feld <em>_q_stopper<\/em> (im Folgenden als \"Stopper\" bezeichnet) wird durch die Funktion initialisiert <em>mblk_init()<\/em>, sein Zeiger auf das vorhergehende Element und das n\u00e4chste Element werden so konfiguriert, dass sie auf sich selbst zeigen. Der Z\u00e4hler der Warteschlangenelemente wird auf Null gesetzt.<\/p>\n<p><\/p>\n<p>Hinzuf\u00fcgen eines neuen Elements (Nachricht):<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void putq(queue_t *q, mblk_t *m);<\/code><\/pre>\n<p><\/p>\n<p>Das neue Element <em>m<\/em> wird ans Ende der Liste hinzugef\u00fcgt, die Zeiger des Elements werden so konfiguriert, dass der Stopper f\u00fcr es das n\u00e4chste Element wird und es f\u00fcr den Stopper das vorhergehende. Der Z\u00e4hler der Warteschlangenelemente wird erh\u00f6ht.<\/p>\n<p><\/p>\n<p>Entnahme eines Elements aus der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * getq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>es wird die Nachricht entnommen, die nach dem Stopper steht, der Z\u00e4hler der Elemente wird vermindert. Wenn in der Warteschlange au\u00dfer dem Stopper keine Elemente vorhanden sind, wird 0 zur\u00fcckgegeben.<\/p>\n<p><\/p>\n<p>Einf\u00fcgen einer Nachricht in die Warteschlange:<\/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>Das Element <em>mp<\/em> wird vor dem Element <em>emp<\/em>. Wenn <em>emp<\/em>gesetzt, ist es gleich 0, wird die Nachricht ans Ende der Warteschlange hinzugef\u00fcgt.<\/p>\n<p><\/p>\n<p>Entnahme der Nachricht vom Kopf der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void remq(queue_t *q, mblk_t *mp); <\/code><\/pre>\n<p><\/p>\n<p>Der Z\u00e4hler der Elemente wird vermindert.<\/p>\n<p><\/p>\n<p>Lesen des Zeigers auf das erste Element in der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * peekq(queue_t *q); <\/code><\/pre>\n<p><\/p>\n<p>Entfernen aller Elemente aus der Warteschlange mit gleichzeitiger Entfernung der Elemente selbst:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void flushq(queue_t *q, int how);<\/code><\/pre>\n<p><\/p>\n<p>Argument <em>how<\/em> wird nicht verwendet. Der Z\u00e4hler der Elemente in der Warteschlange wird auf null gesetzt.<\/p>\n<p><\/p>\n<p>Makro zur Ablesung des Zeigers auf das letzte Element der Warteschlange:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">mblk_t * qlast(queue_t *q);<\/code><\/pre>\n<p><\/p>\n<p>Bei der Arbeit mit Nachrichtenwarteschlangen sollte beachtet werden, dass beim Aufruf <em>ms_queue_put(q, m)<\/em> mit einem Nullzeiger auf die Nachricht die Funktion in eine Endlosschleife ger\u00e4t. Ihr Programm wird h\u00e4ngen bleiben. \u00c4hnlich verh\u00e4lt sich <em>ms_queue_next(q, m)<\/em>.<\/p>\n<p><\/p>\n<h3 id=\"soedinenie-filtrov\">Filterverbindungen<\/h3>\n<p><\/p>\n<p>Die oben beschriebene Warteschlange wird verwendet, um Nachrichten von einem Filter zu einem anderen oder von einem Filter zu mehreren Filtern zu \u00fcbertragen. Filter und ihre Verbindungen untereinander bilden einen gerichteten Graphen. Den Eingang oder Ausgang eines Filters nennen wir zusammenfassend \"Pin\". Zur Beschreibung der Reihenfolge, in der die Filter miteinander verbunden sind, wird im Media-Streamer der Begriff \"Signalpunkt\" verwendet. Der Signalpunkt ist eine Struktur <em>_MSCPoint<\/em>, die einen Zeiger auf den Filter und die Nummer eines seiner Pins enth\u00e4lt; dementsprechend beschreibt sie die Verbindung eines der Eing\u00e4nge oder Ausg\u00e4nge des Filters. <\/p>\n<p><\/p>\n<h4 id=\"signalnaya-tochka-grafa-obrabotki-dannyh\">Signalpunkt im Datenverarbeitungsgraphen<\/h4>\n<p><\/p>\n<pre><code class=\"cpp\">typedef struct _MSCPoint{\nstruct _MSFilter *filter; \\\\ Zeiger auf den Mediastreamer-Filter.\nint pin;                        \\\\ Nummer eines der Eing\u00e4nge oder Ausg\u00e4nge des Filters, d.h. Pin.\n} MSCPoint;\n<\/code><\/pre>\n<p><\/p>\n<p>Die Pins der Filter werden beginnend bei null nummeriert. <\/p>\n<p><\/p>\n<p>Die Verbindung zweier Pins \u00fcber eine Nachrichtenwarteschlange wird durch die Struktur <em>_MSQueue<\/em>, die die Nachrichtenwarteschlange und Zeiger auf zwei Signalpunkte, die sie verbindet, enth\u00e4lt:<\/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>Wir nennen diese Struktur Signal-Link. Jeder Mediastreamer-Filter enth\u00e4lt eine Tabelle der Eingangslinks und eine Tabelle der Ausgangslinks (<em>MSQueue<\/em>). Die Gr\u00f6\u00dfe der Tabellen wird bei der Erstellung des Filters festgelegt; das haben wir bereits mit Hilfe der exportierten Variablen vom Typ <em>MSFilterDesc<\/em>, als wir unseren eigenen Filter entwickelt haben. Unten ist die Struktur dargestellt, die jeden Filter im Mediastreamer beschreibt, <em>MSFilter<\/em>:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">\nstruct _MSFilter{\n    MSFilterDesc *desc;    \n    \/* Zeiger auf die Filterbeschreibung. *\/\n    \/* Gesch\u00fctzte Attribute, sie k\u00f6nnen nicht verschoben oder entfernt werden, da sonst die Funktionsweise mit Plugins beeintr\u00e4chtigt wird. *\/\n    ms_mutex_t lock;      \/* Semaphore. *\/\n    MSQueue **inputs;     \/* Tabelle der Eingabelinks. *\/\n    MSQueue **outputs;    \/* Tabelle der Ausgabelinks. *\/\n    struct _MSFactory *factory; \/* Zeiger auf die Fabrik, die diese Filterinstanz erstellt hat. *\/\n    void *padding;              \/* Nicht verwendet, wird verwendet, wenn gesch\u00fctzte Felder hinzugef\u00fcgt werden. *\/\n    void *data;                 \/* Zeiger auf eine beliebige Struktur zur Speicherung von Daten des internen Zustands des Filters und Zwischenergebnissen. *\/\n    struct _MSTicker *ticker;   \/* Zeiger auf das Tickobjekt, welches nicht null sein darf, wenn die Funktion process() aufgerufen wird. *\/\n    \/*private attributes, they can be moved and changed at any time*\/\n    MSList *notify_callbacks; \/* Liste von R\u00fcckrufmethoden, die zur Behandlung von Filterereignissen verwendet werden. *\/\n    uint32_t last_tick;       \/* Nummer des letzten Taktes, in dem die Funktion process() aufgerufen wurde. *\/\n    MSFilterStats *stats;     \/* Statistik der Filteroperation.*\/\n    int postponed_task; \/* Anzahl der aufgeschobenen Aufgaben. Einige Filter k\u00f6nnen die Verarbeitung von Daten (den Aufruf von process()) um mehrere Takte verschieben.*\/\n    bool_t seen;  \/* Flag, das vom Ticker verwendet wird, um zu kennzeichnen, dass diese Filterinstanz bereits in diesem Takt verarbeitet wurde.*\/\n};\ntypedef struct _MSFilter MSFilter;\n<\/code><\/pre>\n<p><\/p>\n<p>Nachdem wir die Filter in unserem C-Programm gem\u00e4\u00df unserem Konzept verbunden haben (aber den Ticker nicht angeschlossen haben), haben wir somit einen gerichteten Graphen erstellt, dessen Knoten Instanzen der Struktur sind, <em>MSFilter<\/em>, und die Kanten sind Linkinstanzen. <em>MSQueue<\/em>. <\/p>\n<p><\/p>\n<h3 id=\"zakulisnaya-deyatelnost-tikera\">Hintergrundaktivit\u00e4ten des Tickers<\/h3>\n<p><\/p>\n<p>Als ich Ihnen sagte, dass der Ticker ein Quelle f\u00fcr Taktsignale ist, war das nicht die ganze Wahrheit \u00fcber ihn. Der Ticker ist ein Objekt, das zeitgesteuert Funktionen ausf\u00fchrt <em>process()<\/em> aller Filter des Diagramms (Graphen), an die er angeschlossen ist. Wenn wir in einem C-Programm einen Ticker mit einem Filter des Graphen verbinden, zeigen wir dem Ticker den Graphen, den er von diesem Zeitpunkt an steuern wird, bis wir ihn trennen. Nach der Verbindung beginnt der Ticker, den ihm anvertrauten Graphen zu durchsuchen und eine Liste der Filter zu erstellen, in die er eindringt. Um nicht denselben Filter zweimal zu \"z\u00e4hlen\", kennzeichnet er die entdeckten Filter, indem er in ihnen ein Flag setzt <em>seen<\/em>setzt. Die Suche erfolgt in den Linktabellen, die jeder Filter hat. <\/p>\n<p><\/p>\n<p>W\u00e4hrend seiner Einf\u00fchrungstour \u00fcberpr\u00fcft der Ticker, ob unter den Filtern mindestens einer vorhanden ist, der die Rolle einer Datenquelle erf\u00fcllt. Wenn keine gefunden wird, gilt das Diagramm als fehlerhaft, und der Ticker bricht die Ausf\u00fchrung ab.<\/p>\n<p><\/p>\n<p>Wenn der Graph \"korrekt\" ist, wird f\u00fcr jeden gefundenen Filter zur Initialisierung die Funktion aufgerufen <em>preprocess()<\/em>aufgerufen. Sobald der Moment f\u00fcr den n\u00e4chsten Verarbeitungsschritt kommt (standardm\u00e4\u00dfig alle 10 Millisekunden), ruft der Ticker die Funktion <em>process()<\/em> f\u00fcr alle zuvor gefundenen Quellfilter auf, gefolgt von den restlichen Filtern in der Liste. Wenn der Filter Eingangslinks hat, wird der Aufruf der Funktion <em>process()<\/em> dies wiederholt sich, bis die Warteschlangen der Eingangslinks leer sind. Danach geht er zum n\u00e4chsten Filter der Liste \u00fcber und \"bl\u00e4ttert\" ihn, bis die Eingangslinks von Nachrichten befreit sind. Der Ticker wechselt von Filter zu Filter, bis die Liste beendet ist. Damit endet die Bearbeitung des Takts. <\/p>\n<p><\/p>\n<p>Jetzt kehren wir zu den Tuplen zur\u00fcck und sprechen dar\u00fcber, warum eine solche Entit\u00e4t im Mediastreamer hinzugef\u00fcgt wurde. Allgemein ben\u00f6tigt der Algorithmus, der innerhalb des Filters arbeitet, andere Datenmengen als die Gr\u00f6\u00dfe der Datenpuffer, die am Eingang ankommen. Nehmen wir an, wir schreiben einen Filter, der eine schnelle Fourier-Transformation durchf\u00fchrt, die definitionsgem\u00e4\u00df nur Datenbl\u00f6cke verarbeiten kann, deren Gr\u00f6\u00dfe eine Potenz von zwei ist. Lassen Sie uns 512 Abtastwerte annehmen. Wenn die Daten \u00fcber einen Telefonkanal generiert werden, wird der Datenpuffer jeder Nachricht am Eingang 160 Abtastwerte des Signals liefern. Es gibt den Anreiz, die Daten vom Eingang nicht abzurufen, bis die ben\u00f6tigte Datenmenge erreicht ist. In diesem Fall w\u00fcrde es jedoch zu einem Konflikt mit dem Ticker kommen, der erfolglos versucht, den Filter zu scrollen, bis die Eingangslinks entleert sind. Wir haben diese Regel zuvor als drittes Prinzip der Funktionsweise des Filters bezeichnet. Gem\u00e4\u00df diesem Prinzip muss die Funktion process() des Filters alle Daten aus den Eingangswarteschlangen abholen.<\/p>\n<p><\/p>\n<p>Dar\u00fcber hinaus k\u00f6nnen am Eingang nur 512 Messwerte entnommen werden, da die Abholung nur in ganzen Bl\u00f6cken m\u00f6glich ist, d.h. der Filter muss 640 Messwerte entnehmen und verwendet 512 davon, w\u00e4hrend der Rest f\u00fcr das Ansammeln neuer Daten reserviert bleibt. So muss unser Filter neben seiner Hauptfunktion auch unterst\u00fctzende Ma\u00dfnahmen zur Zwischenlagerung der Eingangsdaten gew\u00e4hrleisten. Die Entwickler des Mediasreamers und der L\u00f6sung dieses allgemeinen Problems haben ein spezielles Objekt \u2013 MSBufferizer (Puffer) \u2013 entwickelt, das diese Aufgabe mit Hilfe von Tupeln l\u00f6st. <\/p>\n<p><\/p>\n<h3 id=\"buferizator-msbufferizer\">Puffer (MSBufferizer)<\/h3>\n<p><\/p>\n<p>Dies ist ein Objekt, das die Eingangsdaten innerhalb des Filters ansammeln wird und mit der Verarbeitung beginnen wird, sobald die Menge der Daten ausreichend ist, um den Filteralgorithmus auszuf\u00fchren. Solange der Puffer Daten ansammelt, wird der Filter im Leerlaufmodus arbeiten, ohne Rechenleistung des Prozessors zu verbrauchen. Sobald jedoch die Lese-Funktion des Puffers einen Wert ungleich Null zur\u00fcckgibt, beginnt die Funktion process() des Filters, die im Puffer gespeicherten Daten in Portionen der ben\u00f6tigten Gr\u00f6\u00dfe zu entnehmen und zu verarbeiten, bis alle Daten verbraucht sind.<br \/>\nVorl\u00e4ufig nicht ben\u00f6tigte Daten bleiben im Puffer als erstes Element des Tupels, an das nachfolgende Bl\u00f6cke von Eingangsdaten angeh\u00e4ngt werden. <\/p>\n<p><\/p>\n<p>Die Struktur, die den Puffer beschreibt: <\/p>\n<p><\/p>\n<pre><code class=\"cpp\">struct _MSBufferizer{\nqueue_t q; \/* Nachrichtenwarteschlange. *\/\nint size; \/* Gesamter Datenumfang, der sich derzeit im Puffer befindet. *\/\n};\ntypedef struct _MSBufferizer MSBufferizer;<\/code><\/pre>\n<p><\/p>\n<h3 id=\"funkcii-raboty-s-msbufferizer\">Funktionen zur Arbeit mit MSBufferizer<\/h3>\n<p><\/p>\n<p>Erstellung eines neuen Puffermoduls:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">MSBufferizer * ms_bufferizer_new(void);<\/code><\/pre>\n<p><\/p>\n<p>Speicher wird zugewiesen, wird in <em>ms_bufferizer_init()<\/em> initialisiert und ein Zeiger wird zur\u00fcckgegeben.<\/p>\n<p><\/p>\n<p>Funktion zur Initialisierung:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_init(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Die Warteschlange <em>q<\/em>, das Feld <em>Gr\u00f6\u00dfe<\/em> wird auf Null gesetzt.<\/p>\n<p><\/p>\n<p>Nachricht hinzuf\u00fcgen:<\/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>Die Nachricht m wird in die Warteschlange eingef\u00fcgt. Die berechnete Gr\u00f6\u00dfe der Datenbl\u00f6cke wird zu <em>Gr\u00f6\u00dfe<\/em>.<\/p>\n<p><\/p>\n<p>Umsetzung in den Puffer aller Nachrichten aus der Datenwarteschlange <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>Der Transfer von Nachrichten aus der Warteschlange <em>q<\/em> in den Puffer erfolgt durch die Funktion <em>ms_bufferizer_put()<\/em>.<\/p>\n<p><\/p>\n<p>Lesen aus dem Puffer:<\/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>Wenn die Gr\u00f6\u00dfe der im Puffer gesammelten Daten kleiner ist als angefordert (<em>datalen<\/em>), die Funktion gibt null zur\u00fcck, das Kopieren von Daten in data wird nicht durchgef\u00fchrt. Andernfalls erfolgt die sequentielle Kopie von Daten aus den Tuples, die sich im Puffer befinden. Nach dem Kopieren wird das Tuple gel\u00f6scht und der Speicher wird freigegeben. Das Kopieren endet, sobald datalen Bytes kopiert wurden. Wenn der Speicher mitten im Datenblock endet, wird in dieser Meldung der Datenblock auf den verbleibenden, nicht kopierten Teil verk\u00fcrzt. Beim n\u00e4chsten Aufruf wird das Kopieren an dieser Stelle fortgesetzt.<\/p>\n<p><\/p>\n<p>Lesen der Menge an Daten, die derzeit im Puffer verf\u00fcgbar sind:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">int ms_bufferizer_get_avail(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Gibt das Feld zur\u00fcck <em>Gr\u00f6\u00dfe<\/em> des Puffers.<\/p>\n<p><\/p>\n<p>Abwerfen eines Teils der Daten, die sich im Puffer befinden:<\/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>Die angegebene Anzahl von Bytes an Daten wird extrahiert und verworfen. Die \u00e4ltesten Daten werden verworfen.<\/p>\n<p><\/p>\n<p>L\u00f6schen aller Nachrichten, die sich im Puffer befinden:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_flush(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Der Datenz\u00e4hler wird auf null zur\u00fcckgesetzt.<\/p>\n<p><\/p>\n<p>L\u00f6schen aller Nachrichten, die sich im Puffer befinden:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_uninit(MSBufferizer *obj); <\/code><\/pre>\n<p><\/p>\n<p>Das Zur\u00fccksetzen des Z\u00e4hlers erfolgt nicht.<\/p>\n<p><\/p>\n<p>L\u00f6schen des Puffers und Freigabe von Speicher:<\/p>\n<p><\/p>\n<pre><code class=\"cpp\">void ms_bufferizer_destroy(MSBufferizer *obj);  <\/code><\/pre>\n<p><\/p>\n<p>Beispiele f\u00fcr die Verwendung des Puffers finden Sie im Quellcode mehrerer Streaming-Filter. Zum Beispiel im Filter MS_L16_ENC, der die Bytes in den Proben von Netzwerk- in Host-Reihenfolge umsortiert:<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>Im n\u00e4chsten Artikel betrachten wir die Frage der Lastbewertung auf dem Ticker und M\u00f6glichkeiten zur Bek\u00e4mpfung \u00fcberm\u00e4\u00dfiger Rechenlast im Medienstreamer.<\/p>\n<p>Quelle: <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\/de\/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=\"de_DE\" \/>\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\/de\/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\udd47Wir lernen die VoIP-Engine Mediastreamer2 kennen. Teil 11 | ProHoster","description":"Das Material des Artikels stammt von meinem Zen-Kanal.","canonical_url":"https:\/\/prohoster.info\/de\/blog\/administrirovanie\/izuchaem-voip-dvizhok-mediastreamer2-chast-11","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"de_DE","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\/de\/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\/de\/wp-json\/wp\/v2\/posts\/79540","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/comments?post=79540"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/posts\/79540\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media\/79541"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/media?parent=79540"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/categories?post=79540"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/de\/wp-json\/wp\/v2\/tags?post=79540"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}