Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11

Матэрыял артыкула ўзяты з майго дзэн-канала.

Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11

Механізм перамяшчэння даных

  • Блок даных dblk_t
  • Паведамленне mblk_t
  • Функцыі працы з паведамленнямі mblk_t
  • Чарга queue_t
  • Функцыі працы з чэргамі queue_t
  • Злучэнне фільтраў
  • Сігнальны пункт графа апрацоўкі дадзеных
  • Закулісная дзейнасць тыкера
  • Буферызатар (MSBufferizer)
  • Функцыі працы з MSBufferizer

У мінулым артыкуле мы распрацавалі свой уласны фільтр. Гэты артыкул мы прысвецім прыладзе ўнутранага механізму перасоўвання дадзеных паміж фільтрамі медыястрымера. Гэта дазволіць у далейшым пісаць выдасканаленыя фільтры з меншымі намаганнямі.

Механізм перамяшчэння даных

Перасоўванне дадзеных у медыястрымеры выконваецца з дапамогай чэргаў апісваных структурай queue_t. Па чэргах перамяшчаюцца чароды паведамленняў тыпу mblk_t, якія самі па сабе не змяшчаюць дадзеных сігналу, а толькі спасылкі на папярэдняе, наступнае паведамленне і на блок дадзеных. Акрамя гэтага, хачу падкрэсліць асоба, ёсць яшчэ поле для спасылкі на паведамленне такога ж тыпу, якое дазваляе арганізаваць аднасувязны спіс з паведамленняў. Групу паведамленняў, аб'яднаных такім спісам, будзем зваць картэжам. Такім чынам, любы элемент чаргі можа аказацца адзінкавым паведамленнем. mblk_t, а можа і галавой картэжа паведамленняў mblk_t. Кожнае паведамленне картэжа можа мець свой падапечны блок даных. Навошта патрэбныя картэжы мы абмяркуем крыху пазней.

Як было сказана вышэй, само па сабе паведамленне не ўтрымоўвае блок дадзеных, замест гэтага яно ўтрымоўвае толькі паказальнік на вобласць памяці дзе захоўваецца блок. У гэтай частцы агульная карціна працы медыястрымера нагадвае склад дзвярэй у мультфільме "Карпарацыя монстраў", дзе дзверы (спасылкі на дадзеныя — пакоі) з вар'яцкай хуткасцю рухаюцца па падвесных канвеерах, пры гэтым самі пакоі застаюцца нерухомымі.

Зараз, рухаючыся па іерархіі знізу ўверх, разгледзім дэталёва пералічаныя сутнасці механізму перадачы дадзеных у медыястрымеры.

Блок дадзеных dblk_t

Блок даных складаецца з загалоўка і буфера даных. Загаловак апісваецца наступнай структурай,

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

Палі структуры ўтрымоўваюць паказальнікі на пачатак буфера, канец буфера, функцыю выдалення буфера дадзеных. Апошні элемент у загалоўку db_ref - лічыльнік спасылак, калі ён дасягае нуля, гэта служыць сігналам да выдалення дадзенага блока з памяці. Калі блок дадзеных быў створаны функцыяй datab_alloc() , то буфер дадзеных будзе размешчаны ў памяці адразу пасля загалоўка. Ва ўсіх іншых выпадках буфер можа размяшчацца недзе асобна. У буферы дадзеных будуць размяшчацца адлікі сігналу ці іншыя дадзеныя, якія мы жадаем апрацоўваць фільтрамі.

Новы асобнік блока дадзеных ствараецца з дапамогай функцыі:

dblk_t *datab_alloc(int size);

У якасці ўваходнага параметра ёй перадаецца памер даных, якія будзе захоўваць блок. Памяці вылучаецца больш, каб у пачатку выдзеленай памяці размясціць загаловак - структуру дадзеныяб. Але пры выкарыстанні іншых функцый так адбываецца не заўсёды, у некаторых выпадках буфер дадзеных можа размяшчацца асобна ад загалоўка блока дадзеных. Палі структуры пры стварэнні наладжваюцца так, каб яе поле db_base паказвала на пачатак вобласці дадзеных, а db_lim на яе канец. Лічыльнік спасылак db_ref устанаўліваецца ў адзінку. Паказальнік функцыі ачысткі дадзеных усталёўваецца ў нуль.

Паведамленне mblk_t

Як было сказана, элементы чаргі маюць тып mblk_t, ён вызначаны наступным чынам:

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;

Структура mblk_t у пачатку змяшчае паказальнікі b_prev, b_next, якія неабходныя для арганізацыі двухсувязнога спісу (якім з'яўляецца чарга queue_t).

Затым ідзе паказальнік b_cont, Які выкарыстоўваецца толькі тады, калі паведамленне ўваходзіць у картэж. Для апошняга паведамлення ў картэжы гэта паказальнік застаецца нулявым.

Далей мы бачым паказальнік на блок дадзеных b_datap, дзеля якога і існуе паведамленне. За ім ідуць паказальнікі, на вобласць усярэдзіне буфера дадзеных блока. Поле b_rptr паказвае месца, з якога будуць чытацца дадзеныя з буфера. Поле b_wptr указвае месца, з якога будуць выконвацца запіс у буфер.

Астатнія палі носяць службовы характар ​​і не адносяцца да працы механізма перадачы даных.

Ніжэй паказана адзіночнае паведамленне з імем m1 і блокам дадзеных d1.
Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11
На наступным малюнку намаляваны картэж з трох паведамленняў m1, m1_1, m1_2.
Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11

Функцыі працы з паведамленнямі mblk_t

Новае паведамленне mblk_t ствараецца функцыяй:

mblk_t *allocb(int size, int pri); 

яна размяшчае ў памяці новае паведамленне mblk_t з блокам дадзеных указанага памеру памер, другі аргумент - паі не выкарыстоўваецца ў разгляданай версіі бібліятэкі. Ён павінен заставацца нулявым. У ходзе работы функцыі будзе выдзелена памяць пад структуру новага паведамлення і выклікана функцыя mblk_init(), якая абнуліць усе палі створанага асобніка структуры і затым, з дапамогай згаданай вышэй datab_alloc(), створыць буфер дадзеных. Пасля чаго будзе выканана настройка палёў у структуры:

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

На выхадзе атрымліваем новае паведамленне з ініцыялізаванымі палямі і пустым буферам даных. Каб дадаць у паведамленне дадзеныя, трэба выканаць іх капіраванне ў буфер блока дадзеных:

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

дзе gegevens - паказальнік на крыніцу дадзеных, а памер - іх памер.
затым трэба абнавіць паказальнік на кропку запісу, каб ён зноў паказваў на пачатак вольнай вобласці ў буферы:

msg->b_wptr = msg->b_wptr + size

Калі патрабуецца стварыць паведамленне з ужо наяўнага буфера, без капіявання, то для гэтага выкарыстоўваецца функцыя:

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

Функцыя пасля стварэння паведамлення і структуры блока дадзеных, наладзіць яго паказальнікі на дадзеныя па адрасе buf. Г.зн. у дадзеным выпадку буфер дадзеных не размяшчаецца ўслед за палямі загалоўка блока дадзеных, як гэта было пры стварэнні блока дадзеных функцыяй datab_alloc(). Перададзены функцыі буфер з дадзенымі застанецца тамака дзе быў, але з дапамогай паказальнікаў будзе падшпілены да толькі што створанаму загалоўку блока дадзеных, а той адпаведна да паведамлення.

Да аднаго паведамлення mblk_t могуць быць паслядоўна прычэпленыя некалькі блокаў дадзеных. Гэта робіцца функцыяй:

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

mp - паведамленне да якога будзе дададзены яшчэ адзін блок дадзеных;
gegevens - паказальнік на блок, копія якога будзе дададзена ў паведамленне;
памер - Памер дадзеных;
пляцоўка - сцяг таго, што памер вылучаемай памяці павінен быць выраўнаваны па мяжы 4 байт (дадатак будзе выканана нулямі).

Калі ў наяўным буферы дадзеных паведамлення досыць месцы, то новыя дадзеныя будуць падклееныя за ўжо наяўнымі там дадзенымі. Калі вольнага месца ў буферы дадзеных паведамлення апынецца менш чым памер, то ствараецца новае паведамленне, з дастатковым памерам буфера і дадзеныя капіююцца ў яго буфер. Гэта новае паведамленне, падчапляецца да зыходнага з дапамогай паказальніка b_cont. У гэтым выпадку паведамленне ператвараецца ў картэж.

Калі ў картэж патрабуецца дадаць яшчэ адзін блок дадзеных, тое трэба выкарыстоўваць функцыю:

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

яна знойдзе апошняе паведамленне ў картэжы (у яго b_cont будзе нулявым) і выкліча для гэтага паведамлення функцыю appendb().

Даведацца памер дадзеных у паведамленні або картэжы можна з дапамогай функцыі:

int msgdsize(const mblk_t *mp);

яна прабяжыцца па ўсіх паведамленнях картэжа і верне сумарную колькасць дадзеных у буферах дадзеных гэтых паведамленняў. Для кожнага паведамлення колькасць дадзеных вылічаецца так:

 mp->b_wptr - mp->b_rptr

Каб аб'яднаць два картэжа прымяняецца функцыя:

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

яна далучае картэж новы у хвост картэжа mp і вяртае паказальнік на апошняе паведамленне атрыманага картэжа.

Пры неабходнасці, картэж можна ператварыць у адно паведамленне з адзіным блокам дадзеных, гэта робіцца функцыяй:

void msgpullup(mblk_t *mp,int len);

калі аргумент Лена роўны -1, то памер які адводзіцца буфера вызначаецца аўтаматычна. Калі Лена станоўчы лік, то будзе створаны буфер гэтага памеру і ў яго будуць скапіяваныя дадзеныя паведамленняў картэжа. Калі буфер скончыцца, то капіраванне будзе на гэтым спынена. Першае паведамленне картэжа атрымае буфер новага памеру са скапіяванымі дадзенымі. Астатнія паведамленні будуць выдаленыя, а памяць вернутая ў кучу.

Пры выдаленні структуры mblk_t улічваецца лічыльнік спасылак блока дадзеных, калі пры выкліку freeb() ён апыняецца роўны нулю, то буфер дадзеных выдаляецца разам з асобнікам mblk_t, які на яго паказвае.

Ініцыялізацыя палёў новага паведамлення:

void mblk_init(mblk_t *mp);

Даданне ў паведамленне яшчэ адной порцыі даных:

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

Калі новыя дадзеныя не змяшчаюцца ў вольнае месца буфера дадзеных паведамлення, то да паведамлення прычэпліваецца асобна створанае паведамленне з буферам патрэбнага памеру (у першым паведамленні ўсталёўваецца паказальнік на дададзенае паведамленне) паведамленне ператвараецца ў картэж.

Даданне порцыі дадзеных у картэж:

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

Функцыя выклікае appendb() у цыкле.

Аб'яднанне двух картэжаў у адзін:

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

Паведамленне новы будзе далучана да mp.

Стварэнне копіі адзіночнага паведамлення:

mblk_t *copyb(const mblk_t *mp);

Поўнае капіраванне картэжа з усімі блокамі дадзеных:

mblk_t *copymsg(const mblk_t *mp);

Элементы картэжа пры гэтым капіююцца функцыяй copyb().

Стварэнне лёгкай копіі паведамлення mblk_t. Пры гэтым блок дадзеных не капіруецца, а павялічваецца лічыльнік яго спасылак. db_ref:

mblk_t *dupb(mblk_t *mp);

Стварэнне лёгкай копіі картэжа. Блокі дадзеных не капіююцца, толькі павялічваюцца іх лічыльнікі спасылак db_ref:

mblk_t *dupmsg(mblk_t* m);

Склейка ўсіх паведамленняў картэжа ў адно паведамленне:

void msgpullup(mblk_t *mp,size_t len);

Калі аргумент Лена роўны -1, то памер які адводзіцца буфера вызначаецца аўтаматычна.

Выдаленне паведамлення, картэжа:

void freemsg(mblk_t *mp);

Лічыльнік спасылак блока дадзеных памяншаецца на адзінку. Калі ён пры гэтым дасягае за нуль, то блок дадзеных таксама выдаляецца.

Падлік сумарнай аб'ём дадзеных у паведамленні ці картэжы.

size_t msgdsize(const mblk_t *mp);

Выманне паведамлення з хваста чаргі:

mblk_t *ms_queue_peek_last (q);

Капіраванне змесціва зарэзерваваных палёў аднаго паведамлення ў іншае паведамленне(насамрэч у гэтых палях знаходзяцца сцягі, якія выкарыстоўваюцца медыястрымерам):

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

чарга queue_t

Чарга паведамленняў у медыястрымеры рэалізавана як кальцавы двухсувязны спіс. Кожны элемент спісу змяшчае паказальнік на блок даных з адлікамі сігналу. Атрымліваецца, што па чарзе перамяшчаюцца толькі паказальнікі на блок даных, у той час як самі даныя застаюцца нерухомымі. Г.зн. перамяшчаюцца толькі спасылкі на іх.
Структура якая апісвае чаргу queue_t, паказана ніжэй:

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

Структура змяшчае поле - паказальнік _q_stopper тыпу *mblk_t, ён паказвае на першы элемент (паведамленне) у чарзе. Другое поле структуры гэта лічыльнік паведамленняў, якія знаходзяцца ў чарзе.
На малюнку ніжэй паказана чарга з імем q1, якая змяшчае 4 паведамленні m1, m2, m3, m4.
Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11
На наступным малюнку паказана чарга з імем q1, якая змяшчае 4 паведамленні m1, m2, m3, m4. Паведамленне m2 з'яўляецца галавой картэжа, у які водзяць яшчэ два паведамлення m2_1 і m2_2.

Вывучаем VoIP-рухавічок Mediastreamer2. Частка 11

Функцыі працы з чэргамі queue_t

Ініцыялізацыю чаргі:

void qinit(queue_t *q);

Поле _q_stopper (далей будзем зваць яго "стопар") ініцыялізуецца функцыяй mblk_init(), яго паказальнік папярэдняга элемента і наступнага элемента наладжваюцца так, каб яны паказвалі на яго самога. Лічыльнік элементаў чаргі абнуляецца.

Даданне новага элемента (паведамленні):

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

Новы элемент m дадаецца ў канец спісу, паказальнікі элемента наладжваюцца так, каб стопар станавіўся для яго наступным элементам, а ён для стопар папярэднім. Інкрыментуецца лічыльнік элементаў чаргі.

Выманне элемента з чаргі:

mblk_t * getq(queue_t *q); 

здабываецца тое паведамленне, якое варта пасля стопару, лічыльнік элементаў дэкрэментуецца. Калі ў чарзе, акрамя стопар, элементаў няма, то вяртаецца 0.

Устаўка паведамлення ў чаргу:

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

элемент mp устаўляецца перад элементам эй. Калі эй=0, то паведамленне дадаецца ў хвост чаргі.

Выманне паведамлення з галавы чаргі:

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

Лічыльнік элементаў дэкрэментуецца.

Чытанне паказальніка на першы элемент у чарзе:

mblk_t * peekq(queue_t *q); 

Выдаленне ўсіх элементаў з чаргі з выдаленнем саміх элементаў:

void flushq(queue_t *q, int how);

аргумент як не выкарыстоўваецца. Лічыльнік элементаў чаргі усталёўваецца ў нуль.

Макрас чытання паказальніка на апошні элемент чаргі:

mblk_t * qlast(queue_t *q);

Пры працы з чэргамі паведамленняў трэба мець на ўвазе, што пры выкліку ms_queue_put(q, m) з нулявым паказальнікам на паведамленне, функцыя зацыкляецца. Ваша праграма завісне. Аналагічна паводзіць сябе ms_queue_next(q, m).

Злучэнне фільтраў

Апісаная вышэй чарга выкарыстоўваюцца для перадачы паведамленняў ад аднаго фільтра да іншага ці ад аднаго да некалькіх фільтраў. Фільтры і іх злучэнні паміж сабой утвораць накіраваны граф. Уваход ці выйсце фільтра будзем зваць абагульняючым словам "пін". Для апісання парадку злучэнняў фільтраў паміж сабой, у медыястрымеры выкарыстоўваецца паняцце "сігнальнай кропкі". Сігнальная кропка гэта структура _MSCPoint, якая змяшчае паказальнік на фільтр і нумар аднаго з яго пінаў, адпаведна яна апісвае злучэнне аднаго з уваходаў ці выхадаў фільтра.

Сігнальны пункт графа апрацоўкі дадзеных

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

Піны фільтраў нумаруюцца пачынальна з нуля.

Злучэнне двух пінаў чаргой паведамленняў апісваецца структурай _MSQueue, якая змяшчае чаргу паведамленняў і паказальнікі на дзве сігнальныя кропкі, якія яна злучае:

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

Будзем зваць гэтую структуру сігнальным лінкам. Кожны фільтр медыястрымера, утрымоўвае табліцу ўваходных лінкаў і табліцу выходных лінкаў (MSQueue). Памер табліц задаецца пры стварэнні фільтра, мы гэта ўжо рабілі з дапамогай экспартаванай зменнай тыпу MSFilterDesc, калі распрацоўвалі наш уласны фільтр. Ніжэй паказана структура якая апісвае любы фільтр у медыястрымеры, 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;

Пасля таго, як мы ў Сі-праграме злучылі фільтры ў адпаведнасці з нашай задумай (але не падлучылі Біржавы сімвал), мы тым самым стварылі накіраваны граф, вузлы якога, гэта асобнікі структуры MSFilter, а рэбры гэта асобнікі лінкаў MSQueue.

Закулісная дзейнасць тыкера

Калі я казаў вам што Біржавы сімвал - гэта фільтр крыніца тактаў, то была не ўся праўда пра яго. Біржавы сімвал гэта аб'ект, які па гадзінах выконвае запуск функцый process() усіх фільтраў схемы (графа) да якой ён падлучаны. Калі мы ў Сі-праграме падлучаем Біржавы сімвал да фільтра графа, мы паказваем Біржавы сімвал граф, якім ён з гэтага моманту будзе кіраваць, пакуль мы яго не адключым. Пасля падлучэння, Біржавы сімвал пачынае аглядаць даручаны яму на апеку граф, складаючы спіс фільтраў у якія ў яго ўваходзяць. Каб не "падлічыць" адзін і той жа фільтр двойчы ён адзначае выяўленыя фільтры, усталёўваючы ў іх сцяжок бачыў. Пошук ажыццяўляецца па табліцах лінкаў, якія ёсць у кожнага фільтра.

Падчас сваёй азнаямленчай экскурсіі па графе, Біржавы сімвал правярае ці ёсць сярод фільтраў, хоць бы адзін, які выконвае ролю крыніцы блокаў дадзеных. Калі такіх не знаходзіцца, то граф прызнаецца няправільным і Біржавы сімвал аварыйна завяршае працу.

Калі граф аказаўся "правільным", у кожнага знойдзенага фільтра, для ініцыялізацыі, выклікаецца функцыя preprocess(). Як толькі надыходзіць момант для чарговага такту апрацоўкі (па змаўчанні кожныя 10 мілісекунд), Біржавы сімвал выклікае функцыю process() для ўсіх знойдзеных раней фільтраў крыніц, а затым і для астатніх фільтраў спісу. Калі фільтр мае ўваходныя лінкі, то запуск функцыі process() паўтараецца датуль, пакуль чэргі ўваходных лінкаў не спусцеюць. Пасля гэтага, ён пераходзіць да наступнага фільтра спісу і "пракручвае" яго да вызвалення ўваходных лінкаў ад паведамленняў. Біржавы сімвал пераходзіць ад фільтра да фільтра пакуль не скончыцца спіс. На гэтым апрацоўка такту заканчваецца.

Цяпер мы вернемся да картэжаў і пагаворым аб тым для чаго ў медыястрымер была дададзена такая сутнасць. У агульным выпадку, аб'ём дадзеных, неабходны алгарытму, які працуе ўнутры фільтра не супадае і не крацены, памеру буфераў дадзеных якія паступаюць на ўваходзе. Напрыклад, мы пішам фільтр, які выконвае хуткае пераўтварэнне Фур'е, якое па вызначэнні можа апрацоўваць толькі блокі дадзеных чый памер роўны ступені двойкі. Няхай гэта будзе 512 адлікаў. Калі дадзеныя генеруюцца тэлефонным каналам, то буфер дадзеных кожнага паведамлення на ўваходзе будзе прыносіць нам па 160 адлікаў сігналу. Ёсць спакуса не забіраць дадзеныя са ўваходу, пакуль тамака не апынецца неабходная колькасць дадзеных. Але ў гэтым выпадку паўстане калізія з Біржавы сімвал, які будзе беспаспяхова спрабаваць пракруціць фільтр да спусташэння ўваходнага лінка. Раней мы пазначылі гэтае правіла як трэці прынцып працы фільтра. У адпаведнасці з гэтым прынцыпам, функцыя process() фільтра павінна забраць усе дадзеныя з уваходных чэргаў.

Акрамя гэтага з уваходу нельга будзе забраць толькі 512 адлікаў, бо забіраць можна толькі цэлымі блокамі, г.зн. фільтру давядзецца забраць 640 адлікаў і выкарыстаўшы 512 з іх, рэшту да назапашвання новай порцыі дадзеных. Такім чынам, наш фільтр, апроч асноўнай сваёй працы павінен забяспечыць дапаможныя дзеянні па прамежкавым захоўванні ўваходных дадзеных. Распрацоўнікі медыястрымеру і рашэнні гэтай агульнай задачы распрацавалі адмысловы аб'ект — MSBufferizer (буферызатар), які вырашае гэтую задачу з дапамогай картэжаў.

Буферызатар (MSBufferizer)

Гэта аб'ект які будзе назапашваць уваходныя дадзеныя ўсярэдзіне фільтра і пачне аддаваць іх у апрацоўку, як толькі колькасць інфармацыі апынецца дастатковым для проворачивания алгарытму фільтра. Пакуль буферызатар назапашвае дадзеныя, фільтр будзе працаваць у халастым рэжыме, не затрачваючы вылічальнай магутнасці працэсара. Але як толькі функцыя чытання з буферызатара верне значэнне выдатнае ад нуля, функцыя process() фільтра пачынае забіраць і апрацоўваць з буферызатара дадзеныя порцыямі патрэбнага памеру, да іх вычарпання.
Незапатрабаваныя пакуль дадзеныя застаюцца ў буферызатары як першы элемент картэжа, да якога прычапляюцца наступныя блокі ўваходных дадзеных.

Структура, якая апісвае буферызатар:

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

Функцыі працы з MSBufferizer

Стварэнне новага экзэмпляра буферызатара:

MSBufferizer * ms_bufferizer_new(void);

Вылучаецца памяць, ініцыялізуецца ў ms_bufferizer_init() і вяртаецца паказальнік.

Функцыя ініцыялізацыі:

void ms_bufferizer_init(MSBufferizer *obj); 

Ініцыялізуецца чарга q, поле памер устанаўліваецца ў нуль.

Даданне паведамлення:

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

Паведамленне m дадаецца ў чаргу. Вылічаны памер блокаў дадзеных дадаецца да памер.

Перакладка ў буферызатар усіх паведамленняў чаргі дадзеных лінка q:

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

Перанос паведамленняў з лінка q у буферызатар выконваецца з дапамогай функцыі ms_bufferizer_put().

Чытанне з буферызатара:

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

Калі памер назапашаных у буферызатары дадзеных аказваецца менш за запытаны (datalen), то функцыя вяртае нуль, капіраванне дадзеных у data не выконваецца. У адваротным выпадку выконваецца паслядоўнае капіраванне дадзеных з картэжаў змешчаных у буферызатары. Пасля капіявання картэж выдаляецца і памяць вызваляецца. Капіраванне заканчваецца ў той момант, калі будзе скапіявана datalen байтаў. Калі месца канчаецца пасярод блока дадзеных, то ў дадзеным паведамленні, блок дадзеных будзе скарочаны да пакінутай некапіраванай часткі. Пры наступным выкліку капіраванне працягнецца з гэтага месца.

Чытанне колькасці дадзеных, якія даступныя дадзены момант у буферызатары:

int ms_bufferizer_get_avail(MSBufferizer *obj); 

Вяртае поле памер буферызатара.

Адкідванне часткі дадзеных, якія знаходзяцца ў буферызатары:

void ms_bufferizer_skip_bytes(MSBufferizer *obj, int bytes);

Указаная колькасць байтаў дадзеных здабываецца і адкідаецца. Адкідаюцца самыя старыя дадзеныя.

Выдаленне ўсіх паведамленняў, якія знаходзяцца ў буферызатары:

void ms_bufferizer_flush(MSBufferizer *obj); 

Лічыльнік дадзеных скідаецца ў нуль.

Выдаленне ўсіх паведамленняў, якія знаходзяцца ў буферызатары:

void ms_bufferizer_uninit(MSBufferizer *obj); 

Абнуленне лічыльніка не выконваецца.

Выдаленне буферызатара і вызваленне памяці:

void ms_bufferizer_destroy(MSBufferizer *obj);  

Прыклады выкарыстання буферызатара можна знайсці ў зыходным кодзе некалькіх фільтраў медыястрымера. Напрыклад у фільтры MS_L16_ENC, які выконвае перастанову байтаў у адліках з сеткавага парадку, у парадак хаста: l16.c

У наступным артыкуле, мы разгледзім пытанне ацэнкі нагрузкі на Біржавы сімвал і спосабы барацьбы з празмернай вылічальнай нагрузкай у медыястрымеры.

Крыніца: habr.com

Дадаць каментар