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/", |
||||
"bitrate": "7M", |
||||
"send_to": "https://127.0.0.1:5001/" |
||||
"fail_max": 10, |
||||
"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