Как использовать boost.Asio с MJPEG?

Я хочу транслировать изображения OpenCV (поступающие с камеры) на удаленный компьютер в режиме реального времени, это должно быть сделано через Ethernet. Изображения непрерывно принимаются в стандартных объектах OpenCV Mat. Окончательный код должен быть интегрирован в приложение C ++ (Qt).

я нашел этот скрипт Python это делает работу очень хорошо.

Теперь я пытаюсь получить C ++ эквивалент этого кода, мне удалось создать HTTP-сервер, используя Boost Asio и Простой веб-сервер проект. Я могу отобразить статичное синее изображение/ изображение с веб-камеры (не обновляется).

Я написал код, но он не работает. Я предполагаю, что данные отправляются только при возврате функции (которая никогда не возвращается).
Как я могу заставить отправлять данные после каждой итерации цикла while?

#include "server_http.hpp"
#include <thread>
#include <boost/chrono.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <opencv2/opencv.hpp>
//#include <opencv/cv.h>

using namespace boost::posix_time;
typedef SimpleWeb::Server<SimpleWeb::HTTP> HttpServer;

cv::Mat image;
cv::VideoCapture cap;

int main()
{
cap.open(0);
if (!cap.isOpened ())
{
std::cerr << "Could not initialize capturing" << std::endl;
return (-1);
}
cap >> image;

HttpServer server(8080, 2);

// Image resource is requested
server.resource["^/cam.mjpg"]["GET"] =
[=](HttpServer::Response& response, std::shared_ptr<HttpServer::Request> request)
{
time_facet *facet = new time_facet("%d-%b-%Y %H:%M:%S");
std::cout.imbue(std::locale(std::cout.getloc(), facet));
std::cout << second_clock::local_time() << " | " << "Camera image requested!" << std::endl;

response <<
"HTTP/1.1 200 OK\r\n""Content-type: multipart/x-mixed-replace; boundary=--jpgboundary";
//TODO: Send header

while (1) // TODO: Allow exiting this
{
std::cout << "Send image" << std::endl;
cap >> image;
// Encode mat to jpg and copy it to content
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());

response << "--jpgboundary\r\n" << // Signal we start a new image
"Content-type: image/jpeg" <<
"Content-Length: " << img_content.length() << "\r\n" <<
"\r\n" << img_content << "\r\n";
std::this_thread::sleep_for(std::chrono::milliseconds(400));
}
};

// Anything else is requested
server.default_resource["GET"] = [](HttpServer::Response& response, std::shared_ptr<HttpServer::Request> request)
{
time_facet *facet = new time_facet("%d-%b-%Y %H:%M:%S");
std::cout.imbue(std::locale(std::cout.getloc(), facet));
std::cout << second_clock::local_time() << " | " << request->path << std::endl;

std::string content =
"<html><head></head><body>""<img src=\"cam.mjpg\"/>""</body></html>";
response <<
"HTTP/1.1 200 OK\r\n""Content-Length: " << content.length() << "\r\n""\r\n" << content;
};

std::thread server_thread([&server]()
{
server.start();
});

std::this_thread::sleep_for(std::chrono::seconds(1));
server_thread.join();
return 0;
}

РЕДАКТИРОВАТЬ 1

Основываясь на комментарии Империи Technik, я вернулся к примеры повышения;
В примере с сервером HTTP ответ отправляется, когда обратный вызов возвращается, поэтому я изменил обратный вызов, чтобы do_write() операции над сокетом.

HTML-страница отображается правильно, но изображение не отображается (вместо этого отображается значок разбитого изображения), я пытался увидеть, что происходит с Wireshark, но я не знаю, что не так.

Вот моя функция handle_request: (request_handler.cpp):

void request_handler::handle_request(const request& req, reply& rep, connection &con)
{
// Decode url to path.
std::string request_path;
if (!url_decode(req.uri, request_path))
{
rep = reply::stock_reply(reply::bad_request);
return;
}

// Request path must be absolute and not contain "..".
if (request_path.empty() || request_path[0] != '/'
|| request_path.find("..") != std::string::npos)
{
rep = reply::stock_reply(reply::bad_request);
return;
}

// Determine the file extension.
std::size_t last_slash_pos = request_path.find_last_of("/");
std::string filename;
if (last_slash_pos != std::string::npos)
filename = request_path.substr(last_slash_pos + 1);

if (filename == "cam.mjpg") // Image is requested
{
rep.status = reply::ok;
rep.headers.resize(1);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = "multipart/x-mixed-replace; boundary=--jpgboundary";
rep.content.empty();
con.do_write();
rep.status = reply::none;

while (true) // FIXME: How do I handle disconnection from the client?
{
cv::Mat image(200, 300, CV_8UC3);
int random = rand() % 255 + 1;
image = cv::Scalar(random, 0, 0); // Fill image with blue
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());

rep.headers.clear();
rep.content.clear();

rep.content.append("--jpgboundary\r\n");
con.do_write();

rep.content.clear();
rep.headers.resize(2);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = mime_types::extension_to_type("jpg");
rep.headers[1].name = "Content-length";
rep.headers[1].value = img_content.size();
rep.content.append(img_content);
rep.content.append("\r\n");
con.do_write();

boost::this_thread::sleep(boost::posix_time::milliseconds(500));
}
}
else // Anything but the image is requested
{
std::string content =
"<html><head></head><body>""Hello :)<br>""<img src=\"cam.mjpg\"/>""</body></html>";

rep.status = reply::ok;
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = content.length();
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime_types::extension_to_type("html");
rep.content.append(content);
con.do_write();
return;
}
}

4

Решение

Я получил его, проанализировав пакеты благодаря сетевому анализатору Firefox, я реплицировал ответ с заголовками / содержимым Python, и он отлично работает:

Я добавил reply::none Тип ответа и убедитесь, что если этот ответ был предоставлен, HTTP-статус не отправляется. Так что в reply::to_buffers() Функция, которую я добавил это:

  if (status != none) // Don't add status to buffer if status is "none"buffers.push_back(status_strings::to_buffer(status));

request_handler.cpp код выглядит так:

  if (filename == "cam.mjpg") // Image is requested
{
rep.status = reply::ok;
rep.headers.resize(1);
rep.headers[0].name = "Content-Type";
rep.headers[0].value = "multipart/x-mixed-replace; boundary=--jpgboundary\r\n";
con.do_write();

while (true) // FIXME: How do I handle disconnection from the client?
{
cv::Mat image(200, 300, CV_8UC3);
int random = rand() % 255 + 1;
image = cv::Scalar(random, 0, 0); // Fill image with blue
std::vector<uchar> buf;
cv::imencode(".jpg", image, buf, std::vector<int>());
std::string img_content(buf.begin(), buf.end());

rep.status = reply::none;
rep.headers.resize(0);
rep.content.clear();
rep.content.append("--jpgboundary");
rep.content.append("\r\n");
rep.content.append("Content-Type: image/jpeg");
rep.content.append("\r\n");
rep.content.append("Content-length: "+boost::lexical_cast<std::string>(img_content.length()));
rep.content.append("\r\n");
rep.content.append("\r\n");
rep.content.append(img_content);
rep.content.append("\r\n");
con.do_write();
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
}
}
else // Anything but the image is requested
{
std::string content =
"<html><head></head><body>""<img src=\"cam.mjpg\"/>""</body></html>";

rep.status = reply::ok;
rep.headers.resize(2);
rep.headers[0].name = "Content-Length";
rep.headers[0].value = content.length();
rep.headers[1].name = "Content-Type";
rep.headers[1].value = mime_types::extension_to_type("html");
rep.content.append(content);
con.do_write();
return;
}

Предложения по улучшению приветствуются в комментариях. Я не знаю, как обрабатывать отключение от клиента (выход из цикла while), поэтому на данный момент сервер будет работать только с 1 запросом; после этого он застревает в цикле while.

0

Другие решения

Http_server.hpp

#ifndef HTTPSERVER_HPP_INCLUDED
#define HTTPSERVER_HPP_INCLUDED

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <opencv2/core/core.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
typedef boost::shared_ptr<tcp::socket> socket_ptr;
class HttpServer
{
public:
std::map<std::string, std::string> winnames;
std::map<std::string,int> requestcounts;
std::map<std::string,std::vector<unsigned char> > jpegbuffers;
short port;
HttpServer();
void run(int portno);
boost::shared_mutex mut;
boost::condition_variable_any cond;
int httpdelay;
void IMSHOW(std::string win, cv::Mat mat);
int compression;
bool is_debug;

private:
int it;
void server(int port);
void session(socket_ptr sock);

void handleinfo(socket_ptr sock);
void handlewindows(socket_ptr sock);
void handlemjpeg(socket_ptr sock,std::string winname);
void handlejpg(socket_ptr sock,std::string winname);
void handle404(socket_ptr sock);
};

#endif

Http_server.cpp

#include "http_server.hpp"#include <fstream>
#include <boost/filesystem.hpp>
#include <boost/format.hpp>
#include <opencv2/opencv.hpp>
#include <boost/lexical_cast.hpp>

namespace bfs= boost::filesystem;
using namespace std;
using boost::lexical_cast;

// Helper functions
#if defined(unix)        || defined(__unix)      || defined(__unix__) \
|| defined(linux)       || defined(__linux)     || defined(__linux__) \
|| defined(sun)         || defined(__sun) \
|| defined(BSD)         || defined(__OpenBSD__) || defined(__NetBSD__) \
|| defined(__FreeBSD__) || defined __DragonFly__ \
|| defined(sgi)         || defined(__sgi) \
|| defined(__MACOSX__)  || defined(__APPLE__) \
|| defined(__CYGWIN__)
#define is_nix
#endif

#if defined(_MSC_VER) || defined(WIN32)  || defined(_WIN32) || defined(__WIN32__) \
|| defined(WIN64)    || defined(_WIN64) || defined(__WIN64__)
#define is_win
#endif

#ifdef is_win
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#endif

#ifdef is_nix
#define SLEEP(ms) usleep(ms*1000)
#endif

std::vector<std::string> &dssplit(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s);
std::string item;
while (getline(ss, item, delim)) {
elems.push_back(item);
}
return elems;
}

std::vector<std::string> dssplit(const std::string &s, char delim) {
std::vector<std::string> elems;
return dssplit(s, delim, elems);
}

void removeEmptyStrings(std::vector<std::string>& strings)
{
std::vector<std::string>::iterator it = remove_if(strings.begin(), strings.end(),     mem_fun_ref(&std::string::empty));
strings.erase(it, strings.end());
}

bool hasEnding(std::string const &fullString, std::string const &ending)
{
if (fullString.length() >= ending.length()) {
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), ending));
}
else {
return false;
}
}

bool startswith(std::string const &src, std::string const &start)
{

if (src.compare(0, start.length(), start) == 0)
{
return true;
}
return false;
}

std::string urldecode(std::string &src) {
std::string ret;
char ch;
int ii;
for (size_t i = 0; i<src.length(); i++) {
if (int(src[i]) == 37) {
sscanf(src.substr(i + 1, 2).c_str(), "%x", &ii);
ch = static_cast<char>(ii);
ret += ch;
i = i + 2;
}
else {
ret += src[i];
}
}
return (ret);
}

// Server implementation
HttpServer::HttpServer() :compression(70), is_debug(true), httpdelay(100)
{

}

void HttpServer::IMSHOW(std::string win, cv::Mat mat)
{winnames[win] = lexical_cast<string>(mat.cols) + "," + lexical_cast<string>(mat.rows);

if (is_debug)
{
cv::imshow(win, mat);
}
else
{
//cvDestroyWindow(win.c_str());
}

if (requestcounts[win] > 0)
{
cv::Mat towrite;
if (mat.type() == CV_8UC1)
{
cvtColor(mat, towrite, CV_GRAY2BGR);
}
else if (mat.type() == CV_32FC3)
{
double minVal, maxVal;
minMaxLoc(mat, &minVal, &maxVal);
mat.convertTo(towrite, CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
}
else{
towrite = mat;
}

std::vector<uchar> buffer;
std::vector<int> param(2);
param[0] = CV_IMWRITE_JPEG_QUALITY;
param[1] = compression;
imencode(".jpg", towrite, buffer, param);
jpegbuffers[win].swap(buffer);

}
}
void HttpServer::run(int portno)
{
port=portno;
boost::thread t(boost::bind(&HttpServer::server,this,port));
}
void HttpServer::server(int port)
{
try
{

boost::asio::io_service io_service;
io_service.run();
tcp::acceptor a(io_service, tcp::endpoint(tcp::v4(), port));
for (;;)
{
socket_ptr sock(new tcp::socket(io_service));
a.accept(*sock);
boost::thread t(boost::bind(&HttpServer::session, this, sock));
}
}
catch (boost::exception & e)
{
std::cout << "OMG!" << boost::diagnostic_information(e)<<endl;
}
}
void HttpServer::session(socket_ptr sock)
{
try
{
boost::system::error_code ec;
boost::asio::streambuf sbuffer;
boost::asio::read_until(* sock, sbuffer, "\0", ec );
const char* header=boost::asio::buffer_cast<const char*>(sbuffer.data());
std::string reqStr(header,header+sbuffer.size());
sbuffer.consume(sbuffer.size());
std::vector<std::string> strs;
strs = dssplit(reqStr,' ');
if(strs.size()>1)
{
std::string requesturl = urldecode(strs[1]);
std::vector<std::string> splited=dssplit(requesturl,'/');
removeEmptyStrings(splited);
if(splited.size()==1)
{
if(startswith(splited[0],"windows"))
{
handlewindows(sock);
}else if(startswith(splited[0],"info"))
{
handleinfo(sock);
}else if(hasEnding(splited[0],".mjpg"))
{
handlemjpeg(sock,splited[0].substr(0,splited[0].size()-5));
}else if(hasEnding(splited[0],".jpg") || splited[0].find(".jpg?")!=string::npos)
{
handlejpg(sock,splited[0]);
}else
{
handle404(sock);
}
}else
{
handle404(sock);
}
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
}

}catch(const std::exception& ex)
{
boost::system::error_code ec;
boost::asio::ip::tcp::endpoint endpoint = sock->remote_endpoint(ec);
if(!ec)
{
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
}
//DPRINTERR(ex.what());
}catch(const  std::string& ex)
{
boost::system::error_code ec;
boost::asio::ip::tcp::endpoint endpoint = sock->remote_endpoint(ec);
if(!ec)
{
sock->shutdown(boost::asio::ip::tcp::socket::shutdown_both);
}
}
}

void HttpServer::handleinfo(socket_ptr sock)
{

boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
string retstr;
for (std::map<std::string,std::string>::iterator it=winnames.begin(); it!=winnames.end(); ++it)
{
string wname =it->first;
int rcnt = 0;
if(requestcounts.find(wname)!=requestcounts.end())
{
rcnt=requestcounts[wname];
}

retstr+=boost::str(boost::format("{""\"name\":\"%s\",""\"reqCnt\":%d,""\"size\":\"%s\"""},")
%wname
%rcnt
%it->second
);
}
if(retstr.size()>0) retstr.resize(retstr.size()-1);

retstr=boost::str(boost::format("{""\"windows\":[%s],""\"version\":\"%s\",""\"fps\":%s""}")
%retstr
%"0.0"% to_string(0.)
);
response_stream << "HTTP/1.1 200 OK\r\n";
response_stream << "Access-Control-Allow-Origin: *\r\n";
response_stream << "Content-Type: text/plain\r\n\r\n";
response_stream << retstr << "\r\n\r\n";

boost::asio::write(*sock, sbuffer);
}
void HttpServer::handlewindows(socket_ptr sock)
{

boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
string retstr;
for (std::map<std::string,std::string>::iterator it=winnames.begin(); it!=winnames.end(); ++it)
{
string wname =it->first;
int rcnt = 0;
if(requestcounts.find(wname)!=requestcounts.end())
{
rcnt=requestcounts[wname];
}

retstr+=boost::str(boost::format("{""\"name\":\"%s\",""\"reqCnt\":%d,""\"size\":\"%s\"""},")
%wname
%rcnt
%it->second
);
}
if(retstr.size()>0) retstr.resize(retstr.size()-1);

retstr="{\"windows\":["+retstr+"]}";
response_stream<<"HTTP/1.1 200 OK\r\n""Content-Type: text/plain\r\n\r\n"<<
retstr<<"\r\n\r\n";

boost::asio::write(*sock, sbuffer);

}
void HttpServer::handlemjpeg(socket_ptr sock,std::string winname)
{
if(requestcounts.find(winname)==requestcounts.end())
{
handle404(sock);
return;
}
std::string frame=winname;
//boost::shared_lock<boost::shared_mutex> lock(mut);
//lock.lock();
requestcounts[frame]++;
//lock.unlock();
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
response_stream<<"HTTP/1.1 200 OK\r\n";
response_stream<<"Content-Type: multipart/mixed;boundary=b\r\n";
response_stream<<"Cache-Control: no-store\r\n";
response_stream<<"Pragma: no-cache\r\n";
response_stream<<"Audio Mode : None\r\n";
response_stream<<"Connection: close\r\n";
response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
for(;;)
{
try
{
if( (jpegbuffers.count(frame)<0 ||
jpegbuffers[frame].size()<4) ||
(jpegbuffers[frame][0]!=0xff && jpegbuffers[frame][1]!=0xd8 &&
jpegbuffers[frame][jpegbuffers[frame].size()-2]!=0xff && jpegbuffers[frame][jpegbuffers[frame].    size()-1]!=0xd9))
{

SLEEP(10);
continue;
}
//boost::shared_lock<boost::shared_mutex> lock(mut);
response_stream<<"--b\r\n";
response_stream<<"Content-Type: image/jpeg\r\n";
response_stream<<"Content-length: "<<jpegbuffers[frame].size()<<"\r\n";
response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
boost::asio::write(*sock,boost::asio::buffer(jpegbuffers[frame], jpegbuffers[frame].size()));
//lock.unlock();
SLEEP(httpdelay);
}
catch (std::exception& e)
{
SLEEP(50);
//lock.lock();
requestcounts[frame]--;
//lock.unlock();
return;
}
}
//lock.lock();
requestcounts[frame]--;
//lock.unlock();
}

void HttpServer::handlejpg(socket_ptr sock,std::string winname)
{
if(winname.find("?")!=string::npos)
{
winname = winname.substr(0,winname.find("?"));
}
winname =winname.substr(0,winname.size()-4);
std::string frame=winname;
requestcounts[frame]++;
boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);

jpegbuffers[frame].clear();
for(;;)
{
try
{
if( (jpegbuffers.count(frame)<0 ||
jpegbuffers[frame].size()<4) ||
(jpegbuffers[frame][0]!=0xff && jpegbuffers[frame][1]!=0xd8 &&
jpegbuffers[frame][jpegbuffers[frame].size()-2]!=0xff && jpegbuffers[frame][jpegbuffers[frame].    size()-1]!=0xd9))
{

SLEEP(10);
continue;
}
response_stream<<"HTTP/1.1 200 OK\r\n";
response_stream<<"Content-Type:  image/jpeg\r\n";
response_stream<<"Cache-Control: no-store\r\n";
response_stream<<"Access-Control-Allow-Origin: *\r\n";
response_stream<<"Pragma: no-cache\r\n";
response_stream<<"Content-length: "<<jpegbuffers[frame].size()<<"\r\n";
response_stream<<"Connection: close\r\n";

response_stream<<"\r\n";
boost::asio::write(*sock, sbuffer);
boost::asio::write(*sock,boost::asio::buffer(jpegbuffers[frame], jpegbuffers[frame].size()));

break;
}
catch (std::exception& e)
{

//DPRINTERR( "net exceptoin:"+std::string(e.what()));
SLEEP(50);
requestcounts[frame]--;
return;
}
}
requestcounts[frame]--;
}
void HttpServer::handle404(socket_ptr sock)
{boost::system::error_code error;
boost::asio::streambuf sbuffer;
std::ostream response_stream(&sbuffer);
response_stream<<"HTTP/1.1 404 Not Found\r\n""Content-Type: text/html\r\n""Connection: close\r\n""Content-Length: 132\r\n\r\n""<html>\r\n""<head><title>404 Not Found</title></head>\r\n""<body bgcolor=\"white\">\r\n""<center><h1>404 Not Found</h1></center>\r\n""</body>\r\n""</html>\r\n";
boost::asio::write(*sock, sbuffer);
}

main.cpp

#include <opencv2/opencv.hpp>
#include "http_server.hpp"#include <iostream>
#include <fstream>

using namespace cv;
#define MJPGFILE_BUFFER_SIZE 10240
class MjpgFileCapture{
public:
static double lastframeseen;
MjpgFileCapture() {};
MjpgFileCapture(std::string filepath)
{
filepath_ = filepath;
is_inited_ = false;
skip_ = true;
imgready_ = false;
ff_ = false;
readbytes_ = -2;
i_ = 0;
};

void init();
MjpgFileCapture& operator >>(cv::Mat& out);
private:
std::string filepath_;
bool is_inited_;
std::ifstream ifstream_;
std::vector<char> data_;
bool skip_;
bool imgready_;
bool ff_;//have we seen ff byte?
long long readbytes_;
char ca_[MJPGFILE_BUFFER_SIZE];
int i_;//loop index
};

void MjpgFileCapture::init()
{
is_inited_ = true;
ifstream_ = std::ifstream(filepath_.c_str(), std::ios::binary);
}

MjpgFileCapture& MjpgFileCapture::operator >> (cv::Mat& out)
{
out = Mat();
if (!is_inited_)
{
init();
}while (1)
{
uchar c;
if (readbytes_ != 0 && readbytes_ != -1)
{
if (i_ >= readbytes_)
{
ifstream_.read(ca_, MJPGFILE_BUFFER_SIZE);
readbytes_ = ifstream_.gcount();
i_ = 0;
}
for (; i_ < readbytes_; i_++)
{
c = ca_[i_];
if (ff_ && c == 0xd8)
{
skip_ = false;
data_.push_back((uchar)0xff);
}
if (ff_ && c == 0xd9)
{
imgready_ = true;
data_.push_back((uchar)0xd9);
skip_ = true;
}
ff_ = c == 0xff;
if (!skip_)
{
data_.push_back(c);
}
if (imgready_)
{
if (data_.size() != 0)
{
cv::Mat data_mat(data_);
cv::Mat frame(imdecode(data_mat, 1));
out = frame;
}
else
{
printf("warning:image is ready and data is empty. Likely bug.");
}
imgready_ = false;
skip_ = true;
data_.clear();
return *this;
}
}
}
else
{
//own exception class
throw std::string("zero byte read:probably end of file.");
}
}
return *this;
}
HttpServer* server = 0;
void file_loop()
{

MjpgFileCapture cap("C:/v/frame.mjpg");
while (true)
{
Mat im;
cap >> im;
server->IMSHOW("im", im);
imshow("im", im);
if (waitKey(1) == 27)
exit(0);
}
}
int main(int argc, char** argv)
{
server = new HttpServer;
//server->port = 8080;
server->run(8080);
while (true)
{
try{
file_loop();
}
catch (...)
{

}
}
return 0;
}

использование

0

По вопросам рекламы [email protected]