Разработка прикладного протокола передачи речи в реальном времени
ОГЛАВЛЕНИЕ
Цель
курсового проекта
Задание на
курсовое проектирование
Вариант
задания
Анализ задачи
Снимок экрана
работающей программы
Алгоритмы
работы функций в виде блок-схемдиаграммы
Листинг
программы
Список
использованной литературы
Цель курсового проекта
Разработка протокола верхнего уровня согласно требованиям на курсовое
проектирование.
Задание на курсовое проектирование
1. Разработать структуру протокола согласно функциям (см. табл.1):
1.1. Определить функции протокола и структуру пакета разрабатываемого
протокола;
1.2. Описать поля заголовка разрабатываемого протокола, рассчитать
необходимую длину полей заголовка;
.3. Рассчитать необходимую длину буфера на приеме в зависимости от
длины пакета и максимально допустимой задержки.
2. Разработать алгоритмы обработки данных на приеме и передаче и
представить их блок-схемы.
3. Разработать программную реализацию протокола:
3.1. Описать разработанные функции, их назначение и структуру;
3.2. Скомпилировать файл , протестировать на реальной сети.
Вариант задания
Табл.1
А
|
Б
|
тип трафика
|
значение поля дополнительной информации
|
число посылок пакетов
|
длина поля данных пакета, байт
|
задержка воспроизведения, пак.
|
2
|
3
|
Real-time
|
Символьное с названием кодека
|
1
|
128
|
6
|
Анализ задачи
Основное отличие трафика «реального времени» отражено в его названии и
заключается в жестких требованиях к задержкам и их вариации (джиттеру), но
одновременно возможны потери некоторого количества пакетов, что связано с
особенностями восприятия речи и изображения человеком и с созданием сложных
алгоритмов кодирования, позволяющих восстанавливать часть информации.
Для данного типа трафика хорошо подошёл протокол UDP, не гарантирующий доставки пакетов, но не вносящий
дополнительных задержек на повторную передачу, как TCP. Но UDP не
решал проблему джиттера. Можно было бы создать новый протокол транспортного
уровня специально для передачи трафика реального времени, но многоуровневый
подход диктует другое решение. Проблему обеспечения качества обслуживания для
трафика реального времени решают специальные протоколы прикладного уровня,
такие как RTP и RTCP.
Чтобы передать данные, необходимо упаковать их в пакеты прикладного
уровня. В этих пакетах кроме поля с данными, содержащими часть передаваемого
файла, должны содержаться дополнительные поля со следующей информацией:
) название кодека
) номер пакета
Поле данных в пакетах имеет фиксированный размер и содержит строго
определенную длительность аудиоданных. Для выполнения этого условия необходимо
использовать голосовой кодек с постоянной скоростью кодирования речевого
сигнала.
В программе используется кодек, реализующий импульсно-кодовую модуляцию с
параметрами:
Частота дискретизации: 8кГц
Точность отсчетов - 8бит
Число каналов - 1 (моно)
На приемнике создается циклический буфер (джиттер-буфер). В этот буфер
помещаются принимаемые аудиоданные. Через определенные промежутки времени,
равные длительности воспроизведения одной ячейки буфера, из него считывается
новый блок данных и воспроизводится. Между записью блока и его отправкой на
воспроизведение вносится задержка. Величина этой задержки должна обеспечивать
подавление эффекта джиттера. В таком случае даже если пришедшие из сети пакеты
записываются в буфер через случайные моменты времени, воспроизводятся эти
данные через равные промежутки времени.
Рис.1. Организация джиттер-буфера
Так как для передачи трафика используется протокол транспортного уровня UDP, пакеты могут передаваться по сети
от источника к получателю по разным маршрутам. В некоторых случаях это может
приводить к тому, что пакеты будут приходить не только через разные промежутки
времени, но и в неправильном порядке. Чтобы предусмотреть такие ситуации в
структуру пакета нужно ввести поле с номером пакета.
При передаче пакеты нумеруются по заданному модулю, а на приеме
анализируется номер принятого пакета. Если приходит пакет, номер которого
меньше или равен номеру предыдущего пакета, то такой пакет отбрасывается, иначе
- отправляется на воспроизведение.
Помимо этого, в структуру пакета можно ввести и другие дополнительные
поля для расширения функциональности протокола. Как уже отмечалось выше, в этом
варианте в качестве дополнительного поля используется имя отправителя, которое
задается на передающей стороне.
Структура пакета
В каждом пакете необходимо передавать его номер, имя отправителя и
непосредственно аудиоданные.
Название кодека
|
Номер пакета
|
Аудиоданные
|
Рис.1.3 Формат пакета приложения передачи речи в реальном времени
Под название кодека выделяется 8 байт, под номер пакета - 4 байта (одна
переменная целого типа (int)). Для поля аудиоданных необходимо выделить 128
байт.
Формат пакета задаётся структурой Sound:
struct Sound
{char CodecName[NAMESIZE];countSend;SoundData[BUFFERLEN];
};
Здесь символьные массивы CodecName
и SoundData содержат соответственно
информацию полей названия кодека и аудиоданных.
Размер строки названия кодека и аудиоданных задаётся через константы NAMESIZE и BUFFERLEN, что сделано для удобства
использования этих параметров в других частях программы.
Целочисленная переменная countSend содержит информацию поля номера пакета.
При размере поля аудиоданных 128 байт при кодировании сигнала 8-битными
отсчетами с частотой 8 кГц получаем длительность звучания одного блока
аудиоданных = 128*(1/8000) =0.016c = 16
мс.
Зная объем памяти, выделяемый под переменные каждого типа можно
определить размер пакета. На переменные типа int выделяется по 4 байта, на
переменные типа char - по одному байту. В итоге размер заголовка вместе с полем
аудио данных составит: 8*1+4*1+128=140 байт. Размер пакета с учетом заголовков
транспортного, сетевого и канального уровней составит:140+42=182 байта.
Размер буфера приемника.
Между записью блока и его отправкой на воспроизведение вносится задержка,
величина которой должна обеспечивать подавление эффекта джиттера. В этом случае
даже если пришедшие из сети пакеты записываются в буфер через случайные моменты
времени, а воспроизводятся через равные, если выбрать размер буфера около 100
мс, то проявление эффекта джиттера маловероятно, а вносимая задержка для вещательного
типа трафика не критична.
В этом случае размер джиттер-буфера должен быть 6*8=48 байт (6 ячеек по
16 мс, 96мс). В этом случае суммарная задержка, вносимая пакетизацией и
джиттер-буфером, составит 16+16*6=112 мс.
Снимок экрана работающей программы
Рис.3. Функция создания и настройки сокета void
myAudioInput::initSocket()
Рис.4. Функция формирования и передачи пакетов void
myAudioInput::SendRecord()
Рис.5. Функция приёма пакетов int myAudioInput::readPendingDatagrams()
Рис.6. Запуск программы
SDL-диаграммы
Листинг программы программы
.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "myaudioinput.h"Ui {MainWindow;
}
RcvThread: public QThread
{_OBJECT:();ConnectSingleShot();run();*Timer2Play;*ptr_mAudioInput;*StateStoped;*SingleshotActive;
};
MainWindow : public QMainWindow
{_OBJECT:MainWindow(QWidget *parent = 0);
~MainWindow();StateStoped;SingleshotActive;CreateThread();
slots:on_pushButton_clicked();
on_pushButton_2_clicked();
on_DstlineEdit_editingFinished();
:::MainWindow
*ui;myinputaudio;mRcvThread;<QHostAddress> *ptrHostlist;
};
#endif // MAINWINDOW_H
//////////////////////////////////////.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
::MainWindow(QWidget *parent) :(parent),(new Ui::MainWindow)
{>setupUi(this);= true;>myinputaudio.CodecLineEdit =
ui->CodecLineEdit;>myinputaudio.RcvPckLineEdit =
ui->RcvPckLineEdit;>myinputaudio.SendPktLineEdit =
ui->SndPktlineEdit;>myinputaudio.stateLabel =
ui->stateLabel;>mRcvThread.ptr_mAudioInput =
&myinputaudio;>myinputaudio.Timer2Play =
this->mRcvThread.Timer2Play;>myinputaudio.StateStoped = &StateStoped;>mRcvThread.StateStoped
= &StateStoped;>myinputaudio.SingleshotActive =
&SingleshotActive;>mRcvThread.SingleshotActive = &SingleshotActive;=
false;>myinputaudio.tohost.setAddress(ui->DstlineEdit->text());
}
::~MainWindow()
{.deleteLater();.terminate();.wait(1);ui;
("MainWindow Desctructor done!");
}
MainWindow::on_pushButton_clicked()
{(StateStoped==false) return;(myinputaudio.tohost.isNull())
{ui->stateLabel->setText("Ошибка! Введите
адрес приемника!");;}>stateLabel->clear();.ConnectSingleShot();.Record();=
false;
}
MainWindow::on_pushButton_2_clicked()
{(StateStoped==true) return;.closeSocket();
}
MainWindow::on_DstlineEdit_editingFinished()
{(StateStoped)
myinputaudio.tohost.setAddress(ui->DstlineEdit->text());
}
MainWindow::CreateThread()
{.start();
}
::RcvThread()
{Play = new QTimer;Play->setTimerType(Qt::PreciseTimer);
}RcvThread::run()
{*ptrStateStoped = StateStoped;(1)(*ptrStateStoped ==
false)_mAudioInput->readPendingDatagrams();
}
RcvThread::ConnectSingleShot()
{(Timer2Play,SIGNAL(timeout()),>ptr_mAudioInput,SLOT(startTimer2()));
}
////////////////////////////////////
.h
#ifndef MYAUDIOINPUT_H
#define MYAUDIOINPUT_H
#include <QObject>
#include <QAudioInput>
#include <QAudioOutput>
#include <QDebug>
#include <QFile>
#include <QTimer>
#include <QBuffer>
#include <QtEndian>
#include <QIODevice>
#include <QList>
#include <QThread>
#include <QNetworkInterface>
#include "ui_mainwindow.h"
#define STARTTIMER2 1
#define CODECNAME "PCM"
#define NAMESIZE 8
#define SIZESOUND 140
#define JITTERLEN 6
#define PKTDELAY 16 //delay between sending packets in ms
milliseconds
#define FREQPCM 0.000125 //frequncy of pcm codec // 1/8000
#define BUFFERLEN 128 // size of jitterbuffer in bytes , i
choose 128byte // (PKTDELAY/FREQPCM)/1000
#define PLAYDELAY 80 //delay between playing rcvd packet in
ms // PKTDELAY*JITTERLEN-PKTDELAY
// считается 96-16 = 80, так как мы используем два таймера. один
запускает 80мс
// после чего запускается основной таймер2 на 16мс. так как воспроизводим
только после 2 таймера,
struct Sound
{CodecName[NAMESIZE];countSend;SoundData[BUFFERLEN];
};myAudioInput : public QObject
{_OBJECT:myAudioInput(QObject *parent = 0);
~myAudioInput();Record();closeSocket();tohost;*StateStoped;*SingleshotActive;*CodecLineEdit;*RcvPckLineEdit;*SendPktLineEdit;*stateLabel;*audio;
// need to cut to audioOutput class*Timer2Play;
readPendingDatagrams();
:
slots:handleStateChanged(QAudio::State
newState);stopRecording();notified();SendRecord(); //made it
publicplaySoundPkt(); //play sound block from
jitterbufferOutnotified();InitPlay(); //need to cut to audiOutput
classOuthandleStateChanged(QAudio::State newState);startTimer2();:format;
//need to copy yo audioOutput class*myaudio;*m_input;*myrecord;mSound; //my
protocol packet*audiobuf;countSend;*Timer1Snd;byteready;
*out_input;* ptrSound;*m_udpSocket;*datagram; //for
receiveng*outarray;PlayBuf[BUFFERLEN];jitterQueue[JITTERLEN][BUFFERLEN];<QByteArray>
jitterbuf;countRcv;fst; //pointer to first packet in jitterqueuwlst; //pointer
to last packet in
jitterqueue:initSocket();slots:socketStateHandle(QAbstractSocket::SocketState);In(char
*Buf);
};
#endif // MYAUDIOINPUT_H
//////////////////////////////////////.cpp
#include "myaudioinput.h"
::myAudioInput(QObject *parent) :(parent)
{= new QByteArray;>resize(SIZESOUND);_input = 0; //
iodevice initialize 0_udpSocket = new QUdpSocket(this);Snd = new
QTimer;Snd->setTimerType(Qt::PreciseTimer);= 0;= 0;(int
i=0;i<NAMESIZE;i++).CodecName[i] = 0;(int i=0;i<JITTERLEN;i++)(int
j=0;j<BUFFERLEN;j++)
{ jitterQueue[i][j]=0;.SoundData[j]=0;
}
(mSound.CodecName,CODECNAME);=
&mSound;(Timer1Snd,SIGNAL(timeout()),this,SLOT(SendRecord());(m_udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),,SLOT(socketStateHandle(QAbstractSocket::SocketState)));
// Set up the desired format, for
example:.setSampleRate(8000);.setChannelCount(1);.setSampleSize(8);.setCodec("audio/pcm");.setByteOrder(QAudioFormat::BigEndian);.setSampleType(QAudioFormat::UnSignedInt);
info = QAudioDeviceInfo::defaultInputDevice();(!info.isFormatSupported(format))
{() << "Default format not supported, trying to use the
nearest.";= info.nearestFormat(format);
}()<<"device input
name"<<info.deviceName();= new QAudioInput(format,
this);(myaudio,SIGNAL(notify()),this,SLOT(notified())); //stat(myaudio,
SIGNAL(stateChanged(QAudio::State)), this,
SLOT(handleStateChanged(QAudio::State)));()<<"NotifyInterval
="<<myaudio->notifyInterval();()<<"Supported
codecs::"<<info.supportedCodecs();>InitPlay();()<<"BUFFERSIZE
of transmitter = "<<myaudio->bufferSize();()<<"Transmitter
configured";
}
/*
* Functions that need cut to Output class
*/myAudioInput::InitPlay()
{= new QByteArray;_input = 0; //for
playinginfo(QAudioDeviceInfo::defaultOutputDevice());(!info.isFormatSupported(format))
{() << "Raw audio format not supported by backend, cannot play
audio.";;
}()<<"device output
name"<<info.deviceName();= new QAudioOutput(format,
this);(audio,SIGNAL(notify()),this,SLOT(Outnotified()));(audio,
SIGNAL(stateChanged(QAudio::State)), this, SLOT(OuthandleStateChanged(QAudio::State)));()<<"BUFFERSIZE
of receiver = "<<audio->bufferSize();()<<"Receiver
configured";
}
myAudioInput::OuthandleStateChanged(QAudio::State newState)
{(newState) {QAudio::IdleState:
// Finished playing (no more data)
*StateStoped = false;
//qDebug("Playing done!");;
QAudio::StoppedState:
// Stopped for other reasons(audio->error() !=
QAudio::NoError) {
// Error handling
}*StateStoped = true;;
QAudio::ActiveState:
*StateStoped = false;;QAudio::SuspendedState:("The audio
device is in a suspended state, this state will only be entered after suspend()
is called.");;
:
// ... other cases as appropriate;
}
}
myAudioInput::Outnotified()
{()<<"output state
is"<<audio->state();()<<"buffersize
="<<audio->bufferSize();() << "OutbytesFree = "
<< audio->bytesFree()
<< ", " << "OutelapsedUSecs =
" << audio->elapsedUSecs()
<< ", " << "OutprocessedUSecs =
"<< audio->processedUSecs();
}
::~myAudioInput()
{>stop();>disconnect();>stop();>disconnect();myaudio;=NULL;myrecord;datagram;=NULL;outarray;=NULL;Timer1Snd;_udpSocket->disconnect();_udpSocket->disconnectFromHost();_udpSocket->close();m_udpSocket;_udpSocket=NULL;("MyAudioInput
Destructor done");
}
myAudioInput::Record()
{(myaudio->state()!=QAudio::StoppedState) return; //check
that audio is stopped=1;= 0;>initSocket();_input = audio->start();
//start receive and playing packets_input = myaudio->start(); //start
transmitter and recordingSnd->start(16); //timer for 32ms for reading from
microphone and sending("Started connection!");
{(newState) {QAudio::StoppedState:(myaudio->error() !=
QAudio::NoError) {("Error in recording!");
// Error handling
} else {("Record finished");
};
QAudio::ActiveState:
;
:;
}
}myAudioInput::stopRecording()
{>stop();
}
myAudioInput::notified()
{
() << "bytesReady = " <<
myaudio->bytesReady()
<< ", " << "elapsedUSecs = "
<<myaudio->elapsedUSecs()
<< ", " << "processedUSecs =
"<<myaudio->processedUSecs();()<<"fst="<<fst<<"
lst="<<lst<<"rcv ="<<countRcv<<"
snd="<<countSend;>CodecLineEdit->setText(ptrSound->CodecName);>RcvPckLineEdit->setText(QString::number(countRcv));>SendPktLineEdit->setText(QString::number(countSend));
}
myAudioInput::initSocket()
{_udpSocket->bind(7755); //listen in any
interface("bind");
}
myAudioInput::SendRecord()
{l;= myaudio->bytesReady(); //check for debug, how much
bytes is ready to read= m_input->read(mSound.SoundData, BUFFERLEN); //send
it like len(l <=0) return;
//fill Sound struct header and payload.countSend =
countSend;bufdata((char*)&mSound,SIZESOUND);
//((char*)&mSound,SIZESOUND);++; //increase count of Senden
packets_udpSocket->writeDatagram(bufdata,bufdata.size(),tohost,7755); //send
len + num of packet(1byte)
//
}
myAudioInput::readPendingDatagrams()
{l;sender; //need for debug, or for NAT bypasssenderPort;
//need for debug, or for NAT bypass=
m_udpSocket->readDatagram(datagram->data(),>size(),&sender,
&senderPort);(l<=0) return 0;= (Sound*) datagram->data();prevcount = countRcv;=
ptrSound->countSend;(countRcv>prevcount)In(ptrSound->SoundData);return
0;
}
myAudioInput::closeSocket()
{(myaudio->state()==QAudio::StoppedState) return; //check
that audio is not stopped
*StateStoped=true;>stop();>stop();Snd->stop();Play->stop();Play->disconnect();_udpSocket->abort();("Socket
disconnected");("Recording and playing stopped");
}
myAudioInput:: socketStateHandle(QAbstractSocket::SocketState
newState)
{()<<"ATTENTION: socket changed
state:"<<newState;
}
myAudioInput::In(char* Buf)
{(jitterQueue[lst],Buf,BUFFERLEN);=
(lst+1)%JITTERLEN;(!Timer2Play->isActive() &&
*SingleshotActive==false)
{Play->setSingleShot(true);Play->start(PLAYDELAY);
*SingleshotActive = true;STARTTIMER2;
}return 0;
}
myAudioInput::playSoundPkt()
{(PlayBuf,jitterQueue[(fst+1)%JITTERLEN],BUFFERLEN);= (fst+1)
%JITTERLEN;l= out_input->write(PlayBuf,BUFFERLEN);
}myAudioInput::startTimer2()
{Play->disconnect();
*SingleshotActive =
false;Play->setSingleShot(false);(Timer2Play,SIGNAL(timeout()),this,SLOT(playSoundPkt()));Play->start(PKTDELAY);
}
///////////////////////////////////////
.cpp
#include "mainwindow.h"
#include <QApplication>
#include <myaudioinput.h>
main(int argc, char *argv[])
{a(argc, argv);w;.show();.CreateThread();a.exec();
}
протокол
буфер прием передача
Список использованной литературы
1. Симонина
О. А., Глазунов А. С., Четвертухин В. Г. Сети ЭВМ и телекоммуникации:
методические указания к курсовому проектированию (спец. 202000, 220400) /
СПбГУТ. СПб, 2004.
. Остерлох,
Хизер. TCP/IP. Семейство протоколов передачи данных в сетях компьютеров / Пер.
с англ. М.: ДиаСофтЮП, 2002.
3. Камер,
Дуглас Э. Сети TCP/IP. Принципы, протоколы и структура /
Пер. с англ. М.: Издательский дом «Вильямс», 2003. Т.1.
. Олифер
В. Г., Олифер Н. А. Компьютерные сети. Принципы, технологии, протоколы. СПб:
Питер, 2002.
. Гольдштейн
Б.С., Пинчук А.В., Суховицкий А.Л. IP-Телефония. М.: Радио и связь, 2001.
. Пол
А. Объектно-ориентированное программирование на C++ /Пер с англ СПб: Невский диалект - Издательство БИНОМ,
1999.
. Штерн
В. Основы C++: Методы программной инженерии. М.:
Лори, 2003.