diff --git a/client.cpp b/client.cpp index d8661d9..edc2a54 100644 --- a/client.cpp +++ b/client.cpp @@ -10,7 +10,7 @@ using namespace std; int main() { // create video capture - //cv::VideoCapture cap = cv::VideoCapture(0); + cv::VideoCapture cap = cv::VideoCapture(0); // create socket int clientSocket = socket(AF_INET, SOCK_STREAM, 0); @@ -21,18 +21,20 @@ int main() { serverAddress.sin_port = htons(8080); serverAddress.sin_addr.s_addr = INADDR_ANY; - cv::Mat image = cv::Mat(cv::Size(640, 480), CV_8UC3, cv::Scalar(255, 0, 0)); + cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3); + info("Ready to connect."); // sending connection request connect(clientSocket, reinterpret_cast(&serverAddress), sizeof(serverAddress)); + info("Connected."); // create buffer for serialization vector imgbuf; while (true) { - //cap.read(image); - cout << "Sending image" << endl; + cap.read(image); + trace("Sending image"); sendImage(clientSocket, image, imgbuf); } diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..78e83aa --- /dev/null +++ b/logging.h @@ -0,0 +1,178 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include +#include +#include +#include + +// Logging levels +#define LOG_ERROR 0 +#define LOG_WARN 1 +#define LOG_INFO 2 +#define LOG_DEBUG 3 +#define LOG_TRACE 4 + +// Set logging arguments +// TODO: set these by compiler arguments or env vars or something +#define LOGGING LOG_DEBUG // logging level +#define LOGGING_COLOR true // enable color +#define LOGGING_TIMESTAMP true // enable timestamp +#define LOGGING_TIMESTAMP_FMT "%Y-%m-%dT%H:%M:%S%z" // timestamp format (local time) +#define LOGGING_POSITION true // display position (only works on C++20 or newer) + +// Color codes +#define ANSI_RESET "\033[0m" +#define ANSI_BLACK "\033[30m" /* Black */ +#define ANSI_RED "\033[31m" /* Red */ +#define ANSI_GREEN "\033[32m" /* Green */ +#define ANSI_YELLOW "\033[33m" /* Yellow */ +#define ANSI_BLUE "\033[34m" /* Blue */ +#define ANSI_MAGENTA "\033[35m" /* Magenta */ +#define ANSI_CYAN "\033[36m" /* Cyan */ +#define ANSI_WHITE "\033[37m" /* White */ +#define ANSI_BOLD "\033[1m" /* Bold */ + +template +void print(T t) { + std::cout << t << std::endl; +} + +template +void print(T t, Args... args) { + std::cout << t; + print(args...); +} + +inline void printTimestamp() { + #if LOGGING_TIMESTAMP + auto now = std::chrono::system_clock::now(); + auto time_c = std::chrono::system_clock::to_time_t(now); + std::tm time_tm; + localtime_r(&time_c, &time_tm); + std::cout << std::put_time(&time_tm, LOGGING_TIMESTAMP_FMT) << ": "; + #endif +} + +// if we're on C++20 or later, then use the source_location header and add source location to logs +#if __cplusplus >= 202002L +#include + +inline void printPosition(std::source_location& location) { + #if LOGGING_POSITION + std::cout << location.file_name() << ":" << location.function_name << ":" << ANSI_CYAN << location.line() << ANSI_RESET << ": "; + #endif +} + +inline void printHeader(std::string name, std::string color, std::source_location& location) { + #if LOGGING_COLOR + std::cout << ANSI_BOLD << color << "[" << name << "] " << ANSI_RESET; + printTimestamp(); + printPosition(location); + #else + printHeader(name); + #endif +} + +inline void printHeader(std::string name, std::source_location& location) { + std::cout << "[" << name << "] "; + printTimestamp(); + printPosition(location); +} + +template +void trace(Args... args, Sl location = std::source_location::current()) { + #if LOGGING >= LOG_TRACE + printHeader("TRACE", ANSI_CYAN, location); + print(args...); + #endif +} + +template +void debug(Args... args, Sl location = std::source_location::current()) { + #if LOGGING >= LOG_DEBUG + printHeader("DEBUG", ANSI_MAGENTA, location); + print(args...); + #endif +} + +template +void info(Args... args, Sl location = std::source_location::current()) { + #if LOGGING >= LOG_INFO + printHeader("INFO", ANSI_GREEN, location); + print(args...); + #endif +} + +template +void warn(Args... args, Sl location = std::source_location::current()) { + #if LOGGING >= LOG_WARN + printHeader("WARN", ANSI_YELLOW, location); + print(args...); + #endif +} + +template +void error(Args... args, Sl location = std::source_location::current()) { + #if LOGGING >= LOG_ERROR + printHeader("ERROR", ANSI_RED, location); + print(args...); + #endif +} +#else +inline void printHeader(std::string name, std::string color) { + #if LOGGING_COLOR + std::cout << ANSI_BOLD << color << "[" << name << "] " << ANSI_RESET; + printTimestamp(); + #else + printHeader(name); + #endif +} + +inline void printHeader(std::string name) { + std::cout << "[" << name << "] "; + printTimestamp(); +} + +template +void trace(Args... args) { + #if LOGGING >= LOG_TRACE + printHeader("TRACE", ANSI_CYAN); + print(args...); + #endif +} + +template +void debug(Args... args) { + #if LOGGING >= LOG_DEBUG + printHeader("DEBUG", ANSI_MAGENTA); + print(args...); + #endif +} + +template +void info(Args... args) { + #if LOGGING >= LOG_INFO + printHeader("INFO", ANSI_GREEN); + print(args...); + #endif +} + +template +void warn(Args... args) { + #if LOGGING >= LOG_WARN + printHeader("WARN", ANSI_YELLOW); + print(args...); + #endif +} + +template +void error(Args... args) { + #if LOGGING >= LOG_ERROR + printHeader("ERROR", ANSI_RED); + print(args...); + #endif +} +#endif + +#endif //LOGGING_H diff --git a/meson.build b/meson.build index c7bdc4b..12f52c6 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,8 @@ #======================================================================================================================= # PROJECT SETTINGS #======================================================================================================================= -project('video-streaming-poc', 'cpp') +project('video-streaming-poc', 'cpp', version : '0.0.1-SNAPSHOT', + default_options : ['c_std=c17', 'cpp_std=c++20']) #======================================================================================================================= # DEPENDENCIES @@ -13,7 +14,7 @@ opencv = dependency('opencv4', version : '>=4.0.0') # SOURCE FILES #======================================================================================================================= # common files between client / server -common = ['transfer.h'] +common = ['transfer.h', 'logging.h'] # client-only files client = common + ['client.cpp'] # server-only files diff --git a/server.cpp b/server.cpp index 603137f..924dc72 100644 --- a/server.cpp +++ b/server.cpp @@ -24,12 +24,13 @@ int main() { bind(serverSocket, reinterpret_cast(&serverAddress), sizeof(serverAddress)); + info("Ready to accept connections."); // listening to the assigned socket listen(serverSocket, 5); // accepting connection request - int clientSocket - = accept(serverSocket, nullptr, nullptr); + int clientSocket = accept(serverSocket, nullptr, nullptr); + info("Client connected."); // TODO: handle multiple images cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3); @@ -39,14 +40,13 @@ int main() { while (running) { // receive data vector buffer; - - cout << "Receiving image" << endl; + trace("Receiving image"); recvImage(clientSocket, buffer); - cout << "Applying image" << endl; + trace("Applying new data to image"); applyImage(image, &buffer); - cout << "Displaying image" << endl; + trace("Displaying image"); imshow("image", image); running = cv::waitKey(30) != 27; } diff --git a/transfer.h b/transfer.h index ae96593..c043f50 100644 --- a/transfer.h +++ b/transfer.h @@ -4,7 +4,24 @@ #include #include #include -#include +#include + +using namespace std; + +struct imageHeader { + size_t size; + chrono::milliseconds timestamp; +}; + +chrono::milliseconds getMillis() { + // Get current time + const auto now = chrono::system_clock::now(); + + // Get time since epoch in milliseconds + const chrono::duration ms = chrono::duration_cast(now.time_since_epoch()); + + return chrono::milliseconds(ms.count()); +} inline void serializeImage(const cv::Mat& image, std::vector& buffer) { cv::imencode(".jpg", image, buffer); @@ -16,9 +33,13 @@ int sendImage(int socket, const cv::Mat& image, std::vector& buffer) { // first send the size of the serialized image const size_t size = buffer.size(); - std::cout << "Buffer size: " << size << std::endl; - if (const ssize_t sent = send(socket, &size, sizeof(size), 0); sent == -1) { - perror("Error sending data"); + const chrono::milliseconds timestamp = getMillis(); + imageHeader header; + header.size = size; + header.timestamp = timestamp; + trace("Buffer size: ", size); + if (const ssize_t sent = send(socket, &header, sizeof(header), 0); sent == -1) { + error("Error sending data header"); return -1; } @@ -26,25 +47,27 @@ int sendImage(int socket, const cv::Mat& image, std::vector& buffer) { while (totalSent < size) { const ssize_t sent = send(socket, buffer.data() + totalSent, size - totalSent, 0); if (sent == -1) { - perror("Error sending data"); + error("Error sending data"); return -1; } totalSent += sent; - std::cout << "Packet sent (" << sent << " bytes, total " << totalSent << " bytes)" << std::endl; + debug("Packet sent (", sent, " bytes, total ", totalSent, " bytes)"); } return 0; } int recvImage(int socket, std::vector& buffer) { - size_t dataSize; - // first receive the size of the image - ssize_t sizeReceived = recv(socket, &dataSize, sizeof(size_t), 0); - if (sizeReceived <= 0) { + imageHeader header; + if (ssize_t recvd = recv(socket, &header, sizeof(imageHeader), 0); recvd <= 0) { + error("Error receiving data header"); return -1; } - std::cout << "Buffer size: " << dataSize << std::endl; + size_t dataSize = header.size; + chrono::milliseconds sentTime = header.timestamp; + + trace("Buffer size: ", dataSize); // resize the buffer to fit the whole image buffer.resize(dataSize); @@ -54,18 +77,23 @@ int recvImage(int socket, std::vector& buffer) { while (totalReceived < dataSize) { ssize_t bytesReceived = recv(socket, buffer.data() + totalReceived, dataSize, 0); if (bytesReceived <= 0) { + error("Error receiving data"); buffer.clear(); return -1; } totalReceived += bytesReceived; - std::cout << "Packet received (" << bytesReceived << " bytes, total " << totalReceived << " bytes)" << std::endl; + debug("Packet received (", bytesReceived, " bytes, total ", totalReceived, " bytes)"); } + chrono::milliseconds currentTime = getMillis(); + + chrono::milliseconds diff = currentTime - sentTime; + debug("Packet latency: ", diff.count(), "ms"); return 0; } bool applyImage(cv::Mat& image, std::vector *src) { // decode the image into an OpenCV Mat - cv::imdecode(*src, 0, &image); + cv::imdecode(*src, cv::IMREAD_UNCHANGED, &image); return true; } #endif //TRANSFER_H