Здравствуйте, есть устройство, передающее в хост изображения размером 640Х480 пикселей (каждый пискель — 4 байта)
со скоростью 50 кадров в секунду по протоколу Udp. У пикселя один байт в оттенках серого, остальные три — это rgb.
Первая версия принимала пакеты в главном потоке, в результате чего замерзало окно приложения.
Перенос readPendingDatagrams в рабочую нить привел к тому, что приложение перестало принимать пакеты.
Wireshark видит, что пакеты приходят к хосту от устройства, а приложение пакеты не видит.
Вот фрагменты кода
Здравствуйте, milkpot, Вы писали:
M>Здравствуйте, есть устройство, передающее в хост изображения размером 640Х480 пикселей (каждый пискель — 4 байта) M>со скоростью 50 кадров в секунду по протоколу Udp. У пикселя один байт в оттенках серого, остальные три — это rgb. M>Первая версия принимала пакеты в главном потоке, в результате чего замерзало окно приложения. M>Перенос readPendingDatagrams в рабочую нить привел к тому, что приложение перестало принимать пакеты. M>Wireshark видит, что пакеты приходят к хосту от устройства, а приложение пакеты не видит. M>Вот фрагменты кода
M>...
Очень тяжело читать такой код (особенно явные сравнения с true/false). Можете убрать всё ненужное и просто сделать пересылку массива байт по udp? Отлаживать пробовали (убедиться что слоты вызываются и т.п.) отладчиком или хотя бы выводом в консоль? Зачем вам conditional variable?
Здравствуйте, SaZ, Вы писали:
SaZ>Очень тяжело читать такой код (особенно явные сравнения с true/false). Можете убрать всё ненужное и просто сделать пересылку массива байт по udp? Отлаживать пробовали (убедиться что слоты вызываются и т.п.) отладчиком или хотя бы выводом в консоль? Зачем вам conditional variable?
Работает только посылка по UDP — функция writeToUdp. readPendingDatagrams не запускается (нет отладочных печатей). Она запускалась
только когда находилась в gui классе. Отладчиком не получается. У меня mingw отладчик в QtCreator.
Здравствуйте, milkpot, Вы писали:
SaZ>>Очень тяжело читать такой код (особенно явные сравнения с true/false). Можете убрать всё ненужное и просто сделать пересылку массива байт по udp? Отлаживать пробовали (убедиться что слоты вызываются и т.п.) отладчиком или хотя бы выводом в консоль? Зачем вам conditional variable?
M>Работает только посылка по UDP — функция writeToUdp. readPendingDatagrams не запускается (нет отладочных печатей). Она запускалась M>только когда находилась в gui классе. Отладчиком не получается. У меня mingw отладчик в QtCreator.
Здравствуйте, milkpot, Вы писали:
M>Здравствуйте, SaZ, Вы писали:
SaZ>>Очень тяжело читать такой код (особенно явные сравнения с true/false). Можете убрать всё ненужное и просто сделать пересылку массива байт по udp? Отлаживать пробовали (убедиться что слоты вызываются и т.п.) отладчиком или хотя бы выводом в консоль? Зачем вам conditional variable?
M>Работает только посылка по UDP — функция writeToUdp. readPendingDatagrams не запускается (нет отладочных печатей). Она запускалась M>только когда находилась в gui классе. Отладчиком не получается. У меня mingw отладчик в QtCreator.
M>...
Подожду пока вы почините отладчик и всё-таки ответите на вопрос, касательно QWaitCondition — зачем он нужен? (есть чуйка, он не нужен). И покажите как вы потоки с воркерами стартуете?
А пока попробуйте покопать в следующую сторону: инстансы всяких стримов, включая QUdpSocket не нужно создавать в конструкторе воркера. А то получается что вы их создаёте в одном потоке, а потом начинаете использоват в другом. Он типа должно работать после moveToThread, но по факту, именно со стримами, есть платформозависимые нюансы. Вообще ничего не делайте в конструкторах воркеров.
SaZ>Подожду пока вы почините отладчик и всё-таки ответите на вопрос, касательно QWaitCondition — зачем он нужен? (есть чуйка, он не нужен). И покажите как вы потоки с воркерами стартуете?
SaZ>А пока попробуйте покопать в следующую сторону: инстансы всяких стримов, включая QUdpSocket не нужно создавать в конструкторе воркера. А то получается что вы их создаёте в одном потоке, а потом начинаете использоват в другом. Он типа должно работать после moveToThread, но по факту, именно со стримами, есть платформозависимые нюансы. Вообще ничего не делайте в конструкторах воркеров.
QWaitCondition в функции doWork нужна была, потому что функция работала как аналог run'а.
Сейчас там нет QWaitCondition.
В выводе приложения QtCreator'а после закрытия приложения появляются записи:
QThread: Destroyed while thread is still running
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
Здравствуйте, milkpot, Вы писали:
M>Здравствуйте, есть устройство, передающее в хост изображения размером 640Х480 пикселей (каждый пискель — 4 байта) M>со скоростью 50 кадров в секунду по протоколу Udp. У пикселя один байт в оттенках серого, остальные три — это rgb.
640*480*4*50*8/1e6 = 491.52
Гм. Полгигабита в секунду для передачи цветной картинки с разрешением VGA — это как-то не очень нормально ИМХО.
Надо увеличить размер приемного буфера до максимума и приоритет принимающего потока до максимума (и при этом он, если зациклится, будет вымораживать всю систему — значит, он не должен делать тяжелую работу, а должен только принимать и складывать; его задача — успевать выгребать приемные буфера UDP-сокета, они в венде не такие уж большие даже если выставлены максимальны возможные значения).
И надо как-то организовать компрессию этого видеопотока. Стримить на скорости полгигабита — это (1) очень серьезная (2) совершенно в данном случае не нужная задача.
Здравствуйте, milkpot, Вы писали:
M>QWaitCondition в функции doWork нужна была, потому что функция работала как аналог run'а. M>Сейчас там нет QWaitCondition.
M>
M>class Worker2 : public QObject
M>{
M> Q_OBJECT
M>public:
M> QMutex mutex;
M> QWaitCondition wtc_worker;
M> // QPixmap pixmap_copy;
M> bool wait_cnd;
M> bool b_counter;
M> bool c_variable=true;
M> int data_quant=81920;
M> quint16 package_num;
M> quint16 str_num;
M> int color_shift=0;
//Я надеюсь это от уменьшения примера, так то все эти переменные можно удалить
M> void doStart()
//Тут код в 2 раза можно сократить
M> void writeToUdp(const QByteArray& qBinArray)
//Я бы по разным классам сделал читателя и писателя. Или писатель тоже в отдельном потоке должен быть?
M>signals:
M> void sendData(const QImage &image);
M>Window::Window()
M>{
M> //---+
M> connect(&worker, &Worker2::sendData, this, &Window::displayImage);
/*Я надеюсь там сохранение QImage в классе и вызов update
void Window::displayImage(const QImage &image)
{
m_Image = image;
update();
}
*/
M>Window::~Window()
M>{
M> // m_Thread.wait();
M> m_Thread.quit();
M>}
M>
M>В выводе приложения QtCreator'а после закрытия приложения появляются записи: M>QThread: Destroyed while thread is still running
вызван m_Thread.quit();, но нет ожидания его завершения(waitForFinished), потом начинает работать ~Window и m_Thread просто уничтожается во время работы M>QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
когда происходит m_Thread.quit(); начинается завершение потока, finished это уже потока нет, а в нем создан socket, который нужно удалить в том же потоке где он был создан
Ну в примере же по другому
m_Receiver.stop(); // Ставим в очередь потока вызов doStop
m_Thread.quit(); // Завершаем поток
m_Thread.waitForFinished(); // Ждем когда отработает из очереди doStop и нормально завершим внутреннее состояние
Здравствуйте, milkpot, Вы писали:
M>Первая версия принимала пакеты в главном потоке, в результате чего замерзало окно приложения.
Видимо, ты в блокирующем режиме читал данные?
M>Перенос readPendingDatagrams в рабочую нить привел к тому, что приложение перестало принимать пакеты.
Тут могут быть какие-то особенности архитектуры Qt. Я как-то из COM-порта пытался читать средствами Qt, нихрена не читалось, оказалось, что нужен цикл обработки сообщений, хотя, казалось бы, причем тут COM-порт в консольной программе. Пришлось что-то руками дёргать. В основном потоке, где у тебя окно, такой цикл есть, а вот в воркере его вероятно нет, и вероятно, что сокету это нужно тоже, как и COM-порту
M>Wireshark видит, что пакеты приходят к хосту от устройства, а приложение пакеты не видит. M>Вот фрагменты кода
Здравствуйте, Pzz, Вы писали:
M>>Здравствуйте, есть устройство, передающее в хост изображения размером 640Х480 пикселей (каждый пискель — 4 байта) M>>со скоростью 50 кадров в секунду по протоколу Udp. У пикселя один байт в оттенках серого, остальные три — это rgb.
Pzz>640*480*4*50*8/1e6 = 491.52
Pzz>Гм. Полгигабита в секунду для передачи цветной картинки с разрешением VGA — это как-то не очень нормально ИМХО.
Как я понимаю, это довольно стандартная практика. В НИИ, где я работал, коллега из другого отдела делал СТЗ, и с камерой так же по UDP общался. И формат ответа камеры именно что тоже несжатые пиксели были. У него была проблема, что он делал кучу запросов для получения кадра (там по частям надо было запрашивать, целиком нельзя было), и хотел получить потом чтением полную картинку, а у него большая часть картинки куда-то пропадала. Я предположил, что надо запрашивать по мере прихода очередной части, или по таймауту, если часть не пришла — он переделал, и заработало — я на такое в молодости напарывался, UDP любит отбрасывать пакеты, которые не влезают во всякие его буфера, что на маршрутизаторах, что на конечных хостах
Здравствуйте, Marty, Вы писали:
Pzz>>Гм. Полгигабита в секунду для передачи цветной картинки с разрешением VGA — это как-то не очень нормально ИМХО.
M>Как я понимаю, это довольно стандартная практика. В НИИ, где я работал, коллега из другого отдела делал СТЗ, и с камерой так же по UDP общался. И формат ответа камеры именно что тоже несжатые пиксели были.
UDP — это нормально для этого случая. Пакеты иногда теряются, и лучше уж, как в UDP, допустить потерю видеокадра, чем как в TCP, застревать иногда на несколько секунд, занимаясь бессмысленными уже (потому, что фатально запоздалыми) ретрансмитами. И, конечно, надо осмысленно подходить к потере пакетов — делать какой-то ограниченный по времени ретрансмит, возможно, FEH (forward error correction).
А вот гнать на такой скорости некомпрессованный видеопоток. Ну, это только в НИИ так делают.
M>У него была проблема, что он делал кучу запросов для получения кадра (там по частям надо было запрашивать, целиком нельзя было), и хотел получить потом чтением полную картинку, а у него большая часть картинки куда-то пропадала. Я предположил, что надо запрашивать по мере прихода очередной части, или по таймауту, если часть не пришла — он переделал, и заработало — я на такое в молодости напарывался, UDP любит отбрасывать пакеты, которые не влезают во всякие его буфера, что на маршрутизаторах, что на конечных хостах
Я, вообще-то, эта. Сетевик-затейник. И медиастримингом тоже нормально занимался. Я даже умудрился попасть в соавторы американского патента на эту тему
Здравствуйте, Pzz, Вы писали:
Pzz>А вот гнать на такой скорости некомпрессованный видеопоток. Ну, это только в НИИ так делают.
У нас в НИИ ничего не переизобретали, у нас в только пользовались существующим от ведущих производителей. Но если ты такой тугой, то флаг тебе в руки и барабан на шею
Здравствуйте, Marty, Вы писали:
Pzz>>А вот гнать на такой скорости некомпрессованный видеопоток. Ну, это только в НИИ так делают.
M>У нас в НИИ ничего не переизобретали, у нас в только пользовались существующим от ведущих производителей. Но если ты такой тугой, то флаг тебе в руки и барабан на шею
Здравствуйте, milkpot, Вы писали:
M>Здравствуйте, есть устройство, передающее в хост изображения размером 640Х480 пикселей (каждый пискель — 4 байта) M>со скоростью 50 кадров в секунду по протоколу Udp.
Похоже на изобретение велосипеда. Готовых решений — вагон и маленькая тележка. Часть из них опенсорсные.
VNC, RDP и пр. Со сжатием-разжатием картинки на лету.
M>>В выводе приложения QtCreator'а после закрытия приложения появляются записи: M>>QThread: Destroyed while thread is still running I>вызван m_Thread.quit();, но нет ожидания его завершения(waitForFinished), потом начинает работать ~Window и m_Thread просто уничтожается во время работы M>>QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread I>когда происходит m_Thread.quit(); начинается завершение потока, finished это уже потока нет, а в нем создан socket, который нужно удалить в том же потоке где он был создан
I>Ну в примере же по другому I>
I>m_Receiver.stop(); // Ставим в очередь потока вызов doStop
I>m_Thread.quit(); // Завершаем поток
I>m_Thread.waitForFinished(); // Ждем когда отработает из очереди doStop и нормально завершим внутреннее состояние
I>
//----
class Window : public QWidget
{
Q_OBJECT
private:
Worker2 worker;
QThread m_Thread;
Worker3 worker3;
QThread m_Thread3;
};
Window::Window()
{
//---+
worker.moveToThread( &m_Thread );
connect(&m_Thread, &QThread::finished, &worker, &QObject::deleteLater);
connect(&worker, &Worker2::resultReady, this, &Window::appendtoLogListBox);
connect(&worker, &Worker2::sendData, this, &Window::displayImage);
worker.startStrm();
m_Thread.start(QThread::TimeCriticalPriority);
//----
//---+
worker3.moveToThread( &m_Thread3 );
connect(&m_Thread3, &QThread::finished, &worker3, &QObject::deleteLater);
//connect(&worker3, &Worker3::resultReady, this, &Window::appendtoLogListBox);
worker3.startStrm3();
m_Thread3.start(QThread::HighPriority);
//----
}
Window::~Window()
{
worker.stopStrm();
m_Thread.quit();
b_result=m_Thread.wait(200);
qDebug() << "m_Thread.wait(200) = " << b_result;// Не печатается
//--- Если Worker3 нет, то печатается
worker3.stopStrm3();
m_Thread3.quit();
b_result=m_Thread3.wait(200);
qDebug() << "m_Thread3.wait(200) = " << b_result;// Не печатается
}
void Window::displayImage(const QImage& img)
{
img1=img;
helper.img2=img1;
update();
return;
}
void Window::launchWrite()
{
QByteArray byte_array;
byte_array.resize(648);
for(int j=0;j<480;j++)
{
for(int i=0;i<648;i++)
{
byte_array[i]=i;
}
// worker.writeToUdp(byte_array);
worker3.writeToUdp3(byte_array);// Выводится сообщение в консоль QtCreator:
//QObject: Cannot create children for a parent that is in a different thread.
//(Parent is QUdpSocket(0x1e03938), parent's thread is QThread(0x61fdc4), current thread is QThread(0x1692d80)
//Или надо делать вызов через signal-slot?
//Если убрать Worker3 и реализовать через Worker2, то сообщение не появляется
//----
}
return;
}
Не понимание времени жизни объектов
1) Если нет new не нужно deleteLater, ну или delete, это двойное удаление, то что на стэке само удалится
2) Если Qt объекту передали родителя, то при уничтожении родителя он удаляет своих детей. Поэтому тут тоже двойное удаление
m_pSocket.reset( new QUdpSocket(this) );
а) Удаляется по this
б) Удаляется при уничтожении QScopedPointer
M>//---- M>
M>private:
M> Worker2 worker;
M> QThread m_Thread;
M> Worker3 worker3;
M> QThread m_Thread3;
M>};
M>Window::Window()
M>{
M> connect(&m_Thread, &QThread::finished, &worker, &QObject::deleteLater);
M> connect(&m_Thread3, &QThread::finished, &worker3, &QObject::deleteLater);
// не нужно так как на стэке worker, m_Thread, worker3, m_Thread3 удалятся сами после деструктора ~Window
M>}
M>Window::~Window()
M>{
M> worker.stopStrm();
M> m_Thread.quit();
M> b_result=m_Thread.wait(200);
//При waitForFinished зависает?
M>}
M>void Window::displayImage(const QImage& img)
M>{
M> helper.img2=img;
M> update();
M>}
M>void Window::launchWrite()
M>{
M> QByteArray byte_array;
M> worker3.writeToUdp3(byte_array);// Выводится сообщение в консоль QtCreator:
M>//QObject: Cannot create children for a parent that is in a different thread.
M>//(Parent is QUdpSocket(0x1e03938), parent's thread is QThread(0x61fdc4), current thread is QThread(0x1692d80)
M>//Или надо делать вызов через signal-slot?
M>//Если убрать Worker3 и реализовать через Worker2, то сообщение не появляется
M>
worker3.writeToUdp3 это вызов метода напрямую из UI потока, при этом и worker3 и m_pSocket3 в другом потоке/
Поэтому
вариант 1: убирай поток,
вариант 2: делай signal из Window и connect на Worker
class Window
signal:
void writeToUdp(QByteArray)
Window::Window() {
connect(this, &Window::writeToUdp, &worker2, &Worker2::writeToUdp3);
void Window::launchWrite() {
//...
emit writeToUdp(byte_array);
}
class Worker3 : public QObject {
public slots:
void writeToUdp3(const QByteArray& qBinArray)
вариант 3:
через очередь потока
class Worker3 : public QObject {
public:
void writeToUdp3(const QByteArray& qBinArray)
{
// не помню как аргументы передавать, псевдокод
QMetaObject::invokeMethod(this, "doWriteToUdp3", Q_ARG(qBinArray), Qt::QueuedConnection);
}
public:
void doWriteToUdp3(const QByteArray& qBinArray)
M>
M>class Worker2 : public QObject
M>{
M> QScopedPointer<QUdpSocket> m_pSocket;
M> void doStart()
M> {
M> m_pSocket.reset( new QUdpSocket(this) );
//Не нужно this передавать
M>//--- Вводим класс Worker3, чтобы производить запись датаграмм в сокет
M>class Worker3 : public QObject
M>{
M>public slots:
M> void doStart3()
M> {
M> m_pSocket3.reset( new QUdpSocket(this) );
//Не нужно this передавать
M> }
M>
Я выбрал второй вариант.
При подаче питания на устройство на текущий момент передаются кадры (изображения) на хост. Если приложение закрыть в момент передачи
кадров, то в окне вывода приложения QtCreator'а появятся сообщения
m_Thread.wait(200) = false
m_Thread3.wait(200) = true
QThread: Destroyed while thread is still running
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
QSocketNotifier: Multiple socket notifiers for same socket 1032 and type Read
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
QSocketNotifier: Multiple socket notifiers for same socket 1032 and type Read
QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread
QUdpSocket::hasPendingDatagrams() called on a QUdpSocket when not in QUdpSocket::BoundState
Если сначала отключить устройство, то в окно вывода приложения выводятся только
m_Thread.wait(200) = true
m_Thread3.wait(200) = true
Здравствуйте, milkpot, Вы писали:
M>m_Thread.wait(200) = false M>m_Thread3.wait(200) = true
А это требования что при закрытии есть только 200мс на остановку? M>QThread: Destroyed while thread is still running M>QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread M>QSocketNotifier: Multiple socket notifiers for same socket 1032 and type Read M>QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread M>QSocketNotifier: Multiple socket notifiers for same socket 1032 and type Read M>QSocketNotifier: Socket notifiers cannot be enabled or disabled from another thread M>QUdpSocket::hasPendingDatagrams() called on a QUdpSocket when not in QUdpSocket::BoundState
Собственно все тоже самое что до этого, за 200мс не успели остановиться, после начинается уничтожение объектов в том состоянии в котором они находятся(поток работает, сокет в этом работающем потоке), ну и вот такие ошибки
M>Если сначала отключить устройство, то в окно вывода приложения выводятся только M>m_Thread.wait(200) = true M>m_Thread3.wait(200) = true
Тут успели остановиться за 200мс всё хорошо.
Ну если это прям требования, что так мало времени есть на остановку, то вводи дополнительно ручную синхронизацию.
Сейчас ситуация, в очереди потока есть слоты которые должны отработать, последовательно, и дойти очередь до слота остановки(или readPendingDatagrams просто не заканчивается так как слишком много данных шлется). Обработку при остановке надо ускорить. Грубо