This commit is contained in:
amazing-hash 2024-04-20 23:24:39 +04:00
parent 1405d25d0b
commit 305f291616
12 changed files with 192 additions and 164 deletions

View File

@ -88,5 +88,6 @@
"filesystem": "cpp",
"shared_mutex": "cpp",
"stdfloat": "cpp"
}
},
"C_Cpp.default.cppStandard": "c++20"
}

View File

@ -56,6 +56,8 @@ include_directories( ${Boost_INCLUDE_DIR} )
set(SRC_FILES
src/main.cpp
src/server.cpp
src/session.cpp
src/state/state.cpp
src/utils.cpp
)

View File

@ -4,6 +4,7 @@
#include <boost/program_options.hpp>
#include <boost/version.hpp>
#include <csignal>
#include <iostream>
#include <thread>
#include "../../version.h"

16
va-hls/src/server.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "server.h"
namespace va {
void Server::do_accept() {
acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
BOOST_LOG_TRIVIAL(debug) << "creating session on: " << socket.remote_endpoint().address().to_string()
<< ":" << socket.remote_endpoint().port();
std::make_shared<Session>(std::move(socket))->start();
} else {
BOOST_LOG_TRIVIAL(error) << ec.message() << std::endl;
}
do_accept();
});
}
} // namespace va

View File

@ -18,19 +18,7 @@ namespace va {
BOOST_LOG_TRIVIAL(info) << "started HLS Server on port " << port;
do_accept();
}
void do_accept() {
acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
BOOST_LOG_TRIVIAL(debug)
<< "creating session on: " << socket.remote_endpoint().address().to_string() << ":"
<< socket.remote_endpoint().port();
std::make_shared<Session>(std::move(socket))->start();
} else {
BOOST_LOG_TRIVIAL(error) << ec.message() << std::endl;
}
do_accept();
});
}
void do_accept();
private:
boost::asio::ip::tcp::acceptor acceptor_;

114
va-hls/src/session.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "session.h"
#include <boost/log/trivial.hpp>
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
constexpr size_t buffer_length = 4 * 1024;
const char *BAD_REUEST =
"HTTP/1.1 400\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccept-Ranges: bytes\r\n\r\n";
const size_t BAD_REUEST_SIZE = strlen(BAD_REUEST);
const char *NOT_FOUND_REUEST =
"HTTP/1.1 404\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccept-Ranges: bytes\r\n\r\n";
const size_t NOT_FOUND_REUEST_SIZE = strlen(NOT_FOUND_REUEST);
namespace va {
void Session::start() {
auto self(shared_from_this());
boost::asio::async_read_until(
socket_, in_buffer_, "\r\n\r\n", [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) {
std::string req{std::istreambuf_iterator<char>(&in_buffer_), std::istreambuf_iterator<char>()};
try {
auto uri = fetch_uri(req);
if (uri.ends_with(".m3u8")) {
// Запрос playlist
auto playlist_opt = make_playlist(uri);
if (playlist_opt.has_value()) {
auto playlist = playlist_opt.value();
auto resp = std::format(
"HTTP/1.1 200\r\nContent-Type: application/x-mpegURL\r\nContent-Length: "
"{}\r\nAccept-Ranges: bytes\r\n\r\n{}",
playlist.size(), playlist);
std::ostream out(&out_buffer_);
std::copy(resp.begin(), resp.end(), std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} else {
std::ostream out(&out_buffer_);
std::copy(NOT_FOUND_REUEST, NOT_FOUND_REUEST + NOT_FOUND_REUEST_SIZE,
std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} else if (uri.ends_with(".ts")) {
// Запрос сегмента
auto path = std::format("/tmp/va{}", uri);
std::ifstream file(path, std::ios::binary);
std::vector<char> data;
std::copy(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>(),
std::back_insert_iterator(data));
auto resp = std::format("HTTP/1.1 200\r\nContent-Type: video/mp2t\r\nContent-Length: "
"{}\r\nAccept-Ranges: bytes\r\n\r\n",
data.size());
std::ostream out(&out_buffer_);
std::copy(resp.begin(), resp.end(), std::ostream_iterator<char>(out));
std::copy(data.begin(), data.end(), std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} else {
// Не валидный запрос
BOOST_LOG_TRIVIAL(error) << "Invalid request";
std::ostream out(&out_buffer_);
std::copy(BAD_REUEST, BAD_REUEST + BAD_REUEST_SIZE, std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} catch (fs::filesystem_error const &ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
std::ostream out(&out_buffer_);
std::copy(NOT_FOUND_REUEST, NOT_FOUND_REUEST + NOT_FOUND_REUEST_SIZE,
std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} catch (std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
std::ostream out(&out_buffer_);
std::copy(BAD_REUEST, BAD_REUEST + BAD_REUEST_SIZE, std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} else {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} // namespace va

View File

@ -2,24 +2,9 @@
#include "utils.h"
#include <boost/asio.hpp>
#include <boost/log/trivial.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
namespace fs = std::filesystem;
using boost::asio::ip::tcp;
constexpr size_t buffer_length = 4 * 1024;
const char *BAD_REUEST =
"HTTP/1.1 400\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccept-Ranges: bytes\r\n\r\n";
const size_t BAD_REUEST_SIZE = strlen(BAD_REUEST);
const char *NOT_FOUND_REUEST =
"HTTP/1.1 404\r\nContent-Type: text/plain\r\nContent-Length: {}\r\nAccept-Ranges: bytes\r\n\r\n";
const size_t NOT_FOUND_REUEST_SIZE = strlen(NOT_FOUND_REUEST);
namespace va {
class Session : public std::enable_shared_from_this<Session> {
public:
@ -30,101 +15,7 @@ namespace va {
return socket_;
}
void start() {
auto self(shared_from_this());
boost::asio::async_read_until(
socket_, in_buffer_, "\r\n\r\n", [this, self](boost::system::error_code ec, std::size_t) {
if (!ec) {
std::string req{std::istreambuf_iterator<char>(&in_buffer_), std::istreambuf_iterator<char>()};
try {
auto uri = fetch_uri(req);
if (uri.ends_with(".m3u8")) {
// Запрос playlist
auto playlist_opt = make_playlist(uri);
if (playlist_opt.has_value()) {
auto playlist = playlist_opt.value();
auto resp = std::format(
"HTTP/1.1 200\r\nContent-Type: application/x-mpegURL\r\nContent-Length: "
"{}\r\nAccept-Ranges: bytes\r\n\r\n{}",
playlist.size(), playlist);
std::ostream out(&out_buffer_);
std::copy(resp.begin(), resp.end(), std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} else {
std::ostream out(&out_buffer_);
std::copy(NOT_FOUND_REUEST, NOT_FOUND_REUEST + NOT_FOUND_REUEST_SIZE,
std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} else if (uri.ends_with(".ts")) {
// Запрос сегмента
auto path = std::format("/tmp/va{}", uri);
std::ifstream file(path, std::ios::binary);
std::vector<char> data;
std::copy(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>(),
std::back_insert_iterator(data));
auto resp = std::format("HTTP/1.1 200\r\nContent-Type: video/mp2t\r\nContent-Length: "
"{}\r\nAccept-Ranges: bytes\r\n\r\n",
data.size());
std::ostream out(&out_buffer_);
std::copy(resp.begin(), resp.end(), std::ostream_iterator<char>(out));
std::copy(data.begin(), data.end(), std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} else {
// Не валидный запрос
BOOST_LOG_TRIVIAL(error) << "Invalid request";
std::ostream out(&out_buffer_);
std::copy(BAD_REUEST, BAD_REUEST + BAD_REUEST_SIZE, std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} catch (fs::filesystem_error const &ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
std::ostream out(&out_buffer_);
std::copy(NOT_FOUND_REUEST, NOT_FOUND_REUEST + NOT_FOUND_REUEST_SIZE,
std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
} catch (std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << ex.what();
std::ostream out(&out_buffer_);
std::copy(BAD_REUEST, BAD_REUEST + BAD_REUEST_SIZE, std::ostream_iterator<char>(out));
boost::asio::async_write(socket_, out_buffer_,
[this, self](boost::system::error_code ec, std::size_t) {
if (ec) {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
} else {
BOOST_LOG_TRIVIAL(error) << ec;
}
});
}
void start();
tcp::socket socket_;
boost::asio::streambuf in_buffer_;

View File

@ -56,6 +56,8 @@ include_directories( ${Boost_INCLUDE_DIR} )
set(SRC_FILES
src/main.cpp
src/utils.cpp
src/app.cpp
src/source/source.cpp
src/state/state.cpp
src/capture/ffmpeg/avformat.cpp

14
va-recorder/src/app.cpp Normal file
View File

@ -0,0 +1,14 @@
#include "app.h"
#include <thread>
namespace va {
void App::run_in_stream(const Source &source) {
auto prefix_path{prefix_archive_path_};
auto duration{duration_file_};
std::jthread th([=](std::stop_token stoken) {
Capture<FFMPEGCapture> capture(source);
capture.run(stoken, prefix_path, duration);
});
threads_.push_back(std::move(th));
}
} // namespace va

View File

@ -5,7 +5,6 @@
#include "source/source.h"
#include <boost/log/trivial.hpp>
#include <boost/version.hpp>
#include <thread>
namespace va {
class App final {
@ -32,15 +31,7 @@ namespace va {
App &operator=(const App &) = delete;
App(App &&) = delete;
App &operator=(App &&) = delete;
void run_in_stream(const Source &source) {
auto prefix_path{prefix_archive_path_};
auto duration{duration_file_};
std::jthread th([=](std::stop_token stoken) {
Capture<FFMPEGCapture> capture(source);
capture.run(stoken, prefix_path, duration);
});
threads_.push_back(std::move(th));
}
void run_in_stream(const Source &source);
const std::string &prefix_archive_path() const & {
return prefix_archive_path_;

36
va-recorder/src/utils.cpp Normal file
View File

@ -0,0 +1,36 @@
#include "utils.h"
namespace {
constexpr size_t buffer_len = std::size("yyyy/mm/dd/hh");
}
namespace va {
namespace utils {
std::string make_endpoint(const Source &source) {
if (!source.password().has_value() && !source.username().has_value()) {
return source.url();
}
if (!source.password().has_value() || !source.username().has_value()) {
throw std::runtime_error("'username' or 'password' are empty. Both "
"must be specified if one is specified");
return source.url();
}
auto &username = source.username().value();
auto &password = source.password().value();
auto &url = source.url();
auto pos = url.find("://");
if (pos == std::string::npos) {
auto err_msg = std::format("could not make endpoint for video "
"source({}), incorrect url address",
source.id());
throw std::runtime_error(err_msg);
}
return std::format("{}{}:{}@{}", url.substr(0, pos + 3), username, password, url.substr(pos + 3));
}
std::string format_datetime_to_path(time_t seconds) {
char buffer[buffer_len];
std::strftime(buffer, buffer_len, "%Y/%m/%d/%H", std::gmtime(&seconds));
return std::string(buffer);
}
} // namespace utils
} // namespace va

View File

@ -6,37 +6,9 @@
#include <ctime>
#include <format>
namespace {
constexpr size_t buffer_len = std::size("yyyy/mm/dd/hh");
}
namespace va {
namespace utils {
inline std::string make_endpoint(const Source &source) {
if (!source.password().has_value() && !source.username().has_value()) {
return source.url();
}
if (!source.password().has_value() || !source.username().has_value()) {
throw std::runtime_error("'username' or 'password' are empty. Both "
"must be specified if one is specified");
return source.url();
}
auto &username = source.username().value();
auto &password = source.password().value();
auto &url = source.url();
auto pos = url.find("://");
if (pos == std::string::npos) {
auto err_msg = std::format("could not make endpoint for video "
"source({}), incorrect url address",
source.id());
throw std::runtime_error(err_msg);
}
return std::format("{}{}:{}@{}", url.substr(0, pos + 3), username, password, url.substr(pos + 3));
}
inline std::string format_datetime_to_path(time_t seconds) {
char buffer[buffer_len];
std::strftime(buffer, buffer_len, "%Y/%m/%d/%H", std::gmtime(&seconds));
return std::string(buffer);
}
std::string make_endpoint(const Source &source);
std::string format_datetime_to_path(time_t seconds);
} // namespace utils
} // namespace va