Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11

Das Material des Artikels stammt von mir Zen-Kanal.

Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11

Datenbewegungsmechanismus

  • Datenblock dblk_t
  • Nachricht mblk_t
  • Funktionen zum Arbeiten mit Nachrichten mblk_t
  • Warteschlange queue_t
  • Funktionen zum Arbeiten mit Warteschlangen queue_t
  • Filter anschließen
  • Signalpunkt des Datenverarbeitungsdiagramms
  • Aktivitäten hinter den Kulissen des Tickers
  • Puffer (MSBufferizer)
  • Funktionen zum Arbeiten mit MSBufferizer

In der Vergangenheit Artikel Wir haben unseren eigenen Filter entwickelt. Dieser Artikel konzentriert sich auf den internen Mechanismus zum Verschieben von Daten zwischen Media-Streamer-Filtern. Dadurch können Sie in Zukunft anspruchsvolle Filter mit weniger Aufwand schreiben.

Datenbewegungsmechanismus

Die Datenbewegung im Medienstreamer erfolgt mithilfe von Warteschlangen, die durch die Struktur beschrieben werden queue_t. Nachrichtenketten wie mblk_t, die selbst keine Signaldaten enthalten, sondern nur Links zur vorherigen, nächsten Nachricht und zum Datenblock enthalten. Darüber hinaus möchte ich besonders hervorheben, dass es auch ein Feld für einen Link zu einer Nachricht des gleichen Typs gibt, mit dem Sie eine einfach verknüpfte Liste von Nachrichten organisieren können. Wir nennen eine Gruppe von Nachrichten, die durch eine solche Liste vereint sind, ein Tupel. Somit kann jedes Element der Warteschlange eine einzelne Nachricht sein mblk_t, und vielleicht der Kopf eines Nachrichtentupels mblk_t. Jede Tupelnachricht kann einen eigenen Stationsdatenblock haben. Wir werden etwas später besprechen, warum Tupel benötigt werden.

Wie oben erwähnt, enthält die Nachricht selbst keinen Datenblock, sondern nur einen Zeiger auf den Speicherbereich, in dem der Block gespeichert ist. In diesem Teil erinnert das Gesamtbild der Arbeit des Medienstreamers an das Türlager im Zeichentrickfilm „Monsters, Inc.“, wo sich Türen (Links zu Daten – Räume) mit wahnsinniger Geschwindigkeit entlang von Hängebändern bewegen, während die Räume selbst bleiben bewegungslos.

Betrachten wir nun die aufgelisteten Entitäten des Datenübertragungsmechanismus im Medienstreamer im Detail, indem wir uns in der Hierarchie von unten nach oben bewegen.

Datenblock dblk_t

Der Datenblock besteht aus einem Header und einem Datenpuffer. Der Header wird durch die folgende Struktur beschrieben:

typedef struct datab
{
unsigned char *db_base; // Указатель на начало буфер данных.
unsigned char *db_lim;  // Указатель на конец буфер данных.
void (*db_freefn)(void*); // Функция освобождения памяти при удалении блока.
int db_ref; // Счетчик ссылок.
} dblk_t;

Die Felder der Struktur enthalten Zeiger auf den Pufferanfang, das Pufferende und die Funktion zum Löschen des Datenpuffers. Letztes Element im Header db_ref — Referenzzähler, wenn er Null erreicht, dient dies als Signal zum Löschen dieses Blocks aus dem Speicher. Ob der Datenblock von der Funktion erstellt wurde datab_alloc() , dann wird der Datenpuffer unmittelbar nach dem Header im Speicher abgelegt. In allen anderen Fällen kann der Puffer irgendwo separat platziert werden. Der Datenpuffer enthält Signalproben oder andere Daten, die wir mit Filtern verarbeiten möchten.

Eine neue Instanz eines Datenbausteins wird mit der Funktion erstellt:

dblk_t *datab_alloc(int size);

Als Eingabeparameter wird die Größe der Daten angegeben, die der Block speichern wird. Es wird mehr Speicher zugewiesen, um eine Header-Struktur am Anfang des zugewiesenen Speichers zu platzieren Datenb. Bei der Verwendung anderer Funktionen ist dies jedoch nicht immer der Fall; in einigen Fällen kann der Datenpuffer getrennt vom Datenblock-Header liegen. Beim Erstellen einer Struktur werden die Felder so konfiguriert, dass ihr Feld db_base zeigte auf den Anfang des Datenbereichs und db_lim bis zu seinem Ende. Anzahl der Links db_ref ist auf eins gesetzt. Der Zeiger der Datenlöschfunktion wird auf Null gesetzt.

Nachricht mblk_t

Wie bereits erwähnt, sind Warteschlangenelemente vom Typ mblk_t, es ist wie folgt definiert:

typedef struct msgb
{
  struct msgb *b_prev;   // Указатель на предыдущий элемент списка.
  struct msgb *b_next;   // Указатель на следующий элемент списка.
  struct msgb *b_cont;   // Указатель для подклейки к сообщению других сообщений, для создания кортежа сообщений.
  struct datab *b_datap; // Указатель на структуру блока данных.
  unsigned char *b_rptr; // Указатель на начало области данных для чтения данных буфера b_datap.
  unsigned char *b_wptr; // Указатель на начало области данных для записи данных буфера b_datap.
  uint32_t reserved1;    // Зарезервированное поле1, медиастример помещает туда служебную информацию. 
  uint32_t reserved2;    // Зарезервированное поле2, медиастример помещает туда служебную информацию.
  #if defined(ORTP_TIMESTAMP)
  struct timeval timestamp;
  #endif
  ortp_recv_addr_t recv_addr;
} mblk_t;

Struktur mblk_t enthält am Anfang Zeiger b_prev, b_next, die zum Organisieren einer doppelt verknüpften Liste (die eine Warteschlange darstellt) erforderlich sind queue_t).

Dann kommt der Zeiger b_cont, die nur verwendet wird, wenn die Nachricht Teil eines Tupels ist. Für die letzte Nachricht im Tupel bleibt dieser Zeiger null.

Als nächstes sehen wir einen Zeiger auf einen Datenblock b_datap, für den die Nachricht existiert. Es folgen Zeiger auf den Bereich innerhalb des Blockdatenpuffers. Feld b_rptr Gibt den Speicherort an, von dem Daten aus dem Puffer gelesen werden. Feld b_wptr gibt den Ort an, von dem aus in den Puffer geschrieben wird.

Die übrigen Felder haben Servicecharakter und beziehen sich nicht auf den Betrieb des Datenübertragungsmechanismus.

Unten ist eine einzelne Nachricht mit dem Namen m1 und Datenblock d1.
Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11
Die folgende Abbildung zeigt ein Tupel aus drei Nachrichten m1, m1_1, m1_2.
Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11

Messaging-Funktionen mblk_t

Eine neue Nachricht mblk_t erstellt durch die Funktion:

mblk_t *allocb(int size, int pri); 

Sie prägt sich eine neue Botschaft ein mblk_t mit einem Datenblock der angegebenen Größe Größe, zweites Argument - bei Wird in dieser Version der Bibliothek nicht verwendet. Es sollte Null bleiben. Während der Ausführung der Funktion wird Speicher für die Struktur der neuen Nachricht reserviert und die Funktion aufgerufen mblk_init(), wodurch alle Felder der erstellten Instanz der Struktur zurückgesetzt und dann die oben genannten Schritte verwendet werden datab_alloc(), erstellt einen Datenpuffer. Anschließend werden die Felder in der Struktur konfiguriert:

mp->b_datap=datab;
mp->b_rptr=mp->b_wptr=datab->db_base;
mp->b_next=mp->b_prev=mp->b_cont=NULL;

Am Ausgang erhalten wir eine neue Nachricht mit initialisierten Feldern und einem leeren Datenpuffer. Um einer Nachricht Daten hinzuzufügen, müssen Sie diese in den Datenblockpuffer kopieren:

memcpy(msg->b_rptr, data, size);

wo technische Daten ist ein Zeiger auf die Datenquelle und Größe - ihre Größe.
Dann müssen Sie den Zeiger auf den Schreibpunkt aktualisieren, sodass er wieder auf den Anfang des freien Bereichs im Puffer zeigt:

msg->b_wptr = msg->b_wptr + size

Wenn Sie eine Nachricht aus einem vorhandenen Puffer erstellen müssen, ohne sie zu kopieren, verwenden Sie die Funktion:

mblk_t *esballoc(uint8_t *buf, int size, int pri, void (*freefn)(void*)); 

Nachdem die Funktion die Nachricht und die Struktur des Datenblocks erstellt hat, konfiguriert sie ihre Zeiger auf die Daten an der Adresse buf. Diese. In diesem Fall liegt der Datenpuffer nicht hinter den Headerfeldern des Datenblocks, wie es beim Erstellen eines Datenblocks mit der Funktion der Fall war datab_alloc(). Der Puffer mit den an die Funktion übergebenen Daten bleibt dort, wo er war, wird aber mit Hilfe von Zeigern an den neu erstellten Header des Datenblocks und damit an die Nachricht angehängt.

Zu einer Nachricht mblk_t Mehrere Datenblöcke können sequentiell verkettet werden. Dies geschieht durch die Funktion:

mblk_t * appendb(mblk_t *mp, const char *data, int size, bool_t pad); 

mp — eine Nachricht, zu der ein weiterer Datenblock hinzugefügt wird;
technische Daten — Zeiger auf den Block, von dem eine Kopie der Nachricht hinzugefügt wird;
Größe — Datengröße;
Unterlage – ein Flag, dass die Größe des zugewiesenen Speichers entlang einer 4-Byte-Grenze ausgerichtet sein muss (das Auffüllen erfolgt mit Nullen).

Wenn im vorhandenen Nachrichtendatenpuffer genügend Platz vorhanden ist, werden die neuen Daten hinter den bereits vorhandenen Daten eingefügt. Wenn im Nachrichtendatenpuffer weniger freier Speicherplatz vorhanden ist als Größe, dann wird eine neue Nachricht mit ausreichender Puffergröße erstellt und die Daten in ihren Puffer kopiert. Dies ist eine neue Nachricht, die über einen Zeiger mit der ursprünglichen Nachricht verknüpft ist b_cont. In diesem Fall wird die Nachricht zu einem Tupel.

Wenn Sie dem Tupel einen weiteren Datenblock hinzufügen müssen, müssen Sie die Funktion verwenden:

void msgappend(mblk_t *mp, const char *data, int size, bool_t pad);

Sie wird die letzte Nachricht im Tupel finden (er hat b_cont wird null sein) und ruft die Funktion für diese Nachricht auf appendb().

Mit der Funktion können Sie die Datengröße in einer Nachricht oder einem Tupel ermitteln:

int msgdsize(const mblk_t *mp);

Es durchläuft alle Nachrichten im Tupel und gibt die Gesamtdatenmenge in den Datenpuffern dieser Nachrichten zurück. Für jede Nachricht wird die Datenmenge wie folgt berechnet:

 mp->b_wptr - mp->b_rptr

Um zwei Tupel zu kombinieren, verwenden Sie die Funktion:

mblk_t *concatb(mblk_t *mp, mblk_t *newm);

Sie hängt das Tupel an Newm zum Ende des Tupels mp und gibt einen Zeiger auf die letzte Nachricht des resultierenden Tupels zurück.

Bei Bedarf kann ein Tupel in eine Nachricht mit einem einzigen Datenblock umgewandelt werden; dies geschieht durch die Funktion:

void msgpullup(mblk_t *mp,int len);

wenn Argument len -1 ist, wird die Größe des zugewiesenen Puffers automatisch ermittelt. Wenn len eine positive Zahl ist, wird ein Puffer dieser Größe erstellt und die Tupelnachrichtendaten werden hinein kopiert. Wenn der Puffer erschöpft ist, wird der Kopiervorgang dort abgebrochen. Die erste Nachricht des Tupels erhält einen neuen Puffer mit den kopierten Daten. Die verbleibenden Nachrichten werden gelöscht und der Speicher an den Heap zurückgegeben.

Beim Löschen einer Struktur mblk_t Beim Aufruf wird der Referenzzähler des Datenbausteins berücksichtigt freeb() es stellt sich heraus, dass es Null ist, dann wird der Datenpuffer zusammen mit der Instanz gelöscht mblk_t, was darauf hinweist.

Initialisierung der Felder einer neuen Nachricht:

void mblk_init(mblk_t *mp);

Der Nachricht ein weiteres Datenelement hinzufügen:

mblk_t * appendb(mblk_t *mp, const char *data, size_t size, bool_t pad);

Passen die neuen Daten nicht in den freien Speicherplatz des Nachrichtendatenpuffers, wird eine separat erstellte Nachricht mit einem Puffer der erforderlichen Größe an die Nachricht angehängt (ein Zeiger auf die hinzugefügte Nachricht wird in der ersten Nachricht gesetzt) ​​und die Die Nachricht wird zu einem Tupel.

Hinzufügen eines Datenelements zu einem Tupel:

void msgappend(mblk_t *mp, const char *data, size_t size, bool_t pad); 

Die Funktion ruft appendb() in einer Schleife auf.

Zwei Tupel zu einem kombinieren:

mblk_t *concatb(mblk_t *mp, mblk_t *newm);

Nachricht Newm wird angehängt mp.

Eine Kopie einer einzelnen Nachricht erstellen:

mblk_t *copyb(const mblk_t *mp);

Vollständiges Kopieren eines Tupels mit allen Datenblöcken:

mblk_t *copymsg(const mblk_t *mp);

Die Elemente des Tupels werden von der Funktion kopiert copyb().

Erstellen Sie eine einfache Kopie einer Nachricht mblk_t. In diesem Fall wird der Datenblock nicht kopiert, sondern sein Referenzzähler erhöht db_ref:

mblk_t *dupb(mblk_t *mp);

Erstellen einer einfachen Kopie eines Tupels. Datenblöcke werden nicht kopiert, lediglich ihre Referenzzähler werden inkrementiert db_ref:

mblk_t *dupmsg(mblk_t* m);

Alle Nachrichten eines Tupels zu einer Nachricht zusammenfügen:

void msgpullup(mblk_t *mp,size_t len);

Wenn das Argument len -1 ist, wird die Größe des zugewiesenen Puffers automatisch ermittelt.

Eine Nachricht löschen, Tupel:

void freemsg(mblk_t *mp);

Der Referenzzähler des Datenblocks wird um eins dekrementiert. Erreicht er Null, wird der Datenblock ebenfalls gelöscht.

Berechnung der Gesamtdatenmenge in einer Nachricht oder einem Tupel.

size_t msgdsize(const mblk_t *mp);

Abrufen einer Nachricht vom Ende der Warteschlange:

mblk_t *ms_queue_peek_last (q);

Kopieren des Inhalts der reservierten Felder einer Nachricht in eine andere Nachricht (tatsächlich enthalten diese Felder Flags, die vom Medienstreamer verwendet werden):

mblk_meta_copy(const mblk_t *source, mblk *dest);

Warteschlange queue_t

Die Nachrichtenwarteschlange im Medienstreamer ist als zirkuläre doppelt verknüpfte Liste implementiert. Jedes Listenelement enthält einen Zeiger auf einen Datenblock mit Signalproben. Es stellt sich heraus, dass sich nacheinander nur Zeiger auf den Datenblock bewegen, während die Daten selbst bewegungslos bleiben. Diese. Es werden nur Links zu ihnen verschoben.
Struktur, die die Warteschlange beschreibt queue_t, unten gezeigt:

typedef struct _queue
{
   mblk_t _q_stopper; /* "Холостой" элемент очереди, не указывает на данные, используется только для управления очередью. При инициализации очереди (qinit()) его указатели настраиваются так, чтобы они указывали на него самого. */
   int q_mcount;        // Количество элементов в очереди.
} queue_t;

Die Struktur enthält ein Feld – einen Zeiger _q_stopper Geben Sie *mblk_t ein, es zeigt auf das erste Element (Nachricht) in der Warteschlange. Das zweite Feld der Struktur ist der Zähler der Nachrichten in der Warteschlange.
Die folgende Abbildung zeigt eine Warteschlange mit dem Namen q1, die 4 Nachrichten m1, m2, m3, m4 enthält.
Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11
Die folgende Abbildung zeigt eine Warteschlange mit dem Namen q1, die 4 Nachrichten m1, m2, m3, m4 enthält. Nachricht m2 ist der Kopf eines Tupels, das zwei weitere Nachrichten m2_1 und m2_2 enthält.

Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 11

Funktionen zum Arbeiten mit Warteschlangen queue_t

Warteschlangeninitialisierung:

void qinit(queue_t *q);

Feld _q_stopper (im Folgenden nennen wir es „Stopper“) wird von der Funktion initialisiert mblk_init(), der Zeiger auf das vorherige Element und das nächste Element wird so angepasst, dass er auf sich selbst zeigt. Der Warteschlangenelementzähler wird auf Null zurückgesetzt.

Hinzufügen eines neuen Elements (Nachrichten):

void putq(queue_t *q, mblk_t *m);

Neues Element m am Ende der Liste hinzugefügt wird, werden die Elementzeiger so angepasst, dass der Stopper das nächste Element für ihn wird und es zum vorherigen Element für den Stopper wird. Der Warteschlangenelementzähler wird erhöht.

Abrufen eines Elements aus der Warteschlange:

mblk_t * getq(queue_t *q); 

Die Meldung erscheint, nachdem der Stopper abgerufen und der Elementzähler dekrementiert wurde. Befinden sich außer dem Stopper keine Elemente in der Warteschlange, wird 0 zurückgegeben.

Einfügen einer Nachricht in eine Warteschlange:

void insq(queue_t *q, mblk_t *emp, mblk_t *mp); 

Element mp vor dem Element eingefügt EMP. Wenn EMP=0, dann wird die Nachricht am Ende der Warteschlange hinzugefügt.

Abrufen einer Nachricht vom Kopf der Warteschlange:

void remq(queue_t *q, mblk_t *mp); 

Der Elementzähler wird dekrementiert.

Einen Zeiger auf das erste Element in der Warteschlange lesen:

mblk_t * peekq(queue_t *q); 

Alle Elemente aus der Warteschlange entfernen und gleichzeitig die Elemente selbst löschen:

void flushq(queue_t *q, int how);

Argument wie Wird nicht benutzt. Der Warteschlangenelementzähler wird auf Null gesetzt.

Makro zum Lesen eines Zeigers auf das letzte Element der Warteschlange:

mblk_t * qlast(queue_t *q);

Beachten Sie dies beim Arbeiten mit Nachrichtenwarteschlangen, wenn Sie anrufen ms_queue_put(q, m) Mit einem Nullzeiger auf die Nachricht führt die Funktion eine Schleife aus. Ihr Programm friert ein. verhält sich ähnlich ms_queue_next(q, m).

Filter anschließen

Die oben beschriebene Warteschlange wird verwendet, um Nachrichten von einem Filter an einen anderen oder von einem an mehrere Filter weiterzuleiten. Filter und ihre Verbindungen bilden einen gerichteten Graphen. Der Eingang oder Ausgang des Filters wird allgemein als „Pin“ bezeichnet. Um die Reihenfolge zu beschreiben, in der Filter miteinander verbunden sind, verwendet der Medienstreamer das Konzept eines „Signalpunkts“. Signalpunkt ist Struktur _MSCPoint, der einen Zeiger auf den Filter und die Nummer eines seiner Pins enthält; dementsprechend beschreibt er den Anschluss eines der Ein- oder Ausgänge des Filters.

Signalpunkt des Datenverarbeitungsdiagramms

typedef struct _MSCPoint{
struct _MSFilter *filter; // Указатель на фильтр медиастримера.
int pin;                        // Номер одного из входов или выходов фильтра, т.е. пин.
} MSCPoint;

Filterpins sind beginnend bei Null nummeriert.

Die Verbindung zweier Pins durch eine Nachrichtenwarteschlange wird durch die Struktur beschrieben _MSQueue, das eine Nachrichtenwarteschlange und Zeiger auf die beiden Signalpunkte enthält, die es verbindet:

typedef struct _MSQueue
{
queue_t q;
MSCPoint prev;
MSCPoint next;
}MSQueue;

Wir nennen diese Struktur einen Signallink. Jeder Media-Streamer-Filter enthält eine Tabelle mit Eingabelinks und eine Tabelle mit Ausgabelinks (MSQueue). Die Größe von Tabellen wird beim Erstellen eines Filters festgelegt; dies haben wir bereits anhand einer exportierten Variablen vom Typ gemacht MSFilterDesc, als wir unseren eigenen Filter entwickelten. Nachfolgend finden Sie eine Struktur, die jeden Filter in einem Medienstreamer beschreibt. MSFilter:


struct _MSFilter{
    MSFilterDesc *desc;    /* Указатель на дескриптор фильтра. */
    /* Защищенные атрибуты, их нельзя сдвигать или убирать иначе будет нарушена работа с плагинами. */
    ms_mutex_t lock;      /* Семафор. */
    MSQueue **inputs;     /* Таблица входных линков. */
    MSQueue **outputs;    /* Таблица выходных линков. */
    struct _MSFactory *factory; /* Указатель на фабрику, которая создала данный экземпляр фильтра. */
    void *padding;              /* Не используется, будет задействован если добавятся защищенные поля. */
    void *data;                 /* Указатель на произвольную структуру для хранения данных внутреннего состояния фильтра и промежуточных вычислений. */
    struct _MSTicker *ticker;   /* Указатель на объект тикера, который не должен быть нулевым когда вызывается функция process(). */
    /*private attributes, they can be moved and changed at any time*/
    MSList *notify_callbacks; /* Список обратных вызовов, используемых для обработки событий фильтра. */
    uint32_t last_tick;       /* Номер последнего такта, когда выполнялся вызов process(). */ 
    MSFilterStats *stats;     /* Статистика работы фильтра.*/
    int postponed_task; /*Количество отложенных задач. Некоторые фильтры могут откладывать обработку данных (вызов process()) на несколько тактов.*/
    bool_t seen;  /* Флаг, который использует тикер, чтобы помечать что этот экземпляр фильтра он уже обслужил на данном такте.*/
};
typedef struct _MSFilter MSFilter;

Nachdem wir die Filter im C-Programm gemäß unserem Plan verbunden haben (aber den Ticker nicht angeschlossen haben), haben wir dadurch einen gerichteten Graphen erstellt, dessen Knoten Instanzen der Struktur sind MSFilterund Kanten sind Instanzen von Links MSQueue.

Aktivitäten hinter den Kulissen des Tickers

Als ich Ihnen sagte, dass der Ticker ein Filter für die Quelle von Zecken ist, war das nicht die ganze Wahrheit. Ein Ticker ist ein Objekt, das Funktionen auf der Uhr ausführt Prozess() alle Filter der Schaltung (Graph), an die es angeschlossen ist. Wenn wir einen Ticker mit einem Diagrammfilter in einem C-Programm verbinden, zeigen wir dem Ticker das Diagramm, das er von nun an steuern wird, bis wir ihn ausschalten. Nach der Verbindung beginnt der Ticker mit der Untersuchung des ihm anvertrauten Diagramms und stellt eine Liste von Filtern zusammen, die es einbeziehen. Um den gleichen Filter nicht zweimal zu „zählen“, markiert es die erkannten Filter, indem es ein Kontrollkästchen darin platziert gesehen. Die Suche erfolgt anhand der Verknüpfungstabellen, über die jeder Filter verfügt.

Bei seinem Einführungsrundgang durch die Grafik prüft der Ticker, ob es unter den Filtern mindestens einen gibt, der als Quelle für Datenblöcke fungiert. Wenn keine vorhanden sind, gilt die Grafik als falsch und der Ticker stürzt ab.

Stellt sich heraus, dass der Graph „korrekt“ ist, wird für jeden gefundenen Filter die Funktion zur Initialisierung aufgerufen Vorverarbeitung (). Sobald der Zeitpunkt für den nächsten Verarbeitungszyklus gekommen ist (standardmäßig alle 10 Millisekunden), ruft der Ticker die Funktion auf Prozess() für alle zuvor gefundenen Quellfilter und dann für die übrigen Filter in der Liste. Wenn der Filter über Eingabelinks verfügt, wird die Funktion ausgeführt Prozess() Wird wiederholt, bis die Eingabe-Link-Warteschlangen leer sind. Danach geht es zum nächsten Filter in der Liste und „scrollt“ ihn, bis die Eingabelinks frei von Nachrichten sind. Der Ticker bewegt sich von Filter zu Filter, bis die Liste endet. Damit ist die Bearbeitung des Zyklus abgeschlossen.

Jetzt kehren wir zu Tupeln zurück und sprechen darüber, warum eine solche Entität dem Medienstreamer hinzugefügt wurde. Im Allgemeinen stimmt die Datenmenge, die der im Filter arbeitende Algorithmus benötigt, nicht überein und ist kein Vielfaches der Größe der am Eingang empfangenen Datenpuffer. Wir schreiben beispielsweise einen Filter, der eine schnelle Fourier-Transformation durchführt, der per Definition nur Datenblöcke verarbeiten kann, deren Größe eine Zweierpotenz ist. Lassen Sie es 512 Zählungen sein. Wenn die Daten von einem Telefonkanal generiert werden, liefert uns der Datenpuffer jeder Nachricht am Eingang 160 Signalproben. Es ist verlockend, keine Daten aus der Eingabe zu sammeln, bis die erforderliche Datenmenge vorhanden ist. In diesem Fall kommt es jedoch zu einer Kollision mit dem Ticker, der erfolglos versucht, durch den Filter zu scrollen, bis der Eingabelink leer ist. Zuvor haben wir diese Regel als drittes Prinzip des Filters bezeichnet. Nach diesem Prinzip muss die Funktion „process()“ des Filters alle Daten aus den Eingabewarteschlangen übernehmen.

Außerdem wird es nicht möglich sein, nur 512 Samples aus der Eingabe zu entnehmen, da man nur ganze Blöcke, d. h. Der Filter muss 640 Proben nehmen und 512 davon verwenden, den Rest, bevor ein neuer Teil der Daten akkumuliert wird. Daher muss unser Filter zusätzlich zu seiner Hauptaufgabe Hilfsaktionen zur Zwischenspeicherung von Eingabedaten bereitstellen. Die Entwickler des Media-Streamers und zur Lösung dieses allgemeinen Problems haben ein spezielles Objekt entwickelt – MSBufferizer (Puffer), das dieses Problem mithilfe von Tupeln löst.

Puffer (MSBufferizer)

Hierbei handelt es sich um ein Objekt, das Eingabedaten im Filter sammelt und mit der Übermittlung zur Verarbeitung beginnt, sobald die Informationsmenge ausreicht, um den Filteralgorithmus auszuführen. Während der Puffer Daten sammelt, arbeitet der Filter im Leerlaufmodus, ohne die Verarbeitungsleistung des Prozessors zu verbrauchen. Sobald jedoch die Lesefunktion aus dem Puffer einen Wert ungleich Null zurückgibt, beginnt die Funktion „process()“ des Filters, Daten aus dem Puffer in Teilen der erforderlichen Größe zu übernehmen und zu verarbeiten, bis diese erschöpft sind.
Noch nicht benötigte Daten verbleiben als erstes Element des Tupels im Puffer, an den nachfolgende Blöcke mit Eingabedaten angehängt werden.

Die Struktur, die den Puffer beschreibt:

struct _MSBufferizer{
queue_t q; /* Очередь сообщений. */
int size; /* Суммарный размер данных находящихся в буферизаторе в данный момент. */
};
typedef struct _MSBufferizer MSBufferizer;

Funktionen zum Arbeiten mit MSBufferizer

Erstellen einer neuen Pufferinstanz:

MSBufferizer * ms_bufferizer_new(void);

Speicher wird zugewiesen und initialisiert in ms_bufferizer_init() und ein Zeiger wird zurückgegeben.

Initialisierungsfunktion:

void ms_bufferizer_init(MSBufferizer *obj); 

Die Warteschlange wird initialisiert q, Feld Größe wird auf Null gesetzt.

Nachricht hinzufügen:

void ms_bufferizer_put(MSBufferizer *obj, mblk_t *m); 

Nachricht m wird zur Warteschlange hinzugefügt. Die berechnete Größe der Datenblöcke wird addiert Größe.

Übertragen aller Nachrichten aus der Verbindungsdatenwarteschlange in den Puffer q:

void ms_bufferizer_put_from_queue(MSBufferizer *obj, MSQueue *q);   

Übertragen von Nachrichten von einem Link q im Puffer erfolgt über die Funktion ms_bufferizer_put().

Aus dem Puffer lesen:

int ms_bufferizer_read(MSBufferizer *obj, uint8_t *data, int datalen); 

Wenn die Größe der im Puffer angesammelten Daten kleiner als die angeforderte ist (datalen), dann gibt die Funktion Null zurück, Daten werden nicht in Daten kopiert. Andernfalls wird ein sequentielles Kopieren der Daten aus den im Puffer befindlichen Tupeln durchgeführt. Nach dem Kopieren wird das Tupel gelöscht und der Speicher freigegeben. Der Kopiervorgang endet in dem Moment, in dem die Datenbytes kopiert werden. Wenn in der Mitte eines Datenblocks kein Platz mehr vorhanden ist, wird der Datenblock in dieser Meldung auf den verbleibenden, nicht kopierten Teil reduziert. Bei Ihrem nächsten Anruf wird der Kopiervorgang ab dieser Stelle fortgesetzt.

Auslesen der aktuell im Puffer verfügbaren Datenmenge:

int ms_bufferizer_get_avail(MSBufferizer *obj); 

Gibt das Feld zurück Größe Puffer.

Einen Teil der Daten im Puffer verwerfen:

void ms_bufferizer_skip_bytes(MSBufferizer *obj, int bytes);

Die angegebene Anzahl an Datenbytes wird abgerufen und verworfen. Die ältesten Daten werden verworfen.

Alle Nachrichten im Puffer löschen:

void ms_bufferizer_flush(MSBufferizer *obj); 

Der Datenzähler wird auf Null zurückgesetzt.

Alle Nachrichten im Puffer löschen:

void ms_bufferizer_uninit(MSBufferizer *obj); 

Der Zähler wird nicht zurückgesetzt.

Puffer entfernen und Speicher freigeben:

void ms_bufferizer_destroy(MSBufferizer *obj);  

Beispiele für die Verwendung des Puffers finden Sie im Quellcode mehrerer Media-Streamer-Filter. Zum Beispiel im MS_L16_ENC-Filter, der die Bytes in den Samples von der Netzwerkreihenfolge in die Hostreihenfolge umordnet: l16.c

Im nächsten Artikel befassen wir uns mit dem Thema der Ticker-Lastschätzung und dem Umgang mit übermäßiger Rechenlast im Media-Streamer.

Source: habr.com

Kommentar hinzufügen