Расчетно-пояснительная записка к курсовому проекту по теме: Надежная передача потокового видео: исследование поведения видео трафика реального времени в различных условиях загрузки сети. Студент: Недашковский В.В. Группа: ИУ3-91 Преподаватель: Федоров С.В. Москва, 2010г 1 Введение Все большая часть информации, предоставляемой конечному пользователю, передается через компьютерные сети: интернет, локальные сети ит.д. Сухие новостные порталы понемногу уходят в прошлое, и наступает время онлайнового вещания телевидения. Это стало возможным, поскольку появились производительные алгоритмы для сжатия видео с минимальными потерями. Однако, передача видео реального времени по сети неизбежно осложняется следующими факторами: ширина канала, ее изменение от времени, задержки, потери и другие. Так же нерешенными остаются проблема, как эффективно передавать видео контент по схеме один ко многим. Цель 1. Исследовать существующие технологии по передачи потокового видео, понять их преимущества и недостатки (в частности: адаптивная передача видео). 2. Исследовать инструменты и библиотеки, которые можно использовать для организации передачи видео в режиме реального времени. 3. Сконструировать систему с помощью, которой можно проводить анализ математической модели потерь в интернете и локальных сетях. Терминология Виды организации передачи видео: 1. multicast - групповая (многоадресная) передача, форма широковещания, при которой передача сообщения производится от одного отправителя конкретной группе сетевых устройств, т.е. нескольким получателям 2. broadcast - широковещание в сетях - передача одного сообщения всем станциям сети 3. unicast - индивидуальная рассылка, одноадресная передача, рассылка сообщений по конкретным узлам сети Общие принципы сжатия видеоинформации Сжатие видео потока достигается с помощью использования похожести и избыточности в исходной видеоинформации. Например, последовательные кадры видео содержат одни и те же объекты, но с небольшим смещением, искажением или изменением цвета. Предположим, что каждый кадр видео закодирован с помощью алгоритма JPEG, который использует пространственную и цветовую избыточность изображения. Соседние пиксели в изображении часто очень похожи, и в естественных изображениях большая часть энергии спектра сконцентрирована с области низких частот. JPEG использует эти особенности, разбивая 2 картинку на части размером 8x8 пикселей и вычисляя 2-D Дискретное косинусное преобразование (ДКП) для каждого блока. ДКП кодирует большую часть энергии сигнала небольшим количеством коэффициентов, которое определяется точность, с которой потом надо будет восстановить изображение. Затем полученные коэффициенты квантуются и кодируются по алгоритму Хафмана. Основная же идея в сжатии видео заключается в том, что бы выделить в видеопотоке базовые кадры, рассчитать разницу между ними и несколькими последующими кадрами, закодировать эту разницу и передать приемнику. После получения происходит обратный процесс восстановления кадров, используя базовые кадры и дельты различий по некоторым критериям. Описание стенда для построения модели ошибок передачи пакетов в компьютерных сетях Задачи 1. Протестировать передачу UDP пакетов через компьютерную сеть на различных режимах передачи и условиях в сети. 2. Собрать данные в удобном для анализа виде, построить графики, зависимости и посчитать показатели процессов. 3. Сделать выводы исходя из полученных данных, о том какие параметры и факты нужно учитывать при проектировании системы передачи потоковой видеоинформации в режиме реального времени. Теоретическое обоснование Для тестирования был выбран транспортный протокол UDP, поскольку вся ответственность за доставку пакетов лежит на уровне приложения, а не на уровне стека системы, как в случае с протоколом TCP. Если пакет или группа пакетов повреждаются или вовсе не приходят получателю, то стек протокола TCP вызывает повторную передачу столько раз сколько необходимо для успешного завершения передачи. В случае с видеоинформацией реального времени такой подход не слишком хорош, поскольку это вносит существенные задержки в процесс воспроизведения видео в программе конечного пользователя. Природа видеоинформации, описанная выше, позволяет «потерять» пакеты из видео потока без сильного влияния на информацию, которую видит пользователь. То есть если мы обеспечиваем надежную передачу базовых кадров, то для других кадров допустимо иметь ошибки или вообще не быть принятыми, поскольку конечной целью является идея о безостановочном воспроизведении пусть даже с некоторой потерей качества. Структура стенда Стенд состоит из клиентской и серверной части, программные реализации которых были написаны под ОС Linux и базы данных MySQL, используемой для хранения логов передачи и приема. 3 Для проведения испытаний требуются два ПК с ОС Linux, и установленным сервером базы данных MySQL, причем на ПК с серверной частью программы доступ к базе данных должен быть открыт извне. В каждый UDP пакет закладывается полезная нагрузка – идентификатор пакета, затем идут байты заполненные случайными данными. Перед отправкой или после получения соответственно отправитель и получатель вычисляют текущее системное время и сохраняют её вместе с идентификатором пакета в базу данных. Структуры таблиц для отправителя и получателя идентичны и имеют следующий SQL код: CREATE TABLE recv_log ( Id bigint(20) NOT NULL, Sec bigint(20) NOT NULL, Usec bigint(20) NOT NULL, PRIMARY KEY (Id) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 CREATE TABLE send_log ( Id bigint(20) NOT NULL, Sec bigint(20) NOT NULL, Usec bigint(20) NOT NULL, PRIMARY KEY (Id) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 Дальнейшие вычисления основываются на данных которые содержатся в каждой из этих таблиц после того как модули передачи и приема закончат свою работу. Серверная часть Серверная часть представляет собой принимающую сторону в процессе передачи UDP пакетов, которая слушает 42001 порт и принимает пакеты заданной длинны (этот и другие параметры задаются при компиляции программы с помощью директивы #define, что позволяет проводить эксперименты с различными параметрами). На стадии инициализации программы создаются подключения к базе данных и к сокету с которого мы будем в дальнейшем получать данные. Далее для созданного сокета задается параметр – размер буфера на приеме. На этапе проектирования и тестирования стенда выяснилось, что это очень важный параметр который надо учитывать при передаче потоковой информации с помощью UDP протокола. Рассмотрим этот вопрос более подробно в следующем пункте. Функция UDP буфера UDP пакеты могу приходить сериями потому, что они отправляются очень быстро или потому, что они склеиваются в буфере коммутаторов и роутеров в сети. Подобным образом могут обрабатываться если для принимающего приложения доступно процессорное время или они могут обрабатываться с задержкой поскольку процессорное время занято другими процессами. UDP буфер нужен, что бы сопоставить скорость прихода UDP пакетов со скоростью их обработки принимающим приложением. Буферизацией занимается ядро системы, которое выделяет каждому сокету фиксированный размер буфера в соответствии с установленной в системе политикой. Эта область памяти недоступна другим приложениям и видимо не выгружается на диск на протяжении всего 4 существования сокета. Если буфер переполняется ядро, отбрасывает входящие пакеты и инкрементирует счетчик принятых пакетов. Из найденной информации следует, что поскольку UDP изначально не проектировался для передачи интенсивного трафика, то максимальный размер буфера в ОС Linux – 4Мб. Относительно этого параметра в ОС Windows информации нет, но есть упоминания, что возможно установить его в значение 10 Мб. Формула для расчета размера буфера: Buffer Size = Max Latency * Average Rate (1) В TCP проблема переполнения буфера возложена на ядро и решается повторной передачей пакетов после временной остановки приема, требуемой принимающему приложению, что обработать данные уже находящиеся в буфере. В нашем случае во время всей передачи должен проводиться мониторинг соответствия буфера и скорости передачи, что бы автоматически подстраивать эти параметры для максимально эффективной передачи. Процесс обработки пакетов Каждый пришедший пакет записывается в базу данных в виде Id извлеченного из пакета и времени его получения. Цикл получения бесконечный, поскольку неизвестно сколько и когда придет пакетов. Для выхода из цикла используется системный сигнал kill, генерируемый по нажатию CTRL+C. Зайдя в обработчик этого сигнала, программа считает, что в базе данных уже лежат актуальные данные и начинает вызов следующих хранимых процедур из MySQL сервера: 1) Подсчет отношения пришедших пакетов к отправленным: CREATE DEFINER=`root`@`localhost` PROCEDURE `Compute_packet_loss`() BEGIN SELECT (SELECT COUNT(*) FROM `mysql`.`send_log` send WHERE send.Id NOT IN(SELECT Id FROM `mysql`.`recv_log`)) /(SELECT COUNT(*) FROM `mysql`.`send_log`) as 'LOSS'; END 2) Вычисление количества пакетных ошибок, средней длинны серии, количества 1, 2 и 3 пакетных ошибок: CREATE DEFINER=`root`@`localhost` PROCEDURE `Count_burst_errors`() BEGIN DECLARE done INT DEFAULT 0; DECLARE pa, pb, pc, pa1, pb1, pc1 INT; DECLARE group_start INT; DECLARE group_len INT; DECLARE avg_group_len INT; DECLARE a, b, c, a1, b1, c1 INT; DECLARE grp_ctr1, grp_ctr2, grp_ctr3 INT; -- 1, 2, 3 пакетные ошибки DECLARE burst_ctr INT DEFAULT 0; DECLARE error_log CURSOR FOR (SELECT * FROM `mysql`.`send_log` send LEFT JOIN `mysql`.`recv_log` recv ON send.Id = recv.Id WHERE recv.Id IS NULL); DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; 5 OPEN error_log; SET group_len = 1; SET avg_group_len =0; FETCH error_log INTO pa, pb, pc, pa1, pb1, pc1; -- предыдущие значения read_loop: LOOP FETCH error_log INTO a, b, c, a1, b1, c1; SET group_len = group_len + 1; SET grp_ctr1 = 0; SET grp_ctr2 = 0; SET grp_ctr3 = 0; IF (a <> pa + 1) AND ( group_len > 1) THEN -- закончилась группа SET burst_ctr = burst_ctr + 1; SET avg_group_len = avg_group_len + group_len; IF group_len = 2 THEN SET grp_ctr2 = grp_ctr2 + 1; END IF; IF group_len = 3 THEN SET grp_ctr3 = grp_ctr3 + 1; END IF; SET group_len = 0; ELSE BEGIN IF group_len = 1 THEN SET grp_ctr1 = grp_ctr1 + 1; END IF; END; END IF; SET pa = a; IF done THEN LEAVE read_loop; END IF; END LOOP; CLOSE error_log; SELECT burst_ctr as "burst packet counter", avg_group_len/burst_ctr as "average group length", grp_ctr1 as "burst group 1", grp_ctr2 as "burst group 2", grp_ctr3 as "burst group 3"; END Код модуля-получателя пакетов // RECIVER #include <sys/socket.h> /* for bind socket accept */ #include <unistd.h> /* for close() */ #include <arpa/inet.h>/* for inet_Addr etc*/ #include <string.h> /* for string and memset etc */ #include <iostream> /* for standard input output */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <sys/time.h> #include <mysql/mysql.h> #include <netinet/in.h> #include <resolv.h> #include <sys/types.h> #include <netinet/in.h> #include <errno.h> 6 using namespace std; #define DEBUG /*constants for module*/ #define MAXBUF 4//payload size #define UDPHDRSIZE 8 #define SYSBUFSIZE 100000 //koef. for system buffer #define ENASYSBUF //enable to allocate user defined buffer #define SID 0 // start index for packets #define REMOTEHOST "localhost" //means to listen socket REMOTEPORT="localhost" on REMOTEPORT=42001 port #define REMOTEPORT 42001 #define FILELOGNAME "recv_log.txt" #define GARBAGELOAD 1200 //whole packet size with garbage #define ENAGARBAGE //enable to load garbage into packets /*mysql #define #define #define #define #define credentials*/ HOST "localhost" //host where situated mysql server USER "root" PASSWORD "685702" INITDB "mysql" DBLOGNAME "recv_log" /*struct for filling into UDP packet data chunk*/ union payloadId { char cdata[4]; unsigned long ldata; }; /*struct for loda garbage in packets*/ union wholeBuffer { struct head { payloadId Id; // id of packet char garbageBuf[GARBAGELOAD - MAXBUF]; }head_buf; char buf[GARBAGELOAD]; }; /*help functions*/ /*system signal kill handler*/ void sigquit(int signal); /*function for save packet info into db*/ int save2mysql(MYSQL *connection, unsigned long id, timeval current_time); /*flush log in mysql db*/ int flushdblog(MYSQL *connection); /*function for print statistics about last transmition*/ void print_stat(MYSQL *connection); /*global variables*/ MYSQL *connection; FILE * recv_log; int iSockFd=-1; int total_packets = 0; timeval start_time; timeval finish_time; int main() { int iLength=0; 7 struct sockaddr_in servAddr,cliAddr; #ifndef ENAGARBAGE char buf[MAXBUF]; #endif payloadId Id; unsigned long receivedId; timeval current_time; MYSQL mysql; #ifdef ENAGARBAGE wholeBuffer wbufId; #endif printf("reciver on %s\n", REMOTEHOST); /*set system signals*/ signal(SIGQUIT, sigquit); signal(SIGHUP,sigquit); /* set function calls */ signal(SIGINT,sigquit); /*create socket details*/ iSockFd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP); memset(&servAddr,0,sizeof(servAddr)); memset(&cliAddr,0,sizeof(cliAddr)); int cur_n; socklen_t cur_n_len; if (getsockopt(iSockFd, SOL_SOCKET, SO_RCVBUF, &cur_n, &cur_n_len) != 1) { printf("Current system buffer length: %d.\n", cur_n); } else { printf("Problem in system query about system buffer length: %d.\n", cur_n); //exit(1); } /*try to allocate system buffer for UDP packets with custom size*/ #ifdef ENASYSBUF #ifndef ENAGARBAGE int n = SYSBUFSIZE * (UDPHDRSIZE + MAXBUF); #endif #ifdef ENAGARBAGE int n = SYSBUFSIZE * (UDPHDRSIZE + GARBAGELOAD); #endif if (setsockopt(iSockFd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) { printf("Problem in allocation system buffer with length: %d.\n", SYSBUFSIZE); exit(1); } else { printf("Current system buffer length after setsockopt: %d.\n", n); } #endif /*establish mysql connection*/ mysql_init(&mysql); connection = mysql_real_connect(&mysql, HOST, USER, PASSWORD, INITDB,0,0,CLIENT_MULTI_RESULTS); if (connection == NULL) { printf("Problem with mysql connection: %s\n", mysql_error(&mysql)); 8 exit(1); } flushdblog(connection); #ifdef DEBUG printf("%s\n", mysql_error(connection)); #endif /*fill socket structure*/ servAddr.sin_family=AF_INET; //servAddr.sin_addr.s_addr=inet_addr(REMOTEHOST); servAddr.sin_addr.s_addr = INADDR_ANY; servAddr.sin_port=htons(REMOTEPORT); int cliAddrLen=sizeof(struct sockaddr_in); int bindRet=bind(iSockFd,(struct sockaddr*)&servAddr,sizeof(servAddr)); /*test connection to socket*/ cout <<"Bind returned "<<bindRet<<endl; /*infinite loop for receiving packets*/ recv_log = fopen(FILELOGNAME, "w+"); gettimeofday(&start_time, 0); while(1) { #ifndef ENAGARBAGE int iRcvdBytes=recvfrom(iSockFd,buf,MAXBUF,0,(struct sockaddr*)&cliAddr,(socklen_t*)&cliAddrLen); gettimeofday(&current_time, 0); memcpy(Id.cdata,buf , 4); printf("%s = %d, sec = %d, usec = %d\n", "recv PacketId", Id.ldata, current_time.tv_sec, current_time.tv_usec); fprintf(recv_log, "%s = %d, sec = %d, usec = %d\n", "recv PacketId", Id.ldata, current_time.tv_sec, current_time.tv_usec); save2mysql(connection, Id.ldata, current_time); #endif #ifdef ENAGARBAGE int iRcvdBytes=recvfrom(iSockFd,wbufId.buf,GARBAGELOAD,0,(struct sockaddr*)&cliAddr,(socklen_t*)&cliAddrLen); gettimeofday(&current_time, 0); printf("%s = %d, sec = %d, usec = %d\n", "recv PacketId", wbufId.head_buf.Id.ldata, current_time.tv_sec, current_time.tv_usec); fprintf(recv_log, "%s = %d, sec = %d, usec = %d\n", "recv PacketId", wbufId.head_buf.Id.ldata, current_time.tv_sec, current_time.tv_usec); save2mysql(connection, wbufId.head_buf.Id.ldata, current_time); #endif #ifdef DEBUG printf("%s\n", mysql_error(connection)); #endif total_packets++; gettimeofday(&finish_time, 0); } mysql_close(connection); fclose(recv_log); close(iSockFd); return (0); } void sigquit(int signal) { printf("\nStop executing the receiver\n"); double total_speed =(((double)((double)total_packets*(GARBAGELOAD + UDPHDRSIZE)))/1000000) 9 /(((double)finish_time.tv_sec + (double)(finish_time.tv_usec)/1000000) - ((double)start_time.tv_sec + (double)(start_time.tv_usec)/1000000)); printf("Total packets received: %d\n", total_packets); printf("Total send speed: %f Mb/s\n", total_speed); if (recv_log != NULL) fclose(recv_log); if (connection != NULL) { print_stat(connection); mysql_close(connection); } close(iSockFd); exit(0); } int save2mysql(MYSQL *connection, unsigned long id, timeval current_time) { char query[300]; sprintf(query, "INSERT INTO %s (Id, Sec, Usec) VALUES(%lu, %lu, %lu)", DBLOGNAME, id, current_time.tv_sec, current_time.tv_usec); return(mysql_query(connection, query)); } int flushdblog(MYSQL *connection) { char query[300]; sprintf(query, "DELETE FROM %s", DBLOGNAME); return(mysql_query(connection, query)); } void print_stat(MYSQL *connection) { char query[300]; int state; MYSQL_RES *result1, *result2; MYSQL_ROW row; MYSQL mysql; //hack MYSQL *connection_hack; printf("Packet statistics:\n"); sprintf(query, "call mysql.Compute_packet_loss"); state = mysql_query(connection, query); if (state !=0) { printf("%s\n", mysql_error(connection)); } else { result1 = mysql_store_result(connection); if ((row=mysql_fetch_row(result1)) != NULL) { printf("Packet loss rate: %s\n", row[0]); } } mysql_free_result(result1); //////////////////////hack////////////////////// mysql_init(&mysql); connection_hack = mysql_real_connect(&mysql, HOST, USER, PASSWORD, INITDB,0,0,CLIENT_MULTI_RESULTS); //////////////////////////////////////////////// if (connection_hack != NULL) 10 { sprintf(query, "call mysql.Count_burst_errors"); state = mysql_query(connection_hack, query); if (state !=0) { printf("%s\n", mysql_error(connection_hack)); } //else state = mysql_query(connection_hack, query); { result2 = mysql_store_result(connection_hack); if ((row=mysql_fetch_row(result2)) != NULL) { printf("Burst errors count: %s\n", row[0]); printf("Average group length: %s\n", row[1]); printf("Burst group 1 count: %s\n", row[2]); printf("Burst group 2 count: %s\n", row[3]); printf("Burst group 3 count: %s\n", row[4]); } mysql_free_result(result2); } mysql_close(connection_hack); } else printf("%s\n", mysql_error(connection_hack)); } Пример вывода информации в консоль во время работы модуля на прием 11 Клиентская часть Клиентская часть представляет собой программу, которая работает на ПК пакеты которого могут достигать ПК с серверной частью, при условии, что известен его адрес. Модуль генерирует UDP трафик при помощи посылки пакетов со структурой, идентичной рассмотренной выше. Присутствует возможность регулирования скорости передачи, при помощи введения задержки в бесконечный цикл передачи пакетов, и размера пакета, что в сумме дает возможность оценить ошибки в канале при различных режимах передачи. В программе указывается локальный MySQL сервер куда идет запись информации о переданных пакетах и адрес получателя трафика, после чего начинается передача. По нажатию CTRL+C программа попадает в обработчик сигнала kill, где вызывается функция чтения символа с пользовательского ввода, подтверждающего, что нужно начинать передачу данных о отправленных пакетах в центральный MySQL сервер. Подтверждать начало этой транзакции надо только после того, как будет понятно, что сервер программы уже получил все пакеты, поскольку если это сделать раньше, то данные по приходу пакетов могут исказиться: системе получателя придется обрабатывать еще и TCP трафик запросов к базе данных. Код модуля-отправителя пакетов //SENDER #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include <stdio.h> <stdlib.h> <unistd.h> <sys/types.h> /* system data type definitions */ <sys/socket.h> /* socket specific definitions */ <netinet/in.h> /* INET constants and stuff */ <arpa/inet.h> /* IP address conversion stuff */ <netdb.h> <signal.h> <time.h> <sys/time.h> <mysql/mysql.h> <iostream> <string.h> <unistd.h> #define DEBUG //#define LOCALHOSTUSE /*constants for module*/ #define UDPHDRSIZE 8 #define SYSBUFSIZE 80000 //#define ENASYSBUF #define MAXBUF 4//1024*1024 #define SID 0 #define REMOTEHOST "192.168.1.6" #define REMOTEPORT 42001 #define FILELOGNAME "send_log.txt" #define UDELAY 2000 // delay between iterations in infinite loop for packet sending #define ENAUDELAY // enable to use delay in loop #define GARBAGELOAD 1200 #define ENAGARBAGE /*mysql credentials*/ #define HOST "localhost" #define USER "root" 12 #define PASSWORD "685702" #define INITDB "mysql" #define DBLOGNAME "send_log" /*mysql #define #define #define #define server credentials on receiver*/ SHOST "192.168.1.6" SUSER "root" SPASSWORD "685702" SINITDB "mysql" /*struct for filling into UDP packet data chunk*/ union payloadId { char cdata[MAXBUF]; unsigned long ldata; }; union wholeBuffer { struct head { payloadId Id; char garbageBuf[GARBAGELOAD - MAXBUF]; }head_buf; char buf[GARBAGELOAD]; }; /*help functions*/ void sigquit(int signal); int save2mysql(MYSQL *connection, unsigned long id, timeval current_time); int flushdblog(MYSQL *connection); void initBuffer(wholeBuffer * buffer); /*function to copy data about sent packets from local mysql server to receiver*/ void copy2server(MYSQL *connection); /*global variables*/ FILE * send_log; MYSQL *connection; int total_packets = 0; timeval start_time; timeval finish_time; int main( int argc, char **argv ) { int sk; struct sockaddr_in server; struct hostent *hp; char buf[MAXBUF]; int buf_len; int n_sent; int n_read; unsigned long current_id; timeval current_time; MYSQL mysql; payloadId Id; wholeBuffer wbufId; /*buffer initialization*/ initBuffer(&wbufId); printf("sender on %s\n", REMOTEHOST); 13 /*set system signals*/ signal(SIGQUIT, sigquit); signal(SIGHUP,sigquit); /* set function calls */ signal(SIGINT,sigquit); /*test socket connection*/ if ((sk = socket( PF_INET, SOCK_DGRAM, 0 )) < 0) { printf("Problem creating socket\n"); exit(1); } int cur_n; socklen_t cur_n_len; if (getsockopt(sk, SOL_SOCKET, SO_RCVBUF, &cur_n, &cur_n_len) == 1) { printf("Current system buffer length: %d.\n", cur_n); } else { printf("Problem in system query about system buffer length: %d.\n", cur_n); //exit(1); } #ifdef ENASYSBUF #ifndef ENAGARBAGE int n = SYSBUFSIZE * (UDPHDRSIZE + MAXBUF); #endif #ifdef ENAGARBAGE int n = SYSBUFSIZE * (UDPHDRSIZE + GARBAGELOAD); #endif if (setsockopt(sk, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) { printf("Problem in allocation system buffer with length: %d.\n", SYSBUFSIZE); exit(1); } else { printf("Current system buffer length: %d.\n", n); } #endif /*establish mysql connection*/ mysql_init(&mysql); connection = mysql_real_connect(&mysql, HOST, USER, PASSWORD, INITDB,0,0,0); if (connection == NULL) { printf("Problem with mysql connection: %s", mysql_error(&mysql)); exit(1); } flushdblog(connection); #ifdef DEBUG printf("%s\n", mysql_error(connection)); #endif /*create connection details*/ server.sin_family=AF_INET; server.sin_addr.s_addr=inet_addr(REMOTEHOST); /* establish the server port number - we must use network byte order! */ server.sin_port = htons(REMOTEPORT); /*infinite loop for sending packets to server*/ 14 current_id = SID; send_log = fopen(FILELOGNAME, "w+"); int i = 0; gettimeofday(&start_time, 0); while(1) { #ifndef ENAGARBAGE Id.ldata = current_id; gettimeofday(&current_time, 0); n_sent = sendto(sk, Id.cdata, sizeof(Id), 0, (struct sockaddr*) &server,sizeof(server)); printf("%s = %d, sec = %d, usec = %d\n", "send PacketId", Id.ldata, current_time.tv_sec, current_time.tv_usec); fprintf(send_log, "%s = %d, sec = %d, usec = %d\n", "send PacketId", Id.ldata, current_time.tv_sec, current_time.tv_usec); save2mysql(connection, Id.ldata, current_time); #endif #ifdef ENAGARBAGE wbufId.head_buf.Id.ldata = current_id; gettimeofday(&current_time, 0); n_sent = sendto(sk, wbufId.buf, sizeof(wbufId), 0, (struct sockaddr*) &server,sizeof(server)); printf("%s = %d, sec = %d, usec = %d\n", "send PacketId", wbufId.head_buf.Id.ldata, current_time.tv_sec, current_time.tv_usec); fprintf(send_log, "%s = %d, sec = %d, usec = %d\n", "send PacketId", wbufId.head_buf.Id.ldata, current_time.tv_sec, current_time.tv_usec); save2mysql(connection, wbufId.head_buf.Id.ldata, current_time); #endif #ifdef DEBUG printf("%s\n", mysql_error(connection)); #endif current_id++; #ifdef ENAUDELAY usleep(UDELAY); #endif i++; total_packets = i; } //copy2server(connection); fclose(send_log); mysql_close(connection); if (n_sent < 0) { perror("Problem sending data"); exit(1); } return(0); } void sigquit(int signal) { gettimeofday(&finish_time, 0); double total_speed =(((double)((double)total_packets*(GARBAGELOAD + UDPHDRSIZE)))/1000000) /(((double)finish_time.tv_sec + (double)(finish_time.tv_usec)/1000000) - ((double)start_time.tv_sec + (double)(start_time.tv_usec)/1000000)); printf("\nStop executing the sender\n"); printf("Total packets sent: %d\n", total_packets); printf("Total send speed: %f Mb/s\n", total_speed); if (send_log != NULL) fclose(send_log); if (connection != NULL) 15 { printf("Waiting for user answer. Do you want to save log to server?(y/n):\n"); #ifndef LOCALHOSTUSE char ready2start=0; scanf("%c", &ready2start); if (ready2start == 'y') { copy2server(connection); printf("Saving to server db\n"); } #endif mysql_close(connection); } exit(0); } int save2mysql(MYSQL *connection, unsigned long id, timeval current_time) { char query[300]; sprintf(query, "INSERT INTO %s (Id, Sec, Usec) VALUES(%lu, %lu, %lu)", DBLOGNAME, id, current_time.tv_sec, current_time.tv_usec); return(mysql_query(connection, query)); } int flushdblog(MYSQL *connection) { char query[300]; sprintf(query, "DELETE FROM %s", DBLOGNAME); return(mysql_query(connection, query)); } void initBuffer(wholeBuffer * buffer) { for(int i=0; i<GARBAGELOAD; i++) buffer->buf[i] = 7; } void copy2server(MYSQL *connection) { MYSQL smysql; MYSQL *sconnection; MYSQL_ROW row; MYSQL_RES *result; int state; mysql_init(&smysql); // подключение к базе на сервере sconnection = mysql_real_connect(&smysql, SHOST, SUSER, SPASSWORD, SINITDB,0,0,0); if (sconnection == NULL) { printf("Problem with mysql connection to server db: %s\n", mysql_error(&smysql)); exit(1); } flushdblog(sconnection);// очищаем базу на сервере char query[300], squery[300]; sprintf(query, "SELECT * FROM send_log"); // выбираем все с клиентской части state = mysql_query(connection, query); //начинаем запрос if (state !=0) { 16 printf("Problem in time of query to client db: %s\n", mysql_error(connection)); exit(1); } //printf("HERE\n"); result = mysql_store_result(connection); while ( ( row=mysql_fetch_row(result)) != NULL ) { //sprintf(squery, "INSERT INTO test_send_log (Id, Sec, Usec) VALUES(%lu, %lu, %lu)", row[0], row[1], row[2]); sprintf(squery, "INSERT INTO send_log (Id, Sec, Usec) VALUES('%s', '%s', '%s')", row[0], row[1], row[2]); mysql_query(sconnection, squery); //printf("Problem in time of query to client db: %s\n", mysql_error(sconnection)); } mysql_free_result(result); mysql_close(sconnection); } Пример вывода информации в консоль во время работы модуля на передачу(модуль находится в состоянии ожидания пользовательского ввода, подтверждающего, что все пакеты получены) Метод получения данных для построения графиков После окончания транзакции передачи данных об отправленных пакетах на сервере можно запустить скрипт для MySQL базы данных который вычисляет задержки по каждому полученному пакету: SELECT send.Id as 'sendId', recv.Id as 'recvId', recv.Sec - send.Sec as 'SDelta', 17 recv.Usec - send.Usec as 'UDelta', (recv.Sec+recv.Usec/1000000) (send.Sec+send.Usec/1000000) as 'Delta' FROM `mysql`.`send_log` send LEFT JOIN `mysql`.`recv_log` recv ON send.Id = recv.Id WHERE recv.Id IS NOT NULL Результат работы скрипта для подсчета задержек Подготовка стенда к проведению экспериментов В процессе проведения экспериментов мы столкнулись с проблемой, что в графе Delta стали появляться отрицательные числа, которые говорят о том, что пакет отправлен позже, чем пришел к получателю, что является следствием рассинхронизации системных часов на двух ПК стенда. Проблема была решена при помощи синхронизации обоих ПК перед проведением экспериментов с глобальными NTP серверами. 18 Статистика по серверам которые есть в списке Выбираем один из них и даем команду для синхронизации двух ПК стенда с ним 19 Существующие системы передачи видео реального времени: интернет телевидение Существует множество научных статей, публикаций и протоколов для построения такого рода систем, однако в этой главе мы попытаемся изучить существующие системы «изнутри». Естественно, что, к примеру, новостные порталы онлайн телевидения не выставляют на всеобщее обобщение протоколы, которыми они пользуются для передачи видеоинформации, но с помощью пакетов, которые приходят на компьютер, мы попытаемся собрать некоторую информацию. Ресурс http://delicast.com/tv/Portugal/news/Porto_Canal Запускаем Wireshark и одновременно с ним просмотр потокового видео, доступного по данной ссылке и получаем следующую картину: Здесь видно, что данные идут в пакетах TCP и RTCP, который так же основан на TCP. Черными полосками помечены пакеты для, которых была осуществлена повторная передача. Такая картина натолкнула нас на идею померить отношение повторно переданных пакетов к 20 общему числу пакетов. Это можно сделать, если в Wireshark наложить следующий фильтра на входной трафик: ip.src == 195.245.168.21 and tcp.analysis.retransmission. Получаем 1,6% потерянных или поврежденных пакетов, для которых потребовалась повторная передача. Аналогичная ситуация с передачей на TCP наблюдается и на ресурсе youtube.com и на сайтах транслирующих телеканалы в интернет. Такой подход, например, для передачи новостей предполагает, что пользователь ждет пока у него буферизуется достаточный кусок для проигрывания видео и затем при просмотре испытывает дискомфорт от того, что видео периодически повисает или картинка пропадает вовсе. Таким же образом обстоит дело и с сервисами прослушивания online музыки, поскольку воспроизведение постоянно прерывается. 21 Достоинства и недостатки передачи видео на UDP После проведения практических экспериментов и тестирования работы существующих систем, мы попытаемся собрать все факты о преимуществах и недостатках передачи видеоданных в пакетах, вложенных в UDP. UDP протокол не осуществляет никакого контроля за доставкой вообще и доставкой пакетов в целостности, что препятствует его применению при передаче ответственных данных, для которых важен каждый бит. Теоретически для передачи потоковой информации он намного более применим, чем TCP, поскольку имеет большую скорость передачи, меньший оверхед и не создаются ситуация, когда программа ждет один потерявшийся пакет, который надо было уже воспроизвести даже при наличии в буфере последующих пакетов. С другой стороны найденная информация говорит о том, что передача UDP на высокой скорости может быть проблематичной, поскольку в нем не присутствует контроль потока(flow control), нет распознавания потери пакетов, нет встроенной возможности понять в каком порядке идут пакеты. Более того как показано было большое количество сетевого оборудования и IP стеков ОС по умолчанию отбрасывают часть пакетов, если уже нет возможности их буферизовать. Таким образом, самое пристально внимание при передаче видео по UDP надо уделять таким параметрам как размер пакета, время задержки и размеры буферов, как на клиенте, так и усредненный буфер, применяемый на сетевом устройстве. Варианты минимизации недостатков UDP После сравнения TCP и UDP встает очевидный вопрос: «Почему бы не использовать преимущества обоих для создания нового протокола?». Совершенно очевидно, что в таком протоколе должна быть возможность отказаться от получения пакета, если он не пришел в нужное время. Причем в идеальном случае протокол должен учитывать особенность видеоинформации – избыточность. Например, можно передавать базовые кадры с избыточностью по TCP, причем система может определять в какое время их лучше передать, поскольку условия в канале связи могут менять в зависимости от времени. Такие возможности и содержатся в адаптивных протоколах передачи видео. Однако даже если использовать такую схему и возлагать всю ответственную передачу на TCP, а промежуточные пакеты (логично, что их большинство) передавать по UDP, не решается проблема со статусом UDP трафика в промежуточных узлах сети интернет. В такой ситуации выходом может служить использование IPv6, где в каждый пакет встраивается меткаидентификатор потока, но на данный момент эта версия IP протокола распространена мало и вопрос о прохождении такого трафика через интернет остается открытым. 22 Адаптивные протоколы для передачи видео Как видно из экспериментов и теоретических данных качественная передача видеоинформации в режиме реального времени зависит от многих внешних факторов (задержка, потери ит.д.), так и от внутренних факторов (системный буфер, ресурсы ПК ит.д.). Значит, протокол для передачи видео должен не только обладать качествами, которые мы упомянули в предыдущей главе, но и уметь автоматически подстраивать параметры передачи под те условия, которые справедливы для данного момента. Это свойство у протоколов называется адаптивностью. A Reliable, Adaptive Network Protocol for Video Transport Статья [A Reliable, Adaptive Network Protocol for Video Transport, 1] посвящена разработке протокола под названием «Управление потоком на основе баллов», который предусматривает систему контроля переполнения буфера на каком-либо узле в сети. Для этого клиент и сервер периодически обмениваются двумя параметрами: количество отправленных пакетов и оценка баланса, которая показывает доступность буфера для потока. Этот протокол так же динамически может менять информационную нагрузку на канал, изменяя нагрузку каждого пакета, при неизменной на некотором интервале времени частоты отправки. Таким образом, конечной целью протокола является минимизировать размер буфера и время задержки при передаче, а так же гарантировать, что все (это кажется фантастикой – вероятно, имелся в виду минимум потерь) пакеты будут доставлены. Adaptive delivery of real-time streaming video Статья [Adaptive delivery of real-time streaming video, 2] также посвящена построению системы передачи потокового видео на основе адаптивного протокола. В этой статье в большей мере делается упор на особенности передаваемого видео: избирательная надежность в доставке, подстройка битрейта(качество видео) потока под текущие условия передачи. В статье также подтверждается, что наибольшей проблемой для потерь и задержек в интернете является буферизация потока и для решения этой проблемы автор использует отправление отчетов серверу в виде RTCP пакетов в на основе которых сервер считает потери и задержки. Такая информация помогает подстроить скорость передачи в соответствии с алгоритмом используемым в Congestion manager(CM) framework[3]. Для подавления джитера в доставке пакетов система использует настраиваемый буфер на приемнике. 23 По рисунку видно, что обратная связь осуществляется по протоколу RTCP, а доставка по модифицированному протоколу SR-RTP с избирательной надежностью передачи. Выводы Мы провели работу по подбору материала для изучения методов организации систем потоковой передачи видео в режиме реального времени. Ознакомились с основами кодирования видео, и привели информацию о существующих системах, работу которых можно ощутить, пройдя по ссылкам, указанным в последнем разделе. Был спроектирован и запущен стенд для экспериментов по передаче видео и выявления проблем, которые могут возникнуть в таких системах. Судя по результатам экспериментов на стенде и данным из освященных выше статей основная сложность состоит в динамическом изменения всех параметров, которые влияют на процесс передачи видео. Может оказаться выгодным применение каких-либо оптимизирующих алгоритмов, которые будут сами решать задачу о подборе параметров, руководствуюсь определенными соотношениями. Очевидно, то при проектировании протокола нужно исходить из наличия обратной связи между приемником и передатчиком для адаптации передачи под текущие условия. Вопрос о том каким образом передавать большую часть видеоинформации, рассмотренный в разделе «Варианты минимизации недостатков UDP» остается открытым, поскольку надо четко определиться с требованиями к проектируемой системе. Например, можно ли использовать IPv6? С другой стороны при использовании этого протокола нужно проанализировать, как с ним работают сетевые устройства, с учетом того, что протокол относительно новый. 24 Использованные источники информации 1. A Reliable, Adaptive Network Protocol for Video Transport, 1995, MITSUBISHI ELECTRIC RESEARCH LABORATORIES[http://nms.lcs.mit.edu/software/videocm/] 2. Adaptive delivery of real-time streaming video, 2001, Massachusetts institute of technology 3. http://nms.lcs.mit.edu/software/videocm/ 4. Video Streaming: Concepts, Algorithms, and Systems, 2002, Mobile and Media Systems Laboratory HP Laboratories Palo Alto 5. http://www.29west.com/docs/THPM/udp-buffer-sizing.html 6. http://www.webhostingtalk.com/archive/index.php/t-626295.html 7. http://www.videolan.org/vlc/streaming.html 25