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