Compare commits
12 Commits
54246257c9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
bf682e0756
|
|||
|
126eba308b
|
|||
|
80690945fe
|
|||
|
5f57f4631b
|
|||
|
2f991b1eaa
|
|||
|
4372684d00
|
|||
|
e3af31f1e0
|
|||
|
ed8c594a30
|
|||
|
ea952e1981
|
|||
|
c85581749b
|
|||
|
a134513b9c
|
|||
|
7c780e0017
|
46
README.md
Normal file
46
README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Video Streaming Proof of Concept
|
||||
|
||||
This project is a demo for streaming video output from multiple "client" devices to one "server". This is a basic demo
|
||||
of what sauron-cv seeks to accomplish.
|
||||
|
||||
## Building
|
||||
|
||||
This project uses [Meson](https://mesonbuild.com/) for build management. This project also optionally uses
|
||||
[Nix](https://nix.dev/) for system dependency management.
|
||||
|
||||
### Using Nix
|
||||
|
||||
```shell
|
||||
nix develop
|
||||
```
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
Install the following packages from your distribution package manager:
|
||||
- `meson`
|
||||
- `opencv`
|
||||
- `boost`
|
||||
|
||||
A better procedure for this (hopefully involving Meson) will be added / documented at a later date.
|
||||
|
||||
### Setup Meson
|
||||
|
||||
```shell
|
||||
meson setup build
|
||||
```
|
||||
|
||||
*NOTE FOR JETBRAINS / CLION USERS: PLEASE SET YOUR MESON BUILD DIRECTORY TO `build` IN THE IDE SETTINGS UNDER "Build /
|
||||
Execution / Deployment" -> "Meson"*
|
||||
|
||||
### Compiling
|
||||
|
||||
```shell
|
||||
meson build client # for client only
|
||||
meson build server # for server only
|
||||
```
|
||||
|
||||
## Running
|
||||
```shell
|
||||
./build/client # for client application
|
||||
./build/server # for server application
|
||||
```
|
||||
54
client.cpp
54
client.cpp
@@ -1,8 +1,60 @@
|
||||
#include <opencv2/videoio.hpp>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include "transfer.h"
|
||||
#include "logging.h"
|
||||
#include <chrono>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
using namespace std;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
int main() {
|
||||
cout << "Hello World!" << endl;
|
||||
const int FRAME_DELAY_MS = 1000 / 30;
|
||||
|
||||
// create video capture
|
||||
cv::VideoCapture cap = cv::VideoCapture(0);
|
||||
try {
|
||||
boost::asio::io_context io_context;
|
||||
tcp::resolver resolver(io_context);
|
||||
|
||||
tcp::resolver::results_type endpoints =
|
||||
resolver.resolve("127.0.0.1", "8080");
|
||||
|
||||
cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
|
||||
|
||||
info("Ready to connect.");
|
||||
// sending connection request
|
||||
tcp::socket socket(io_context);
|
||||
boost::asio::connect(socket, endpoints);
|
||||
|
||||
info("Connected.");
|
||||
// create buffer for serialization
|
||||
vector<uchar> imgbuf;
|
||||
|
||||
while (true) {
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
cap.read(image);
|
||||
trace("Sending image");
|
||||
sendImage(socket, image, imgbuf);
|
||||
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
||||
|
||||
// Calculate the remaining time to sleep
|
||||
auto sleep_duration = std::chrono::milliseconds(FRAME_DELAY_MS) - elapsed_time;
|
||||
|
||||
// Sleep for the remaining duration if positive
|
||||
if (sleep_duration.count() > 0) {
|
||||
std::this_thread::sleep_for(sleep_duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
error(e.what());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756386758,
|
||||
"narHash": "sha256-1wxxznpW2CKvI9VdniaUnTT2Os6rdRJcRUf65ZK9OtE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "dfb2f12e899db4876308eba6d93455ab7da304cd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
63
flake.nix
Normal file
63
flake.nix
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
description = "video-streaming-poc devShell";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in with pkgs;
|
||||
let
|
||||
opencv-custom = pkgs.opencv.override {
|
||||
enableGtk3 = true;
|
||||
#enableCuda = true;
|
||||
enablePython = true;
|
||||
};
|
||||
in {
|
||||
devShells.default = mkShell rec {
|
||||
buildInputs = [
|
||||
# Meson
|
||||
meson
|
||||
pkg-config
|
||||
ninja
|
||||
|
||||
# Boost
|
||||
boost
|
||||
|
||||
# OpenCV
|
||||
opencv-custom
|
||||
];
|
||||
};
|
||||
packages.default = pkgs.stdenv.mkDerivation {
|
||||
name = "video-streaming-poc";
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
meson
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
boost
|
||||
opencv-custom
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
meson setup --wipe build
|
||||
meson compile
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
cp build/client $out/bin/
|
||||
cp build/server $out/bin/
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
170
logging.h
Normal file
170
logging.h
Normal file
@@ -0,0 +1,170 @@
|
||||
#ifndef LOGGING_H
|
||||
#define LOGGING_H
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
|
||||
// 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 false // 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 <typename... Args>
|
||||
void print(Args... args) {
|
||||
(std::cout << ... << args) << "\n";
|
||||
}
|
||||
|
||||
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 && LOGGING_POSITION
|
||||
#include <source_location>
|
||||
|
||||
inline void printPosition(std::source_location& location) {
|
||||
std::cout << location.file_name() << ":" << location.function_name() << ":" << ANSI_CYAN << location.line() << ANSI_RESET << ": ";
|
||||
}
|
||||
|
||||
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 <typename... Args, typename Sl = std::source_location>
|
||||
void trace(Args... args, Sl location = std::source_location::current()) {
|
||||
#if LOGGING >= LOG_TRACE
|
||||
printHeader("TRACE", ANSI_CYAN, location);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args, typename Sl = std::source_location>
|
||||
void debug(Args... args, Sl location = std::source_location::current()) {
|
||||
#if LOGGING >= LOG_DEBUG
|
||||
printHeader("DEBUG", ANSI_MAGENTA, location);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args, typename Sl = std::source_location>
|
||||
void info(Args... args, Sl location = std::source_location::current()) {
|
||||
#if LOGGING >= LOG_INFO
|
||||
printHeader("INFO", ANSI_GREEN, location);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args, typename Sl = std::source_location>
|
||||
void warn(Args... args, Sl location = std::source_location::current()) {
|
||||
#if LOGGING >= LOG_WARN
|
||||
printHeader("WARN", ANSI_YELLOW, location);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args, typename Sl = std::source_location>
|
||||
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 <typename... Args>
|
||||
void trace(Args... args) {
|
||||
#if LOGGING >= LOG_TRACE
|
||||
printHeader("TRACE", ANSI_CYAN);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void debug(Args... args) {
|
||||
#if LOGGING >= LOG_DEBUG
|
||||
printHeader("DEBUG", ANSI_MAGENTA);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(Args... args) {
|
||||
#if LOGGING >= LOG_INFO
|
||||
printHeader("INFO", ANSI_GREEN);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(Args... args) {
|
||||
#if LOGGING >= LOG_WARN
|
||||
printHeader("WARN", ANSI_YELLOW);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void error(Args... args) {
|
||||
#if LOGGING >= LOG_ERROR
|
||||
printHeader("ERROR", ANSI_RED);
|
||||
print(args...);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //LOGGING_H
|
||||
34
meson.build
34
meson.build
@@ -1,13 +1,37 @@
|
||||
project('video-streaming-poc', 'cpp')
|
||||
add_project_arguments('-Wall', '-Wextra', language : 'cpp')
|
||||
#=======================================================================================================================
|
||||
# PROJECT SETTINGS
|
||||
#=======================================================================================================================
|
||||
project('video-streaming-poc', 'cpp', version : '0.0.1-SNAPSHOT',
|
||||
default_options : ['c_std=c17', 'cpp_std=c++20'])
|
||||
|
||||
#=======================================================================================================================
|
||||
# DEPENDENCIES
|
||||
#=======================================================================================================================
|
||||
# opencv dependency
|
||||
opencv = dependency('opencv4', version : '>=4.0.0')
|
||||
opencv_incl_dir = opencv.get_variable(cmake : 'OpenCV_INCLUDE_DIRECTORIES', pkgconfig : 'includedir')
|
||||
include = include_directories(opencv_incl_dir)
|
||||
# boost dependency
|
||||
boost = dependency('boost')
|
||||
|
||||
common = []
|
||||
#=======================================================================================================================
|
||||
# SOURCE FILES
|
||||
#=======================================================================================================================
|
||||
# common files between client / server
|
||||
common = ['transfer.h', 'logging.h']
|
||||
# client-only files
|
||||
client = common + ['client.cpp']
|
||||
# server-only files
|
||||
server = common + ['server.cpp']
|
||||
|
||||
client_exe = executable('client', client, dependencies : opencv, include_directories : include)
|
||||
server_exe = executable('server', server, dependencies : opencv, include_directories : include)
|
||||
#=======================================================================================================================
|
||||
# BUILD TARGETS
|
||||
#=======================================================================================================================
|
||||
# client executable
|
||||
client_exe = executable('client', client,
|
||||
dependencies : [opencv, boost],
|
||||
include_directories : include)
|
||||
# server executable
|
||||
server_exe = executable('server', server,
|
||||
dependencies : [opencv, boost],
|
||||
include_directories : include)
|
||||
56
server.cpp
56
server.cpp
@@ -1,16 +1,56 @@
|
||||
#include <opencv2/highgui.hpp>
|
||||
#include <opencv2/core/mat.hpp>
|
||||
#include <opencv2/imgproc.hpp>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#include "transfer.h"
|
||||
#include "logging.h"
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
using namespace std;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
int main() {
|
||||
cv::VideoCapture cap = cv::VideoCapture(0);
|
||||
bool running = true;
|
||||
cv::Mat image = cv::Mat::zeros(480, 640, CV_8UC3);
|
||||
while (running) {
|
||||
cap.read(image);
|
||||
imshow("image", image);
|
||||
running = cv::waitKey(30) != 27;
|
||||
try {
|
||||
boost::asio::io_context io_context;
|
||||
// creating socket
|
||||
|
||||
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));
|
||||
|
||||
tcp::socket socket(io_context);
|
||||
|
||||
info("Ready to accept connections.");
|
||||
|
||||
// accepting connection request
|
||||
acceptor.accept(socket);
|
||||
info("Client connected.");
|
||||
|
||||
// TODO: handle multiple images
|
||||
cv::Mat image = cv::Mat::zeros(cv::Size(640, 480), CV_8UC3);
|
||||
|
||||
bool running = true;
|
||||
// TODO: make this asynchronous. probably do that in tandem with setting up networking
|
||||
while (running) {
|
||||
// receive data
|
||||
vector<uchar> buffer;
|
||||
trace("Receiving image");
|
||||
int latency = recvImage(socket, buffer);
|
||||
|
||||
trace("Applying new data to image");
|
||||
applyImage(image, &buffer);
|
||||
|
||||
cv::putText(image, to_string(latency), cv::Point(0, 480), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(255, 0, 0), 1, cv::LINE_AA);
|
||||
|
||||
trace("Displaying image");
|
||||
imshow("image", image);
|
||||
running = cv::waitKey(30) != 27;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
error(e.what());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
108
transfer.h
Normal file
108
transfer.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#ifndef TRANSFER_H
|
||||
#define TRANSFER_H
|
||||
|
||||
#include <chrono>
|
||||
#include <opencv2/core/mat.hpp>
|
||||
#include <opencv2/imgcodecs.hpp>
|
||||
#include <sys/socket.h>
|
||||
#include "logging.h"
|
||||
#include <vector>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
using namespace std;
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
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<chrono::milliseconds>(now.time_since_epoch());
|
||||
|
||||
return chrono::milliseconds(ms.count());
|
||||
}
|
||||
|
||||
inline void serializeImage(const cv::Mat& image, std::vector<uchar>& buffer) {
|
||||
cv::imencode(".jpg", image, buffer);
|
||||
}
|
||||
|
||||
void sendImage(tcp::socket& socket, const cv::Mat& image, std::vector<uchar>& buffer) {
|
||||
boost::system::error_code e;
|
||||
|
||||
serializeImage(image, buffer);
|
||||
size_t totalSent = 0;
|
||||
|
||||
// first send the size of the serialized image
|
||||
const size_t size = buffer.size();
|
||||
const chrono::milliseconds timestamp = getMillis();
|
||||
imageHeader header;
|
||||
header.size = size;
|
||||
header.timestamp = timestamp;
|
||||
trace("Buffer size: ", size);
|
||||
if (const ssize_t sent = boost::asio::write(socket, boost::asio::buffer(&header, sizeof(header)), e); sent == -1) {
|
||||
throw boost::system::system_error(e);
|
||||
}
|
||||
|
||||
// then start sending the serialized image
|
||||
while (totalSent < size) {
|
||||
const ssize_t sent = boost::asio::write(socket, boost::asio::buffer(buffer), e);
|
||||
if (sent == -1) {
|
||||
throw boost::system::system_error(e);
|
||||
}
|
||||
totalSent += sent;
|
||||
debug("Packet sent (", sent, " bytes, total ", totalSent, " / ", size, " bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
int recvImage(tcp::socket& socket, std::vector<uchar>& buffer) {
|
||||
boost::system::error_code e;
|
||||
|
||||
// first receive the size of the image
|
||||
imageHeader header;
|
||||
|
||||
size_t len = 0;
|
||||
while (len < sizeof(imageHeader)) {
|
||||
len += socket.read_some(boost::asio::buffer(&header, sizeof(header)), e);
|
||||
if (e.failed()) {
|
||||
throw boost::system::system_error(e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// start receiving the image until the whole thing arrives
|
||||
size_t totalReceived = 0;
|
||||
while (totalReceived < dataSize) {
|
||||
size_t bytesReceived = boost::asio::read(socket, boost::asio::buffer(buffer), e);
|
||||
if (e == boost::asio::error::eof) // Connection closed cleanly by peer.
|
||||
throw boost::system::system_error(e);
|
||||
else if (e)
|
||||
throw boost::system::system_error(e);
|
||||
totalReceived += bytesReceived;
|
||||
debug("Packet received (", bytesReceived, " bytes, total ", totalReceived, " / ", dataSize, " bytes)");
|
||||
}
|
||||
chrono::milliseconds currentTime = getMillis();
|
||||
|
||||
chrono::milliseconds diff = currentTime - sentTime;
|
||||
debug("Packet latency: ", diff.count(), "ms");
|
||||
|
||||
return diff.count();
|
||||
}
|
||||
|
||||
bool applyImage(cv::Mat& image, std::vector<uchar> *src) {
|
||||
// decode the image into an OpenCV Mat
|
||||
cv::imdecode(*src, cv::IMREAD_UNCHANGED, &image);
|
||||
return true;
|
||||
}
|
||||
#endif //TRANSFER_H
|
||||
Reference in New Issue
Block a user