В прошлый раз мы решили задачу формирования видео-потока. Теперь займемся сетевой частью. Реализуем передачу видео-потока по сети.
Начнем с исправления одной неприятной ошибки, которая осталась незамеченной в предыдущей части. Она касалась некорректного управления памятью при захвате изображения курсора мыши под Linux.
Проблема заключалась в том, что конструктор QImage
не делает глубокую копию переданного буфера с данными изображения. Поэтому память освобождалась и поведение приложения становилось неопределенным (хоть это и было незаметно).
Немного изменим структуру Cursor
:
struct Cursor {
QImage img;
QPoint pos;
// Linux-reserved
QVarLengthArray< quint32 > buffer;
};
А теперь саму функцию получения курсора для Linux:
Cursor DesktopRecorder::captureCursor() const {
Cursor cursor;
if( auto curImage = XFixesGetCursorImage( QX11Info::display() ) ) {
cursor.buffer.resize( curImage->width * curImage->height );
for( int i = 0; i < cursor.buffer.size(); ++i ) {
cursor.buffer[ i ] = curImage->pixels[ i ] & 0xffffffff;
}
cursor.img = QImage(
reinterpret_cast< const uchar* >( cursor.buffer.data() ),
curImage->width,
curImage->height,
QImage::Format_ARGB32_Premultiplied
);
cursor.pos = QCursor::pos() - QPoint( curImage->xhot, curImage->yhot );
XFree( curImage );
}
return cursor;
}
Проблема решена.
Для сетевого взаимодействия воспользуемся библиотекой LibQxt. О ней мы уже говорили, когда создавали простой чат, поэтому обойдемся без лишних пояснений.
Модуль назовем RemoteControlServer
. Начинаем с заголовочного файла remotecontrolserver.h
:
#ifndef REMOTECONTROLSERVER_H
#define REMOTECONTROLSERVER_H
#include <QObject>
#include <QxtRPCPeer>
#include <desktoprecorder.h>
class RemoteControlServer : public QObject {
Q_OBJECT
public:
explicit RemoteControlServer( QObject* parent = 0 );
bool start();
private slots:
void onClientConnected( quint64 clientID );
void onClientDisconnected( quint64 clientID );
void onEnableCursorCapture( quint64, bool enabled );
void onFrameAvailable( const QImage& frame );
private:
QxtRPCPeer m_peer;
ITNotes::DesktopRecorder m_recorder;
};
#endif // REMOTECONTROLSERVER_H
Далее реализация в remotecontrolserver.cpp
:
#include "remotecontrolserver.h"
#include <shareddefs.h>
#include <QBuffer>
static const QSize SCALED_FRAME_SIZE = QSize( 900, 550 );
RemoteControlServer::RemoteControlServer( QObject* parent ) : QObject( parent ) {
connect( &m_peer, SIGNAL( clientConnected( quint64 ) ), SLOT( onClientConnected( quint64 ) ) );
connect( &m_peer, SIGNAL( clientDisconnected( quint64 ) ), SLOT( onClientDisconnected( quint64 ) ) );
connect( &m_recorder, SIGNAL( frameAvailable( QImage ) ), SLOT( onFrameAvailable( QImage ) ) );
m_peer.attachSlot( ENABLE_CURSOR_CAPTURE_SIG, this, SLOT( onEnableCursorCapture( quint64, bool ) ) );
}
bool RemoteControlServer::start() {
return m_peer.listen( QHostAddress::Any, PORT );
}
void RemoteControlServer::onClientConnected( quint64 clientID ) {
qDebug() << "Client connected:" << clientID;
m_recorder.start( 10 );
}
void RemoteControlServer::onClientDisconnected( quint64 clientID ) {
qDebug() << "Client disconnected:" << clientID;
m_recorder.stop();
}
void RemoteControlServer::onEnableCursorCapture( quint64, bool enabled ) {
m_recorder.enableCursorCapture( enabled );
}
void RemoteControlServer::onFrameAvailable( const QImage& frame ) {
QByteArray ba;
QBuffer buffer( &ba );
frame.scaled( SCALED_FRAME_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation ).save( &buffer, "JPG" );
m_peer.call( FRAME_AVAILABLE_SIG, qCompress( ba, 9 ), frame.size() );
}
Сетевое взаимодействие двунаправленное. Сервер перенаправляем кадры видео-потока, полученные от DesktopRecorder
. А Клиент может включать/отключать захват курсора мыши. Связь осуществляется через именованные функции FRAME_AVAILABLE_SIG
и ENABLE_CURSOR_CAPTURE_SIG
, объявленные в shareddefs.h
:
#ifndef SHAREDDEFS_H
#define SHAREDDEFS_H
#include <QString>
static const char* const FRAME_AVAILABLE_SIG = "FRAME_AVAILABLE_SIG";
static const char* const ENABLE_CURSOR_CAPTURE_SIG = "ENABLE_CURSOR_CAPTURE_SIG";
static const int PORT = 9900;
#endif // SHAREDDEFS_H
В этом же файле задана константа PORT
, которая определяет то, на каком порту работает сервер.
Обратим внимание на то, что Сервер не просто отправляет кадры в том виде, в котором они приходят от DesktopRecorder
, а уменьшает их масштаб, кодирует их в JPG
и дополнительно сжимает с помощью qCompress()
. В зависимости от исходного разрешения кадра размер (в байтах) получившегося изображения оказывается существенно меньше. Однако это приводит к заметной потере качества.
Также мы передаем исходный размер изображения, чтобы потом иметь возможность согласовывать координаты мыши.
Такой метод кодирования очень примитивен, и не рекомендуется для реального использования. При увеличении качества изображения плавность трансляции на клиентской стороне в целом сохраняется за счет буферизации видео-потока, но происходит серьезное запаздывание кадров, что абсолютно неприемлемо для наших дальнейших целей. Более оптимальным было бы жесткое выпадение кадров, но и это не является наилучшим решением, поэтому пока что не будем ничего усложнять.
Алгоритм получился не слишком адаптивным. Я настроил его на оптимальное соотношение частоты кадров/качества картинки для своей локальной сети. На своей системе вы можете покрутить константой SCALED_FRAME_SIZE
, которая определяет масштаб пересылаемого кадра.
Позаимствуем внешний вид клиентского сетевого приложения от виджета DesktopRecorderDemo
из прошлой части. При запуске Сервера на Linux и Клиента на Windows (допустимы и любые другие комбинации) имеем следующее:
Обращаю внимание, что на форме виджета можно указать ip
-адрес или имя хоста для сервера, к которому нужно подключиться.
Заголовочный файл remotecontrolclientwidget.h
:
#ifndef REMOTECONTROLCLIENTWIDGET_H
#define REMOTECONTROLCLIENTWIDGET_H
#include <QWidget>
#include <QxtRPCPeer>
namespace Ui {
class RemoteControlClientWidget;
}
class RemoteControlClientWidget : public QWidget {
Q_OBJECT
public:
explicit RemoteControlClientWidget( QWidget* parent = 0 );
~RemoteControlClientWidget();
private slots:
void onStartStop();
void onFrameAvailable( const QByteArray& imgData, const QSize& realSize );
void onConnectedToServer();
void onDisconnectedFromServer();
void onServerError( const QAbstractSocket::SocketError& error );
void refreshConnection();
private:
Ui::RemoteControlClientWidget* ui;
QxtRPCPeer m_peer;
bool m_connected;
};
#endif // REMOTECONTROLCLIENTWIDGET_H
И реализация remotecontrolclientwidget.cpp
:
#include "remotecontrolclientwidget.h"
#include "ui_remotecontrolclientwidget.h"
#include <shareddefs.h>
#include <QTimer>
static const int CONNECTION_RETRY_TIME_OUT_MSEC = 2 * 1000; // 2 секунды
RemoteControlClientWidget::RemoteControlClientWidget( QWidget* parent ) :
QWidget( parent ),
ui( new Ui::RemoteControlClientWidget ),
m_connected( false ) {
ui->setupUi( this );
ui->stopBn->hide();
connect( ui->startBn, SIGNAL( clicked( bool ) ), SLOT( onStartStop() ) );
connect( ui->stopBn, SIGNAL( clicked( bool ) ), SLOT( onStartStop() ) );
connect( &m_peer, SIGNAL( connectedToServer() ), SLOT( onConnectedToServer() ) );
connect( &m_peer, SIGNAL( disconnectedFromServer() ), SLOT( onDisconnectedFromServer() ) );
connect(
&m_peer,
SIGNAL( serverError( const QAbstractSocket::SocketError& ) ),
SLOT( onServerError( const QAbstractSocket::SocketError& ) )
);
m_peer.attachSlot( FRAME_AVAILABLE_SIG, this, SLOT( onFrameAvailable( QByteArray, QSize ) ) );
m_peer.attachSignal( ui->captureCursorChkBox, SIGNAL( clicked( bool ) ), ENABLE_CURSOR_CAPTURE_SIG );
}
RemoteControlClientWidget::~RemoteControlClientWidget() {
delete ui;
}
void RemoteControlClientWidget::onStartStop() {
if( ui->startBn->isVisible() ) {
refreshConnection();
} else {
m_peer.disconnectServer();
m_connected = false;
}
ui->startBn->setVisible( !ui->startBn->isVisible() );
ui->stopBn->setVisible( !ui->stopBn->isVisible() );
ui->ipEd->setEnabled( !ui->stopBn->isVisible() );
}
void RemoteControlClientWidget::onFrameAvailable( const QByteArray& imgData, const QSize& realSize ) {
Q_UNUSED( realSize )
auto img = QImage::fromData( qUncompress( imgData ), "JPG" );
ui->viewLbl->setPixmap( QPixmap::fromImage( img ).scaled(
ui->viewLbl->size(),
Qt::KeepAspectRatio,
Qt::SmoothTransformation
));
}
void RemoteControlClientWidget::onConnectedToServer() {
qDebug() << "Connected to server";
m_connected = true;
ui->startBn->setVisible( false );
ui->stopBn->setVisible( true );
ui->ipEd->setEnabled( false );
m_peer.call( ENABLE_CURSOR_CAPTURE_SIG, ui->captureCursorChkBox->isChecked() );
}
void RemoteControlClientWidget::onDisconnectedFromServer() {
qDebug() << "Disconnected from server";
m_connected = false;
}
void RemoteControlClientWidget::onServerError( const QAbstractSocket::SocketError& error ) {
qDebug() << "Server error:" << QString::number( error, 16 );
m_connected = false;
if( ui->stopBn->isVisible() ) {
QTimer::singleShot( CONNECTION_RETRY_TIME_OUT_MSEC, this, SLOT( refreshConnection() ) );
}
}
void RemoteControlClientWidget::refreshConnection() {
if( !m_connected ) {
m_peer.connect( ui->ipEd->text(), PORT );
}
}
Видео-трансляции так или иначе передается по сети. Качество картинки заметно уступает TeamViewer-у, и ближе к тому, что может предложить Skype в режиме показа экрана. Но это уже вопрос технологий кодирования видео-потоков, поэтому отложим его до лучших времен. Следующий шаг - передача действий мыши…
Скачать исходники примера RemoteControlDemo
Также они доступны на github: https://github.com/itnotesblog/RemoteControlDemo под тэгом v.0.2.1
.
Сборка проекта проверялась под Linux и Windows с Qt 4.8.4 и компиляторами gcc (x64) и msvc.2010 (32-bit) соответственно.
Обратите внимание, что у проекта есть зависимость от LibQxt. Вам понадобится либо использовать совместимый компилятор, либо пересобрать LibQxt самостоятельно из исходников.
Для Windows-версии dll
-файлы LibQxt расположены в lib.win32/LibQxt/
. Не забудьте скопировать их в bin/(debug|release)/
.
Anonymous:
bin/(debug|release)/ это надо скопировать в директорию tq или проекта ? тогда подключится библиотека ?
В директорию проекта. Так, чтобы dll-библиотеки LibQxt лежали рядом с получившимися исполняемыми файлами RemoteControlServer.exe и RemoteControlClient.exe.
Программа завершился с кодом -1073741515 как это исправить ?
Anonymous:
Программа завершился с кодом -1073741515 как это исправить ?
Здравствуйте. Какая именно программа (Клиент/Сервер или оба)? Насколько я понимаю, запуск осуществляется под Windows? С какой версией компилятора и Qt работаете? Ошибка выдается в сборке Release/Debug или в обоих случаях?
Не удалось запустить программу. Путь или права недопустимы?
C:\Qt\qtcreator-2.5.2\RemoteControlDemo-v.0.2.1\bin\debug\RemoteControlClient.exe завершился с кодом -1 Клиент не запускается
C:\Qt\qtcreator-2.5.2\RemoteControlDemo-v.0.2.1\bin\debug\RemoteControlServer.exe завершился с кодом -1 сервер тоже в режиме отладки не запускается . Запуск под Windows ,Qt 4.8.4 ,Qt Creator v2.5.2 ,компилятор msvc.2010 (32-bit).
Anonymous:
сервер тоже в режиме отладки не запускается
А как дела с Релизом? Стартует ли DesktopRecorderDemo.exe? Пробовали запускаться и из Qt Creator, и из файловой системы?
Могу посоветовать приложение Dependency Walker. Оно позволяет исследовать зависимости исполняемых файлов от библиотек. Возможно, какой-то из dll-файлов не виден. В Дебаге Клиент с Сервером дополнительно должны зависеть от QxtCored.dll и QxtNetworkd.dll (кроме основных зависимостей), а в Релизе d на конце имен библиотек исчезает - QxtCore.dll и QxtNetwork.dll.
Дополнительно можете попробовать провести сборку на чистом проекте из консоли Visual Studio Command Prompt (2010), которая находится среди инструментов Microsoft Visual Studio 2010 --> Visual Studio Tools. Для этого достаточно выполнить команды "qmake && nmake" (без кавычек).
Если в системе установлено несколько сборок Qt, то очень похоже на путаницу в переменной среды окружения Path. Попробуйте ее перепроверить.
клиент работает а сервер собрался с ошибкой запускал через Qt 4.8.4 Command Promt Ошибка link Fatal error Lin1104 не удается открыть DesktopRecorder.lib . Что делать ?
Anonymous:
клиент работает а сервер собрался с ошибкой запускал через Qt 4.8.4 Command Promt Ошибка link Fatal error Lin1104 не удается открыть DesktopRecorder.lib . Что делать ?
А сборка осуществляется из корневого каталога проекта? Убедитесь, что библиотека DesktopRecorder собралась, а в lib.win32/ появились файлы DesktopRecorder.lib и DesktopRecorder.dll.
она наверно не собралась вот путь как я собирал C:\Qt\qtcreator-2.5.2\RemoteControlDemo-v.0.2.1\src\RemoteControlServer
Anonymous:
она наверно не собралась вот путь как я собирал C:\Qt\qtcreator-2.5.2\RemoteControlDemo-v.0.2.1\src\RemoteControlServer
Попробуйте выполнить "qmake && nmake" из чистого каталога проекта в C:\Qt\qtcreator-2.5.2\RemoteControlDemo-v.0.2.1\
да работает спасибо =)
Пожалуйста :)
А как установить Qt 4.8.4 Linux Ubuntu 14.04 ?
Anonymous:
А как установить Qt 4.8.4 Linux Ubuntu 14.04 ?
Для Linux не так принципиальна точная версия Qt. Можете попробовать с любой сборкой Qt4.
Единственная важная деталь - я собирал LibQxt в 64-бит, поэтому и Qt нужен соответствующий.
:-1: ошибка: skipping incompatible /home/project/RemoteControlDemo-v.0.2.1//lib.linux//LibQxt//libQxtCore.so when searching for -lQxtCore
:-1: ошибка: cannot find -lQxtCore
:-1: ошибка: skipping incompatible /home/project/RemoteControlDemo-v.0.2.1//lib.linux//LibQxt//libQxtNetwork.so when searching for -lQxtNetwork
:-1: ошибка: cannot find -lQxtNetwork
:-1: ошибка: collect2: error: ld returned 1 exit status
как исправить ошибки
Здравствуйте. Сообщите версию gcc (команда "gcc -v") и Qt (команда "qmake -v"), используемые для сборки.
Qt 4.8.6 GCC 4.8.3 20140627 gcc-4_8-branch revision 212064
Anonymous:
Qt 4.8.6 GCC 4.8.3 20140627 gcc-4_8-branch revision 212064
Ошибка про "skipping incompatible … when searching for" явно указывает на несовместимость архитектур (32/64-bit) сборки и линкуемой библиотеки.
Вы собираете проект прямо из консоли? Прошу все же прислать сюда полный вывод команды "gcc -v" (без кавычек).
А почему в папке релиз нету исполняемого файла программы ?
Anonymous:
А почему в папке релиз нету исполняемого файла программы ?
Под какой системой осуществляется сборка? Выводятся ли какие-нибудь ошибки?
линукс ошибок нет программа работает а выходных файлов нету
есть только Make file ?
Anonymous:
линукс ошибок нет программа работает а выходных файлов нету
Похоже, что либо собирается debug, либо используется режим Qt Creator "Shadow build".
debug тоже нету а как по русски этот режим называется ?
Shadow build = Теневая сборка
сборка под виндовс получил EXE файлы но при запуске пишет нет соответствующих библиотек как запустить программу под виндовс ?
Anonymous:
сборка под виндовс получил EXE файлы но при запуске пишет нет соответствующих библиотек как запустить программу под виндовс ?
Говорится ли о том, каких именно библиотек не хватает? Скопированы ли dll-файлы из lib.win32/LibQxt/ в каталог к исполняемым?
не хватает библиотек QtGui4.dll , QxtCore.dll
Anonymous:
не хватает библиотек QtGui4.dll , QxtCore.dll
QtGui4.dll - библиотека, которая находится в bin-каталоге Qt, а QxtCore.dll прилагается к проекту, и расположена в lib.win32/LibQxt/.
Вероятно, понадобятся и другие библиотеки. Все они явно присутствуют на компьютере, если сборка проекта прошла успешно. Можете найти их через функцию поиска Windows.
да я нашел библиотеки приложение запустилось но нет изображения рабочего стола какой то библиотеки не хватает ?
Anonymous:
да я нашел библиотеки приложение запустилось но нет изображения рабочего стола какой то библиотеки не хватает ?
Если бы не хватало библиотек, то приложение не запустилось бы.
Требуется уточнить, о каком именно приложении идет речь. Работает ли сервер? Реагирует ли он на подключение от клиента?
Для начала можно попробовать DesktopRecorderDemo.exe. С ним проблем быть не должно.
DesktopRecorderDemo.exe он работает сервер подключается и выключается а какртинки на клиенте нету ?
Клиент и сервер запускаются на одном и том же компьютере? Какая версия Windows используется?
И я так и не понял, выводятся ли кадры через DesktopRecorderDemo.exe?
Возможно, приложения выдают какие-то предупреждения? Проблема может быть в отсутствии плагина для кодирования/декодирования Jpeg, но это маловероятно.
Хотя не так уж и маловероятно, что не находит плагины, если не находило QtGui4.dll. Попробуйте скопировать к исполняемым файлам проекта весь каталог imageformats/ (не содержимое, а именно каталог) из директории plugins/, который находится в корневом каталоге установленной версии Qt. Должно помочь.
Но лучше все же настроить пути в переменных окружения.
Anonymous
bin/(debug|release)/ это надо скопировать в директорию tq или проекта ? тогда подключится библиотека ?