File indexing completed on 2025-01-05 03:53:06
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2021-07-24 0007 * Description : a MJPEG Stream server to export items on the network. 0008 * 0009 * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "mjpegserver_p.h" 0016 0017 // Must be placed in first to not break Windows Compilation with WinSock API 0018 0019 #ifdef Q_OS_WIN 0020 # ifdef WIN32_LEAN_AND_MEAN // krazy:exclude=cpp 0021 # undef WIN32_LEAN_AND_MEAN 0022 # endif 0023 #endif 0024 0025 // C ANSI includes 0026 0027 #ifndef Q_OS_WIN 0028 # include <sys/socket.h> 0029 #else 0030 # include <windows.h> 0031 # define MSG_NOSIGNAL 0 0032 #endif 0033 0034 // Qt includes 0035 0036 #include <QString> 0037 #include <QBuffer> 0038 #include <QtConcurrent> // krazy:exclude=includes 0039 0040 // Local includes 0041 0042 #include "actionthreadbase.h" 0043 0044 namespace DigikamGenericMjpegStreamPlugin 0045 { 0046 0047 MjpegServer::Private::Private(QObject* const parent) 0048 : QObject(parent), 0049 server (nullptr), 0050 rate (15), 0051 delay (40000) 0052 { 0053 } 0054 0055 MjpegServer::Private::~Private() 0056 { 0057 } 0058 0059 void MjpegServer::Private::setMaxClients(int max) 0060 { 0061 if (isOpened()) 0062 { 0063 server->setMaxPendingConnections(max); 0064 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server max clients:" << maxClients(); 0065 } 0066 } 0067 0068 int MjpegServer::Private::maxClients() const 0069 { 0070 if (isOpened()) 0071 { 0072 return server->maxPendingConnections(); 0073 } 0074 0075 return (-1); 0076 } 0077 0078 int MjpegServer::Private::writeInSocket(int sock, const QByteArray& data) const 0079 { 0080 if (!data.isEmpty()) 0081 { 0082 try 0083 { 0084 return (::send(sock, data.constData(), data.size(), MSG_NOSIGNAL)); 0085 } 0086 catch (int e) 0087 { 0088 qCDebug(DIGIKAM_GENERAL_LOG) << "Socket::send() exception occurred:" << e; 0089 } 0090 } 0091 0092 return (-1); 0093 } 0094 0095 QString MjpegServer::Private::clientDescription(QTcpSocket* const client) const 0096 { 0097 return (QString::fromLatin1("%1:%2").arg(client->peerAddress().toString()) 0098 .arg(client->peerPort())); 0099 } 0100 0101 bool MjpegServer::Private::isOpened() const 0102 { 0103 if (!server) 0104 { 0105 return false; 0106 } 0107 0108 return (server->isListening()); 0109 } 0110 0111 bool MjpegServer::Private::open(const QString& address, int port) 0112 { 0113 server = new QTcpServer(parent()); 0114 0115 connect(server, SIGNAL(newConnection()), 0116 SLOT(slotNewConnection())); 0117 0118 if (!server->listen(address.isEmpty() ? QHostAddress::Any 0119 : QHostAddress(address), 0120 port)) 0121 { 0122 qCWarning(DIGIKAM_GENERAL_LOG) << "Error : couldn't listen with server" 0123 << server->serverAddress() 0124 << "to port" << server->serverPort() << "!"; 0125 close(); 0126 return false; 0127 } 0128 0129 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server address :" << server->serverAddress(); 0130 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server port :" << server->serverPort(); 0131 0132 return true; 0133 } 0134 0135 void MjpegServer::Private::close() 0136 { 0137 if (isOpened()) 0138 { 0139 server->close(); 0140 } 0141 0142 server->deleteLater(); 0143 } 0144 0145 void MjpegServer::Private::start() 0146 { 0147 srvTask = QtConcurrent::run( 0148 0149 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0150 0151 &MjpegServer::Private::writerThread, this 0152 0153 #else 0154 0155 this, &MjpegServer::Private::writerThread 0156 0157 #endif 0158 0159 ); 0160 0161 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server started..."; 0162 } 0163 0164 void MjpegServer::Private::stop() 0165 { 0166 close(); 0167 srvTask.waitForFinished(); 0168 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server stopped..."; 0169 } 0170 0171 void MjpegServer::Private::slotNewConnection() 0172 { 0173 while (server->hasPendingConnections()) 0174 { 0175 QTcpSocket* const client = server->nextPendingConnection(); 0176 0177 if (client) 0178 { 0179 if (!blackList.contains(client->peerAddress().toString())) 0180 { 0181 connect(client, SIGNAL(disconnected()), 0182 this, SLOT(slotClientDisconnected())); 0183 0184 mutexClients.lock(); 0185 { 0186 client->write(QByteArray("HTTP/1.0 200 OK\r\n")); 0187 0188 client->write(QByteArray("Server: digiKamMjpeg/1.0\r\n" 0189 "Accept-Range: bytes\r\n" 0190 "Connection: close\r\n" 0191 "Max-Age: 0\r\n" 0192 "Expires: 0\r\n" 0193 "Cache-Control: no-cache, private\r\n" 0194 "Pragma: no-cache\r\n" 0195 "Content-Type: multipart/x-mixed-replace; boundary=--mjpegstream\r\n" 0196 "\r\n")); 0197 0198 clients.push_back(client); 0199 0200 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server new client :" << clientDescription(client); 0201 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server total clients :" << clients.count(); 0202 } 0203 mutexClients.unlock(); 0204 } 0205 else 0206 { 0207 client->close(); 0208 } 0209 } 0210 } 0211 } 0212 0213 void MjpegServer::Private::slotClientDisconnected() 0214 { 0215 QTcpSocket* const client = dynamic_cast<QTcpSocket*>(sender()); 0216 0217 if (!client) 0218 { 0219 return; 0220 } 0221 0222 mutexClients.lock(); 0223 { 0224 int index = clients.indexOf(client); 0225 0226 if (index != -1) 0227 { 0228 clients.removeAt(index); 0229 0230 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server client disconnected :" << clientDescription(client); 0231 qCDebug(DIGIKAM_GENERAL_LOG) << "MJPEG server total clients :" << clients.count(); 0232 0233 client->deleteLater(); 0234 } 0235 } 0236 mutexClients.unlock(); 0237 } 0238 0239 // ----------------------------------------------------- 0240 // Multi-threaded methods. 0241 0242 void MjpegServer::Private::writerThread() 0243 { 0244 while (isOpened()) 0245 { 0246 QList <QFuture<void> > sockTasks; 0247 0248 mutexFrame.lock(); 0249 { 0250 mutexClients.lock(); 0251 { 0252 Q_FOREACH (QTcpSocket* const client, clients) 0253 { 0254 sockTasks.append(QtConcurrent::run( 0255 0256 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 0257 0258 &MjpegServer::Private::clientWriteMultithreaded, this, 0259 0260 #else 0261 0262 this, &MjpegServer::Private::clientWriteMultithreaded, 0263 0264 #endif 0265 0266 client->socketDescriptor(), 0267 lastFrame) 0268 ); 0269 } 0270 } 0271 mutexClients.unlock(); 0272 0273 Q_FOREACH (QFuture<void> t, sockTasks) 0274 { 0275 t.waitForFinished(); 0276 } 0277 } 0278 mutexFrame.unlock(); 0279 0280 QThread::usleep(delay); 0281 } 0282 } 0283 0284 void MjpegServer::Private::clientWriteMultithreaded(int client, const QByteArray& data) 0285 { 0286 QString head; 0287 head.append(QLatin1String("--mjpegstream\r\n" 0288 "Content-type: image/jpeg\r\n" 0289 "Content-length: ")); 0290 head.append(QString::number(data.size())); 0291 head.append(QLatin1String("\r\n\r\n")); 0292 0293 // Write header 0294 0295 (void)writeInSocket(client, head.toLatin1()); 0296 0297 // Write image data 0298 0299 (void)writeInSocket(client, data); 0300 (void)writeInSocket(client, QByteArray("\r\n\r\n")); 0301 } 0302 0303 } // namespace DigikamGenericMjpegStreamPlugin 0304 0305 #include "moc_mjpegserver_p.cpp"