Backported various little edits from Pro edition.
This commit is contained in:
parent
ef9938da0c
commit
4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -55,4 +55,5 @@ build.ninja
|
|||
rules.ninja
|
||||
.ninja_log
|
||||
.ninja_deps
|
||||
aes_ctr128
|
||||
|
||||
|
|
|
@ -103,6 +103,7 @@ set(libHeaders
|
|||
${SOURCE_DIR}/lib/defines.h
|
||||
${SOURCE_DIR}/lib/dtsc.h
|
||||
${SOURCE_DIR}/lib/flv_tag.h
|
||||
${SOURCE_DIR}/lib/h264.h
|
||||
${SOURCE_DIR}/lib/http_parser.h
|
||||
${SOURCE_DIR}/lib/json.h
|
||||
${SOURCE_DIR}/lib/mp4_adobe.h
|
||||
|
@ -120,6 +121,7 @@ set(libHeaders
|
|||
${SOURCE_DIR}/lib/timing.h
|
||||
${SOURCE_DIR}/lib/tinythread.h
|
||||
${SOURCE_DIR}/lib/ts_packet.h
|
||||
${SOURCE_DIR}/lib/util.h
|
||||
${SOURCE_DIR}/lib/vorbis.h
|
||||
)
|
||||
|
||||
|
@ -136,6 +138,7 @@ set(libSources
|
|||
${SOURCE_DIR}/lib/dtsc.cpp
|
||||
${SOURCE_DIR}/lib/dtscmeta.cpp
|
||||
${SOURCE_DIR}/lib/flv_tag.cpp
|
||||
${SOURCE_DIR}/lib/h264.cpp
|
||||
${SOURCE_DIR}/lib/http_parser.cpp
|
||||
${SOURCE_DIR}/lib/json.cpp
|
||||
${SOURCE_DIR}/lib/mp4_adobe.cpp
|
||||
|
@ -153,6 +156,7 @@ set(libSources
|
|||
${SOURCE_DIR}/lib/timing.cpp
|
||||
${SOURCE_DIR}/lib/tinythread.cpp
|
||||
${SOURCE_DIR}/lib/ts_packet.cpp
|
||||
${SOURCE_DIR}/lib/util.cpp
|
||||
${SOURCE_DIR}/lib/vorbis.cpp
|
||||
)
|
||||
|
||||
|
@ -245,9 +249,9 @@ makeInput(Buffer buffer)
|
|||
########################################
|
||||
macro(makeOutput outputName format)
|
||||
#Parse all extra arguments, for http and ts flags
|
||||
SET (tsBaseClass Output)
|
||||
if (";${ARGN};" MATCHES ";http;")
|
||||
SET(httpOutput src/output/output_http.cpp)
|
||||
SET(tsBaseClass Output)
|
||||
if (";${ARGN};" MATCHES ";ts;")
|
||||
SET(tsBaseClass HTTPOutput)
|
||||
endif()
|
||||
|
|
|
@ -228,7 +228,7 @@ TAB_SIZE = 2
|
|||
# "Side Effects:". You can put \n's in the value part of an alias to insert
|
||||
# newlines.
|
||||
|
||||
ALIASES = "api=\xrefitem api \"API call\" \"API calls\""
|
||||
ALIASES = "api=\xrefitem api \"API call\" \"API calls\"" "triggers=\xrefitem triggers \"Trigger\" \"Triggers\""
|
||||
|
||||
# This tag can be used to specify a number of word-keyword mappings (TCL only).
|
||||
# A mapping has the form "name=value". For example adding "class=itcl::class"
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
add_library ( mist SHARED
|
||||
amf.cpp
|
||||
amf.h
|
||||
auth.cpp
|
||||
auth.h
|
||||
base64.cpp
|
||||
base64.h
|
||||
bitfields.cpp
|
||||
bitfields.h
|
||||
bitstream.cpp
|
||||
bitstream.h
|
||||
checksum.h
|
||||
CMakeLists.txt
|
||||
config.cpp
|
||||
config.h
|
||||
converter.cpp
|
||||
converter.h
|
||||
defines.h
|
||||
dtsc.cpp
|
||||
dtsc.h
|
||||
dtscmeta.cpp
|
||||
filesystem.cpp
|
||||
filesystem.h
|
||||
flv_tag.cpp
|
||||
flv_tag.h
|
||||
ftp.cpp
|
||||
ftp.h
|
||||
http_parser.cpp
|
||||
http_parser.h
|
||||
json.cpp
|
||||
json.h
|
||||
mp4_adobe.cpp
|
||||
mp4_adobe.h
|
||||
mp4.cpp
|
||||
mp4_generic.cpp
|
||||
mp4_generic.h
|
||||
mp4.h
|
||||
mp4_ms.cpp
|
||||
mp4_ms.h
|
||||
nal.cpp
|
||||
nal.h
|
||||
ogg.cpp
|
||||
ogg.h
|
||||
procs.cpp
|
||||
procs.h
|
||||
rtmpchunks.cpp
|
||||
rtmpchunks.h
|
||||
shared_memory.cpp
|
||||
shared_memory.h
|
||||
socket.cpp
|
||||
socket.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
theora.cpp
|
||||
theora.h
|
||||
timing.cpp
|
||||
timing.h
|
||||
tinythread.cpp
|
||||
tinythread.h
|
||||
ts_packet.cpp
|
||||
ts_packet.h
|
||||
vorbis.cpp
|
||||
vorbis.h
|
||||
)
|
||||
|
||||
target_link_libraries( mist
|
||||
-lpthread
|
||||
-lrt
|
||||
)
|
|
@ -15,7 +15,7 @@ namespace Bit{
|
|||
//Host to binary/binary to host functions - similar to kernel ntoh/hton functions.
|
||||
|
||||
/// Retrieves a short in network order from the pointer p.
|
||||
inline unsigned short btohs(char * p) {
|
||||
inline unsigned short btohs(const char * p) {
|
||||
return ((unsigned short)p[0] << 8) | p[1];
|
||||
}
|
||||
|
||||
|
|
207
lib/config.cpp
207
lib/config.cpp
|
@ -32,6 +32,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <fstream>
|
||||
#include <dirent.h> //for getMyExec
|
||||
#include "procs.h"
|
||||
|
||||
bool Util::Config::is_active = false;
|
||||
unsigned int Util::Config::printDebugLevel = DEBUG;//
|
||||
|
@ -69,8 +70,6 @@ Util::Config::Config(std::string cmd) {
|
|||
/// {
|
||||
/// "short":"o", //The short option letter
|
||||
/// "long":"onName", //The long option
|
||||
/// "short_off":"n", //The short option-off letter
|
||||
/// "long_off":"offName", //The long option-off
|
||||
/// "arg":"integer", //The type of argument, if required.
|
||||
/// "value":[], //The default value(s) for this option if it is not given on the commandline.
|
||||
/// "arg_num":1, //The count this value has on the commandline, after all the options have been processed.
|
||||
|
@ -88,9 +87,6 @@ void Util::Config::addOption(std::string optname, JSON::Value option) {
|
|||
if (it->isMember("long")) {
|
||||
long_count++;
|
||||
}
|
||||
if (it->isMember("long_off")) {
|
||||
long_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,12 +106,6 @@ void Util::Config::printHelp(std::ostream & output) {
|
|||
longest = current;
|
||||
}
|
||||
current = 0;
|
||||
if (it->isMember("long_off")) {
|
||||
current += (*it)["long_off"].asString().size() + 4;
|
||||
}
|
||||
if (it->isMember("short_off")) {
|
||||
current += (*it)["short_off"].asString().size() + 3;
|
||||
}
|
||||
if (current > longest) {
|
||||
longest = current;
|
||||
}
|
||||
|
@ -158,26 +148,6 @@ void Util::Config::printHelp(std::ostream & output) {
|
|||
output << f << (*it)["help"].asString() << std::endl;
|
||||
}
|
||||
}
|
||||
if (it->isMember("long_off") || it->isMember("short_off")) {
|
||||
if (it->isMember("long_off") && it->isMember("short_off")) {
|
||||
f = "--" + (*it)["long_off"].asString() + ", -" + (*it)["short_off"].asString();
|
||||
} else {
|
||||
if (it->isMember("long_off")) {
|
||||
f = "--" + (*it)["long_off"].asString();
|
||||
}
|
||||
if (it->isMember("short_off")) {
|
||||
f = "-" + (*it)["short_off"].asString();
|
||||
}
|
||||
}
|
||||
while (f.size() < longest) {
|
||||
f.append(" ");
|
||||
}
|
||||
if (it->isMember("arg")) {
|
||||
output << f << "(" << (*it)["arg"].asString() << ") " << (*it)["help"].asString() << std::endl;
|
||||
} else {
|
||||
output << f << (*it)["help"].asString() << std::endl;
|
||||
}
|
||||
}
|
||||
if (it->isMember("arg_num")) {
|
||||
f = it.key();
|
||||
while (f.size() < longest) {
|
||||
|
@ -204,12 +174,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
|
|||
shortopts += ":";
|
||||
}
|
||||
}
|
||||
if (it->isMember("short_off")) {
|
||||
shortopts += (*it)["short_off"].asString();
|
||||
if (it->isMember("arg")) {
|
||||
shortopts += ":";
|
||||
}
|
||||
}
|
||||
if (it->isMember("long")) {
|
||||
longOpts[long_i].name = (*it)["long"].asStringRef().c_str();
|
||||
longOpts[long_i].val = (*it)["short"].asString()[0];
|
||||
|
@ -218,14 +182,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
|
|||
}
|
||||
long_i++;
|
||||
}
|
||||
if (it->isMember("long_off")) {
|
||||
longOpts[long_i].name = (*it)["long_off"].asStringRef().c_str();
|
||||
longOpts[long_i].val = (*it)["short_off"].asString()[0];
|
||||
if (it->isMember("arg")) {
|
||||
longOpts[long_i].has_arg = 1;
|
||||
}
|
||||
long_i++;
|
||||
}
|
||||
if (it->isMember("arg_num") && !(it->isMember("value") && (*it)["value"].size())) {
|
||||
if ((*it)["arg_num"].asInt() > arg_count) {
|
||||
arg_count = (*it)["arg_num"].asInt();
|
||||
|
@ -263,9 +219,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
if (it->isMember("short_off") && (*it)["short_off"].asString()[0] == opt) {
|
||||
(*it)["value"].append((long long int)0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -289,6 +242,10 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Util::Config::hasOption(const std::string & optname) {
|
||||
return vals.isMember(optname);
|
||||
}
|
||||
|
||||
/// Returns a reference to the current value of an option or default if none was set.
|
||||
/// If the option does not exist, this exits the application with a return code of 37.
|
||||
JSON::Value & Util::Config::getOption(std::string optname, bool asArray) {
|
||||
|
@ -298,12 +255,18 @@ JSON::Value & Util::Config::getOption(std::string optname, bool asArray) {
|
|||
}
|
||||
if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) {
|
||||
vals[optname]["value"].append(JSON::Value());
|
||||
vals[optname]["value"].shrink(0);
|
||||
}
|
||||
if (asArray) {
|
||||
return vals[optname]["value"];
|
||||
} else {
|
||||
int n = vals[optname]["value"].size();
|
||||
return vals[optname]["value"][n - 1];
|
||||
if (!n){
|
||||
static JSON::Value empty = "";
|
||||
return empty;
|
||||
}else{
|
||||
return vals[optname]["value"][n - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,6 +304,7 @@ static void callThreadCallback(void * cDataArg) {
|
|||
}
|
||||
|
||||
int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) {
|
||||
Util::Procs::socketList.insert(server_socket.getSocket());
|
||||
while (is_active && server_socket.connected()) {
|
||||
Socket::Connection S = server_socket.accept();
|
||||
if (S.connected()) { //check if the new connection is valid
|
||||
|
@ -356,11 +320,13 @@ int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(S
|
|||
Util::sleep(10); //sleep 10ms
|
||||
}
|
||||
}
|
||||
Util::Procs::socketList.erase(server_socket.getSocket());
|
||||
server_socket.close();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) {
|
||||
Util::Procs::socketList.insert(server_socket.getSocket());
|
||||
while (is_active && server_socket.connected()) {
|
||||
Socket::Connection S = server_socket.accept();
|
||||
if (S.connected()) { //check if the new connection is valid
|
||||
|
@ -376,6 +342,7 @@ int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Soc
|
|||
Util::sleep(10); //sleep 10ms
|
||||
}
|
||||
}
|
||||
Util::Procs::socketList.erase(server_socket.getSocket());
|
||||
server_socket.close();
|
||||
return 0;
|
||||
}
|
||||
|
@ -385,8 +352,8 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)) {
|
|||
if (vals.isMember("socket")) {
|
||||
server_socket = Socket::Server(Util::getTmpFolder() + getString("socket"));
|
||||
}
|
||||
if (vals.isMember("listen_port") && vals.isMember("listen_interface")) {
|
||||
server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false);
|
||||
if (vals.isMember("port") && vals.isMember("interface")) {
|
||||
server_socket = Socket::Server(getInteger("port"), getString("interface"), false);
|
||||
}
|
||||
if (!server_socket.connected()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Failure to open socket");
|
||||
|
@ -402,8 +369,8 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) {
|
|||
if (vals.isMember("socket")) {
|
||||
server_socket = Socket::Server(Util::getTmpFolder() + getString("socket"));
|
||||
}
|
||||
if (vals.isMember("listen_port") && vals.isMember("listen_interface")) {
|
||||
server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false);
|
||||
if (vals.isMember("port") && vals.isMember("interface")) {
|
||||
server_socket = Socket::Server(getInteger("port"), getString("interface"), false);
|
||||
}
|
||||
if (!server_socket.connected()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Failure to open socket");
|
||||
|
@ -416,7 +383,6 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) {
|
|||
|
||||
/// Activated the stored config. This will:
|
||||
/// - Drop permissions to the stored "username", if any.
|
||||
/// - Daemonize the process if "daemonize" exists and is true.
|
||||
/// - Set is_active to true.
|
||||
/// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals.
|
||||
void Util::Config::activate() {
|
||||
|
@ -424,14 +390,6 @@ void Util::Config::activate() {
|
|||
setUser(getString("username"));
|
||||
vals.removeMember("username");
|
||||
}
|
||||
if (vals.isMember("daemonize") && getBool("daemonize")) {
|
||||
if (vals.isMember("logfile") && getString("logfile") != "") {
|
||||
Daemonize(true);
|
||||
} else {
|
||||
Daemonize(false);
|
||||
}
|
||||
vals.removeMember("daemonize");
|
||||
}
|
||||
struct sigaction new_action;
|
||||
struct sigaction cur_action;
|
||||
new_action.sa_sigaction = signal_handler;
|
||||
|
@ -476,33 +434,78 @@ void Util::Config::signal_handler(int signum, siginfo_t * sigInfo, void * ignore
|
|||
}
|
||||
} //signal_handler
|
||||
|
||||
|
||||
/// Adds the options from the given JSON capabilities structure.
|
||||
/// Recurses into optional and required, added options as needed.
|
||||
void Util::Config::addOptionsFromCapabilities(const JSON::Value & capa){
|
||||
//First add the required options.
|
||||
if (capa.isMember("required") && capa["required"].size()){
|
||||
jsonForEachConst(capa["required"], it){
|
||||
if (!it->isMember("short") || !it->isMember("option") || !it->isMember("type")){
|
||||
FAIL_MSG("Incomplete required option: %s", it.key().c_str());
|
||||
continue;
|
||||
}
|
||||
JSON::Value opt;
|
||||
opt["short"] = (*it)["short"];
|
||||
opt["long"] = (*it)["option"].asStringRef().substr(2);
|
||||
if (it->isMember("type")){
|
||||
//int, uint, debug, select, str
|
||||
if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){
|
||||
opt["arg"] = "integer";
|
||||
}else{
|
||||
opt["arg"] = "string";
|
||||
}
|
||||
}
|
||||
if (it->isMember("default")){
|
||||
opt["value"].append((*it)["default"]);
|
||||
}
|
||||
opt["help"] = (*it)["help"];
|
||||
addOption(it.key(), opt);
|
||||
}
|
||||
}
|
||||
//Then, the optionals.
|
||||
if (capa.isMember("optional") && capa["optional"].size()){
|
||||
jsonForEachConst(capa["optional"], it){
|
||||
if (it.key() == "debug"){continue;}
|
||||
if (!it->isMember("short") || !it->isMember("option") || !it->isMember("default")){
|
||||
FAIL_MSG("Incomplete optional option: %s", it.key().c_str());
|
||||
continue;
|
||||
}
|
||||
JSON::Value opt;
|
||||
opt["short"] = (*it)["short"];
|
||||
opt["long"] = (*it)["option"].asStringRef().substr(2);
|
||||
if (it->isMember("type")){
|
||||
//int, uint, debug, select, str
|
||||
if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){
|
||||
opt["arg"] = "integer";
|
||||
}else{
|
||||
opt["arg"] = "string";
|
||||
}
|
||||
}
|
||||
if (it->isMember("default")){
|
||||
opt["value"].append((*it)["default"]);
|
||||
}
|
||||
opt["help"] = (*it)["help"];
|
||||
addOption(it.key(), opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the default connector options. Also updates the capabilities structure with the default options.
|
||||
/// Besides the options addBasicConnectorOptions adds, this function also adds port and interface options.
|
||||
void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) {
|
||||
JSON::Value option;
|
||||
option.null();
|
||||
option["long"] = "port";
|
||||
option["short"] = "p";
|
||||
option["arg"] = "integer";
|
||||
option["help"] = "TCP port to listen on";
|
||||
option["value"].append((long long)port);
|
||||
addOption("listen_port", option);
|
||||
capabilities["optional"]["port"]["name"] = "TCP port";
|
||||
capabilities["optional"]["port"]["help"] = "TCP port to listen on - default if unprovided is " + option["value"][0u].asString();
|
||||
capabilities["optional"]["port"]["help"] = "TCP port to listen on";
|
||||
capabilities["optional"]["port"]["type"] = "uint";
|
||||
capabilities["optional"]["port"]["short"] = "p";
|
||||
capabilities["optional"]["port"]["option"] = "--port";
|
||||
capabilities["optional"]["port"]["default"] = option["value"][0u];
|
||||
capabilities["optional"]["port"]["default"] = (long long)port;
|
||||
|
||||
option.null();
|
||||
option["long"] = "interface";
|
||||
option["short"] = "i";
|
||||
option["arg"] = "string";
|
||||
option["help"] = "Interface address to listen on, or 0.0.0.0 for all available interfaces.";
|
||||
option["value"].append("0.0.0.0");
|
||||
addOption("listen_interface", option);
|
||||
capabilities["optional"]["interface"]["name"] = "Interface";
|
||||
capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on - default if unprovided is all interfaces";
|
||||
capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on";
|
||||
capabilities["optional"]["interface"]["default"] = "0.0.0.0";
|
||||
capabilities["optional"]["interface"]["option"] = "--interface";
|
||||
capabilities["optional"]["interface"]["short"] = "i";
|
||||
capabilities["optional"]["interface"]["type"] = "str";
|
||||
|
||||
addBasicConnectorOptions(capabilities);
|
||||
|
@ -510,38 +513,16 @@ void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) {
|
|||
|
||||
/// Adds the default connector options. Also updates the capabilities structure with the default options.
|
||||
void Util::Config::addBasicConnectorOptions(JSON::Value & capabilities) {
|
||||
JSON::Value option;
|
||||
option.null();
|
||||
option["long"] = "username";
|
||||
option["short"] = "u";
|
||||
option["arg"] = "string";
|
||||
option["help"] = "Username to drop privileges to, or root to not drop provileges.";
|
||||
option["value"].append("root");
|
||||
addOption("username", option);
|
||||
capabilities["optional"]["username"]["name"] = "Username";
|
||||
capabilities["optional"]["username"]["help"] = "Username to drop privileges to - default if unprovided means do not drop privileges";
|
||||
capabilities["optional"]["username"]["option"] = "--username";
|
||||
capabilities["optional"]["username"]["short"] = "u";
|
||||
capabilities["optional"]["username"]["default"] = "root";
|
||||
capabilities["optional"]["username"]["type"] = "str";
|
||||
|
||||
addOptionsFromCapabilities(capabilities);
|
||||
|
||||
if (capabilities.isMember("socket")) {
|
||||
option.null();
|
||||
option["arg"] = "string";
|
||||
option["help"] = "Socket name that can be connected to for this connector.";
|
||||
option["value"].append(capabilities["socket"]);
|
||||
addOption("socket", option);
|
||||
}
|
||||
|
||||
option.null();
|
||||
option["long"] = "daemon";
|
||||
option["short"] = "d";
|
||||
option["long_off"] = "nodaemon";
|
||||
option["short_off"] = "n";
|
||||
option["help"] = "Whether or not to daemonize the process after starting.";
|
||||
option["value"].append(0ll);
|
||||
addOption("daemonize", option);
|
||||
|
||||
option.null();
|
||||
JSON::Value option;
|
||||
option["long"] = "json";
|
||||
option["short"] = "j";
|
||||
option["help"] = "Output connector info in JSON format, then exit.";
|
||||
|
@ -631,17 +612,3 @@ void Util::setUser(std::string username) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Will turn the current process into a daemon.
|
||||
/// Works by calling daemon(1,0):
|
||||
/// Does not change directory to root.
|
||||
/// Does redirect output to /dev/null
|
||||
void Util::Daemonize(bool notClose) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Going into background mode...");
|
||||
int noClose = 0;
|
||||
if (notClose) {
|
||||
noClose = 1;
|
||||
}
|
||||
if (daemon(1, noClose) < 0) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Failed to daemonize: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace Util {
|
|||
void addOption(std::string optname, JSON::Value option);
|
||||
void printHelp(std::ostream & output);
|
||||
bool parseArgs(int & argc, char ** & argv);
|
||||
bool hasOption(const std::string & optname);
|
||||
JSON::Value & getOption(std::string optname, bool asArray = false);
|
||||
std::string getString(std::string optname);
|
||||
long long int getInteger(std::string optname);
|
||||
|
@ -40,6 +41,7 @@ namespace Util {
|
|||
int serveThreadedSocket(int (*callback)(Socket::Connection & S));
|
||||
int serveForkedSocket(int (*callback)(Socket::Connection & S));
|
||||
int servePlainSocket(int (*callback)(Socket::Connection & S));
|
||||
void addOptionsFromCapabilities(const JSON::Value & capabilities);
|
||||
void addBasicConnectorOptions(JSON::Value & capabilities);
|
||||
void addConnectorOptions(int port, JSON::Value & capabilities);
|
||||
};
|
||||
|
@ -53,7 +55,4 @@ namespace Util {
|
|||
/// Will set the active user to the named username.
|
||||
void setUser(std::string user);
|
||||
|
||||
/// Will turn the current process into a daemon.
|
||||
void Daemonize(bool notClose = false);
|
||||
|
||||
}
|
||||
|
|
|
@ -57,11 +57,28 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "
|
|||
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef SHM_DATASIZE
|
||||
#define SHM_DATASIZE 25
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef STATS_DELAY
|
||||
#define STATS_DELAY 15
|
||||
#endif
|
||||
|
||||
#ifndef INPUT_TIMEOUT
|
||||
#define INPUT_TIMEOUT STATS_DELAY
|
||||
#endif
|
||||
|
||||
/// The size used for stream header pages under Windows, where they cannot be size-detected.
|
||||
#define DEFAULT_META_PAGE_SIZE 16 * 1024 * 1024
|
||||
|
||||
/// The size used for stream header pages under Windows, where they cannot be size-detected.
|
||||
#define DEFAULT_STRM_PAGE_SIZE 4 * 1024 * 1024
|
||||
|
||||
/// The size used for stream data pages under Windows, where they cannot be size-detected.
|
||||
#define DEFAULT_DATA_PAGE_SIZE 25 * 1024 * 1024
|
||||
#define DEFAULT_DATA_PAGE_SIZE SHM_DATASIZE * 1024 * 1024
|
||||
|
||||
/// The size used for server configuration pages.
|
||||
#define DEFAULT_CONF_PAGE_SIZE 4 * 1024 * 1024
|
||||
|
@ -72,10 +89,15 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "
|
|||
#define SHM_STREAM_INDEX "MstSTRM%s" //%s stream name
|
||||
#define SHM_TRACK_META "MstTRAK%s@%lu" //%s stream name, %lu track ID
|
||||
#define SHM_TRACK_INDEX "MstTRID%s@%lu" //%s stream name, %lu track ID
|
||||
#define SHM_TRACK_INDEX_SIZE 8192
|
||||
#define SHM_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page #
|
||||
#define SHM_STATISTICS "MstSTAT"
|
||||
#define SHM_USERS "MstUSER%s" //%s stream name
|
||||
#define SEM_LIVE "MstLIVE%s" //%s stream name
|
||||
#define SHM_TRIGGER "MstTRIG%s" //%s trigger name
|
||||
#define SEM_LIVE "/MstLIVE%s" //%s stream name
|
||||
#define SEM_INPUT "/MstInpt%s" //%s stream name
|
||||
#define SEM_CONF "/MstConfLock"
|
||||
#define SHM_CONF "MstConf"
|
||||
#define NAME_BUFFER_SIZE 200 //char buffer size for snprintf'ing shm filenames
|
||||
|
||||
#define SIMUL_TRACKS 10
|
||||
|
|
58
lib/dtsc.cpp
58
lib/dtsc.cpp
|
@ -9,6 +9,7 @@
|
|||
char DTSC::Magic_Header[] = "DTSC";
|
||||
char DTSC::Magic_Packet[] = "DTPD";
|
||||
char DTSC::Magic_Packet2[] = "DTP2";
|
||||
char DTSC::Magic_Command[] = "DTCM";
|
||||
|
||||
DTSC::File::File() {
|
||||
F = 0;
|
||||
|
@ -32,8 +33,7 @@ DTSC::File & DTSC::File::operator =(const File & rhs) {
|
|||
if (rhs.myPack) {
|
||||
myPack = rhs.myPack;
|
||||
}
|
||||
metaStorage = rhs.metaStorage;
|
||||
metadata = metaStorage;
|
||||
metadata = rhs.metadata;
|
||||
currtime = rhs.currtime;
|
||||
lastreadpos = rhs.lastreadpos;
|
||||
headerSize = rhs.headerSize;
|
||||
|
@ -67,7 +67,7 @@ DTSC::File::File(std::string filename, bool create) {
|
|||
}
|
||||
created = create;
|
||||
if (!F) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not open file %s", filename.c_str());
|
||||
HIGH_MSG("Could not open file %s", filename.c_str());
|
||||
return;
|
||||
}
|
||||
fseek(F, 0, SEEK_END);
|
||||
|
@ -83,7 +83,7 @@ DTSC::File::File(std::string filename, bool create) {
|
|||
return;
|
||||
}
|
||||
if (memcmp(buffer, DTSC::Magic_Header, 4) != 0) {
|
||||
if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && memcmp(buffer, DTSC::Magic_Packet, 4) != 0) {
|
||||
if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && memcmp(buffer, DTSC::Magic_Packet, 4) != 0 && memcmp(buffer, DTSC::Magic_Command, 4) != 0) {
|
||||
DEBUG_MSG(DLVL_ERROR, "%s is not a valid DTSC file", filename.c_str());
|
||||
fclose(F);
|
||||
F = 0;
|
||||
|
@ -113,8 +113,7 @@ DTSC::File::File(std::string filename, bool create) {
|
|||
fseek(F, 0, SEEK_SET);
|
||||
File Fhead(filename + ".dtsh");
|
||||
if (Fhead) {
|
||||
metaStorage = Fhead.metaStorage;
|
||||
metadata = metaStorage;
|
||||
metadata = Fhead.metadata;
|
||||
}
|
||||
}
|
||||
currframe = 0;
|
||||
|
@ -346,8 +345,9 @@ void DTSC::File::seekNext() {
|
|||
}
|
||||
|
||||
void DTSC::File::parseNext(){
|
||||
char header_buffer[4] = {0, 0, 0, 0};
|
||||
lastreadpos = ftell(F);
|
||||
if (fread(buffer, 4, 1, F) != 1) {
|
||||
if (fread(header_buffer, 4, 1, F) != 1) {
|
||||
if (feof(F)) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos);
|
||||
} else {
|
||||
|
@ -356,55 +356,26 @@ void DTSC::File::parseNext(){
|
|||
myPack.null();
|
||||
return;
|
||||
}
|
||||
if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) {
|
||||
if (lastreadpos != 0) {
|
||||
readHeader(lastreadpos);
|
||||
std::string tmp = metaStorage.toNetPacked();
|
||||
myPack.reInit(tmp.data(), tmp.size());
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read another header");
|
||||
} else {
|
||||
if (fread(buffer, 4, 1, F) != 1) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not read header size @ %d", (int)lastreadpos);
|
||||
myPack.null();
|
||||
return;
|
||||
}
|
||||
long packSize = ntohl(((unsigned long *)buffer)[0]);
|
||||
std::string strBuffer = "DTSC";
|
||||
strBuffer.append((char *)buffer, 4);
|
||||
strBuffer.resize(packSize + 8);
|
||||
if (fread((void *)(strBuffer.c_str() + 8), packSize, 1, F) != 1) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", (int)lastreadpos);
|
||||
myPack.null();
|
||||
return;
|
||||
}
|
||||
myPack.reInit(strBuffer.data(), strBuffer.size());
|
||||
}
|
||||
return;
|
||||
}
|
||||
long long unsigned int version = 0;
|
||||
if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0) {
|
||||
if (memcmp(header_buffer, DTSC::Magic_Packet, 4) == 0 || memcmp(header_buffer, DTSC::Magic_Command, 4) == 0 || memcmp(header_buffer, DTSC::Magic_Header, 4) == 0) {
|
||||
version = 1;
|
||||
}
|
||||
if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0) {
|
||||
if (memcmp(header_buffer, DTSC::Magic_Packet2, 4) == 0) {
|
||||
version = 2;
|
||||
}
|
||||
if (version == 0) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x - %.4s != %.4s @ %d", (unsigned int)lastreadpos, (char *)buffer, DTSC::Magic_Packet2, (int)lastreadpos);
|
||||
DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x: %.4s", (unsigned int)lastreadpos, (char *)buffer);
|
||||
myPack.null();
|
||||
return;
|
||||
}
|
||||
if (fread(buffer, 4, 1, F) != 1) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %d", (int)lastreadpos);
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %#x", (unsigned int)lastreadpos);
|
||||
myPack.null();
|
||||
return;
|
||||
}
|
||||
long packSize = ntohl(((unsigned long *)buffer)[0]);
|
||||
char * packBuffer = (char *)malloc(packSize + 8);
|
||||
if (version == 1) {
|
||||
memcpy(packBuffer, "DTPD", 4);
|
||||
} else {
|
||||
memcpy(packBuffer, "DTP2", 4);
|
||||
}
|
||||
memcpy(packBuffer, header_buffer, 4);
|
||||
memcpy(packBuffer + 4, buffer, 4);
|
||||
if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos);
|
||||
|
@ -432,6 +403,11 @@ bool DTSC::File::seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek
|
|||
if (!forceSeek && myPack && ms >= myPack.getTime() && trackNo >= myPack.getTrackId()) {
|
||||
tmpPos.seekTime = myPack.getTime();
|
||||
tmpPos.bytePos = getBytePos();
|
||||
/*
|
||||
if (trackNo == myPack.getTrackId()){
|
||||
tmpPos.bytePos += myPack.getDataLen();
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
tmpPos.seekTime = 0;
|
||||
tmpPos.bytePos = 0;
|
||||
|
|
45
lib/dtsc.h
45
lib/dtsc.h
|
@ -34,6 +34,7 @@ namespace DTSC {
|
|||
extern char Magic_Header[]; ///< The magic bytes for a DTSC header
|
||||
extern char Magic_Packet[]; ///< The magic bytes for a DTSC packet
|
||||
extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2
|
||||
extern char Magic_Command[]; ///< The magic bytes for a DTCM packet
|
||||
|
||||
///\brief A simple structure used for ordering byte seek positions.
|
||||
struct seekPos {
|
||||
|
@ -61,7 +62,8 @@ namespace DTSC {
|
|||
DTSC_INVALID,
|
||||
DTSC_HEAD,
|
||||
DTSC_V1,
|
||||
DTSC_V2
|
||||
DTSC_V2,
|
||||
DTCM
|
||||
};
|
||||
|
||||
/// This class allows scanning through raw binary format DTSC data.
|
||||
|
@ -107,6 +109,7 @@ namespace DTSC {
|
|||
void operator = (const Packet & rhs);
|
||||
operator bool() const;
|
||||
packType getVersion() const;
|
||||
void reInit(Socket::Connection & src);
|
||||
void reInit(const char * data_, unsigned int len, bool noCopy = false);
|
||||
void genericFill(long long packTime, long long packOffset, long long packTrack, const char * packData, long long packDataSize, long long packBytePos, bool isKeyframe);
|
||||
void getString(const char * identifier, char *& result, unsigned int & len) const;
|
||||
|
@ -205,6 +208,17 @@ namespace DTSC {
|
|||
char * getData();
|
||||
void toPrettyString(std::ostream & str, int indent = 0);
|
||||
private:
|
||||
#ifdef BIGMETA
|
||||
#define PACKED_KEY_SIZE 25
|
||||
///\brief Data storage for this Key.
|
||||
///
|
||||
/// - 8 bytes: MSB storage of the position of the first packet of this keyframe within the file.
|
||||
/// - 3 bytes: MSB storage of the duration of this keyframe.
|
||||
/// - 4 bytes: MSB storage of the number of this keyframe.
|
||||
/// - 2 bytes: MSB storage of the amount of parts in this keyframe.
|
||||
/// - 8 bytes: MSB storage of the timestamp associated with this keyframe's first packet.
|
||||
#else
|
||||
#define PACKED_KEY_SIZE 16
|
||||
///\brief Data storage for this Key.
|
||||
///
|
||||
/// - 5 bytes: MSB storage of the position of the first packet of this keyframe within the file.
|
||||
|
@ -212,7 +226,8 @@ namespace DTSC {
|
|||
/// - 2 bytes: MSB storage of the number of this keyframe.
|
||||
/// - 2 bytes: MSB storage of the amount of parts in this keyframe.
|
||||
/// - 4 bytes: MSB storage of the timestamp associated with this keyframe's first packet.
|
||||
char data[16];
|
||||
#endif
|
||||
char data[PACKED_KEY_SIZE];
|
||||
};
|
||||
|
||||
///\brief Basic class for storage of data associated with fragments.
|
||||
|
@ -229,13 +244,24 @@ namespace DTSC {
|
|||
char * getData();
|
||||
void toPrettyString(std::ostream & str, int indent = 0);
|
||||
private:
|
||||
///\Brief Data storage for this Fragment.
|
||||
#ifdef BIGMETA
|
||||
#define PACKED_FRAGMENT_SIZE 13
|
||||
///\brief Data storage for this Fragment.
|
||||
///
|
||||
/// - 4 bytes: duration (in milliseconds)
|
||||
/// - 1 byte: length (amount of keyframes)
|
||||
/// - 4 bytes: number of first keyframe in fragment
|
||||
/// - 4 bytes: size of fragment in bytes
|
||||
#else
|
||||
#define PACKED_FRAGMENT_SIZE 11
|
||||
///\brief Data storage for this Fragment.
|
||||
///
|
||||
/// - 4 bytes: duration (in milliseconds)
|
||||
/// - 1 byte: length (amount of keyframes)
|
||||
/// - 2 bytes: number of first keyframe in fragment
|
||||
/// - 4 bytes: size of fragment in bytes
|
||||
char data[11];
|
||||
#endif
|
||||
char data[PACKED_FRAGMENT_SIZE];
|
||||
};
|
||||
|
||||
///\brief Class for storage of track data
|
||||
|
@ -249,10 +275,10 @@ namespace DTSC {
|
|||
return (parts.size() && keySizes.size() && (keySizes.size() == keys.size()));
|
||||
}
|
||||
void update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size = 5000);
|
||||
int getSendLen();
|
||||
void send(Socket::Connection & conn);
|
||||
int getSendLen(bool skipDynamic = false);
|
||||
void send(Socket::Connection & conn, bool skipDynamic = false);
|
||||
void writeTo(char *& p);
|
||||
JSON::Value toJSON();
|
||||
JSON::Value toJSON(bool skipDynamic = false);
|
||||
std::deque<Fragment> fragments;
|
||||
std::deque<Key> keys;
|
||||
std::deque<unsigned long> keySizes;
|
||||
|
@ -302,8 +328,8 @@ namespace DTSC {
|
|||
void updatePosOverride(DTSC::Packet & pack, unsigned long bpos);
|
||||
void update(JSON::Value & pack, unsigned long segment_size = 5000);
|
||||
void update(long long packTime, long long packOffset, long long packTrack, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize = 0, unsigned long segment_size = 5000);
|
||||
unsigned int getSendLen();
|
||||
void send(Socket::Connection & conn);
|
||||
unsigned int getSendLen(bool skipDynamic = false, std::set<unsigned long> selectedTracks = std::set<unsigned long>());
|
||||
void send(Socket::Connection & conn, bool skipDynamic = false, std::set<unsigned long> selectedTracks = std::set<unsigned long>());
|
||||
void writeTo(char * p);
|
||||
JSON::Value toJSON();
|
||||
void reset();
|
||||
|
@ -348,7 +374,6 @@ namespace DTSC {
|
|||
long int endPos;
|
||||
void readHeader(int pos);
|
||||
DTSC::Packet myPack;
|
||||
JSON::Value metaStorage;
|
||||
Meta metadata;
|
||||
std::map<unsigned int, std::string> trackMapping;
|
||||
long long int currtime;
|
||||
|
|
218
lib/dtscmeta.cpp
218
lib/dtscmeta.cpp
|
@ -109,6 +109,32 @@ namespace DTSC {
|
|||
}
|
||||
}
|
||||
|
||||
void Packet::reInit(Socket::Connection & src) {
|
||||
int sleepCount = 0;
|
||||
null();
|
||||
int toReceive = 0;
|
||||
while (src.connected()){
|
||||
if (!toReceive && src.Received().available(8)){
|
||||
if (src.Received().copy(2) != "DT"){
|
||||
INFO_MSG("Invalid DTSC Packet header encountered (%s)", src.Received().copy(4).c_str());
|
||||
break;
|
||||
}
|
||||
toReceive = Bit::btohl(src.Received().copy(8).data() + 4);
|
||||
}
|
||||
if (toReceive && src.Received().available(toReceive + 8)){
|
||||
std::string dataBuf = src.Received().remove(toReceive + 8);
|
||||
reInit(dataBuf.data(), dataBuf.size());
|
||||
return;
|
||||
}
|
||||
if(!src.spool()){
|
||||
if (sleepCount++ > 60){
|
||||
return;
|
||||
}
|
||||
Util::sleep(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///\brief Initializes a packet with new data
|
||||
///\param data_ The new data for the packet
|
||||
///\param len The length of the data pointed to by data_
|
||||
|
@ -159,11 +185,15 @@ namespace DTSC {
|
|||
if (!memcmp(data, Magic_Header, 4)) {
|
||||
version = DTSC_HEAD;
|
||||
} else {
|
||||
if (!memcmp(data, Magic_Command, 4)) {
|
||||
version = DTCM;
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4");
|
||||
return;
|
||||
|
@ -248,7 +278,7 @@ namespace DTSC {
|
|||
if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) {
|
||||
p++;
|
||||
//object, scan contents
|
||||
while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE)
|
||||
while (p < max && p[0] + p[1] != 0) { //while not encountering 0x0000 (we assume 0x0000EE)
|
||||
if (p + 2 >= max) {
|
||||
return 0;//out of packet!
|
||||
}
|
||||
|
@ -264,7 +294,7 @@ namespace DTSC {
|
|||
if (p[0] == DTSC_ARR) {
|
||||
p++;
|
||||
//array, scan contents
|
||||
while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE)
|
||||
while (p < max && p[0] + p[1] != 0) { //while not encountering 0x0000 (we assume 0x0000EE)
|
||||
//search through contents...
|
||||
p = skipDTSC(p, max);
|
||||
if (!p) {
|
||||
|
@ -844,53 +874,93 @@ namespace DTSC {
|
|||
|
||||
///\brief Returns the byteposition of a keyframe
|
||||
unsigned long long Key::getBpos() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohll(data);
|
||||
#else
|
||||
return (((unsigned long long)data[0] << 32) | (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Key::setBpos(unsigned long long newBpos) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobll(data, newBpos);
|
||||
#else
|
||||
data[4] = newBpos & 0xFF;
|
||||
data[3] = (newBpos >> 8) & 0xFF;
|
||||
data[2] = (newBpos >> 16) & 0xFF;
|
||||
data[1] = (newBpos >> 24) & 0xFF;
|
||||
data[0] = (newBpos >> 32) & 0xFF;
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned long Key::getLength() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btoh24(data+8);
|
||||
#else
|
||||
return Bit::btoh24(data+5);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Key::setLength(unsigned long newLength) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htob24(data+8, newLength);
|
||||
#else
|
||||
Bit::htob24(data+5, newLength);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns the number of a keyframe
|
||||
unsigned long Key::getNumber() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohl(data + 11);
|
||||
#else
|
||||
return Bit::btohs(data + 8);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Sets the number of a keyframe
|
||||
void Key::setNumber(unsigned long newNumber) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobl(data + 11, newNumber);
|
||||
#else
|
||||
Bit::htobs(data + 8, newNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns the number of parts of a keyframe
|
||||
unsigned short Key::getParts() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohs(data + 15);
|
||||
#else
|
||||
return Bit::btohs(data + 10);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Sets the number of parts of a keyframe
|
||||
void Key::setParts(unsigned short newParts) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobs(data + 15, newParts);
|
||||
#else
|
||||
Bit::htobs(data + 10, newParts);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns the timestamp of a keyframe
|
||||
unsigned long long Key::getTime() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohll(data + 17);
|
||||
#else
|
||||
return Bit::btohl(data + 12);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Sets the timestamp of a keyframe
|
||||
void Key::setTime(unsigned long long newTime) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobll(data + 17, newTime);
|
||||
#else
|
||||
Bit::htobl(data + 12, newTime);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns the data of this keyframe struct
|
||||
|
@ -927,22 +997,38 @@ namespace DTSC {
|
|||
|
||||
///\brief Returns the number of the first keyframe in this fragment
|
||||
unsigned long Fragment::getNumber() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohl(data + 5);
|
||||
#else
|
||||
return Bit::btohs(data + 5);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Sets the number of the first keyframe in this fragment
|
||||
void Fragment::setNumber(unsigned long newNumber) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobl(data + 5, newNumber);
|
||||
#else
|
||||
Bit::htobs(data + 5, newNumber);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns the size of a fragment
|
||||
unsigned long Fragment::getSize() {
|
||||
#ifdef BIGMETA
|
||||
return Bit::btohl(data + 9);
|
||||
#else
|
||||
return Bit::btohl(data + 7);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Sets the size of a fragement
|
||||
void Fragment::setSize(unsigned long newSize) {
|
||||
#ifdef BIGMETA
|
||||
Bit::htobl(data + 9, newSize);
|
||||
#else
|
||||
Bit::htobl(data + 7, newSize);
|
||||
#endif
|
||||
}
|
||||
|
||||
///\brief Returns thte data of this fragment structure
|
||||
|
@ -976,11 +1062,11 @@ namespace DTSC {
|
|||
Track::Track(JSON::Value & trackRef) {
|
||||
if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) {
|
||||
Fragment * tmp = (Fragment *)trackRef["fragments"].asStringRef().data();
|
||||
fragments = std::deque<Fragment>(tmp, tmp + (trackRef["fragments"].asStringRef().size() / 11));
|
||||
fragments = std::deque<Fragment>(tmp, tmp + (trackRef["fragments"].asStringRef().size() / PACKED_FRAGMENT_SIZE));
|
||||
}
|
||||
if (trackRef.isMember("keys") && trackRef["keys"].isString()) {
|
||||
Key * tmp = (Key *)trackRef["keys"].asStringRef().data();
|
||||
keys = std::deque<Key>(tmp, tmp + (trackRef["keys"].asStringRef().size() / 16));
|
||||
keys = std::deque<Key>(tmp, tmp + (trackRef["keys"].asStringRef().size() / PACKED_KEY_SIZE));
|
||||
}
|
||||
if (trackRef.isMember("parts") && trackRef["parts"].isString()) {
|
||||
Part * tmp = (Part *)trackRef["parts"].asStringRef().data();
|
||||
|
@ -1018,13 +1104,13 @@ namespace DTSC {
|
|||
char * tmp = 0;
|
||||
unsigned int tmplen = 0;
|
||||
trackRef.getMember("fragments").getString(tmp, tmplen);
|
||||
fragments = std::deque<Fragment>((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / 11));
|
||||
fragments = std::deque<Fragment>((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / PACKED_FRAGMENT_SIZE));
|
||||
}
|
||||
if (trackRef.getMember("keys").getType() == DTSC_STR) {
|
||||
char * tmp = 0;
|
||||
unsigned int tmplen = 0;
|
||||
trackRef.getMember("keys").getString(tmp, tmplen);
|
||||
keys = std::deque<Key>((Key *)tmp, ((Key *)tmp) + (tmplen / 16));
|
||||
keys = std::deque<Key>((Key *)tmp, ((Key *)tmp) + (tmplen / PACKED_KEY_SIZE));
|
||||
}
|
||||
if (trackRef.getMember("parts").getType() == DTSC_STR) {
|
||||
char * tmp = 0;
|
||||
|
@ -1064,7 +1150,13 @@ namespace DTSC {
|
|||
///Will also insert keyframes on non-video tracks, and creates fragments
|
||||
void Track::update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size) {
|
||||
if ((unsigned long long)packTime < lastms) {
|
||||
DEBUG_MSG(DLVL_WARN, "Received packets for track %u in wrong order (%lld < %llu) - ignoring!", trackID, packTime, lastms);
|
||||
static bool warned = false;
|
||||
if (!warned){
|
||||
ERROR_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further messages on HIGH level.", trackID, packTime, lastms);
|
||||
warned = true;
|
||||
}else{
|
||||
HIGH_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further messages on HIGH level.", trackID, packTime, lastms);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Part newPart;
|
||||
|
@ -1384,20 +1476,22 @@ namespace DTSC {
|
|||
}
|
||||
|
||||
///\brief Determines the "packed" size of a track
|
||||
int Track::getSendLen() {
|
||||
int result = 146 + init.size() + codec.size() + type.size() + getWritableIdentifier().size();
|
||||
result += fragments.size() * 11;
|
||||
result += keys.size() * 16;
|
||||
int Track::getSendLen(bool skipDynamic) {
|
||||
int result = 107 + init.size() + codec.size() + type.size() + getWritableIdentifier().size();
|
||||
if (!skipDynamic){
|
||||
result += fragments.size() * PACKED_FRAGMENT_SIZE + 16;
|
||||
result += keys.size() * PACKED_KEY_SIZE + 11;
|
||||
if (keySizes.size()){
|
||||
result += 11 + (keySizes.size() * 4) + 4;
|
||||
result += (keySizes.size() * 4) + 15;
|
||||
}
|
||||
result += parts.size() * 9 + 12;
|
||||
}
|
||||
result += parts.size() * 9;
|
||||
if (type == "audio") {
|
||||
result += 49;
|
||||
} else if (type == "video") {
|
||||
result += 48;
|
||||
}
|
||||
if (missedFrags) {
|
||||
if (!skipDynamic && missedFrags) {
|
||||
result += 23;
|
||||
}
|
||||
return result;
|
||||
|
@ -1420,19 +1514,23 @@ namespace DTSC {
|
|||
|
||||
///\brief Writes a track to a pointer
|
||||
void Track::writeTo(char *& p) {
|
||||
std::deque<Fragment>::iterator firstFrag = fragments.begin();
|
||||
if (fragments.size() && (&firstFrag) == 0){
|
||||
return;
|
||||
}
|
||||
std::string trackIdent = getWritableIdentifier();
|
||||
writePointer(p, convertShort(trackIdent.size()), 2);
|
||||
writePointer(p, trackIdent);
|
||||
writePointer(p, "\340", 1);//Begin track object
|
||||
writePointer(p, "\000\011fragments\002", 12);
|
||||
writePointer(p, convertInt(fragments.size() * 11), 4);
|
||||
writePointer(p, convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4);
|
||||
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
||||
writePointer(p, it->getData(), 11);
|
||||
writePointer(p, it->getData(), PACKED_FRAGMENT_SIZE);
|
||||
}
|
||||
writePointer(p, "\000\004keys\002", 7);
|
||||
writePointer(p, convertInt(keys.size() * 16), 4);
|
||||
writePointer(p, convertInt(keys.size() * PACKED_KEY_SIZE), 4);
|
||||
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
||||
writePointer(p, it->getData(), 16);
|
||||
writePointer(p, it->getData(), PACKED_KEY_SIZE);
|
||||
}
|
||||
writePointer(p, "\000\010keysizes\002,", 11);
|
||||
writePointer(p, convertInt(keySizes.size() * 4), 4);
|
||||
|
@ -1490,19 +1588,20 @@ namespace DTSC {
|
|||
}
|
||||
|
||||
///\brief Writes a track to a socket
|
||||
void Track::send(Socket::Connection & conn) {
|
||||
void Track::send(Socket::Connection & conn, bool skipDynamic) {
|
||||
conn.SendNow(convertShort(getWritableIdentifier().size()), 2);
|
||||
conn.SendNow(getWritableIdentifier());
|
||||
conn.SendNow("\340", 1);//Begin track object
|
||||
if (!skipDynamic){
|
||||
conn.SendNow("\000\011fragments\002", 12);
|
||||
conn.SendNow(convertInt(fragments.size() * 11), 4);
|
||||
conn.SendNow(convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4);
|
||||
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
||||
conn.SendNow(it->getData(), 11);
|
||||
conn.SendNow(it->getData(), PACKED_FRAGMENT_SIZE);
|
||||
}
|
||||
conn.SendNow("\000\004keys\002", 7);
|
||||
conn.SendNow(convertInt(keys.size() * 16), 4);
|
||||
conn.SendNow(convertInt(keys.size() * PACKED_KEY_SIZE), 4);
|
||||
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
||||
conn.SendNow(it->getData(), 16);
|
||||
conn.SendNow(it->getData(), PACKED_KEY_SIZE);
|
||||
}
|
||||
conn.SendNow("\000\010keysizes\002,", 11);
|
||||
conn.SendNow(convertInt(keySizes.size() * 4), 4);
|
||||
|
@ -1520,9 +1619,10 @@ namespace DTSC {
|
|||
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
||||
conn.SendNow(it->getData(), 9);
|
||||
}
|
||||
}
|
||||
conn.SendNow("\000\007trackid\001", 10);
|
||||
conn.SendNow(convertLongLong(trackID), 8);
|
||||
if (missedFrags) {
|
||||
if (!skipDynamic && missedFrags) {
|
||||
conn.SendNow("\000\014missed_frags\001", 15);
|
||||
conn.SendNow(convertLongLong(missedFrags), 8);
|
||||
}
|
||||
|
@ -1560,10 +1660,12 @@ namespace DTSC {
|
|||
}
|
||||
|
||||
///\brief Determines the "packed" size of a meta object
|
||||
unsigned int Meta::getSendLen() {
|
||||
unsigned int Meta::getSendLen(bool skipDynamic, std::set<unsigned long> selectedTracks) {
|
||||
unsigned int dataLen = 16 + (vod ? 14 : 0) + (live ? 15 : 0) + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21;
|
||||
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
||||
dataLen += it->second.getSendLen();
|
||||
if (!selectedTracks.size() || selectedTracks.count(it->first)){
|
||||
dataLen += it->second.getSendLen(skipDynamic);
|
||||
}
|
||||
}
|
||||
return dataLen + 8; //add 8 bytes header
|
||||
}
|
||||
|
@ -1600,13 +1702,15 @@ namespace DTSC {
|
|||
}
|
||||
|
||||
///\brief Writes a meta object to a socket
|
||||
void Meta::send(Socket::Connection & conn) {
|
||||
int dataLen = getSendLen() - 8; //strip 8 bytes header
|
||||
void Meta::send(Socket::Connection & conn, bool skipDynamic, std::set<unsigned long> selectedTracks) {
|
||||
int dataLen = getSendLen(skipDynamic, selectedTracks) - 8; //strip 8 bytes header
|
||||
conn.SendNow(DTSC::Magic_Header, 4);
|
||||
conn.SendNow(convertInt(dataLen), 4);
|
||||
conn.SendNow("\340\000\006tracks\340", 10);
|
||||
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) {
|
||||
it->second.send(conn);
|
||||
if (!selectedTracks.size() || selectedTracks.count(it->first)){
|
||||
it->second.send(conn, skipDynamic);
|
||||
}
|
||||
}
|
||||
conn.SendNow("\000\000\356", 3);//End tracks object
|
||||
if (vod) {
|
||||
|
@ -1631,35 +1735,38 @@ namespace DTSC {
|
|||
}
|
||||
|
||||
///\brief Converts a track to a JSON::Value
|
||||
JSON::Value Track::toJSON() {
|
||||
JSON::Value Track::toJSON(bool skipDynamic) {
|
||||
JSON::Value result;
|
||||
std::string tmp;
|
||||
tmp.reserve(fragments.size() * 11);
|
||||
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
||||
tmp.append(it->getData(), 11);
|
||||
if (!skipDynamic) {
|
||||
tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE);
|
||||
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
|
||||
tmp.append(it->getData(), PACKED_FRAGMENT_SIZE);
|
||||
}
|
||||
result["fragments"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(keys.size() * PACKED_KEY_SIZE);
|
||||
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
||||
tmp.append(it->getData(), PACKED_KEY_SIZE);
|
||||
}
|
||||
result["keys"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(keySizes.size() * 4);
|
||||
for (unsigned int i = 0; i < keySizes.size(); i++){
|
||||
tmp += (char)((keySizes[i] >> 24));
|
||||
tmp += (char)((keySizes[i] >> 16));
|
||||
tmp += (char)((keySizes[i] >> 8));
|
||||
tmp += (char)(keySizes[i]);
|
||||
}
|
||||
result["keysizes"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(parts.size() * 9);
|
||||
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
||||
tmp.append(it->getData(), 9);
|
||||
}
|
||||
result["parts"] = tmp;
|
||||
}
|
||||
result["fragments"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(keys.size() * 16);
|
||||
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
|
||||
tmp.append(it->getData(), 16);
|
||||
}
|
||||
result["keys"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(keySizes.size() * 4);
|
||||
for (unsigned int i = 0; i < keySizes.size(); i++){
|
||||
tmp += (char)((keySizes[i] >> 24));
|
||||
tmp += (char)((keySizes[i] >> 16));
|
||||
tmp += (char)((keySizes[i] >> 8));
|
||||
tmp += (char)(keySizes[i]);
|
||||
}
|
||||
result["keysizes"] = tmp;
|
||||
tmp = "";
|
||||
tmp.reserve(parts.size() * 9);
|
||||
for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
|
||||
tmp.append(it->getData(), 9);
|
||||
}
|
||||
result["parts"] = tmp;
|
||||
result["init"] = init;
|
||||
result["trackid"] = trackID;
|
||||
result["firstms"] = (long long)firstms;
|
||||
result["lastms"] = (long long)lastms;
|
||||
|
@ -1669,7 +1776,6 @@ namespace DTSC {
|
|||
}
|
||||
result["codec"] = codec;
|
||||
result["type"] = type;
|
||||
result["init"] = init;
|
||||
if (type == "audio") {
|
||||
result["rate"] = rate;
|
||||
result["size"] = size;
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
#include <string.h> //memcpy
|
||||
#include <sstream>
|
||||
|
||||
|
||||
#include "h264.h" //Needed for init data parsing in case of invalid values from FLV init
|
||||
|
||||
/// Holds the last FLV header parsed.
|
||||
/// Defaults to a audio+video header on FLV version 0x01 if no header received yet.
|
||||
char FLV::Header[13] = {'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09, 0, 0, 0, 0};
|
||||
|
@ -1099,6 +1102,15 @@ JSON::Value FLV::Tag::toJSON(DTSC::Meta & metadata, AMF::Object & amf_storage, u
|
|||
}
|
||||
metadata.tracks[reTrack].init = std::string((char *)data + 12, (size_t)len - 16);
|
||||
}
|
||||
///this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data...
|
||||
if (!metadata.tracks[reTrack].width || !metadata.tracks[reTrack].height || !metadata.tracks[reTrack].fpks){
|
||||
h264::sequenceParameterSet sps;
|
||||
sps.fromDTSCInit(metadata.tracks[reTrack].init);
|
||||
h264::SPSMeta spsChar = sps.getCharacteristics();
|
||||
metadata.tracks[reTrack].width = spsChar.width;
|
||||
metadata.tracks[reTrack].height = spsChar.height;
|
||||
metadata.tracks[reTrack].fpks = spsChar.fps * 1000;
|
||||
}
|
||||
pack_out.null();
|
||||
return pack_out; //skip rest of parsing, get next tag.
|
||||
}
|
||||
|
|
10
lib/h264.cpp
10
lib/h264.cpp
|
@ -81,6 +81,12 @@ namespace h264 {
|
|||
|
||||
sequenceParameterSet::sequenceParameterSet(const char * _data, unsigned long _dataLen) : data(_data), dataLen(_dataLen) {}
|
||||
|
||||
//DTSC Initdata is the payload for an avcc box. init[8+] is data, init[6-7] is a network-encoded length
|
||||
void sequenceParameterSet::fromDTSCInit(const std::string & dtscInit){
|
||||
data = dtscInit.data() + 8;
|
||||
dataLen = Bit::btohs(dtscInit.data() + 6);
|
||||
}
|
||||
|
||||
SPSMeta sequenceParameterSet::getCharacteristics() const {
|
||||
SPSMeta result;
|
||||
|
||||
|
@ -107,8 +113,10 @@ namespace h264 {
|
|||
}
|
||||
|
||||
char profileIdc = bs.get(8);
|
||||
result.profile = profileIdc;
|
||||
//Start skipping unused data
|
||||
bs.skip(16);
|
||||
bs.skip(8);
|
||||
result.level = bs.get(8);
|
||||
bs.getUExpGolomb();
|
||||
if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) {
|
||||
//chroma format idc
|
||||
|
|
|
@ -12,6 +12,8 @@ namespace h264 {
|
|||
unsigned int width;
|
||||
unsigned int height;
|
||||
double fps;
|
||||
uint8_t profile;
|
||||
uint8_t level;
|
||||
};
|
||||
|
||||
///Class for analyzing generic nal units
|
||||
|
@ -50,7 +52,8 @@ namespace h264 {
|
|||
|
||||
class sequenceParameterSet {
|
||||
public:
|
||||
sequenceParameterSet(const char * _data, unsigned long _dataLen);
|
||||
sequenceParameterSet(const char * _data = NULL, unsigned long _dataLen = 0);
|
||||
void fromDTSCInit(const std::string & dtscInit);
|
||||
SPSMeta getCharacteristics() const;
|
||||
private:
|
||||
const char * data;
|
||||
|
|
|
@ -181,13 +181,11 @@ void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & co
|
|||
StartResponse("200", "OK", request, conn, bufferAllChunks);
|
||||
}
|
||||
|
||||
/// After receiving a header with this object, this function call will:
|
||||
/// - Forward the headers to the 'to' Socket::Connection.
|
||||
/// After receiving a header with this object, and after a call with SendResponse/SendRequest with this object, this function call will:
|
||||
/// - Retrieve all the body from the 'from' Socket::Connection.
|
||||
/// - Forward those contents as-is to the 'to' Socket::Connection.
|
||||
/// It blocks until completed or either of the connections reaches an error state.
|
||||
void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) {
|
||||
SendResponse(url, method, to);
|
||||
if (getChunks) {
|
||||
unsigned int proxyingChunk = 0;
|
||||
while (to.connected() && from.connected()) {
|
||||
|
@ -315,6 +313,20 @@ std::string HTTP::Parser::GetVar(std::string i) {
|
|||
return vars[i];
|
||||
}
|
||||
|
||||
std::string HTTP::Parser::allVars(){
|
||||
std::string ret;
|
||||
if (!vars.size()){return ret;}
|
||||
for (std::map<std::string, std::string>::iterator it = vars.begin(); it != vars.end(); ++it){
|
||||
if (ret.size() > 1){
|
||||
ret += "&";
|
||||
}else{
|
||||
ret += "?";
|
||||
}
|
||||
ret += it->first + "=" + Encodings::URL::encode(it->second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Sets header i to string value v.
|
||||
void HTTP::Parser::SetHeader(std::string i, std::string v) {
|
||||
Trim(i);
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace HTTP {
|
|||
std::string GetHeader(std::string i);
|
||||
std::string GetVar(std::string i);
|
||||
std::string getUrl();
|
||||
std::string allVars();
|
||||
void SetHeader(std::string i, std::string v);
|
||||
void SetHeader(std::string i, long long v);
|
||||
void setCORSHeaders();
|
||||
|
@ -44,11 +45,13 @@ namespace HTTP {
|
|||
unsigned int length;
|
||||
bool headerOnly; ///< If true, do not parse body if the length is a known size.
|
||||
bool bufferChunks;
|
||||
//this bool was private
|
||||
bool sendingChunks;
|
||||
|
||||
private:
|
||||
bool seenHeaders;
|
||||
bool seenReq;
|
||||
bool getChunks;
|
||||
bool sendingChunks;
|
||||
unsigned int doingChunk;
|
||||
bool parse(std::string & HTTPbuffer);
|
||||
void parseVars(std::string data);
|
||||
|
|
14
lib/mp4.cpp
14
lib/mp4.cpp
|
@ -104,6 +104,7 @@ namespace MP4 {
|
|||
}
|
||||
} else if (size == 0) {
|
||||
fseek(newData, 0, SEEK_END);
|
||||
return true;
|
||||
}
|
||||
DONTEVEN_MSG("skipping size 0x%.8lX", size);
|
||||
if (fseek(newData, pos + size, SEEK_SET) == 0) {
|
||||
|
@ -132,6 +133,10 @@ namespace MP4 {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (size == 0){//no else if, because the extended size MAY be 0...
|
||||
fseek(newData, 0, SEEK_END);
|
||||
size = ftell(newData) - pos;
|
||||
}
|
||||
fseek(newData, pos, SEEK_SET);
|
||||
data = (char *)realloc(data, size);
|
||||
data_size = size;
|
||||
|
@ -160,6 +165,9 @@ namespace MP4 {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (size == 0){
|
||||
size = newData.size();
|
||||
}
|
||||
if (newData.size() >= size) {
|
||||
data = (char *)realloc(data, size);
|
||||
data_size = size;
|
||||
|
@ -383,9 +391,10 @@ namespace MP4 {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
std::string retval = std::string(indent, ' ') + "Unimplemented pretty-printing for box " + std::string(data + 4, 4) + "\n";
|
||||
std::stringstream retval;
|
||||
retval << std::string(indent, ' ') << "Unimplemented pretty-printing for box " << std::string(data + 4, 4) << " (" << ntohl(((int*)data)[0]) << ")\n";
|
||||
/// \todo Implement hexdump for unimplemented boxes?
|
||||
return retval;
|
||||
return retval.str();
|
||||
}
|
||||
|
||||
/// Sets the 8 bits integer at the given index.
|
||||
|
@ -807,4 +816,5 @@ namespace MP4 {
|
|||
}
|
||||
return r.str();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1091,10 +1091,15 @@ namespace MP4 {
|
|||
return;
|
||||
}
|
||||
}
|
||||
memset(data + payloadOffset + 4, 0, 4);
|
||||
memcpy(data + payloadOffset + 4, newMinorVersion, 4);
|
||||
}
|
||||
|
||||
std::string FTYP::getMinorVersion() {
|
||||
static char zero[4] = {0,0,0,0};
|
||||
if (memcmp(zero, data+payloadOffset+4, 4) == 0){
|
||||
return "";
|
||||
}
|
||||
return std::string(data + payloadOffset + 4, 4);
|
||||
}
|
||||
|
||||
|
@ -2335,7 +2340,8 @@ namespace MP4 {
|
|||
setEntryCount(no + 1);
|
||||
}
|
||||
setInt32(newCTTSEntry.sampleCount, 8 + no * 8);
|
||||
setInt32(newCTTSEntry.sampleOffset, 8 + (no * 8) + 4);
|
||||
setInt32(*(reinterpret_cast<uint32_t*>(&newCTTSEntry.sampleOffset)), 8 + (no * 8) + 4);
|
||||
|
||||
}
|
||||
|
||||
CTTSEntry CTTS::getCTTSEntry(uint32_t no) {
|
||||
|
@ -2345,7 +2351,8 @@ namespace MP4 {
|
|||
return inval;
|
||||
}
|
||||
retval.sampleCount = getInt32(8 + (no * 8));
|
||||
retval.sampleOffset = getInt32(8 + (no * 8) + 4);
|
||||
uint32_t tmp = getInt32(8 + (no * 8) + 4);
|
||||
retval.sampleOffset = *(reinterpret_cast<int32_t*>(&tmp));
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
|
|
@ -486,7 +486,7 @@ namespace MP4 {
|
|||
|
||||
struct CTTSEntry {
|
||||
uint32_t sampleCount;
|
||||
uint32_t sampleOffset;
|
||||
int32_t sampleOffset;
|
||||
};
|
||||
|
||||
class CTTS: public fullBox {
|
||||
|
@ -714,3 +714,4 @@ namespace MP4 {
|
|||
std::string toPrettyString(uint32_t indent = 0);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,9 @@ namespace MP4 {
|
|||
if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") {
|
||||
return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent);
|
||||
}
|
||||
if (UUID == "6d1d9b05-42d5-44e6-80e2-141daff757b2") {
|
||||
return ((UUID_TFXD *)this)->toPrettyString(indent);
|
||||
}
|
||||
std::stringstream r;
|
||||
r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl;
|
||||
r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl;
|
||||
|
@ -205,4 +208,68 @@ namespace MP4 {
|
|||
}
|
||||
return r.str();
|
||||
}
|
||||
|
||||
UUID_TFXD::UUID_TFXD() {
|
||||
setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2");
|
||||
setVersion(0);
|
||||
setFlags(0);
|
||||
}
|
||||
|
||||
void UUID_TFXD::setVersion(uint32_t newVersion) {
|
||||
setInt8(newVersion, 16);
|
||||
}
|
||||
|
||||
uint32_t UUID_TFXD::getVersion() {
|
||||
return getInt8(16);
|
||||
}
|
||||
|
||||
void UUID_TFXD::setFlags(uint32_t newFlags) {
|
||||
setInt24(newFlags, 17);
|
||||
}
|
||||
|
||||
uint32_t UUID_TFXD::getFlags() {
|
||||
return getInt24(17);
|
||||
}
|
||||
|
||||
void UUID_TFXD::setTime(uint64_t newTime) {
|
||||
if (getVersion() == 0) {
|
||||
setInt32(newTime, 20);
|
||||
} else {
|
||||
setInt64(newTime, 20);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t UUID_TFXD::getTime() {
|
||||
if (getVersion() == 0) {
|
||||
return getInt32(20);
|
||||
} else {
|
||||
return getInt64(20);
|
||||
}
|
||||
}
|
||||
|
||||
void UUID_TFXD::setDuration(uint64_t newDuration) {
|
||||
if (getVersion() == 0) {
|
||||
setInt32(newDuration, 24);
|
||||
} else {
|
||||
setInt64(newDuration, 28);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t UUID_TFXD::getDuration() {
|
||||
if (getVersion() == 0) {
|
||||
return getInt32(24);
|
||||
} else {
|
||||
return getInt64(28);
|
||||
}
|
||||
}
|
||||
|
||||
std::string UUID_TFXD::toPrettyString(uint32_t indent) {
|
||||
std::stringstream r;
|
||||
setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2");
|
||||
r << std::string(indent, ' ') << "[6d1d9b05-42d5-44e6-80e2-141daff757b2] TFXD Box (" << boxedSize() << ")" << std::endl;
|
||||
r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl;
|
||||
r << std::string(indent + 1, ' ') << "Time = " << getTime() << std::endl;
|
||||
r << std::string(indent + 1, ' ') << "Duration = " << getDuration() << std::endl;
|
||||
return r.str();
|
||||
}
|
||||
}
|
||||
|
|
13
lib/mp4_ms.h
13
lib/mp4_ms.h
|
@ -37,4 +37,17 @@ namespace MP4 {
|
|||
std::string toPrettyString(uint32_t indent = 0);
|
||||
};
|
||||
|
||||
class UUID_TFXD: public UUID {
|
||||
public:
|
||||
UUID_TFXD();
|
||||
void setVersion(uint32_t newVersion);
|
||||
uint32_t getVersion();
|
||||
void setFlags(uint32_t newFlags);
|
||||
uint32_t getFlags();
|
||||
void setTime(uint64_t newTime);
|
||||
uint64_t getTime();
|
||||
void setDuration(uint64_t newDuration);
|
||||
uint64_t getDuration();
|
||||
std::string toPrettyString(uint32_t indent = 0);
|
||||
};
|
||||
}
|
||||
|
|
136
lib/procs.cpp
136
lib/procs.cpp
|
@ -22,13 +22,31 @@
|
|||
#include "timing.h"
|
||||
|
||||
std::set<pid_t> Util::Procs::plist;
|
||||
std::set<int> Util::Procs::socketList;
|
||||
bool Util::Procs::handler_set = false;
|
||||
bool Util::Procs::thread_handler = false;
|
||||
tthread::mutex Util::Procs::plistMutex;
|
||||
tthread::thread * Util::Procs::reaper_thread = 0;
|
||||
|
||||
|
||||
|
||||
static bool childRunning(pid_t p) {
|
||||
pid_t ret = waitpid(p, 0, WNOHANG);
|
||||
/// Local-only function. Attempts to reap child and returns current running status.
|
||||
bool Util::Procs::childRunning(pid_t p) {
|
||||
int status;
|
||||
pid_t ret = waitpid(p, &status, WNOHANG);
|
||||
if (ret == p) {
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
int exitcode = -1;
|
||||
if (WIFEXITED(status)) {
|
||||
exitcode = WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
exitcode = -WTERMSIG(status);
|
||||
}
|
||||
if (plist.count(ret)) {
|
||||
HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode);
|
||||
plist.erase(ret);
|
||||
} else {
|
||||
HIGH_MSG("Child process %d exited with code %d", ret, exitcode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (ret < 0 && errno == EINTR) {
|
||||
|
@ -48,7 +66,17 @@ bool Util::Procs::isRunning(pid_t pid){
|
|||
/// all remaining children. Waits one more second for cleanup to finish, then exits.
|
||||
void Util::Procs::exit_handler() {
|
||||
int waiting = 0;
|
||||
std::set<pid_t> listcopy = plist;
|
||||
std::set<pid_t> listcopy;
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
listcopy = plist;
|
||||
thread_handler = false;
|
||||
}
|
||||
if (reaper_thread){
|
||||
reaper_thread->join();
|
||||
delete reaper_thread;
|
||||
reaper_thread = 0;
|
||||
}
|
||||
std::set<pid_t>::iterator it;
|
||||
if (listcopy.empty()) {
|
||||
return;
|
||||
|
@ -62,7 +90,7 @@ void Util::Procs::exit_handler() {
|
|||
break;
|
||||
}
|
||||
if (!listcopy.empty()) {
|
||||
Util::sleep(20);
|
||||
Util::wait(20);
|
||||
++waiting;
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +99,7 @@ void Util::Procs::exit_handler() {
|
|||
return;
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Sending SIGINT to remaining %d children", (int)listcopy.size());
|
||||
WARN_MSG("Sending SIGINT to remaining %d children", (int)listcopy.size());
|
||||
//send sigint to all remaining
|
||||
if (!listcopy.empty()) {
|
||||
for (it = listcopy.begin(); it != listcopy.end(); it++) {
|
||||
|
@ -80,7 +108,7 @@ void Util::Procs::exit_handler() {
|
|||
}
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Waiting up to 5 seconds for %d children to terminate.", (int)listcopy.size());
|
||||
INFO_MSG("Waiting up to 5 seconds for %d children to terminate.", (int)listcopy.size());
|
||||
waiting = 0;
|
||||
//wait up to 5 seconds for applications to shut down
|
||||
while (!listcopy.empty() && waiting <= 250) {
|
||||
|
@ -90,7 +118,7 @@ void Util::Procs::exit_handler() {
|
|||
break;
|
||||
}
|
||||
if (!listcopy.empty()) {
|
||||
Util::sleep(20);
|
||||
Util::wait(20);
|
||||
++waiting;
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +127,7 @@ void Util::Procs::exit_handler() {
|
|||
return;
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Sending SIGKILL to remaining %d children", (int)listcopy.size());
|
||||
ERROR_MSG("Sending SIGKILL to remaining %d children", (int)listcopy.size());
|
||||
//send sigkill to all remaining
|
||||
if (!listcopy.empty()) {
|
||||
for (it = listcopy.begin(); it != listcopy.end(); it++) {
|
||||
|
@ -108,7 +136,7 @@ void Util::Procs::exit_handler() {
|
|||
}
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL, "Waiting up to a second for %d children to terminate.", (int)listcopy.size());
|
||||
INFO_MSG("Waiting up to a second for %d children to terminate.", (int)listcopy.size());
|
||||
waiting = 0;
|
||||
//wait up to 1 second for applications to shut down
|
||||
while (!listcopy.empty() && waiting <= 50) {
|
||||
|
@ -118,7 +146,7 @@ void Util::Procs::exit_handler() {
|
|||
break;
|
||||
}
|
||||
if (!listcopy.empty()) {
|
||||
Util::sleep(20);
|
||||
Util::wait(20);
|
||||
++waiting;
|
||||
}
|
||||
}
|
||||
|
@ -126,14 +154,17 @@ void Util::Procs::exit_handler() {
|
|||
if (listcopy.empty()) {
|
||||
return;
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Giving up with %d children left.", (int)listcopy.size());
|
||||
|
||||
FAIL_MSG("Giving up with %d children left.", (int)listcopy.size());
|
||||
}
|
||||
|
||||
/// Sets up exit and childsig handlers.
|
||||
/// Spawns grim_reaper. exit handler despawns grim_reaper
|
||||
/// Called by every Start* function.
|
||||
void Util::Procs::setHandler() {
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
if (!handler_set) {
|
||||
thread_handler = true;
|
||||
reaper_thread = new tthread::thread(grim_reaper, 0);
|
||||
struct sigaction new_action;
|
||||
new_action.sa_handler = childsig_handler;
|
||||
sigemptyset(&new_action.sa_mask);
|
||||
|
@ -144,19 +175,21 @@ void Util::Procs::setHandler() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Used internally to capture child signals and update plist.
|
||||
void Util::Procs::childsig_handler(int signum) {
|
||||
if (signum != SIGCHLD) {
|
||||
return;
|
||||
}
|
||||
///Thread that loops until thread_handler is false.
|
||||
///Reaps available children and then sleeps for a second.
|
||||
///Not done in signal handler so we can use a mutex to prevent race conditions.
|
||||
void Util::Procs::grim_reaper(void * n){
|
||||
VERYHIGH_MSG("Grim reaper start");
|
||||
while (thread_handler){
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
int status;
|
||||
pid_t ret = -1;
|
||||
while (ret != 0) {
|
||||
ret = waitpid(-1, &status, WNOHANG);
|
||||
if (ret <= 0) { //ignore, would block otherwise
|
||||
if (ret == 0 || errno != EINTR) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -166,20 +199,25 @@ void Util::Procs::childsig_handler(int signum) {
|
|||
} else if (WIFSIGNALED(status)) {
|
||||
exitcode = -WTERMSIG(status);
|
||||
} else { // not possible
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
if (plist.count(ret)) {
|
||||
HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode);
|
||||
plist.erase(ret);
|
||||
#if DEBUG >= DLVL_HIGH
|
||||
if (!isActive(pname)) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Process %d fully terminated", ret);
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_HIGH, "Child process %d exited", ret);
|
||||
HIGH_MSG("Child process %d exited with code %d", ret, exitcode);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
Util::sleep(500);
|
||||
}
|
||||
VERYHIGH_MSG("Grim reaper stop");
|
||||
}
|
||||
|
||||
/// Ignores everything. Separate thread handles waiting for children.
|
||||
void Util::Procs::childsig_handler(int signum) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/// Runs the given command and returns the stdout output as a string.
|
||||
|
@ -187,7 +225,7 @@ std::string Util::Procs::getOutputOf(char * const * argv) {
|
|||
std::string ret;
|
||||
int fin = 0, fout = -1, ferr = 0;
|
||||
pid_t myProc = StartPiped(argv, &fin, &fout, &ferr);
|
||||
while (isActive(myProc)) {
|
||||
while (childRunning(myProc)) {
|
||||
Util::sleep(100);
|
||||
}
|
||||
FILE * outFile = fdopen(fout, "r");
|
||||
|
@ -201,6 +239,31 @@ std::string Util::Procs::getOutputOf(char * const * argv) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
|
||||
///This function prepares a deque for getOutputOf and automatically inserts a NULL at the end of the char* const*
|
||||
char* const* Util::Procs::dequeToArgv(std::deque<std::string> & argDeq){
|
||||
char** ret = (char**)malloc((argDeq.size()+1)*sizeof(char*));
|
||||
for (int i = 0; i<argDeq.size(); i++){
|
||||
ret[i] = (char*)argDeq[i].c_str();
|
||||
}
|
||||
ret[argDeq.size()] = NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string Util::Procs::getOutputOf(std::deque<std::string> & argDeq){
|
||||
std::string ret;
|
||||
char* const* argv = dequeToArgv(argDeq);//Note: Do not edit deque before executing command
|
||||
ret = getOutputOf(argv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pid_t Util::Procs::StartPiped(std::deque<std::string> & argDeq, int * fdin, int * fdout, int * fderr) {
|
||||
pid_t ret;
|
||||
char* const* argv = dequeToArgv(argDeq);//Note: Do not edit deque before executing command
|
||||
ret = Util::Procs::StartPiped(argv, fdin, fdout, fderr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// Starts a new process with given fds if the name is not already active.
|
||||
/// \return 0 if process was not started, process PID otherwise.
|
||||
/// \arg argv Command for this process.
|
||||
|
@ -258,6 +321,10 @@ pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int
|
|||
}
|
||||
pid = fork();
|
||||
if (pid == 0) { //child
|
||||
//Close all sockets in the socketList
|
||||
for (std::set<int>::iterator it = Util::Procs::socketList.begin(); it != Util::Procs::socketList.end(); ++it){
|
||||
close(*it);
|
||||
}
|
||||
if (!fdin) {
|
||||
dup2(devnull, STDIN_FILENO);
|
||||
} else if (*fdin == -1) {
|
||||
|
@ -319,7 +386,10 @@ pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int
|
|||
}
|
||||
return 0;
|
||||
} else { //parent
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
plist.insert(pid);
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Piped process %s started, PID %d", argv[0], pid);
|
||||
if (devnull != -1) {
|
||||
close(devnull);
|
||||
|
@ -354,7 +424,11 @@ void Util::Procs::Murder(pid_t name) {
|
|||
|
||||
/// (Attempts to) stop all running child processes.
|
||||
void Util::Procs::StopAll() {
|
||||
std::set<pid_t> listcopy = plist;
|
||||
std::set<pid_t> listcopy;
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
listcopy = plist;
|
||||
}
|
||||
std::set<pid_t>::iterator it;
|
||||
for (it = listcopy.begin(); it != listcopy.end(); it++) {
|
||||
Stop(*it);
|
||||
|
@ -363,11 +437,13 @@ void Util::Procs::StopAll() {
|
|||
|
||||
/// Returns the number of active child processes.
|
||||
int Util::Procs::Count() {
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
return plist.size();
|
||||
}
|
||||
|
||||
/// Returns true if a process with this PID is currently active.
|
||||
bool Util::Procs::isActive(pid_t name) {
|
||||
tthread::lock_guard<tthread::mutex> guard(plistMutex);
|
||||
return (plist.count(name) == 1) && (kill(name, 0) == 0);
|
||||
}
|
||||
|
||||
|
|
15
lib/procs.h
15
lib/procs.h
|
@ -6,6 +6,8 @@
|
|||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include "tinythread.h"
|
||||
|
||||
/// Contains utility code, not directly related to streaming media
|
||||
namespace Util {
|
||||
|
@ -13,21 +15,30 @@ namespace Util {
|
|||
/// Deals with spawning, monitoring and stopping child processes
|
||||
class Procs {
|
||||
private:
|
||||
static std::set<pid_t> plist; ///< Holds active processes
|
||||
static bool childRunning(pid_t p);
|
||||
static tthread::mutex plistMutex;
|
||||
static tthread::thread * reaper_thread;
|
||||
static std::set<pid_t> plist; ///< Holds active process list.
|
||||
static bool handler_set; ///< If true, the sigchld handler has been setup.
|
||||
static bool thread_handler;///< True while thread handler should be running.
|
||||
static void childsig_handler(int signum);
|
||||
static void exit_handler();
|
||||
static void runCmd(std::string & cmd);
|
||||
static void setHandler();
|
||||
static char* const* dequeToArgv(std::deque<std::string> & argDeq);
|
||||
static void grim_reaper(void * n);
|
||||
public:
|
||||
static std::string getOutputOf(char * const * argv);
|
||||
static std::string getOutputOf(std::deque<std::string> & argDeq);
|
||||
static pid_t StartPiped(char * const * argv, int * fdin, int * fdout, int * fderr);
|
||||
static pid_t StartPiped(std::deque<std::string> & argDeq, int * fdin, int * fdout, int * fderr);
|
||||
static void Stop(pid_t name);
|
||||
static void Murder(pid_t name);
|
||||
static void StopAll();
|
||||
static int Count();
|
||||
static bool isActive(pid_t name);
|
||||
static bool isRunning(pid_t pid);
|
||||
static std::set<int> socketList; ///< Holds sockets that should be closed before forking
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,6 @@
|
|||
#include "timing.h"
|
||||
#include "auth.h"
|
||||
|
||||
#ifndef FILLER_DATA
|
||||
#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio."
|
||||
#endif
|
||||
|
||||
std::string RTMPStream::handshake_in; ///< Input for the handshake.
|
||||
std::string RTMPStream::handshake_out; ///< Output for the handshake.
|
||||
|
||||
|
@ -205,7 +201,7 @@ RTMPStream::Chunk::Chunk() {
|
|||
std::string & RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data) {
|
||||
static RTMPStream::Chunk ch;
|
||||
ch.cs_id = cs_id;
|
||||
ch.timestamp = Util::getMS();
|
||||
ch.timestamp = 0;
|
||||
ch.len = data.size();
|
||||
ch.real_len = data.size();
|
||||
ch.len_left = 0;
|
||||
|
@ -458,16 +454,16 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer & buffer) {
|
|||
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "Parsing RTMP chunk result: len_left=%d, real_len=%d", len_left, real_len);
|
||||
|
||||
//read extended timestamp, if neccesary
|
||||
if (ts_header == 0x00ffffff) {
|
||||
//read extended timestamp, if necessary
|
||||
if (ts_header == 0x00ffffff && headertype != 0xC0) {
|
||||
if (!buffer.available(i + 4)) {
|
||||
return false;
|
||||
} //can't read timestamp
|
||||
indata = buffer.copy(i + 4);
|
||||
timestamp += indata[i++ ];
|
||||
timestamp = indata[i++ ];
|
||||
timestamp += indata[i++ ] * 256;
|
||||
timestamp += indata[i++ ] * 256 * 256;
|
||||
timestamp = indata[i++ ] * 256 * 256 * 256;
|
||||
timestamp += indata[i++ ] * 256 * 256 * 256;
|
||||
ts_delta = timestamp;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
#include <arpa/inet.h>
|
||||
#include "socket.h"
|
||||
|
||||
#ifndef FILLER_DATA
|
||||
#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio."
|
||||
#endif
|
||||
|
||||
//forward declaration of FLV::Tag to avoid circular dependencies.
|
||||
namespace FLV {
|
||||
class Tag;
|
||||
|
|
|
@ -95,13 +95,13 @@ namespace IPC {
|
|||
///\param oflag The flags with which to open the semaphore
|
||||
///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise
|
||||
///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise
|
||||
semaphore::semaphore(const char * name, int oflag, mode_t mode, unsigned int value) {
|
||||
semaphore::semaphore(const char * name, int oflag, mode_t mode, unsigned int value, bool noWait) {
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
mySem = 0;
|
||||
#else
|
||||
mySem = SEM_FAILED;
|
||||
#endif
|
||||
open(name, oflag, mode, value);
|
||||
open(name, oflag, mode, value, noWait);
|
||||
}
|
||||
|
||||
///\brief The deconstructor
|
||||
|
@ -126,13 +126,13 @@ namespace IPC {
|
|||
///\param oflag The flags with which to open the semaphore
|
||||
///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise
|
||||
///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise
|
||||
void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value) {
|
||||
void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value, bool noWait) {
|
||||
close();
|
||||
int timer = 0;
|
||||
while (!(*this) && timer++ < 10) {
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
std::string semaName = "Global\\";
|
||||
semaName += name;
|
||||
semaName += (name+1);
|
||||
if (oflag & O_CREAT) {
|
||||
if (oflag & O_EXCL) {
|
||||
//attempt opening, if succes, close handle and return false;
|
||||
|
@ -165,7 +165,7 @@ namespace IPC {
|
|||
mySem = sem_open(name, oflag);
|
||||
}
|
||||
if (!(*this)) {
|
||||
if (errno == ENOENT) {
|
||||
if (errno == ENOENT && !noWait) {
|
||||
Util::wait(500);
|
||||
} else {
|
||||
break;
|
||||
|
@ -405,7 +405,7 @@ namespace IPC {
|
|||
int i = 0;
|
||||
do {
|
||||
if (i != 0) {
|
||||
Util::sleep(1000);
|
||||
Util::wait(1000);
|
||||
}
|
||||
handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str());
|
||||
i++;
|
||||
|
@ -438,14 +438,14 @@ namespace IPC {
|
|||
int i = 0;
|
||||
while (i < 10 && handle == -1 && autoBackoff) {
|
||||
i++;
|
||||
Util::sleep(1000);
|
||||
Util::wait(1000);
|
||||
handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handle == -1) {
|
||||
if (!master_ && autoBackoff) {
|
||||
FAIL_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno));
|
||||
HIGH_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -558,7 +558,7 @@ namespace IPC {
|
|||
int i = 0;
|
||||
while (i < 10 && handle == -1 && autoBackoff) {
|
||||
i++;
|
||||
Util::sleep(1000);
|
||||
Util::wait(1000);
|
||||
handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_RDWR, (mode_t)0600);
|
||||
}
|
||||
}
|
||||
|
@ -676,7 +676,7 @@ namespace IPC {
|
|||
if (splitChar != std::string::npos) {
|
||||
name[splitChar] = '+';
|
||||
}
|
||||
memcpy(data + 48, name.c_str(), std::min((int)name.size(), 100));
|
||||
snprintf(data+48, 100, "%s", name.c_str());
|
||||
}
|
||||
|
||||
///\brief Gets the name of the stream this user is viewing
|
||||
|
@ -686,7 +686,7 @@ namespace IPC {
|
|||
|
||||
///\brief Sets the name of the connector through which this user is viewing
|
||||
void statExchange::connector(std::string name) {
|
||||
memcpy(data + 148, name.c_str(), std::min((int)name.size(), 20));
|
||||
snprintf(data+148, 20, "%s", name.c_str());
|
||||
}
|
||||
|
||||
///\brief Gets the name of the connector through which this user is viewing
|
||||
|
@ -706,6 +706,16 @@ namespace IPC {
|
|||
return result;
|
||||
}
|
||||
|
||||
///\brief Sets checksum field
|
||||
void statExchange::setSync(char s) {
|
||||
data[172] = s;
|
||||
}
|
||||
|
||||
///\brief Gets checksum field
|
||||
char statExchange::getSync() {
|
||||
return data[172];
|
||||
}
|
||||
|
||||
///\brief Creates a semaphore guard, locks the semaphore on call
|
||||
semGuard::semGuard(semaphore * thisSemaphore) : mySemaphore(thisSemaphore) {
|
||||
mySemaphore->wait();
|
||||
|
@ -762,6 +772,7 @@ namespace IPC {
|
|||
|
||||
///\brief The deconstructor
|
||||
sharedServer::~sharedServer() {
|
||||
finishEach();
|
||||
mySemaphore.close();
|
||||
mySemaphore.unlink();
|
||||
}
|
||||
|
@ -818,6 +829,62 @@ namespace IPC {
|
|||
return false;
|
||||
}
|
||||
|
||||
///Disconnect all connected users
|
||||
void sharedServer::finishEach(){
|
||||
if (!hasCounter){
|
||||
return;
|
||||
}
|
||||
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
|
||||
if (!it->mapped || !it->len) {
|
||||
break;
|
||||
}
|
||||
unsigned int offset = 0;
|
||||
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) {
|
||||
it->mapped[offset] = 126;
|
||||
offset += payLen + (hasCounter ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///Returns a pointer to the data for the given index.
|
||||
///Returns null on error or if index is empty.
|
||||
char * sharedServer::getIndex(unsigned int requestId){
|
||||
char * empty = 0;
|
||||
if (!hasCounter) {
|
||||
empty = (char *)malloc(payLen * sizeof(char));
|
||||
memset(empty, 0, payLen);
|
||||
}
|
||||
semGuard tmpGuard(&mySemaphore);
|
||||
unsigned int id = 0;
|
||||
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
|
||||
if (!it->mapped || !it->len) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?");
|
||||
return 0;
|
||||
}
|
||||
unsigned int offset = 0;
|
||||
while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) {
|
||||
if (id == requestId){
|
||||
if (hasCounter) {
|
||||
if (it->mapped[offset] != 0) {
|
||||
return it->mapped + offset + 1;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (memcmp(empty, it->mapped + offset, payLen)) {
|
||||
return it->mapped + offset;
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += payLen + (hasCounter ? 1 : 0);
|
||||
id ++;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
///\brief Parse each of the possible payload pieces, and runs a callback on it if in use.
|
||||
void sharedServer::parseEach(void (*callback)(char * data, size_t len, unsigned int id)) {
|
||||
char * empty = 0;
|
||||
|
@ -829,6 +896,7 @@ namespace IPC {
|
|||
unsigned int id = 0;
|
||||
unsigned int userCount = 0;
|
||||
unsigned int emptyCount = 0;
|
||||
connectedUsers = 0;
|
||||
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
|
||||
if (!it->mapped || !it->len) {
|
||||
DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?");
|
||||
|
@ -842,28 +910,25 @@ namespace IPC {
|
|||
char * counter = it->mapped + offset;
|
||||
//increase the count if needed
|
||||
++userCount;
|
||||
if (*counter & 0x80){
|
||||
connectedUsers++;
|
||||
}
|
||||
if (id >= amount) {
|
||||
amount = id + 1;
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
}
|
||||
unsigned short tmpPID = *((unsigned short *)(it->mapped + 1 + offset + payLen - 2));
|
||||
if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)) {
|
||||
WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID);
|
||||
uint32_t tmpPID = *((uint32_t *)(it->mapped + 1 + offset + payLen - 4));
|
||||
if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127)){
|
||||
WARN_MSG("process disappeared, timing out. (pid %lu)", tmpPID);
|
||||
*counter = 126; //if process is already dead, instant timeout.
|
||||
}
|
||||
callback(it->mapped + offset + 1, payLen, id);
|
||||
switch (*counter) {
|
||||
case 127:
|
||||
DEBUG_MSG(DLVL_HIGH, "Client %u requested disconnect", id);
|
||||
HIGH_MSG("Client %u requested disconnect", id);
|
||||
break;
|
||||
case 126:
|
||||
DEBUG_MSG(DLVL_WARN, "Client %u timed out", id);
|
||||
break;
|
||||
case 255:
|
||||
DEBUG_MSG(DLVL_HIGH, "Client %u disconnected on request", id);
|
||||
break;
|
||||
case 254:
|
||||
DEBUG_MSG(DLVL_WARN, "Client %u disconnect timed out", id);
|
||||
HIGH_MSG("Client %u timed out", id);
|
||||
break;
|
||||
default:
|
||||
#ifndef NOCRASHCHECK
|
||||
|
@ -883,7 +948,7 @@ namespace IPC {
|
|||
#endif
|
||||
break;
|
||||
}
|
||||
if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) {
|
||||
if (*counter == 127 || *counter == 126){
|
||||
memset(it->mapped + offset + 1, 0, payLen);
|
||||
it->mapped[offset] = 0;
|
||||
} else {
|
||||
|
@ -895,7 +960,7 @@ namespace IPC {
|
|||
//bring the counter down if this was the last element
|
||||
if (id == amount - 1) {
|
||||
amount = id;
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
}
|
||||
//stop, we're guaranteed no more pages are full at this point
|
||||
break;
|
||||
|
@ -907,7 +972,7 @@ namespace IPC {
|
|||
//increase the count if needed
|
||||
if (id >= amount) {
|
||||
amount = id + 1;
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
}
|
||||
callback(it->mapped + offset, payLen, id);
|
||||
} else {
|
||||
|
@ -916,7 +981,7 @@ namespace IPC {
|
|||
//bring the counter down if this was the last element
|
||||
if (id == amount - 1) {
|
||||
amount = id;
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount);
|
||||
}
|
||||
//stop, we're guaranteed no more pages are full at this point
|
||||
if (empty) {
|
||||
|
@ -952,12 +1017,14 @@ namespace IPC {
|
|||
hasCounter = 0;
|
||||
payLen = 0;
|
||||
offsetOnPage = 0;
|
||||
countAsViewer= true;
|
||||
}
|
||||
|
||||
|
||||
///\brief Copy constructor for sharedClients
|
||||
///\param rhs The client ro copy
|
||||
sharedClient::sharedClient(const sharedClient & rhs) {
|
||||
countAsViewer = rhs.countAsViewer;
|
||||
baseName = rhs.baseName;
|
||||
payLen = rhs.payLen;
|
||||
hasCounter = rhs.hasCounter;
|
||||
|
@ -978,6 +1045,7 @@ namespace IPC {
|
|||
|
||||
///\brief Assignment operator
|
||||
void sharedClient::operator =(const sharedClient & rhs) {
|
||||
countAsViewer = rhs.countAsViewer;
|
||||
baseName = rhs.baseName;
|
||||
payLen = rhs.payLen;
|
||||
hasCounter = rhs.hasCounter;
|
||||
|
@ -1001,6 +1069,7 @@ namespace IPC {
|
|||
///\param len The size of the payload to allocate
|
||||
///\param withCounter Whether or not this payload has a counter
|
||||
sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/" + name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) {
|
||||
countAsViewer = true;
|
||||
#ifdef __APPLE__
|
||||
//note: O_CREAT is only needed for mac, probably
|
||||
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
|
||||
|
@ -1035,7 +1104,7 @@ namespace IPC {
|
|||
offsetOnPage = offset;
|
||||
if (hasCounter) {
|
||||
myPage.mapped[offset] = 1;
|
||||
*((unsigned short *)(myPage.mapped + 1 + offset + len - 2)) = getpid();
|
||||
*((uint32_t *)(myPage.mapped + 1 + offset + len - 4)) = getpid();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1058,6 +1127,8 @@ namespace IPC {
|
|||
///\brief The deconstructor
|
||||
sharedClient::~sharedClient() {
|
||||
mySemaphore.close();
|
||||
|
||||
|
||||
}
|
||||
|
||||
///\brief Writes data to the shared data
|
||||
|
@ -1079,7 +1150,7 @@ namespace IPC {
|
|||
}
|
||||
if (myPage.mapped) {
|
||||
semGuard tmpGuard(&mySemaphore);
|
||||
myPage.mapped[offsetOnPage] = 127;
|
||||
myPage.mapped[offsetOnPage] = 126;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1089,13 +1160,20 @@ namespace IPC {
|
|||
DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element without counters");
|
||||
return;
|
||||
}
|
||||
if (myPage.mapped[offsetOnPage] < 128) {
|
||||
myPage.mapped[offsetOnPage] = 1;
|
||||
if ((myPage.mapped[offsetOnPage] & 0x7F) < 126) {
|
||||
myPage.mapped[offsetOnPage] = (countAsViewer ? 0x81 : 0x01);
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element that needs to timeout, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
bool sharedClient::isAlive() {
|
||||
if (!hasCounter) {
|
||||
return true;
|
||||
}
|
||||
return (myPage.mapped[offsetOnPage] & 0x7F) < 126;
|
||||
}
|
||||
|
||||
///\brief Get a pointer to the data of this client
|
||||
char * sharedClient::getData() {
|
||||
if (!myPage.mapped) {
|
||||
|
@ -1104,8 +1182,21 @@ namespace IPC {
|
|||
return (myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0));
|
||||
}
|
||||
|
||||
int sharedClient::getCounter() {
|
||||
if (!hasCounter){
|
||||
return -1;
|
||||
}
|
||||
if (!myPage.mapped) {
|
||||
return 0;
|
||||
}
|
||||
return *(myPage.mapped + offsetOnPage);
|
||||
}
|
||||
|
||||
userConnection::userConnection(char * _data) {
|
||||
data = _data;
|
||||
if (!data){
|
||||
WARN_MSG("userConnection created with null pointer!");
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long userConnection::getTrackId(size_t offset) const {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
#define STAT_EX_SIZE 174
|
||||
#define STAT_EX_SIZE 177
|
||||
#define PLAY_EX_SIZE 2+6*SIMUL_TRACKS
|
||||
|
||||
namespace IPC {
|
||||
|
@ -37,6 +37,8 @@ namespace IPC {
|
|||
void connector(std::string name);
|
||||
std::string connector();
|
||||
void crc(unsigned int sum);
|
||||
char getSync();
|
||||
void setSync(char s);
|
||||
unsigned int crc();
|
||||
private:
|
||||
///\brief The payload for the stat exchange
|
||||
|
@ -49,6 +51,8 @@ namespace IPC {
|
|||
/// - 100 byte - streamName (name of the stream peer is viewing)
|
||||
/// - 20 byte - connector (name of the connector the peer is using)
|
||||
/// - 4 byte - CRC32 of user agent (or zero if none)
|
||||
/// - 1 byte sync (was seen by controller yes/no)
|
||||
/// - (implicit 4 bytes: PID)
|
||||
char * data;
|
||||
};
|
||||
|
||||
|
@ -56,10 +60,10 @@ namespace IPC {
|
|||
class semaphore {
|
||||
public:
|
||||
semaphore();
|
||||
semaphore(const char * name, int oflag, mode_t mode, unsigned int value);
|
||||
semaphore(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0, bool noWait = false);
|
||||
~semaphore();
|
||||
operator bool() const;
|
||||
void open(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0);
|
||||
void open(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0, bool noWait = false);
|
||||
int getVal() const;
|
||||
void post();
|
||||
void wait();
|
||||
|
@ -177,9 +181,11 @@ namespace IPC {
|
|||
void init(std::string name, int len, bool withCounter = false);
|
||||
~sharedServer();
|
||||
void parseEach(void (*callback)(char * data, size_t len, unsigned int id));
|
||||
char * getIndex(unsigned int id);
|
||||
operator bool() const;
|
||||
///\brief The amount of connected clients
|
||||
unsigned int amount;
|
||||
unsigned int connectedUsers;
|
||||
private:
|
||||
bool isInUse(unsigned int id);
|
||||
void newPage();
|
||||
|
@ -194,6 +200,7 @@ namespace IPC {
|
|||
semaphore mySemaphore;
|
||||
///\brief Whether the payload has a counter, if so, it is added in front of the payload
|
||||
bool hasCounter;
|
||||
void finishEach();
|
||||
};
|
||||
|
||||
///\brief The client part of a server/client model for shared memory.
|
||||
|
@ -215,7 +222,10 @@ namespace IPC {
|
|||
void write(char * data, int len);
|
||||
void finish();
|
||||
void keepAlive();
|
||||
bool isAlive();
|
||||
char * getData();
|
||||
int getCounter();
|
||||
bool countAsViewer;
|
||||
private:
|
||||
///\brief The basename of the shared pages.
|
||||
std::string baseName;
|
||||
|
|
|
@ -510,11 +510,8 @@ unsigned int Socket::Connection::iwrite(const void * buffer, int len) {
|
|||
return 0;
|
||||
break;
|
||||
default:
|
||||
if (errno != EPIPE && errno != ECONNRESET) {
|
||||
Error = true;
|
||||
remotehost = strerror(errno);
|
||||
DEBUG_MSG(DLVL_WARN, "Could not iwrite data! Error: %s", remotehost.c_str());
|
||||
}
|
||||
INSANE_MSG("Could not iwrite data! Error: %s", strerror(errno));
|
||||
close();
|
||||
return 0;
|
||||
break;
|
||||
|
@ -555,11 +552,8 @@ int Socket::Connection::iread(void * buffer, int len, int flags) {
|
|||
return 0;
|
||||
break;
|
||||
default:
|
||||
if (errno != EPIPE) {
|
||||
Error = true;
|
||||
remotehost = strerror(errno);
|
||||
DEBUG_MSG(DLVL_WARN, "Could not iread data! Error: %s", remotehost.c_str());
|
||||
}
|
||||
INSANE_MSG("Could not iread data! Error: %s", strerror(errno));
|
||||
close();
|
||||
return 0;
|
||||
break;
|
||||
|
|
|
@ -73,10 +73,37 @@ void Util::sanitizeName(std::string & streamname) {
|
|||
}
|
||||
}
|
||||
|
||||
JSON::Value Util::getStreamConfig(std::string streamname){
|
||||
JSON::Value result;
|
||||
if (streamname.size() > 100){
|
||||
FAIL_MSG("Stream opening denied: %s is longer than 100 characters (%lu).", streamname.c_str(), streamname.size());
|
||||
return result;
|
||||
}
|
||||
IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len);
|
||||
|
||||
sanitizeName(streamname);
|
||||
std::string smp = streamname.substr(0, streamname.find_first_of("+ "));
|
||||
//check if smp (everything before + or space) exists
|
||||
DTSC::Scan stream_cfg = config.getMember("streams").getMember(smp);
|
||||
if (!stream_cfg){
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Stream %s not configured", streamname.c_str());
|
||||
}else{
|
||||
result = stream_cfg.asJSON();
|
||||
}
|
||||
configLock.post();//unlock the config semaphore
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Checks if the given streamname has an active input serving it. Returns true if this is the case.
|
||||
/// Assumes the streamname has already been through sanitizeName()!
|
||||
bool Util::streamAlive(std::string & streamname){
|
||||
IPC::semaphore playerLock(std::string("/lock_" + streamname).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
char semName[NAME_BUFFER_SIZE];
|
||||
snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamname.c_str());
|
||||
IPC::semaphore playerLock(semName, O_RDWR, ACCESSPERMS, 1, true);
|
||||
if (!playerLock){return false;}
|
||||
if (!playerLock.tryWait()) {
|
||||
playerLock.close();
|
||||
return true;
|
||||
|
@ -109,8 +136,8 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
|||
}
|
||||
|
||||
//Attempt to load up configuration and find this stream
|
||||
IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
//Lock the config to prevent race conditions and corruption issues while reading
|
||||
configLock.wait();
|
||||
DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len);
|
||||
|
@ -149,7 +176,21 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
|||
input = inputs.getIndice(i);
|
||||
|
||||
//if match voor current stream && priority is hoger dan wat we al hebben
|
||||
if (curPrio < input.getMember("priority").asInt()){
|
||||
if (input.getMember("source_match") && curPrio < input.getMember("priority").asInt()){
|
||||
if (input.getMember("source_match").getSize()){
|
||||
for(unsigned int j = 0; j < input.getMember("source_match").getSize(); ++j){
|
||||
std::string source = input.getMember("source_match").getIndice(j).asString();
|
||||
std::string front = source.substr(0,source.find('*'));
|
||||
std::string back = source.substr(source.find('*')+1);
|
||||
MEDIUM_MSG("Checking input %s: %s (%s)", inputs.getIndiceName(i).c_str(), input.getMember("name").asString().c_str(), source.c_str());
|
||||
|
||||
if (filename.substr(0,front.size()) == front && filename.substr(filename.size()-back.size()) == back){
|
||||
player_bin = Util::getMyPath() + "MistIn" + input.getMember("name").asString();
|
||||
curPrio = input.getMember("priority").asInt();
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
std::string source = input.getMember("source_match").asString();
|
||||
std::string front = source.substr(0,source.find('*'));
|
||||
std::string back = source.substr(source.find('*')+1);
|
||||
|
@ -160,6 +201,8 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
|||
curPrio = input.getMember("priority").asInt();
|
||||
selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,5 +273,11 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
|
|||
FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), strerror(errno));
|
||||
_exit(42);
|
||||
}
|
||||
return true;
|
||||
|
||||
unsigned int waiting = 0;
|
||||
while (!streamAlive(streamname) && ++waiting < 40){
|
||||
Util::wait(250);
|
||||
}
|
||||
return streamAlive(streamname);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,10 +4,13 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "socket.h"
|
||||
#include "json.h"
|
||||
|
||||
namespace Util {
|
||||
std::string getTmpFolder();
|
||||
void sanitizeName(std::string & streamname);
|
||||
bool streamAlive(std::string & streamname);
|
||||
bool startInput(std::string streamname, std::string filename = "", bool forkFirst = true);
|
||||
JSON::Value getStreamConfig(std::string streamname);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@ namespace TS {
|
|||
clear();
|
||||
}
|
||||
|
||||
Packet::Packet(const Packet & rhs){
|
||||
memcpy(strBuf, rhs.strBuf, 188);
|
||||
pos = 188;
|
||||
}
|
||||
|
||||
/// This function fills a Packet from a file.
|
||||
/// It fills the content with the next 188 bytes int he file.
|
||||
/// \param Data The data to be read into the packet.
|
||||
|
@ -34,11 +39,11 @@ namespace TS {
|
|||
if (!fread((void *)strBuf, 188, 1, data)) {
|
||||
return false;
|
||||
}
|
||||
pos=188;
|
||||
if (strBuf[0] != 0x47){
|
||||
HIGH_MSG("Failed to read a good packet on pos %lld", bPos);
|
||||
return false;
|
||||
}
|
||||
pos=188;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -412,7 +417,7 @@ namespace TS {
|
|||
/// \return A character pointer to the internal packet buffer data
|
||||
const char * Packet::checkAndGetBuffer() const{
|
||||
if (pos != 188) {
|
||||
DEBUG_MSG(DLVL_ERROR, "Size invalid (%d) - invalid data from this point on", pos);
|
||||
DEBUG_MSG(DLVL_HIGH, "Size invalid (%d) - invalid data from this point on", pos);
|
||||
}
|
||||
return strBuf;
|
||||
}
|
||||
|
@ -564,6 +569,11 @@ namespace TS {
|
|||
}
|
||||
|
||||
|
||||
ProgramAssociationTable & ProgramAssociationTable::operator = (const Packet & rhs){
|
||||
memcpy(strBuf, rhs.checkAndGetBuffer(), 188);
|
||||
pos = 188;
|
||||
return *this;
|
||||
}
|
||||
///Retrieves the current addStuffingoffset value for a PAT
|
||||
char ProgramAssociationTable::getOffset() const{
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0);
|
||||
|
@ -680,6 +690,10 @@ namespace TS {
|
|||
return data[0];
|
||||
}
|
||||
|
||||
void ProgramMappingEntry::setStreamType(int newType){
|
||||
data[0] = newType;
|
||||
}
|
||||
|
||||
std::string ProgramMappingEntry::getCodec() const{
|
||||
switch (getStreamType()){
|
||||
case 0x01:
|
||||
|
@ -730,10 +744,25 @@ namespace TS {
|
|||
return ((data[1] << 8) | data[2]) & 0x1FFF;
|
||||
}
|
||||
|
||||
void ProgramMappingEntry::setElementaryPid(int newElementaryPid) {
|
||||
data[1] = newElementaryPid >> 8 & 0x1F;
|
||||
data[2] = newElementaryPid & 0xFF;
|
||||
}
|
||||
|
||||
int ProgramMappingEntry::getESInfoLength() const{
|
||||
return ((data[3] << 8) | data[4]) & 0x0FFF;
|
||||
}
|
||||
|
||||
const char * ProgramMappingEntry::getESInfo() const{
|
||||
return data + 5;
|
||||
}
|
||||
|
||||
void ProgramMappingEntry::setESInfo(const std::string & newInfo){
|
||||
data[3] = (newInfo.size() >> 8) & 0x0F;
|
||||
data[4] = newInfo.size() & 0xFF;
|
||||
memcpy(data + 5, newInfo.data(), newInfo.size());
|
||||
}
|
||||
|
||||
void ProgramMappingEntry::advance(){
|
||||
if (!(*this)) {
|
||||
return;
|
||||
|
@ -749,6 +778,12 @@ namespace TS {
|
|||
pos=4;
|
||||
}
|
||||
|
||||
ProgramMappingTable & ProgramMappingTable::operator = (const Packet & rhs) {
|
||||
memcpy(strBuf, rhs.checkAndGetBuffer(), 188);
|
||||
pos = 188;
|
||||
return *this;
|
||||
}
|
||||
|
||||
char ProgramMappingTable::getOffset() const{
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0);
|
||||
return strBuf[loc];
|
||||
|
@ -868,14 +903,6 @@ namespace TS {
|
|||
strBuf[loc+1] = (char)newVal;
|
||||
}
|
||||
|
||||
short ProgramMappingTable::getProgramCount() const{
|
||||
return (getSectionLength() - 13) / 5;
|
||||
}
|
||||
|
||||
void ProgramMappingTable::setProgramCount(short newVal) {
|
||||
setSectionLength(newVal * 5 + 13);
|
||||
}
|
||||
|
||||
ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{
|
||||
int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset();
|
||||
ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) );
|
||||
|
@ -885,59 +912,6 @@ namespace TS {
|
|||
return res;
|
||||
}
|
||||
|
||||
char ProgramMappingTable::getStreamType(short index) const{
|
||||
if (index > getProgramCount()) {
|
||||
return 0;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
||||
return strBuf[loc + (index * 5)];
|
||||
}
|
||||
|
||||
void ProgramMappingTable::setStreamType(char newVal, short index) {
|
||||
if (index > getProgramCount()) {
|
||||
return;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); //TODO
|
||||
updPos(loc+(index*5)+1);
|
||||
strBuf[loc + (index * 5)] = newVal;
|
||||
}
|
||||
|
||||
short ProgramMappingTable::getElementaryPID(short index) const{
|
||||
if (index > getProgramCount()) {
|
||||
return 0;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
||||
return (((short)strBuf[loc + (index * 5) + 1] & 0x1F) << 8) | strBuf[loc + (index * 5) + 2];
|
||||
}
|
||||
|
||||
void ProgramMappingTable::setElementaryPID(short newVal, short index) {
|
||||
if (index > getProgramCount()) {
|
||||
return;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
||||
updPos(loc+(index*5)+3);
|
||||
strBuf[loc + (index * 5)+1] = ((newVal >> 8) & 0x1F )| 0xE0;
|
||||
strBuf[loc + (index * 5)+2] = (char)newVal;
|
||||
}
|
||||
|
||||
short ProgramMappingTable::getESInfoLength(short index) const{
|
||||
if (index > getProgramCount()) {
|
||||
return 0;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
||||
return (((short)strBuf[loc + (index * 5) + 3] & 0x0F) << 8) | strBuf[loc + (index * 5) + 4];
|
||||
}
|
||||
|
||||
void ProgramMappingTable::setESInfoLength(short newVal, short index) {
|
||||
if (index > getProgramCount()) {
|
||||
return;
|
||||
}
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength();
|
||||
updPos(loc+(index*5)+5);
|
||||
strBuf[loc + (index * 5)+3] = ((newVal >> 8) & 0x0F) | 0xF0;
|
||||
strBuf[loc + (index * 5)+4] = (char)newVal;
|
||||
}
|
||||
|
||||
int ProgramMappingTable::getCRC() const{
|
||||
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength();
|
||||
return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3];
|
||||
|
@ -995,7 +969,14 @@ namespace TS {
|
|||
PMT.setPID(4096);
|
||||
PMT.setTableId(2);
|
||||
//section length met 2 tracks: 0xB017
|
||||
PMT.setSectionLength(0xB00D + (selectedTracks.size() * 5));
|
||||
int sectionLen = 0;
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
sectionLen += 5;
|
||||
if (myMeta.tracks[*it].codec == "ID3"){
|
||||
sectionLen += myMeta.tracks[*it].init.size();
|
||||
}
|
||||
}
|
||||
PMT.setSectionLength(0xB00D + sectionLen);
|
||||
PMT.setProgramNumber(1);
|
||||
PMT.setVersionNumber(0);
|
||||
PMT.setCurrentNextIndicator(0);
|
||||
|
@ -1012,20 +993,20 @@ namespace TS {
|
|||
if (vidTrack == -1){
|
||||
vidTrack = *(selectedTracks.begin());
|
||||
}
|
||||
PMT.setPCRPID(0x100 + vidTrack - 1);
|
||||
PMT.setPCRPID(vidTrack);
|
||||
PMT.setProgramInfoLength(0);
|
||||
short id = 0;
|
||||
ProgramMappingEntry entry = PMT.getEntry(0);
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
entry.setElementaryPid(*it);
|
||||
if (myMeta.tracks[*it].codec == "H264"){
|
||||
PMT.setStreamType(0x1B,id);
|
||||
entry.setStreamType(0x1B);
|
||||
}else if (myMeta.tracks[*it].codec == "AAC"){
|
||||
PMT.setStreamType(0x0F,id);
|
||||
entry.setStreamType(0x0F);
|
||||
}else if (myMeta.tracks[*it].codec == "MP3"){
|
||||
PMT.setStreamType(0x03,id);
|
||||
entry.setStreamType(0x03);
|
||||
}
|
||||
PMT.setElementaryPID(0x100 + (*it) - 1, id);
|
||||
PMT.setESInfoLength(0,id);
|
||||
id++;
|
||||
entry.advance();
|
||||
}
|
||||
PMT.calcCRC();
|
||||
return PMT.checkAndGetBuffer();
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace TS {
|
|||
public:
|
||||
//Constructors and fillers
|
||||
Packet();
|
||||
Packet(const Packet & rhs);
|
||||
~Packet();
|
||||
bool FromPointer(const char * data);
|
||||
bool FromFile(FILE * data);
|
||||
|
@ -83,6 +84,7 @@ namespace TS {
|
|||
|
||||
class ProgramAssociationTable : public Packet {
|
||||
public:
|
||||
ProgramAssociationTable & operator = (const Packet & rhs);
|
||||
char getOffset() const;
|
||||
char getTableId() const;
|
||||
short getSectionLength() const;
|
||||
|
@ -105,11 +107,14 @@ namespace TS {
|
|||
operator bool() const;
|
||||
|
||||
int getStreamType() const;
|
||||
void setStreamType(int newType);
|
||||
std::string getCodec() const;
|
||||
std::string getStreamTypeString() const;
|
||||
int getElementaryPid() const;
|
||||
void setElementaryPid(int newElementaryPid);
|
||||
int getESInfoLength() const;
|
||||
char * getESInfo() const;
|
||||
const char * getESInfo() const;
|
||||
void setESInfo(const std::string & newInfo);
|
||||
void advance();
|
||||
private:
|
||||
char* data;
|
||||
|
@ -119,6 +124,7 @@ namespace TS {
|
|||
class ProgramMappingTable : public Packet {
|
||||
public:
|
||||
ProgramMappingTable();
|
||||
ProgramMappingTable & operator = (const Packet & rhs);
|
||||
char getOffset() const;
|
||||
void setOffset(char newVal);
|
||||
char getTableId() const;
|
||||
|
@ -139,15 +145,7 @@ namespace TS {
|
|||
void setPCRPID(short newVal);
|
||||
short getProgramInfoLength() const;
|
||||
void setProgramInfoLength(short newVal);
|
||||
short getProgramCount() const;
|
||||
void setProgramCount(short newVal);
|
||||
ProgramMappingEntry getEntry(int index) const;
|
||||
void setStreamType(char newVal, short index);
|
||||
char getStreamType(short index) const;
|
||||
void setElementaryPID(short newVal, short index);
|
||||
short getElementaryPID(short index) const;
|
||||
void setESInfoLength(short newVal,short index);
|
||||
short getESInfoLength(short index) const;
|
||||
int getCRC() const;
|
||||
void calcCRC();
|
||||
std::string toPrettyString(size_t indent) const;
|
||||
|
|
40
lib/util.cpp
Normal file
40
lib/util.cpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#include "util.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace Util {
|
||||
bool stringScan(const std::string & src, const std::string & pattern, std::deque<std::string> & result){
|
||||
result.clear();
|
||||
std::deque<size_t> positions;
|
||||
size_t pos = pattern.find("%", 0);
|
||||
while (pos != std::string::npos){
|
||||
positions.push_back(pos);
|
||||
pos = pattern.find("%", pos + 1);
|
||||
}
|
||||
if (positions.size() == 0){
|
||||
return false;
|
||||
}
|
||||
size_t sourcePos = 0;
|
||||
size_t patternPos = 0;
|
||||
std::deque<size_t>::iterator posIter = positions.begin();
|
||||
while (sourcePos != std::string::npos){
|
||||
//Match first part of the string
|
||||
if (pattern.substr(patternPos, *posIter - patternPos) != src.substr(sourcePos, *posIter - patternPos)){
|
||||
break;
|
||||
}
|
||||
sourcePos += *posIter - patternPos;
|
||||
std::deque<size_t>::iterator nxtIter = posIter + 1;
|
||||
if (nxtIter != positions.end()){
|
||||
patternPos = *posIter+2;
|
||||
size_t tmpPos = src.find(pattern.substr(*posIter+2, *nxtIter - patternPos), sourcePos);
|
||||
result.push_back(src.substr(sourcePos, tmpPos - sourcePos));
|
||||
sourcePos = tmpPos;
|
||||
}else{
|
||||
result.push_back(src.substr(sourcePos));
|
||||
sourcePos = std::string::npos;
|
||||
}
|
||||
posIter++;
|
||||
}
|
||||
return result.size() == positions.size();
|
||||
}
|
||||
}
|
||||
|
6
lib/util.h
Normal file
6
lib/util.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#include <string>
|
||||
#include <deque>
|
||||
|
||||
namespace Util {
|
||||
bool stringScan(const std::string & src, const std::string & pattern, std::deque<std::string> & result);
|
||||
}
|
|
@ -1924,7 +1924,7 @@ var UI = {
|
|||
help: 'You can set the amount of debug information MistServer saves in the log. A full reboot of MistServer is required before some components of MistServer can post debug information.'
|
||||
},{
|
||||
type: 'checkbox',
|
||||
label: 'Force JSON file save',
|
||||
label: 'Force configurations save',
|
||||
pointer: {
|
||||
main: mist.data,
|
||||
index: 'save'
|
||||
|
@ -3464,7 +3464,7 @@ var UI = {
|
|||
},{
|
||||
label: 'Target',
|
||||
type: 'str',
|
||||
help: 'Where the stream will be pushed to.<br>Valid formats:<ul><li>'+target_match.join('</li><li>')+'</li></ul>',
|
||||
help: 'Where the stream will be pushed to.<br>Valid formats:<ul><li>'+target_match.join('</li><li>')+'</li></ul> Valid text replacements:<ul><li>$stream - inserts the stream name used to push to MistServer</li><li>$day - inserts the current day number</li><li>$month - inserts the current month number</li><li>$year - inserts the current year number</li><li>$hour - inserts the hour timestamp when stream was received</li><li>$minute - inserts the minute timestamp the stream was received</li><li>$seconds - inserts the seconds timestamp when the stream was received</li><li>$datetime - inserts $year.$month.$day.$hour.$minute.$seconds timestamp when the stream was received</li>',
|
||||
pointer: {
|
||||
main: saveas,
|
||||
index: 'target'
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
macro(makeAnalyser analyserName format)
|
||||
add_executable( MistAnalyser${analyserName} analysers/${format}_analyser.cpp )
|
||||
target_link_libraries( MistAnalyser${analyserName} mist )
|
||||
endmacro()
|
||||
|
||||
macro(makeInput inputName format)
|
||||
add_executable( MistIn${inputName} input/mist_in.cpp input/input.cpp input/input_${format}.cpp )
|
||||
set_target_properties( MistIn${inputName} PROPERTIES COMPILE_DEFINITIONS INPUTTYPE=\"input_${format}.h\")
|
||||
target_link_libraries( MistIn${inputName} mist )
|
||||
endmacro()
|
||||
|
||||
macro(makeOutput outputName format)
|
||||
#check if 'http' is one of the argyments, if yes, this is an http output
|
||||
if (";${ARGN};" MATCHES ";http;")
|
||||
SET(httpOutput output/output_http.cpp)
|
||||
if (";${ARGN};" MATCHES ";ts;")
|
||||
SET(tsBaseClass HTTPOutput)
|
||||
else()
|
||||
SET(tsBaseClass Output)
|
||||
endif()
|
||||
endif()
|
||||
if (";${ARGN};" MATCHES ";ts;")
|
||||
SET(tsOutput output/output_ts_base.cpp)
|
||||
endif()
|
||||
add_executable( MistOut${outputName} output/mist_out.cpp output/output.cpp ${httpOutput} ${tsOutput} output/output_${format}.cpp )
|
||||
set_target_properties( MistOut${outputName} PROPERTIES COMPILE_DEFINITIONS "OUTPUTTYPE=\"output_${format}.h\";TS_BASECLASS=${tsBaseClass}")
|
||||
target_link_libraries( MistOut${outputName} mist )
|
||||
endmacro()
|
||||
|
||||
makeAnalyser(RTMP rtmp)
|
||||
makeAnalyser(FLV flv)
|
||||
makeAnalyser(DTSC dtsc)
|
||||
makeAnalyser(AMF amf)
|
||||
makeAnalyser(MP4 mp4)
|
||||
makeAnalyser(OGG ogg)
|
||||
|
||||
makeInput(DTSC dtsc)
|
||||
makeInput(MP3 mp3)
|
||||
makeInput(FLV flv)
|
||||
makeInput(OGG ogg)
|
||||
makeInput(Buffer buffer)
|
||||
|
||||
makeOutput(RTMP rtmp)
|
||||
makeOutput(OGG progressive_ogg http)
|
||||
makeOutput(FLV progressive_flv http)
|
||||
makeOutput(MP4 progressive_mp4 http)
|
||||
makeOutput(MP3 progressive_mp3 http)
|
||||
makeOutput(HSS hss http)
|
||||
makeOutput(HDS hds http)
|
||||
makeOutput(SRT srt http)
|
||||
makeOutput(JSON json http)
|
||||
makeOutput(TS ts ts)
|
||||
makeOutput(HTTPTS httpts http ts)
|
||||
makeOutput(HLS hls http ts)
|
||||
|
||||
#get the bitlength of this system
|
||||
execute_process(COMMAND getconf LONG_BIT OUTPUT_VARIABLE RELEASE_RAW )
|
||||
#strip off the trailing spaces and newline
|
||||
string(STRIP ${RELEASE_RAW} RELEASE)
|
||||
set(RELEASE \"${RELEASE}\" )
|
||||
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_executable( sourcery sourcery.cpp )
|
||||
|
||||
add_custom_target( embedcode
|
||||
ALL
|
||||
./sourcery ${CMAKE_CURRENT_SOURCE_DIR}/embed.js embed_js ${CMAKE_CURRENT_BINARY_DIR}/embed.js.h
|
||||
DEPENDS sourcery ${CMAKE_CURRENT_SOURCE_DIR}/embed.js
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target( localSettingsPage
|
||||
ALL
|
||||
./sourcery ${BINARY_DIR}/lsp/server.html server_html ${CMAKE_CURRENT_BINARY_DIR}/server.html.h
|
||||
DEPENDS sourcery lsp
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_executable( MistOutHTTP output/mist_out.cpp output/output.cpp output/output_http.cpp output/output_http_internal.cpp)
|
||||
set_target_properties( MistOutHTTP PROPERTIES COMPILE_DEFINITIONS "OUTPUTTYPE=\"output_http_internal.h\"")
|
||||
add_dependencies(MistOutHTTP embedcode)
|
||||
target_link_libraries( MistOutHTTP mist )
|
||||
|
||||
add_executable( MistController
|
||||
controller/controller.cpp
|
||||
controller/controller_api.h
|
||||
controller/controller_api.cpp
|
||||
controller/controller_capabilities.h
|
||||
controller/controller_capabilities.cpp
|
||||
controller/controller_connectors.h
|
||||
controller/controller_connectors.cpp
|
||||
controller/controller_statistics.h
|
||||
controller/controller_statistics.cpp
|
||||
controller/controller_storage.h
|
||||
controller/controller_storage.cpp
|
||||
controller/controller_streams.h
|
||||
controller/controller_streams.cpp
|
||||
)
|
||||
set_target_properties( MistController PROPERTIES COMPILE_DEFINITIONS RELEASE=${RELEASE})
|
||||
target_link_libraries( MistController mist )
|
||||
add_dependencies(MistController localSettingsPage)
|
|
@ -23,7 +23,10 @@ namespace Analysers {
|
|||
std::cerr << "Not a valid DTSC file" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
||||
|
||||
if (F.getMeta().vod || F.getMeta().live){
|
||||
F.getMeta().toPrettyString(std::cout,0, 0x03);
|
||||
}
|
||||
|
||||
int bPos = 0;
|
||||
F.seek_bpos(0);
|
||||
|
@ -42,6 +45,10 @@ namespace Analysers {
|
|||
std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
case DTSC::DTCM: {
|
||||
std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos);
|
||||
break;
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <string.h>
|
||||
#include <mist/mp4.h>
|
||||
#include <mist/config.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
///\brief Holds everything unique to the analysers.
|
||||
namespace Analysers {
|
||||
|
@ -25,9 +26,15 @@ namespace Analysers {
|
|||
mp4Buffer.erase(mp4Buffer.size() - 1, 1);
|
||||
|
||||
MP4::Box mp4Data;
|
||||
int dataSize = mp4Buffer.size();
|
||||
int curPos = 0;
|
||||
while (mp4Data.read(mp4Buffer)){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
|
||||
std::cerr << mp4Data.toPrettyString(0) << std::endl;
|
||||
curPos += dataSize - mp4Buffer.size();
|
||||
dataSize = mp4Buffer.size();
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// \page api API calls
|
||||
/// \brief Listing of all controller API calls.
|
||||
/// The controller listens for commands through a JSON-based API. This page describes the API in full.
|
||||
///
|
||||
/// A default interface implementing this API as a single HTML page is included in the controller itself. This default interface will be send for invalid API requests, and is thus triggered by default when a browser attempts to access the API port directly.
|
||||
|
@ -20,7 +21,9 @@
|
|||
///
|
||||
/// You may also include a `"callback"` or `"jsonp"` HTTP variable, to trigger JSONP compatibility mode. JSONP is useful for getting around the cross-domain scripting protection in most modern browsers. Developers creating non-JavaScript applications will most likely not want to use JSONP mode, though nothing is stopping you if you really want to.
|
||||
///
|
||||
/// \brief Listing of all controller API calls.
|
||||
|
||||
|
||||
|
||||
|
||||
/// \file controller.cpp
|
||||
/// Contains all code for the controller executable.
|
||||
|
@ -88,6 +91,7 @@ void createAccount (std::string account){
|
|||
/// Status monitoring thread.
|
||||
/// Will check outputs, inputs and converters every five seconds
|
||||
void statusMonitor(void * np){
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
while (Controller::conf.is_active){
|
||||
//this scope prevents the configMutex from being locked constantly
|
||||
{
|
||||
|
@ -99,19 +103,20 @@ void statusMonitor(void * np){
|
|||
changed |= Controller::CheckAllStreams(Controller::Storage["streams"]);
|
||||
|
||||
//check if the config semaphore is stuck, by trying to lock it for 5 attempts of 1 second...
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond()){
|
||||
//that failed. We now unlock it, no matter what - and print a warning that it was stuck.
|
||||
WARN_MSG("Configuration semaphore was stuck. Force-unlocking it and re-writing config.");
|
||||
changed = true;
|
||||
}
|
||||
configLock.post();
|
||||
if (changed){
|
||||
if (changed || Controller::configChanged){
|
||||
Controller::writeConfig();
|
||||
Controller::configChanged = false;
|
||||
}
|
||||
}
|
||||
Util::wait(5000);//wait at least 5 seconds
|
||||
}
|
||||
configLock.unlink();
|
||||
}
|
||||
|
||||
///\brief The main entry point for the controller.
|
||||
|
@ -134,10 +139,9 @@ int main(int argc, char ** argv){
|
|||
stored_user["default"] = "root";
|
||||
}
|
||||
Controller::conf = Util::Config(argv[0]);
|
||||
Controller::conf.addOption("listen_port", stored_port);
|
||||
Controller::conf.addOption("listen_interface", stored_interface);
|
||||
Controller::conf.addOption("port", stored_port);
|
||||
Controller::conf.addOption("interface", stored_interface);
|
||||
Controller::conf.addOption("username", stored_user);
|
||||
Controller::conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":0, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Turns deamon mode on (-d) or off (-n). -d runs quietly in background, -n (default) enables verbose in foreground.\"}"));
|
||||
Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}"));
|
||||
Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}"));
|
||||
Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}"));
|
||||
|
@ -183,14 +187,19 @@ int main(int argc, char ** argv){
|
|||
//check for port, interface and username in arguments
|
||||
//if they are not there, take them from config file, if there
|
||||
if (Controller::Storage["config"]["controller"]["port"]){
|
||||
Controller::conf.getOption("listen_port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
|
||||
Controller::conf.getOption("port", true)[0u] = Controller::Storage["config"]["controller"]["port"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["interface"]){
|
||||
Controller::conf.getOption("listen_interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"];
|
||||
}
|
||||
if (Controller::Storage["config"]["controller"]["username"]){
|
||||
Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"];
|
||||
}
|
||||
{
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.unlink();
|
||||
}
|
||||
Controller::writeConfig();
|
||||
Controller::checkAvailProtocols();
|
||||
createAccount(Controller::conf.getString("account"));
|
||||
|
||||
|
@ -244,16 +253,16 @@ int main(int argc, char ** argv){
|
|||
}else{//logfile is enabled
|
||||
//check for username
|
||||
if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){
|
||||
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("listen_port") << " and follow the instructions." << std::endl;
|
||||
std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("port") << " and follow the instructions." << std::endl;
|
||||
}
|
||||
//check for protocols
|
||||
if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){
|
||||
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
|
||||
std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
|
||||
}
|
||||
}
|
||||
//check for streams - regardless of logfile setting
|
||||
if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){
|
||||
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl;
|
||||
std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl;
|
||||
}
|
||||
}//connected to a terminal
|
||||
|
||||
|
@ -274,8 +283,8 @@ int main(int argc, char ** argv){
|
|||
}else{
|
||||
shutdown_reason = "socket problem (API port closed)";
|
||||
}
|
||||
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
|
||||
Controller::conf.is_active = false;
|
||||
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
|
||||
//join all joinable threads
|
||||
statsThread.join();
|
||||
monitorThread.join();
|
||||
|
@ -300,3 +309,4 @@ int main(int argc, char ** argv){
|
|||
std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -58,9 +58,6 @@
|
|||
/// ~~~~~~~~~~~~~~~
|
||||
void Controller::checkConfig(JSON::Value & in, JSON::Value & out){
|
||||
out = in;
|
||||
if (out["basepath"].asString()[out["basepath"].asString().size() - 1] == '/'){
|
||||
out["basepath"] = out["basepath"].asString().substr(0, out["basepath"].asString().size() - 1);
|
||||
}
|
||||
if (out.isMember("debug")){
|
||||
if (Util::Config::printDebugLevel != out["debug"].asInt()){
|
||||
Util::Config::printDebugLevel = out["debug"].asInt();
|
||||
|
@ -169,6 +166,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
H.SetHeader("X-Info", "To force an API response, request the file /api");
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
H.SetHeader("Content-Length", server_html_len);
|
||||
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
|
||||
H.SendResponse("200", "OK", conn);
|
||||
conn.SendNow(server_html, server_html_len);
|
||||
H.Clean();
|
||||
|
@ -247,6 +245,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
|
|||
/// ]
|
||||
/// ]
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
///
|
||||
if(Request.isMember("browse")){
|
||||
if(Request["browse"] == ""){
|
||||
Request["browse"] = ".";
|
||||
|
|
|
@ -278,7 +278,11 @@ namespace Controller {
|
|||
unsigned long long c_user, c_nice, c_syst, c_idle, c_total;
|
||||
if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){
|
||||
c_total = c_user + c_nice + c_syst + c_idle;
|
||||
if (c_total - cl_total > 0){
|
||||
capa["cpu_use"] = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total));
|
||||
}else{
|
||||
capa["cpu_use"] = 0ll;
|
||||
}
|
||||
cl_total = c_total;
|
||||
cl_idle = c_idle;
|
||||
break;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <list>
|
||||
#include <mist/config.h>
|
||||
#include "controller_statistics.h"
|
||||
#include "controller_storage.h"
|
||||
|
||||
// These are used to store "clients" field requests in a bitfield for speedup.
|
||||
#define STAT_CLI_HOST 1
|
||||
|
@ -21,6 +22,8 @@
|
|||
#define STAT_TOT_BPS_UP 4
|
||||
#define STAT_TOT_ALL 0xFF
|
||||
|
||||
#define COUNTABLE_BYTES 128*1024
|
||||
|
||||
|
||||
std::map<Controller::sessIndex, Controller::statSession> Controller::sessions; ///< list of sessions that have statistics data available
|
||||
std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
|
||||
|
@ -37,6 +40,12 @@ Controller::sessIndex::sessIndex(){
|
|||
crc = 0;
|
||||
}
|
||||
|
||||
std::string Controller::sessIndex::toStr(){
|
||||
std::stringstream s;
|
||||
s << host << " " << crc << " " << streamName << " " << connector;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
/// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into strings.
|
||||
/// This extracts the host, stream name, connector and crc field, ignoring everything else.
|
||||
Controller::sessIndex::sessIndex(IPC::statExchange & data){
|
||||
|
@ -80,6 +89,9 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
|
|||
return !(*this < b);
|
||||
}
|
||||
|
||||
/// \todo Make this prettier.
|
||||
IPC::sharedServer * statPointer = 0;
|
||||
|
||||
|
||||
/// This function runs as a thread and roughly once per second retrieves
|
||||
/// statistics from all connected clients, as well as wipes
|
||||
|
@ -87,9 +99,12 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
|
|||
void Controller::SharedMemStats(void * config){
|
||||
DEBUG_MSG(DLVL_HIGH, "Starting stats thread");
|
||||
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
statPointer = &statServer;
|
||||
std::set<std::string> inactiveStreams;
|
||||
while(((Util::Config*)config)->is_active){
|
||||
{
|
||||
tthread::lock_guard<tthread::mutex> guard(statsMutex);
|
||||
tthread::lock_guard<tthread::mutex> guard(Controller::configMutex);
|
||||
tthread::lock_guard<tthread::mutex> guard2(statsMutex);
|
||||
//parse current users
|
||||
statServer.parseEach(parseStatistics);
|
||||
//wipe old statistics
|
||||
|
@ -108,8 +123,9 @@ void Controller::SharedMemStats(void * config){
|
|||
}
|
||||
}
|
||||
}
|
||||
Util::sleep(1000);
|
||||
Util::wait(1000);
|
||||
}
|
||||
statPointer = 0;
|
||||
DEBUG_MSG(DLVL_HIGH, "Stopping stats thread");
|
||||
}
|
||||
|
||||
|
@ -147,6 +163,18 @@ void Controller::statSession::wipeOld(unsigned long long cutOff){
|
|||
oldConns.pop_front();
|
||||
}
|
||||
}
|
||||
if (curConns.size()){
|
||||
for (std::map<unsigned long, statStorage>::iterator it = curConns.begin(); it != curConns.end(); ++it){
|
||||
while (it->second.log.size() > 1 && it->second.log.begin()->first < cutOff){
|
||||
it->second.log.erase(it->second.log.begin());
|
||||
}
|
||||
if (it->second.log.size()){
|
||||
if (firstSec > it->second.log.begin()->first){
|
||||
firstSec = it->second.log.begin()->first;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Archives the given connection.
|
||||
|
@ -361,6 +389,11 @@ long long Controller::statSession::getBpsUp(unsigned long long t){
|
|||
}
|
||||
}
|
||||
|
||||
Controller::statStorage::statStorage(){
|
||||
removeDown = 0;
|
||||
removeUp = 0;
|
||||
}
|
||||
|
||||
/// Returns true if there is data available for timestamp t.
|
||||
bool Controller::statStorage::hasDataFor(unsigned long long t) {
|
||||
if (!log.size()){return false;}
|
||||
|
@ -390,8 +423,13 @@ void Controller::statStorage::update(IPC::statExchange & data) {
|
|||
statLog tmp;
|
||||
tmp.time = data.time();
|
||||
tmp.lastSecond = data.lastSecond();
|
||||
tmp.down = data.down();
|
||||
tmp.up = data.up();
|
||||
tmp.down = data.down() - removeDown;
|
||||
tmp.up = data.up() - removeUp;
|
||||
if (!log.size() && tmp.down + tmp.up > COUNTABLE_BYTES){
|
||||
//substract the start values if they are too high - this is a resumed connection of some sort
|
||||
removeDown = tmp.down;
|
||||
removeUp = tmp.up;
|
||||
}
|
||||
log[data.now()] = tmp;
|
||||
//wipe data older than approx. STAT_CUTOFF seconds
|
||||
/// \todo Remove least interesting data first.
|
||||
|
@ -420,7 +458,7 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){
|
|||
sessions[idx].update(id, tmpEx);
|
||||
//check validity of stats data
|
||||
char counter = (*(data - 1));
|
||||
if (counter == 126 || counter == 127 || counter == 254 || counter == 255){
|
||||
if (counter == 126 || counter == 127){
|
||||
//the data is no longer valid - connection has gone away, store for later
|
||||
sessions[idx].finish(id);
|
||||
connToSession.erase(id);
|
||||
|
@ -432,7 +470,7 @@ bool Controller::hasViewers(std::string streamName){
|
|||
if (sessions.size()){
|
||||
long long currTime = Util::epoch();
|
||||
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){
|
||||
if (it->first.streamName == streamName && it->second.hasDataFor(currTime)){
|
||||
if (it->first.streamName == streamName && (it->second.hasDataFor(currTime) || it->second.hasDataFor(currTime-1))){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#pragma once
|
||||
#include <mist/shared_memory.h>
|
||||
#include <mist/timing.h>
|
||||
#include <mist/defines.h>
|
||||
|
@ -36,11 +37,16 @@ namespace Controller {
|
|||
bool operator<= (const sessIndex &o) const;
|
||||
bool operator< (const sessIndex &o) const;
|
||||
bool operator>= (const sessIndex &o) const;
|
||||
std::string toStr();
|
||||
};
|
||||
|
||||
|
||||
class statStorage {
|
||||
private:
|
||||
long long removeUp;
|
||||
long long removeDown;
|
||||
public:
|
||||
statStorage();
|
||||
void update(IPC::statExchange & data);
|
||||
bool hasDataFor(unsigned long long);
|
||||
statLog & getDataFor(unsigned long long);
|
||||
|
|
|
@ -15,6 +15,8 @@ namespace Controller {
|
|||
JSON::Value Storage; ///< Global storage of data.
|
||||
tthread::mutex configMutex;
|
||||
tthread::mutex logMutex;
|
||||
bool configChanged = false;
|
||||
|
||||
///\brief Store and print a log message.
|
||||
///\param kind The type of message.
|
||||
///\param message The message to be logged.
|
||||
|
@ -70,6 +72,7 @@ namespace Controller {
|
|||
printf("%s", buf);
|
||||
}
|
||||
}
|
||||
Log("LOG", "Logger exiting");
|
||||
fclose(output);
|
||||
close((long long int)err);
|
||||
}
|
||||
|
@ -95,8 +98,8 @@ namespace Controller {
|
|||
}
|
||||
if (!changed){return;}//cancel further processing if no changes
|
||||
|
||||
static IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE, true);
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
//lock semaphore
|
||||
configLock.wait();
|
||||
//write config
|
||||
|
|
|
@ -8,6 +8,7 @@ namespace Controller {
|
|||
extern JSON::Value Storage; ///< Global storage of data.
|
||||
extern tthread::mutex logMutex;///< Mutex for log thread.
|
||||
extern tthread::mutex configMutex;///< Mutex for server config access.
|
||||
extern bool configChanged; ///< Bool that indicates config must be written to SHM.
|
||||
|
||||
/// Store and print a log message.
|
||||
void Log(std::string kind, std::string message);
|
||||
|
|
|
@ -207,7 +207,7 @@ namespace Controller {
|
|||
//actually delete the streams
|
||||
while (toDelete.size() > 0){
|
||||
std::string deleting = *(toDelete.begin());
|
||||
out.removeMember(deleting);
|
||||
deleteStream(deleting, out);
|
||||
toDelete.erase(deleting);
|
||||
}
|
||||
|
||||
|
@ -226,4 +226,13 @@ namespace Controller {
|
|||
|
||||
}
|
||||
|
||||
void deleteStream(const std::string & name, JSON::Value & out) {
|
||||
if (!out.isMember(name)){
|
||||
return;
|
||||
}
|
||||
Log("STRM", std::string("Deleted stream ") + name);
|
||||
out.removeMember(name);
|
||||
}
|
||||
|
||||
} //Controller namespace
|
||||
|
||||
|
|
|
@ -6,9 +6,11 @@ namespace Controller {
|
|||
bool CheckAllStreams(JSON::Value & data);
|
||||
void CheckStreams(JSON::Value & in, JSON::Value & out);
|
||||
void AddStreams(JSON::Value & in, JSON::Value & out);
|
||||
void deleteStream(const std::string & name, JSON::Value & out);
|
||||
|
||||
struct liveCheck {
|
||||
long long int lastms;
|
||||
long long int last_active;
|
||||
};
|
||||
|
||||
} //Controller namespace
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
#include "input.h"
|
||||
#include <sstream>
|
||||
|
@ -69,7 +70,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void Input::checkHeaderTimes(std::string streamFile){
|
||||
if ( streamFile == "-" ){
|
||||
if (streamFile == "-" || streamFile == "push://") {
|
||||
return;
|
||||
}
|
||||
std::string headerFile = streamFile + ".dtsh";
|
||||
|
@ -99,15 +100,22 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Input::run() {
|
||||
streamName = config->getString("streamname");
|
||||
if (config->getBool("json")) {
|
||||
std::cout << capa.toString() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (streamName != "") {
|
||||
config->getOption("streamname") = streamName;
|
||||
}
|
||||
streamName = config->getString("streamname");
|
||||
nProxy.streamName = streamName;
|
||||
|
||||
if (!setup()){
|
||||
std::cerr << config->getString("cmd") << " setup failed." << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
checkHeaderTimes(config->getString("input"));
|
||||
if (!readHeader()){
|
||||
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
|
||||
|
@ -115,7 +123,7 @@ namespace Mist {
|
|||
}
|
||||
parseHeader();
|
||||
|
||||
if (!config->getString("streamname").size()){
|
||||
if (!streamName.size()) {
|
||||
convert();
|
||||
}else{
|
||||
serve();
|
||||
|
@ -155,28 +163,32 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void Input::serve(){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userPage.init(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!isBuffer){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
bufferFrame(it->first, 1);
|
||||
}
|
||||
}
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userPage.init(userPageName, PLAY_EX_SIZE, true);
|
||||
|
||||
DEBUG_MSG(DLVL_DEVEL,"Input for stream %s started", streamName.c_str());
|
||||
|
||||
long long int activityCounter = Util::bootSecs();
|
||||
while ((Util::bootSecs() - activityCounter) < 10 && config->is_active){//10 second timeout
|
||||
Util::wait(1000);
|
||||
removeUnused();
|
||||
while ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT && config->is_active) { //15 second timeout
|
||||
userPage.parseEach(callbackWrapper);
|
||||
if (userPage.amount){
|
||||
removeUnused();
|
||||
if (userPage.connectedUsers) {
|
||||
if (myMeta.tracks.size()){
|
||||
activityCounter = Util::bootSecs();
|
||||
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.amount);
|
||||
}
|
||||
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.connectedUsers);
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_INSANE, "Timer running");
|
||||
}
|
||||
if (config->is_active){
|
||||
Util::wait(1000);
|
||||
}
|
||||
}
|
||||
finish();
|
||||
DEBUG_MSG(DLVL_DEVEL,"Input for stream %s closing clean", streamName.c_str());
|
||||
|
@ -191,7 +203,7 @@ namespace Mist {
|
|||
}
|
||||
removeUnused();
|
||||
if (standAlone){
|
||||
for (std::map<unsigned long, IPC::sharedPage>::iterator it = metaPages.begin(); it != metaPages.end(); it++){
|
||||
for (std::map<unsigned long, IPC::sharedPage>::iterator it = nProxy.metaPages.begin(); it != nProxy.metaPages.end(); it++) {
|
||||
it->second.master = true;
|
||||
}
|
||||
}
|
||||
|
@ -210,9 +222,9 @@ namespace Mist {
|
|||
bufferRemove(it->first, it2->first);
|
||||
pageCounter[it->first].erase(it2->first);
|
||||
for (int i = 0; i < 8192; i += 8){
|
||||
unsigned int thisKeyNum = ntohl(((((long long int *)(metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF);
|
||||
unsigned int thisKeyNum = ntohl(((((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF);
|
||||
if (thisKeyNum == it2->first){
|
||||
(((long long int *)(metaPages[it->first].mapped + i))[0]) = 0;
|
||||
(((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) = 0;
|
||||
}
|
||||
}
|
||||
change = true;
|
||||
|
@ -253,13 +265,13 @@ namespace Mist {
|
|||
for (int i = 0; i < it->second.keys.size(); i++){
|
||||
if (newData){
|
||||
//i+1 because keys are 1-indexed
|
||||
pagesByTrack[it->first][i+1].firstTime = it->second.keys[i].getTime();
|
||||
nProxy.pagesByTrack[it->first][i + 1].firstTime = it->second.keys[i].getTime();
|
||||
newData = false;
|
||||
}
|
||||
pagesByTrack[it->first].rbegin()->second.keyNum++;
|
||||
pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
||||
pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
||||
if (pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE){
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.keyNum++;
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
|
||||
nProxy.pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
|
||||
if (nProxy.pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) {
|
||||
newData = true;
|
||||
}
|
||||
}
|
||||
|
@ -292,7 +304,7 @@ namespace Mist {
|
|||
}
|
||||
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){
|
||||
if (curData[tid].dataSize > FLIP_DATA_PAGE_SIZE) {
|
||||
pagesByTrack[tid][bookKeeping[tid].first] = curData[tid];
|
||||
nProxy.pagesByTrack[tid][bookKeeping[tid].first] = curData[tid];
|
||||
bookKeeping[tid].first += curData[tid].keyNum;
|
||||
curData[tid].keyNum = 0;
|
||||
curData[tid].dataSize = 0;
|
||||
|
@ -309,17 +321,17 @@ namespace Mist {
|
|||
getNext(false);
|
||||
}
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (curData.count(it->first) && !pagesByTrack[it->first].count(bookKeeping[it->first].first)){
|
||||
pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
||||
if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].first)) {
|
||||
nProxy.pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (!pagesByTrack.count(it->first)){
|
||||
if (!nProxy.pagesByTrack.count(it->first)) {
|
||||
DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), pagesByTrack[it->first].size());
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++){
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), nProxy.pagesByTrack[it->first].size());
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = nProxy.pagesByTrack[it->first].begin(); it2 != nProxy.pagesByTrack[it->first].end(); it2++) {
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Page %lu-%lu, (%llu bytes)", it2->first, it2->first + it2->second.keyNum - 1, it2->second.dataSize);
|
||||
}
|
||||
}
|
||||
|
@ -328,29 +340,38 @@ namespace Mist {
|
|||
|
||||
|
||||
bool Input::bufferFrame(unsigned int track, unsigned int keyNum){
|
||||
VERYHIGH_MSG("bufferFrame for stream %s, track %u, key %u", streamName.c_str(), track, keyNum);
|
||||
VERYHIGH_MSG("Buffering stream %s, track %u, key %u", streamName.c_str(), track, keyNum);
|
||||
if (keyNum > myMeta.tracks[track].keys.size()){
|
||||
//End of movie here, returning true to avoid various error messages
|
||||
VERYHIGH_MSG("Key number is higher than total key count. Cancelling bufferFrame");
|
||||
WARN_MSG("Key %llu is higher than total (%llu). Cancelling buffering.", keyNum, myMeta.tracks[track].keys.size());
|
||||
return true;
|
||||
}
|
||||
if (keyNum < 1){keyNum = 1;}
|
||||
//abort in case already buffered
|
||||
int pageNumber = bufferedOnPage(track, keyNum);
|
||||
if (pageNumber){
|
||||
if (keyNum < 1) {
|
||||
keyNum = 1;
|
||||
}
|
||||
if (nProxy.isBuffered(track, keyNum)) {
|
||||
//get corresponding page number
|
||||
int pageNumber = 0;
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it = nProxy.pagesByTrack[track].begin(); it != nProxy.pagesByTrack[track].end(); it++) {
|
||||
if (it->first <= keyNum) {
|
||||
pageNumber = it->first;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pageCounter[track][pageNumber] = 15;
|
||||
VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber);
|
||||
return true;
|
||||
}
|
||||
if (!pagesByTrack.count(track)){
|
||||
if (!nProxy.pagesByTrack.count(track)) {
|
||||
WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track);
|
||||
return false;
|
||||
}
|
||||
//Update keynum to point to the corresponding page
|
||||
INFO_MSG("Loading key %u from page %lu", keyNum, (--(pagesByTrack[track].upper_bound(keyNum)))->first);
|
||||
keyNum = (--(pagesByTrack[track].upper_bound(keyNum)))->first;
|
||||
INFO_MSG("Loading key %u from page %lu", keyNum, (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first);
|
||||
keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first;
|
||||
if (!bufferStart(track, keyNum)){
|
||||
WARN_MSG("bufferStart failed! Cancelling bufferFrame", track);
|
||||
WARN_MSG("bufferStart failed! Cancelling bufferFrame");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -359,8 +380,8 @@ namespace Mist {
|
|||
trackSelect(trackSpec.str());
|
||||
seek(myMeta.tracks[track].keys[keyNum - 1].getTime());
|
||||
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
|
||||
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + pagesByTrack[track][keyNum].keyNum){
|
||||
stopTime = myMeta.tracks[track].keys[keyNum - 1 + pagesByTrack[track][keyNum].keyNum].getTime();
|
||||
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum) {
|
||||
stopTime = myMeta.tracks[track].keys[keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum].getTime();
|
||||
}
|
||||
DEBUG_MSG(DLVL_HIGH, "Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime);
|
||||
getNext();
|
||||
|
|
|
@ -20,7 +20,11 @@ namespace Mist {
|
|||
public:
|
||||
Input(Util::Config * cfg);
|
||||
virtual int run();
|
||||
virtual void onCrash(){}
|
||||
virtual void argumentsParsed(){}
|
||||
virtual ~Input() {};
|
||||
|
||||
virtual bool needsLock(){return true;}
|
||||
protected:
|
||||
static void callbackWrapper(char * data, size_t len, unsigned int id);
|
||||
virtual bool setup() = 0;
|
||||
|
@ -29,6 +33,9 @@ namespace Mist {
|
|||
virtual void getNext(bool smart = true) {};
|
||||
virtual void seek(int seekTime){};
|
||||
virtual void finish();
|
||||
virtual bool openStreamSource() { return false; };
|
||||
virtual void closeStreamSource() {};
|
||||
virtual void parseStreamHeader() {};
|
||||
void play(int until = 0);
|
||||
void playOnce();
|
||||
void quitPlay();
|
||||
|
@ -36,11 +43,11 @@ namespace Mist {
|
|||
virtual void removeUnused();
|
||||
virtual void trackSelect(std::string trackSpec){};
|
||||
virtual void userCallback(char * data, size_t len, unsigned int id);
|
||||
|
||||
void serve();
|
||||
void convert();
|
||||
virtual void convert();
|
||||
virtual void serve();
|
||||
|
||||
void parseHeader();
|
||||
|
||||
virtual void parseHeader();
|
||||
bool bufferFrame(unsigned int track, unsigned int keyNum);
|
||||
|
||||
unsigned int packTime;///Media-timestamp of the last packet.
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "input_buffer.h"
|
||||
|
||||
#ifndef TIMEOUTMULTIPLIER
|
||||
#define TIMEOUTMULTIPLIER 10
|
||||
#define TIMEOUTMULTIPLIER 2
|
||||
#endif
|
||||
|
||||
namespace Mist {
|
||||
|
@ -41,6 +41,8 @@ namespace Mist {
|
|||
singleton = this;
|
||||
bufferTime = 50000;
|
||||
cutTime = 0;
|
||||
hasPush = false;
|
||||
resumeMode = false;
|
||||
}
|
||||
|
||||
inputBuffer::~inputBuffer() {
|
||||
|
@ -48,16 +50,111 @@ namespace Mist {
|
|||
if (myMeta.tracks.size()) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes");
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
while (removeKey(it->first)) {}
|
||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[it->first];
|
||||
if (!nProxy.metaPages.count(it->first) || !nProxy.metaPages[it->first].mapped) {
|
||||
continue;
|
||||
}
|
||||
//First detect all entries on metaPage
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[it->first].mapped + i);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0) {
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
locations[keyNum].curOffset = 0;
|
||||
}
|
||||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator it2 = locations.begin(); it2 != locations.end(); it2++) {
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), it->first, it2->first);
|
||||
IPC::sharedPage erasePage(thisPageName, 20971520);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.unlink();
|
||||
}
|
||||
|
||||
|
||||
///Cleans up any left-over data for the current stream
|
||||
void inputBuffer::onCrash(){
|
||||
WARN_MSG("Buffer crashed. Cleaning.");
|
||||
streamName = config->getString("streamname");
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
|
||||
//Set userpage to all 0xFF's, will disconnect all current clients.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
std::string baseName = pageName;
|
||||
for (long unsigned i = 0; i < 15; ++i){
|
||||
unsigned int size = std::min(((8192 * 2) << i), (32 * 1024 * 1024));
|
||||
IPC::sharedPage tmp(std::string(baseName + (char)(i + (int)'A')), size, false, false);
|
||||
if (tmp.mapped){
|
||||
tmp.master = true;
|
||||
WARN_MSG("Wiping %s", std::string(baseName + (char)(i + (int)'A')).c_str());
|
||||
memset(tmp.mapped, 0xFF, size);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
//Delete the live stream semaphore, if any.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.unlink();
|
||||
}{
|
||||
//Delete the stream index metapage.
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
IPC::sharedPage erasePage(pageName, DEFAULT_STRM_PAGE_SIZE, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
//Delete most if not all temporary track metadata pages.
|
||||
for (long unsigned i = 1001; i <= 1024; ++i){
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), i);
|
||||
IPC::sharedPage erasePage(pageName, 1024, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
//Delete most if not all track indexes and data pages.
|
||||
for (long unsigned i = 1; i <= 24; ++i){
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), i);
|
||||
IPC::sharedPage indexPage(pageName, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
indexPage.master = true;
|
||||
if (indexPage.mapped){
|
||||
char * mappedPointer = indexPage.mapped;
|
||||
for (int j = 0; j < 8192; j += 8) {
|
||||
int * tmpOffset = (int *)(mappedPointer + j);
|
||||
if (tmpOffset[0] == 0 && tmpOffset[1] == 0){
|
||||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), i, keyNum);
|
||||
IPC::sharedPage erasePage(pageName, 1024, false, false);
|
||||
erasePage.master = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// \triggers
|
||||
/// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is:
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
/// streamname
|
||||
/// FULL or EMPTY (depending on current state)
|
||||
/// ~~~~~~~~~~~~~~~
|
||||
void inputBuffer::updateMeta() {
|
||||
long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull;
|
||||
long long unsigned int lastms = 0;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
if (it->second.type == "meta" || !it->second.type.size()){continue;}
|
||||
if (it->second.type == "meta" || !it->second.type.size()) {
|
||||
continue;
|
||||
}
|
||||
if (it->second.init.size()){
|
||||
if (!initData.count(it->first) || initData[it->first] != it->second.init){
|
||||
initData[it->first] = it->second.init;
|
||||
|
@ -81,14 +178,14 @@ namespace Mist {
|
|||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
liveMeta.wait();
|
||||
if (!metaPages.count(0) || !metaPages[0].mapped) {
|
||||
if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped) {
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true);
|
||||
metaPages[0].master = false;
|
||||
nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true);
|
||||
nProxy.metaPages[0].master = false;
|
||||
}
|
||||
myMeta.writeTo(metaPages[0].mapped);
|
||||
memset(metaPages[0].mapped + myMeta.getSendLen(), 0, (metaPages[0].len > myMeta.getSendLen() ? std::min(metaPages[0].len - myMeta.getSendLen(), 4ll) : 0));
|
||||
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||
memset(nProxy.metaPages[0].mapped + myMeta.getSendLen(), 0, (nProxy.metaPages[0].len > myMeta.getSendLen() ? std::min(nProxy.metaPages[0].len - myMeta.getSendLen(), 4ll) : 0));
|
||||
liveMeta.post();
|
||||
}
|
||||
|
||||
|
@ -118,30 +215,15 @@ namespace Mist {
|
|||
if (bufferLocations[tid].size() > 1) {
|
||||
//Check if the first key starts on the second page or higher
|
||||
if (myMeta.tracks[tid].keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active) {
|
||||
//Find page in indexpage and null it
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
unsigned int thisKeyNum = ((((long long int *)(metaPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF;
|
||||
if (thisKeyNum == htonl(bufferLocations[tid].begin()->first) && ((((long long int *)(metaPages[tid].mapped + i))[0]) != 0)){
|
||||
(((long long int *)(metaPages[tid].mapped + i))[0]) = 0;
|
||||
}
|
||||
}
|
||||
DEBUG_MSG(DLVL_DEVEL, "Erasing track %d, keys %lu-%lu from buffer", tid, bufferLocations[tid].begin()->first, bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1);
|
||||
bufferRemove(tid, bufferLocations[tid].begin()->first);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
int tmpNum = ntohl(tmpOffset[0]);
|
||||
if (tmpNum == bufferLocations[tid].begin()->first) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
curPageNum.erase(tid);
|
||||
nProxy.curPageNum.erase(tid);
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
||||
curPage[tid].init(thisPageName, 20971520);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
|
||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||
} else {
|
||||
|
@ -158,13 +240,13 @@ namespace Mist {
|
|||
for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first);
|
||||
curPage[tid].init(thisPageName, 20971520, false, false);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520, false, false);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
}
|
||||
bufferLocations.erase(tid);
|
||||
metaPages[tid].master = true;
|
||||
metaPages.erase(tid);
|
||||
nProxy.metaPages[tid].master = true;
|
||||
nProxy.metaPages.erase(tid);
|
||||
}
|
||||
|
||||
void inputBuffer::finish() {
|
||||
|
@ -189,11 +271,13 @@ namespace Mist {
|
|||
long long unsigned int time = Util::bootSecs();
|
||||
long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull;
|
||||
long long unsigned int compareLast = 0;
|
||||
std::set<std::string> activeTypes;
|
||||
//for tracks that were updated in the last 5 seconds, get the first and last ms edges.
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it2 = myMeta.tracks.begin(); it2 != myMeta.tracks.end(); it2++) {
|
||||
if ((time - lastUpdated[it2->first]) > 5) {
|
||||
continue;
|
||||
}
|
||||
activeTypes.insert(it2->second.type);
|
||||
if (it2->second.lastms > compareLast) {
|
||||
compareLast = it2->second.lastms;
|
||||
}
|
||||
|
@ -204,7 +288,7 @@ namespace Mist {
|
|||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//if not updated for an entire buffer duration, or last updated track and this track differ by an entire buffer duration, erase the track.
|
||||
if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000) ||
|
||||
(compareLast && (long long int)(time - lastUpdated[it->first]) > 5 && (
|
||||
(compareLast && activeTypes.count(it->second.type) && (long long int)(time - lastUpdated[it->first]) > 5 && (
|
||||
(compareLast < it->second.firstms && (long long int)(it->second.firstms - compareLast) > bufferTime)
|
||||
||
|
||||
(compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime)
|
||||
|
@ -213,26 +297,26 @@ namespace Mist {
|
|||
unsigned int tid = it->first;
|
||||
//erase this track
|
||||
if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000)){
|
||||
INFO_MSG("Erasing track %d because not updated for %ds (> %ds)", it->first, (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000));
|
||||
WARN_MSG("Erasing %s track %d (%s/%s) because not updated for %ds (> %ds)", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000));
|
||||
}else{
|
||||
INFO_MSG("Erasing inactive track %u because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", it->first, it->second.firstms / 1000, it->second.lastms / 1000, compareFirst/1000, compareLast/1000, bufferTime / 1000);
|
||||
WARN_MSG("Erasing %s inactive track %u (%s/%s) because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), it->second.firstms / 1000, it->second.lastms / 1000, compareFirst / 1000, compareLast / 1000, bufferTime / 1000);
|
||||
}
|
||||
lastUpdated.erase(tid);
|
||||
/// \todo Consider replacing with eraseTrackDataPages(it->first)?
|
||||
while (bufferLocations[tid].size()){
|
||||
char thisPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first);
|
||||
curPage[tid].init(thisPageName, 20971520);
|
||||
curPage[tid].master = true;
|
||||
curPage.erase(tid);
|
||||
nProxy.curPage[tid].init(thisPageName, 20971520);
|
||||
nProxy.curPage[tid].master = true;
|
||||
nProxy.curPage.erase(tid);
|
||||
bufferLocations[tid].erase(bufferLocations[tid].begin());
|
||||
}
|
||||
if (pushLocation.count(it->first)){
|
||||
pushLocation.erase(it->first);
|
||||
}
|
||||
curPageNum.erase(it->first);
|
||||
metaPages[it->first].master = true;
|
||||
metaPages.erase(it->first);
|
||||
nProxy.curPageNum.erase(it->first);
|
||||
nProxy.metaPages[it->first].master = true;
|
||||
nProxy.metaPages.erase(it->first);
|
||||
activeTracks.erase(it->first);
|
||||
myMeta.tracks.erase(it->first);
|
||||
changed = true;
|
||||
|
@ -251,8 +335,9 @@ namespace Mist {
|
|||
}
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
|
||||
//non-video tracks need to have a second keyframe that is <= firstVideo
|
||||
//firstVideo = 1 happens when there are no tracks, in which case we don't care any more
|
||||
if (it->second.type != "video") {
|
||||
if (it->second.keys.size() < 2 || it->second.keys[1].getTime() > firstVideo) {
|
||||
if (it->second.keys.size() < 2 || (it->second.keys[1].getTime() > firstVideo && firstVideo != 1)){
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -270,6 +355,14 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
updateMeta();
|
||||
static bool everHadPush = false;
|
||||
if (hasPush) {
|
||||
hasPush = false;
|
||||
everHadPush = true;
|
||||
} else if (everHadPush && !resumeMode && config->is_active) {
|
||||
INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected");
|
||||
config->is_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
|
||||
|
@ -278,10 +371,10 @@ namespace Mist {
|
|||
//Get the counter of this user
|
||||
char counter = (*(data - 1));
|
||||
//Each user can have at maximum SIMUL_TRACKS elements in their userpage.
|
||||
IPC::userConnection userConn(data);
|
||||
for (int index = 0; index < SIMUL_TRACKS; index++) {
|
||||
char * thisData = data + (index * 6);
|
||||
//Get the track id from the current element
|
||||
unsigned long value = ((long)(thisData[0]) << 24) | ((long)(thisData[1]) << 16) | ((long)(thisData[2]) << 8) | thisData[3];
|
||||
unsigned long value = userConn.getTrackId(index);
|
||||
//Skip value 0xFFFFFFFF as this indicates a previously declined track
|
||||
if (value == 0xFFFFFFFF) {
|
||||
continue;
|
||||
|
@ -294,12 +387,10 @@ namespace Mist {
|
|||
//If the current value indicates a valid trackid, and it is pushed from this user
|
||||
if (pushLocation[value] == data) {
|
||||
//Check for timeouts, and erase the track if necessary
|
||||
if (counter == 126 || counter == 127 || counter == 254 || counter == 255) {
|
||||
if (counter == 126 || counter == 127){
|
||||
pushLocation.erase(value);
|
||||
if (negotiatingTracks.count(value)) {
|
||||
negotiatingTracks.erase(value);
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
}
|
||||
if (activeTracks.count(value)) {
|
||||
updateMeta();
|
||||
|
@ -307,8 +398,8 @@ namespace Mist {
|
|||
activeTracks.erase(value);
|
||||
bufferLocations.erase(value);
|
||||
}
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -320,28 +411,24 @@ namespace Mist {
|
|||
//Add the temporary track id to the list of tracks that are currently being negotiated
|
||||
negotiatingTracks.insert(tempMapping);
|
||||
//Write the temporary id to the userpage element
|
||||
thisData[0] = (tempMapping >> 24) & 0xFF;
|
||||
thisData[1] = (tempMapping >> 16) & 0xFF;
|
||||
thisData[2] = (tempMapping >> 8) & 0xFF;
|
||||
thisData[3] = (tempMapping) & 0xFF;
|
||||
userConn.setTrackId(index, tempMapping);
|
||||
//Obtain the original track number for the pushing process
|
||||
unsigned long originalTrack = ((long)(thisData[4]) << 8) | thisData[5];
|
||||
unsigned long originalTrack = userConn.getKeynum(index);
|
||||
//Overwrite it with 0xFFFF
|
||||
thisData[4] = 0xFF;
|
||||
thisData[5] = 0xFF;
|
||||
userConn.setKeynum(index, 0xFFFF);
|
||||
DEBUG_MSG(DLVL_HIGH, "Incoming track %lu from pushing process %d has now been assigned temporary id %llu", originalTrack, id, tempMapping);
|
||||
}
|
||||
|
||||
//The track id is set to the value of a track that we are currently negotiating about
|
||||
if (negotiatingTracks.count(value)) {
|
||||
//If the metadata page for this track is not yet registered, initialize it
|
||||
if (!metaPages.count(value) || !metaPages[value].mapped) {
|
||||
if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped) {
|
||||
char tempMetaName[NAME_BUFFER_SIZE];
|
||||
snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, config->getString("streamname").c_str(), value);
|
||||
metaPages[value].init(tempMetaName, 8388608, false, false);
|
||||
nProxy.metaPages[value].init(tempMetaName, 8388608, false, false);
|
||||
}
|
||||
//If this tracks metdata page is not initialize, skip the entire element for now. It will be instantiated later
|
||||
if (!metaPages[value].mapped) {
|
||||
if (!nProxy.metaPages[value].mapped) {
|
||||
//remove the negotiation if it has timed out
|
||||
if (++negotiationTimeout[value] >= 1000){
|
||||
negotiatingTracks.erase(value);
|
||||
|
@ -353,13 +440,13 @@ namespace Mist {
|
|||
//The page exist, now we try to read in the metadata of the track
|
||||
|
||||
//Store the size of the dtsc packet to read.
|
||||
unsigned int len = ntohl(((int *)metaPages[value].mapped)[1]);
|
||||
unsigned int len = ntohl(((int *)nProxy.metaPages[value].mapped)[1]);
|
||||
//Temporary variable, won't be used again
|
||||
unsigned int tempForReadingMeta = 0;
|
||||
//Read in the metadata through a temporary JSON object
|
||||
///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately
|
||||
JSON::Value tempJSONForMeta;
|
||||
JSON::fromDTMI((const unsigned char *)metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta);
|
||||
JSON::fromDTMI((const unsigned char *)nProxy.metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta);
|
||||
//Construct a metadata object for the current track
|
||||
DTSC::Meta trackMeta(tempJSONForMeta);
|
||||
//If the track metadata does not contain the negotiated track, assume the metadata is currently being written, and skip the element for now. It will be instantiated in the next call.
|
||||
|
@ -368,8 +455,8 @@ namespace Mist {
|
|||
if (++negotiationTimeout[value] >= 1000){
|
||||
negotiatingTracks.erase(value);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
negotiationTimeout.erase(value);
|
||||
}
|
||||
continue;
|
||||
|
@ -381,8 +468,8 @@ namespace Mist {
|
|||
//Remove the "negotiate" status in either case
|
||||
negotiatingTracks.erase(value);
|
||||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
metaPages[value].master = true;
|
||||
metaPages.erase(value);
|
||||
nProxy.metaPages[value].master = true;
|
||||
nProxy.metaPages.erase(value);
|
||||
|
||||
int finalMap = 3;
|
||||
if (trackMeta.tracks.find(value)->second.type == "video"){finalMap = 1;}
|
||||
|
@ -402,8 +489,8 @@ namespace Mist {
|
|||
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages
|
||||
updateMeta();
|
||||
eraseTrackDataPages(value);
|
||||
metaPages[finalMap].master = true;
|
||||
metaPages.erase(finalMap);
|
||||
nProxy.metaPages[finalMap].master = true;
|
||||
nProxy.metaPages.erase(finalMap);
|
||||
bufferLocations.erase(finalMap);
|
||||
}
|
||||
|
||||
|
@ -415,43 +502,39 @@ namespace Mist {
|
|||
pushLocation[finalMap] = data;
|
||||
//Initialize the metadata for this track if it was not in place yet.
|
||||
if (!myMeta.tracks.count(finalMap)) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %d", finalMap);
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Inserting metadata for track number %d", finalMap);
|
||||
myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second;
|
||||
myMeta.tracks[finalMap].trackID = finalMap;
|
||||
}
|
||||
//Write the final mapped track number to the user page element
|
||||
thisData[0] = (finalMap >> 24) & 0xFF;
|
||||
thisData[1] = (finalMap >> 16) & 0xFF;
|
||||
thisData[2] = (finalMap >> 8) & 0xFF;
|
||||
thisData[3] = (finalMap) & 0xFF;
|
||||
//Write the key number to start pushing from to to the userpage element.
|
||||
//Write the final mapped track number and keyframe number to the user page element
|
||||
//This is used to resume pushing as well as pushing new tracks
|
||||
unsigned long keyNum = myMeta.tracks[finalMap].keys.size();
|
||||
thisData[4] = (keyNum >> 8) & 0xFF;
|
||||
thisData[5] = keyNum & 0xFF;
|
||||
userConn.setTrackId(index, finalMap);
|
||||
userConn.setKeynum(index, myMeta.tracks[finalMap].keys.size());
|
||||
//Update the metadata to reflect all changes
|
||||
updateMeta();
|
||||
}
|
||||
//If the track is active, and this is the element responsible for pushing it
|
||||
if (activeTracks.count(value) && pushLocation[value] == data) {
|
||||
//Open the track index page if we dont have it open yet
|
||||
if (!metaPages.count(value) || !metaPages[value].mapped) {
|
||||
if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped) {
|
||||
char firstPage[NAME_BUFFER_SIZE];
|
||||
snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value);
|
||||
metaPages[value].init(firstPage, 8192, false, false);
|
||||
nProxy.metaPages[value].init(firstPage, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
}
|
||||
if (metaPages[value].mapped) {
|
||||
if (nProxy.metaPages[value].mapped) {
|
||||
//Update the metadata for this track
|
||||
updateTrackMeta(value);
|
||||
hasPush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void inputBuffer::updateTrackMeta(unsigned long tNum) {
|
||||
VERYHIGH_MSG("Updating meta for track %d", tNum);
|
||||
//Store a reference for easier access
|
||||
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[tNum];
|
||||
char * mappedPointer = metaPages[tNum].mapped;
|
||||
char * mappedPointer = nProxy.metaPages[tNum].mapped;
|
||||
|
||||
//First detect all entries on metaPage
|
||||
for (int i = 0; i < 8192; i += 8) {
|
||||
|
@ -460,6 +543,7 @@ namespace Mist {
|
|||
continue;
|
||||
}
|
||||
unsigned long keyNum = ntohl(tmpOffset[0]);
|
||||
INSANE_MSG("Page %d detected, with %d keys", keyNum, ntohl(tmpOffset[1]));
|
||||
|
||||
//Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
|
||||
if (!locations.count(keyNum)) {
|
||||
|
@ -468,7 +552,6 @@ namespace Mist {
|
|||
locations[keyNum].pageNum = keyNum;
|
||||
locations[keyNum].keyNum = ntohl(tmpOffset[1]);
|
||||
}
|
||||
|
||||
//Since the map is ordered by keynumber, this loop updates the metadata for each page from oldest to newest
|
||||
for (std::map<unsigned long, DTSCPageData>::iterator pageIt = locations.begin(); pageIt != locations.end(); pageIt++) {
|
||||
updateMetaFromPage(tNum, pageIt->first);
|
||||
|
@ -477,6 +560,7 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputBuffer::updateMetaFromPage(unsigned long tNum, unsigned long pageNum) {
|
||||
VERYHIGH_MSG("Updating meta for track %d page %d", tNum, pageNum);
|
||||
DTSCPageData & pageData = bufferLocations[tNum][pageNum];
|
||||
|
||||
//If the current page is over its 8mb "splitting" boundary
|
||||
|
@ -491,27 +575,27 @@ namespace Mist {
|
|||
//Otherwise open and parse the page
|
||||
|
||||
//Open the page if it is not yet open
|
||||
if (!curPageNum.count(tNum) || curPageNum[tNum] != pageNum || !curPage[tNum].mapped){
|
||||
if (!nProxy.curPageNum.count(tNum) || nProxy.curPageNum[tNum] != pageNum || !nProxy.curPage[tNum].mapped) {
|
||||
//DO NOT ERASE THE PAGE HERE, master is not set to true
|
||||
curPageNum.erase(tNum);
|
||||
nProxy.curPageNum.erase(tNum);
|
||||
char nextPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tNum, pageNum);
|
||||
curPage[tNum].init(nextPageName, 20971520);
|
||||
nProxy.curPage[tNum].init(nextPageName, 20971520);
|
||||
//If the page can not be opened, stop here
|
||||
if (!curPage[tNum].mapped) {
|
||||
if (!nProxy.curPage[tNum].mapped) {
|
||||
WARN_MSG("Could not open page: %s", nextPageName);
|
||||
return;
|
||||
}
|
||||
curPageNum[tNum] = pageNum;
|
||||
nProxy.curPageNum[tNum] = pageNum;
|
||||
}
|
||||
|
||||
|
||||
DTSC::Packet tmpPack;
|
||||
if (!curPage[tNum].mapped[pageData.curOffset]){
|
||||
if (!nProxy.curPage[tNum].mapped[pageData.curOffset]) {
|
||||
VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum);
|
||||
return;
|
||||
}
|
||||
tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
//No new data has been written on the page since last update
|
||||
if (!tmpPack) {
|
||||
return;
|
||||
|
@ -527,7 +611,7 @@ namespace Mist {
|
|||
//Update the offset on the page with the size of the current packet
|
||||
pageData.curOffset += tmpPack.getDataLen();
|
||||
//Attempt to read in the next packet
|
||||
tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,8 +619,8 @@ namespace Mist {
|
|||
std::string strName = config->getString("streamname");
|
||||
Util::sanitizeName(strName);
|
||||
strName = strName.substr(0, (strName.find_first_of("+ ")));
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(strName);
|
||||
long long tmpNum;
|
||||
|
@ -553,7 +637,9 @@ namespace Mist {
|
|||
tmpNum = config->getOption("bufferTime").asInt();
|
||||
}
|
||||
}
|
||||
if (tmpNum < 1000){tmpNum = 1000;}
|
||||
if (tmpNum < 1000) {
|
||||
tmpNum = 1000;
|
||||
}
|
||||
//if the new value is different, print a message and apply it
|
||||
if (bufferTime != tmpNum) {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum);
|
||||
|
|
|
@ -7,9 +7,12 @@ namespace Mist {
|
|||
public:
|
||||
inputBuffer(Util::Config * cfg);
|
||||
~inputBuffer();
|
||||
void onCrash();
|
||||
private:
|
||||
unsigned int bufferTime;
|
||||
unsigned int cutTime;
|
||||
bool hasPush;
|
||||
bool resumeMode;
|
||||
protected:
|
||||
//Private Functions
|
||||
bool setup();
|
||||
|
@ -33,8 +36,7 @@ namespace Mist {
|
|||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
|
||||
std::map<unsigned long, char *> pushLocation;
|
||||
inputBuffer * singleton;
|
||||
|
||||
//This is used for an ugly fix to prevent metadata from dissapearing in some cases.
|
||||
//This is used for an ugly fix to prevent metadata from disappearing in some cases.
|
||||
std::map<unsigned long, std::string> initData;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
#include <mist/stream.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
#include <mist/util.h>
|
||||
#include <mist/bitfields.h>
|
||||
|
||||
#include "input_dtsc.h"
|
||||
|
||||
namespace Mist {
|
||||
|
@ -14,7 +17,8 @@ namespace Mist {
|
|||
capa["name"] = "DTSC";
|
||||
capa["desc"] = "Enables DTSC Input";
|
||||
capa["priority"] = 9ll;
|
||||
capa["source_match"] = "/*.dtsc";
|
||||
capa["source_match"].append("/*.dtsc");
|
||||
capa["source_match"].append("dtsc://*");
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][0u].append("H263");
|
||||
capa["codecs"][0u][0u].append("VP6");
|
||||
|
@ -24,7 +28,142 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("vorbis");
|
||||
}
|
||||
|
||||
bool inputDTSC::needsLock(){
|
||||
return config->getString("input").substr(0, 7) != "dtsc://";
|
||||
}
|
||||
|
||||
void parseDTSCURI(const std::string & src, std::string & host, uint16_t & port, std::string & password, std::string & streamName) {
|
||||
host = "";
|
||||
port = 4200;
|
||||
password = "";
|
||||
streamName = "";
|
||||
std::deque<std::string> matches;
|
||||
if (Util::stringScan(src, "%s:%s@%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
password = matches[2];
|
||||
streamName = matches[3];
|
||||
return;
|
||||
}
|
||||
//Using default streamname
|
||||
if (Util::stringScan(src, "%s:%s@%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
password = matches[2];
|
||||
return;
|
||||
}
|
||||
//Without password
|
||||
if (Util::stringScan(src, "%s:%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
streamName = matches[2];
|
||||
return;
|
||||
}
|
||||
//Using default port
|
||||
if (Util::stringScan(src, "%s@%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
password = matches[1];
|
||||
streamName = matches[2];
|
||||
return;
|
||||
}
|
||||
//Default port, no password
|
||||
if (Util::stringScan(src, "%s/%s", matches)) {
|
||||
host = matches[0];
|
||||
streamName = matches[1];
|
||||
return;
|
||||
}
|
||||
//No password, default streamname
|
||||
if (Util::stringScan(src, "%s:%s", matches)) {
|
||||
host = matches[0];
|
||||
port = atoi(matches[1].c_str());
|
||||
return;
|
||||
}
|
||||
//Default port and streamname
|
||||
if (Util::stringScan(src, "%s@%s", matches)) {
|
||||
host = matches[0];
|
||||
password = matches[1];
|
||||
return;
|
||||
}
|
||||
//Default port and streamname, no password
|
||||
if (Util::stringScan(src, "%s", matches)) {
|
||||
host = matches[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::parseStreamHeader() {
|
||||
while (srcConn.connected()){
|
||||
srcConn.spool();
|
||||
if (srcConn.Received().available(8)){
|
||||
if (srcConn.Received().copy(4) == "DTCM" || srcConn.Received().copy(4) == "DTSC") {
|
||||
// Command message
|
||||
std::string toRec = srcConn.Received().copy(8);
|
||||
unsigned long rSize = Bit::btohl(toRec.c_str() + 4);
|
||||
if (!srcConn.Received().available(8 + rSize)) {
|
||||
continue; //abort - not enough data yet
|
||||
}
|
||||
//Ignore initial DTCM message, as this is a "hi" message from the server
|
||||
if (srcConn.Received().copy(4) == "DTCM"){
|
||||
srcConn.Received().remove(8 + rSize);
|
||||
}else{
|
||||
std::string dataPacket = srcConn.Received().remove(8+rSize);
|
||||
DTSC::Packet metaPack(dataPacket.data(), dataPacket.size());
|
||||
myMeta.reinit(metaPack);
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
continueNegotiate(it->first, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool inputDTSC::openStreamSource() {
|
||||
std::string source = config->getString("input");
|
||||
if (source.find("dtsc://") == 0) {
|
||||
source.erase(0, 7);
|
||||
}
|
||||
std::string host;
|
||||
uint16_t port;
|
||||
std::string password;
|
||||
std::string streamName;
|
||||
parseDTSCURI(source, host, port, password, streamName);
|
||||
std::string givenStream = config->getString("streamname");
|
||||
if (streamName == "") {
|
||||
streamName = givenStream;
|
||||
}else{
|
||||
if (givenStream.find("+") != std::string::npos){
|
||||
streamName += givenStream.substr(givenStream.find("+"));
|
||||
}
|
||||
}
|
||||
srcConn = Socket::Connection(host, port, true);
|
||||
if (!srcConn.connected()){
|
||||
return false;
|
||||
}
|
||||
JSON::Value prep;
|
||||
prep["cmd"] = "play";
|
||||
prep["version"] = "MistServer " PACKAGE_VERSION;
|
||||
prep["stream"] = streamName;
|
||||
srcConn.SendNow("DTCM");
|
||||
char sSize[4] = {0, 0, 0, 0};
|
||||
Bit::htobl(sSize, prep.packedSize());
|
||||
srcConn.SendNow(sSize, 4);
|
||||
prep.sendTo(srcConn);
|
||||
return true;
|
||||
}
|
||||
|
||||
void inputDTSC::closeStreamSource(){
|
||||
srcConn.close();
|
||||
}
|
||||
|
||||
bool inputDTSC::setup() {
|
||||
if (!needsLock()) {
|
||||
return true;
|
||||
} else {
|
||||
if (config->getString("input") == "-") {
|
||||
std::cerr << "Input from stdin not yet supported" << std::endl;
|
||||
return false;
|
||||
|
@ -46,10 +185,14 @@ namespace Mist {
|
|||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool inputDTSC::readHeader() {
|
||||
if (!needsLock()) {
|
||||
return true;
|
||||
}
|
||||
if (!inFile) {
|
||||
return false;
|
||||
}
|
||||
|
@ -69,12 +212,62 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void inputDTSC::getNext(bool smart) {
|
||||
if (!needsLock()){
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() == DTSC::DTCM){
|
||||
std::string cmd;
|
||||
thisPacket.getString("cmd", cmd);
|
||||
if (cmd == "reset"){
|
||||
//Read next packet
|
||||
thisPacket.reInit(srcConn);
|
||||
if (thisPacket.getVersion() == DTSC::DTSC_HEAD){
|
||||
DTSC::Meta newMeta;
|
||||
newMeta.reinit(thisPacket);
|
||||
//Detect new tracks
|
||||
std::set<unsigned int> newTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){
|
||||
if (!myMeta.tracks.count(it->first)){
|
||||
newTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for (std::set<unsigned int>::iterator it = newTracks.begin(); it != newTracks.end(); it++){
|
||||
INFO_MSG("Adding track %d to internal metadata", *it);
|
||||
myMeta.tracks[*it] = newMeta.tracks[*it];
|
||||
continueNegotiate(*it, true);
|
||||
}
|
||||
|
||||
//Detect removed tracks
|
||||
std::set<unsigned int> deletedTracks;
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (!newMeta.tracks.count(it->first)){
|
||||
deletedTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
for(std::set<unsigned int>::iterator it = deletedTracks.begin(); it != deletedTracks.end(); it++){
|
||||
INFO_MSG("Deleting track %d from internal metadata", *it);
|
||||
myMeta.tracks.erase(*it);
|
||||
}
|
||||
|
||||
//Read next packet before returning
|
||||
thisPacket.reInit(srcConn);
|
||||
}else{
|
||||
myMeta = DTSC::Meta();
|
||||
}
|
||||
}else{
|
||||
//Read next packet before returning
|
||||
thisPacket.reInit(srcConn);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (smart){
|
||||
inFile.seekNext();
|
||||
}else{
|
||||
inFile.parseNext();
|
||||
}
|
||||
thisPacket = inFile.getPacket();
|
||||
}
|
||||
}
|
||||
|
||||
void inputDTSC::seek(int seekTime) {
|
||||
|
|
|
@ -5,8 +5,12 @@ namespace Mist {
|
|||
class inputDTSC : public Input {
|
||||
public:
|
||||
inputDTSC(Util::Config * cfg);
|
||||
bool needsLock();
|
||||
protected:
|
||||
//Private Functions
|
||||
bool openStreamSource();
|
||||
void closeStreamSource();
|
||||
void parseStreamHeader();
|
||||
bool setup();
|
||||
bool readHeader();
|
||||
void getNext(bool smart = true);
|
||||
|
@ -14,6 +18,8 @@ namespace Mist {
|
|||
void trackSelect(std::string trackSpec);
|
||||
|
||||
DTSC::File inFile;
|
||||
|
||||
Socket::Connection srcConn;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
if (!offset){
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos);
|
||||
DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos);
|
||||
return;
|
||||
}
|
||||
filePos += offset;
|
||||
|
|
|
@ -16,24 +16,34 @@ int main(int argc, char * argv[]) {
|
|||
mistIn conv(&conf);
|
||||
if (conf.parseArgs(argc, argv)) {
|
||||
std::string streamName = conf.getString("streamname");
|
||||
conv.argumentsParsed();
|
||||
|
||||
IPC::semaphore playerLock;
|
||||
if (streamName.size()){
|
||||
playerLock.open(std::string("/lock_" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!playerLock.tryWait()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str());
|
||||
return 1;
|
||||
if (conv.needsLock()){
|
||||
if (streamName.size()){
|
||||
char semName[NAME_BUFFER_SIZE];
|
||||
snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamName.c_str());
|
||||
playerLock.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
if (!playerLock.tryWait()){
|
||||
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
conf.activate();
|
||||
while (conf.is_active){
|
||||
pid_t pid = fork();
|
||||
if (pid == 0){
|
||||
playerLock.close();
|
||||
if (conv.needsLock()){
|
||||
playerLock.close();
|
||||
}
|
||||
return conv.run();
|
||||
}
|
||||
if (pid == -1){
|
||||
DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process");
|
||||
playerLock.post();
|
||||
if (conv.needsLock()){
|
||||
playerLock.post();
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
//wait for the process to exit
|
||||
|
@ -50,6 +60,11 @@ int main(int argc, char * argv[]) {
|
|||
DEBUG_MSG(DLVL_MEDIUM, "Input for stream %s shut down cleanly", streamName.c_str());
|
||||
break;
|
||||
}
|
||||
#if DEBUG >= DLVL_DEVEL
|
||||
WARN_MSG("Aborting autoclean; this is a development build.");
|
||||
#else
|
||||
conv.onCrash();
|
||||
#endif
|
||||
if (DEBUG >= DLVL_DEVEL){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Aborting restart; this is a development build.", streamName.c_str());
|
||||
break;
|
||||
|
@ -57,8 +72,11 @@ int main(int argc, char * argv[]) {
|
|||
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Restarting...", streamName.c_str());
|
||||
}
|
||||
}
|
||||
playerLock.post();
|
||||
playerLock.close();
|
||||
if (conv.needsLock()){
|
||||
playerLock.post();
|
||||
playerLock.unlink();
|
||||
playerLock.close();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
233
src/io.cpp
233
src/io.cpp
|
@ -1,4 +1,9 @@
|
|||
#include <mist/stream.h>
|
||||
#include <mist/json.h>
|
||||
#include <mist/auth.h>
|
||||
#include <mist/encode.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <cstdlib>
|
||||
#include "io.h"
|
||||
|
||||
namespace Mist {
|
||||
|
@ -11,12 +16,29 @@ namespace Mist {
|
|||
//Open the page for the metadata
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages[0].init(pageName, myMeta.getSendLen(), true);
|
||||
nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true);
|
||||
//Make sure we don't delete it on accident
|
||||
metaPages[0].master = false;
|
||||
nProxy.metaPages[0].master = false;
|
||||
|
||||
//Write the metadata to the page
|
||||
myMeta.writeTo(metaPages[0].mapped);
|
||||
myMeta.writeTo(nProxy.metaPages[0].mapped);
|
||||
|
||||
}
|
||||
|
||||
bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) {
|
||||
VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber);
|
||||
//Initialize the stream metadata if it does not yet exist
|
||||
if (!nProxy.metaPages.count(0)) {
|
||||
initiateMeta();
|
||||
}
|
||||
//If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with.
|
||||
if (standAlone) {
|
||||
if (!nProxy.trackMap.count(tid)) {
|
||||
nProxy.trackMap[tid] = tid;
|
||||
}
|
||||
}
|
||||
//Negotiate the requested track if needed.
|
||||
return nProxy.bufferStart(tid, pageNumber, myMeta);
|
||||
}
|
||||
|
||||
///Starts the buffering of a new page.
|
||||
|
@ -26,30 +48,38 @@ namespace Mist {
|
|||
///Buffering itself is done by bufferNext().
|
||||
///\param tid The trackid of the page to start buffering
|
||||
///\param pageNumber The number of the page to start buffering
|
||||
bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) {
|
||||
VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber);
|
||||
//Initialize the stream metadata if it does not yet exist
|
||||
if (!metaPages.count(0)) {
|
||||
initiateMeta();
|
||||
}
|
||||
//If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with.
|
||||
if (standAlone) {
|
||||
if (!trackMap.count(tid)) {
|
||||
trackMap[tid] = tid;
|
||||
}
|
||||
}
|
||||
bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta) {
|
||||
//Negotiate the requested track if needed.
|
||||
continueNegotiate(tid);
|
||||
continueNegotiate(tid, myMeta);
|
||||
|
||||
//If the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later.
|
||||
if (trackState[tid] != FILL_ACC) {
|
||||
///\return false if the track has not been accepted (yet)
|
||||
return false;
|
||||
}
|
||||
|
||||
//If the track is accepted, we will have a mapped tid
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
|
||||
//Before we start a new page, make sure we can be heard by the buffer about this.
|
||||
//Otherwise, it might linger forever as a nasty data leak.
|
||||
//Nobody likes nasty data leaks.
|
||||
{
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), mapTid);
|
||||
IPC::sharedPage checkPage(pageName, SHM_TRACK_INDEX_SIZE, false, false);
|
||||
if (!checkPage.mapped){
|
||||
WARN_MSG("Buffer deleted %s@%lu (%s) index. Re-negotiating...", streamName.c_str(), mapTid, myMeta.tracks[tid].codec.c_str());
|
||||
trackState.erase(tid);
|
||||
trackMap.erase(tid);
|
||||
trackOffset.erase(tid);
|
||||
pagesByTrack.erase(tid);
|
||||
metaPages.erase(tid);
|
||||
curPageNum.erase(tid);
|
||||
curPage.erase(tid);
|
||||
return bufferStart(tid, pageNumber, myMeta);
|
||||
}
|
||||
}
|
||||
|
||||
//If we are currently buffering a page, abandon it completely and print a message about this
|
||||
//This page will NEVER be deleted, unless we open it again later.
|
||||
if (curPage.count(tid)) {
|
||||
|
@ -94,6 +124,9 @@ namespace Mist {
|
|||
//Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext()
|
||||
pagesByTrack[tid][pageNumber].curOffset = 0;
|
||||
|
||||
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
||||
|
||||
|
||||
if (myMeta.live){
|
||||
//Register this page on the meta page
|
||||
//NOTE: It is important that this only happens if the stream is live....
|
||||
|
@ -102,18 +135,17 @@ namespace Mist {
|
|||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) {
|
||||
tmpOffset[0] = htonl(curPageNum[tid]);
|
||||
if (pagesByTrack[tid][pageNumber].dataSize == (25 * 1024 * 1024)){
|
||||
tmpOffset[1] = htonl(1000);
|
||||
} else {
|
||||
tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum);
|
||||
}
|
||||
tmpOffset[1] = htonl(1000);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!inserted){
|
||||
FAIL_MSG("Could not insert page in track index. Aborting.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
|
||||
///\return true if everything was successful
|
||||
return true;
|
||||
}
|
||||
|
@ -128,13 +160,28 @@ namespace Mist {
|
|||
//A different process will handle this for us
|
||||
return;
|
||||
}
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
if (!pagesByTrack.count(tid)){
|
||||
unsigned long mapTid = nProxy.trackMap[tid];
|
||||
|
||||
DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid);
|
||||
int i = 0;
|
||||
for (; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[tid].mapped + (i * 8));
|
||||
if (ntohl(tmpOffset[0]) == pageNumber) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 1024){
|
||||
FAIL_MSG("Could not erase page %lu for track %lu->%lu stream %s from track index!", pageNumber, tid, mapTid, streamName.c_str());
|
||||
}
|
||||
|
||||
if (!nProxy.pagesByTrack.count(tid)){
|
||||
// If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer)
|
||||
return;
|
||||
}
|
||||
//If the given pagenumber is not a valid page on this track, do nothing
|
||||
if (!pagesByTrack[tid].count(pageNumber)){
|
||||
if (!nProxy.pagesByTrack[tid].count(pageNumber)){
|
||||
INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", pageNumber, tid, mapTid);
|
||||
return;
|
||||
}
|
||||
|
@ -146,23 +193,14 @@ namespace Mist {
|
|||
#ifdef __CYGWIN__
|
||||
toErase.init(pageName, 26 * 1024 * 1024, false);
|
||||
#else
|
||||
toErase.init(pageName, pagesByTrack[tid][pageNumber].dataSize, false);
|
||||
toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false);
|
||||
#endif
|
||||
//Set the master flag so that the page will be destroyed once it leaves scope
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
IPC::releasePage(pageName);
|
||||
#endif
|
||||
toErase.master = true;
|
||||
|
||||
//Remove the page from the tracks index page
|
||||
DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
if (ntohl(tmpOffset[0]) == pageNumber) {
|
||||
tmpOffset[0] = 0;
|
||||
tmpOffset[1] = 0;
|
||||
}
|
||||
}
|
||||
//Leaving scope here, the page will now be destroyed
|
||||
}
|
||||
|
||||
|
@ -170,7 +208,7 @@ namespace Mist {
|
|||
///Checks whether a key is buffered
|
||||
///\param tid The trackid on which to locate the key
|
||||
///\param keyNum The number of the keyframe to find
|
||||
bool InOutBase::isBuffered(unsigned long tid, unsigned long keyNum) {
|
||||
bool negotiationProxy::isBuffered(unsigned long tid, unsigned long keyNum) {
|
||||
///\return The result of bufferedOnPage(tid, keyNum)
|
||||
return bufferedOnPage(tid, keyNum);
|
||||
}
|
||||
|
@ -178,24 +216,24 @@ namespace Mist {
|
|||
///Returns the pagenumber where this key is buffered on
|
||||
///\param tid The trackid on which to locate the key
|
||||
///\param keyNum The number of the keyframe to find
|
||||
unsigned long InOutBase::bufferedOnPage(unsigned long tid, unsigned long keyNum) {
|
||||
unsigned long negotiationProxy::bufferedOnPage(unsigned long tid, unsigned long keyNum) {
|
||||
//Check whether the track is accepted
|
||||
if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) {
|
||||
///\return 0 if the page has not been mapped yet
|
||||
return 0;
|
||||
}
|
||||
//Loop over the index page
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
int len = metaPages[tid].len / 8;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
|
||||
int pageNum = ntohl(tmpOffset[0]);
|
||||
int keyAmount = ntohl(tmpOffset[1]);
|
||||
unsigned int keyAmount = ntohl(tmpOffset[1]);
|
||||
if (keyAmount == 0){continue;}
|
||||
//Check whether the key is on this page
|
||||
unsigned int pageNum = ntohl(tmpOffset[0]);
|
||||
if (pageNum <= keyNum && keyNum < pageNum + keyAmount) {
|
||||
///\return The pagenumber of the page the key is located on, if the page is registered on the track index page
|
||||
return pageNum;
|
||||
}
|
||||
}
|
||||
///\return 0 if the key was not found
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -205,12 +243,16 @@ namespace Mist {
|
|||
std::string packData = pack.toNetPacked();
|
||||
DTSC::Packet newPack(packData.data(), packData.size());
|
||||
///\note Internally calls bufferNext(DTSC::Packet & pack)
|
||||
bufferNext(newPack);
|
||||
nProxy.bufferNext(newPack, myMeta);
|
||||
}
|
||||
|
||||
///Buffers the next packet on the currently opened page
|
||||
///\param pack The packet to buffer
|
||||
void InOutBase::bufferNext(DTSC::Packet & pack) {
|
||||
nProxy.bufferNext(pack, myMeta);
|
||||
}
|
||||
|
||||
void negotiationProxy::bufferNext(DTSC::Packet & pack, DTSC::Meta & myMeta) {
|
||||
//Save the trackid of the track for easier access
|
||||
unsigned long tid = pack.getTrackId();
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
|
@ -261,6 +303,10 @@ namespace Mist {
|
|||
///Registers the data page on the track index page as well
|
||||
///\param tid The trackid of the page to finalize
|
||||
void InOutBase::bufferFinalize(unsigned long tid) {
|
||||
nProxy.bufferFinalize(tid, myMeta);
|
||||
}
|
||||
|
||||
void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta & myMeta){
|
||||
unsigned long mapTid = trackMap[tid];
|
||||
//If no page is open, do nothing
|
||||
if (!curPage.count(tid)) {
|
||||
|
@ -342,6 +388,10 @@ namespace Mist {
|
|||
///Initiates/continues negotiation with the buffer as well
|
||||
///\param packet The packet to buffer
|
||||
void InOutBase::bufferLivePacket(DTSC::Packet & packet){
|
||||
nProxy.bufferLivePacket(packet, myMeta);
|
||||
}
|
||||
|
||||
void negotiationProxy::bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta){
|
||||
myMeta.vod = false;
|
||||
myMeta.live = true;
|
||||
//Store the trackid for easier access
|
||||
|
@ -353,31 +403,13 @@ namespace Mist {
|
|||
}
|
||||
//If the track is not negotiated yet, start the negotiation
|
||||
if (!trackState.count(tid)) {
|
||||
continueNegotiate(tid);
|
||||
continueNegotiate(tid, myMeta);
|
||||
}
|
||||
//If the track is declined, stop here
|
||||
if (trackState[tid] == FILL_DEC) {
|
||||
INFO_MSG("Track %lu Declined", tid);
|
||||
return;
|
||||
}
|
||||
//Check if a different track is already accepted
|
||||
bool shouldBlock = true;
|
||||
if (pagesByTrack.count(tid) && pagesByTrack[tid].size()) {
|
||||
for (std::map<unsigned long, negotiationState>::iterator it = trackState.begin(); it != trackState.end(); it++) {
|
||||
if (it->second == FILL_ACC) {
|
||||
//If so, we do not block here
|
||||
shouldBlock = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
//Block if no tracks are accepted yet, until we have a definite state
|
||||
if (shouldBlock) {
|
||||
while (trackState[tid] != FILL_DEC && trackState[tid] != FILL_ACC) {
|
||||
INFO_MSG("Blocking on track %lu", tid);
|
||||
continueNegotiate(tid);
|
||||
Util::sleep(500);
|
||||
}
|
||||
}
|
||||
//This update needs to happen whether the track is accepted or not.
|
||||
///\todo Figure out how to act with declined track here
|
||||
bool isKeyframe = false;
|
||||
|
@ -398,21 +430,21 @@ namespace Mist {
|
|||
}
|
||||
//Determine if we need to open the next page
|
||||
int nextPageNum = -1;
|
||||
if (isKeyframe || !pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) {
|
||||
if (isKeyframe && trackState[tid] == FILL_ACC) {
|
||||
//If there is no page, create it
|
||||
if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) {
|
||||
nextPageNum = 1;
|
||||
pagesByTrack[tid][1].dataSize = (25 * 1024 * 1024);//Initialize op 25mb
|
||||
pagesByTrack[tid][1].dataSize = DEFAULT_DATA_PAGE_SIZE;//Initialize op 25mb
|
||||
pagesByTrack[tid][1].pageNum = 1;
|
||||
}
|
||||
//Take the last allocated page
|
||||
std::map<unsigned long, DTSCPageData>::reverse_iterator tmpIt = pagesByTrack[tid].rbegin();
|
||||
//Compare on 8 mb boundary
|
||||
if (tmpIt->second.curOffset > (8 * 1024 * 1024)) {
|
||||
if (tmpIt->second.curOffset > FLIP_DATA_PAGE_SIZE) {
|
||||
//Create the book keeping data for the new page
|
||||
nextPageNum = tmpIt->second.pageNum + tmpIt->second.keyNum;
|
||||
INFO_MSG("We should go to next page now, transition from %lu to %d", tmpIt->second.pageNum, nextPageNum);
|
||||
pagesByTrack[tid][nextPageNum].dataSize = (25 * 1024 * 1024);
|
||||
pagesByTrack[tid][nextPageNum].dataSize = DEFAULT_DATA_PAGE_SIZE;
|
||||
pagesByTrack[tid][nextPageNum].pageNum = nextPageNum;
|
||||
}
|
||||
pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime();
|
||||
|
@ -426,6 +458,10 @@ namespace Mist {
|
|||
nextPageNum = 1;
|
||||
}
|
||||
}
|
||||
//If we have no pages by track, we have not received a starting keyframe yet. Drop this packet.
|
||||
if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){
|
||||
return;
|
||||
}
|
||||
//At this point we can stop parsing when the track is not accepted
|
||||
if (trackState[tid] != FILL_ACC) {
|
||||
return;
|
||||
|
@ -435,16 +471,20 @@ namespace Mist {
|
|||
if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) {
|
||||
if (curPageNum.count(tid)) {
|
||||
//Close the currently opened page when it exists
|
||||
bufferFinalize(tid);
|
||||
bufferFinalize(tid, myMeta);
|
||||
}
|
||||
//Open the new page
|
||||
bufferStart(tid, nextPageNum);
|
||||
bufferStart(tid, nextPageNum, myMeta);
|
||||
}
|
||||
//Buffer the packet
|
||||
bufferNext(packet);
|
||||
bufferNext(packet, myMeta);
|
||||
}
|
||||
|
||||
void InOutBase::continueNegotiate(unsigned long tid) {
|
||||
void InOutBase::continueNegotiate(unsigned long tid, bool quickNegotiate) {
|
||||
nProxy.continueNegotiate(tid, myMeta, quickNegotiate);
|
||||
}
|
||||
|
||||
void negotiationProxy::continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate) {
|
||||
if (!tid) {
|
||||
return;
|
||||
}
|
||||
|
@ -457,7 +497,7 @@ namespace Mist {
|
|||
trackState[tid] = FILL_ACC;
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), tid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
return;
|
||||
}
|
||||
|
@ -490,7 +530,7 @@ namespace Mist {
|
|||
if (!userClient.getData()){
|
||||
char userPageName[100];
|
||||
sprintf(userPageName, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
}
|
||||
char * tmp = userClient.getData();
|
||||
if (!tmp) {
|
||||
|
@ -500,12 +540,47 @@ namespace Mist {
|
|||
unsigned long offset = 6 * trackOffset[tid];
|
||||
//If we have a new track to negotiate
|
||||
if (!trackState.count(tid)) {
|
||||
INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]);
|
||||
memset(tmp + offset, 0, 4);
|
||||
tmp[offset] = 0x80;
|
||||
tmp[offset + 4] = ((tid >> 8) & 0xFF);
|
||||
tmp[offset + 5] = (tid & 0xFF);
|
||||
trackState[tid] = FILL_NEW;
|
||||
if (quickNegotiate){
|
||||
unsigned long finalTid = tid;
|
||||
unsigned short firstPage = 1;
|
||||
MEDIUM_MSG("Buffer has indicated that incoming track %lu should start writing on track %lu, page %lu", tid, finalTid, firstPage);
|
||||
trackMap[tid] = finalTid;
|
||||
if (myMeta.tracks.count(finalTid) && myMeta.tracks[finalTid].lastms){
|
||||
myMeta.tracks[finalTid].lastms = 0;
|
||||
}
|
||||
trackState[tid] = FILL_ACC;
|
||||
|
||||
|
||||
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].master = false;
|
||||
DTSC::Meta tmpMeta;
|
||||
tmpMeta.tracks[finalTid] = myMeta.tracks[tid];
|
||||
tmpMeta.tracks[finalTid].trackID = finalTid;
|
||||
JSON::Value tmpVal = tmpMeta.toJSON();
|
||||
std::string tmpStr = tmpVal.toNetPacked();
|
||||
memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size());
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
Bit::htobl(tmp + offset, finalTid | 0xC0000000);
|
||||
Bit::htobs(tmp + offset + 4, firstPage);
|
||||
}else{
|
||||
INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]);
|
||||
memset(tmp + offset, 0, 4);
|
||||
tmp[offset] = 0x80;
|
||||
tmp[offset + 4] = ((tid >> 8) & 0xFF);
|
||||
tmp[offset + 5] = (tid & 0xFF);
|
||||
trackState[tid] = FILL_NEW;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if defined(__CYGWIN__) || defined(_WIN32)
|
||||
|
@ -579,7 +654,7 @@ namespace Mist {
|
|||
trackState[tid] = FILL_ACC;
|
||||
char pageName[NAME_BUFFER_SIZE];
|
||||
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid);
|
||||
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
|
||||
metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true);
|
||||
metaPages[tid].master = false;
|
||||
break;
|
||||
}
|
||||
|
|
53
src/io.h
53
src/io.h
|
@ -25,6 +25,36 @@ namespace Mist {
|
|||
unsigned long lastKeyTime;///<The last key time encountered on this track.
|
||||
};
|
||||
|
||||
class negotiationProxy {
|
||||
public:
|
||||
negotiationProxy() {}
|
||||
bool bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta);
|
||||
void bufferNext(DTSC::Packet & pack, DTSC::Meta & myMeta);
|
||||
void bufferFinalize(unsigned long tid, DTSC::Meta &myMeta);
|
||||
void bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta);
|
||||
bool isBuffered(unsigned long tid, unsigned long keyNum);
|
||||
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
|
||||
|
||||
|
||||
|
||||
|
||||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack;///<Holds the data for all pages of a track. Based on unmapped tids
|
||||
|
||||
//Negotiation stuff (from unmapped tid's)
|
||||
std::map<unsigned long, unsigned long> trackOffset; ///< Offset of data on user page
|
||||
std::map<unsigned long, negotiationState> trackState; ///< State of the negotiation for tracks
|
||||
std::map<unsigned long, unsigned long> trackMap;///<Determines which input track maps onto which "final" track
|
||||
std::map<unsigned long, IPC::sharedPage> metaPages;///< For each track, holds the page that describes which dataPages are mapped
|
||||
std::map<unsigned long, unsigned long> curPageNum;///< For each track, holds the number page that is currently being written.
|
||||
std::map<unsigned long, IPC::sharedPage> curPage;///< For each track, holds the page that is currently being written.
|
||||
|
||||
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
|
||||
|
||||
std::string streamName;///< Name of the stream to connect to
|
||||
|
||||
void continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate = false);
|
||||
};
|
||||
|
||||
///\brief Class containing all basic input and output functions.
|
||||
class InOutBase {
|
||||
public:
|
||||
|
@ -36,32 +66,23 @@ namespace Mist {
|
|||
void bufferRemove(unsigned long tid, unsigned long pageNumber);
|
||||
void bufferLivePacket(JSON::Value & packet);
|
||||
void bufferLivePacket(DTSC::Packet & packet);
|
||||
bool isBuffered(unsigned long tid, unsigned long keyNum);
|
||||
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
|
||||
protected:
|
||||
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
|
||||
|
||||
|
||||
|
||||
bool standAlone;
|
||||
static Util::Config * config;
|
||||
|
||||
void continueNegotiate(unsigned long tid);
|
||||
negotiationProxy nProxy;
|
||||
|
||||
DTSC::Packet thisPacket;//The current packet that is being parsed
|
||||
|
||||
std::string streamName;///< Name of the stream to connect to
|
||||
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
|
||||
std::string streamName;
|
||||
|
||||
DTSC::Meta myMeta;///< Stores either the input or output metadata
|
||||
|
||||
std::set<unsigned long> selectedTracks;///< Stores the track id's that are either selected for playback or input
|
||||
|
||||
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > pagesByTrack;///<Holds the data for all pages of a track. Based on unmapped tids
|
||||
|
||||
//Negotiation stuff (from unmapped tid's)
|
||||
std::map<unsigned long, unsigned long> trackOffset; ///< Offset of data on user page
|
||||
std::map<unsigned long, negotiationState> trackState; ///< State of the negotiation for tracks
|
||||
std::map<unsigned long, unsigned long> trackMap;///<Determines which input track maps onto which "final" track
|
||||
std::map<unsigned long, IPC::sharedPage> metaPages;///< For each track, holds the page that describes which dataPages are mapped
|
||||
std::map<unsigned long, unsigned long> curPageNum;///< For each track, holds the number page that is currently being written.
|
||||
std::map<unsigned long, IPC::sharedPage> curPage;///< For each track, holds the page that is currently being written.
|
||||
std::map<unsigned long, std::deque<DTSC::Packet> > trackBuffer; ///< Buffer to be used during active track negotiation
|
||||
DTSC::Meta myMeta;///< Stores either the input or output metadata
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include OUTPUTTYPE
|
||||
#include <mist/config.h>
|
||||
#include <mist/socket.h>
|
||||
#include <mist/defines.h>
|
||||
|
||||
int spawnForked(Socket::Connection & S){
|
||||
mistOut tmp(S);
|
||||
|
@ -15,6 +16,7 @@ int main(int argc, char * argv[]) {
|
|||
std::cout << mistOut::capa.toString() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
conf.activate();
|
||||
if (mistOut::listenMode()){
|
||||
conf.serveForkedSocket(spawnForked);
|
||||
}else{
|
||||
|
|
|
@ -44,7 +44,6 @@ namespace Mist {
|
|||
maxSkipAhead = 7500;
|
||||
minSkipAhead = 5000;
|
||||
realTime = 1000;
|
||||
completeKeyReadyTimeOut = false;
|
||||
if (myConn){
|
||||
setBlocking(true);
|
||||
}else{
|
||||
|
@ -57,26 +56,31 @@ namespace Mist {
|
|||
isBlocking = blocking;
|
||||
myConn.setBlocking(isBlocking);
|
||||
}
|
||||
|
||||
Output::~Output(){}
|
||||
|
||||
void Output::updateMeta(){
|
||||
//read metadata from page to myMeta variable
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
IPC::semaphore * liveSem = 0;
|
||||
if (!myMeta.vod){
|
||||
static char liveSemName[NAME_BUFFER_SIZE];
|
||||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
bool lock = myMeta.live;
|
||||
if (lock){
|
||||
liveMeta.wait();
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true);
|
||||
liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 1);
|
||||
if (*liveSem){
|
||||
liveSem->wait();
|
||||
}else{
|
||||
delete liveSem;
|
||||
liveSem = 0;
|
||||
}
|
||||
}
|
||||
DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true);
|
||||
if (tmpMeta.getVersion()){
|
||||
myMeta.reinit(tmpMeta);
|
||||
}
|
||||
}
|
||||
if (lock){
|
||||
liveMeta.post();
|
||||
if (liveSem){
|
||||
liveSem->post();
|
||||
delete liveSem;
|
||||
liveSem = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +99,7 @@ namespace Mist {
|
|||
if (isInitialized){
|
||||
return;
|
||||
}
|
||||
if (metaPages[0].mapped){
|
||||
if (nProxy.metaPages[0].mapped){
|
||||
return;
|
||||
}
|
||||
if (streamName.size() < 1){
|
||||
|
@ -120,23 +124,24 @@ namespace Mist {
|
|||
return myConn.getBinHost();
|
||||
}
|
||||
|
||||
bool Output::isReadyForPlay() {
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >= 2){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// Connects or reconnects to the stream.
|
||||
/// Assumes streamName class member has been set already.
|
||||
/// Will start input if not currently active, calls onFail() if this does not succeed.
|
||||
/// After assuring stream is online, clears metaPages, then sets metaPages[0], statsPage and userClient to (hopefully) valid handles.
|
||||
/// Finally, calls updateMeta() and stats()
|
||||
/// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles.
|
||||
/// Finally, calls updateMeta()
|
||||
void Output::reconnect(){
|
||||
if (!Util::startInput(streamName)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization");
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
metaPages.clear();
|
||||
metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
if (!metaPages[0].mapped){
|
||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||
FAIL_MSG("Opening stream %s failed - aborting initalization", streamName.c_str());
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
|
@ -144,14 +149,35 @@ namespace Mist {
|
|||
statsPage.finish();
|
||||
}
|
||||
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
|
||||
if (userClient.getData()){
|
||||
userClient.finish();
|
||||
if (nProxy.userClient.getData()){
|
||||
nProxy.userClient.finish();
|
||||
}
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
stats();
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
nProxy.metaPages.clear();
|
||||
nProxy.metaPages[0].init(pageId, DEFAULT_STRM_PAGE_SIZE);
|
||||
if (!nProxy.metaPages[0].mapped){
|
||||
FAIL_MSG("Could not connect to server for %s", streamName.c_str());
|
||||
onFail();
|
||||
return;
|
||||
}
|
||||
stats(true);
|
||||
updateMeta();
|
||||
if (myMeta.live && !isReadyForPlay()){
|
||||
unsigned long long waitUntil = Util::epoch() + 15;
|
||||
while (!isReadyForPlay()){
|
||||
if (Util::epoch() > waitUntil){
|
||||
INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), getConnectedHost().c_str());
|
||||
break;
|
||||
}
|
||||
Util::wait(750);
|
||||
stats();
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Output::selectDefaultTracks(){
|
||||
|
@ -192,10 +218,12 @@ namespace Mist {
|
|||
}
|
||||
if (!found){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
if (trit->second.codec == (*itc).asStringRef()){
|
||||
if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){
|
||||
genCounter++;
|
||||
found = true;
|
||||
break;
|
||||
if ((*itc).asStringRef() != "*"){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,16 +234,16 @@ namespace Mist {
|
|||
if (selCounter + genCounter > bestSoFarCount){
|
||||
bestSoFarCount = selCounter + genCounter;
|
||||
bestSoFar = index;
|
||||
DEBUG_MSG(DLVL_HIGH, "Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str());
|
||||
HIGH_MSG("Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str());
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Not a match for currently selected tracks: %s", (*it).toString().c_str());
|
||||
VERYHIGH_MSG("Not a match for currently selected tracks: %s", (*it).toString().c_str());
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str());
|
||||
MEDIUM_MSG("Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str());
|
||||
//try to fill as many codecs simultaneously as possible
|
||||
if (capa["codecs"][bestSoFar].size() > 0){
|
||||
jsonForEach(capa["codecs"][bestSoFar], itb) {
|
||||
|
@ -233,10 +261,12 @@ namespace Mist {
|
|||
}
|
||||
if (!found){
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
if (trit->second.codec == (*itc).asStringRef()){
|
||||
if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){
|
||||
selectedTracks.insert(trit->first);
|
||||
found = true;
|
||||
break;
|
||||
if ((*itc).asStringRef() != "*"){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +289,37 @@ namespace Mist {
|
|||
DEBUG_MSG(DLVL_MEDIUM, "Selected tracks: %s (%lu)", selected.str().c_str(), selectedTracks.size());
|
||||
}
|
||||
|
||||
if (selectedTracks.size() == 0) {
|
||||
INSANE_MSG("We didn't find any tracks which that we can use. selectedTrack.size() is 0.");
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){
|
||||
INSANE_MSG("Found track/codec: %s", trit->second.codec.c_str());
|
||||
}
|
||||
static std::string source;
|
||||
if (!source.size()){
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
std::string smp = streamName.substr(0, streamName.find_first_of("+ "));
|
||||
//check if smp (everything before + or space) exists
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(smp);
|
||||
if (streamCfg){
|
||||
source = streamCfg.getMember("source").asString();
|
||||
}
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
}
|
||||
if (!myMeta.tracks.size() && (source.find("dtsc://") == 0)){
|
||||
//Wait 5 seconds and try again. Keep a counter, try at most 3 times
|
||||
static int counter = 0;
|
||||
if (counter++ < 10){
|
||||
Util::wait(1000);
|
||||
nProxy.userClient.keepAlive();
|
||||
stats();
|
||||
updateMeta();
|
||||
selectDefaultTracks();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the buffer, sets parseData to false, and generally makes not very much happen at all.
|
||||
|
@ -287,14 +348,15 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (!metaPages.count(trackId)){
|
||||
if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||
metaPages[trackId].init(id, 8 * 1024);
|
||||
nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE);
|
||||
}
|
||||
int len = metaPages[trackId].len / 8;
|
||||
if (!nProxy.metaPages[trackId].mapped){return -1;}
|
||||
int len = nProxy.metaPages[trackId].len / 8;
|
||||
for (int i = 0; i < len; i++){
|
||||
int * tmpOffset = (int *)(metaPages[trackId].mapped + (i * 8));
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8));
|
||||
long amountKey = ntohl(tmpOffset[1]);
|
||||
if (amountKey == 0){continue;}
|
||||
long tmpKey = ntohl(tmpOffset[0]);
|
||||
|
@ -304,19 +366,40 @@ namespace Mist {
|
|||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// Gets the highest page number available for the given trackId.
|
||||
int Output::pageNumMax(long unsigned int trackId){
|
||||
if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId);
|
||||
nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE);
|
||||
}
|
||||
if (!nProxy.metaPages[trackId].mapped){return -1;}
|
||||
int len = nProxy.metaPages[trackId].len / 8;
|
||||
int highest = -1;
|
||||
for (int i = 0; i < len; i++){
|
||||
int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8));
|
||||
long amountKey = ntohl(tmpOffset[1]);
|
||||
if (amountKey == 0){continue;}
|
||||
long tmpKey = ntohl(tmpOffset[0]);
|
||||
if (tmpKey > highest){highest = tmpKey;}
|
||||
}
|
||||
return highest;
|
||||
}
|
||||
|
||||
void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
|
||||
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){
|
||||
curPage.erase(trackId);
|
||||
INFO_MSG("Seek in track %lu to key %lld aborted, is > %lld", trackId, keyNum, myMeta.tracks[trackId].keys.rbegin()->getNumber());
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Loading track %lu, containing key %lld", trackId, keyNum);
|
||||
VERYHIGH_MSG("Loading track %lu, containing key %lld", trackId, keyNum);
|
||||
unsigned int timeout = 0;
|
||||
unsigned long pageNum = pageNumForKey(trackId, keyNum);
|
||||
while (pageNum == -1){
|
||||
if (!timeout){
|
||||
DEBUG_MSG(DLVL_HIGH, "Requesting page with key %lu:%lld", trackId, keyNum);
|
||||
HIGH_MSG("Requesting page with key %lu:%lld", trackId, keyNum);
|
||||
}
|
||||
++timeout;
|
||||
//if we've been waiting for this page for 3 seconds, reconnect to the stream - something might be going wrong...
|
||||
|
@ -325,8 +408,8 @@ namespace Mist {
|
|||
reconnect();
|
||||
}
|
||||
if (timeout > 100){
|
||||
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||
curPage.erase(trackId);
|
||||
FAIL_MSG("Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
|
||||
nProxy.curPage.erase(trackId);
|
||||
currKeyOpen.erase(trackId);
|
||||
return;
|
||||
}
|
||||
|
@ -335,7 +418,7 @@ namespace Mist {
|
|||
}else{
|
||||
nxtKeyNum[trackId] = 0;
|
||||
}
|
||||
stats();
|
||||
stats(true);
|
||||
Util::wait(100);
|
||||
pageNum = pageNumForKey(trackId, keyNum);
|
||||
}
|
||||
|
@ -345,20 +428,20 @@ namespace Mist {
|
|||
}else{
|
||||
nxtKeyNum[trackId] = 0;
|
||||
}
|
||||
stats();
|
||||
nxtKeyNum[trackId] = pageNum;
|
||||
stats(true);
|
||||
|
||||
if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){
|
||||
return;
|
||||
}
|
||||
char id[NAME_BUFFER_SIZE];
|
||||
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
|
||||
curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(curPage[trackId].mapped)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str());
|
||||
nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
|
||||
if (!(nProxy.curPage[trackId].mapped)){
|
||||
FAIL_MSG("Initializing page %s failed", nProxy.curPage[trackId].name.c_str());
|
||||
return;
|
||||
}
|
||||
currKeyOpen[trackId] = pageNum;
|
||||
VERYHIGH_MSG("Page %s loaded for %s", id, streamName.c_str());
|
||||
}
|
||||
|
||||
/// Prepares all tracks from selectedTracks for seeking to the specified ms position.
|
||||
|
@ -373,44 +456,58 @@ namespace Mist {
|
|||
if (myMeta.live){
|
||||
updateMeta();
|
||||
}
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Seeking to %llums", pos);
|
||||
MEDIUM_MSG("Seeking to %llums", pos);
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
seek(*it, pos);
|
||||
if (myMeta.tracks.count(*it)){
|
||||
seek(*it, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
|
||||
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||
if (!curPage.count(tid) || !curPage[tid].mapped){
|
||||
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid);
|
||||
if (myMeta.tracks[tid].lastms < pos){
|
||||
INFO_MSG("Aborting seek to %llums in track %u: past end of track (= %llums).", pos, tid, myMeta.tracks[tid].lastms);
|
||||
return false;
|
||||
}
|
||||
unsigned int keyNum = getKeyForTime(tid, pos);
|
||||
if (myMeta.tracks[tid].getKey(keyNum).getTime() > pos){
|
||||
if (myMeta.live){
|
||||
INFO_MSG("Actually seeking to %d, for %d is not available any more", myMeta.tracks[tid].getKey(keyNum).getTime(), pos);
|
||||
pos = myMeta.tracks[tid].getKey(keyNum).getTime();
|
||||
}
|
||||
}
|
||||
loadPageForKey(tid, keyNum + (getNextKey?1:0));
|
||||
if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){
|
||||
INFO_MSG("Aborting seek to %llums in track %u: not available.", pos, tid);
|
||||
return false;
|
||||
}
|
||||
sortedPageInfo tmp;
|
||||
tmp.tid = tid;
|
||||
tmp.offset = 0;
|
||||
DTSC::Packet tmpPack;
|
||||
tmpPack.reInit(curPage[tid].mapped + tmp.offset, 0, true);
|
||||
tmpPack.reInit(nProxy.curPage[tid].mapped + tmp.offset, 0, true);
|
||||
tmp.time = tmpPack.getTime();
|
||||
char * mpd = curPage[tid].mapped;
|
||||
char * mpd = nProxy.curPage[tid].mapped;
|
||||
while ((long long)tmp.time < pos && tmpPack){
|
||||
tmp.offset += tmpPack.getDataLen();
|
||||
tmpPack.reInit(mpd + tmp.offset, 0, true);
|
||||
tmp.time = tmpPack.getTime();
|
||||
}
|
||||
if (tmpPack){
|
||||
HIGH_MSG("Sought to time %d in %s@%u", tmp.time, streamName.c_str(), tid);
|
||||
buffer.insert(tmp);
|
||||
return true;
|
||||
}else{
|
||||
//don't print anything for empty packets - not sign of corruption, just unfinished stream.
|
||||
if (curPage[tid].mapped[tmp.offset] != 0){
|
||||
DEBUG_MSG(DLVL_FAIL, "Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
||||
if (nProxy.curPage[tid].mapped[tmp.offset] != 0){
|
||||
FAIL_MSG("Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
|
||||
}else{
|
||||
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
|
||||
unsigned int i = 0;
|
||||
while (curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
||||
while (nProxy.curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){
|
||||
Util::wait(100);
|
||||
}
|
||||
if (curPage[tid].mapped[tmp.offset] == 0){
|
||||
if (nProxy.curPage[tid].mapped[tmp.offset] == 0){
|
||||
FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
|
||||
}else{
|
||||
return seek(tid, pos, getNextKey);
|
||||
|
@ -434,9 +531,8 @@ namespace Mist {
|
|||
}
|
||||
|
||||
int Output::run() {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler started");
|
||||
while (config->is_active && myConn.connected() && (wantRequest || parseData)){
|
||||
stats();
|
||||
DONTEVEN_MSG("MistOut client handler started");
|
||||
while (config->is_active && myConn && (wantRequest || parseData)){
|
||||
if (wantRequest){
|
||||
requestHandler();
|
||||
}
|
||||
|
@ -445,11 +541,67 @@ namespace Mist {
|
|||
initialize();
|
||||
}
|
||||
if ( !sentHeader){
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "sendHeader");
|
||||
DONTEVEN_MSG("sendHeader");
|
||||
sendHeader();
|
||||
}
|
||||
prepareNext();
|
||||
if (!sought){
|
||||
if (myMeta.live){
|
||||
long unsigned int mainTrack = getMainSelectedTrack();
|
||||
//cancel if there are no keys in the main track
|
||||
if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){break;}
|
||||
//seek to the newest keyframe, unless that is <5s, then seek to the oldest keyframe
|
||||
unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime();
|
||||
if (seekPos < 5000){
|
||||
seekPos = myMeta.tracks[mainTrack].keys.begin()->getTime();
|
||||
}
|
||||
seek(seekPos);
|
||||
}else{
|
||||
seek(0);
|
||||
}
|
||||
}
|
||||
if (prepareNext()){
|
||||
if (thisPacket){
|
||||
|
||||
|
||||
//slow down processing, if real time speed is wanted
|
||||
if (realTime){
|
||||
while (thisPacket.getTime() > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) {
|
||||
Util::sleep(std::min(thisPacket.getTime() - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime, 1000llu));
|
||||
stats();
|
||||
}
|
||||
}
|
||||
|
||||
//delay the stream until its current keyframe is complete, if only complete keys wanted
|
||||
if (completeKeysOnly){
|
||||
bool completeKeyReady = false;
|
||||
int timeoutTries = 40;//wait default 250ms*40=10 seconds
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >1){
|
||||
int thisTimeoutTries = ((it->second.lastms - it->second.firstms) / (it->second.keys.size()-1)) / 125;
|
||||
if (thisTimeoutTries > timeoutTries) timeoutTries = thisTimeoutTries;
|
||||
}
|
||||
}
|
||||
while(!completeKeyReady && timeoutTries>0){
|
||||
completeKeyReady = true;
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= thisPacket.getTime() ){
|
||||
completeKeyReady = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!completeKeyReady){
|
||||
timeoutTries--;//we count down
|
||||
stats();
|
||||
Util::wait(250);
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
if (timeoutTries<=0){
|
||||
WARN_MSG("Waiting for key frame timed out");
|
||||
completeKeysOnly = false;
|
||||
}
|
||||
}
|
||||
|
||||
sendNext();
|
||||
}else{
|
||||
if (!onFinish()){
|
||||
|
@ -458,9 +610,12 @@ namespace Mist {
|
|||
}
|
||||
}
|
||||
}
|
||||
DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data");
|
||||
stats();
|
||||
userClient.finish();
|
||||
}
|
||||
MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data");
|
||||
|
||||
stats(true);
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
myConn.close();
|
||||
return 0;
|
||||
|
@ -473,250 +628,258 @@ namespace Mist {
|
|||
return 0;
|
||||
}
|
||||
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (myMeta.tracks[*it].type == "video"){
|
||||
if (myMeta.tracks.count(*it) && myMeta.tracks[*it].type == "video"){
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
return *(selectedTracks.begin());
|
||||
}
|
||||
|
||||
void Output::prepareNext(){
|
||||
static int nonVideoCount = 0;
|
||||
if (!sought){
|
||||
if (myMeta.live){
|
||||
long unsigned int mainTrack = getMainSelectedTrack();
|
||||
if (myMeta.tracks[mainTrack].keys.size() < 2){
|
||||
if (!myMeta.tracks[mainTrack].keys.size()){
|
||||
myConn.close();
|
||||
return;
|
||||
}else{
|
||||
seek(myMeta.tracks[mainTrack].keys.begin()->getTime());
|
||||
prepareNext();
|
||||
return;
|
||||
}
|
||||
}
|
||||
unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime();
|
||||
if (seekPos < 5000){
|
||||
seekPos = 0;
|
||||
}
|
||||
seek(seekPos);
|
||||
}else{
|
||||
seek(0);
|
||||
void Output::dropTrack(uint32_t trackId, std::string reason, bool probablyBad){
|
||||
//depending on whether this is probably bad and the current debug level, print a message
|
||||
unsigned int printLevel = DLVL_INFO;
|
||||
if (probablyBad){
|
||||
printLevel = DLVL_WARN;
|
||||
}
|
||||
DEBUG_MSG(printLevel, "Dropping %s (%s) track %lu@k%lu (nextP=%d, lastP=%d): %s", streamName.c_str(), myMeta.tracks[trackId].codec.c_str(), (long unsigned)trackId, nxtKeyNum[trackId]+1, pageNumForKey(trackId, nxtKeyNum[trackId]+1), pageNumMax(trackId), reason.c_str());
|
||||
//now actually drop the track from the buffer
|
||||
for (std::set<sortedPageInfo>::iterator it = buffer.begin(); it != buffer.end(); ++it){
|
||||
if (it->tid == trackId){
|
||||
buffer.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
selectedTracks.erase(trackId);
|
||||
}
|
||||
|
||||
///Attempts to prepare a new packet for output.
|
||||
///If thisPacket evaluates to false, playback has completed.
|
||||
///Could be called repeatedly in a loop if you really really want a new packet.
|
||||
/// \returns true if thisPacket was filled with the next packet.
|
||||
/// \returns false if we could not reliably determine the next packet yet.
|
||||
bool Output::prepareNext(){
|
||||
static bool atLivePoint = false;
|
||||
static int nonVideoCount = 0;
|
||||
static unsigned int emptyCount = 0;
|
||||
if (!buffer.size()){
|
||||
thisPacket.null();
|
||||
DEBUG_MSG(DLVL_DEVEL, "Buffer completely played out");
|
||||
onFinish();
|
||||
return;
|
||||
INFO_MSG("Buffer completely played out");
|
||||
return true;
|
||||
}
|
||||
sortedPageInfo nxt = *(buffer.begin());
|
||||
buffer.erase(buffer.begin());
|
||||
|
||||
DEBUG_MSG(DLVL_DONTEVEN, "Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
||||
|
||||
if (nxt.offset >= curPage[nxt.tid].len){
|
||||
if (!myMeta.tracks.count(nxt.tid)){
|
||||
dropTrack(nxt.tid, "disappeared from metadata", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
DONTEVEN_MSG("Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time);
|
||||
|
||||
//if we're going to read past the end of the data page, load the next page
|
||||
//this only happens for VoD
|
||||
if (nxt.offset >= nProxy.curPage[nxt.tid].len){
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
|
||||
dropTrack(nxt.tid, "time going backwards");
|
||||
}else{
|
||||
nxt.time = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
nxt.time = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
//swap out the next object in the buffer with a new one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
}
|
||||
prepareNext();
|
||||
return;
|
||||
}else{
|
||||
dropTrack(nxt.tid, "page load failure", true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!curPage.count(nxt.tid) || !curPage[nxt.tid].mapped){
|
||||
//mapping failure? Drop this track and go to next.
|
||||
//not an error - usually means end of stream.
|
||||
DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid);
|
||||
prepareNext();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
//have we arrived at the end of the memory page? (4 zeroes mark the end)
|
||||
if (!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
||||
if (!memcmp(nProxy.curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){
|
||||
//if we don't currently know where we are, we're lost. We should drop the track.
|
||||
if (!nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid);
|
||||
prepareNext();
|
||||
return;
|
||||
dropTrack(nxt.tid, "timeless empty packet");
|
||||
return false;
|
||||
}
|
||||
//if this is a live stream, we might have just reached the live point.
|
||||
//check where the next key is
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
int nextPage = pageNumForKey(nxt.tid, nxtKeyNum[nxt.tid]+1);
|
||||
//are we live, and the next key hasn't shown up on another page? then we're waiting.
|
||||
//are we live, and the next key hasn't shown up on another page, then we're waiting.
|
||||
if (myMeta.live && currKeyOpen.count(nxt.tid) && (currKeyOpen[nxt.tid] == (unsigned int)nextPage || nextPage == -1)){
|
||||
if (myMeta && ++emptyCount < 42){
|
||||
//we're waiting for new data. Simply retry.
|
||||
buffer.insert(nxt);
|
||||
}else{
|
||||
//after ~10 seconds, give up and drop the track.
|
||||
DEBUG_MSG(DLVL_DEVEL, "Empty packet on track %u @ key %lu (next=%d) - could not reload, dropping track.", nxt.tid, nxtKeyNum[nxt.tid]+1, nextPage);
|
||||
}
|
||||
//keep updating the metadata at 250ms intervals while waiting for more data
|
||||
Util::sleep(250);
|
||||
updateMeta();
|
||||
}else{
|
||||
//if we're not live, we've simply reached the end of the page. Load the next key.
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime && nextTime < nxt.time){
|
||||
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid);
|
||||
if (++emptyCount < 100){
|
||||
Util::wait(250);
|
||||
//we're waiting for new data to show up
|
||||
if (emptyCount % 8 == 0){
|
||||
reconnect();//reconnect every 2 seconds
|
||||
}else{
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
if (emptyCount % 4 == 0){
|
||||
updateMeta();
|
||||
}
|
||||
buffer.insert(nxt);
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Next page for track %u starts at %llu.", nxt.tid, nxt.time);
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_DEVEL, "Could not load next memory page for track %u - dropping track.", nxt.tid);
|
||||
//after ~25 seconds, give up and drop the track.
|
||||
dropTrack(nxt.tid, "could not reload empty packet");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
prepareNext();
|
||||
return;
|
||||
}
|
||||
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
if (thisPacket){
|
||||
if (thisPacket.getTime() != nxt.time && nxt.time){
|
||||
WARN_MSG("Loaded track %ld@%llu instead of %ld@%llu", thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time);
|
||||
}
|
||||
if ((myMeta.tracks[nxt.tid].type == "video" && thisPacket.getFlag("keyframe")) || (++nonVideoCount % 30 == 0)){
|
||||
if (myMeta.live){
|
||||
updateMeta();
|
||||
}
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
DEBUG_MSG(DLVL_VERYHIGH, "Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]);
|
||||
}
|
||||
emptyCount = 0;
|
||||
}
|
||||
nxt.offset += thisPacket.getDataLen();
|
||||
if (realTime){
|
||||
while (nxt.time > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) {
|
||||
Util::sleep(nxt.time - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime);
|
||||
}
|
||||
}
|
||||
//delay the stream until its current keyframe is complete
|
||||
if (completeKeysOnly){
|
||||
bool completeKeyReady = false;
|
||||
int timeoutTries = 40;//attempts to updateMeta before timeOut and moving on; default is approximately 10 seconds
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >1){
|
||||
int thisTimeoutTries = ((it->second.lastms - it->second.firstms) / (it->second.keys.size()-1)) / 125;
|
||||
if (thisTimeoutTries > timeoutTries) timeoutTries = thisTimeoutTries;
|
||||
}
|
||||
}
|
||||
while(!completeKeyReady && timeoutTries>0){
|
||||
completeKeyReady = true;
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
|
||||
if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= nxt.time ){
|
||||
completeKeyReady = false;
|
||||
break;
|
||||
|
||||
//We've simply reached the end of the page. Load the next key = next page.
|
||||
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
|
||||
nxt.offset = 0;
|
||||
if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime && nextTime < nxt.time){
|
||||
dropTrack(nxt.tid, "time going backwards");
|
||||
}else{
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}
|
||||
//swap out the next object in the buffer with a new one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
MEDIUM_MSG("Next page for track %u starts at %llu.", nxt.tid, nxt.time);
|
||||
}
|
||||
if (!completeKeyReady){
|
||||
if (completeKeyReadyTimeOut){
|
||||
INSANE_MSG("Complete Key not ready and time-out is being skipped");
|
||||
timeoutTries = 0;
|
||||
}else{
|
||||
INSANE_MSG("Timeout: %d",timeoutTries);
|
||||
timeoutTries--;//we count down
|
||||
stats();
|
||||
Util::wait(250);
|
||||
updateMeta();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (timeoutTries<=0){
|
||||
if (!completeKeyReadyTimeOut){
|
||||
INFO_MSG("Wait for keyframe Timeout triggered! Ended to avoid endless loops");
|
||||
}
|
||||
completeKeyReadyTimeOut = true;
|
||||
}else{
|
||||
//untimeout handling
|
||||
completeKeyReadyTimeOut = false;
|
||||
dropTrack(nxt.tid, "page load failure");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (curPage[nxt.tid]){
|
||||
if (nxt.offset < curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}else{
|
||||
++nxt.time;
|
||||
|
||||
//we've handled all special cases - at this point the packet should exist
|
||||
//let's load it
|
||||
thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true);
|
||||
//if it failed, drop the track and continue
|
||||
if (!thisPacket){
|
||||
dropTrack(nxt.tid, "packet load failure");
|
||||
return false;
|
||||
}
|
||||
emptyCount = 0;//valid packet - reset empty counter
|
||||
|
||||
//if there's a timestamp mismatch, print this.
|
||||
//except for live, where we never know the time in advance
|
||||
if (thisPacket.getTime() != nxt.time && nxt.time && !atLivePoint){
|
||||
static int warned = 0;
|
||||
if (warned < 5){
|
||||
WARN_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str());
|
||||
if (++warned == 5){
|
||||
WARN_MSG("Further warnings about time mismatches printed on HIGH level.");
|
||||
}
|
||||
}else{
|
||||
HIGH_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str());
|
||||
}
|
||||
buffer.insert(nxt);
|
||||
}
|
||||
stats();
|
||||
|
||||
//when live, every keyframe, check correctness of the keyframe number
|
||||
if (myMeta.live && thisPacket.getFlag("keyframe")){
|
||||
//Check whether returned keyframe is correct. If not, wait for approximately 10 seconds while checking.
|
||||
//Failure here will cause tracks to drop due to inconsistent internal state.
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
int counter = 0;
|
||||
while(counter < 40 && myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){
|
||||
if (counter++){
|
||||
//Only sleep 250ms if this is not the first updatemeta try
|
||||
Util::wait(250);
|
||||
}
|
||||
updateMeta();
|
||||
nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
|
||||
}
|
||||
if (myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){
|
||||
WARN_MSG("Keyframe value is not correct - state will now be inconsistent.");
|
||||
}
|
||||
EXTREME_MSG("Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]);
|
||||
}
|
||||
|
||||
//always assume we're not at the live point
|
||||
atLivePoint = false;
|
||||
//we assume the next packet is the next on this same page
|
||||
nxt.offset += thisPacket.getDataLen();
|
||||
if (nxt.offset < nProxy.curPage[nxt.tid].len){
|
||||
unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
|
||||
if (nextTime){
|
||||
nxt.time = nextTime;
|
||||
}else{
|
||||
++nxt.time;
|
||||
//no packet -> we are at the live point
|
||||
atLivePoint = true;
|
||||
}
|
||||
}
|
||||
|
||||
//exchange the current packet in the buffer for the next one
|
||||
buffer.erase(buffer.begin());
|
||||
buffer.insert(nxt);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Output::stats(){
|
||||
static bool setHost = true;
|
||||
if (!isInitialized){
|
||||
return;
|
||||
/// Returns the name as it should be used in statistics.
|
||||
/// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name.
|
||||
/// The default implementation is usually good enough for all the non-INPUT types.
|
||||
std::string Output::getStatsName(){
|
||||
if (config->hasOption("target") && config->getString("target").size()){
|
||||
return "OUTPUT";
|
||||
}else{
|
||||
return capa["name"].asStringRef();
|
||||
}
|
||||
}
|
||||
|
||||
void Output::stats(bool force){
|
||||
//cancel stats update if not initialized
|
||||
if (!isInitialized){return;}
|
||||
//also cancel if it has been less than a second since the last update
|
||||
//unless force is set to true
|
||||
unsigned long long int now = Util::epoch();
|
||||
if (now == lastStats && !force){return;}
|
||||
lastStats = now;
|
||||
|
||||
EXTREME_MSG("Writing stats: %s, %s, %lu", getConnectedHost().c_str(), streamName.c_str(), crc & 0xFFFFFFFFu);
|
||||
if (statsPage.getData()){
|
||||
unsigned long long int now = Util::epoch();
|
||||
if (now != lastStats){
|
||||
lastStats = now;
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
if (setHost){
|
||||
tmpEx.host(getConnectedBinHost());
|
||||
setHost = false;
|
||||
}
|
||||
tmpEx.crc(crc);
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector(capa["name"].asString());
|
||||
tmpEx.up(myConn.dataUp());
|
||||
tmpEx.down(myConn.dataDown());
|
||||
tmpEx.time(now - myConn.connTime());
|
||||
if (thisPacket){
|
||||
tmpEx.lastSecond(thisPacket.getTime());
|
||||
}else{
|
||||
tmpEx.lastSecond(0);
|
||||
}
|
||||
statsPage.keepAlive();
|
||||
IPC::statExchange tmpEx(statsPage.getData());
|
||||
tmpEx.now(now);
|
||||
if (tmpEx.host() == std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){
|
||||
tmpEx.host(getConnectedBinHost());
|
||||
}
|
||||
tmpEx.crc(crc);
|
||||
tmpEx.streamName(streamName);
|
||||
tmpEx.connector(getStatsName());
|
||||
tmpEx.up(myConn.dataUp());
|
||||
tmpEx.down(myConn.dataDown());
|
||||
tmpEx.time(now - myConn.connTime());
|
||||
if (thisPacket){
|
||||
tmpEx.lastSecond(thisPacket.getTime());
|
||||
}else{
|
||||
tmpEx.lastSecond(0);
|
||||
}
|
||||
statsPage.keepAlive();
|
||||
}
|
||||
int tNum = 0;
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!userClient.getData()){
|
||||
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output");
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
if (!nProxy.userClient.getData()){
|
||||
WARN_MSG("Player connection failure - aborting output");
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!trackMap.size()){
|
||||
if (!nProxy.userClient.isAlive()){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
if (!nProxy.trackMap.size()){
|
||||
IPC::userConnection userConn(nProxy.userClient.getData());
|
||||
for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
|
||||
unsigned int tId = *it;
|
||||
char * thisData = userClient.getData() + (6 * tNum);
|
||||
thisData[0] = ((tId >> 24) & 0xFF);
|
||||
thisData[1] = ((tId >> 16) & 0xFF);
|
||||
thisData[2] = ((tId >> 8) & 0xFF);
|
||||
thisData[3] = ((tId) & 0xFF);
|
||||
thisData[4] = ((nxtKeyNum[tId] >> 8) & 0xFF);
|
||||
thisData[5] = ((nxtKeyNum[tId]) & 0xFF);
|
||||
userConn.setTrackId(tNum, *it);
|
||||
userConn.setKeynum(tNum, nxtKeyNum[*it]);
|
||||
tNum ++;
|
||||
}
|
||||
}
|
||||
userClient.keepAlive();
|
||||
nProxy.userClient.keepAlive();
|
||||
if (tNum > SIMUL_TRACKS){
|
||||
DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||
WARN_MSG("Too many tracks selected, using only first %d", SIMUL_TRACKS);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,4 +893,24 @@ namespace Mist {
|
|||
//just set the sentHeader bool to true, by default
|
||||
sentHeader = true;
|
||||
}
|
||||
|
||||
bool Output::connectToFile(std::string file) {
|
||||
int flags = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
int mode = O_RDWR | O_CREAT | O_TRUNC;
|
||||
int outFile = open(file.c_str(), mode, flags);
|
||||
if (outFile < 0) {
|
||||
ERROR_MSG("Failed to open file %s, error: %s", file.c_str(), strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int r = dup2(outFile, myConn.getSocket());
|
||||
if (r == -1) {
|
||||
ERROR_MSG("Failed to create an alias for the socket using dup2: %s.", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
close(outFile);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -37,13 +37,12 @@ namespace Mist {
|
|||
public:
|
||||
//constructor and destructor
|
||||
Output(Socket::Connection & conn);
|
||||
virtual ~Output();
|
||||
//static members for initialization and capabilities
|
||||
static void init(Util::Config * cfg);
|
||||
static JSON::Value capa;
|
||||
//non-virtual generic functions
|
||||
int run();
|
||||
void stats();
|
||||
void stats(bool force = false);
|
||||
void seek(unsigned long long pos);
|
||||
bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false);
|
||||
void stop();
|
||||
|
@ -51,10 +50,13 @@ namespace Mist {
|
|||
long unsigned int getMainSelectedTrack();
|
||||
void updateMeta();
|
||||
void selectDefaultTracks();
|
||||
bool connectToFile(std::string file);
|
||||
static bool listenMode(){return true;}
|
||||
virtual bool isReadyForPlay();
|
||||
//virtuals. The optional virtuals have default implementations that do as little as possible.
|
||||
virtual void sendNext() {}//REQUIRED! Others are optional.
|
||||
virtual void prepareNext();
|
||||
bool prepareNext();
|
||||
virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true);
|
||||
virtual void onRequest();
|
||||
virtual bool onFinish() {
|
||||
return false;
|
||||
|
@ -68,21 +70,21 @@ namespace Mist {
|
|||
std::map<unsigned long, unsigned int> currKeyOpen;
|
||||
void loadPageForKey(long unsigned int trackId, long long int keyNum);
|
||||
int pageNumForKey(long unsigned int trackId, long long int keyNum);
|
||||
int pageNumMax(long unsigned int trackId);
|
||||
unsigned int lastStats;///<Time of last sending of stats.
|
||||
long long unsigned int firstTime;///< Time of first packet after last seek. Used for real-time sending.
|
||||
std::map<unsigned long, unsigned long> nxtKeyNum;///< Contains the number of the next key, for page seeking purposes.
|
||||
std::set<sortedPageInfo> buffer;///< A sorted list of next-to-be-loaded packets.
|
||||
bool sought;///<If a seek has been done, this is set to true. Used for seeking on prepareNext().
|
||||
bool completeKeyReadyTimeOut;//a bool to see if there has been a keyframe TimeOut for complete keys in Live
|
||||
protected://these are to be messed with by child classes
|
||||
|
||||
virtual std::string getConnectedHost();
|
||||
virtual std::string getConnectedBinHost();
|
||||
|
||||
virtual std::string getStatsName();
|
||||
virtual bool hasSessionIDs(){return false;}
|
||||
|
||||
IPC::sharedClient statsPage;///< Shared memory used for statistics reporting.
|
||||
bool isBlocking;///< If true, indicates that myConn is blocking.
|
||||
unsigned int crc;///< Checksum, if any, for usage in the stats.
|
||||
uint32_t crc;///< Checksum, if any, for usage in the stats.
|
||||
unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp);
|
||||
|
||||
//stream delaying variables
|
||||
|
@ -103,3 +105,4 @@ namespace Mist {
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("G711mu");
|
||||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "flash/11";
|
||||
capa["methods"][0u]["priority"] = 7ll;
|
||||
capa["methods"][0u]["priority"] = 6ll;
|
||||
capa["methods"][0u]["player_url"] = "/flashplayer.swf";
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
mstime = myMeta.tracks[tid].getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()).getTime();
|
||||
|
|
|
@ -3,17 +3,26 @@
|
|||
#include <unistd.h>
|
||||
|
||||
namespace Mist {
|
||||
bool OutHLS::isReadyForPlay() {
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.fragments.size() >= 3){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///\brief Builds an index file for HTTP Live streaming.
|
||||
///\return The index file for HTTP Live Streaming.
|
||||
std::string OutHLS::liveIndex(){
|
||||
std::stringstream result;
|
||||
result << "#EXTM3U\r\n";
|
||||
int audioId = -1;
|
||||
std::string audioName;
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "AAC"){
|
||||
audioId = it->first;
|
||||
audioName = it->second.getIdentifier();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +42,7 @@ namespace Mist {
|
|||
if (audioId != -1){
|
||||
result << "_" << audioId;
|
||||
}
|
||||
result << "/index.m3u8\r\n";
|
||||
result << "/index.m3u8?sessId=" << getpid() << "\r\n";
|
||||
}
|
||||
}
|
||||
if (!vidTracks && audioId){
|
||||
|
@ -44,13 +53,13 @@ namespace Mist {
|
|||
return result.str();
|
||||
}
|
||||
|
||||
std::string OutHLS::liveIndex(int tid){
|
||||
std::string OutHLS::liveIndex(int tid, std::string & sessId) {
|
||||
updateMeta();
|
||||
std::stringstream result;
|
||||
//parse single track
|
||||
int longestFragment = 0;
|
||||
if (!myMeta.tracks[tid].fragments.size()){
|
||||
DEBUG_MSG(DLVL_FAIL, "liveIndex called with track %d, which has no fragments!", tid);
|
||||
INFO_MSG("liveIndex called with track %d, which has no fragments!", tid);
|
||||
return "";
|
||||
}
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++){
|
||||
|
@ -66,15 +75,18 @@ namespace Mist {
|
|||
std::deque<std::string> lines;
|
||||
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++){
|
||||
long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
|
||||
std::stringstream line;
|
||||
long long duration = it->getDuration();
|
||||
if (duration <= 0){
|
||||
duration = myMeta.tracks[tid].lastms - starttime;
|
||||
}
|
||||
line << "#EXTINF:" << ((duration + 500) / 1000) << ", no desc\r\n" << starttime << "_" << duration + starttime << ".ts\r\n";
|
||||
lines.push_back(line.str());
|
||||
char lineBuf[400];
|
||||
if (sessId.size()){
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts?sessId=%s\r\n", ((duration + 500) / 1000), starttime, starttime + duration, sessId.c_str());
|
||||
}else{
|
||||
snprintf(lineBuf, 400, "#EXTINF:%lld, no desc\r\n%lld_%lld.ts\r\n", ((duration + 500) / 1000), starttime, starttime + duration);
|
||||
}
|
||||
lines.push_back(lineBuf);
|
||||
}
|
||||
|
||||
unsigned int skippedLines = 0;
|
||||
if (myMeta.live){
|
||||
//only print the last segment when VoD
|
||||
|
@ -135,7 +147,7 @@ namespace Mist {
|
|||
|
||||
void OutHLS::onHTTP(){
|
||||
std::string method = H.method;
|
||||
|
||||
std::string sessId = H.GetVar("sessId");
|
||||
|
||||
if (H.url == "/crossdomain.xml"){
|
||||
H.Clean();
|
||||
|
@ -152,12 +164,24 @@ namespace Mist {
|
|||
H.Clean(); //clean for any possible next requests
|
||||
return;
|
||||
} //crossdomain.xml
|
||||
|
||||
if (H.method == "OPTIONS") {
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "application/octet-stream");
|
||||
H.SetHeader("Cache-Control", "no-cache");
|
||||
H.setCORSHeaders();
|
||||
H.SetBody("");
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
|
||||
if (H.url.find("hls") == std::string::npos){
|
||||
myConn.close();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
appleCompat = (H.GetHeader("User-Agent").find("iPad") != std::string::npos) || (H.GetHeader("User-Agent").find("iPod") != std::string::npos)|| (H.GetHeader("User-Agent").find("iPhone") != std::string::npos);
|
||||
bool VLCworkaround = false;
|
||||
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
|
||||
|
@ -167,6 +191,7 @@ namespace Mist {
|
|||
VLCworkaround = true;
|
||||
}
|
||||
}
|
||||
|
||||
initialize();
|
||||
if (H.url.find(".m3u") == std::string::npos){
|
||||
std::string tmpStr = H.getUrl().substr(5+streamName.size());
|
||||
|
@ -189,6 +214,11 @@ namespace Mist {
|
|||
selectedTracks.insert(vidTrack);
|
||||
selectedTracks.insert(audTrack);
|
||||
}
|
||||
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.codec == "ID3"){
|
||||
selectedTracks.insert(it->first);
|
||||
}
|
||||
}
|
||||
|
||||
if (myMeta.live){
|
||||
unsigned int timeout = 0;
|
||||
|
@ -202,7 +232,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
}while (myConn && seekable > 0);
|
||||
|
@ -266,7 +296,7 @@ namespace Mist {
|
|||
manifest = liveIndex();
|
||||
}else{
|
||||
int selectId = atoi(request.substr(0,request.find("/")).c_str());
|
||||
manifest = liveIndex(selectId);
|
||||
manifest = liveIndex(selectId, sessId);
|
||||
}
|
||||
H.SetBody(manifest);
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
|
|
|
@ -9,9 +9,11 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void sendTS(const char * tsData, unsigned int len=188);
|
||||
void onHTTP();
|
||||
bool isReadyForPlay();
|
||||
protected:
|
||||
bool hasSessionIDs(){return true;}
|
||||
std::string liveIndex();
|
||||
std::string liveIndex(int tid);
|
||||
std::string liveIndex(int tid, std::string & sessId);
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
unsigned int vidTrack;
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <mist/mp4_generic.h>
|
||||
#include <mist/http_parser.h>
|
||||
#include <mist/stream.h>
|
||||
#include <mist/bitfields.h>
|
||||
#include <mist/checksum.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
@ -56,11 +57,9 @@ namespace Mist {
|
|||
capa["methods"][0u]["handler"] = "http";
|
||||
capa["methods"][0u]["type"] = "html5/application/vnd.ms-ss";
|
||||
capa["methods"][0u]["priority"] = 9ll;
|
||||
capa["methods"][0u]["nolive"] = 1;
|
||||
capa["methods"][1u]["handler"] = "http";
|
||||
capa["methods"][1u]["type"] = "silverlight";
|
||||
capa["methods"][1u]["priority"] = 1ll;
|
||||
capa["methods"][1u]["nolive"] = 1;
|
||||
}
|
||||
|
||||
void OutHSS::sendNext() {
|
||||
|
@ -132,7 +131,7 @@ namespace Mist {
|
|||
myConn.close();
|
||||
break;
|
||||
}
|
||||
Util::sleep(500);
|
||||
Util::wait(500);
|
||||
updateMeta();
|
||||
}
|
||||
}while (myConn && seekable > 0);
|
||||
|
@ -201,11 +200,11 @@ namespace Mist {
|
|||
|
||||
//Wrap everything in mp4 boxes
|
||||
MP4::MFHD mfhd_box;
|
||||
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + tid);///\todo Urgent: Check this for multitrack... :P wtf... :P
|
||||
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2));
|
||||
|
||||
MP4::TFHD tfhd_box;
|
||||
tfhd_box.setFlags(MP4::tfhdSampleFlag);
|
||||
tfhd_box.setTrackID(tid);
|
||||
tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2));
|
||||
if (myMeta.tracks[tid].type == "video") {
|
||||
tfhd_box.setDefaultSampleFlags(0x00004001);
|
||||
} else {
|
||||
|
@ -254,19 +253,24 @@ namespace Mist {
|
|||
//If the stream is live, we want to have a fragref box if possible
|
||||
//////HEREHEREHERE
|
||||
if (myMeta.live) {
|
||||
MP4::UUID_TFXD tfxd_box;
|
||||
tfxd_box.setTime(keyObj.getTime());
|
||||
tfxd_box.setDuration(keyObj.getLength());
|
||||
traf_box.setContent(tfxd_box, 3);
|
||||
|
||||
MP4::UUID_TrackFragmentReference fragref_box;
|
||||
fragref_box.setVersion(1);
|
||||
fragref_box.setFragmentCount(0);
|
||||
int fragCount = 0;
|
||||
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) {
|
||||
if (myMeta.tracks[tid].keys[i].getTime() > seekTime) {
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %ld > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
DEBUG_MSG(DLVL_HIGH, "Key %d added to fragRef box, time %llu > %lld", i, myMeta.tracks[tid].keys[i].getTime(), seekTime);
|
||||
fragref_box.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
|
||||
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
|
||||
fragref_box.setFragmentCount(++fragCount);
|
||||
}
|
||||
}
|
||||
traf_box.setContent(fragref_box, 3);
|
||||
traf_box.setContent(fragref_box, 4);
|
||||
}
|
||||
|
||||
MP4::MOOF moof_box;
|
||||
|
@ -467,9 +471,4 @@ namespace Mist {
|
|||
sendHeader();
|
||||
}
|
||||
}
|
||||
|
||||
void OutHSS::initialize() {
|
||||
Output::initialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,10 +9,8 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
void initialize();/*LTS*/
|
||||
void sendHeader();
|
||||
protected:
|
||||
JSON::Value encryption;
|
||||
std::string smoothIndex();
|
||||
int canSeekms(unsigned int ms);
|
||||
int keysToSend;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "output_http.h"
|
||||
#include <mist/stream.h>
|
||||
#include <mist/checksum.h>
|
||||
#include <set>
|
||||
|
||||
namespace Mist {
|
||||
HTTPOutput::HTTPOutput(Socket::Connection & conn) : Output(conn) {
|
||||
|
@ -104,9 +105,9 @@ namespace Mist {
|
|||
}
|
||||
|
||||
//loop over the connectors
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors");
|
||||
unsigned int capa_ctr = capa.getSize();
|
||||
for (unsigned int i = 0; i < capa_ctr; ++i){
|
||||
|
@ -171,7 +172,7 @@ namespace Mist {
|
|||
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str());
|
||||
streamName = H.GetVar("stream");
|
||||
userClient.finish();
|
||||
nProxy.userClient.finish();
|
||||
statsPage.finish();
|
||||
reConnector(handler);
|
||||
H.Clean();
|
||||
|
@ -209,8 +210,19 @@ namespace Mist {
|
|||
|
||||
void HTTPOutput::onRequest(){
|
||||
while (H.Read(myConn)){
|
||||
std::string ua = H.GetHeader("User-Agent");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
if (hasSessionIDs()){
|
||||
if (H.GetVar("sessId").size()){
|
||||
std::string ua = H.GetVar("sessId");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}else{
|
||||
std::string ua = JSON::Value((long long)getpid()).asString();
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}
|
||||
}else{
|
||||
std::string ua = H.GetHeader("User-Agent") + H.GetHeader("X-Playback-Session-Id");
|
||||
crc = checksum::crc32(0, ua.data(), ua.size());
|
||||
}
|
||||
|
||||
INFO_MSG("Received request %s", H.getUrl().c_str());
|
||||
selectedTracks.clear();
|
||||
if (H.GetVar("audio") != ""){
|
||||
|
@ -239,6 +251,7 @@ namespace Mist {
|
|||
for (std::set<unsigned long>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
|
||||
selectedTracks.erase(*it);
|
||||
}
|
||||
|
||||
onHTTP();
|
||||
if (!H.bufferChunks){
|
||||
H.Clean();
|
||||
|
@ -275,9 +288,9 @@ namespace Mist {
|
|||
for (int i=0; i<20; i++){argarr[i] = 0;}
|
||||
int id = -1;
|
||||
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan prots = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("config").getMember("protocols");
|
||||
unsigned int prots_ctr = prots.getSize();
|
||||
|
||||
|
|
|
@ -248,9 +248,9 @@ namespace Mist {
|
|||
|
||||
std::string port, url_rel;
|
||||
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan prtcls = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("config").getMember("protocols");
|
||||
DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors").getMember("RTMP");
|
||||
unsigned int pro_cnt = prtcls.getSize();
|
||||
|
@ -320,11 +320,13 @@ namespace Mist {
|
|||
}
|
||||
response = "// Generating info code for stream " + streamName + "\n\nif (!mistvideo){var mistvideo = {};}\n";
|
||||
JSON::Value json_resp;
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore metaLocker(std::string("liveMeta@" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
static char liveSemName[NAME_BUFFER_SIZE];
|
||||
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
|
||||
IPC::semaphore metaLocker(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
bool metaLock = false;
|
||||
configLock.wait();
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
|
||||
DTSC::Scan strm = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName).getMember("meta");
|
||||
IPC::sharedPage streamIndex;
|
||||
if (!strm){
|
||||
|
@ -333,7 +335,7 @@ namespace Mist {
|
|||
if (Util::startInput(streamName)){
|
||||
char pageId[NAME_BUFFER_SIZE];
|
||||
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
|
||||
streamIndex.init(pageId, DEFAULT_META_PAGE_SIZE);
|
||||
streamIndex.init(pageId, DEFAULT_STRM_PAGE_SIZE);
|
||||
if (streamIndex.mapped){
|
||||
metaLock = true;
|
||||
metaLocker.wait();
|
||||
|
|
|
@ -25,13 +25,19 @@ namespace Mist {
|
|||
}
|
||||
|
||||
void OutHTTPTS::onHTTP(){
|
||||
std::string method = H.method;
|
||||
|
||||
initialize();
|
||||
H.Clean();
|
||||
H.SetHeader("Content-Type", "video/mp2t");
|
||||
H.setCORSHeaders();
|
||||
if(method == "OPTIONS" || method == "HEAD"){
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
H.StartResponse(H, myConn);
|
||||
parseData = true;
|
||||
wantRequest = false;
|
||||
H.Clean(); //clean for any possible next requests
|
||||
}
|
||||
|
||||
void OutHTTPTS::sendTS(const char * tsData, unsigned int len){
|
||||
|
|
|
@ -9,13 +9,6 @@ namespace Mist {
|
|||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendTS(const char * tsData, unsigned int len=188);
|
||||
protected:
|
||||
int keysToSend;
|
||||
long long int playUntil;
|
||||
long long unsigned int lastVid;
|
||||
long long unsigned int until;
|
||||
unsigned int vidTrack;
|
||||
unsigned int audTrack;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Mist {
|
||||
OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){}
|
||||
OutProgressiveFLV::~OutProgressiveFLV() {}
|
||||
|
||||
void OutProgressiveFLV::init(Util::Config * cfg){
|
||||
HTTPOutput::init(cfg);
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Mist {
|
|||
class OutProgressiveFLV : public HTTPOutput {
|
||||
public:
|
||||
OutProgressiveFLV(Socket::Connection & conn);
|
||||
~OutProgressiveFLV();
|
||||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace Mist {
|
||||
OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){}
|
||||
OutProgressiveMP3::~OutProgressiveMP3(){}
|
||||
|
||||
void OutProgressiveMP3::init(Util::Config * cfg){
|
||||
HTTPOutput::init(cfg);
|
||||
|
|
|
@ -5,7 +5,6 @@ namespace Mist {
|
|||
class OutProgressiveMP3 : public HTTPOutput {
|
||||
public:
|
||||
OutProgressiveMP3(Socket::Connection & conn);
|
||||
~OutProgressiveMP3();
|
||||
static void init(Util::Config * cfg);
|
||||
void onHTTP();
|
||||
void sendNext();
|
||||
|
|
|
@ -4,11 +4,11 @@ namespace Mist {
|
|||
OutRaw::OutRaw(Socket::Connection & conn) : Output(conn) {
|
||||
streamName = config->getString("streamname");
|
||||
initialize();
|
||||
selectedTracks.clear();
|
||||
std::string tracks = config->getString("tracks");
|
||||
if (tracks.size()){
|
||||
selectedTracks.clear();
|
||||
unsigned int currTrack = 0;
|
||||
//loop over tracks, add any found track IDs to selectedTracks
|
||||
if (tracks != ""){
|
||||
for (unsigned int i = 0; i < tracks.size(); ++i){
|
||||
if (tracks[i] >= '0' && tracks[i] <= '9'){
|
||||
currTrack = currTrack*10 + (tracks[i] - '0');
|
||||
|
@ -46,8 +46,7 @@ namespace Mist {
|
|||
capa["optional"]["seek"]["help"] = "The time in milliseconds to seek to, 0 by default.";
|
||||
capa["optional"]["seek"]["type"] = "int";
|
||||
capa["optional"]["seek"]["option"] = "--seek";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][0u].append("*");
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("tracks",
|
||||
|
@ -68,3 +67,4 @@ namespace Mist {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
namespace Mist {
|
||||
OutRTMP::OutRTMP(Socket::Connection & conn) : Output(conn) {
|
||||
setBlocking(true);
|
||||
while (!conn.Received().available(1537) && conn.connected()) {
|
||||
while (!conn.Received().available(1537) && conn.connected() && config->is_active) {
|
||||
conn.spool();
|
||||
}
|
||||
if (!conn){
|
||||
if (!conn || !config->is_active){
|
||||
return;
|
||||
}
|
||||
RTMPStream::handshake_in.append(conn.Received().remove(1537));
|
||||
|
@ -21,21 +21,41 @@ namespace Mist {
|
|||
|
||||
if (RTMPStream::doHandshake()) {
|
||||
conn.SendNow(RTMPStream::handshake_out);
|
||||
while (!conn.Received().available(1536) && conn.connected()) {
|
||||
while (!conn.Received().available(1536) && conn.connected() && config->is_active) {
|
||||
conn.spool();
|
||||
}
|
||||
conn.Received().remove(1536);
|
||||
RTMPStream::rec_cnt += 1536;
|
||||
DEBUG_MSG(DLVL_HIGH, "Handshake success!");
|
||||
HIGH_MSG("Handshake success");
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_DEVEL, "Handshake fail!");
|
||||
MEDIUM_MSG("Handshake fail (this is not a problem, usually)");
|
||||
}
|
||||
setBlocking(false);
|
||||
maxSkipAhead = 1500;
|
||||
minSkipAhead = 500;
|
||||
}
|
||||
|
||||
OutRTMP::~OutRTMP() {}
|
||||
bool OutRTMP::isReadyForPlay(){
|
||||
if (isPushing){
|
||||
return true;
|
||||
}
|
||||
if (myMeta.tracks.size()){
|
||||
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
|
||||
if (it->second.keys.size() >= 2){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string OutRTMP::getStatsName(){
|
||||
if (isPushing){
|
||||
return "INPUT";
|
||||
}else{
|
||||
return Output::getStatsName();
|
||||
}
|
||||
}
|
||||
|
||||
void OutRTMP::parseVars(std::string data){
|
||||
std::string varname;
|
||||
|
@ -94,7 +114,7 @@ namespace Mist {
|
|||
void OutRTMP::init(Util::Config * cfg) {
|
||||
Output::init(cfg);
|
||||
capa["name"] = "RTMP";
|
||||
capa["desc"] = "Enables the RTMP protocol which is used by Adobe Flash Player.";
|
||||
capa["desc"] = "Enables ingest and output over Adobe's RTMP protocol.";
|
||||
capa["deps"] = "";
|
||||
capa["url_rel"] = "/play/$";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
|
@ -114,13 +134,28 @@ namespace Mist {
|
|||
capa["codecs"][0u][1u].append("G711mu");
|
||||
capa["methods"][0u]["handler"] = "rtmp";
|
||||
capa["methods"][0u]["type"] = "flash/10";
|
||||
capa["methods"][0u]["priority"] = 6ll;
|
||||
capa["methods"][0u]["priority"] = 7ll;
|
||||
capa["methods"][0u]["player_url"] = "/flashplayer.swf";
|
||||
cfg->addConnectorOptions(1935, capa);
|
||||
config = cfg;
|
||||
}
|
||||
|
||||
void OutRTMP::sendNext() {
|
||||
|
||||
//If there are now more selectable tracks, select the new track and do a seek to the current timestamp
|
||||
//Set sentHeader to false to force it to send init data
|
||||
if (selectedTracks.size() < 2 && myMeta.tracks.size() > 1){
|
||||
size_t prevTrackCount = selectedTracks.size();
|
||||
selectDefaultTracks();
|
||||
if (selectedTracks.size() > prevTrackCount){
|
||||
INFO_MSG("Picked up new track - selecting it and resetting state.");
|
||||
sentHeader = false;
|
||||
seek(thisPacket.getTime());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
char rtmpheader[] = {0, //byte 0 = cs_id | ch_type
|
||||
0, 0, 0, //bytes 1-3 = timestamp
|
||||
0, 0, 0, //bytes 4-6 = length
|
||||
|
@ -312,9 +347,7 @@ namespace Mist {
|
|||
///\param messageType The type of message.
|
||||
///\param streamId The ID of the AMF stream.
|
||||
void OutRTMP::sendCommand(AMF::Object & amfReply, int messageType, int streamId) {
|
||||
#if DEBUG >= 8
|
||||
std::cerr << amfReply.Print() << std::endl;
|
||||
#endif
|
||||
HIGH_MSG("Sending: %s", amfReply.Print().c_str());
|
||||
if (messageType == 17) {
|
||||
myConn.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack()));
|
||||
} else {
|
||||
|
@ -327,38 +360,13 @@ namespace Mist {
|
|||
///\param messageType The type of message.
|
||||
///\param streamId The ID of the AMF stream.
|
||||
void OutRTMP::parseAMFCommand(AMF::Object & amfData, int messageType, int streamId) {
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "Received command: %s\n", amfData.Print().c_str());
|
||||
#endif
|
||||
#if DEBUG >= 8
|
||||
fprintf(stderr, "AMF0 command: %s\n", amfData.getContentP(0)->StrValue().c_str());
|
||||
#endif
|
||||
MEDIUM_MSG("Received command: %s", amfData.Print().c_str());
|
||||
HIGH_MSG("AMF0 command: %s", amfData.getContentP(0)->StrValue().c_str());
|
||||
if (amfData.getContentP(0)->StrValue() == "connect") {
|
||||
double objencoding = 0;
|
||||
if (amfData.getContentP(2)->getContentP("objectEncoding")) {
|
||||
objencoding = amfData.getContentP(2)->getContentP("objectEncoding")->NumValue();
|
||||
}
|
||||
#if DEBUG >= 6
|
||||
int tmpint;
|
||||
if (amfData.getContentP(2)->getContentP("videoCodecs")) {
|
||||
tmpint = (int)amfData.getContentP(2)->getContentP("videoCodecs")->NumValue();
|
||||
if (tmpint & 0x04) {
|
||||
fprintf(stderr, "Sorensen video support detected\n");
|
||||
}
|
||||
if (tmpint & 0x80) {
|
||||
fprintf(stderr, "H264 video support detected\n");
|
||||
}
|
||||
}
|
||||
if (amfData.getContentP(2)->getContentP("audioCodecs")) {
|
||||
tmpint = (int)amfData.getContentP(2)->getContentP("audioCodecs")->NumValue();
|
||||
if (tmpint & 0x04) {
|
||||
fprintf(stderr, "MP3 audio support detected\n");
|
||||
}
|
||||
if (tmpint & 0x400) {
|
||||
fprintf(stderr, "AAC audio support detected\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
app_name = amfData.getContentP(2)->getContentP("tcUrl")->StrValue();
|
||||
app_name = app_name.substr(app_name.find('/', 7) + 1);
|
||||
RTMPStream::chunk_snd_max = 4096;
|
||||
|
@ -467,7 +475,7 @@ namespace Mist {
|
|||
} //getStreamLength
|
||||
if ((amfData.getContentP(0)->StrValue() == "publish")) {
|
||||
if (amfData.getContentP(3)) {
|
||||
streamName = amfData.getContentP(3)->StrValue();
|
||||
streamName = Encodings::URL::decode(amfData.getContentP(3)->StrValue());
|
||||
|
||||
if (streamName.find('/')){
|
||||
streamName = streamName.substr(0, streamName.find('/'));
|
||||
|
@ -485,32 +493,33 @@ namespace Mist {
|
|||
|
||||
Util::sanitizeName(streamName);
|
||||
//pull the server configuration
|
||||
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
|
||||
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
|
||||
configLock.wait();
|
||||
|
||||
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName);
|
||||
if (streamCfg){
|
||||
if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str());
|
||||
FAIL_MSG("Push rejected - stream %s not a push-able stream. (%s != push://*)", streamName.c_str(), streamCfg.getMember("source").asString().c_str());
|
||||
myConn.close();
|
||||
}else{
|
||||
std::string source = streamCfg.getMember("source").asString().substr(7);
|
||||
std::string IP = source.substr(0, source.find('@'));
|
||||
if (IP != ""){
|
||||
if (!myConn.isAddress(IP)){
|
||||
DEBUG_MSG(DLVL_FAIL, "Push from %s to %s rejected - source host not whitelisted", getConnectedHost().c_str(), streamName.c_str());
|
||||
FAIL_MSG("Push from %s to %s rejected - source host not whitelisted", getConnectedHost().c_str(), streamName.c_str());
|
||||
myConn.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}else{
|
||||
DEBUG_MSG(DLVL_FAIL, "Push from %s rejected - stream '%s' not configured.", getConnectedHost().c_str(), streamName.c_str());
|
||||
FAIL_MSG("Push from %s rejected - stream '%s' not configured.", getConnectedHost().c_str(), streamName.c_str());
|
||||
myConn.close();
|
||||
}
|
||||
configLock.post();
|
||||
configLock.close();
|
||||
if (!myConn){return;}//do not initialize if rejected
|
||||
isPushing = true;
|
||||
initialize();
|
||||
}
|
||||
//send a _result reply
|
||||
|
@ -698,16 +707,22 @@ namespace Mist {
|
|||
}
|
||||
return;
|
||||
} //seek
|
||||
if (amfData.getContentP(0)->StrValue() == "_error") {
|
||||
WARN_MSG("Received error response: %s", amfData.Print().c_str());
|
||||
return;
|
||||
}
|
||||
if ((amfData.getContentP(0)->StrValue() == "_result") || (amfData.getContentP(0)->StrValue() == "onFCPublish") || (amfData.getContentP(0)->StrValue() == "onStatus")) {
|
||||
//Results are ignored. We don't really care.
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "AMF0 command not processed!\n%s\n", amfData.Print().c_str());
|
||||
#endif
|
||||
WARN_MSG("AMF0 command not processed: %s", amfData.Print().c_str());
|
||||
//send a _result reply
|
||||
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
|
||||
amfReply.addContent(AMF::Object("", "_error")); //result success
|
||||
amfReply.addContent(amfData.getContent(1)); //same transaction ID
|
||||
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info
|
||||
amfReply.addContent(AMF::Object("Command not implemented or recognized", "")); //stream ID?
|
||||
amfReply.addContent(AMF::Object("", amfData.getContentP(0)->StrValue())); //null - command info
|
||||
amfReply.addContent(AMF::Object("", "Command not implemented or recognized")); //stream ID?
|
||||
sendCommand(amfReply, messageType, streamId);
|
||||
} //parseAMFCommand
|
||||
|
||||
|
@ -734,9 +749,7 @@ namespace Mist {
|
|||
|
||||
switch (next.msg_type_id) {
|
||||
case 0: //does not exist
|
||||
#if DEBUG >= 2
|
||||
fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n");
|
||||
#endif
|
||||
WARN_MSG("UNKN: Received a zero-type message. Possible data corruption? Aborting!");
|
||||
while (inputBuffer.size()) {
|
||||
inputBuffer.get().clear();
|
||||
}
|
||||
|
@ -745,20 +758,14 @@ namespace Mist {
|
|||
break; //happens when connection breaks unexpectedly
|
||||
case 1: //set chunk size
|
||||
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
|
||||
break;
|
||||
case 2: //abort message - we ignore this one
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Abort message\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Abort message");
|
||||
//4 bytes of stream id to drop
|
||||
break;
|
||||
case 3: //ack
|
||||
#if DEBUG >= 8
|
||||
fprintf(stderr, "CTRL: Acknowledgement\n");
|
||||
#endif
|
||||
VERYHIGH_MSG("CTRL: Acknowledgement");
|
||||
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
|
||||
RTMPStream::snd_window_at = RTMPStream::snd_cnt;
|
||||
break;
|
||||
|
@ -773,49 +780,43 @@ namespace Mist {
|
|||
//6 = pingrequest, 4 bytes data
|
||||
//7 = pingresponse, 4 bytes data
|
||||
//we don't need to process this
|
||||
#if DEBUG >= 5
|
||||
short int ucmtype = ntohs(*(short int *)next.data.c_str());
|
||||
switch (ucmtype) {
|
||||
case 0:
|
||||
fprintf(stderr, "CTRL: UCM StreamBegin %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamBegin %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 1:
|
||||
fprintf(stderr, "CTRL: UCM StreamEOF %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamEOF %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 2:
|
||||
fprintf(stderr, "CTRL: UCM StreamDry %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamDry %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 3:
|
||||
fprintf(stderr, "CTRL: UCM SetBufferLength %i %i\n", ntohl(*((int *)(next.data.c_str() + 2))), ntohl(*((int *)(next.data.c_str() + 6))));
|
||||
MEDIUM_MSG("CTRL: UCM SetBufferLength %i %i", ntohl(*((int *)(next.data.c_str() + 2))), ntohl(*((int *)(next.data.c_str() + 6))));
|
||||
break;
|
||||
case 4:
|
||||
fprintf(stderr, "CTRL: UCM StreamIsRecorded %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM StreamIsRecorded %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 6:
|
||||
fprintf(stderr, "CTRL: UCM PingRequest %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM PingRequest %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
case 7:
|
||||
fprintf(stderr, "CTRL: UCM PingResponse %i\n", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
MEDIUM_MSG("CTRL: UCM PingResponse %i", ntohl(*((int *)(next.data.c_str() + 2))));
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype);
|
||||
MEDIUM_MSG("CTRL: UCM Unknown (%hi)", ucmtype);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
case 5: //window size of other end
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Window size\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Window size");
|
||||
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
|
||||
RTMPStream::rec_window_at = RTMPStream::rec_cnt;
|
||||
myConn.SendNow(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
|
||||
break;
|
||||
case 6:
|
||||
#if DEBUG >= 5
|
||||
fprintf(stderr, "CTRL: Set peer bandwidth\n");
|
||||
#endif
|
||||
MEDIUM_MSG("CTRL: Set peer bandwidth");
|
||||
//4 bytes window size, 1 byte limit type (ignored)
|
||||
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str());
|
||||
myConn.SendNow(RTMPStream::SendCTL(5, RTMPStream::snd_window_size)); //send window acknowledgement size (msg 5)
|
||||
|
@ -838,32 +839,31 @@ namespace Mist {
|
|||
}
|
||||
JSON::Value pack_out = F.toJSON(myMeta, *amf_storage, next.cs_id*3 + (F.data[0] == 0x09 ? 0 : (F.data[0] == 0x08 ? 1 : 2) ));
|
||||
if ( !pack_out.isNull()){
|
||||
if (!userClient.getData()){
|
||||
if (!nProxy.userClient.getData()){
|
||||
char userPageName[NAME_BUFFER_SIZE];
|
||||
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
|
||||
userClient = IPC::sharedClient(userPageName, 30, true);
|
||||
nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
|
||||
}
|
||||
continueNegotiate(pack_out["trackid"].asInt());
|
||||
nProxy.streamName = streamName;
|
||||
bufferLivePacket(pack_out);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 15:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 data message");
|
||||
MEDIUM_MSG("Received AMF3 data message");
|
||||
break;
|
||||
case 16:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 shared object");
|
||||
MEDIUM_MSG("Received AMF3 shared object");
|
||||
break;
|
||||
case 17: {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 command message");
|
||||
MEDIUM_MSG("Received AMF3 command message");
|
||||
if (next.data[0] != 0) {
|
||||
next.data = next.data.substr(1);
|
||||
amf3data = AMF::parse3(next.data);
|
||||
#if DEBUG >= 5
|
||||
amf3data.Print();
|
||||
#endif
|
||||
MEDIUM_MSG("AMF3: %s", amf3data.Print().c_str());
|
||||
} else {
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3-0 command message");
|
||||
MEDIUM_MSG("Received AMF3-0 command message");
|
||||
next.data = next.data.substr(1);
|
||||
amfdata = AMF::parse(next.data);
|
||||
parseAMFCommand(amfdata, 17, next.msg_stream_id);
|
||||
|
@ -871,7 +871,7 @@ namespace Mist {
|
|||
}
|
||||
break;
|
||||
case 19:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received AMF0 shared object");
|
||||
MEDIUM_MSG("Received AMF0 shared object");
|
||||
break;
|
||||
case 20: { //AMF0 command message
|
||||
amfdata = AMF::parse(next.data);
|
||||
|
@ -879,10 +879,10 @@ namespace Mist {
|
|||
}
|
||||
break;
|
||||
case 22:
|
||||
DEBUG_MSG(DLVL_MEDIUM, "Received aggregate message");
|
||||
MEDIUM_MSG("Received aggregate message");
|
||||
break;
|
||||
default:
|
||||
DEBUG_MSG(DLVL_FAIL, "Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||
FAIL_MSG("Unknown chunk received! Probably protocol corruption, stopping parsing of incoming data.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,19 @@ namespace Mist {
|
|||
class OutRTMP : public Output {
|
||||
public:
|
||||
OutRTMP(Socket::Connection & conn);
|
||||
~OutRTMP();
|
||||
static void init(Util::Config * cfg);
|
||||
void onRequest();
|
||||
void sendNext();
|
||||
void sendHeader();
|
||||
bool isReadyForPlay();
|
||||
protected:
|
||||
bool isPushing;
|
||||
void parseVars(std::string data);
|
||||
std::string app_name;
|
||||
void parseChunk(Socket::Buffer & inputBuffer);
|
||||
void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId);
|
||||
void sendCommand(AMF::Object & amfReply, int messageType, int streamId);
|
||||
virtual std::string getStatsName();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -40,17 +40,16 @@ namespace Mist {
|
|||
capa["required"]["streamname"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports.";
|
||||
capa["required"]["streamname"]["type"] = "str";
|
||||
capa["required"]["streamname"]["option"] = "--stream";
|
||||
capa["required"]["streamname"]["short"] = "s";
|
||||
capa["optional"]["tracks"]["name"] = "Tracks";
|
||||
capa["optional"]["tracks"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
|
||||
capa["optional"]["tracks"]["type"] = "str";
|
||||
capa["optional"]["tracks"]["option"] = "--tracks";
|
||||
capa["optional"]["tracks"]["short"] = "t";
|
||||
capa["optional"]["tracks"]["default"] = "";
|
||||
capa["codecs"][0u][0u].append("H264");
|
||||
capa["codecs"][0u][1u].append("AAC");
|
||||
capa["codecs"][0u][1u].append("MP3");
|
||||
cfg->addOption("streamname",
|
||||
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
|
||||
cfg->addOption("tracks",
|
||||
JSON::fromString("{\"arg\":\"string\",\"value\":[\"\"],\"short\": \"t\",\"long\":\"tracks\",\"help\":\"The track IDs of the stream that this connector will transmit separated by spaces.\"}"));
|
||||
cfg->addConnectorOptions(8888, capa);
|
||||
config = cfg;
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Mist {
|
|||
|
||||
if (packData.getBytesFree() == 184){
|
||||
packData.clear();
|
||||
packData.setPID(0x100 - 1 + thisPacket.getTrackId());
|
||||
packData.setPID(thisPacket.getTrackId());
|
||||
packData.setContinuityCounter(++contCounters[packData.getPID()]);
|
||||
if (first[thisPacket.getTrackId()]){
|
||||
packData.setUnitStart(1);
|
||||
|
@ -125,7 +125,7 @@ namespace Mist {
|
|||
break;
|
||||
}
|
||||
if (alreadySent + 4 > watKunnenWeIn1Ding){
|
||||
nalLead = 4 - watKunnenWeIn1Ding-alreadySent;
|
||||
nalLead = 4 - (watKunnenWeIn1Ding-alreadySent);
|
||||
fillPacket("\000\000\000\001",watKunnenWeIn1Ding-alreadySent);
|
||||
i += watKunnenWeIn1Ding-alreadySent;
|
||||
alreadySent += watKunnenWeIn1Ding-alreadySent;
|
||||
|
|
Loading…
Add table
Reference in a new issue