Khám phá công cụ VoIP Mediastreamer2. Phần 11

Tài liệu của bài viết được lấy từ tài liệu của tôi kênh thiền.

Khám phá công cụ VoIP Mediastreamer2. Phần 11

Cơ chế di chuyển dữ liệu

  • Khối dữ liệu dblk_t
  • Tin nhắn mblk_t
  • Các hàm làm việc với tin nhắn mblk_t
  • Hàng đợi_t
  • Các hàm làm việc với hàng đợi queue_t
  • Kết nối bộ lọc
  • Điểm tín hiệu của đồ thị xử lý dữ liệu
  • Hoạt động hậu trường của cổ phiếu
  • Bộ đệm (MSBufferizer)
  • Các chức năng làm việc với MSBufferizer

Cuối cùng Bài viết chúng tôi đã phát triển bộ lọc của riêng mình. Bài viết này sẽ tập trung vào cơ chế nội bộ để di chuyển dữ liệu giữa các bộ lọc media streaming. Điều này sẽ cho phép bạn viết các bộ lọc phức tạp với ít nỗ lực hơn trong tương lai.

Cơ chế di chuyển dữ liệu

Di chuyển dữ liệu trong bộ truyền phát phương tiện được thực hiện bằng cách sử dụng hàng đợi được mô tả bởi cấu trúc hàng đợi_t. Chuỗi tin nhắn như mblk_t, bản thân chúng không chứa dữ liệu tín hiệu mà chỉ liên kết đến tin nhắn trước đó, tin nhắn tiếp theo và khối dữ liệu. Ngoài ra, tôi muốn đặc biệt nhấn mạnh rằng còn có một trường dành cho liên kết đến một tin nhắn cùng loại, cho phép bạn sắp xếp một danh sách các tin nhắn được liên kết đơn lẻ. Chúng ta sẽ gọi một nhóm các thông điệp được hợp nhất bởi một danh sách như vậy là một bộ. Vì vậy, bất kỳ phần tử nào của hàng đợi đều có thể là một thông điệp mblk_tvà có thể là phần đầu của một bộ thông điệp mblk_t. Mỗi tin nhắn tuple có thể có khối dữ liệu ward riêng. Chúng ta sẽ thảo luận tại sao cần có bộ dữ liệu sau này.

Như đã đề cập ở trên, bản thân thông báo không chứa khối dữ liệu; thay vào đó, nó chỉ chứa một con trỏ tới vùng bộ nhớ nơi khối được lưu trữ. Trong phần này, bức tranh tổng thể về công việc của người truyền phát phương tiện gợi nhớ đến nhà kho có cửa trong phim hoạt hình “Monsters, Inc.”, nơi các cánh cửa (liên kết đến dữ liệu - phòng) di chuyển với tốc độ chóng mặt dọc theo băng tải trên cao, trong khi chính các phòng vẫn bất động.

Bây giờ, di chuyển dọc theo hệ thống phân cấp từ dưới lên trên, hãy xem xét chi tiết các thực thể được liệt kê của cơ chế truyền dữ liệu trong bộ truyền phát phương tiện.

Khối dữ liệu dblk_t

Khối dữ liệu bao gồm tiêu đề và bộ đệm dữ liệu. Tiêu đề được mô tả theo cấu trúc sau,

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

Các trường của cấu trúc chứa các con trỏ tới đầu bộ đệm, cuối bộ đệm và hàm xóa bộ đệm dữ liệu. Phần tử cuối cùng trong tiêu đề db_ref - bộ đếm tham chiếu, nếu nó đạt đến 0, đây là tín hiệu để xóa khối này khỏi bộ nhớ. Nếu khối dữ liệu được tạo bởi hàm datab_alloc() , thì bộ đệm dữ liệu sẽ được đặt vào bộ nhớ ngay sau tiêu đề. Trong tất cả các trường hợp khác, bộ đệm có thể được đặt ở đâu đó riêng biệt. Bộ đệm dữ liệu sẽ chứa các mẫu tín hiệu hoặc dữ liệu khác mà chúng ta muốn xử lý bằng bộ lọc.

Một phiên bản mới của khối dữ liệu được tạo bằng hàm:

dblk_t *datab_alloc(int size);

Là một tham số đầu vào, nó được cung cấp kích thước của dữ liệu mà khối sẽ lưu trữ. Nhiều bộ nhớ hơn được phân bổ để đặt tiêu đề - cấu trúc - ở đầu bộ nhớ được phân bổ cơ sở dữ liệu. Nhưng khi sử dụng các chức năng khác, điều này không phải lúc nào cũng xảy ra; trong một số trường hợp, bộ đệm dữ liệu có thể được đặt tách biệt với tiêu đề khối dữ liệu. Khi tạo cấu trúc, các trường được cấu hình sao cho trường của nó db_base trỏ đến phần đầu của vùng dữ liệu và db_lim đến cuối cùng của nó. Số lượng liên kết db_ref được đặt thành một. Con trỏ hàm xóa dữ liệu được đặt thành 0.

thông điệp mblk_t

Như đã nêu, các phần tử hàng đợi có kiểu mblk_t, Nó được định nghĩa như sau:

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;

Cấu trúc mblk_t chứa con trỏ ở đầu b_prev, b_tiếp theo, cần thiết để tổ chức một danh sách liên kết đôi (là một hàng đợi hàng đợi_t).

Sau đó đến con trỏ b_tiếp, chỉ được sử dụng khi tin nhắn là một phần của bộ dữ liệu. Đối với thông báo cuối cùng trong bộ dữ liệu, con trỏ này vẫn rỗng.

Tiếp theo chúng ta thấy một con trỏ tới khối dữ liệu b_datap, mà thông báo tồn tại. Theo sau nó là các con trỏ tới khu vực bên trong bộ đệm dữ liệu khối. Cánh đồng b_rptr chỉ định vị trí mà dữ liệu từ bộ đệm sẽ được đọc. Cánh đồng b_wptr cho biết vị trí mà việc ghi vào bộ đệm sẽ được thực hiện.

Các trường còn lại mang tính chất dịch vụ và không liên quan đến hoạt động của cơ chế truyền dữ liệu.

Dưới đây là một tin nhắn có tên m1 và khối dữ liệu d1.
Khám phá công cụ VoIP Mediastreamer2. Phần 11
Hình dưới đây hiển thị một bộ gồm ba thông báo m1, m1_1, m1_2.
Khám phá công cụ VoIP Mediastreamer2. Phần 11

Chức năng nhắn tin mblk_t

Một tin nhắn mới mblk_t được tạo bởi hàm:

mblk_t *allocb(int size, int pri); 

cô ấy đặt một tin nhắn mới vào bộ nhớ mblk_t với khối dữ liệu có kích thước được chỉ định kích thước, đối số thứ hai - linh mục không được sử dụng trong phiên bản này của thư viện. Nó sẽ vẫn bằng không. Trong quá trình hoạt động của hàm, bộ nhớ sẽ được cấp phát cho cấu trúc của thông báo mới và hàm sẽ được gọi mblk_init(), nó sẽ đặt lại tất cả các trường của phiên bản đã tạo của cấu trúc và sau đó, sử dụng thông tin đã đề cập ở trên datab_alloc(), sẽ tạo bộ đệm dữ liệu. Sau đó các trường trong cấu trúc sẽ được cấu hình:

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

Ở đầu ra, chúng tôi nhận được một thông báo mới với các trường được khởi tạo và bộ đệm dữ liệu trống. Để thêm dữ liệu vào tin nhắn, bạn cần sao chép nó vào bộ đệm khối dữ liệu:

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

đâu dữ liệu là một con trỏ tới nguồn dữ liệu và kích thước - kích thước của chúng.
sau đó bạn cần cập nhật con trỏ thành điểm ghi để nó trỏ lại điểm bắt đầu của vùng trống trong bộ đệm:

msg->b_wptr = msg->b_wptr + size

Nếu bạn cần tạo một tin nhắn từ bộ đệm hiện có mà không cần sao chép, hãy sử dụng hàm:

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

Hàm sau khi tạo thông báo và cấu trúc khối dữ liệu sẽ cấu hình con trỏ của nó tới dữ liệu tại địa chỉ buf. Những thứ kia. trong trường hợp này, bộ đệm dữ liệu không được đặt sau các trường tiêu đề của khối dữ liệu, như trường hợp khi tạo khối dữ liệu bằng hàm datab_alloc(). Bộ đệm chứa dữ liệu được truyền đến hàm sẽ vẫn giữ nguyên vị trí cũ, nhưng với sự trợ giúp của con trỏ, nó sẽ được gắn vào tiêu đề mới được tạo của khối dữ liệu và theo đó, vào thông báo.

Đến một tin nhắn mblk_t Một số khối dữ liệu có thể được nối tuần tự. Điều này được thực hiện bởi hàm:

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

mp — một thông báo mà khối dữ liệu khác sẽ được thêm vào;
dữ liệu — con trỏ tới khối, một bản sao của khối này sẽ được thêm vào tin nhắn;
kích thước - kích thước dữ liệu;
tập giấy — cờ cho biết kích thước của bộ nhớ được phân bổ phải được căn chỉnh dọc theo ranh giới 4 byte (việc đệm sẽ được thực hiện bằng số XNUMX).

Nếu có đủ dung lượng trong bộ đệm dữ liệu tin nhắn hiện có thì dữ liệu mới sẽ được dán vào sau dữ liệu đã có ở đó. Nếu có ít dung lượng trống trong bộ đệm dữ liệu tin nhắn hơn kích thước, khi đó một thông báo mới sẽ được tạo với kích thước bộ đệm đủ và dữ liệu sẽ được sao chép vào bộ đệm của nó. Đây là tin nhắn mới, được liên kết với tin nhắn gốc bằng con trỏ b_tiếp. Trong trường hợp này, tin nhắn biến thành một bộ dữ liệu.

Nếu bạn cần thêm một khối dữ liệu khác vào bộ dữ liệu, thì bạn cần sử dụng hàm:

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

cô ấy sẽ tìm thấy tin nhắn cuối cùng trong bộ dữ liệu (anh ấy có b_tiếp sẽ là null) và sẽ gọi hàm cho thông báo này phần phụ thêm().

Bạn có thể tìm hiểu kích thước của dữ liệu trong một tin nhắn hoặc bộ dữ liệu bằng hàm:

int msgdsize(const mblk_t *mp);

nó sẽ lặp qua tất cả các tin nhắn trong bộ dữ liệu và trả về tổng lượng dữ liệu trong bộ đệm dữ liệu của những tin nhắn đó. Với mỗi tin nhắn, lượng dữ liệu được tính như sau:

 mp->b_wptr - mp->b_rptr

Để kết hợp hai bộ dữ liệu, hãy sử dụng hàm:

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

cô ấy nối thêm bộ dữ liệu newm đến đuôi của tuple mp và trả về một con trỏ tới thông báo cuối cùng của bộ dữ liệu kết quả.

Nếu cần thiết, một bộ dữ liệu có thể được chuyển thành một thông báo với một khối dữ liệu; việc này được thực hiện bằng hàm:

void msgpullup(mblk_t *mp,int len);

nếu đối số len là -1 thì kích thước của bộ đệm được phân bổ sẽ được xác định tự động. Nếu như len là một số dương, một bộ đệm có kích thước này sẽ được tạo và dữ liệu thông báo bộ dữ liệu sẽ được sao chép vào đó. Nếu hết bộ đệm, việc sao chép sẽ dừng ở đó. Thông báo đầu tiên của bộ dữ liệu sẽ nhận được bộ đệm kích thước mới với dữ liệu được sao chép. Các tin nhắn còn lại sẽ bị xóa và bộ nhớ sẽ quay trở lại heap.

Khi xóa cấu trúc mblk_t số tham chiếu của khối dữ liệu được tính đến nếu khi gọi miễn phí() hóa ra là 0 thì bộ đệm dữ liệu sẽ bị xóa cùng với phiên bản mblk_t, trỏ đến nó.

Đang khởi tạo các trường của tin nhắn mới:

void mblk_init(mblk_t *mp);

Thêm một phần dữ liệu khác vào tin nhắn:

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

Nếu dữ liệu mới không vừa với không gian trống của bộ đệm dữ liệu tin nhắn thì một tin nhắn được tạo riêng với bộ đệm có kích thước yêu cầu sẽ được đính kèm vào tin nhắn (một con trỏ tới tin nhắn đã thêm sẽ được đặt trong tin nhắn đầu tiên) và tin nhắn biến thành một tuple.

Thêm một phần dữ liệu vào một bộ dữ liệu:

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

Hàm gọi phần bổ sung() trong một vòng lặp.

Kết hợp hai bộ dữ liệu thành một:

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

thông điệp newm sẽ được gắn vào mp.

Tạo một bản sao của một tin nhắn:

mblk_t *copyb(const mblk_t *mp);

Sao chép hoàn chỉnh một bộ dữ liệu với tất cả các khối dữ liệu:

mblk_t *copymsg(const mblk_t *mp);

Các phần tử của bộ dữ liệu được sao chép bởi hàm sao chép().

Tạo bản sao tin nhắn dễ dàng mblk_t. Trong trường hợp này, khối dữ liệu không được sao chép nhưng bộ đếm tham chiếu của nó được tăng lên db_ref:

mblk_t *dupb(mblk_t *mp);

Tạo một bản sao nhẹ của một tuple. Các khối dữ liệu không được sao chép, chỉ có bộ đếm tham chiếu của chúng được tăng lên db_ref:

mblk_t *dupmsg(mblk_t* m);

Dán tất cả các tin nhắn của một bộ dữ liệu vào một tin nhắn:

void msgpullup(mblk_t *mp,size_t len);

Nếu đối số len là -1 thì kích thước của bộ đệm được phân bổ sẽ được xác định tự động.

Xóa tin nhắn, tuple:

void freemsg(mblk_t *mp);

Số tham chiếu của khối dữ liệu giảm đi một. Nếu nó về 0 thì khối dữ liệu cũng bị xóa.

Tính toán tổng lượng dữ liệu trong một tin nhắn hoặc bộ dữ liệu.

size_t msgdsize(const mblk_t *mp);

Lấy một tin nhắn từ cuối hàng đợi:

mblk_t *ms_queue_peek_last (q);

Sao chép nội dung của các trường dành riêng của một tin nhắn sang một tin nhắn khác (trên thực tế, các trường này chứa các cờ được bộ truyền phát phương tiện sử dụng):

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

Xếp hàng hàng đợi_t

Hàng đợi tin nhắn trong bộ truyền phát đa phương tiện được triển khai dưới dạng danh sách liên kết đôi vòng tròn. Mỗi phần tử danh sách chứa một con trỏ tới khối dữ liệu có mẫu tín hiệu. Hóa ra chỉ có các con trỏ tới khối dữ liệu lần lượt di chuyển, trong khi bản thân dữ liệu vẫn bất động. Những thứ kia. chỉ các liên kết đến chúng được di chuyển.
Cấu trúc mô tả hàng đợi hàng đợi_t, hiển thị dưới đây:

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

Cấu trúc chứa một trường - một con trỏ _q_stopper gõ *mblk_t, nó trỏ đến phần tử (tin nhắn) đầu tiên trong hàng đợi. Trường thứ hai của cấu trúc là bộ đếm các tin nhắn trong hàng đợi.
Hình bên dưới hiển thị hàng đợi có tên q1 chứa 4 tin nhắn m1, m2, m3, m4.
Khám phá công cụ VoIP Mediastreamer2. Phần 11
Hình dưới đây cho thấy hàng đợi có tên q1 chứa 4 tin nhắn m1,m2,m3,m4. Thông báo m2 là phần đầu của bộ dữ liệu chứa thêm hai thông báo m2_1 và m2_2.

Khám phá công cụ VoIP Mediastreamer2. Phần 11

Các hàm làm việc với hàng đợi queue_t

Khởi tạo hàng đợi:

void qinit(queue_t *q);

Lĩnh vực _q_stopper (sau đây gọi là “stopper”) được khởi tạo bởi hàm mblk_init(), phần tử trước và con trỏ phần tử tiếp theo của nó được điều chỉnh để trỏ tới chính nó. Bộ đếm phần tử hàng đợi được đặt lại về 0.

Thêm một thành phần mới (tin nhắn):

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

Yếu tố mới m được thêm vào cuối danh sách, các con trỏ phần tử được điều chỉnh sao cho nút chặn trở thành phần tử tiếp theo cho nó và nó trở thành phần tử trước đó cho nút chặn. Bộ đếm phần tử hàng đợi được tăng lên.

Lấy một phần tử từ hàng đợi:

mblk_t * getq(queue_t *q); 

Thông báo xuất hiện sau khi lấy được nút chặn và bộ đếm phần tử bị giảm đi. Nếu không có phần tử nào trong hàng đợi ngoại trừ phần tử chặn thì trả về 0.

Chèn tin nhắn vào hàng đợi:

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

Элемент mp chèn trước phần tử emp. Nếu emp=0 thì tin nhắn sẽ được thêm vào cuối hàng đợi.

Lấy tin nhắn từ đầu hàng đợi:

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

Bộ đếm phần tử bị giảm đi.

Đọc con trỏ tới phần tử đầu tiên trong hàng đợi:

mblk_t * peekq(queue_t *q); 

Xóa tất cả các phần tử khỏi hàng đợi trong khi xóa chính các phần tử đó:

void flushq(queue_t *q, int how);

Tranh luận làm thế nào không được sử dụng. Bộ đếm phần tử hàng đợi được đặt thành 0.

Macro để đọc con trỏ tới phần tử cuối cùng của hàng đợi:

mblk_t * qlast(queue_t *q);

Khi làm việc với hàng đợi tin nhắn, hãy lưu ý rằng khi bạn gọi ms_queue_put(q, m) với một con trỏ rỗng tới tin nhắn, hàm sẽ lặp lại. Chương trình của bạn sẽ đóng băng. cư xử tương tự ms_queue_next(q, m).

Kết nối bộ lọc

Hàng đợi được mô tả ở trên được sử dụng để chuyển tin nhắn từ bộ lọc này sang bộ lọc khác hoặc từ bộ lọc này sang bộ lọc khác. Các bộ lọc và kết nối của chúng tạo thành một biểu đồ có hướng. Đầu vào hoặc đầu ra của bộ lọc sẽ được gọi chung là “pin”. Để mô tả thứ tự các bộ lọc được kết nối với nhau, bộ truyền phát phương tiện sử dụng khái niệm “điểm tín hiệu”. Điểm tín hiệu là cấu trúc _MSCĐiểm, chứa một con trỏ tới bộ lọc và số của một trong các chân của nó; theo đó, nó mô tả kết nối của một trong các đầu vào hoặc đầu ra của bộ lọc.

Điểm tín hiệu của đồ thị xử lý dữ liệu

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

Các chân của bộ lọc được đánh số bắt đầu từ số 0.

Việc kết nối hai chân bằng hàng đợi tin nhắn được mô tả bằng cấu trúc _MSHàng đợi, chứa hàng đợi tin nhắn và con trỏ tới hai điểm tín hiệu mà nó kết nối:

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

Chúng ta sẽ gọi cấu trúc này là liên kết tín hiệu. Mỗi bộ lọc bộ truyền phát phương tiện chứa một bảng liên kết đầu vào và một bảng liên kết đầu ra (Hàng đợi MS). Kích thước của các bảng được đặt khi tạo bộ lọc; chúng tôi đã thực hiện việc này bằng cách sử dụng biến loại được xuất MSFilterDesc, khi chúng tôi phát triển bộ lọc của riêng mình. Dưới đây là cấu trúc mô tả bất kỳ bộ lọc nào trong bộ truyền phát phương tiện, Bộ lọc MS:


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;

Sau khi chúng tôi kết nối các bộ lọc trong chương trình C theo kế hoạch của chúng tôi (nhưng không kết nối mã đánh dấu), từ đó chúng tôi đã tạo ra một biểu đồ có hướng, các nút trong đó là các thể hiện của cấu trúc Bộ lọc MSvà các cạnh là các thể hiện của liên kết Hàng đợi MS.

Hoạt động hậu trường của cổ phiếu

Khi tôi nói với bạn rằng mã báo giá là một bộ lọc để tìm nguồn gốc của các mã báo giá, đó không phải là toàn bộ sự thật về nó. Máy đánh dấu là một vật thể thực hiện các chức năng trên đồng hồ tiến trình() tất cả các bộ lọc của mạch (đồ thị) mà nó được kết nối. Khi chúng tôi kết nối một mã đánh dấu với bộ lọc biểu đồ trong chương trình C, chúng tôi hiển thị cho mã đánh dấu biểu đồ mà nó sẽ kiểm soát từ bây giờ cho đến khi chúng tôi tắt nó. Sau khi kết nối, mã đánh dấu bắt đầu kiểm tra biểu đồ được giao phó cho nó chăm sóc, biên soạn danh sách các bộ lọc bao gồm nó. Để không “đếm” cùng một bộ lọc hai lần, nó đánh dấu các bộ lọc được phát hiện bằng cách đặt hộp kiểm vào chúng đã xem. Việc tìm kiếm được thực hiện bằng cách sử dụng các bảng liên kết mà mỗi bộ lọc có.

Trong chuyến tham quan giới thiệu về biểu đồ, mã đánh dấu sẽ kiểm tra xem trong số các bộ lọc có ít nhất một bộ lọc hoạt động như một nguồn khối dữ liệu hay không. Nếu không có thì biểu đồ được coi là không chính xác và mã đánh dấu bị lỗi.

Nếu biểu đồ là “chính xác”, đối với mỗi bộ lọc được tìm thấy, hàm sẽ được gọi để khởi tạo tiền xử lý(). Ngay khi đến thời điểm cho chu kỳ xử lý tiếp theo (theo mặc định cứ sau 10 mili giây), mã đánh dấu sẽ gọi hàm tiến trình() cho tất cả các bộ lọc nguồn được tìm thấy trước đó và sau đó cho các bộ lọc còn lại trong danh sách. Nếu bộ lọc có liên kết đầu vào thì hãy chạy hàm tiến trình() lặp lại cho đến khi hàng đợi liên kết đầu vào trống. Sau đó, nó chuyển sang bộ lọc tiếp theo trong danh sách và “cuộn” nó cho đến khi các liên kết đầu vào không có tin nhắn. Mã đánh dấu di chuyển từ bộ lọc này sang bộ lọc khác cho đến khi danh sách kết thúc. Điều này hoàn thành việc xử lý chu trình.

Bây giờ chúng ta sẽ quay lại bộ dữ liệu và nói về lý do tại sao một thực thể như vậy lại được thêm vào bộ truyền phát phương tiện. Nói chung, lượng dữ liệu được yêu cầu bởi thuật toán hoạt động bên trong bộ lọc không trùng nhau và không phải là bội số của kích thước bộ đệm dữ liệu nhận được ở đầu vào. Ví dụ: chúng tôi đang viết một bộ lọc thực hiện phép biến đổi Fourier nhanh, theo định nghĩa, bộ lọc này chỉ có thể xử lý các khối dữ liệu có kích thước là lũy thừa của hai. Hãy để nó là 512 đếm. Nếu dữ liệu được tạo bởi kênh điện thoại thì bộ đệm dữ liệu của mỗi tin nhắn ở đầu vào sẽ mang đến cho chúng ta 160 mẫu tín hiệu. Bạn không nên thu thập dữ liệu từ đầu vào cho đến khi có đủ lượng dữ liệu cần thiết. Nhưng trong trường hợp này, xung đột sẽ xảy ra với mã đánh dấu, mã này sẽ cố gắng cuộn bộ lọc không thành công cho đến khi liên kết đầu vào trống. Trước đây, chúng tôi đã chỉ định quy tắc này là nguyên tắc thứ ba của bộ lọc. Theo nguyên tắc này, hàm process() của bộ lọc phải lấy tất cả dữ liệu từ hàng đợi đầu vào.

Ngoài ra, sẽ không thể chỉ lấy 512 mẫu từ đầu vào vì bạn chỉ có thể lấy toàn bộ khối, tức là. bộ lọc sẽ phải lấy 640 mẫu và sử dụng 512 mẫu trong số đó, phần còn lại trước khi tích lũy phần dữ liệu mới. Như vậy, bộ lọc của chúng ta ngoài công việc chính còn phải cung cấp các tác vụ phụ trợ để lưu trữ trung gian dữ liệu đầu vào. Các nhà phát triển media streaming và giải pháp cho vấn đề chung này đã phát triển một đối tượng đặc biệt - MSBufferizer (bộ đệm), giải quyết vấn đề này bằng cách sử dụng bộ dữ liệu.

Bộ đệm (MSBufferizer)

Đây là đối tượng sẽ tích lũy dữ liệu đầu vào bên trong bộ lọc và bắt đầu gửi nó để xử lý ngay khi lượng thông tin đủ để chạy thuật toán lọc. Trong khi bộ đệm đang tích lũy dữ liệu, bộ lọc sẽ hoạt động ở chế độ không tải, không sử dụng hết sức mạnh xử lý của bộ xử lý. Nhưng ngay khi hàm đọc từ bộ đệm trả về một giá trị khác 0, hàm process() của bộ lọc sẽ bắt đầu lấy và xử lý dữ liệu từ bộ đệm theo các phần có kích thước được yêu cầu, cho đến khi hết dữ liệu.
Dữ liệu chưa được yêu cầu vẫn còn trong bộ đệm dưới dạng phần tử đầu tiên của bộ dữ liệu, nơi các khối dữ liệu đầu vào tiếp theo được đính kèm.

Cấu trúc mô tả bộ đệm:

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

Các chức năng làm việc với MSBufferizer

Tạo một phiên bản bộ đệm mới:

MSBufferizer * ms_bufferizer_new(void);

Bộ nhớ được phân bổ, khởi tạo trong ms_bufferizer_init() và một con trỏ được trả về.

Chức năng khởi tạo:

void ms_bufferizer_init(MSBufferizer *obj); 

Hàng đợi đang khởi tạo q, đồng ruộng kích thước được đặt thành không.

Thêm tin nhắn:

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

Tin nhắn m được thêm vào hàng đợi. Kích thước tính toán của khối dữ liệu được thêm vào kích thước.

Chuyển tất cả tin nhắn từ hàng đợi dữ liệu liên kết sang bộ đệm q:

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

Chuyển tin nhắn từ một liên kết q trong bộ đệm được thực hiện bằng hàm ms_bufferizer_put().

Đọc từ bộ đệm:

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

Nếu kích thước của dữ liệu tích lũy trong bộ đệm nhỏ hơn kích thước được yêu cầu (dữ liệu), thì hàm trả về 0, dữ liệu không được sao chép vào dữ liệu. Mặt khác, việc sao chép tuần tự dữ liệu từ các bộ dữ liệu nằm trong bộ đệm sẽ được thực hiện. Sau khi sao chép, bộ dữ liệu sẽ bị xóa và bộ nhớ được giải phóng. Quá trình sao chép kết thúc tại thời điểm các byte dữ liệu được sao chép. Nếu hết dung lượng ở giữa khối dữ liệu thì trong thông báo này, khối dữ liệu sẽ bị giảm xuống phần còn lại chưa được sao chép. Lần tiếp theo bạn gọi, việc sao chép sẽ tiếp tục từ thời điểm này.

Đọc lượng dữ liệu hiện có trong bộ đệm:

int ms_bufferizer_get_avail(MSBufferizer *obj); 

Trả về trường kích thước bộ đệm.

Loại bỏ một phần dữ liệu trong bộ đệm:

void ms_bufferizer_skip_bytes(MSBufferizer *obj, int bytes);

Số byte dữ liệu được chỉ định sẽ được truy xuất và loại bỏ. Dữ liệu cũ nhất sẽ bị loại bỏ.

Xóa tất cả tin nhắn trong bộ đệm:

void ms_bufferizer_flush(MSBufferizer *obj); 

Bộ đếm dữ liệu được đặt lại về 0.

Xóa tất cả tin nhắn trong bộ đệm:

void ms_bufferizer_uninit(MSBufferizer *obj); 

Bộ đếm không được thiết lập lại.

Xóa bộ đệm và giải phóng bộ nhớ:

void ms_bufferizer_destroy(MSBufferizer *obj);  

Ví dụ về việc sử dụng bộ đệm có thể được tìm thấy trong mã nguồn của một số bộ lọc bộ truyền phát phương tiện. Ví dụ: trong bộ lọc MS_L16_ENC, sắp xếp lại các byte trong các mẫu từ thứ tự mạng sang thứ tự máy chủ: l16.c

Trong bài viết tiếp theo, chúng ta sẽ xem xét vấn đề ước tính tải mã đánh dấu và cách xử lý tải máy tính quá mức trong bộ truyền phát phương tiện.

Nguồn: www.habr.com

Thêm một lời nhận xét