QAudioInput from array - c++

I have a device with microphones that connects to my computer through Ethernet and it cannot be seen by Qt as an audio device, so, I get packets from it and put them to QByteArray. I need to play these packets from stream. Somewhere in the Internet I found a solution to almost the same problem, but there internal microphone was used.
#include <QApplication>
#include <iostream>
#include <cassert>
#include <QCoreApplication>
#include <QAudioInput>
#include <QAudioOutput>
#include <QBuffer>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
QBuffer rdBuff;
QBuffer wrBuff;
wrBuff.open(QBuffer::WriteOnly);
rdBuff.open(QBuffer::ReadOnly);
QObject::connect(&wrBuff, &QIODevice::bytesWritten, [&wrBuff, &rdBuff](qint64)
{
rdBuff.buffer().remove(0, rdBuff.pos());
// set pointer to the beginning of the unread data
const auto res = rdBuff.seek(0);
assert(res);
// write new data
rdBuff.buffer().append(wrBuff.buffer());
// remove all data that was already written
wrBuff.buffer().clear();
wrBuff.seek(0);
});
const auto decideAudioFormat = [](const QAudioDeviceInfo& devInfo)
{
QAudioFormat format;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if (devInfo.isFormatSupported(format))
{
return format;
}
else
{
std::cerr << "Raw audio format not supported by backend, cannot play audio.\n";
throw 0;
}
};
QAudioInput audioInput(decideAudioFormat(QAudioDeviceInfo::defaultInputDevice()));
QAudioOutput audioOutput(decideAudioFormat(QAudioDeviceInfo::defaultOutputDevice()));
audioInput.start(&wrBuff);
audioOutput.start(&rdBuff);
return app.exec();
}
It works quite well, but I need to set QByteArray as QAudioInput's source.
Is there any possible solution?

Not sure if i'm directly answering your question. But a possible solution is feed the output audio device manually (push mode) when new data comes.
You can also use a custom (QFile inherited) class to record sound, and when sound come, feeds both the file and output audio device.
Here is a example:
AudioOutput.h:
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H
#include <QtCore>
#include <QtMultimedia>
#define MAX_BUFFERED_TIME 10*1000
static inline int timeToSize(int ms, const QAudioFormat &format)
{
return ((format.channelCount() * (format.sampleSize() / 8) * format.sampleRate()) * ms / 1000);
}
class AudioOutput : public QObject
{
Q_OBJECT
public:
explicit AudioOutput(QObject *parent = nullptr);
public slots:
bool start(const QAudioDeviceInfo &devinfo,
const QAudioFormat &format,
int time_to_buffer);
void write(const QByteArray &data);
private slots:
void verifyBuffer();
void preplay();
void play();
private:
bool m_initialized;
QAudioOutput *m_audio_output;
QIODevice *m_device;
QByteArray m_buffer;
bool m_buffer_requested;
bool m_play_called;
int m_size_to_buffer;
int m_time_to_buffer;
int m_max_size_to_buffer;
QAudioFormat m_format;
};
#endif // AUDIOOUTPUT_H
AudioRecorder.h:
#ifndef AUDIORECORDER_H
#define AUDIORECORDER_H
#include <QtCore>
#include <QtMultimedia>
class AudioRecorder : public QFile
{
Q_OBJECT
public:
explicit AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent = nullptr);
~AudioRecorder();
using QFile::open;
public slots:
bool open();
qint64 write(const QByteArray &data);
void close();
private:
void writeHeader();
bool hasSupportedFormat();
QAudioFormat format;
};
#endif // AUDIORECORDER_H
AudioOutput.cpp:
#include "audiooutput.h"
AudioOutput::AudioOutput(QObject *parent) : QObject(parent)
{
m_initialized = false;
m_audio_output = nullptr;
m_device = nullptr;
m_buffer_requested = true;
m_play_called = false;
m_size_to_buffer = 0;
m_time_to_buffer = 0;
m_max_size_to_buffer = 0;
}
bool AudioOutput::start(const QAudioDeviceInfo &devinfo,
const QAudioFormat &format,
int time_to_buffer)
{
if (!devinfo.isFormatSupported(format))
{
qDebug() << "Format not supported by output device";
return m_initialized;
}
m_format = format;
int internal_buffer_size;
//Adjust internal buffer size
if (format.sampleRate() >= 44100)
internal_buffer_size = (1024 * 10) * format.channelCount();
else if (format.sampleRate() >= 24000)
internal_buffer_size = (1024 * 6) * format.channelCount();
else
internal_buffer_size = (1024 * 4) * format.channelCount();
//Initialize the audio output device
m_audio_output = new QAudioOutput(devinfo, format, this);
//Increase the buffer size to enable higher sample rates
m_audio_output->setBufferSize(internal_buffer_size);
m_time_to_buffer = time_to_buffer;
//Compute the size in bytes to be buffered based on the current format
m_size_to_buffer = timeToSize(m_time_to_buffer, m_format);
//Define a highest size that the buffer are allowed to have in the given time
//This value is used to discard too old buffered data
m_max_size_to_buffer = m_size_to_buffer + timeToSize(MAX_BUFFERED_TIME, m_format);
m_device = m_audio_output->start();
if (!m_device)
{
qDebug() << "Failed to open output audio device";
return m_initialized;
}
//Timer that helps to keep playing data while it's available on the internal buffer
QTimer *timer_play = new QTimer(this);
timer_play->setTimerType(Qt::PreciseTimer);
connect(timer_play, &QTimer::timeout, this, &AudioOutput::preplay);
timer_play->start(10);
//Timer that checks for too old data in the buffer
QTimer *timer_verifier = new QTimer(this);
connect(timer_verifier, &QTimer::timeout, this, &AudioOutput::verifyBuffer);
timer_verifier->start(qMax(m_time_to_buffer, 10));
m_initialized = true;
return m_initialized;
}
void AudioOutput::verifyBuffer()
{
if (m_buffer.size() >= m_max_size_to_buffer)
m_buffer.clear();
}
void AudioOutput::write(const QByteArray &data)
{
m_buffer.append(data);
preplay();
}
void AudioOutput::preplay()
{
if (!m_initialized)
return;
//Verify if exists a pending call to play function
//If not, call the play function async
if (!m_play_called)
{
m_play_called = true;
QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
}
}
void AudioOutput::play()
{
//Set that last async call was triggered
m_play_called = false;
if (m_buffer.isEmpty())
{
//If data is empty set that nothing should be played
//until the buffer has at least the minimum buffered size already set
m_buffer_requested = true;
return;
}
else if (m_buffer.size() < m_size_to_buffer)
{
//If buffer doesn't contains enough data,
//check if exists a already flag telling that the buffer comes
//from a empty state and should not play anything until have the minimum data size
if (m_buffer_requested)
return;
}
else
{
//Buffer is ready and data can be played
m_buffer_requested = false;
}
int readlen = m_audio_output->periodSize();
int chunks = m_audio_output->bytesFree() / readlen;
//Play data while it's available in the output device
while (chunks)
{
//Get chunk from the buffer
QByteArray samples = m_buffer.mid(0, readlen);
int len = samples.size();
m_buffer.remove(0, len);
//Write data to the output device
if (len)
m_device->write(samples);
//If chunk is smaller than the output chunk size, exit loop
if (len != readlen)
break;
//Decrease the available number of chunks
chunks--;
}
}
AudioRecorder.cpp:
#include "audiorecorder.h"
AudioRecorder::AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent) : QFile(name, parent), format(format)
{
}
AudioRecorder::~AudioRecorder()
{
if (!isOpen())
return;
close();
}
bool AudioRecorder::hasSupportedFormat()
{
return (format.sampleSize() == 8
&& format.sampleType() == QAudioFormat::UnSignedInt)
|| (format.sampleSize() > 8
&& format.sampleType() == QAudioFormat::SignedInt
&& format.byteOrder() == QAudioFormat::LittleEndian);
}
bool AudioRecorder::open()
{
if (!hasSupportedFormat())
{
setErrorString("Wav PCM supports only 8-bit unsigned samples "
"or 16-bit (or more) signed samples (in little endian)");
return false;
}
else
{
if (!QFile::open(ReadWrite | Truncate))
return false;
writeHeader();
return true;
}
}
qint64 AudioRecorder::write(const QByteArray &data)
{
return QFile::write(data);
}
void AudioRecorder::writeHeader()
{
QDataStream out(this);
out.setByteOrder(QDataStream::LittleEndian);
// RIFF chunk
out.writeRawData("RIFF", 4);
out << quint32(0); // Placeholder for the RIFF chunk size (filled by close())
out.writeRawData("WAVE", 4);
// Format description chunk
out.writeRawData("fmt ", 4);
out << quint32(16); // "fmt " chunk size (always 16 for PCM)
out << quint16(1); // data format (1 => PCM)
out << quint16(format.channelCount());
out << quint32(format.sampleRate());
out << quint32(format.sampleRate() * format.channelCount()
* format.sampleSize() / 8 ); // bytes per second
out << quint16(format.channelCount() * format.sampleSize() / 8); // Block align
out << quint16(format.sampleSize()); // Significant Bits Per Sample
// Data chunk
out.writeRawData("data", 4);
out << quint32(0); // Placeholder for the data chunk size (filled by close())
Q_ASSERT(pos() == 44); // Must be 44 for WAV PCM
}
void AudioRecorder::close()
{
// Fill the header size placeholders
quint32 fileSize = size();
QDataStream out(this);
// Set the same ByteOrder like in writeHeader()
out.setByteOrder(QDataStream::LittleEndian);
// RIFF chunk size
seek(4);
out << quint32(fileSize - 8);
// data chunk size
seek(40);
out << quint32(fileSize - 44);
QFile::close();
}
main.cpp:
#include <QtCore>
#include "audiooutput.h"
#include "audiorecorder.h"
#include <signal.h>
QByteArray tone_generator()
{
//Tone generator from http://www.cplusplus.com/forum/general/129827/
const unsigned int samplerate = 8000;
const unsigned short channels = 1;
const double pi = M_PI;
const qint16 amplitude = std::numeric_limits<qint16>::max() * 0.5;
const unsigned short n_frequencies = 8;
const unsigned short n_seconds_each = 1;
float frequencies[n_frequencies] = {55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0};
const int n_samples = channels * samplerate * n_frequencies * n_seconds_each;
QVector<qint16> data;
data.resize(n_samples);
int index = n_samples / n_frequencies;
for (unsigned short i = 0; i < n_frequencies; ++i)
{
float freq = frequencies[i];
double d = (samplerate / freq);
int c = 0;
for (int j = index * i; j < index * (i + 1); j += 2)
{
double deg = 360.0 / d;
data[j] = data[j + (channels - 1)] = qSin((c++ * deg) * pi / 180.0) * amplitude;
}
}
return QByteArray((char*)data.data(), data.size() * sizeof(qint16));
}
void signalHandler(int signum)
{
qDebug().nospace() << "Interrupt signal (" << signum << ") received.";
qApp->exit();
}
int main(int argc, char *argv[])
{
//Handle console close to ensure destructors being called
#ifdef Q_OS_WIN
signal(SIGBREAK, signalHandler);
#else
signal(SIGHUP, signalHandler);
#endif
signal(SIGINT, signalHandler);
QCoreApplication a(argc, argv);
QAudioFormat format;
format.setSampleRate(8000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
AudioOutput output;
AudioRecorder file("tone.wav", format);
if (!output.start(QAudioDeviceInfo::defaultOutputDevice(), format, 10 * 1000)) //10 seconds of buffer
return a.exec();
if (!file.open())
{
qDebug() << qPrintable(file.errorString());
return a.exec();
}
qDebug() << "Started!";
QByteArray audio_data = tone_generator();
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&]{
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
file.write(audio_data);
});
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
file.write(audio_data);
timer.start(8000); //8 seconds because we generated 8 seconds of sound
return a.exec();
}

Well. I tried to apply the code (just playback for now) to my situation but there is a problem that it plays data once, if there is no emitting "MorePackets()", or doesn't play at all, if "MorePackets()" occurs.
micserver.h
#ifndef MICSERVER_H
#define MICSERVER_H
//-----------------------------//
#include <QObject>
#include <QDebug>
#include <QTcpServer>
#include <QTcpSocket>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QFile>
#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <iostream>
#include "audiooutput.h"
//-----------------------------//
class MicServer : public QObject {
Q_OBJECT
public:
explicit MicServer(QObject *parent = nullptr);
QTcpSocket* Socket;
private:
QTcpServer* Server;
QAudioFormat format;
AudioOutput output;
QByteArray GetPackets(QTcpSocket* ClientP, int PacketsNumP);
signals:
void MorePackets();
public slots:
void NewConnection();
void Play();
};
//-----------------------------//
#endif
micserver.cpp
#include "micserver.h"
//-----------------------------//
QByteArray MicServer :: GetPackets(QTcpSocket* ClientP, int PacketsNumP) {
QBuffer BufferL;
BufferL.open(QBuffer :: WriteOnly);
QDataStream InputL(&BufferL);
InputL.setVersion(QDataStream::Qt_5_10);
QByteArray TempArray;
for (int i = 0; i < PacketsNumP; i++) {
ClientP -> waitForReadyRead(3000);
InputL << ClientP -> readAll();
}
for (int i = 0; i < PacketsNumP; i++) {
TempArray.push_back(BufferL.buffer().mid(76 + i * 1172, 256));
}
BufferL.close();
return TempArray;
}
//-----------------------------//
MicServer :: MicServer(QObject *parent) : QObject(parent) {
Server = new QTcpServer;
if (!Server -> listen(QHostAddress :: Any, 49112)) {
qDebug() << "Failed to launch server!";
} else {
qDebug() << "Server launched!";
}
connect(Server, SIGNAL(newConnection()), this, SLOT(NewConnection()));
connect(Server, SIGNAL(newConnection()), this, SLOT(Play()));
connect(this, SIGNAL(MorePackets()), this, SLOT(Play()));
format.setSampleRate(16000);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
output.start(QAudioDeviceInfo::defaultOutputDevice(), format);
}
void MicServer :: NewConnection() {
Socket = Server -> nextPendingConnection();
qDebug() << "New connection!";
qDebug() << Socket -> localAddress().toString();
}
void MicServer :: Play() {
QByteArray audio_data = GetPackets(Socket, 250);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&]{
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
});
qDebug() << "Writting" << audio_data.size() << "bytes";
output.write(audio_data);
timer.start(2000);
emit(MorePackets());
}

Related

What is the correct way to run use threads in Qt for that can perform a blocking task?

I am making a media player app in Qt with C++. There will be a list of audio files and when the user clicks on an item it will start playing. If the user clicks another item while one is already playing the currently playing item will stop and the new one will start. If the whole file is finished playing then the audio should stop as well.
I am using SDL to play audio and initially the audio playing would cause the UI to hang so I put it on a background thread. Now the UI does not hang but now if I click through different files the whole program will hang then crash and not start and stop each file. I think it might be because I am making an object for each click and it eats up memory? I am not sure as I am new to Qt and C++ development.
This is the header file for the Audio player.
#ifndef AUDIOPLAYER_H
#define AUDIOPLAYER_H
#define SDL_MAIN_HANDLED
#include <iostream>
#include <QObject>
#include <SDL2/SDL.h>
struct AudioData
{
Uint8* pos;
Uint32 length;
};
class AudioPlayer : public QObject
{
Q_OBJECT
public:
AudioPlayer(char * fileName);
~AudioPlayer();
public slots:
void process();
signals:
void finished();
void error();
private:
char *fileName;
static void callback(void* userdata, Uint8* stream, int streamLength);
};
#endif // AUDIOPLAYER_H
This is the audio player implementation. It takes a file during initialization and then plays it when process is called.
#include "audioplayer.h"
AudioPlayer::AudioPlayer(char *fileName)
{
this->fileName = fileName;
}
AudioPlayer::~AudioPlayer()
{}
void AudioPlayer::process()
{
if(SDL_AUDIO_PLAYING || SDL_AUDIO_PAUSED) SDL_CloseAudio();
SDL_Init(SDL_INIT_AUDIO);
SDL_AudioSpec wavSpec;
Uint8* wavStart;
Uint32 wavLength;
if(SDL_LoadWAV(this->fileName, &wavSpec, &wavStart, &wavLength) == NULL)
{
std::cerr << "Error: " << this->fileName
<< " could not be loaded as an audio file" << std::endl;
return;
}
AudioData audio;
audio.pos = wavStart;
audio.length = wavLength;
wavSpec.callback = callback;
wavSpec.userdata = &audio;
SDL_AudioDeviceID device = SDL_OpenAudioDevice(NULL, 0, &wavSpec, NULL, SDL_AUDIO_ALLOW_ANY_CHANGE);
if(device == 0)
{
std::cerr << "Error: " << SDL_GetError() << std::endl;
return;
}
SDL_PauseAudioDevice(device, 0);
while(audio.length > 0)
{
SDL_Delay(100);
}
SDL_CloseAudioDevice(device);
SDL_FreeWAV(wavStart);
SDL_Quit();
emit finished();
return;
}
void AudioPlayer::callback(void* userdata, Uint8* stream, int streamLength)
{
AudioData* audio = (AudioData*) userdata;
if(audio->length == 0) return;
Uint32 length = (Uint32)streamLength;
length = (length > audio->length ? audio->length : length);
SDL_memcpy(stream, audio->pos, length);
audio->pos += length;
audio->length -= length;
}
Audio player is created and called in my main window in a slot for when an TreeView item is clicked.
void MainWindow::playSound(const QModelIndex& clickedItem)
{
auto item = fileFilterModel->mapToSource(clickedItem);
QStandardItem *qStandardItem = filemodel->itemFromIndex(item);
TreeViewData data = qStandardItem->data().value<TreeViewData>();
if(data.fileInfo.isFile())
{
string filePath = data.fileInfo.absoluteFilePath().toStdString();
char* c = strcpy(new char[filePath.length() + 1], filePath.c_str());
QThread* thread = new QThread();
AudioPlayer *audioPlayer = new AudioPlayer(c);
audioPlayer->moveToThread(thread);
connect(thread, &QThread::started, audioPlayer, &AudioPlayer::process);
connect(audioPlayer, &AudioPlayer::finished, thread, &QThread::quit);
connect(audioPlayer, &AudioPlayer::finished, audioPlayer, &AudioPlayer::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
}

Saving to text-File from serial (UART) STM32 with QT/C++

I'm trying to save a parameter from UART input to txt-File. Please help me to understand what is wrong! Parameter have a form from 0|0|0|0 to 999|999|999|999. Txt File "SAVE_DATA_LOG" stays empty.
Why?
I'm also trying to get a system-timecode next to values - how can I get it?
I'm also sending a strings like "MP11500" (already works).
#include <UART_PC.h>
#include <stdio.h>
#include <QFile>
#include <QTextStream>
//#include <ui_UART_PC.h>
#include <QDebug>
#include <QByteArray>
using namespace std;
// ************************************************************************************************
UART_PC::UART_PC() {
if (0 == connectSerial()) {
qDebug() << "Serial Ok!\n";
} else {
qDebug() << "Serial Error!\n";
return;
}
m_state = WAIT;
m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), this, SLOT(processMeas()),Qt::QueuedConnection);
//m_timer->setInterval(1000);
m_timer->start(1000); //Timer 1 sec
m_cnt = 0;
m_values.setFileName(PWM_VALUES); //PWM - Values for motors
m_values.open(QIODevice::ReadOnly);
qDebug()<<"Start..";
QSerialPort *serialPort= new QSerialPort();
}
// ************************************************************************************************
UART_PC::~UART_PC() {
}
// ************************************************************************************************
uint32_t UART_PC::connectSerial(void) { //Serial init
m_serial.setPortName(SDEV);
m_serial.setBaudRate(QSerialPort::Baud9600);
m_serial.setDataBits(QSerialPort::Data8);
m_serial.setParity(QSerialPort::NoParity);
m_serial.setStopBits(QSerialPort::OneStop);
m_serial.setFlowControl(QSerialPort::NoFlowControl);
if (!m_serial.open(QIODevice::ReadWrite)) {
return 1;
}
connect(&m_serial, SIGNAL(readyRead()), this, SLOT(readRequest()),Qt::QueuedConnection);
return 0;
}
// ************************************************************************************************
void UART_PC::processMeas() { //State machine
qDebug()<<"Tick:"<<m_cnt;
if (m_state == WAIT) { //State 0 - Wait
m_cnt++;
if(m_cnt == 5){
m_state = SET_V1;
}
}
if (m_state == SET_V1) { //State 1 - Send
if(m_values.atEnd()){
m_values.seek(0);
}
if(!m_values.atEnd()){
QString v = m_values.readLine();
QStringList chunks = v.split(",");
QString cmd = "MP"+chunks.at(0)+chunks.at(1)+"\n\0";
m_serial.write(cmd.toUtf8());
m_serial.waitForBytesWritten(1000);
// qDebug()<<cmd<<v;
m_cnt = 0;
m_state = SET_V2;
// m_state = WAIT;
}
if (m_state == SET_V2){ // State 2 - receive and write
UART_PC::saveFile(QString::fromStdString(buffer.data()));
m_state = WAIT;
}
}
}
// ************************************************************************************************
void UART_PC::readRequest() { //Read and save
QMutexLocker lock(&m_mutex_serial);
m_serial_data += m_serial.readAll();
qDebug() << hex << m_serial_data;
UART_PC::saveFile(QString::fromStdString(m_serial_data.data()));
// buffer = m_serial_data;
m_serial_data.clear();
}
// ************************************************************************************************
void UART_PC::saveFile(QString buffer){ //Save
// qDebug() << "Ohlolololo"+buffer;
QString LOG_FILE = SAVE_DATA_LOG;
QFile file(LOG_FILE);
file.open(QIODevice::ReadWrite);
QTextStream stream(&file);
stream << buffer << endl;
file.close();
}
Thank you very much!

Qt audio file to wave like audacity

I have to manage movie and audio file and I need to render a wave of the sound like in audacity. But I just find example for realtime render.
I want to render all the file without playing it.
Expected result:
My actual result:
With Qt I tried to use QAudioDecoder to open my file and get a QAudioBuffer but I don't find an algorithm to transform all the data into wave. I also try to see with the Qt Spectrum Example but it's not trivial to understand and it's still in realtime.
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include <QWidget>
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QWidget
{
Q_OBJECT
public:
Track(QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
protected:
void paintEvent(QPaintEvent *e) override;
private:
int pointDistance(const QPoint& a, const QPoint& b);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QByteArray byteArr;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QPaintEvent>
#include <QPainter>
#include <QAudioDecoder>
Track::Track(QWidget *parent)
: QWidget(parent)
, decoder(new QAudioDecoder(this))
{
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(update()));
}
Track::~Track()
{
delete decoder;
}
void Track::setSource(const QString &fileName)
{
byteArr.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
byteArr.append(buffer.constData<char>(), buffer.byteCount());
}
void Track::paintEvent(QPaintEvent *e)
{
QWidget::paintEvent(e);
int w = width(), h = height();
QBrush backgroundBrush(Qt::white);
QPainter painter(this);
painter.fillRect(0, 0, w, h, backgroundBrush);
painter.drawLine(0, h/2, w, h/2);
if (!byteArr.isEmpty()){
QPen pen(QColor(Qt::blue));
painter.setPen(pen);
int length = byteArr.size();
int samplesPerPixel = length/w;
int idx=0;
for (int i=0; i<w; i++){
QLine line;
int higher = 0;
for (int j=0; j<samplesPerPixel && idx+1<length; j++){
const QPoint a(i, byteArr.at(idx)+(h/2));
const QPoint b(i, byteArr.at(idx+1)+(h/2));
if (higher < pointDistance(a, b))
line = QLine(a, b);
idx++;
}
painter.drawLine(line);
}
}
}
int Track::pointDistance(const QPoint &a, const QPoint &b)
{
int ret = 0;
ret = sqrt(pow(b.x()-a.x(), 2) + pow(b.y()-a.y(), 2));
return ret;
}
I finally found a solution by using QCustomPlot widget (by reading this post):
My result:
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include "qcustomplot.h"
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QCustomPlot
{
Q_OBJECT
public:
Track(TrackType type, QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
void plot();
private:
qreal getPeakValue(const QAudioFormat& format);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QVector<double> samples;
QCPGraph *wavePlot;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QAudioDecoder>
Track::Track(Track::TrackType type, QWidget *parent)
: QCustomPlot(parent)
, decoder(new QAudioDecoder(this))
{
this->type = type;
wavePlot = addGraph();
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(plot()));
}
Track::~Track()
{
delete decoder;
// wavePlot delete auto ?
}
void Track::setSource(const QString &fileName)
{
samples.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
qreal peak = getPeakValue(buffer.format());
const qint16 *data = buffer.constData<qint16>();
int count = buffer.sampleCount() / 2;
for (int i=0; i<count; i++){
double val = data[i]/peak;
samples.append(val);
}
}
void Track::plot()
{
QVector<double> x(samples.size());
for (int i=0; i<x.size(); i++)
x[i] = i;
wavePlot->addData(x, samples);
yAxis->setRange(QCPRange(-1, 1));
xAxis->setRange(QCPRange(0, samples.size()));
replot();
}
/**
* https://stackoverflow.com/questions/46947668/draw-waveform-from-raw-data-using-qaudioprobe
* #brief Track::getPeakValue
* #param format
* #return The peak value
*/
qreal Track::getPeakValue(const QAudioFormat &format)
{
qreal ret(0);
if (format.isValid()){
switch (format.sampleType()) {
case QAudioFormat::Unknown:
break;
case QAudioFormat::Float:
if (format.sampleSize() != 32) // other sample formats are not supported
ret = 0;
else
ret = 1.00003;
break;
case QAudioFormat::SignedInt:
if (format.sampleSize() == 32)
#ifdef Q_OS_WIN
ret = INT_MAX;
#endif
#ifdef Q_OS_UNIX
ret = SHRT_MAX;
#endif
else if (format.sampleSize() == 16)
ret = SHRT_MAX;
else if (format.sampleSize() == 8)
ret = CHAR_MAX;
break;
case QAudioFormat::UnSignedInt:
if (format.sampleSize() == 32)
ret = UINT_MAX;
else if (format.sampleSize() == 16)
ret = USHRT_MAX;
else if (format.sampleSize() == 8)
ret = UCHAR_MAX;
break;
default:
break;
}
}
return ret;
}

Periodic sending w/ QTcpSocket, QTcpServer & QTimer

I'm here to seek help on a strange behavior In am having w/ my project. So here goes !
Issue
I have a receiving class called DataReceiver, using a QTcpServer and a QTcpSocket, and I have the DataSender class feeding it via a QTcpSocket. I send the data periodically, and trigger the "send" slot w/ a QTimer.
BUT, after a few iterations, the feed stalls, and is not periodic anymore. I can't really understand what is happening.
I have confirmed this issue w/ terminal printing on the Receiver side.
The code
datasender.cpp
// Con/Destructors
DataSender::DataSender(QObject *parent) :
QObject(parent),
mTcpSocket(new QTcpSocket(this)),
mDestinationAddress("127.0.0.1"),
mDestinationPort(51470),
mTimer(new QTimer(this))
{
connectToHost();
connect(mTimer, SIGNAL(timeout(void)), this, SLOT(sendPeriodicData()));
mTimer->start(1000);
}
DataSender::~DataSender(){
mTcpSocket->disconnectFromHost();
mTcpSocket->waitForDisconnected();
delete mTcpSocket;
}
// Network Management
bool DataSender::connectToHost(void){
connect(mTcpSocket, SIGNAL(connected()), this, SLOT(onConnect()));
connect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
connect(mTcpSocket, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
qDebug() << "connecting...";
mTcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption, true);
mTcpSocket->setSocketOption(QAbstractSocket::WriteOnly);
mTcpSocket->connectToHost(getDestinationAddress(), getDestinationPort());
if(!mTcpSocket->waitForConnected(1000))
{
qDebug() << "Error: " << mTcpSocket->errorString();
return false;
}
// Setting meteo data to send
mMeteoData.messageID = METEO_MESSAGE;
mMeteoData.temperature = 5.5;
mMeteoData.pressure = 10.2;
mMeteoData.humidity = 45.5;
// Setting positiondata to send
mPositionData.messageID = POSITION_MESSAGE;
mPositionData.north = 120.3;
mPositionData.pitch = 1.5;
mPositionData.roll = 2.5;
mPositionData.yaw = 3.5;
mPositionData.a_x = 30.5;
mPositionData.a_y = 40.5;
mPositionData.a_z = 50.5;
return true;
}
void DataSender::sendData(void) const{
QByteArray lData("Hello, this is DataSender ! Do you copy ? I repeat, do you copy ?");
if(mTcpSocket->state() == QAbstractSocket::ConnectedState)
{
mTcpSocket->write(lData);
mTcpSocket->waitForBytesWritten();
}
}
void DataSender::sendData(const QByteArray &pData) const{
//QByteArray lData("Hello, this is DataSender ! Do you copy ? I repeat, do you copy ?");
if(mTcpSocket->state() == QAbstractSocket::ConnectedState)
{
mTcpSocket->write(pData);
mTcpSocket->waitForBytesWritten();
mTcpSocket->flush();
//usleep(1000);
}
}
// Getters
QString DataSender::getDestinationAddress(void) const{
return mDestinationAddress;
}
unsigned int DataSender::getDestinationPort(void) const{
return mDestinationPort;
}
// Setters
void DataSender::setDestinationAddress(const QString &pDestinationAddress){
mDestinationAddress = pDestinationAddress;
}
void DataSender::setDestinationPort(const unsigned int &pDestinationPort){
mDestinationPort = pDestinationPort;
}
// Public Slots
void DataSender::onConnect(){
qDebug() << "connected...";
}
void DataSender::onDisconnect(){
qDebug() << "disconnected...";
}
void DataSender::onBytesWritten(qint64 bytes){
qDebug() << bytes << " bytes written...";
}
void DataSender::sendPeriodicData(void){
mTcpSocket->
// Changing data for testing
mPositionData.north += 10;
mPositionData.north = std::fmod(mPositionData.north, 360);
mMeteoData.temperature += 10;
mMeteoData.temperature = std::fmod(mMeteoData.temperature, 500);
// Declaring QByteArrays
QByteArray lMeteoByteArray;
QByteArray lPositionByteArray;
// Serializing
lMeteoByteArray = serializeMeteoData(mMeteoData);
lPositionByteArray = serializePositionData(mPositionData);
// Sending
sendData(lMeteoByteArray);
sendData(lPositionByteArray);
}
datareceiver.cpp
// Con/Destructors
DataReceiver::DataReceiver(QObject *parent) :
QObject(parent),
mTcpServer(new QTcpServer(this)),
mSourceAddress("127.0.0.1"),
mSourcePort(51470)
{
initData();
connect(mTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
if(!mTcpServer->listen(QHostAddress(getSourceAddress()), getSourcePort()))
qDebug() << "<DataReceiver> Server could not start. ";
else
qDebug() << "<DataReceiver> Server started !";
}
DataReceiver::DataReceiver(const QString &pSourceAddress,
const unsigned int &pSourcePort,
QObject *parent) :
QObject(parent),
mTcpServer(new QTcpServer(this)),
mSourceAddress(pSourceAddress),
mSourcePort(pSourcePort)
{
initData();
connect(mTcpServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
if(!mTcpServer->listen(QHostAddress(getSourceAddress()), getSourcePort()))
qDebug() << "<DataReceiver> Server could not start. ";
else
qDebug() << "<DataReceiver> Server started !";
}
DataReceiver::~DataReceiver(){
if(mTcpSocket != nullptr) delete mTcpSocket;
delete mTcpServer;
if(mTcpSocket != nullptr)
delete mTcpSocket;
}
// Getters
QTcpServer *DataReceiver::getTcpServer(void) const{
return mTcpServer;
}
QString DataReceiver::getSourceAddress(void) const{
return mSourceAddress;
}
unsigned int DataReceiver::getSourcePort(void) const{
return mSourcePort;
}
positionData_t DataReceiver::getPositionData(void) const{
return mPositionData;
}
meteoData_t DataReceiver::getMeteoData(void) const{
return mMeteoData;
}
// Setters
void DataReceiver::setSourceAddress(const QString &pSourceAddress){
mSourceAddress = pSourceAddress;
}
void DataReceiver::setSourcePort(const unsigned int &pSourcePort){
mSourcePort = pSourcePort;
}
void DataReceiver::setPositionData(const positionData_t &pPositionData){
mPositionData = pPositionData;
}
void DataReceiver::setMeteoData(const meteoData_t &pMeteoData){
mMeteoData = pMeteoData;
}
// Data Management
void DataReceiver::initPositionData(void){
mPositionData.messageID = METEO_MESSAGE;
mPositionData.pitch = .0;
mPositionData.roll = .0;
mPositionData.yaw = .0;
mPositionData.a_x = .0;
mPositionData.a_y = .0;
mPositionData.a_z = .0;
}
void DataReceiver::initMeteoData(void){
mMeteoData.messageID = POSITION_MESSAGE;
mMeteoData.temperature = .0;
mMeteoData.humidity = .0;
mMeteoData.pressure = .0;
}
void DataReceiver::initData(void){
initPositionData();
initMeteoData();
}
void DataReceiver::reinitData(void){
initData();
}
// Public Slots
void DataReceiver::onConnect(){
qDebug() << "QTcpSocket connected...";
}
void DataReceiver::onDisconnect(){
qDebug() << "QTcpSocket disconnected...";
disconnect(mTcpSocket, SIGNAL(readyRead()), this, SLOT(onDataReceived()));
disconnect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
}
void DataReceiver::onBytesWritten(qint64 bytes){
qDebug() << bytes << " bytes written to QTcpSocket...";
}
void DataReceiver::onDataReceived(){
// Not yet implemented, code is for testing
qDebug() << "onDataReceived called !";
QByteArray lReceivedData;
while(mTcpSocket->bytesAvailable()){
lReceivedData = mTcpSocket->read(mTcpSocket->bytesAvailable());
decodeData(lReceivedData);
qDebug() << lReceivedData << "\n";
}
}
void DataReceiver::onNewConnection(){
qDebug() << "onNewConnection called !";
mTcpSocket = mTcpServer->nextPendingConnection();
mTcpSocket->setSocketOption(QAbstractSocket::ReadOnly, true);
connect(mTcpSocket, SIGNAL(readyRead()), this, SLOT(onDataReceived()));
connect(mTcpSocket, SIGNAL(disconnected()), this, SLOT(onDisconnect()));
}
void DataReceiver::onDataChanged(void){
qDebug() << "onDataChanged called !";
qDebug() << "\nPrinting mMeteoData : ";
qDebug() << "mMeteoData.messageID" << mMeteoData.messageID;
qDebug() << "mMeteoData.temperature" << mMeteoData.temperature;
qDebug() << "mMeteoData.humidity" << mMeteoData.humidity;
qDebug() << "mMeteoData.pressure" << mMeteoData.pressure;
qDebug() << "\nPrinting mPositionData";
qDebug() << "mPositionData.messageID" << mPositionData.messageID;
qDebug() << "mPositionData.north" << mPositionData.north;
qDebug() << "mPositionData.pitch" << mPositionData.pitch;
qDebug() << "mPositionData.roll" << mPositionData.roll;
qDebug() << "mPositionData.yaw" << mPositionData.yaw;
qDebug() << "mPositionData.a_x" << mPositionData.a_x;
qDebug() << "mPositionData.a_y" << mPositionData.a_y;
qDebug() << "mPositionData.a_z" << mPositionData.a_z;
}
// Private Methods
void DataReceiver::decodeData(const QByteArray &pMessage){
// Not yet implemented
quint8 tempMessageID = fetchMessageID(pMessage);
switch(tempMessageID){
case UNKNOWN_MESSAGE:
break;
case METEO_MESSAGE:
mMeteoData = deserializeMeteoData(pMessage);
emit dataChanged();
break;
case POSITION_MESSAGE:
mPositionData = deserializePositionData(pMessage);
emit dataChanged();
break;
}
return;
}
More information
I send two types of data, both coming from structs. On the receiver side, I decode the data, identify it, and set corresponding attributes.
The receiver class is used in a QT GUI App. It is not particularly threaded. I do not really know if it is necessary, as at the beginning the data is received periodically as intended.
Conclusion
I would really like to find a solution for this... I tried a few things to no avail, like changing how I read the socket or using the QTcpSocket::flush() method (not sure it does what I thought it did)
Thanks a lot,
Clovel
Bonus
I would also like to be able to open de Receiver end and have the sender automatically detect it and start sending the data to the Receiver. I need to dig a little more into this, but if you have a tip, it would be welcome !
While the #Kuba Ober comment seems to solve the lock of your sender, i will also advice about TCP, because one send operation can result in multiples readyRead SIGNALs and also multiples send operations can result in a single readyRead SIGNAL, so my advice is to write a small protocol to avoid problems with corrupted data!
I wrote a protocol here and it was well accepted, now i'm improving it!
The following example is generic, so you have to adapt it to fit your needs, but i think you can do it without much hassle :), it has been tested with Qt 5.5.1 MinGW in Windows 10.
To reach your bonus i used a UDP socket to send a broadcast message and try to find peers in range.
You can see the full project here!
common.h:
#ifndef COMMON_H
#define COMMON_H
#include <QtCore>
#include <QtNetwork>
//Helper macro to set a QObject pointer to nullptr when it's get destroyed
#define SETTONULLPTR(obj) QObject::connect(obj, &QObject::destroyed, [=]{obj = nullptr;})
//Define a max size for each send data operation
#define MAX_NETWORK_CHUNK_SIZE 10*1024*1024
//Create differents types for incomming data, valid for both client and server
namespace Type
{
enum
{
DataType1,
DataType2
};
}
//Convert some data of type T to QByteArray, by default in big endian order
template <typename T>
static inline QByteArray getBytes(T input)
{
QByteArray tmp;
QDataStream data(&tmp, QIODevice::WriteOnly);
data << input;
return tmp;
}
//Convert some QByteArray to data of type T
template <typename T>
static inline T getValue(QByteArray bytes)
{
T tmp;
QDataStream data(&bytes, QIODevice::ReadOnly);
data >> tmp;
return tmp;
}
//Struct that holds data and information about the peer
typedef struct PeerData {
QByteArray data;
QHostAddress host;
qintptr descriptor;
} PeerData;
#endif // COMMON_H
client.h:
#ifndef CLIENT_H
#define CLIENT_H
#include <QtCore>
#include <QtNetwork>
#include "common.h"
class Client : public QObject
{
Q_OBJECT
public:
explicit Client(QObject *parent = nullptr);
~Client();
signals:
void peerFound(QHostAddress);
void searchPeersFinished();
void connected(PeerData);
void disconnected(PeerData);
void readyRead(PeerData);
void error(QString);
public slots:
void abort();
void connectToHost(const QString &host, quint16 port);
void searchPeers(quint16 port);
void stop();
int write(const QByteArray &data);
private slots:
void UDPReadyRead();
void UDPWrite();
void searchPeersEnd();
void timeout();
void connectedPrivate();
void disconnectedPrivate();
void errorPrivate(QAbstractSocket::SocketError e);
void readyReadPrivate();
private:
QTcpSocket *m_socket;
QUdpSocket *m_udp_socket;
quint16 m_udp_port;
QByteArray m_buffer;
qint32 m_size;
QTimer *m_timer;
};
#endif // CLIENT_H
client.cpp:
#include "client.h"
Client::Client(QObject *parent) : QObject(parent)
{
qRegisterMetaType<PeerData>("PeerData");
qRegisterMetaType<QHostAddress>("QHostAddress");
m_socket = nullptr;
m_size = 0;
m_udp_socket = nullptr;
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &Client::timeout);
m_timer->setSingleShot(true);
}
Client::~Client()
{
disconnectedPrivate();
}
//Disconnects the socket
void Client::abort()
{
if (m_socket)
m_socket->abort();
}
//Start connection to server
void Client::connectToHost(const QString &host, quint16 port)
{
if (m_socket)
return;
m_socket = new QTcpSocket(this);
SETTONULLPTR(m_socket);
connect(m_socket, &QTcpSocket::readyRead, this, &Client::readyReadPrivate);
connect(m_socket, &QTcpSocket::connected, this, &Client::connectedPrivate);
connect(m_socket, &QTcpSocket::disconnected, this, &Client::disconnectedPrivate);
connect(m_socket, static_cast<void(QTcpSocket::*)(QTcpSocket::SocketError)>(&QTcpSocket::error), this, &Client::errorPrivate);
m_timer->start(10 * 1000);
m_socket->connectToHost(host, port);
}
//Perform udp broadcast to search peers
void Client::searchPeers(quint16 port)
{
if (m_udp_socket)
return;
m_udp_socket = new QUdpSocket(this);
SETTONULLPTR(m_udp_socket);
connect(m_udp_socket, &QUdpSocket::readyRead, this, &Client::UDPReadyRead);
m_udp_port = port;
QTimer::singleShot(50, this, &Client::UDPWrite);
}
//Ready read specific for udp socket
void Client::UDPReadyRead()
{
while (m_udp_socket->hasPendingDatagrams())
{
QByteArray data;
data.resize(m_udp_socket->pendingDatagramSize());
QHostAddress peer_address;
quint16 peer_port;
m_udp_socket->readDatagram(data.data(), data.size(), &peer_address, &peer_port);
//Test the header used in this udp broadcast, you can freely change this value,
//but do for both client and server
if (QLatin1String(data) == QLatin1Literal("TEST"))
emit peerFound(peer_address);
}
}
//Send the udp broadcast message to all the network interfaces
void Client::UDPWrite()
{
QList<QHostAddress> broadcast;
foreach (QHostAddress address, QNetworkInterface::allAddresses())
{
if (address.protocol() == QAbstractSocket::IPv4Protocol)
{
address.setAddress(address.toIPv4Address());
QStringList list = address.toString().split(".");
list.replace(3, "255");
QString currentbroadcast = list.join(".");
QHostAddress address = QHostAddress(QHostAddress(currentbroadcast).toIPv4Address());
broadcast.append(address);
}
}
QByteArray datagram = QString("TEST").toLatin1();
foreach (const QHostAddress &address, broadcast)
m_udp_socket->writeDatagram(datagram, address, m_udp_port);
//Wait 0.5 seconds for an answer
QTimer::singleShot(500, this, &Client::searchPeersEnd);
}
//Stop the udp socket
void Client::searchPeersEnd()
{
m_udp_socket->deleteLater();
emit searchPeersFinished();
}
void Client::timeout()
{
emit error("Operation timed out");
stop();
}
//Handle connected state
void Client::connectedPrivate()
{
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit connected(pd);
m_timer->stop();
}
//Handle disconnected state
void Client::disconnectedPrivate()
{
if (!m_socket)
return;
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
stop();
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit disconnected(pd);
}
//Handle error
void Client::errorPrivate(QAbstractSocket::SocketError e)
{
if (e != QAbstractSocket::RemoteHostClosedError)
{
QString err = m_socket->errorString();
emit error(err);
}
stop();
}
//Stop the tcp socket
void Client::stop()
{
m_timer->stop();
m_size = 0;
m_buffer.clear();
if (m_socket)
{
m_socket->abort();
m_socket->deleteLater();
}
}
//Write data to the server
int Client::write(const QByteArray &data)
{
if (!m_socket)
return 0;
m_socket->write(getBytes<qint32>(data.size()));
m_socket->write(data);
return 1;
}
//Receive message from server
void Client::readyReadPrivate()
{
if (!m_socket)
return;
while (m_socket->bytesAvailable() > 0)
{
m_buffer.append(m_socket->readAll());
while ((m_size == 0 && m_buffer.size() >= 4) || (m_size > 0 && m_buffer.size() >= m_size))
{
if (m_size == 0 && m_buffer.size() >= 4)
{
m_size = getValue<qint32>(m_buffer.mid(0, 4));
m_buffer.remove(0, 4);
if (m_size < 0 || m_size > MAX_NETWORK_CHUNK_SIZE)
{
m_socket->abort();
return;
}
}
if (m_size > 0 && m_buffer.size() >= m_size)
{
QByteArray data = m_buffer.mid(0, m_size);
m_buffer.remove(0, m_size);
m_size = 0;
QHostAddress host = m_socket->peerAddress();
qintptr descriptor = m_socket->socketDescriptor();
PeerData pd;
pd.data = data;
pd.host = host;
pd.descriptor = descriptor;
emit readyRead(pd);
}
}
}
}
server.h:
#ifndef SERVER_H
#define SERVER_H
#include <QtCore>
#include <QtNetwork>
#include "common.h"
class Server : public QObject
{
Q_OBJECT
public:
explicit Server(QObject *parent = nullptr);
~Server();
signals:
void connected(PeerData);
void disconnected(PeerData);
void listening(quint16);
void readyRead(PeerData);
void error(QString);
public slots:
void abort(qintptr descriptor);
void listen(quint16 port);
void stop();
void writeToHost(const QByteArray &data, qintptr descriptor);
int writeToAll(const QByteArray &data);
private slots:
void newConnectionPrivate();
void UDPListen(quint16 port);
void UDPReadyRead();
void readyReadPrivate();
void disconnectedPrivate();
void removeSocket(QTcpSocket *socket);
private:
QTcpServer *m_server;
QUdpSocket *m_udp_server;
QList<QTcpSocket*> m_socket_list;
QHash<qintptr, QTcpSocket*> m_socket_hash;
QHash<QTcpSocket*, qintptr> m_descriptor_hash;
QHash<QTcpSocket*, QByteArray> m_buffer_hash;
QHash<QTcpSocket*, qint32> m_size_hash;
};
#endif // SERVER_H
server.cpp:
#include "server.h"
Server::Server(QObject *parent) : QObject(parent)
{
qRegisterMetaType<PeerData>("PeerData");
qRegisterMetaType<QHostAddress>("QHostAddress");
m_server = nullptr;
m_udp_server = nullptr;
}
Server::~Server()
{
stop();
}
//Disconnect the socket specified by descriptor
void Server::abort(qintptr descriptor)
{
QTcpSocket *socket = m_socket_hash.value(descriptor);
socket->abort();
}
//Try to start in listening state
void Server::listen(quint16 port)
{
if (m_server)
return;
m_server = new QTcpServer(this);
SETTONULLPTR(m_server);
connect(m_server, &QTcpServer::newConnection, this, &Server::newConnectionPrivate);
if (port < 1)
{
emit error("Invalid port value");
stop();
return;
}
bool is_listening = m_server->listen(QHostAddress::AnyIPv4, port);
if (!is_listening)
{
emit error(m_server->errorString());
stop();
return;
}
UDPListen(port);
emit listening(m_server->serverPort());
}
//Start the udp server, used to perform netowork search
void Server::UDPListen(quint16 port)
{
if (m_udp_server)
return;
m_udp_server = new QUdpSocket(this);
SETTONULLPTR(m_udp_server);
connect(m_udp_server, &QUdpSocket::readyRead, this, &Server::UDPReadyRead);
m_udp_server->bind(port);
}
//ReadyRead specific for udp
void Server::UDPReadyRead()
{
while (m_udp_server->hasPendingDatagrams())
{
QByteArray data;
QHostAddress address;
quint16 port;
data.resize(m_udp_server->pendingDatagramSize());
m_udp_server->readDatagram(data.data(), data.size(), &address, &port);
//Test the header used in this udp broadcast, you can freely change this value,
//but do for both client and server
if (QLatin1String(data) == QLatin1Literal("TEST"))
{
m_udp_server->writeDatagram(data, address, port);
}
}
}
//Stop both tcp and udp servers and also
//removes peers that may be connected
void Server::stop()
{
while (!m_socket_list.isEmpty())
removeSocket(m_socket_list.first());
if (m_server)
{
m_server->close();
m_server->deleteLater();
}
if (m_udp_server)
{
m_udp_server->deleteLater();
}
}
//Handle new connection
void Server::newConnectionPrivate()
{
while (m_server->hasPendingConnections())
{
QTcpSocket *socket = m_server->nextPendingConnection();
QHostAddress host = socket->peerAddress();
qintptr descriptor = socket->socketDescriptor();
QByteArray m_buffer;
qint32 size = 0;
m_descriptor_hash.insert(socket, descriptor);
m_socket_hash.insert(descriptor, socket);
m_buffer_hash.insert(socket, m_buffer);
m_size_hash.insert(socket, size);
m_socket_list.append(socket);
connect(socket, &QTcpSocket::disconnected, this, &Server::disconnectedPrivate);
connect(socket, &QTcpSocket::readyRead, this, &Server::readyReadPrivate);
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit connected(pd);
}
}
//Write to specific socket if more than one is connected
void Server::writeToHost(const QByteArray &data, qintptr descriptor)
{
if (!m_socket_hash.contains(descriptor))
return;
QTcpSocket *socket = m_socket_hash.value(descriptor);
socket->write(getBytes<qint32>(data.size()));
socket->write(data);
}
//Write to all sockets
int Server::writeToAll(const QByteArray &data)
{
foreach (QTcpSocket *socket, m_socket_list)
{
socket->write(getBytes<qint32>(data.size()));
socket->write(data);
}
return m_socket_list.size();
}
//ReadyRead function shared by all sockets connected
void Server::readyReadPrivate()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QByteArray *m_buffer = &m_buffer_hash[socket];
qint32 *size = &m_size_hash[socket];
Q_UNUSED(size)
#define m_size *size
while (socket->bytesAvailable() > 0)
{
m_buffer->append(socket->readAll());
while ((m_size == 0 && m_buffer->size() >= 4) || (m_size > 0 && m_buffer->size() >= m_size))
{
if (m_size == 0 && m_buffer->size() >= 4)
{
m_size = getValue<qint32>(m_buffer->mid(0, 4));
m_buffer->remove(0, 4);
if (m_size < 0 || m_size > MAX_NETWORK_CHUNK_SIZE)
{
socket->abort();
return;
}
}
if (m_size > 0 && m_buffer->size() >= m_size)
{
QByteArray data = m_buffer->mid(0, m_size);
m_buffer->remove(0, m_size);
m_size = 0;
QHostAddress host = socket->peerAddress();
qintptr descriptor = socket->socketDescriptor();
PeerData pd;
pd.data = data;
pd.host = host;
pd.descriptor = descriptor;
emit readyRead(pd);
}
}
}
}
//Handle socket disconnection
void Server::disconnectedPrivate()
{
QTcpSocket *socket = static_cast<QTcpSocket*>(sender());
QHostAddress host = socket->peerAddress();
qintptr descriptor = m_descriptor_hash.value(socket);
removeSocket(socket);
PeerData pd;
pd.host = host;
pd.descriptor = descriptor;
emit disconnected(pd);
}
//Handle socket removal
void Server::removeSocket(QTcpSocket *socket)
{
qintptr descriptor = m_descriptor_hash.value(socket);
m_socket_hash.remove(descriptor);
m_descriptor_hash.remove(socket);
m_buffer_hash.remove(socket);
m_size_hash.remove(socket);
m_socket_list.removeAll(socket);
socket->abort();
socket->deleteLater();
}
Client/worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QtCore>
#include "client.h"
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void start(quint16 port);
private:
Client m_client;
quint16 m_port;
QTimer m_timer;
double m_double_1;
double m_double_2;
QMetaObject::Connection m_connection;
};
#endif // WORKER_H
Client/worker.cpp
#include "worker.h"
Worker::Worker(QObject *parent) : QObject(parent)
{
m_port = 0;
m_double_1 = 0;
m_double_2 = 0;
m_timer.setInterval(1000);
}
void Worker::start(quint16 port)
{
m_port = port;
connect(&m_client, &Client::searchPeersFinished, []{
qDebug() << "Search peers finished!";
});
m_connection = connect(&m_client, &Client::peerFound, [&](const QHostAddress &peer_address){
disconnect(m_connection); //Disconnect signal, only first peer found will be handled!
m_client.connectToHost(QHostAddress(peer_address.toIPv4Address()).toString(), m_port);
});
connect(&m_client, &Client::error, [](const QString &error){
qDebug() << "Error:" << qPrintable(error);
});
connect(&m_client, &Client::connected, [&](const PeerData &pd){
qDebug() << "Connected to:" << qPrintable(pd.host.toString());
m_timer.start();
});
connect(&m_client, &Client::disconnected, [](const PeerData &pd){
qDebug() << "Disconnected from:" << qPrintable(pd.host.toString());
});
connect(&m_client, &Client::readyRead, [](const PeerData &pd){
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< qPrintable(QString::asprintf("%.2f", getValue<double>(pd.data)));
});
connect(&m_timer, &QTimer::timeout, [&]{
m_double_1 += 0.5; //Just an example of data
QByteArray data1;
data1.append(getBytes<quint8>(Type::DataType1)); //Data 1 has Type 1, added as header
data1.append(getBytes<double>(m_double_1)); //The data itself
m_client.write(data1); //Write the data1 to the server
m_double_2 += 1.0; //Just an example of data
QByteArray data2;
data2.append(getBytes<quint8>(Type::DataType2)); //Data 2 has Type 2, added as header
data2.append(getBytes<double>(m_double_2)); //The data itself
m_client.write(data2); //Write the data2 to the server
});
qDebug() << "Searching...";
m_client.searchPeers(m_port); //Search for peers in range, if found, handle the first and connect to it!
}
Client/main.cpp
#include <QtCore>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Worker worker;
worker.start(1024);
return a.exec();
}
Server/worker.h
#ifndef WORKER_H
#define WORKER_H
#include <QtCore>
#include "server.h"
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject *parent = nullptr);
public slots:
void start(quint16 port);
private:
Server m_server;
quint16 m_port;
};
#endif // WORKER_H
Server/worker.cpp
#include "worker.h"
Worker::Worker(QObject *parent) : QObject(parent)
{
m_port = 0;
}
void Worker::start(quint16 port)
{
m_port = port;
connect(&m_server, &Server::error, [](const QString &error){
if (!error.isEmpty())
qDebug() << "Error:" << qPrintable(error);
});
connect(&m_server, &Server::listening, [](quint16 port){
qDebug() << "Listening on port:" << port;
});
connect(&m_server, &Server::connected, [](const PeerData &pd){
qDebug() << "Connected to:" << qPrintable(pd.host.toString());
});
connect(&m_server, &Server::disconnected, [](const PeerData &pd){
qDebug() << "Disconnected from:" << qPrintable(pd.host.toString());
});
connect(&m_server, &Server::readyRead, [&](const PeerData &pd){
QByteArray data = pd.data;
if (data.isEmpty())
return;
quint8 header = getValue<quint8>(data.mid(0, 1)); //Read the 1 byte header
data.remove(0, 1); //Remove the header from data
switch (header)
{
case Type::DataType1:
{
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< "DataType1" << qPrintable(QString::asprintf("%.2f", getValue<double>(data)));
break;
}
case Type::DataType2:
{
qDebug() << "Data from" << qPrintable(pd.host.toString())
<< "DataType2" << qPrintable(QString::asprintf("%.2f", getValue<double>(data)));
break;
}
default:
break;
}
m_server.writeToHost(data, pd.descriptor);
});
m_server.listen(m_port);
}
Server/main.cpp
#include <QtCore>
#include "worker.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Worker worker;
worker.start(1024);
return a.exec();
}

How to interact with a child QProcess?

In my program I have a child process that interacts with a serial port specified to it when the parent process demands it. For example the parent process commands the child process to read a number of bytes with a certain time out from the opened port by sending this command: read 100 1000. The child process launches and opens the port successfully and I can see the message port openned successfully! but from there onwards it won't read the parent commands.
Here's the child source code:
SerialPortHandler.h
#ifndef SERIALPORTHANDLER_H
#define SERIALPORTHANDLER_H
#include <QObject>
#include <QSocketNotifier>
#include <QTextStream>
#include <QSerialPort>
#include <QFile>
#include <QTimer>
#include <QDebug>
#include <QtCore>
enum CommandType { READ, WRITE, BAD, UNKNOWN };
class SerialPortHandler : public QObject
{
Q_OBJECT
public:
explicit SerialPortHandler(QString portname, QString baudrate, QObject *parent = 0);
signals:
public slots:
void execmd();
bool open(const QString& portname, const QString& baudrate);
qint64 read(char * buff, const qint64 size, const qint32 timeout);
QString convertToCaseInsensitiveRegExp(QString str);
CommandType analyze(const QString& line);
qint64 getNum(const QString &line, int index);
void reply(char *buff);
private:
QSocketNotifier *innotif;
QSerialPort *sp;
QTimer *timer;
};
#endif // SERIALPORTHANDLER_H
SerialPortHandler.cpp
#include "SerialPortHandler.h"
#include <unistd.h>
#include <limits>
SerialPortHandler::SerialPortHandler(QString portname, QString baudrate, QObject *parent) :
QObject(parent)
{
timer = new QTimer(this);
sp = new QSerialPort(this);
if(!open(portname, baudrate)) {
qDebug() << sp->error() << sp->errorString();
exit(sp->error());
}
innotif = new QSocketNotifier(STDIN_FILENO, QSocketNotifier::Read, this);
connect(innotif, SIGNAL(activated(int)), this, SLOT(execmd()));
}
void SerialPortHandler::execmd()
{
qDebug() << "command received. analyzing...";
// qint64 nbr = -1, size = -1;
// qint32 timeout = -1;
// char * buff = 0;
// QTextStream in(stdin);
// QString ln = in.readAll();
// switch (analyze(ln)) {
// case READ:
// size = getNum(ln, 1);
// timeout = getNum(ln, 2);
// if(size > -1 && timeout > -1)
// nbr = read(buff, size, timeout);
// if(nbr > -1)
// reply(buff);
// break;
// default:
// break;
// }
}
bool SerialPortHandler::open(const QString &portname, const QString &baudrate)
{
sp->setPortName(portname);
if (!sp->open(QIODevice::ReadWrite) ||
!sp->setBaudRate(baudrate.toInt()) ||
!sp->setDataBits(QSerialPort::Data8) ||
!sp->setParity(QSerialPort::NoParity) ||
!sp->setStopBits(QSerialPort::OneStop) ||
!sp->setFlowControl(QSerialPort::NoFlowControl)) {
return false;
}
sp->clear();
qDebug() << "port openned successfully!";
return true;
}
//day light wont affect this timer so the system wont freeze
qint64 SerialPortHandler::read(char *buff, const qint64 size, const qint32 timeout)
{
qint64 numbytesread = -1;
timer->start(timeout);
while (true) {
if(timer->remainingTime() > 0) {
return -1;
}
if((sp->isReadable() && sp->bytesAvailable() > 0) ||
(sp->isReadable() && sp->waitForReadyRead(10))) {
numbytesread += sp->read(buff, size);
}
if(numbytesread < 0) {
return -1;
}
if(numbytesread == size) {
break;
}
}
return numbytesread;
}
void SerialPortHandler::notify()
{
}
QString SerialPortHandler::convertToCaseInsensitiveRegExp(QString str)
{
QString result;
for(int i = 0 ; i < str.size() ; ++i) {
result.append("[");
result.append(str.at(i).toLower());
result.append(str.at(i).toUpper());
result.append("]");
}
return result;
}
CommandType SerialPortHandler::analyze(const QString &line)
{
QString read, write;
read = convertToCaseInsensitiveRegExp("read");
write = convertToCaseInsensitiveRegExp("write");
if(line.contains(QRegExp(QString("^.*%1\\s+[1-9]\\d*\\s+[1-9]\\d*.*").arg(read)))) {
return READ;
}
return UNKNOWN;
}
qint64 SerialPortHandler::getNum(const QString& line, int index) {
QStringList args(line.split(QRegExp("\\s+")));
bool done;
qint64 size = args.at(index).toInt(&done, 10);
if(done) {
return size;
}
return -1;
}
void SerialPortHandler::reply(char * buff) {
QDataStream out(stdout);
out << buff;
}
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "SerialPortHandler.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
if(argc != 3) {
qDebug() << "usage:" << argv[0] << "port" << "baudrate";
} else {
SerialPortHandler *sph = new SerialPortHandler(argv[1], argv[2]);
}
return a.exec();
}
My parent process consists of the following:
ParentProcess.h
#ifndef PARENTPROCESS_H
#define PARENTPROCESS_H
#include <QObject>
#include <QtCore>
class ParentProcess : public QObject
{
Q_OBJECT
public:
explicit ParentProcess(QObject *parent = 0);
signals:
public slots:
private slots:
void sendRead();
void writeSomething();
void handleError(QProcess::ProcessError error);
private:
QProcess *p;
};
#endif // PARENTPROCESS_H
ParentProcess.cpp
#include "ParentProcess.h"
#include <QDebug>
ParentProcess::ParentProcess(QObject *parent) :
QObject(parent)
{
p = new QProcess(this);
connect(p, SIGNAL(readyReadStandardOutput()), this, SLOT(sendRead()));
connect(p, SIGNAL(readyReadStandardError()), this, SLOT(sendRead()));
connect(p, SIGNAL(started()), this, SLOT(writeSomething()));
connect(p, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleError(QProcess::ProcessError)));
QStringList args;
args << "/dev/ttyUSB0" << "115200";
p->start("/home/moki/Work/Programs/build-serialio-Desktop_Qt_5_3_0_GCC_64bit-Debug/serialio", args, QProcess::ReadWrite);
}
void ParentProcess::sendRead() {
qDebug() << "data:" << p->readAllStandardError() << p->readAllStandardOutput();
}
void ParentProcess::writeSomething() {
qDebug() << "writing";
QString cmd = "read 10 10000\n";
qint64 a = p->write(cmd.toStdString().c_str());
qDebug() << "wrote:" << a;
}
void ParentProcess::handleError(QProcess::ProcessError error)
{
switch (error) {
case QProcess::FailedToStart:
qDebug() << "failed to start";
break;
case QProcess::Crashed:
qDebug() << "crashed.";
break;
default:
break;
}
}
main.cpp
#include <QCoreApplication>
#include "ParentProcess.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
ParentProcess p;
return a.exec();
}
I have seen a couple of other answers in SO but none of them address my issue. As you can see my child process is not supposed to complete and exit. It will remain launched as long as the parent process wishes. Is it correct to use QProcess-launched processes this way?
I changed my code to the following and now it's working. But I haven't understood why this code works and my original one doesn't.
in the parent process source i changed the constructor as follows:
ParentProcess::ParentProcess(QObject *parent) :
QObject(parent)
{
p = new QProcess(this);
connect(p, SIGNAL(error(QProcess::ProcessError)), this, SLOT(handleError(QProcess::ProcessError)));
connect(p, SIGNAL(readyRead()), this, SLOT(sendRead()));
connect(p, SIGNAL(started()), this, SLOT(writeSomething()));
QStringList args;
args << "/dev/ttyUSB0" << "115200";
p->setProcessChannelMode(QProcess::MergedChannels);
p->start("/home/moki/Work/Programs/build-serialio-Desktop_Qt_5_3_0_GCC_64bit-Debug/serialio", args, QProcess::ReadWrite);
}
and the sendRead() function to this:
void ParentProcess::sendRead() {
int bytes = p->bytesAvailable();
qDebug() << "data:" << p->read(bytes);
}
finally, the writeSomething() to:
void ParentProcess::writeSomething() {
qDebug() << "gonna write.";
if(p->state() == QProcess::Running) {
qDebug() << "writing";
QString cmd = "read 10 10000\n";
qint64 a = p->write(cmd.toStdString().c_str());
qDebug() << "wrote:" << a << "bytes.";
}
}
and now it works. If anyone could please explain this to me, I would be really grateful.