Refactored to allow for multiple targets with different bitrates, gradual backoff of retrying failed ffmpeg runs, and a config.json that works better.
parent
b0274b8254
commit
984d757914
@ -0,0 +1,27 @@ |
|||||||
|
#include "config.hpp" |
||||||
|
#include <fstream> |
||||||
|
|
||||||
|
using namespace nlohmann; |
||||||
|
|
||||||
|
Config::Config(const string json_file) { |
||||||
|
load(json_file); |
||||||
|
} |
||||||
|
|
||||||
|
void Config::load(const string file_name) { |
||||||
|
std::ifstream infile(file_name); |
||||||
|
json_config = json::parse(infile); |
||||||
|
|
||||||
|
listen_at = json_config["listen_at"].template get<string>(); |
||||||
|
fail_max = json_config["fail_max"].template get<int>(); |
||||||
|
json send_list = json_config["send_to"]; |
||||||
|
|
||||||
|
for(auto &el : send_list.items()) { |
||||||
|
json spec = el.value(); |
||||||
|
SendTo send_spec{ |
||||||
|
.url = spec["url"].template get<string>(), |
||||||
|
.bitrate = spec["bitrate"].template get<string>() |
||||||
|
}; |
||||||
|
|
||||||
|
send_to[el.key()] = send_spec; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
#pragma once |
||||||
|
#include <nlohmann/json.hpp> |
||||||
|
#include <map> |
||||||
|
|
||||||
|
using std::string; |
||||||
|
|
||||||
|
struct SendTo { |
||||||
|
string url = ""; |
||||||
|
string bitrate = ""; |
||||||
|
}; |
||||||
|
|
||||||
|
struct Config { |
||||||
|
nlohmann::json json_config; |
||||||
|
string listen_at = ""; |
||||||
|
string bitrate = ""; |
||||||
|
int fail_max = 6; |
||||||
|
std::map<string, SendTo> send_to; |
||||||
|
|
||||||
|
Config(const string json_file); |
||||||
|
|
||||||
|
void load(const string file_name); |
||||||
|
}; |
@ -1,5 +1,18 @@ |
|||||||
{ |
{ |
||||||
"listen_at": "rtmp://192.168.254.147/", |
"listen_at": "rtmp://192.168.254.147/", |
||||||
"bitrate": "7M", |
"fail_max": 10, |
||||||
"send_to": "https://127.0.0.1:5001/" |
"send_to": { |
||||||
|
"Youtube": { |
||||||
|
"url": "https://127.0.0.1:5001/", |
||||||
|
"bitrate": "9M" |
||||||
|
}, |
||||||
|
"Twitch": { |
||||||
|
"url": "https://127.0.0.1:5001/", |
||||||
|
"bitrate": "7M" |
||||||
|
}, |
||||||
|
"Twitter": { |
||||||
|
"url": "https://127.0.0.1:5001/", |
||||||
|
"bitrate": "9M" |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,83 @@ |
|||||||
|
#include "server.hpp" |
||||||
|
#include <nlohmann/json.hpp> |
||||||
|
#include <fmt/core.h> |
||||||
|
#include <fmt/chrono.h> |
||||||
|
#include <thread> |
||||||
|
#include "dbc.hpp" |
||||||
|
|
||||||
|
using namespace fmt; |
||||||
|
|
||||||
|
FILE *run_ffmpeg(Config &config) { |
||||||
|
string ffmpeg_cmd = format("ffmpeg -listen 1 -i {} -bufsize 3000k", |
||||||
|
config.listen_at); |
||||||
|
|
||||||
|
for(auto &el : config.send_to) { |
||||||
|
SendTo send_spec = el.second; |
||||||
|
ffmpeg_cmd += format(" -maxrate {} -flags +global_header -c:v libx264 -preset veryfast -tune zerolatency -g:v 60 -vb {} -c:a copy -f flv {}", |
||||||
|
send_spec.bitrate, send_spec.bitrate, send_spec.url); |
||||||
|
} |
||||||
|
|
||||||
|
println("RUNNING: {}", ffmpeg_cmd); |
||||||
|
|
||||||
|
return popen(ffmpeg_cmd.c_str(), "re"); |
||||||
|
} |
||||||
|
|
||||||
|
bool Server::stopped() { |
||||||
|
return in_state(ServerState::STOP); |
||||||
|
} |
||||||
|
|
||||||
|
void Server::event(ServerEvent ev) { |
||||||
|
switch(_state) { |
||||||
|
FSM_STATE(ServerState, START, ev); |
||||||
|
FSM_STATE(ServerState, READING, ev); |
||||||
|
FSM_STATE(ServerState, STOP, ev); |
||||||
|
FSM_STATE(ServerState, ERROR, ev); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Server::START(ServerEvent ev) { |
||||||
|
handle = run_ffmpeg(config); |
||||||
|
if(handle == nullptr) { |
||||||
|
dbc::log("ERROR when launching ffmpeg"); |
||||||
|
state(ServerState::ERROR); |
||||||
|
} else { |
||||||
|
wait_time = 200ms; |
||||||
|
fail_count = 0; |
||||||
|
state(ServerState::READING); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Server::READING(ServerEvent ev) { |
||||||
|
res = fgets(buffer, BUF_MAX, handle); |
||||||
|
|
||||||
|
if(res == nullptr) { |
||||||
|
dbc::log("STOP shutting down..."); |
||||||
|
state(ServerState::STOP); |
||||||
|
} else { |
||||||
|
state(ServerState::READING); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Server::STOP(ServerEvent ev) { |
||||||
|
int rc = pclose(handle); |
||||||
|
if(rc != 0) { |
||||||
|
dbc::log("ERROR when calling pclose on ffmpeg"); |
||||||
|
state(ServerState::ERROR); |
||||||
|
} else { |
||||||
|
state(ServerState::STOP); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void Server::ERROR(ServerEvent ev) { |
||||||
|
fail_count++; |
||||||
|
if(fail_count < config.fail_max) { |
||||||
|
println("Error in server, waiting {}...", wait_time); |
||||||
|
wait_time *= 2; |
||||||
|
std::this_thread::sleep_for(wait_time); |
||||||
|
dbc::log("Attempting to run it again..."); |
||||||
|
state(ServerState::START); |
||||||
|
} else { |
||||||
|
dbc::log("ffmpeg failed to many times, exiting."); |
||||||
|
state(ServerState::STOP); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
#pragma once |
||||||
|
#include "fsm.hpp" |
||||||
|
#include "config.hpp" |
||||||
|
#include <chrono> |
||||||
|
using namespace nlohmann; |
||||||
|
using namespace std::chrono_literals; |
||||||
|
|
||||||
|
#define BUF_MAX 1024 * 10 |
||||||
|
|
||||||
|
enum class ServerState { |
||||||
|
START, READING, STOP, ERROR |
||||||
|
}; |
||||||
|
|
||||||
|
enum class ServerEvent { |
||||||
|
GO |
||||||
|
}; |
||||||
|
|
||||||
|
class Server : DeadSimpleFSM<ServerState, ServerEvent> { |
||||||
|
Config &config; |
||||||
|
FILE *handle = nullptr; |
||||||
|
char buffer[BUF_MAX]; |
||||||
|
char *res = nullptr; |
||||||
|
std::chrono::milliseconds wait_time = 200ms; |
||||||
|
int fail_count = 0; |
||||||
|
|
||||||
|
public: |
||||||
|
|
||||||
|
Server(Config &config) : config(config) {}; |
||||||
|
|
||||||
|
bool stopped(); |
||||||
|
|
||||||
|
void event(ServerEvent ev); |
||||||
|
|
||||||
|
// states
|
||||||
|
void START(ServerEvent ev); |
||||||
|
void READING(ServerEvent ev); |
||||||
|
void STOP(ServerEvent ev); |
||||||
|
void ERROR(ServerEvent ev); |
||||||
|
}; |
Loading…
Reference in new issue