Backported various little edits from Pro edition.

This commit is contained in:
Thulinma 2016-05-30 15:17:54 +02:00
parent ef9938da0c
commit 4c9c6fa7ba
78 changed files with 2334 additions and 1266 deletions

1
.gitignore vendored
View file

@ -55,4 +55,5 @@ build.ninja
rules.ninja rules.ninja
.ninja_log .ninja_log
.ninja_deps .ninja_deps
aes_ctr128

View file

@ -103,6 +103,7 @@ set(libHeaders
${SOURCE_DIR}/lib/defines.h ${SOURCE_DIR}/lib/defines.h
${SOURCE_DIR}/lib/dtsc.h ${SOURCE_DIR}/lib/dtsc.h
${SOURCE_DIR}/lib/flv_tag.h ${SOURCE_DIR}/lib/flv_tag.h
${SOURCE_DIR}/lib/h264.h
${SOURCE_DIR}/lib/http_parser.h ${SOURCE_DIR}/lib/http_parser.h
${SOURCE_DIR}/lib/json.h ${SOURCE_DIR}/lib/json.h
${SOURCE_DIR}/lib/mp4_adobe.h ${SOURCE_DIR}/lib/mp4_adobe.h
@ -120,6 +121,7 @@ set(libHeaders
${SOURCE_DIR}/lib/timing.h ${SOURCE_DIR}/lib/timing.h
${SOURCE_DIR}/lib/tinythread.h ${SOURCE_DIR}/lib/tinythread.h
${SOURCE_DIR}/lib/ts_packet.h ${SOURCE_DIR}/lib/ts_packet.h
${SOURCE_DIR}/lib/util.h
${SOURCE_DIR}/lib/vorbis.h ${SOURCE_DIR}/lib/vorbis.h
) )
@ -136,6 +138,7 @@ set(libSources
${SOURCE_DIR}/lib/dtsc.cpp ${SOURCE_DIR}/lib/dtsc.cpp
${SOURCE_DIR}/lib/dtscmeta.cpp ${SOURCE_DIR}/lib/dtscmeta.cpp
${SOURCE_DIR}/lib/flv_tag.cpp ${SOURCE_DIR}/lib/flv_tag.cpp
${SOURCE_DIR}/lib/h264.cpp
${SOURCE_DIR}/lib/http_parser.cpp ${SOURCE_DIR}/lib/http_parser.cpp
${SOURCE_DIR}/lib/json.cpp ${SOURCE_DIR}/lib/json.cpp
${SOURCE_DIR}/lib/mp4_adobe.cpp ${SOURCE_DIR}/lib/mp4_adobe.cpp
@ -153,6 +156,7 @@ set(libSources
${SOURCE_DIR}/lib/timing.cpp ${SOURCE_DIR}/lib/timing.cpp
${SOURCE_DIR}/lib/tinythread.cpp ${SOURCE_DIR}/lib/tinythread.cpp
${SOURCE_DIR}/lib/ts_packet.cpp ${SOURCE_DIR}/lib/ts_packet.cpp
${SOURCE_DIR}/lib/util.cpp
${SOURCE_DIR}/lib/vorbis.cpp ${SOURCE_DIR}/lib/vorbis.cpp
) )
@ -245,9 +249,9 @@ makeInput(Buffer buffer)
######################################## ########################################
macro(makeOutput outputName format) macro(makeOutput outputName format)
#Parse all extra arguments, for http and ts flags #Parse all extra arguments, for http and ts flags
SET (tsBaseClass Output)
if (";${ARGN};" MATCHES ";http;") if (";${ARGN};" MATCHES ";http;")
SET(httpOutput src/output/output_http.cpp) SET(httpOutput src/output/output_http.cpp)
SET(tsBaseClass Output)
if (";${ARGN};" MATCHES ";ts;") if (";${ARGN};" MATCHES ";ts;")
SET(tsBaseClass HTTPOutput) SET(tsBaseClass HTTPOutput)
endif() endif()

View file

@ -228,7 +228,7 @@ TAB_SIZE = 2
# "Side Effects:". You can put \n's in the value part of an alias to insert # "Side Effects:". You can put \n's in the value part of an alias to insert
# newlines. # 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). # 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" # A mapping has the form "name=value". For example adding "class=itcl::class"

View file

@ -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
)

View file

@ -15,7 +15,7 @@ namespace Bit{
//Host to binary/binary to host functions - similar to kernel ntoh/hton functions. //Host to binary/binary to host functions - similar to kernel ntoh/hton functions.
/// Retrieves a short in network order from the pointer p. /// 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]; return ((unsigned short)p[0] << 8) | p[1];
} }

View file

@ -32,6 +32,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <fstream> #include <fstream>
#include <dirent.h> //for getMyExec #include <dirent.h> //for getMyExec
#include "procs.h"
bool Util::Config::is_active = false; bool Util::Config::is_active = false;
unsigned int Util::Config::printDebugLevel = DEBUG;// unsigned int Util::Config::printDebugLevel = DEBUG;//
@ -69,8 +70,6 @@ Util::Config::Config(std::string cmd) {
/// { /// {
/// "short":"o", //The short option letter /// "short":"o", //The short option letter
/// "long":"onName", //The long option /// "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. /// "arg":"integer", //The type of argument, if required.
/// "value":[], //The default value(s) for this option if it is not given on the commandline. /// "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. /// "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")) { if (it->isMember("long")) {
long_count++; long_count++;
} }
if (it->isMember("long_off")) {
long_count++;
}
} }
} }
@ -110,12 +106,6 @@ void Util::Config::printHelp(std::ostream & output) {
longest = current; longest = current;
} }
current = 0; 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) { if (current > longest) {
longest = current; longest = current;
} }
@ -158,26 +148,6 @@ void Util::Config::printHelp(std::ostream & output) {
output << f << (*it)["help"].asString() << std::endl; 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")) { if (it->isMember("arg_num")) {
f = it.key(); f = it.key();
while (f.size() < longest) { while (f.size() < longest) {
@ -204,12 +174,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
shortopts += ":"; shortopts += ":";
} }
} }
if (it->isMember("short_off")) {
shortopts += (*it)["short_off"].asString();
if (it->isMember("arg")) {
shortopts += ":";
}
}
if (it->isMember("long")) { if (it->isMember("long")) {
longOpts[long_i].name = (*it)["long"].asStringRef().c_str(); longOpts[long_i].name = (*it)["long"].asStringRef().c_str();
longOpts[long_i].val = (*it)["short"].asString()[0]; longOpts[long_i].val = (*it)["short"].asString()[0];
@ -218,14 +182,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
} }
long_i++; 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->isMember("arg_num") && !(it->isMember("value") && (*it)["value"].size())) {
if ((*it)["arg_num"].asInt() > arg_count) { if ((*it)["arg_num"].asInt() > arg_count) {
arg_count = (*it)["arg_num"].asInt(); arg_count = (*it)["arg_num"].asInt();
@ -263,9 +219,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
} }
break; break;
} }
if (it->isMember("short_off") && (*it)["short_off"].asString()[0] == opt) {
(*it)["value"].append((long long int)0);
}
} }
break; break;
} }
@ -289,6 +242,10 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) {
return true; 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. /// 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. /// 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) { JSON::Value & Util::Config::getOption(std::string optname, bool asArray) {
@ -298,13 +255,19 @@ JSON::Value & Util::Config::getOption(std::string optname, bool asArray) {
} }
if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) { if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) {
vals[optname]["value"].append(JSON::Value()); vals[optname]["value"].append(JSON::Value());
vals[optname]["value"].shrink(0);
} }
if (asArray) { if (asArray) {
return vals[optname]["value"]; return vals[optname]["value"];
} else { } else {
int n = vals[optname]["value"].size(); int n = vals[optname]["value"].size();
if (!n){
static JSON::Value empty = "";
return empty;
}else{
return vals[optname]["value"][n - 1]; return vals[optname]["value"][n - 1];
} }
}
} }
/// Returns the current value of an option or default if none was set as a string. /// Returns the current value of an option or default if none was set as a string.
@ -341,6 +304,7 @@ static void callThreadCallback(void * cDataArg) {
} }
int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { 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()) { while (is_active && server_socket.connected()) {
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()) { //check if the new connection is valid 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::sleep(10); //sleep 10ms
} }
} }
Util::Procs::socketList.erase(server_socket.getSocket());
server_socket.close(); server_socket.close();
return 0; return 0;
} }
int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { 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()) { while (is_active && server_socket.connected()) {
Socket::Connection S = server_socket.accept(); Socket::Connection S = server_socket.accept();
if (S.connected()) { //check if the new connection is valid 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::sleep(10); //sleep 10ms
} }
} }
Util::Procs::socketList.erase(server_socket.getSocket());
server_socket.close(); server_socket.close();
return 0; return 0;
} }
@ -385,8 +352,8 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)) {
if (vals.isMember("socket")) { if (vals.isMember("socket")) {
server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); server_socket = Socket::Server(Util::getTmpFolder() + getString("socket"));
} }
if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { if (vals.isMember("port") && vals.isMember("interface")) {
server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); server_socket = Socket::Server(getInteger("port"), getString("interface"), false);
} }
if (!server_socket.connected()) { if (!server_socket.connected()) {
DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); 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")) { if (vals.isMember("socket")) {
server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); server_socket = Socket::Server(Util::getTmpFolder() + getString("socket"));
} }
if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { if (vals.isMember("port") && vals.isMember("interface")) {
server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); server_socket = Socket::Server(getInteger("port"), getString("interface"), false);
} }
if (!server_socket.connected()) { if (!server_socket.connected()) {
DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); 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: /// Activated the stored config. This will:
/// - Drop permissions to the stored "username", if any. /// - Drop permissions to the stored "username", if any.
/// - Daemonize the process if "daemonize" exists and is true.
/// - Set is_active to true. /// - Set is_active to true.
/// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals. /// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals.
void Util::Config::activate() { void Util::Config::activate() {
@ -424,14 +390,6 @@ void Util::Config::activate() {
setUser(getString("username")); setUser(getString("username"));
vals.removeMember("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 new_action;
struct sigaction cur_action; struct sigaction cur_action;
new_action.sa_sigaction = signal_handler; new_action.sa_sigaction = signal_handler;
@ -476,33 +434,78 @@ void Util::Config::signal_handler(int signum, siginfo_t * sigInfo, void * ignore
} }
} //signal_handler } //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. /// 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. /// Besides the options addBasicConnectorOptions adds, this function also adds port and interface options.
void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { 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"]["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"]["type"] = "uint";
capabilities["optional"]["port"]["short"] = "p";
capabilities["optional"]["port"]["option"] = "--port"; 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"]["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"]["option"] = "--interface";
capabilities["optional"]["interface"]["short"] = "i";
capabilities["optional"]["interface"]["type"] = "str"; capabilities["optional"]["interface"]["type"] = "str";
addBasicConnectorOptions(capabilities); 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. /// Adds the default connector options. Also updates the capabilities structure with the default options.
void Util::Config::addBasicConnectorOptions(JSON::Value & capabilities) { 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"]["name"] = "Username";
capabilities["optional"]["username"]["help"] = "Username to drop privileges to - default if unprovided means do not drop privileges"; 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"]["option"] = "--username";
capabilities["optional"]["username"]["short"] = "u";
capabilities["optional"]["username"]["default"] = "root";
capabilities["optional"]["username"]["type"] = "str"; capabilities["optional"]["username"]["type"] = "str";
addOptionsFromCapabilities(capabilities);
if (capabilities.isMember("socket")) { JSON::Value option;
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();
option["long"] = "json"; option["long"] = "json";
option["short"] = "j"; option["short"] = "j";
option["help"] = "Output connector info in JSON format, then exit."; 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));
}
}

View file

@ -30,6 +30,7 @@ namespace Util {
void addOption(std::string optname, JSON::Value option); void addOption(std::string optname, JSON::Value option);
void printHelp(std::ostream & output); void printHelp(std::ostream & output);
bool parseArgs(int & argc, char ** & argv); bool parseArgs(int & argc, char ** & argv);
bool hasOption(const std::string & optname);
JSON::Value & getOption(std::string optname, bool asArray = false); JSON::Value & getOption(std::string optname, bool asArray = false);
std::string getString(std::string optname); std::string getString(std::string optname);
long long int getInteger(std::string optname); long long int getInteger(std::string optname);
@ -40,6 +41,7 @@ namespace Util {
int serveThreadedSocket(int (*callback)(Socket::Connection & S)); int serveThreadedSocket(int (*callback)(Socket::Connection & S));
int serveForkedSocket(int (*callback)(Socket::Connection & S)); int serveForkedSocket(int (*callback)(Socket::Connection & S));
int servePlainSocket(int (*callback)(Socket::Connection & S)); int servePlainSocket(int (*callback)(Socket::Connection & S));
void addOptionsFromCapabilities(const JSON::Value & capabilities);
void addBasicConnectorOptions(JSON::Value & capabilities); void addBasicConnectorOptions(JSON::Value & capabilities);
void addConnectorOptions(int port, 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. /// Will set the active user to the named username.
void setUser(std::string user); void setUser(std::string user);
/// Will turn the current process into a daemon.
void Daemonize(bool notClose = false);
} }

View file

@ -57,11 +57,28 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", "
#endif #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. /// The size used for stream header pages under Windows, where they cannot be size-detected.
#define DEFAULT_META_PAGE_SIZE 16 * 1024 * 1024 #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. /// 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. /// The size used for server configuration pages.
#define DEFAULT_CONF_PAGE_SIZE 4 * 1024 * 1024 #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_STREAM_INDEX "MstSTRM%s" //%s stream name
#define SHM_TRACK_META "MstTRAK%s@%lu" //%s stream name, %lu track ID #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 "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_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page #
#define SHM_STATISTICS "MstSTAT" #define SHM_STATISTICS "MstSTAT"
#define SHM_USERS "MstUSER%s" //%s stream name #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 NAME_BUFFER_SIZE 200 //char buffer size for snprintf'ing shm filenames
#define SIMUL_TRACKS 10 #define SIMUL_TRACKS 10

View file

@ -9,6 +9,7 @@
char DTSC::Magic_Header[] = "DTSC"; char DTSC::Magic_Header[] = "DTSC";
char DTSC::Magic_Packet[] = "DTPD"; char DTSC::Magic_Packet[] = "DTPD";
char DTSC::Magic_Packet2[] = "DTP2"; char DTSC::Magic_Packet2[] = "DTP2";
char DTSC::Magic_Command[] = "DTCM";
DTSC::File::File() { DTSC::File::File() {
F = 0; F = 0;
@ -32,8 +33,7 @@ DTSC::File & DTSC::File::operator =(const File & rhs) {
if (rhs.myPack) { if (rhs.myPack) {
myPack = rhs.myPack; myPack = rhs.myPack;
} }
metaStorage = rhs.metaStorage; metadata = rhs.metadata;
metadata = metaStorage;
currtime = rhs.currtime; currtime = rhs.currtime;
lastreadpos = rhs.lastreadpos; lastreadpos = rhs.lastreadpos;
headerSize = rhs.headerSize; headerSize = rhs.headerSize;
@ -67,7 +67,7 @@ DTSC::File::File(std::string filename, bool create) {
} }
created = create; created = create;
if (!F) { 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; return;
} }
fseek(F, 0, SEEK_END); fseek(F, 0, SEEK_END);
@ -83,7 +83,7 @@ DTSC::File::File(std::string filename, bool create) {
return; return;
} }
if (memcmp(buffer, DTSC::Magic_Header, 4) != 0) { 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()); DEBUG_MSG(DLVL_ERROR, "%s is not a valid DTSC file", filename.c_str());
fclose(F); fclose(F);
F = 0; F = 0;
@ -113,8 +113,7 @@ DTSC::File::File(std::string filename, bool create) {
fseek(F, 0, SEEK_SET); fseek(F, 0, SEEK_SET);
File Fhead(filename + ".dtsh"); File Fhead(filename + ".dtsh");
if (Fhead) { if (Fhead) {
metaStorage = Fhead.metaStorage; metadata = Fhead.metadata;
metadata = metaStorage;
} }
} }
currframe = 0; currframe = 0;
@ -346,8 +345,9 @@ void DTSC::File::seekNext() {
} }
void DTSC::File::parseNext(){ void DTSC::File::parseNext(){
char header_buffer[4] = {0, 0, 0, 0};
lastreadpos = ftell(F); lastreadpos = ftell(F);
if (fread(buffer, 4, 1, F) != 1) { if (fread(header_buffer, 4, 1, F) != 1) {
if (feof(F)) { if (feof(F)) {
DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos); DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos);
} else { } else {
@ -356,55 +356,26 @@ void DTSC::File::parseNext(){
myPack.null(); myPack.null();
return; 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; 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; version = 1;
} }
if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0) { if (memcmp(header_buffer, DTSC::Magic_Packet2, 4) == 0) {
version = 2; version = 2;
} }
if (version == 0) { 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(); myPack.null();
return; return;
} }
if (fread(buffer, 4, 1, F) != 1) { 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(); myPack.null();
return; return;
} }
long packSize = ntohl(((unsigned long *)buffer)[0]); long packSize = ntohl(((unsigned long *)buffer)[0]);
char * packBuffer = (char *)malloc(packSize + 8); char * packBuffer = (char *)malloc(packSize + 8);
if (version == 1) { memcpy(packBuffer, header_buffer, 4);
memcpy(packBuffer, "DTPD", 4);
} else {
memcpy(packBuffer, "DTP2", 4);
}
memcpy(packBuffer + 4, buffer, 4); memcpy(packBuffer + 4, buffer, 4);
if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) { if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) {
DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); 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()) { if (!forceSeek && myPack && ms >= myPack.getTime() && trackNo >= myPack.getTrackId()) {
tmpPos.seekTime = myPack.getTime(); tmpPos.seekTime = myPack.getTime();
tmpPos.bytePos = getBytePos(); tmpPos.bytePos = getBytePos();
/*
if (trackNo == myPack.getTrackId()){
tmpPos.bytePos += myPack.getDataLen();
}
*/
} else { } else {
tmpPos.seekTime = 0; tmpPos.seekTime = 0;
tmpPos.bytePos = 0; tmpPos.bytePos = 0;

View file

@ -34,6 +34,7 @@ namespace DTSC {
extern char Magic_Header[]; ///< The magic bytes for a DTSC header 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_Packet[]; ///< The magic bytes for a DTSC packet
extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2 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. ///\brief A simple structure used for ordering byte seek positions.
struct seekPos { struct seekPos {
@ -61,7 +62,8 @@ namespace DTSC {
DTSC_INVALID, DTSC_INVALID,
DTSC_HEAD, DTSC_HEAD,
DTSC_V1, DTSC_V1,
DTSC_V2 DTSC_V2,
DTCM
}; };
/// This class allows scanning through raw binary format DTSC data. /// This class allows scanning through raw binary format DTSC data.
@ -107,6 +109,7 @@ namespace DTSC {
void operator = (const Packet & rhs); void operator = (const Packet & rhs);
operator bool() const; operator bool() const;
packType getVersion() const; packType getVersion() const;
void reInit(Socket::Connection & src);
void reInit(const char * data_, unsigned int len, bool noCopy = false); 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 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; void getString(const char * identifier, char *& result, unsigned int & len) const;
@ -205,6 +208,17 @@ namespace DTSC {
char * getData(); char * getData();
void toPrettyString(std::ostream & str, int indent = 0); void toPrettyString(std::ostream & str, int indent = 0);
private: 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. ///\brief Data storage for this Key.
/// ///
/// - 5 bytes: MSB storage of the position of the first packet of this keyframe within the file. /// - 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 number of this keyframe.
/// - 2 bytes: MSB storage of the amount of parts in 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. /// - 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. ///\brief Basic class for storage of data associated with fragments.
@ -229,13 +244,24 @@ namespace DTSC {
char * getData(); char * getData();
void toPrettyString(std::ostream & str, int indent = 0); void toPrettyString(std::ostream & str, int indent = 0);
private: 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) /// - 4 bytes: duration (in milliseconds)
/// - 1 byte: length (amount of keyframes) /// - 1 byte: length (amount of keyframes)
/// - 2 bytes: number of first keyframe in fragment /// - 2 bytes: number of first keyframe in fragment
/// - 4 bytes: size of fragment in bytes /// - 4 bytes: size of fragment in bytes
char data[11]; #endif
char data[PACKED_FRAGMENT_SIZE];
}; };
///\brief Class for storage of track data ///\brief Class for storage of track data
@ -249,10 +275,10 @@ namespace DTSC {
return (parts.size() && keySizes.size() && (keySizes.size() == keys.size())); 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); 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(); int getSendLen(bool skipDynamic = false);
void send(Socket::Connection & conn); void send(Socket::Connection & conn, bool skipDynamic = false);
void writeTo(char *& p); void writeTo(char *& p);
JSON::Value toJSON(); JSON::Value toJSON(bool skipDynamic = false);
std::deque<Fragment> fragments; std::deque<Fragment> fragments;
std::deque<Key> keys; std::deque<Key> keys;
std::deque<unsigned long> keySizes; std::deque<unsigned long> keySizes;
@ -302,8 +328,8 @@ namespace DTSC {
void updatePosOverride(DTSC::Packet & pack, unsigned long bpos); void updatePosOverride(DTSC::Packet & pack, unsigned long bpos);
void update(JSON::Value & pack, unsigned long segment_size = 5000); 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); 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(); unsigned int getSendLen(bool skipDynamic = false, std::set<unsigned long> selectedTracks = std::set<unsigned long>());
void send(Socket::Connection & conn); void send(Socket::Connection & conn, bool skipDynamic = false, std::set<unsigned long> selectedTracks = std::set<unsigned long>());
void writeTo(char * p); void writeTo(char * p);
JSON::Value toJSON(); JSON::Value toJSON();
void reset(); void reset();
@ -348,7 +374,6 @@ namespace DTSC {
long int endPos; long int endPos;
void readHeader(int pos); void readHeader(int pos);
DTSC::Packet myPack; DTSC::Packet myPack;
JSON::Value metaStorage;
Meta metadata; Meta metadata;
std::map<unsigned int, std::string> trackMapping; std::map<unsigned int, std::string> trackMapping;
long long int currtime; long long int currtime;

View file

@ -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 ///\brief Initializes a packet with new data
///\param data_ The new data for the packet ///\param data_ The new data for the packet
///\param len The length of the data pointed to by data_ ///\param len The length of the data pointed to by data_
@ -158,12 +184,16 @@ namespace DTSC {
} else { } else {
if (!memcmp(data, Magic_Header, 4)) { if (!memcmp(data, Magic_Header, 4)) {
version = DTSC_HEAD; version = DTSC_HEAD;
} else {
if (!memcmp(data, Magic_Command, 4)) {
version = DTCM;
} else { } else {
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header"); DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header");
return; return;
} }
} }
} }
}
} else { } else {
DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4"); DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4");
return; return;
@ -248,7 +278,7 @@ namespace DTSC {
if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) { if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) {
p++; p++;
//object, scan contents //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) { if (p + 2 >= max) {
return 0;//out of packet! return 0;//out of packet!
} }
@ -264,7 +294,7 @@ namespace DTSC {
if (p[0] == DTSC_ARR) { if (p[0] == DTSC_ARR) {
p++; p++;
//array, scan contents //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... //search through contents...
p = skipDTSC(p, max); p = skipDTSC(p, max);
if (!p) { if (!p) {
@ -844,53 +874,93 @@ namespace DTSC {
///\brief Returns the byteposition of a keyframe ///\brief Returns the byteposition of a keyframe
unsigned long long Key::getBpos() { 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]); 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) { void Key::setBpos(unsigned long long newBpos) {
#ifdef BIGMETA
Bit::htobll(data, newBpos);
#else
data[4] = newBpos & 0xFF; data[4] = newBpos & 0xFF;
data[3] = (newBpos >> 8) & 0xFF; data[3] = (newBpos >> 8) & 0xFF;
data[2] = (newBpos >> 16) & 0xFF; data[2] = (newBpos >> 16) & 0xFF;
data[1] = (newBpos >> 24) & 0xFF; data[1] = (newBpos >> 24) & 0xFF;
data[0] = (newBpos >> 32) & 0xFF; data[0] = (newBpos >> 32) & 0xFF;
#endif
} }
unsigned long Key::getLength() { unsigned long Key::getLength() {
#ifdef BIGMETA
return Bit::btoh24(data+8);
#else
return Bit::btoh24(data+5); return Bit::btoh24(data+5);
#endif
} }
void Key::setLength(unsigned long newLength) { void Key::setLength(unsigned long newLength) {
#ifdef BIGMETA
Bit::htob24(data+8, newLength);
#else
Bit::htob24(data+5, newLength); Bit::htob24(data+5, newLength);
#endif
} }
///\brief Returns the number of a keyframe ///\brief Returns the number of a keyframe
unsigned long Key::getNumber() { unsigned long Key::getNumber() {
#ifdef BIGMETA
return Bit::btohl(data + 11);
#else
return Bit::btohs(data + 8); return Bit::btohs(data + 8);
#endif
} }
///\brief Sets the number of a keyframe ///\brief Sets the number of a keyframe
void Key::setNumber(unsigned long newNumber) { void Key::setNumber(unsigned long newNumber) {
#ifdef BIGMETA
Bit::htobl(data + 11, newNumber);
#else
Bit::htobs(data + 8, newNumber); Bit::htobs(data + 8, newNumber);
#endif
} }
///\brief Returns the number of parts of a keyframe ///\brief Returns the number of parts of a keyframe
unsigned short Key::getParts() { unsigned short Key::getParts() {
#ifdef BIGMETA
return Bit::btohs(data + 15);
#else
return Bit::btohs(data + 10); return Bit::btohs(data + 10);
#endif
} }
///\brief Sets the number of parts of a keyframe ///\brief Sets the number of parts of a keyframe
void Key::setParts(unsigned short newParts) { void Key::setParts(unsigned short newParts) {
#ifdef BIGMETA
Bit::htobs(data + 15, newParts);
#else
Bit::htobs(data + 10, newParts); Bit::htobs(data + 10, newParts);
#endif
} }
///\brief Returns the timestamp of a keyframe ///\brief Returns the timestamp of a keyframe
unsigned long long Key::getTime() { unsigned long long Key::getTime() {
#ifdef BIGMETA
return Bit::btohll(data + 17);
#else
return Bit::btohl(data + 12); return Bit::btohl(data + 12);
#endif
} }
///\brief Sets the timestamp of a keyframe ///\brief Sets the timestamp of a keyframe
void Key::setTime(unsigned long long newTime) { void Key::setTime(unsigned long long newTime) {
#ifdef BIGMETA
Bit::htobll(data + 17, newTime);
#else
Bit::htobl(data + 12, newTime); Bit::htobl(data + 12, newTime);
#endif
} }
///\brief Returns the data of this keyframe struct ///\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 ///\brief Returns the number of the first keyframe in this fragment
unsigned long Fragment::getNumber() { unsigned long Fragment::getNumber() {
#ifdef BIGMETA
return Bit::btohl(data + 5);
#else
return Bit::btohs(data + 5); return Bit::btohs(data + 5);
#endif
} }
///\brief Sets the number of the first keyframe in this fragment ///\brief Sets the number of the first keyframe in this fragment
void Fragment::setNumber(unsigned long newNumber) { void Fragment::setNumber(unsigned long newNumber) {
#ifdef BIGMETA
Bit::htobl(data + 5, newNumber);
#else
Bit::htobs(data + 5, newNumber); Bit::htobs(data + 5, newNumber);
#endif
} }
///\brief Returns the size of a fragment ///\brief Returns the size of a fragment
unsigned long Fragment::getSize() { unsigned long Fragment::getSize() {
#ifdef BIGMETA
return Bit::btohl(data + 9);
#else
return Bit::btohl(data + 7); return Bit::btohl(data + 7);
#endif
} }
///\brief Sets the size of a fragement ///\brief Sets the size of a fragement
void Fragment::setSize(unsigned long newSize) { void Fragment::setSize(unsigned long newSize) {
#ifdef BIGMETA
Bit::htobl(data + 9, newSize);
#else
Bit::htobl(data + 7, newSize); Bit::htobl(data + 7, newSize);
#endif
} }
///\brief Returns thte data of this fragment structure ///\brief Returns thte data of this fragment structure
@ -976,11 +1062,11 @@ namespace DTSC {
Track::Track(JSON::Value & trackRef) { Track::Track(JSON::Value & trackRef) {
if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) { if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) {
Fragment * tmp = (Fragment *)trackRef["fragments"].asStringRef().data(); 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()) { if (trackRef.isMember("keys") && trackRef["keys"].isString()) {
Key * tmp = (Key *)trackRef["keys"].asStringRef().data(); 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()) { if (trackRef.isMember("parts") && trackRef["parts"].isString()) {
Part * tmp = (Part *)trackRef["parts"].asStringRef().data(); Part * tmp = (Part *)trackRef["parts"].asStringRef().data();
@ -1018,13 +1104,13 @@ namespace DTSC {
char * tmp = 0; char * tmp = 0;
unsigned int tmplen = 0; unsigned int tmplen = 0;
trackRef.getMember("fragments").getString(tmp, tmplen); 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) { if (trackRef.getMember("keys").getType() == DTSC_STR) {
char * tmp = 0; char * tmp = 0;
unsigned int tmplen = 0; unsigned int tmplen = 0;
trackRef.getMember("keys").getString(tmp, tmplen); 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) { if (trackRef.getMember("parts").getType() == DTSC_STR) {
char * tmp = 0; char * tmp = 0;
@ -1064,7 +1150,13 @@ namespace DTSC {
///Will also insert keyframes on non-video tracks, and creates fragments ///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) { 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) { 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; return;
} }
Part newPart; Part newPart;
@ -1384,20 +1476,22 @@ namespace DTSC {
} }
///\brief Determines the "packed" size of a track ///\brief Determines the "packed" size of a track
int Track::getSendLen() { int Track::getSendLen(bool skipDynamic) {
int result = 146 + init.size() + codec.size() + type.size() + getWritableIdentifier().size(); int result = 107 + init.size() + codec.size() + type.size() + getWritableIdentifier().size();
result += fragments.size() * 11; if (!skipDynamic){
result += keys.size() * 16; result += fragments.size() * PACKED_FRAGMENT_SIZE + 16;
result += keys.size() * PACKED_KEY_SIZE + 11;
if (keySizes.size()){ 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") { if (type == "audio") {
result += 49; result += 49;
} else if (type == "video") { } else if (type == "video") {
result += 48; result += 48;
} }
if (missedFrags) { if (!skipDynamic && missedFrags) {
result += 23; result += 23;
} }
return result; return result;
@ -1420,19 +1514,23 @@ namespace DTSC {
///\brief Writes a track to a pointer ///\brief Writes a track to a pointer
void Track::writeTo(char *& p) { void Track::writeTo(char *& p) {
std::deque<Fragment>::iterator firstFrag = fragments.begin();
if (fragments.size() && (&firstFrag) == 0){
return;
}
std::string trackIdent = getWritableIdentifier(); std::string trackIdent = getWritableIdentifier();
writePointer(p, convertShort(trackIdent.size()), 2); writePointer(p, convertShort(trackIdent.size()), 2);
writePointer(p, trackIdent); writePointer(p, trackIdent);
writePointer(p, "\340", 1);//Begin track object writePointer(p, "\340", 1);//Begin track object
writePointer(p, "\000\011fragments\002", 12); 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++) { 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, "\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++) { 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, "\000\010keysizes\002,", 11);
writePointer(p, convertInt(keySizes.size() * 4), 4); writePointer(p, convertInt(keySizes.size() * 4), 4);
@ -1490,19 +1588,20 @@ namespace DTSC {
} }
///\brief Writes a track to a socket ///\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(convertShort(getWritableIdentifier().size()), 2);
conn.SendNow(getWritableIdentifier()); conn.SendNow(getWritableIdentifier());
conn.SendNow("\340", 1);//Begin track object conn.SendNow("\340", 1);//Begin track object
if (!skipDynamic){
conn.SendNow("\000\011fragments\002", 12); 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++) { 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("\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++) { 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("\000\010keysizes\002,", 11);
conn.SendNow(convertInt(keySizes.size() * 4), 4); 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++) { for (std::deque<Part>::iterator it = parts.begin(); it != parts.end(); it++) {
conn.SendNow(it->getData(), 9); conn.SendNow(it->getData(), 9);
} }
}
conn.SendNow("\000\007trackid\001", 10); conn.SendNow("\000\007trackid\001", 10);
conn.SendNow(convertLongLong(trackID), 8); conn.SendNow(convertLongLong(trackID), 8);
if (missedFrags) { if (!skipDynamic && missedFrags) {
conn.SendNow("\000\014missed_frags\001", 15); conn.SendNow("\000\014missed_frags\001", 15);
conn.SendNow(convertLongLong(missedFrags), 8); conn.SendNow(convertLongLong(missedFrags), 8);
} }
@ -1560,10 +1660,12 @@ namespace DTSC {
} }
///\brief Determines the "packed" size of a meta object ///\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; 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++) { 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 return dataLen + 8; //add 8 bytes header
} }
@ -1600,13 +1702,15 @@ namespace DTSC {
} }
///\brief Writes a meta object to a socket ///\brief Writes a meta object to a socket
void Meta::send(Socket::Connection & conn) { void Meta::send(Socket::Connection & conn, bool skipDynamic, std::set<unsigned long> selectedTracks) {
int dataLen = getSendLen() - 8; //strip 8 bytes header int dataLen = getSendLen(skipDynamic, selectedTracks) - 8; //strip 8 bytes header
conn.SendNow(DTSC::Magic_Header, 4); conn.SendNow(DTSC::Magic_Header, 4);
conn.SendNow(convertInt(dataLen), 4); conn.SendNow(convertInt(dataLen), 4);
conn.SendNow("\340\000\006tracks\340", 10); conn.SendNow("\340\000\006tracks\340", 10);
for (std::map<unsigned int, Track>::iterator it = tracks.begin(); it != tracks.end(); it++) { 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 conn.SendNow("\000\000\356", 3);//End tracks object
if (vod) { if (vod) {
@ -1631,18 +1735,19 @@ namespace DTSC {
} }
///\brief Converts a track to a JSON::Value ///\brief Converts a track to a JSON::Value
JSON::Value Track::toJSON() { JSON::Value Track::toJSON(bool skipDynamic) {
JSON::Value result; JSON::Value result;
std::string tmp; std::string tmp;
tmp.reserve(fragments.size() * 11); if (!skipDynamic) {
tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE);
for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) { for (std::deque<Fragment>::iterator it = fragments.begin(); it != fragments.end(); it++) {
tmp.append(it->getData(), 11); tmp.append(it->getData(), PACKED_FRAGMENT_SIZE);
} }
result["fragments"] = tmp; result["fragments"] = tmp;
tmp = ""; tmp = "";
tmp.reserve(keys.size() * 16); tmp.reserve(keys.size() * PACKED_KEY_SIZE);
for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) { for (std::deque<Key>::iterator it = keys.begin(); it != keys.end(); it++) {
tmp.append(it->getData(), 16); tmp.append(it->getData(), PACKED_KEY_SIZE);
} }
result["keys"] = tmp; result["keys"] = tmp;
tmp = ""; tmp = "";
@ -1660,6 +1765,8 @@ namespace DTSC {
tmp.append(it->getData(), 9); tmp.append(it->getData(), 9);
} }
result["parts"] = tmp; result["parts"] = tmp;
}
result["init"] = init;
result["trackid"] = trackID; result["trackid"] = trackID;
result["firstms"] = (long long)firstms; result["firstms"] = (long long)firstms;
result["lastms"] = (long long)lastms; result["lastms"] = (long long)lastms;
@ -1669,7 +1776,6 @@ namespace DTSC {
} }
result["codec"] = codec; result["codec"] = codec;
result["type"] = type; result["type"] = type;
result["init"] = init;
if (type == "audio") { if (type == "audio") {
result["rate"] = rate; result["rate"] = rate;
result["size"] = size; result["size"] = size;

View file

@ -11,6 +11,9 @@
#include <string.h> //memcpy #include <string.h> //memcpy
#include <sstream> #include <sstream>
#include "h264.h" //Needed for init data parsing in case of invalid values from FLV init
/// Holds the last FLV header parsed. /// Holds the last FLV header parsed.
/// Defaults to a audio+video header on FLV version 0x01 if no header received yet. /// 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}; 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); 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(); pack_out.null();
return pack_out; //skip rest of parsing, get next tag. return pack_out; //skip rest of parsing, get next tag.
} }

View file

@ -81,6 +81,12 @@ namespace h264 {
sequenceParameterSet::sequenceParameterSet(const char * _data, unsigned long _dataLen) : data(_data), dataLen(_dataLen) {} 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 sequenceParameterSet::getCharacteristics() const {
SPSMeta result; SPSMeta result;
@ -107,8 +113,10 @@ namespace h264 {
} }
char profileIdc = bs.get(8); char profileIdc = bs.get(8);
result.profile = profileIdc;
//Start skipping unused data //Start skipping unused data
bs.skip(16); bs.skip(8);
result.level = bs.get(8);
bs.getUExpGolomb(); bs.getUExpGolomb();
if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) { if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) {
//chroma format idc //chroma format idc

View file

@ -12,6 +12,8 @@ namespace h264 {
unsigned int width; unsigned int width;
unsigned int height; unsigned int height;
double fps; double fps;
uint8_t profile;
uint8_t level;
}; };
///Class for analyzing generic nal units ///Class for analyzing generic nal units
@ -50,7 +52,8 @@ namespace h264 {
class sequenceParameterSet { class sequenceParameterSet {
public: 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; SPSMeta getCharacteristics() const;
private: private:
const char * data; const char * data;

View file

@ -181,13 +181,11 @@ void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & co
StartResponse("200", "OK", request, conn, bufferAllChunks); StartResponse("200", "OK", request, conn, bufferAllChunks);
} }
/// After receiving a header with this object, this function call will: /// After receiving a header with this object, and after a call with SendResponse/SendRequest with this object, this function call will:
/// - Forward the headers to the 'to' Socket::Connection.
/// - Retrieve all the body from the 'from' Socket::Connection. /// - Retrieve all the body from the 'from' Socket::Connection.
/// - Forward those contents as-is to the 'to' 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. /// It blocks until completed or either of the connections reaches an error state.
void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) { void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) {
SendResponse(url, method, to);
if (getChunks) { if (getChunks) {
unsigned int proxyingChunk = 0; unsigned int proxyingChunk = 0;
while (to.connected() && from.connected()) { while (to.connected() && from.connected()) {
@ -315,6 +313,20 @@ std::string HTTP::Parser::GetVar(std::string i) {
return vars[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. /// Sets header i to string value v.
void HTTP::Parser::SetHeader(std::string i, std::string v) { void HTTP::Parser::SetHeader(std::string i, std::string v) {
Trim(i); Trim(i);

View file

@ -19,6 +19,7 @@ namespace HTTP {
std::string GetHeader(std::string i); std::string GetHeader(std::string i);
std::string GetVar(std::string i); std::string GetVar(std::string i);
std::string getUrl(); std::string getUrl();
std::string allVars();
void SetHeader(std::string i, std::string v); void SetHeader(std::string i, std::string v);
void SetHeader(std::string i, long long v); void SetHeader(std::string i, long long v);
void setCORSHeaders(); void setCORSHeaders();
@ -44,11 +45,13 @@ namespace HTTP {
unsigned int length; unsigned int length;
bool headerOnly; ///< If true, do not parse body if the length is a known size. bool headerOnly; ///< If true, do not parse body if the length is a known size.
bool bufferChunks; bool bufferChunks;
//this bool was private
bool sendingChunks;
private: private:
bool seenHeaders; bool seenHeaders;
bool seenReq; bool seenReq;
bool getChunks; bool getChunks;
bool sendingChunks;
unsigned int doingChunk; unsigned int doingChunk;
bool parse(std::string & HTTPbuffer); bool parse(std::string & HTTPbuffer);
void parseVars(std::string data); void parseVars(std::string data);

View file

@ -104,6 +104,7 @@ namespace MP4 {
} }
} else if (size == 0) { } else if (size == 0) {
fseek(newData, 0, SEEK_END); fseek(newData, 0, SEEK_END);
return true;
} }
DONTEVEN_MSG("skipping size 0x%.8lX", size); DONTEVEN_MSG("skipping size 0x%.8lX", size);
if (fseek(newData, pos + size, SEEK_SET) == 0) { if (fseek(newData, pos + size, SEEK_SET) == 0) {
@ -132,6 +133,10 @@ namespace MP4 {
return false; 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); fseek(newData, pos, SEEK_SET);
data = (char *)realloc(data, size); data = (char *)realloc(data, size);
data_size = size; data_size = size;
@ -160,6 +165,9 @@ namespace MP4 {
return false; return false;
} }
} }
if (size == 0){
size = newData.size();
}
if (newData.size() >= size) { if (newData.size() >= size) {
data = (char *)realloc(data, size); data = (char *)realloc(data, size);
data_size = size; data_size = size;
@ -383,9 +391,10 @@ namespace MP4 {
default: default:
break; 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? /// \todo Implement hexdump for unimplemented boxes?
return retval; return retval.str();
} }
/// Sets the 8 bits integer at the given index. /// Sets the 8 bits integer at the given index.
@ -807,4 +816,5 @@ namespace MP4 {
} }
return r.str(); return r.str();
} }
} }

View file

@ -1091,10 +1091,15 @@ namespace MP4 {
return; return;
} }
} }
memset(data + payloadOffset + 4, 0, 4);
memcpy(data + payloadOffset + 4, newMinorVersion, 4); memcpy(data + payloadOffset + 4, newMinorVersion, 4);
} }
std::string FTYP::getMinorVersion() { 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); return std::string(data + payloadOffset + 4, 4);
} }
@ -2335,7 +2340,8 @@ namespace MP4 {
setEntryCount(no + 1); setEntryCount(no + 1);
} }
setInt32(newCTTSEntry.sampleCount, 8 + no * 8); 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) { CTTSEntry CTTS::getCTTSEntry(uint32_t no) {
@ -2345,7 +2351,8 @@ namespace MP4 {
return inval; return inval;
} }
retval.sampleCount = getInt32(8 + (no * 8)); 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; return retval;
} }

View file

@ -486,7 +486,7 @@ namespace MP4 {
struct CTTSEntry { struct CTTSEntry {
uint32_t sampleCount; uint32_t sampleCount;
uint32_t sampleOffset; int32_t sampleOffset;
}; };
class CTTS: public fullBox { class CTTS: public fullBox {
@ -714,3 +714,4 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); std::string toPrettyString(uint32_t indent = 0);
}; };
} }

View file

@ -127,6 +127,9 @@ namespace MP4 {
if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") { if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") {
return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent); return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent);
} }
if (UUID == "6d1d9b05-42d5-44e6-80e2-141daff757b2") {
return ((UUID_TFXD *)this)->toPrettyString(indent);
}
std::stringstream r; std::stringstream r;
r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl; r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl;
r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl; r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl;
@ -205,4 +208,68 @@ namespace MP4 {
} }
return r.str(); 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();
}
} }

View file

@ -37,4 +37,17 @@ namespace MP4 {
std::string toPrettyString(uint32_t indent = 0); 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);
};
} }

View file

@ -22,13 +22,31 @@
#include "timing.h" #include "timing.h"
std::set<pid_t> Util::Procs::plist; std::set<pid_t> Util::Procs::plist;
std::set<int> Util::Procs::socketList;
bool Util::Procs::handler_set = false; bool Util::Procs::handler_set = false;
bool Util::Procs::thread_handler = false;
tthread::mutex Util::Procs::plistMutex;
tthread::thread * Util::Procs::reaper_thread = 0;
/// Local-only function. Attempts to reap child and returns current running status.
static bool childRunning(pid_t p) { bool Util::Procs::childRunning(pid_t p) {
pid_t ret = waitpid(p, 0, WNOHANG); int status;
pid_t ret = waitpid(p, &status, WNOHANG);
if (ret == p) { 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; return false;
} }
if (ret < 0 && errno == EINTR) { 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. /// all remaining children. Waits one more second for cleanup to finish, then exits.
void Util::Procs::exit_handler() { void Util::Procs::exit_handler() {
int waiting = 0; 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; std::set<pid_t>::iterator it;
if (listcopy.empty()) { if (listcopy.empty()) {
return; return;
@ -62,7 +90,7 @@ void Util::Procs::exit_handler() {
break; break;
} }
if (!listcopy.empty()) { if (!listcopy.empty()) {
Util::sleep(20); Util::wait(20);
++waiting; ++waiting;
} }
} }
@ -71,7 +99,7 @@ void Util::Procs::exit_handler() {
return; 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 //send sigint to all remaining
if (!listcopy.empty()) { if (!listcopy.empty()) {
for (it = listcopy.begin(); it != listcopy.end(); it++) { 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; waiting = 0;
//wait up to 5 seconds for applications to shut down //wait up to 5 seconds for applications to shut down
while (!listcopy.empty() && waiting <= 250) { while (!listcopy.empty() && waiting <= 250) {
@ -90,7 +118,7 @@ void Util::Procs::exit_handler() {
break; break;
} }
if (!listcopy.empty()) { if (!listcopy.empty()) {
Util::sleep(20); Util::wait(20);
++waiting; ++waiting;
} }
} }
@ -99,7 +127,7 @@ void Util::Procs::exit_handler() {
return; 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 //send sigkill to all remaining
if (!listcopy.empty()) { if (!listcopy.empty()) {
for (it = listcopy.begin(); it != listcopy.end(); it++) { 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; waiting = 0;
//wait up to 1 second for applications to shut down //wait up to 1 second for applications to shut down
while (!listcopy.empty() && waiting <= 50) { while (!listcopy.empty() && waiting <= 50) {
@ -118,7 +146,7 @@ void Util::Procs::exit_handler() {
break; break;
} }
if (!listcopy.empty()) { if (!listcopy.empty()) {
Util::sleep(20); Util::wait(20);
++waiting; ++waiting;
} }
} }
@ -126,14 +154,17 @@ void Util::Procs::exit_handler() {
if (listcopy.empty()) { if (listcopy.empty()) {
return; 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. /// Sets up exit and childsig handlers.
/// Spawns grim_reaper. exit handler despawns grim_reaper
/// Called by every Start* function. /// Called by every Start* function.
void Util::Procs::setHandler() { void Util::Procs::setHandler() {
tthread::lock_guard<tthread::mutex> guard(plistMutex);
if (!handler_set) { if (!handler_set) {
thread_handler = true;
reaper_thread = new tthread::thread(grim_reaper, 0);
struct sigaction new_action; struct sigaction new_action;
new_action.sa_handler = childsig_handler; new_action.sa_handler = childsig_handler;
sigemptyset(&new_action.sa_mask); sigemptyset(&new_action.sa_mask);
@ -144,19 +175,21 @@ void Util::Procs::setHandler() {
} }
} }
///Thread that loops until thread_handler is false.
/// Used internally to capture child signals and update plist. ///Reaps available children and then sleeps for a second.
void Util::Procs::childsig_handler(int signum) { ///Not done in signal handler so we can use a mutex to prevent race conditions.
if (signum != SIGCHLD) { void Util::Procs::grim_reaper(void * n){
return; VERYHIGH_MSG("Grim reaper start");
} while (thread_handler){
{
tthread::lock_guard<tthread::mutex> guard(plistMutex);
int status; int status;
pid_t ret = -1; pid_t ret = -1;
while (ret != 0) { while (ret != 0) {
ret = waitpid(-1, &status, WNOHANG); ret = waitpid(-1, &status, WNOHANG);
if (ret <= 0) { //ignore, would block otherwise if (ret <= 0) { //ignore, would block otherwise
if (ret == 0 || errno != EINTR) { if (ret == 0 || errno != EINTR) {
return; break;
} }
continue; continue;
} }
@ -166,19 +199,24 @@ void Util::Procs::childsig_handler(int signum) {
} else if (WIFSIGNALED(status)) { } else if (WIFSIGNALED(status)) {
exitcode = -WTERMSIG(status); exitcode = -WTERMSIG(status);
} else { // not possible } else { // not possible
return; break;
} }
if (plist.count(ret)) {
HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode);
plist.erase(ret); plist.erase(ret);
#if DEBUG >= DLVL_HIGH
if (!isActive(pname)) {
DEBUG_MSG(DLVL_HIGH, "Process %d fully terminated", ret);
} else { } 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;
} }
@ -187,7 +225,7 @@ std::string Util::Procs::getOutputOf(char * const * argv) {
std::string ret; std::string ret;
int fin = 0, fout = -1, ferr = 0; int fin = 0, fout = -1, ferr = 0;
pid_t myProc = StartPiped(argv, &fin, &fout, &ferr); pid_t myProc = StartPiped(argv, &fin, &fout, &ferr);
while (isActive(myProc)) { while (childRunning(myProc)) {
Util::sleep(100); Util::sleep(100);
} }
FILE * outFile = fdopen(fout, "r"); FILE * outFile = fdopen(fout, "r");
@ -201,6 +239,31 @@ std::string Util::Procs::getOutputOf(char * const * argv) {
return ret; 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. /// Starts a new process with given fds if the name is not already active.
/// \return 0 if process was not started, process PID otherwise. /// \return 0 if process was not started, process PID otherwise.
/// \arg argv Command for this process. /// \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(); pid = fork();
if (pid == 0) { //child 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) { if (!fdin) {
dup2(devnull, STDIN_FILENO); dup2(devnull, STDIN_FILENO);
} else if (*fdin == -1) { } else if (*fdin == -1) {
@ -319,7 +386,10 @@ pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int
} }
return 0; return 0;
} else { //parent } else { //parent
{
tthread::lock_guard<tthread::mutex> guard(plistMutex);
plist.insert(pid); plist.insert(pid);
}
DEBUG_MSG(DLVL_HIGH, "Piped process %s started, PID %d", argv[0], pid); DEBUG_MSG(DLVL_HIGH, "Piped process %s started, PID %d", argv[0], pid);
if (devnull != -1) { if (devnull != -1) {
close(devnull); close(devnull);
@ -354,7 +424,11 @@ void Util::Procs::Murder(pid_t name) {
/// (Attempts to) stop all running child processes. /// (Attempts to) stop all running child processes.
void Util::Procs::StopAll() { 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; std::set<pid_t>::iterator it;
for (it = listcopy.begin(); it != listcopy.end(); it++) { for (it = listcopy.begin(); it != listcopy.end(); it++) {
Stop(*it); Stop(*it);
@ -363,11 +437,13 @@ void Util::Procs::StopAll() {
/// Returns the number of active child processes. /// Returns the number of active child processes.
int Util::Procs::Count() { int Util::Procs::Count() {
tthread::lock_guard<tthread::mutex> guard(plistMutex);
return plist.size(); return plist.size();
} }
/// Returns true if a process with this PID is currently active. /// Returns true if a process with this PID is currently active.
bool Util::Procs::isActive(pid_t name) { bool Util::Procs::isActive(pid_t name) {
tthread::lock_guard<tthread::mutex> guard(plistMutex);
return (plist.count(name) == 1) && (kill(name, 0) == 0); return (plist.count(name) == 1) && (kill(name, 0) == 0);
} }

View file

@ -6,6 +6,8 @@
#include <string> #include <string>
#include <set> #include <set>
#include <vector> #include <vector>
#include <deque>
#include "tinythread.h"
/// Contains utility code, not directly related to streaming media /// Contains utility code, not directly related to streaming media
namespace Util { namespace Util {
@ -13,21 +15,30 @@ namespace Util {
/// Deals with spawning, monitoring and stopping child processes /// Deals with spawning, monitoring and stopping child processes
class Procs { class Procs {
private: 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 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 childsig_handler(int signum);
static void exit_handler(); static void exit_handler();
static void runCmd(std::string & cmd); static void runCmd(std::string & cmd);
static void setHandler(); static void setHandler();
static char* const* dequeToArgv(std::deque<std::string> & argDeq);
static void grim_reaper(void * n);
public: public:
static std::string getOutputOf(char * const * argv); 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(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 Stop(pid_t name);
static void Murder(pid_t name); static void Murder(pid_t name);
static void StopAll(); static void StopAll();
static int Count(); static int Count();
static bool isActive(pid_t name); static bool isActive(pid_t name);
static bool isRunning(pid_t pid); static bool isRunning(pid_t pid);
static std::set<int> socketList; ///< Holds sockets that should be closed before forking
}; };
} }

View file

@ -7,10 +7,6 @@
#include "timing.h" #include "timing.h"
#include "auth.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_in; ///< Input for the handshake.
std::string RTMPStream::handshake_out; ///< Output 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) { 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; static RTMPStream::Chunk ch;
ch.cs_id = cs_id; ch.cs_id = cs_id;
ch.timestamp = Util::getMS(); ch.timestamp = 0;
ch.len = data.size(); ch.len = data.size();
ch.real_len = data.size(); ch.real_len = data.size();
ch.len_left = 0; 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); DEBUG_MSG(DLVL_DONTEVEN, "Parsing RTMP chunk result: len_left=%d, real_len=%d", len_left, real_len);
//read extended timestamp, if neccesary //read extended timestamp, if necessary
if (ts_header == 0x00ffffff) { if (ts_header == 0x00ffffff && headertype != 0xC0) {
if (!buffer.available(i + 4)) { if (!buffer.available(i + 4)) {
return false; return false;
} //can't read timestamp } //can't read timestamp
indata = buffer.copy(i + 4); indata = buffer.copy(i + 4);
timestamp += indata[i++ ]; timestamp = indata[i++ ];
timestamp += indata[i++ ] * 256; timestamp += indata[i++ ] * 256;
timestamp += indata[i++ ] * 256 * 256; timestamp += indata[i++ ] * 256 * 256;
timestamp = indata[i++ ] * 256 * 256 * 256; timestamp += indata[i++ ] * 256 * 256 * 256;
ts_delta = timestamp; ts_delta = timestamp;
} }

View file

@ -10,6 +10,10 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include "socket.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. //forward declaration of FLV::Tag to avoid circular dependencies.
namespace FLV { namespace FLV {
class Tag; class Tag;

View file

@ -95,13 +95,13 @@ namespace IPC {
///\param oflag The flags with which to open the semaphore ///\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 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 ///\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) #if defined(__CYGWIN__) || defined(_WIN32)
mySem = 0; mySem = 0;
#else #else
mySem = SEM_FAILED; mySem = SEM_FAILED;
#endif #endif
open(name, oflag, mode, value); open(name, oflag, mode, value, noWait);
} }
///\brief The deconstructor ///\brief The deconstructor
@ -126,13 +126,13 @@ namespace IPC {
///\param oflag The flags with which to open the semaphore ///\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 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 ///\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(); close();
int timer = 0; int timer = 0;
while (!(*this) && timer++ < 10) { while (!(*this) && timer++ < 10) {
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
std::string semaName = "Global\\"; std::string semaName = "Global\\";
semaName += name; semaName += (name+1);
if (oflag & O_CREAT) { if (oflag & O_CREAT) {
if (oflag & O_EXCL) { if (oflag & O_EXCL) {
//attempt opening, if succes, close handle and return false; //attempt opening, if succes, close handle and return false;
@ -165,7 +165,7 @@ namespace IPC {
mySem = sem_open(name, oflag); mySem = sem_open(name, oflag);
} }
if (!(*this)) { if (!(*this)) {
if (errno == ENOENT) { if (errno == ENOENT && !noWait) {
Util::wait(500); Util::wait(500);
} else { } else {
break; break;
@ -405,7 +405,7 @@ namespace IPC {
int i = 0; int i = 0;
do { do {
if (i != 0) { if (i != 0) {
Util::sleep(1000); Util::wait(1000);
} }
handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str()); handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str());
i++; i++;
@ -438,14 +438,14 @@ namespace IPC {
int i = 0; int i = 0;
while (i < 10 && handle == -1 && autoBackoff) { while (i < 10 && handle == -1 && autoBackoff) {
i++; i++;
Util::sleep(1000); Util::wait(1000);
handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS); handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS);
} }
} }
} }
if (handle == -1) { if (handle == -1) {
if (!master_ && autoBackoff) { 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; return;
} }
@ -558,7 +558,7 @@ namespace IPC {
int i = 0; int i = 0;
while (i < 10 && handle == -1 && autoBackoff) { while (i < 10 && handle == -1 && autoBackoff) {
i++; i++;
Util::sleep(1000); Util::wait(1000);
handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_RDWR, (mode_t)0600); 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) { if (splitChar != std::string::npos) {
name[splitChar] = '+'; 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 ///\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 ///\brief Sets the name of the connector through which this user is viewing
void statExchange::connector(std::string name) { 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 ///\brief Gets the name of the connector through which this user is viewing
@ -706,6 +706,16 @@ namespace IPC {
return result; 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 ///\brief Creates a semaphore guard, locks the semaphore on call
semGuard::semGuard(semaphore * thisSemaphore) : mySemaphore(thisSemaphore) { semGuard::semGuard(semaphore * thisSemaphore) : mySemaphore(thisSemaphore) {
mySemaphore->wait(); mySemaphore->wait();
@ -762,6 +772,7 @@ namespace IPC {
///\brief The deconstructor ///\brief The deconstructor
sharedServer::~sharedServer() { sharedServer::~sharedServer() {
finishEach();
mySemaphore.close(); mySemaphore.close();
mySemaphore.unlink(); mySemaphore.unlink();
} }
@ -818,6 +829,62 @@ namespace IPC {
return false; 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. ///\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)) { void sharedServer::parseEach(void (*callback)(char * data, size_t len, unsigned int id)) {
char * empty = 0; char * empty = 0;
@ -829,6 +896,7 @@ namespace IPC {
unsigned int id = 0; unsigned int id = 0;
unsigned int userCount = 0; unsigned int userCount = 0;
unsigned int emptyCount = 0; unsigned int emptyCount = 0;
connectedUsers = 0;
for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) { for (std::set<sharedPage>::iterator it = myPages.begin(); it != myPages.end(); it++) {
if (!it->mapped || !it->len) { if (!it->mapped || !it->len) {
DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?"); DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?");
@ -842,28 +910,25 @@ namespace IPC {
char * counter = it->mapped + offset; char * counter = it->mapped + offset;
//increase the count if needed //increase the count if needed
++userCount; ++userCount;
if (*counter & 0x80){
connectedUsers++;
}
if (id >= amount) { if (id >= amount) {
amount = id + 1; 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)); uint32_t tmpPID = *((uint32_t *)(it->mapped + 1 + offset + payLen - 4));
if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)) { if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127)){
WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID); WARN_MSG("process disappeared, timing out. (pid %lu)", tmpPID);
*counter = 126; //if process is already dead, instant timeout. *counter = 126; //if process is already dead, instant timeout.
} }
callback(it->mapped + offset + 1, payLen, id); callback(it->mapped + offset + 1, payLen, id);
switch (*counter) { switch (*counter) {
case 127: case 127:
DEBUG_MSG(DLVL_HIGH, "Client %u requested disconnect", id); HIGH_MSG("Client %u requested disconnect", id);
break; break;
case 126: case 126:
DEBUG_MSG(DLVL_WARN, "Client %u timed out", id); HIGH_MSG("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);
break; break;
default: default:
#ifndef NOCRASHCHECK #ifndef NOCRASHCHECK
@ -883,7 +948,7 @@ namespace IPC {
#endif #endif
break; break;
} }
if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) { if (*counter == 127 || *counter == 126){
memset(it->mapped + offset + 1, 0, payLen); memset(it->mapped + offset + 1, 0, payLen);
it->mapped[offset] = 0; it->mapped[offset] = 0;
} else { } else {
@ -895,7 +960,7 @@ namespace IPC {
//bring the counter down if this was the last element //bring the counter down if this was the last element
if (id == amount - 1) { if (id == amount - 1) {
amount = id; 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 //stop, we're guaranteed no more pages are full at this point
break; break;
@ -907,7 +972,7 @@ namespace IPC {
//increase the count if needed //increase the count if needed
if (id >= amount) { if (id >= amount) {
amount = id + 1; 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); callback(it->mapped + offset, payLen, id);
} else { } else {
@ -916,7 +981,7 @@ namespace IPC {
//bring the counter down if this was the last element //bring the counter down if this was the last element
if (id == amount - 1) { if (id == amount - 1) {
amount = id; 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 //stop, we're guaranteed no more pages are full at this point
if (empty) { if (empty) {
@ -952,12 +1017,14 @@ namespace IPC {
hasCounter = 0; hasCounter = 0;
payLen = 0; payLen = 0;
offsetOnPage = 0; offsetOnPage = 0;
countAsViewer= true;
} }
///\brief Copy constructor for sharedClients ///\brief Copy constructor for sharedClients
///\param rhs The client ro copy ///\param rhs The client ro copy
sharedClient::sharedClient(const sharedClient & rhs) { sharedClient::sharedClient(const sharedClient & rhs) {
countAsViewer = rhs.countAsViewer;
baseName = rhs.baseName; baseName = rhs.baseName;
payLen = rhs.payLen; payLen = rhs.payLen;
hasCounter = rhs.hasCounter; hasCounter = rhs.hasCounter;
@ -978,6 +1045,7 @@ namespace IPC {
///\brief Assignment operator ///\brief Assignment operator
void sharedClient::operator =(const sharedClient & rhs) { void sharedClient::operator =(const sharedClient & rhs) {
countAsViewer = rhs.countAsViewer;
baseName = rhs.baseName; baseName = rhs.baseName;
payLen = rhs.payLen; payLen = rhs.payLen;
hasCounter = rhs.hasCounter; hasCounter = rhs.hasCounter;
@ -1001,6 +1069,7 @@ namespace IPC {
///\param len The size of the payload to allocate ///\param len The size of the payload to allocate
///\param withCounter Whether or not this payload has a counter ///\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) { sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/" + name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) {
countAsViewer = true;
#ifdef __APPLE__ #ifdef __APPLE__
//note: O_CREAT is only needed for mac, probably //note: O_CREAT is only needed for mac, probably
mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0); mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0);
@ -1035,7 +1104,7 @@ namespace IPC {
offsetOnPage = offset; offsetOnPage = offset;
if (hasCounter) { if (hasCounter) {
myPage.mapped[offset] = 1; myPage.mapped[offset] = 1;
*((unsigned short *)(myPage.mapped + 1 + offset + len - 2)) = getpid(); *((uint32_t *)(myPage.mapped + 1 + offset + len - 4)) = getpid();
} }
break; break;
} }
@ -1058,6 +1127,8 @@ namespace IPC {
///\brief The deconstructor ///\brief The deconstructor
sharedClient::~sharedClient() { sharedClient::~sharedClient() {
mySemaphore.close(); mySemaphore.close();
} }
///\brief Writes data to the shared data ///\brief Writes data to the shared data
@ -1079,7 +1150,7 @@ namespace IPC {
} }
if (myPage.mapped) { if (myPage.mapped) {
semGuard tmpGuard(&mySemaphore); 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"); DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element without counters");
return; return;
} }
if (myPage.mapped[offsetOnPage] < 128) { if ((myPage.mapped[offsetOnPage] & 0x7F) < 126) {
myPage.mapped[offsetOnPage] = 1; myPage.mapped[offsetOnPage] = (countAsViewer ? 0x81 : 0x01);
} else { } else {
DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element that needs to timeout, ignoring"); 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 ///\brief Get a pointer to the data of this client
char * sharedClient::getData() { char * sharedClient::getData() {
if (!myPage.mapped) { if (!myPage.mapped) {
@ -1104,8 +1182,21 @@ namespace IPC {
return (myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0)); 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) { userConnection::userConnection(char * _data) {
data = _data; data = _data;
if (!data){
WARN_MSG("userConnection created with null pointer!");
}
} }
unsigned long userConnection::getTrackId(size_t offset) const { unsigned long userConnection::getTrackId(size_t offset) const {

View file

@ -11,7 +11,7 @@
#include <semaphore.h> #include <semaphore.h>
#endif #endif
#define STAT_EX_SIZE 174 #define STAT_EX_SIZE 177
#define PLAY_EX_SIZE 2+6*SIMUL_TRACKS #define PLAY_EX_SIZE 2+6*SIMUL_TRACKS
namespace IPC { namespace IPC {
@ -37,6 +37,8 @@ namespace IPC {
void connector(std::string name); void connector(std::string name);
std::string connector(); std::string connector();
void crc(unsigned int sum); void crc(unsigned int sum);
char getSync();
void setSync(char s);
unsigned int crc(); unsigned int crc();
private: private:
///\brief The payload for the stat exchange ///\brief The payload for the stat exchange
@ -49,6 +51,8 @@ namespace IPC {
/// - 100 byte - streamName (name of the stream peer is viewing) /// - 100 byte - streamName (name of the stream peer is viewing)
/// - 20 byte - connector (name of the connector the peer is using) /// - 20 byte - connector (name of the connector the peer is using)
/// - 4 byte - CRC32 of user agent (or zero if none) /// - 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; char * data;
}; };
@ -56,10 +60,10 @@ namespace IPC {
class semaphore { class semaphore {
public: public:
semaphore(); 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(); ~semaphore();
operator bool() const; 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; int getVal() const;
void post(); void post();
void wait(); void wait();
@ -177,9 +181,11 @@ namespace IPC {
void init(std::string name, int len, bool withCounter = false); void init(std::string name, int len, bool withCounter = false);
~sharedServer(); ~sharedServer();
void parseEach(void (*callback)(char * data, size_t len, unsigned int id)); void parseEach(void (*callback)(char * data, size_t len, unsigned int id));
char * getIndex(unsigned int id);
operator bool() const; operator bool() const;
///\brief The amount of connected clients ///\brief The amount of connected clients
unsigned int amount; unsigned int amount;
unsigned int connectedUsers;
private: private:
bool isInUse(unsigned int id); bool isInUse(unsigned int id);
void newPage(); void newPage();
@ -194,6 +200,7 @@ namespace IPC {
semaphore mySemaphore; semaphore mySemaphore;
///\brief Whether the payload has a counter, if so, it is added in front of the payload ///\brief Whether the payload has a counter, if so, it is added in front of the payload
bool hasCounter; bool hasCounter;
void finishEach();
}; };
///\brief The client part of a server/client model for shared memory. ///\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 write(char * data, int len);
void finish(); void finish();
void keepAlive(); void keepAlive();
bool isAlive();
char * getData(); char * getData();
int getCounter();
bool countAsViewer;
private: private:
///\brief The basename of the shared pages. ///\brief The basename of the shared pages.
std::string baseName; std::string baseName;

View file

@ -510,11 +510,8 @@ unsigned int Socket::Connection::iwrite(const void * buffer, int len) {
return 0; return 0;
break; break;
default: default:
if (errno != EPIPE && errno != ECONNRESET) {
Error = true; Error = true;
remotehost = strerror(errno); INSANE_MSG("Could not iwrite data! Error: %s", strerror(errno));
DEBUG_MSG(DLVL_WARN, "Could not iwrite data! Error: %s", remotehost.c_str());
}
close(); close();
return 0; return 0;
break; break;
@ -555,11 +552,8 @@ int Socket::Connection::iread(void * buffer, int len, int flags) {
return 0; return 0;
break; break;
default: default:
if (errno != EPIPE) {
Error = true; Error = true;
remotehost = strerror(errno); INSANE_MSG("Could not iread data! Error: %s", strerror(errno));
DEBUG_MSG(DLVL_WARN, "Could not iread data! Error: %s", remotehost.c_str());
}
close(); close();
return 0; return 0;
break; break;

View file

@ -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. /// 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()! /// Assumes the streamname has already been through sanitizeName()!
bool Util::streamAlive(std::string & streamname){ 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()) { if (!playerLock.tryWait()) {
playerLock.close(); playerLock.close();
return true; 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 //Attempt to load up configuration and find this stream
IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE); IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE);
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
//Lock the config to prevent race conditions and corruption issues while reading //Lock the config to prevent race conditions and corruption issues while reading
configLock.wait(); configLock.wait();
DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len); 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); input = inputs.getIndice(i);
//if match voor current stream && priority is hoger dan wat we al hebben //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 source = input.getMember("source_match").asString();
std::string front = source.substr(0,source.find('*')); std::string front = source.substr(0,source.find('*'));
std::string back = source.substr(source.find('*')+1); std::string back = source.substr(source.find('*')+1);
@ -161,6 +202,8 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir
selected = true; selected = true;
} }
} }
}
} }
if (!selected){ if (!selected){
@ -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)); FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), strerror(errno));
_exit(42); _exit(42);
} }
return true;
unsigned int waiting = 0;
while (!streamAlive(streamname) && ++waiting < 40){
Util::wait(250);
}
return streamAlive(streamname);
} }

View file

@ -4,10 +4,13 @@
#pragma once #pragma once
#include <string> #include <string>
#include "socket.h" #include "socket.h"
#include "json.h"
namespace Util { namespace Util {
std::string getTmpFolder(); std::string getTmpFolder();
void sanitizeName(std::string & streamname); void sanitizeName(std::string & streamname);
bool streamAlive(std::string & streamname); bool streamAlive(std::string & streamname);
bool startInput(std::string streamname, std::string filename = "", bool forkFirst = true); bool startInput(std::string streamname, std::string filename = "", bool forkFirst = true);
JSON::Value getStreamConfig(std::string streamname);
} }

View file

@ -25,6 +25,11 @@ namespace TS {
clear(); clear();
} }
Packet::Packet(const Packet & rhs){
memcpy(strBuf, rhs.strBuf, 188);
pos = 188;
}
/// This function fills a Packet from a file. /// This function fills a Packet from a file.
/// It fills the content with the next 188 bytes int he file. /// It fills the content with the next 188 bytes int he file.
/// \param Data The data to be read into the packet. /// \param Data The data to be read into the packet.
@ -34,11 +39,11 @@ namespace TS {
if (!fread((void *)strBuf, 188, 1, data)) { if (!fread((void *)strBuf, 188, 1, data)) {
return false; return false;
} }
pos=188;
if (strBuf[0] != 0x47){ if (strBuf[0] != 0x47){
HIGH_MSG("Failed to read a good packet on pos %lld", bPos); HIGH_MSG("Failed to read a good packet on pos %lld", bPos);
return false; return false;
} }
pos=188;
return true; return true;
} }
@ -412,7 +417,7 @@ namespace TS {
/// \return A character pointer to the internal packet buffer data /// \return A character pointer to the internal packet buffer data
const char * Packet::checkAndGetBuffer() const{ const char * Packet::checkAndGetBuffer() const{
if (pos != 188) { 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; 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 ///Retrieves the current addStuffingoffset value for a PAT
char ProgramAssociationTable::getOffset() const{ char ProgramAssociationTable::getOffset() const{
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0);
@ -680,6 +690,10 @@ namespace TS {
return data[0]; return data[0];
} }
void ProgramMappingEntry::setStreamType(int newType){
data[0] = newType;
}
std::string ProgramMappingEntry::getCodec() const{ std::string ProgramMappingEntry::getCodec() const{
switch (getStreamType()){ switch (getStreamType()){
case 0x01: case 0x01:
@ -730,10 +744,25 @@ namespace TS {
return ((data[1] << 8) | data[2]) & 0x1FFF; 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{ int ProgramMappingEntry::getESInfoLength() const{
return ((data[3] << 8) | data[4]) & 0x0FFF; 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(){ void ProgramMappingEntry::advance(){
if (!(*this)) { if (!(*this)) {
return; return;
@ -749,6 +778,12 @@ namespace TS {
pos=4; pos=4;
} }
ProgramMappingTable & ProgramMappingTable::operator = (const Packet & rhs) {
memcpy(strBuf, rhs.checkAndGetBuffer(), 188);
pos = 188;
return *this;
}
char ProgramMappingTable::getOffset() const{ char ProgramMappingTable::getOffset() const{
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0);
return strBuf[loc]; return strBuf[loc];
@ -868,14 +903,6 @@ namespace TS {
strBuf[loc+1] = (char)newVal; 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{ ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{
int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset(); int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset();
ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) ); ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) );
@ -885,59 +912,6 @@ namespace TS {
return res; 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{ int ProgramMappingTable::getCRC() const{
unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength(); 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]; 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.setPID(4096);
PMT.setTableId(2); PMT.setTableId(2);
//section length met 2 tracks: 0xB017 //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.setProgramNumber(1);
PMT.setVersionNumber(0); PMT.setVersionNumber(0);
PMT.setCurrentNextIndicator(0); PMT.setCurrentNextIndicator(0);
@ -1012,20 +993,20 @@ namespace TS {
if (vidTrack == -1){ if (vidTrack == -1){
vidTrack = *(selectedTracks.begin()); vidTrack = *(selectedTracks.begin());
} }
PMT.setPCRPID(0x100 + vidTrack - 1); PMT.setPCRPID(vidTrack);
PMT.setProgramInfoLength(0); PMT.setProgramInfoLength(0);
short id = 0; short id = 0;
ProgramMappingEntry entry = PMT.getEntry(0);
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
entry.setElementaryPid(*it);
if (myMeta.tracks[*it].codec == "H264"){ if (myMeta.tracks[*it].codec == "H264"){
PMT.setStreamType(0x1B,id); entry.setStreamType(0x1B);
}else if (myMeta.tracks[*it].codec == "AAC"){ }else if (myMeta.tracks[*it].codec == "AAC"){
PMT.setStreamType(0x0F,id); entry.setStreamType(0x0F);
}else if (myMeta.tracks[*it].codec == "MP3"){ }else if (myMeta.tracks[*it].codec == "MP3"){
PMT.setStreamType(0x03,id); entry.setStreamType(0x03);
} }
PMT.setElementaryPID(0x100 + (*it) - 1, id); entry.advance();
PMT.setESInfoLength(0,id);
id++;
} }
PMT.calcCRC(); PMT.calcCRC();
return PMT.checkAndGetBuffer(); return PMT.checkAndGetBuffer();

View file

@ -22,6 +22,7 @@ namespace TS {
public: public:
//Constructors and fillers //Constructors and fillers
Packet(); Packet();
Packet(const Packet & rhs);
~Packet(); ~Packet();
bool FromPointer(const char * data); bool FromPointer(const char * data);
bool FromFile(FILE * data); bool FromFile(FILE * data);
@ -83,6 +84,7 @@ namespace TS {
class ProgramAssociationTable : public Packet { class ProgramAssociationTable : public Packet {
public: public:
ProgramAssociationTable & operator = (const Packet & rhs);
char getOffset() const; char getOffset() const;
char getTableId() const; char getTableId() const;
short getSectionLength() const; short getSectionLength() const;
@ -105,11 +107,14 @@ namespace TS {
operator bool() const; operator bool() const;
int getStreamType() const; int getStreamType() const;
void setStreamType(int newType);
std::string getCodec() const; std::string getCodec() const;
std::string getStreamTypeString() const; std::string getStreamTypeString() const;
int getElementaryPid() const; int getElementaryPid() const;
void setElementaryPid(int newElementaryPid);
int getESInfoLength() const; int getESInfoLength() const;
char * getESInfo() const; const char * getESInfo() const;
void setESInfo(const std::string & newInfo);
void advance(); void advance();
private: private:
char* data; char* data;
@ -119,6 +124,7 @@ namespace TS {
class ProgramMappingTable : public Packet { class ProgramMappingTable : public Packet {
public: public:
ProgramMappingTable(); ProgramMappingTable();
ProgramMappingTable & operator = (const Packet & rhs);
char getOffset() const; char getOffset() const;
void setOffset(char newVal); void setOffset(char newVal);
char getTableId() const; char getTableId() const;
@ -139,15 +145,7 @@ namespace TS {
void setPCRPID(short newVal); void setPCRPID(short newVal);
short getProgramInfoLength() const; short getProgramInfoLength() const;
void setProgramInfoLength(short newVal); void setProgramInfoLength(short newVal);
short getProgramCount() const;
void setProgramCount(short newVal);
ProgramMappingEntry getEntry(int index) const; 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; int getCRC() const;
void calcCRC(); void calcCRC();
std::string toPrettyString(size_t indent) const; std::string toPrettyString(size_t indent) const;

40
lib/util.cpp Normal file
View 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
View 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);
}

View file

@ -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.' 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', type: 'checkbox',
label: 'Force JSON file save', label: 'Force configurations save',
pointer: { pointer: {
main: mist.data, main: mist.data,
index: 'save' index: 'save'
@ -3464,7 +3464,7 @@ var UI = {
},{ },{
label: 'Target', label: 'Target',
type: 'str', 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: { pointer: {
main: saveas, main: saveas,
index: 'target' index: 'target'

View file

@ -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)

View file

@ -23,7 +23,10 @@ namespace Analysers {
std::cerr << "Not a valid DTSC file" << std::endl; std::cerr << "Not a valid DTSC file" << std::endl;
return 1; return 1;
} }
if (F.getMeta().vod || F.getMeta().live){
F.getMeta().toPrettyString(std::cout,0, 0x03); F.getMeta().toPrettyString(std::cout,0, 0x03);
}
int bPos = 0; int bPos = 0;
F.seek_bpos(0); F.seek_bpos(0);
@ -42,6 +45,10 @@ namespace Analysers {
std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl; std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl;
break; break;
} }
case DTSC::DTCM: {
std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl;
break;
}
default: default:
DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos); DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos);
break; break;

View file

@ -9,6 +9,7 @@
#include <string.h> #include <string.h>
#include <mist/mp4.h> #include <mist/mp4.h>
#include <mist/config.h> #include <mist/config.h>
#include <mist/defines.h>
///\brief Holds everything unique to the analysers. ///\brief Holds everything unique to the analysers.
namespace Analysers { namespace Analysers {
@ -25,9 +26,15 @@ namespace Analysers {
mp4Buffer.erase(mp4Buffer.size() - 1, 1); mp4Buffer.erase(mp4Buffer.size() - 1, 1);
MP4::Box mp4Data; MP4::Box mp4Data;
int dataSize = mp4Buffer.size();
int curPos = 0;
while (mp4Data.read(mp4Buffer)){ while (mp4Data.read(mp4Buffer)){
DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos);
std::cerr << mp4Data.toPrettyString(0) << std::endl; 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; return 0;
} }
} }

View file

@ -1,4 +1,5 @@
/// \page api API calls /// \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. /// 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. /// 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. /// 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 /// \file controller.cpp
/// Contains all code for the controller executable. /// Contains all code for the controller executable.
@ -88,6 +91,7 @@ void createAccount (std::string account){
/// Status monitoring thread. /// Status monitoring thread.
/// Will check outputs, inputs and converters every five seconds /// Will check outputs, inputs and converters every five seconds
void statusMonitor(void * np){ void statusMonitor(void * np){
IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
while (Controller::conf.is_active){ while (Controller::conf.is_active){
//this scope prevents the configMutex from being locked constantly //this scope prevents the configMutex from being locked constantly
{ {
@ -99,19 +103,20 @@ void statusMonitor(void * np){
changed |= Controller::CheckAllStreams(Controller::Storage["streams"]); changed |= Controller::CheckAllStreams(Controller::Storage["streams"]);
//check if the config semaphore is stuck, by trying to lock it for 5 attempts of 1 second... //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()){ 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. //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."); WARN_MSG("Configuration semaphore was stuck. Force-unlocking it and re-writing config.");
changed = true; changed = true;
} }
configLock.post(); configLock.post();
if (changed){ if (changed || Controller::configChanged){
Controller::writeConfig(); Controller::writeConfig();
Controller::configChanged = false;
} }
} }
Util::wait(5000);//wait at least 5 seconds Util::wait(5000);//wait at least 5 seconds
} }
configLock.unlink();
} }
///\brief The main entry point for the controller. ///\brief The main entry point for the controller.
@ -134,10 +139,9 @@ int main(int argc, char ** argv){
stored_user["default"] = "root"; stored_user["default"] = "root";
} }
Controller::conf = Util::Config(argv[0]); Controller::conf = Util::Config(argv[0]);
Controller::conf.addOption("listen_port", stored_port); Controller::conf.addOption("port", stored_port);
Controller::conf.addOption("listen_interface", stored_interface); Controller::conf.addOption("interface", stored_interface);
Controller::conf.addOption("username", stored_user); 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("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("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.\"}")); 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 //check for port, interface and username in arguments
//if they are not there, take them from config file, if there //if they are not there, take them from config file, if there
if (Controller::Storage["config"]["controller"]["port"]){ 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"]){ 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"]){ if (Controller::Storage["config"]["controller"]["username"]){
Controller::conf.getOption("username", true)[0u] = 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(); Controller::checkAvailProtocols();
createAccount(Controller::conf.getString("account")); createAccount(Controller::conf.getString("account"));
@ -244,16 +253,16 @@ int main(int argc, char ** argv){
}else{//logfile is enabled }else{//logfile is enabled
//check for username //check for username
if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){ 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 //check for protocols
if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){ 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 //check for streams - regardless of logfile setting
if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){ 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 }//connected to a terminal
@ -274,8 +283,8 @@ int main(int argc, char ** argv){
}else{ }else{
shutdown_reason = "socket problem (API port closed)"; shutdown_reason = "socket problem (API port closed)";
} }
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
Controller::conf.is_active = false; Controller::conf.is_active = false;
Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason);
//join all joinable threads //join all joinable threads
statsThread.join(); statsThread.join();
monitorThread.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; std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl;
return 0; return 0;
} }

View file

@ -58,9 +58,6 @@
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
void Controller::checkConfig(JSON::Value & in, JSON::Value & out){ void Controller::checkConfig(JSON::Value & in, JSON::Value & out){
out = in; 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 (out.isMember("debug")){
if (Util::Config::printDebugLevel != out["debug"].asInt()){ if (Util::Config::printDebugLevel != out["debug"].asInt()){
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("X-Info", "To force an API response, request the file /api");
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
H.SetHeader("Content-Length", server_html_len); H.SetHeader("Content-Length", server_html_len);
H.SetHeader("X-UA-Compatible","IE=edge;chrome=1");
H.SendResponse("200", "OK", conn); H.SendResponse("200", "OK", conn);
conn.SendNow(server_html, server_html_len); conn.SendNow(server_html, server_html_len);
H.Clean(); H.Clean();
@ -247,6 +245,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){
/// ] /// ]
/// ] /// ]
/// ~~~~~~~~~~~~~~~ /// ~~~~~~~~~~~~~~~
///
if(Request.isMember("browse")){ if(Request.isMember("browse")){
if(Request["browse"] == ""){ if(Request["browse"] == ""){
Request["browse"] = "."; Request["browse"] = ".";

View file

@ -278,7 +278,11 @@ namespace Controller {
unsigned long long c_user, c_nice, c_syst, c_idle, c_total; 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){ 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; 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)); 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_total = c_total;
cl_idle = c_idle; cl_idle = c_idle;
break; break;

View file

@ -2,6 +2,7 @@
#include <list> #include <list>
#include <mist/config.h> #include <mist/config.h>
#include "controller_statistics.h" #include "controller_statistics.h"
#include "controller_storage.h"
// These are used to store "clients" field requests in a bitfield for speedup. // These are used to store "clients" field requests in a bitfield for speedup.
#define STAT_CLI_HOST 1 #define STAT_CLI_HOST 1
@ -21,6 +22,8 @@
#define STAT_TOT_BPS_UP 4 #define STAT_TOT_BPS_UP 4
#define STAT_TOT_ALL 0xFF #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<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. std::map<unsigned long, Controller::sessIndex> Controller::connToSession; ///< Map of socket IDs to session info.
@ -37,6 +40,12 @@ Controller::sessIndex::sessIndex(){
crc = 0; 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. /// 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. /// This extracts the host, stream name, connector and crc field, ignoring everything else.
Controller::sessIndex::sessIndex(IPC::statExchange & data){ Controller::sessIndex::sessIndex(IPC::statExchange & data){
@ -80,6 +89,9 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{
return !(*this < b); return !(*this < b);
} }
/// \todo Make this prettier.
IPC::sharedServer * statPointer = 0;
/// This function runs as a thread and roughly once per second retrieves /// This function runs as a thread and roughly once per second retrieves
/// statistics from all connected clients, as well as wipes /// 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){ void Controller::SharedMemStats(void * config){
DEBUG_MSG(DLVL_HIGH, "Starting stats thread"); DEBUG_MSG(DLVL_HIGH, "Starting stats thread");
IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true); IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true);
statPointer = &statServer;
std::set<std::string> inactiveStreams;
while(((Util::Config*)config)->is_active){ 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 //parse current users
statServer.parseEach(parseStatistics); statServer.parseEach(parseStatistics);
//wipe old statistics //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"); DEBUG_MSG(DLVL_HIGH, "Stopping stats thread");
} }
@ -147,6 +163,18 @@ void Controller::statSession::wipeOld(unsigned long long cutOff){
oldConns.pop_front(); 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. /// 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. /// Returns true if there is data available for timestamp t.
bool Controller::statStorage::hasDataFor(unsigned long long t) { bool Controller::statStorage::hasDataFor(unsigned long long t) {
if (!log.size()){return false;} if (!log.size()){return false;}
@ -390,8 +423,13 @@ void Controller::statStorage::update(IPC::statExchange & data) {
statLog tmp; statLog tmp;
tmp.time = data.time(); tmp.time = data.time();
tmp.lastSecond = data.lastSecond(); tmp.lastSecond = data.lastSecond();
tmp.down = data.down(); tmp.down = data.down() - removeDown;
tmp.up = data.up(); 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; log[data.now()] = tmp;
//wipe data older than approx. STAT_CUTOFF seconds //wipe data older than approx. STAT_CUTOFF seconds
/// \todo Remove least interesting data first. /// \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); sessions[idx].update(id, tmpEx);
//check validity of stats data //check validity of stats data
char counter = (*(data - 1)); 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 //the data is no longer valid - connection has gone away, store for later
sessions[idx].finish(id); sessions[idx].finish(id);
connToSession.erase(id); connToSession.erase(id);
@ -432,7 +470,7 @@ bool Controller::hasViewers(std::string streamName){
if (sessions.size()){ if (sessions.size()){
long long currTime = Util::epoch(); long long currTime = Util::epoch();
for (std::map<sessIndex, statSession>::iterator it = sessions.begin(); it != sessions.end(); it++){ 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; return true;
} }
} }

View file

@ -1,3 +1,4 @@
#pragma once
#include <mist/shared_memory.h> #include <mist/shared_memory.h>
#include <mist/timing.h> #include <mist/timing.h>
#include <mist/defines.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; bool operator< (const sessIndex &o) const;
bool operator>= (const sessIndex &o) const; bool operator>= (const sessIndex &o) const;
std::string toStr();
}; };
class statStorage { class statStorage {
private:
long long removeUp;
long long removeDown;
public: public:
statStorage();
void update(IPC::statExchange & data); void update(IPC::statExchange & data);
bool hasDataFor(unsigned long long); bool hasDataFor(unsigned long long);
statLog & getDataFor(unsigned long long); statLog & getDataFor(unsigned long long);

View file

@ -15,6 +15,8 @@ namespace Controller {
JSON::Value Storage; ///< Global storage of data. JSON::Value Storage; ///< Global storage of data.
tthread::mutex configMutex; tthread::mutex configMutex;
tthread::mutex logMutex; tthread::mutex logMutex;
bool configChanged = false;
///\brief Store and print a log message. ///\brief Store and print a log message.
///\param kind The type of message. ///\param kind The type of message.
///\param message The message to be logged. ///\param message The message to be logged.
@ -70,6 +72,7 @@ namespace Controller {
printf("%s", buf); printf("%s", buf);
} }
} }
Log("LOG", "Logger exiting");
fclose(output); fclose(output);
close((long long int)err); close((long long int)err);
} }
@ -95,8 +98,8 @@ namespace Controller {
} }
if (!changed){return;}//cancel further processing if no changes if (!changed){return;}//cancel further processing if no changes
static IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE, true); static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true);
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
//lock semaphore //lock semaphore
configLock.wait(); configLock.wait();
//write config //write config

View file

@ -8,6 +8,7 @@ namespace Controller {
extern JSON::Value Storage; ///< Global storage of data. extern JSON::Value Storage; ///< Global storage of data.
extern tthread::mutex logMutex;///< Mutex for log thread. extern tthread::mutex logMutex;///< Mutex for log thread.
extern tthread::mutex configMutex;///< Mutex for server config access. 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. /// Store and print a log message.
void Log(std::string kind, std::string message); void Log(std::string kind, std::string message);

View file

@ -207,7 +207,7 @@ namespace Controller {
//actually delete the streams //actually delete the streams
while (toDelete.size() > 0){ while (toDelete.size() > 0){
std::string deleting = *(toDelete.begin()); std::string deleting = *(toDelete.begin());
out.removeMember(deleting); deleteStream(deleting, out);
toDelete.erase(deleting); 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 } //Controller namespace

View file

@ -6,9 +6,11 @@ namespace Controller {
bool CheckAllStreams(JSON::Value & data); bool CheckAllStreams(JSON::Value & data);
void CheckStreams(JSON::Value & in, JSON::Value & out); void CheckStreams(JSON::Value & in, JSON::Value & out);
void AddStreams(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 { struct liveCheck {
long long int lastms; long long int lastms;
long long int last_active; long long int last_active;
}; };
} //Controller namespace } //Controller namespace

View file

@ -2,6 +2,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <mist/stream.h>
#include <mist/defines.h> #include <mist/defines.h>
#include "input.h" #include "input.h"
#include <sstream> #include <sstream>
@ -69,7 +70,7 @@ namespace Mist {
} }
void Input::checkHeaderTimes(std::string streamFile){ void Input::checkHeaderTimes(std::string streamFile){
if ( streamFile == "-" ){ if (streamFile == "-" || streamFile == "push://") {
return; return;
} }
std::string headerFile = streamFile + ".dtsh"; std::string headerFile = streamFile + ".dtsh";
@ -99,15 +100,22 @@ namespace Mist {
} }
int Input::run() { int Input::run() {
streamName = config->getString("streamname");
if (config->getBool("json")) { if (config->getBool("json")) {
std::cout << capa.toString() << std::endl; std::cout << capa.toString() << std::endl;
return 0; return 0;
} }
if (streamName != "") {
config->getOption("streamname") = streamName;
}
streamName = config->getString("streamname");
nProxy.streamName = streamName;
if (!setup()){ if (!setup()){
std::cerr << config->getString("cmd") << " setup failed." << std::endl; std::cerr << config->getString("cmd") << " setup failed." << std::endl;
return 0; return 0;
} }
checkHeaderTimes(config->getString("input")); checkHeaderTimes(config->getString("input"));
if (!readHeader()){ if (!readHeader()){
std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl; std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl;
@ -115,7 +123,7 @@ namespace Mist {
} }
parseHeader(); parseHeader();
if (!config->getString("streamname").size()){ if (!streamName.size()) {
convert(); convert();
}else{ }else{
serve(); serve();
@ -155,28 +163,32 @@ namespace Mist {
} }
void Input::serve(){ 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){ if (!isBuffer){
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
bufferFrame(it->first, 1); 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()); DEBUG_MSG(DLVL_DEVEL,"Input for stream %s started", streamName.c_str());
long long int activityCounter = Util::bootSecs(); long long int activityCounter = Util::bootSecs();
while ((Util::bootSecs() - activityCounter) < 10 && config->is_active){//10 second timeout while ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT && config->is_active) { //15 second timeout
Util::wait(1000);
removeUnused();
userPage.parseEach(callbackWrapper); userPage.parseEach(callbackWrapper);
if (userPage.amount){ removeUnused();
if (userPage.connectedUsers) {
if (myMeta.tracks.size()){
activityCounter = Util::bootSecs(); activityCounter = Util::bootSecs();
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.amount); }
DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.connectedUsers);
}else{ }else{
DEBUG_MSG(DLVL_INSANE, "Timer running"); DEBUG_MSG(DLVL_INSANE, "Timer running");
} }
if (config->is_active){
Util::wait(1000);
}
} }
finish(); finish();
DEBUG_MSG(DLVL_DEVEL,"Input for stream %s closing clean", streamName.c_str()); DEBUG_MSG(DLVL_DEVEL,"Input for stream %s closing clean", streamName.c_str());
@ -191,7 +203,7 @@ namespace Mist {
} }
removeUnused(); removeUnused();
if (standAlone){ 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; it->second.master = true;
} }
} }
@ -210,9 +222,9 @@ namespace Mist {
bufferRemove(it->first, it2->first); bufferRemove(it->first, it2->first);
pageCounter[it->first].erase(it2->first); pageCounter[it->first].erase(it2->first);
for (int i = 0; i < 8192; i += 8){ 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){ 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; change = true;
@ -253,13 +265,13 @@ namespace Mist {
for (int i = 0; i < it->second.keys.size(); i++){ for (int i = 0; i < it->second.keys.size(); i++){
if (newData){ if (newData){
//i+1 because keys are 1-indexed //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; newData = false;
} }
pagesByTrack[it->first].rbegin()->second.keyNum++; nProxy.pagesByTrack[it->first].rbegin()->second.keyNum++;
pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts(); nProxy.pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts();
pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i]; nProxy.pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i];
if (pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE){ if (nProxy.pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) {
newData = true; newData = true;
} }
} }
@ -292,7 +304,7 @@ namespace Mist {
} }
if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){ if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){
if (curData[tid].dataSize > FLIP_DATA_PAGE_SIZE) { 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; bookKeeping[tid].first += curData[tid].keyNum;
curData[tid].keyNum = 0; curData[tid].keyNum = 0;
curData[tid].dataSize = 0; curData[tid].dataSize = 0;
@ -309,17 +321,17 @@ namespace Mist {
getNext(false); getNext(false);
} }
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { 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)){ if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].first)) {
pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->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++){ 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); DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first);
}else{ }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()); 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 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++){ 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); 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){ 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()){ if (keyNum > myMeta.tracks[track].keys.size()){
//End of movie here, returning true to avoid various error messages //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; return true;
} }
if (keyNum < 1){keyNum = 1;} if (keyNum < 1) {
//abort in case already buffered keyNum = 1;
int pageNumber = bufferedOnPage(track, keyNum); }
if (pageNumber){ 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; pageCounter[track][pageNumber] = 15;
VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber); VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber);
return true; return true;
} }
if (!pagesByTrack.count(track)){ if (!nProxy.pagesByTrack.count(track)) {
WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track); WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track);
return false; return false;
} }
//Update keynum to point to the corresponding page //Update keynum to point to the corresponding page
INFO_MSG("Loading key %u from page %lu", keyNum, (--(pagesByTrack[track].upper_bound(keyNum)))->first); INFO_MSG("Loading key %u from page %lu", keyNum, (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first);
keyNum = (--(pagesByTrack[track].upper_bound(keyNum)))->first; keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first;
if (!bufferStart(track, keyNum)){ if (!bufferStart(track, keyNum)){
WARN_MSG("bufferStart failed! Cancelling bufferFrame", track); WARN_MSG("bufferStart failed! Cancelling bufferFrame");
return false; return false;
} }
@ -359,8 +380,8 @@ namespace Mist {
trackSelect(trackSpec.str()); trackSelect(trackSpec.str());
seek(myMeta.tracks[track].keys[keyNum - 1].getTime()); seek(myMeta.tracks[track].keys[keyNum - 1].getTime());
long long unsigned int stopTime = myMeta.tracks[track].lastms + 1; long long unsigned int stopTime = myMeta.tracks[track].lastms + 1;
if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + pagesByTrack[track][keyNum].keyNum){ if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum) {
stopTime = myMeta.tracks[track].keys[keyNum - 1 + pagesByTrack[track][keyNum].keyNum].getTime(); 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); DEBUG_MSG(DLVL_HIGH, "Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime);
getNext(); getNext();

View file

@ -20,7 +20,11 @@ namespace Mist {
public: public:
Input(Util::Config * cfg); Input(Util::Config * cfg);
virtual int run(); virtual int run();
virtual void onCrash(){}
virtual void argumentsParsed(){}
virtual ~Input() {}; virtual ~Input() {};
virtual bool needsLock(){return true;}
protected: protected:
static void callbackWrapper(char * data, size_t len, unsigned int id); static void callbackWrapper(char * data, size_t len, unsigned int id);
virtual bool setup() = 0; virtual bool setup() = 0;
@ -29,6 +33,9 @@ namespace Mist {
virtual void getNext(bool smart = true) {}; virtual void getNext(bool smart = true) {};
virtual void seek(int seekTime){}; virtual void seek(int seekTime){};
virtual void finish(); virtual void finish();
virtual bool openStreamSource() { return false; };
virtual void closeStreamSource() {};
virtual void parseStreamHeader() {};
void play(int until = 0); void play(int until = 0);
void playOnce(); void playOnce();
void quitPlay(); void quitPlay();
@ -36,11 +43,11 @@ namespace Mist {
virtual void removeUnused(); virtual void removeUnused();
virtual void trackSelect(std::string trackSpec){}; virtual void trackSelect(std::string trackSpec){};
virtual void userCallback(char * data, size_t len, unsigned int id); virtual void userCallback(char * data, size_t len, unsigned int id);
virtual void convert();
virtual void serve();
void serve();
void convert();
void parseHeader(); virtual void parseHeader();
bool bufferFrame(unsigned int track, unsigned int keyNum); bool bufferFrame(unsigned int track, unsigned int keyNum);
unsigned int packTime;///Media-timestamp of the last packet. unsigned int packTime;///Media-timestamp of the last packet.

View file

@ -13,7 +13,7 @@
#include "input_buffer.h" #include "input_buffer.h"
#ifndef TIMEOUTMULTIPLIER #ifndef TIMEOUTMULTIPLIER
#define TIMEOUTMULTIPLIER 10 #define TIMEOUTMULTIPLIER 2
#endif #endif
namespace Mist { namespace Mist {
@ -41,6 +41,8 @@ namespace Mist {
singleton = this; singleton = this;
bufferTime = 50000; bufferTime = 50000;
cutTime = 0; cutTime = 0;
hasPush = false;
resumeMode = false;
} }
inputBuffer::~inputBuffer() { inputBuffer::~inputBuffer() {
@ -48,16 +50,111 @@ namespace Mist {
if (myMeta.tracks.size()) { if (myMeta.tracks.size()) {
DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes"); 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++) { 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() { void inputBuffer::updateMeta() {
long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull; long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull;
long long unsigned int lastms = 0; long long unsigned int lastms = 0;
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { 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 (it->second.init.size()){
if (!initData.count(it->first) || initData[it->first] != it->second.init){ if (!initData.count(it->first) || initData[it->first] != it->second.init){
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()); snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
liveMeta.wait(); liveMeta.wait();
if (!metaPages.count(0) || !metaPages[0].mapped) { if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped) {
char pageName[NAME_BUFFER_SIZE]; char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str());
metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true); nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true);
metaPages[0].master = false; nProxy.metaPages[0].master = false;
} }
myMeta.writeTo(metaPages[0].mapped); myMeta.writeTo(nProxy.metaPages[0].mapped);
memset(metaPages[0].mapped + myMeta.getSendLen(), 0, (metaPages[0].len > myMeta.getSendLen() ? std::min(metaPages[0].len - myMeta.getSendLen(), 4ll) : 0)); 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(); liveMeta.post();
} }
@ -118,30 +215,15 @@ namespace Mist {
if (bufferLocations[tid].size() > 1) { if (bufferLocations[tid].size() > 1) {
//Check if the first key starts on the second page or higher //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) { 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); 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); 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]; 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); 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); nProxy.curPage[tid].init(thisPageName, 20971520);
curPage[tid].master = true; nProxy.curPage[tid].master = true;
curPage.erase(tid); nProxy.curPage.erase(tid);
bufferLocations[tid].erase(bufferLocations[tid].begin()); bufferLocations[tid].erase(bufferLocations[tid].begin());
} else { } else {
@ -158,13 +240,13 @@ namespace Mist {
for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){ for (std::map<unsigned long, DTSCPageData>::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){
char thisPageName[NAME_BUFFER_SIZE]; char thisPageName[NAME_BUFFER_SIZE];
snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first); snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first);
curPage[tid].init(thisPageName, 20971520, false, false); nProxy.curPage[tid].init(thisPageName, 20971520, false, false);
curPage[tid].master = true; nProxy.curPage[tid].master = true;
curPage.erase(tid); nProxy.curPage.erase(tid);
} }
bufferLocations.erase(tid); bufferLocations.erase(tid);
metaPages[tid].master = true; nProxy.metaPages[tid].master = true;
metaPages.erase(tid); nProxy.metaPages.erase(tid);
} }
void inputBuffer::finish() { void inputBuffer::finish() {
@ -189,11 +271,13 @@ namespace Mist {
long long unsigned int time = Util::bootSecs(); long long unsigned int time = Util::bootSecs();
long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull; long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull;
long long unsigned int compareLast = 0; 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 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++) { for (std::map<unsigned int, DTSC::Track>::iterator it2 = myMeta.tracks.begin(); it2 != myMeta.tracks.end(); it2++) {
if ((time - lastUpdated[it2->first]) > 5) { if ((time - lastUpdated[it2->first]) > 5) {
continue; continue;
} }
activeTypes.insert(it2->second.type);
if (it2->second.lastms > compareLast) { if (it2->second.lastms > compareLast) {
compareLast = it2->second.lastms; 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++) { 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 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) || 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) (compareLast < it->second.firstms && (long long int)(it->second.firstms - compareLast) > bufferTime)
|| ||
(compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime) (compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime)
@ -213,26 +297,26 @@ namespace Mist {
unsigned int tid = it->first; unsigned int tid = it->first;
//erase this track //erase this track
if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000)){ 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{ }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); lastUpdated.erase(tid);
/// \todo Consider replacing with eraseTrackDataPages(it->first)? /// \todo Consider replacing with eraseTrackDataPages(it->first)?
while (bufferLocations[tid].size()){ while (bufferLocations[tid].size()){
char thisPageName[NAME_BUFFER_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); 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); nProxy.curPage[tid].init(thisPageName, 20971520);
curPage[tid].master = true; nProxy.curPage[tid].master = true;
curPage.erase(tid); nProxy.curPage.erase(tid);
bufferLocations[tid].erase(bufferLocations[tid].begin()); bufferLocations[tid].erase(bufferLocations[tid].begin());
} }
if (pushLocation.count(it->first)){ if (pushLocation.count(it->first)){
pushLocation.erase(it->first); pushLocation.erase(it->first);
} }
curPageNum.erase(it->first); nProxy.curPageNum.erase(it->first);
metaPages[it->first].master = true; nProxy.metaPages[it->first].master = true;
metaPages.erase(it->first); nProxy.metaPages.erase(it->first);
activeTracks.erase(it->first); activeTracks.erase(it->first);
myMeta.tracks.erase(it->first); myMeta.tracks.erase(it->first);
changed = true; 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++) { 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 //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.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; continue;
} }
} }
@ -270,6 +355,14 @@ namespace Mist {
} }
} }
updateMeta(); 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) { void inputBuffer::userCallback(char * data, size_t len, unsigned int id) {
@ -278,10 +371,10 @@ namespace Mist {
//Get the counter of this user //Get the counter of this user
char counter = (*(data - 1)); char counter = (*(data - 1));
//Each user can have at maximum SIMUL_TRACKS elements in their userpage. //Each user can have at maximum SIMUL_TRACKS elements in their userpage.
IPC::userConnection userConn(data);
for (int index = 0; index < SIMUL_TRACKS; index++) { for (int index = 0; index < SIMUL_TRACKS; index++) {
char * thisData = data + (index * 6);
//Get the track id from the current element //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 //Skip value 0xFFFFFFFF as this indicates a previously declined track
if (value == 0xFFFFFFFF) { if (value == 0xFFFFFFFF) {
continue; continue;
@ -294,12 +387,10 @@ namespace Mist {
//If the current value indicates a valid trackid, and it is pushed from this user //If the current value indicates a valid trackid, and it is pushed from this user
if (pushLocation[value] == data) { if (pushLocation[value] == data) {
//Check for timeouts, and erase the track if necessary //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); pushLocation.erase(value);
if (negotiatingTracks.count(value)) { if (negotiatingTracks.count(value)) {
negotiatingTracks.erase(value); negotiatingTracks.erase(value);
metaPages[value].master = true;
metaPages.erase(value);
} }
if (activeTracks.count(value)) { if (activeTracks.count(value)) {
updateMeta(); updateMeta();
@ -307,8 +398,8 @@ namespace Mist {
activeTracks.erase(value); activeTracks.erase(value);
bufferLocations.erase(value); bufferLocations.erase(value);
} }
metaPages[value].master = true; nProxy.metaPages[value].master = true;
metaPages.erase(value); nProxy.metaPages.erase(value);
continue; continue;
} }
} }
@ -320,28 +411,24 @@ namespace Mist {
//Add the temporary track id to the list of tracks that are currently being negotiated //Add the temporary track id to the list of tracks that are currently being negotiated
negotiatingTracks.insert(tempMapping); negotiatingTracks.insert(tempMapping);
//Write the temporary id to the userpage element //Write the temporary id to the userpage element
thisData[0] = (tempMapping >> 24) & 0xFF; userConn.setTrackId(index, tempMapping);
thisData[1] = (tempMapping >> 16) & 0xFF;
thisData[2] = (tempMapping >> 8) & 0xFF;
thisData[3] = (tempMapping) & 0xFF;
//Obtain the original track number for the pushing process //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 //Overwrite it with 0xFFFF
thisData[4] = 0xFF; userConn.setKeynum(index, 0xFFFF);
thisData[5] = 0xFF;
DEBUG_MSG(DLVL_HIGH, "Incoming track %lu from pushing process %d has now been assigned temporary id %llu", originalTrack, id, tempMapping); 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 //The track id is set to the value of a track that we are currently negotiating about
if (negotiatingTracks.count(value)) { if (negotiatingTracks.count(value)) {
//If the metadata page for this track is not yet registered, initialize it //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]; char tempMetaName[NAME_BUFFER_SIZE];
snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, config->getString("streamname").c_str(), value); 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 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 //remove the negotiation if it has timed out
if (++negotiationTimeout[value] >= 1000){ if (++negotiationTimeout[value] >= 1000){
negotiatingTracks.erase(value); negotiatingTracks.erase(value);
@ -353,13 +440,13 @@ namespace Mist {
//The page exist, now we try to read in the metadata of the track //The page exist, now we try to read in the metadata of the track
//Store the size of the dtsc packet to read. //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 //Temporary variable, won't be used again
unsigned int tempForReadingMeta = 0; unsigned int tempForReadingMeta = 0;
//Read in the metadata through a temporary JSON object //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 ///\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::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 //Construct a metadata object for the current track
DTSC::Meta trackMeta(tempJSONForMeta); 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. //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){ if (++negotiationTimeout[value] >= 1000){
negotiatingTracks.erase(value); negotiatingTracks.erase(value);
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages //Set master to true before erasing the page, because we are responsible for cleaning up unused pages
metaPages[value].master = true; nProxy.metaPages[value].master = true;
metaPages.erase(value); nProxy.metaPages.erase(value);
negotiationTimeout.erase(value); negotiationTimeout.erase(value);
} }
continue; continue;
@ -381,8 +468,8 @@ namespace Mist {
//Remove the "negotiate" status in either case //Remove the "negotiate" status in either case
negotiatingTracks.erase(value); negotiatingTracks.erase(value);
//Set master to true before erasing the page, because we are responsible for cleaning up unused pages //Set master to true before erasing the page, because we are responsible for cleaning up unused pages
metaPages[value].master = true; nProxy.metaPages[value].master = true;
metaPages.erase(value); nProxy.metaPages.erase(value);
int finalMap = 3; int finalMap = 3;
if (trackMeta.tracks.find(value)->second.type == "video"){finalMap = 1;} 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 //Set master to true before erasing the page, because we are responsible for cleaning up unused pages
updateMeta(); updateMeta();
eraseTrackDataPages(value); eraseTrackDataPages(value);
metaPages[finalMap].master = true; nProxy.metaPages[finalMap].master = true;
metaPages.erase(finalMap); nProxy.metaPages.erase(finalMap);
bufferLocations.erase(finalMap); bufferLocations.erase(finalMap);
} }
@ -415,43 +502,39 @@ namespace Mist {
pushLocation[finalMap] = data; pushLocation[finalMap] = data;
//Initialize the metadata for this track if it was not in place yet. //Initialize the metadata for this track if it was not in place yet.
if (!myMeta.tracks.count(finalMap)) { 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] = trackMeta.tracks.begin()->second;
myMeta.tracks[finalMap].trackID = finalMap; myMeta.tracks[finalMap].trackID = finalMap;
} }
//Write the final mapped track number to the user page element //Write the final mapped track number and keyframe 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.
//This is used to resume pushing as well as pushing new tracks //This is used to resume pushing as well as pushing new tracks
unsigned long keyNum = myMeta.tracks[finalMap].keys.size(); userConn.setTrackId(index, finalMap);
thisData[4] = (keyNum >> 8) & 0xFF; userConn.setKeynum(index, myMeta.tracks[finalMap].keys.size());
thisData[5] = keyNum & 0xFF;
//Update the metadata to reflect all changes //Update the metadata to reflect all changes
updateMeta(); updateMeta();
} }
//If the track is active, and this is the element responsible for pushing it //If the track is active, and this is the element responsible for pushing it
if (activeTracks.count(value) && pushLocation[value] == data) { if (activeTracks.count(value) && pushLocation[value] == data) {
//Open the track index page if we dont have it open yet //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]; char firstPage[NAME_BUFFER_SIZE];
snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value); 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 //Update the metadata for this track
updateTrackMeta(value); updateTrackMeta(value);
hasPush = true;
} }
} }
} }
} }
void inputBuffer::updateTrackMeta(unsigned long tNum) { void inputBuffer::updateTrackMeta(unsigned long tNum) {
VERYHIGH_MSG("Updating meta for track %d", tNum);
//Store a reference for easier access //Store a reference for easier access
std::map<unsigned long, DTSCPageData> & locations = bufferLocations[tNum]; 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 //First detect all entries on metaPage
for (int i = 0; i < 8192; i += 8) { for (int i = 0; i < 8192; i += 8) {
@ -460,6 +543,7 @@ namespace Mist {
continue; continue;
} }
unsigned long keyNum = ntohl(tmpOffset[0]); 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. //Add an entry into bufferLocations[tNum] for the pages we haven't handled yet.
if (!locations.count(keyNum)) { if (!locations.count(keyNum)) {
@ -468,7 +552,6 @@ namespace Mist {
locations[keyNum].pageNum = keyNum; locations[keyNum].pageNum = keyNum;
locations[keyNum].keyNum = ntohl(tmpOffset[1]); 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 //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++) { for (std::map<unsigned long, DTSCPageData>::iterator pageIt = locations.begin(); pageIt != locations.end(); pageIt++) {
updateMetaFromPage(tNum, pageIt->first); updateMetaFromPage(tNum, pageIt->first);
@ -477,6 +560,7 @@ namespace Mist {
} }
void inputBuffer::updateMetaFromPage(unsigned long tNum, unsigned long pageNum) { 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]; DTSCPageData & pageData = bufferLocations[tNum][pageNum];
//If the current page is over its 8mb "splitting" boundary //If the current page is over its 8mb "splitting" boundary
@ -491,27 +575,27 @@ namespace Mist {
//Otherwise open and parse the page //Otherwise open and parse the page
//Open the page if it is not yet open //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 //DO NOT ERASE THE PAGE HERE, master is not set to true
curPageNum.erase(tNum); nProxy.curPageNum.erase(tNum);
char nextPageName[NAME_BUFFER_SIZE]; char nextPageName[NAME_BUFFER_SIZE];
snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tNum, pageNum); 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 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); WARN_MSG("Could not open page: %s", nextPageName);
return; return;
} }
curPageNum[tNum] = pageNum; nProxy.curPageNum[tNum] = pageNum;
} }
DTSC::Packet tmpPack; 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); VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum);
return; 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 //No new data has been written on the page since last update
if (!tmpPack) { if (!tmpPack) {
return; return;
@ -527,7 +611,7 @@ namespace Mist {
//Update the offset on the page with the size of the current packet //Update the offset on the page with the size of the current packet
pageData.curOffset += tmpPack.getDataLen(); pageData.curOffset += tmpPack.getDataLen();
//Attempt to read in the next packet //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"); std::string strName = config->getString("streamname");
Util::sanitizeName(strName); Util::sanitizeName(strName);
strName = strName.substr(0, (strName.find_first_of("+ "))); strName = strName.substr(0, (strName.find_first_of("+ ")));
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
configLock.wait(); configLock.wait();
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(strName); DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(strName);
long long tmpNum; long long tmpNum;
@ -553,7 +637,9 @@ namespace Mist {
tmpNum = config->getOption("bufferTime").asInt(); 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 the new value is different, print a message and apply it
if (bufferTime != tmpNum) { if (bufferTime != tmpNum) {
DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum); DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum);

View file

@ -7,9 +7,12 @@ namespace Mist {
public: public:
inputBuffer(Util::Config * cfg); inputBuffer(Util::Config * cfg);
~inputBuffer(); ~inputBuffer();
void onCrash();
private: private:
unsigned int bufferTime; unsigned int bufferTime;
unsigned int cutTime; unsigned int cutTime;
bool hasPush;
bool resumeMode;
protected: protected:
//Private Functions //Private Functions
bool setup(); bool setup();
@ -33,8 +36,7 @@ namespace Mist {
std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations; std::map<unsigned long, std::map<unsigned long, DTSCPageData> > bufferLocations;
std::map<unsigned long, char *> pushLocation; std::map<unsigned long, char *> pushLocation;
inputBuffer * singleton; inputBuffer * singleton;
//This is used for an ugly fix to prevent metadata from disappearing in some cases.
//This is used for an ugly fix to prevent metadata from dissapearing in some cases.
std::map<unsigned long, std::string> initData; std::map<unsigned long, std::string> initData;
}; };
} }

View file

@ -7,6 +7,9 @@
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/defines.h> #include <mist/defines.h>
#include <mist/util.h>
#include <mist/bitfields.h>
#include "input_dtsc.h" #include "input_dtsc.h"
namespace Mist { namespace Mist {
@ -14,7 +17,8 @@ namespace Mist {
capa["name"] = "DTSC"; capa["name"] = "DTSC";
capa["desc"] = "Enables DTSC Input"; capa["desc"] = "Enables DTSC Input";
capa["priority"] = 9ll; 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("H264");
capa["codecs"][0u][0u].append("H263"); capa["codecs"][0u][0u].append("H263");
capa["codecs"][0u][0u].append("VP6"); capa["codecs"][0u][0u].append("VP6");
@ -24,7 +28,142 @@ namespace Mist {
capa["codecs"][0u][1u].append("vorbis"); 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() { bool inputDTSC::setup() {
if (!needsLock()) {
return true;
} else {
if (config->getString("input") == "-") { if (config->getString("input") == "-") {
std::cerr << "Input from stdin not yet supported" << std::endl; std::cerr << "Input from stdin not yet supported" << std::endl;
return false; return false;
@ -46,10 +185,14 @@ namespace Mist {
if (!inFile) { if (!inFile) {
return false; return false;
} }
}
return true; return true;
} }
bool inputDTSC::readHeader() { bool inputDTSC::readHeader() {
if (!needsLock()) {
return true;
}
if (!inFile) { if (!inFile) {
return false; return false;
} }
@ -69,6 +212,55 @@ namespace Mist {
} }
void inputDTSC::getNext(bool smart) { 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){ if (smart){
inFile.seekNext(); inFile.seekNext();
}else{ }else{
@ -76,6 +268,7 @@ namespace Mist {
} }
thisPacket = inFile.getPacket(); thisPacket = inFile.getPacket();
} }
}
void inputDTSC::seek(int seekTime) { void inputDTSC::seek(int seekTime) {
inFile.seek_time(seekTime); inFile.seek_time(seekTime);

View file

@ -5,8 +5,12 @@ namespace Mist {
class inputDTSC : public Input { class inputDTSC : public Input {
public: public:
inputDTSC(Util::Config * cfg); inputDTSC(Util::Config * cfg);
bool needsLock();
protected: protected:
//Private Functions //Private Functions
bool openStreamSource();
void closeStreamSource();
void parseStreamHeader();
bool setup(); bool setup();
bool readHeader(); bool readHeader();
void getNext(bool smart = true); void getNext(bool smart = true);
@ -14,6 +18,8 @@ namespace Mist {
void trackSelect(std::string trackSpec); void trackSelect(std::string trackSpec);
DTSC::File inFile; DTSC::File inFile;
Socket::Connection srcConn;
}; };
} }

View file

@ -122,7 +122,7 @@ namespace Mist {
} }
} }
if (!offset){ 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; return;
} }
filePos += offset; filePos += offset;

View file

@ -16,24 +16,34 @@ int main(int argc, char * argv[]) {
mistIn conv(&conf); mistIn conv(&conf);
if (conf.parseArgs(argc, argv)) { if (conf.parseArgs(argc, argv)) {
std::string streamName = conf.getString("streamname"); std::string streamName = conf.getString("streamname");
conv.argumentsParsed();
IPC::semaphore playerLock; IPC::semaphore playerLock;
if (conv.needsLock()){
if (streamName.size()){ if (streamName.size()){
playerLock.open(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());
playerLock.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1);
if (!playerLock.tryWait()){ if (!playerLock.tryWait()){
DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str()); DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str());
return 1; return 1;
} }
} }
}
conf.activate(); conf.activate();
while (conf.is_active){ while (conf.is_active){
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0){ if (pid == 0){
if (conv.needsLock()){
playerLock.close(); playerLock.close();
}
return conv.run(); return conv.run();
} }
if (pid == -1){ if (pid == -1){
DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process"); DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process");
if (conv.needsLock()){
playerLock.post(); playerLock.post();
}
return 2; return 2;
} }
//wait for the process to exit //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()); DEBUG_MSG(DLVL_MEDIUM, "Input for stream %s shut down cleanly", streamName.c_str());
break; break;
} }
#if DEBUG >= DLVL_DEVEL
WARN_MSG("Aborting autoclean; this is a development build.");
#else
conv.onCrash();
#endif
if (DEBUG >= DLVL_DEVEL){ 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()); DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Aborting restart; this is a development build.", streamName.c_str());
break; break;
@ -57,9 +72,12 @@ int main(int argc, char * argv[]) {
DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Restarting...", streamName.c_str()); DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Restarting...", streamName.c_str());
} }
} }
if (conv.needsLock()){
playerLock.post(); playerLock.post();
playerLock.unlink();
playerLock.close(); playerLock.close();
} }
}
return 0; return 0;
} }

View file

@ -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 <mist/bitfields.h>
#include <cstdlib>
#include "io.h" #include "io.h"
namespace Mist { namespace Mist {
@ -11,12 +16,29 @@ namespace Mist {
//Open the page for the metadata //Open the page for the metadata
char pageName[NAME_BUFFER_SIZE]; char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); 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 //Make sure we don't delete it on accident
metaPages[0].master = false; nProxy.metaPages[0].master = false;
//Write the metadata to the page //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. ///Starts the buffering of a new page.
@ -26,30 +48,38 @@ namespace Mist {
///Buffering itself is done by bufferNext(). ///Buffering itself is done by bufferNext().
///\param tid The trackid of the page to start buffering ///\param tid The trackid of the page to start buffering
///\param pageNumber The number 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) { bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta) {
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;
}
}
//Negotiate the requested track if needed. //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 the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later.
if (trackState[tid] != FILL_ACC) { if (trackState[tid] != FILL_ACC) {
///\return false if the track has not been accepted (yet) ///\return false if the track has not been accepted (yet)
return false; return false;
} }
//If the track is accepted, we will have a mapped tid //If the track is accepted, we will have a mapped tid
unsigned long mapTid = trackMap[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 //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. //This page will NEVER be deleted, unless we open it again later.
if (curPage.count(tid)) { 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() //Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext()
pagesByTrack[tid][pageNumber].curOffset = 0; pagesByTrack[tid][pageNumber].curOffset = 0;
HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid);
if (myMeta.live){ if (myMeta.live){
//Register this page on the meta page //Register this page on the meta page
//NOTE: It is important that this only happens if the stream is live.... //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)); int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) { if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) {
tmpOffset[0] = htonl(curPageNum[tid]); tmpOffset[0] = htonl(curPageNum[tid]);
if (pagesByTrack[tid][pageNumber].dataSize == (25 * 1024 * 1024)){
tmpOffset[1] = htonl(1000); tmpOffset[1] = htonl(1000);
} else {
tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum);
}
inserted = true; inserted = true;
break; 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 if everything was successful
return true; return true;
} }
@ -128,13 +160,28 @@ namespace Mist {
//A different process will handle this for us //A different process will handle this for us
return; return;
} }
unsigned long mapTid = trackMap[tid]; unsigned long mapTid = nProxy.trackMap[tid];
if (!pagesByTrack.count(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) // If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer)
return; return;
} }
//If the given pagenumber is not a valid page on this track, do nothing //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); INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", pageNumber, tid, mapTid);
return; return;
} }
@ -146,23 +193,14 @@ namespace Mist {
#ifdef __CYGWIN__ #ifdef __CYGWIN__
toErase.init(pageName, 26 * 1024 * 1024, false); toErase.init(pageName, 26 * 1024 * 1024, false);
#else #else
toErase.init(pageName, pagesByTrack[tid][pageNumber].dataSize, false); toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false);
#endif #endif
//Set the master flag so that the page will be destroyed once it leaves scope //Set the master flag so that the page will be destroyed once it leaves scope
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
IPC::releasePage(pageName); IPC::releasePage(pageName);
#endif #endif
toErase.master = true; toErase.master = true;
//Remove the page from the tracks index page //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 //Leaving scope here, the page will now be destroyed
} }
@ -170,7 +208,7 @@ namespace Mist {
///Checks whether a key is buffered ///Checks whether a key is buffered
///\param tid The trackid on which to locate the key ///\param tid The trackid on which to locate the key
///\param keyNum The number of the keyframe to find ///\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 The result of bufferedOnPage(tid, keyNum)
return bufferedOnPage(tid, keyNum); return bufferedOnPage(tid, keyNum);
} }
@ -178,24 +216,24 @@ namespace Mist {
///Returns the pagenumber where this key is buffered on ///Returns the pagenumber where this key is buffered on
///\param tid The trackid on which to locate the key ///\param tid The trackid on which to locate the key
///\param keyNum The number of the keyframe to find ///\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 //Check whether the track is accepted
if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) { if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) {
///\return 0 if the page has not been mapped yet ///\return 0 if the page has not been mapped yet
return 0; return 0;
} }
//Loop over the index page //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 * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8));
int pageNum = ntohl(tmpOffset[0]); unsigned int keyAmount = ntohl(tmpOffset[1]);
int keyAmount = ntohl(tmpOffset[1]); if (keyAmount == 0){continue;}
//Check whether the key is on this page //Check whether the key is on this page
unsigned int pageNum = ntohl(tmpOffset[0]);
if (pageNum <= keyNum && keyNum < pageNum + keyAmount) { 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 pageNum;
} }
} }
///\return 0 if the key was not found
return 0; return 0;
} }
@ -205,12 +243,16 @@ namespace Mist {
std::string packData = pack.toNetPacked(); std::string packData = pack.toNetPacked();
DTSC::Packet newPack(packData.data(), packData.size()); DTSC::Packet newPack(packData.data(), packData.size());
///\note Internally calls bufferNext(DTSC::Packet & pack) ///\note Internally calls bufferNext(DTSC::Packet & pack)
bufferNext(newPack); nProxy.bufferNext(newPack, myMeta);
} }
///Buffers the next packet on the currently opened page ///Buffers the next packet on the currently opened page
///\param pack The packet to buffer ///\param pack The packet to buffer
void InOutBase::bufferNext(DTSC::Packet & pack) { 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 //Save the trackid of the track for easier access
unsigned long tid = pack.getTrackId(); unsigned long tid = pack.getTrackId();
unsigned long mapTid = trackMap[tid]; unsigned long mapTid = trackMap[tid];
@ -261,6 +303,10 @@ namespace Mist {
///Registers the data page on the track index page as well ///Registers the data page on the track index page as well
///\param tid The trackid of the page to finalize ///\param tid The trackid of the page to finalize
void InOutBase::bufferFinalize(unsigned long tid) { void InOutBase::bufferFinalize(unsigned long tid) {
nProxy.bufferFinalize(tid, myMeta);
}
void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta & myMeta){
unsigned long mapTid = trackMap[tid]; unsigned long mapTid = trackMap[tid];
//If no page is open, do nothing //If no page is open, do nothing
if (!curPage.count(tid)) { if (!curPage.count(tid)) {
@ -342,6 +388,10 @@ namespace Mist {
///Initiates/continues negotiation with the buffer as well ///Initiates/continues negotiation with the buffer as well
///\param packet The packet to buffer ///\param packet The packet to buffer
void InOutBase::bufferLivePacket(DTSC::Packet & packet){ void InOutBase::bufferLivePacket(DTSC::Packet & packet){
nProxy.bufferLivePacket(packet, myMeta);
}
void negotiationProxy::bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta){
myMeta.vod = false; myMeta.vod = false;
myMeta.live = true; myMeta.live = true;
//Store the trackid for easier access //Store the trackid for easier access
@ -353,31 +403,13 @@ namespace Mist {
} }
//If the track is not negotiated yet, start the negotiation //If the track is not negotiated yet, start the negotiation
if (!trackState.count(tid)) { if (!trackState.count(tid)) {
continueNegotiate(tid); continueNegotiate(tid, myMeta);
} }
//If the track is declined, stop here //If the track is declined, stop here
if (trackState[tid] == FILL_DEC) { if (trackState[tid] == FILL_DEC) {
INFO_MSG("Track %lu Declined", tid); INFO_MSG("Track %lu Declined", tid);
return; 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. //This update needs to happen whether the track is accepted or not.
///\todo Figure out how to act with declined track here ///\todo Figure out how to act with declined track here
bool isKeyframe = false; bool isKeyframe = false;
@ -398,21 +430,21 @@ namespace Mist {
} }
//Determine if we need to open the next page //Determine if we need to open the next page
int nextPageNum = -1; 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 there is no page, create it
if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) { if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) {
nextPageNum = 1; 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; pagesByTrack[tid][1].pageNum = 1;
} }
//Take the last allocated page //Take the last allocated page
std::map<unsigned long, DTSCPageData>::reverse_iterator tmpIt = pagesByTrack[tid].rbegin(); std::map<unsigned long, DTSCPageData>::reverse_iterator tmpIt = pagesByTrack[tid].rbegin();
//Compare on 8 mb boundary //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 //Create the book keeping data for the new page
nextPageNum = tmpIt->second.pageNum + tmpIt->second.keyNum; 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); 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][nextPageNum].pageNum = nextPageNum;
} }
pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime(); pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime();
@ -426,6 +458,10 @@ namespace Mist {
nextPageNum = 1; 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 //At this point we can stop parsing when the track is not accepted
if (trackState[tid] != FILL_ACC) { if (trackState[tid] != FILL_ACC) {
return; return;
@ -435,16 +471,20 @@ namespace Mist {
if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) { if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) {
if (curPageNum.count(tid)) { if (curPageNum.count(tid)) {
//Close the currently opened page when it exists //Close the currently opened page when it exists
bufferFinalize(tid); bufferFinalize(tid, myMeta);
} }
//Open the new page //Open the new page
bufferStart(tid, nextPageNum); bufferStart(tid, nextPageNum, myMeta);
} }
//Buffer the packet //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) { if (!tid) {
return; return;
} }
@ -457,7 +497,7 @@ namespace Mist {
trackState[tid] = FILL_ACC; trackState[tid] = FILL_ACC;
char pageName[NAME_BUFFER_SIZE]; char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), tid); 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; metaPages[tid].master = false;
return; return;
} }
@ -490,7 +530,7 @@ namespace Mist {
if (!userClient.getData()){ if (!userClient.getData()){
char userPageName[100]; char userPageName[100];
sprintf(userPageName, SHM_USERS, streamName.c_str()); 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(); char * tmp = userClient.getData();
if (!tmp) { if (!tmp) {
@ -500,12 +540,47 @@ namespace Mist {
unsigned long offset = 6 * trackOffset[tid]; unsigned long offset = 6 * trackOffset[tid];
//If we have a new track to negotiate //If we have a new track to negotiate
if (!trackState.count(tid)) { if (!trackState.count(tid)) {
memset(tmp + offset, 0, 4);
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]); INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]);
memset(tmp + offset, 0, 4); memset(tmp + offset, 0, 4);
tmp[offset] = 0x80; tmp[offset] = 0x80;
tmp[offset + 4] = ((tid >> 8) & 0xFF); tmp[offset + 4] = ((tid >> 8) & 0xFF);
tmp[offset + 5] = (tid & 0xFF); tmp[offset + 5] = (tid & 0xFF);
trackState[tid] = FILL_NEW; trackState[tid] = FILL_NEW;
}
return; return;
} }
#if defined(__CYGWIN__) || defined(_WIN32) #if defined(__CYGWIN__) || defined(_WIN32)
@ -579,7 +654,7 @@ namespace Mist {
trackState[tid] = FILL_ACC; trackState[tid] = FILL_ACC;
char pageName[NAME_BUFFER_SIZE]; char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid); 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; metaPages[tid].master = false;
break; break;
} }

View file

@ -25,6 +25,36 @@ namespace Mist {
unsigned long lastKeyTime;///<The last key time encountered on this track. 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. ///\brief Class containing all basic input and output functions.
class InOutBase { class InOutBase {
public: public:
@ -36,32 +66,23 @@ namespace Mist {
void bufferRemove(unsigned long tid, unsigned long pageNumber); void bufferRemove(unsigned long tid, unsigned long pageNumber);
void bufferLivePacket(JSON::Value & packet); void bufferLivePacket(JSON::Value & packet);
void bufferLivePacket(DTSC::Packet & packet); void bufferLivePacket(DTSC::Packet & packet);
bool isBuffered(unsigned long tid, unsigned long keyNum);
unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum);
protected: protected:
void continueNegotiate(unsigned long tid, bool quickNegotiate = false);
bool standAlone; bool standAlone;
static Util::Config * config; static Util::Config * config;
void continueNegotiate(unsigned long tid); negotiationProxy nProxy;
DTSC::Packet thisPacket;//The current packet that is being parsed DTSC::Packet thisPacket;//The current packet that is being parsed
std::string streamName;///< Name of the stream to connect to std::string streamName;
IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process.
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::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 DTSC::Meta myMeta;///< Stores either the input or output metadata
//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
}; };
} }

View file

@ -1,6 +1,7 @@
#include OUTPUTTYPE #include OUTPUTTYPE
#include <mist/config.h> #include <mist/config.h>
#include <mist/socket.h> #include <mist/socket.h>
#include <mist/defines.h>
int spawnForked(Socket::Connection & S){ int spawnForked(Socket::Connection & S){
mistOut tmp(S); mistOut tmp(S);
@ -15,6 +16,7 @@ int main(int argc, char * argv[]) {
std::cout << mistOut::capa.toString() << std::endl; std::cout << mistOut::capa.toString() << std::endl;
return -1; return -1;
} }
conf.activate();
if (mistOut::listenMode()){ if (mistOut::listenMode()){
conf.serveForkedSocket(spawnForked); conf.serveForkedSocket(spawnForked);
}else{ }else{

View file

@ -44,7 +44,6 @@ namespace Mist {
maxSkipAhead = 7500; maxSkipAhead = 7500;
minSkipAhead = 5000; minSkipAhead = 5000;
realTime = 1000; realTime = 1000;
completeKeyReadyTimeOut = false;
if (myConn){ if (myConn){
setBlocking(true); setBlocking(true);
}else{ }else{
@ -58,25 +57,30 @@ namespace Mist {
myConn.setBlocking(isBlocking); myConn.setBlocking(isBlocking);
} }
Output::~Output(){}
void Output::updateMeta(){ void Output::updateMeta(){
//read metadata from page to myMeta variable //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]; static char liveSemName[NAME_BUFFER_SIZE];
snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str());
IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1); liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 1);
bool lock = myMeta.live; if (*liveSem){
if (lock){ liveSem->wait();
liveMeta.wait(); }else{
delete liveSem;
liveSem = 0;
} }
if (metaPages[0].mapped){ }
DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true); DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true);
if (tmpMeta.getVersion()){ if (tmpMeta.getVersion()){
myMeta.reinit(tmpMeta); myMeta.reinit(tmpMeta);
} }
if (liveSem){
liveSem->post();
delete liveSem;
liveSem = 0;
} }
if (lock){
liveMeta.post();
} }
} }
@ -95,7 +99,7 @@ namespace Mist {
if (isInitialized){ if (isInitialized){
return; return;
} }
if (metaPages[0].mapped){ if (nProxy.metaPages[0].mapped){
return; return;
} }
if (streamName.size() < 1){ if (streamName.size() < 1){
@ -120,23 +124,24 @@ namespace Mist {
return myConn.getBinHost(); 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. /// Connects or reconnects to the stream.
/// Assumes streamName class member has been set already. /// Assumes streamName class member has been set already.
/// Will start input if not currently active, calls onFail() if this does not succeed. /// 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. /// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles.
/// Finally, calls updateMeta() and stats() /// Finally, calls updateMeta()
void Output::reconnect(){ void Output::reconnect(){
if (!Util::startInput(streamName)){ if (!Util::startInput(streamName)){
DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization"); FAIL_MSG("Opening stream %s failed - aborting initalization", streamName.c_str());
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());
onFail(); onFail();
return; return;
} }
@ -144,15 +149,36 @@ namespace Mist {
statsPage.finish(); statsPage.finish();
} }
statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true);
if (userClient.getData()){ if (nProxy.userClient.getData()){
userClient.finish(); nProxy.userClient.finish();
} }
char userPageName[NAME_BUFFER_SIZE]; char userPageName[NAME_BUFFER_SIZE];
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); 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(); stats();
updateMeta(); updateMeta();
} }
}
}
void Output::selectDefaultTracks(){ void Output::selectDefaultTracks(){
if (!isInitialized){ if (!isInitialized){
@ -192,9 +218,10 @@ namespace Mist {
} }
if (!found){ if (!found){
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ 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++; genCounter++;
found = true; found = true;
if ((*itc).asStringRef() != "*"){
break; break;
} }
} }
@ -202,20 +229,21 @@ namespace Mist {
} }
} }
} }
}
if (selCounter == selectedTracks.size()){ if (selCounter == selectedTracks.size()){
if (selCounter + genCounter > bestSoFarCount){ if (selCounter + genCounter > bestSoFarCount){
bestSoFarCount = selCounter + genCounter; bestSoFarCount = selCounter + genCounter;
bestSoFar = index; 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{ }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++; 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 //try to fill as many codecs simultaneously as possible
if (capa["codecs"][bestSoFar].size() > 0){ if (capa["codecs"][bestSoFar].size() > 0){
jsonForEach(capa["codecs"][bestSoFar], itb) { jsonForEach(capa["codecs"][bestSoFar], itb) {
@ -233,9 +261,10 @@ namespace Mist {
} }
if (!found){ if (!found){
for (std::map<unsigned int,DTSC::Track>::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ 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); selectedTracks.insert(trit->first);
found = true; found = true;
if ((*itc).asStringRef() != "*"){
break; break;
} }
} }
@ -244,6 +273,7 @@ namespace Mist {
} }
} }
} }
}
if (Util::Config::printDebugLevel >= DLVL_MEDIUM){ if (Util::Config::printDebugLevel >= DLVL_MEDIUM){
//print the selected tracks //print the selected tracks
@ -259,6 +289,37 @@ namespace Mist {
DEBUG_MSG(DLVL_MEDIUM, "Selected tracks: %s (%lu)", selected.str().c_str(), selectedTracks.size()); 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. /// 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){ 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]; char id[NAME_BUFFER_SIZE];
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId); 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++){ 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]); long amountKey = ntohl(tmpOffset[1]);
if (amountKey == 0){continue;} if (amountKey == 0){continue;}
long tmpKey = ntohl(tmpOffset[0]); long tmpKey = ntohl(tmpOffset[0]);
@ -305,18 +367,39 @@ namespace Mist {
return -1; 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){ void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){
if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){ 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); currKeyOpen.erase(trackId);
return; 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 int timeout = 0;
unsigned long pageNum = pageNumForKey(trackId, keyNum); unsigned long pageNum = pageNumForKey(trackId, keyNum);
while (pageNum == -1){ while (pageNum == -1){
if (!timeout){ 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; ++timeout;
//if we've been waiting for this page for 3 seconds, reconnect to the stream - something might be going wrong... //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(); reconnect();
} }
if (timeout > 100){ if (timeout > 100){
DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId); FAIL_MSG("Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId);
curPage.erase(trackId); nProxy.curPage.erase(trackId);
currKeyOpen.erase(trackId); currKeyOpen.erase(trackId);
return; return;
} }
@ -335,7 +418,7 @@ namespace Mist {
}else{ }else{
nxtKeyNum[trackId] = 0; nxtKeyNum[trackId] = 0;
} }
stats(); stats(true);
Util::wait(100); Util::wait(100);
pageNum = pageNumForKey(trackId, keyNum); pageNum = pageNumForKey(trackId, keyNum);
} }
@ -345,20 +428,20 @@ namespace Mist {
}else{ }else{
nxtKeyNum[trackId] = 0; nxtKeyNum[trackId] = 0;
} }
stats(); stats(true);
nxtKeyNum[trackId] = pageNum;
if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){ if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){
return; return;
} }
char id[NAME_BUFFER_SIZE]; char id[NAME_BUFFER_SIZE];
snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum); snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum);
curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE); nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE);
if (!(curPage[trackId].mapped)){ if (!(nProxy.curPage[trackId].mapped)){
DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str()); FAIL_MSG("Initializing page %s failed", nProxy.curPage[trackId].name.c_str());
return; return;
} }
currKeyOpen[trackId] = pageNum; 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. /// Prepares all tracks from selectedTracks for seeking to the specified ms position.
@ -373,44 +456,58 @@ namespace Mist {
if (myMeta.live){ if (myMeta.live){
updateMeta(); 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++){ for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){
if (myMeta.tracks.count(*it)){
seek(*it, pos); seek(*it, pos);
} }
} }
}
bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){ bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){
loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0)); if (myMeta.tracks[tid].lastms < pos){
if (!curPage.count(tid) || !curPage[tid].mapped){ INFO_MSG("Aborting seek to %llums in track %u: past end of track (= %llums).", pos, tid, myMeta.tracks[tid].lastms);
INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid); 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; return false;
} }
sortedPageInfo tmp; sortedPageInfo tmp;
tmp.tid = tid; tmp.tid = tid;
tmp.offset = 0; tmp.offset = 0;
DTSC::Packet tmpPack; 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(); tmp.time = tmpPack.getTime();
char * mpd = curPage[tid].mapped; char * mpd = nProxy.curPage[tid].mapped;
while ((long long)tmp.time < pos && tmpPack){ while ((long long)tmp.time < pos && tmpPack){
tmp.offset += tmpPack.getDataLen(); tmp.offset += tmpPack.getDataLen();
tmpPack.reInit(mpd + tmp.offset, 0, true); tmpPack.reInit(mpd + tmp.offset, 0, true);
tmp.time = tmpPack.getTime(); tmp.time = tmpPack.getTime();
} }
if (tmpPack){ if (tmpPack){
HIGH_MSG("Sought to time %d in %s@%u", tmp.time, streamName.c_str(), tid);
buffer.insert(tmp); buffer.insert(tmp);
return true; return true;
}else{ }else{
//don't print anything for empty packets - not sign of corruption, just unfinished stream. //don't print anything for empty packets - not sign of corruption, just unfinished stream.
if (curPage[tid].mapped[tmp.offset] != 0){ if (nProxy.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); FAIL_MSG("Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid);
}else{ }else{
VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset); VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset);
unsigned int i = 0; 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); 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)); FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0));
}else{ }else{
return seek(tid, pos, getNextKey); return seek(tid, pos, getNextKey);
@ -434,9 +531,8 @@ namespace Mist {
} }
int Output::run() { int Output::run() {
DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler started"); DONTEVEN_MSG("MistOut client handler started");
while (config->is_active && myConn.connected() && (wantRequest || parseData)){ while (config->is_active && myConn && (wantRequest || parseData)){
stats();
if (wantRequest){ if (wantRequest){
requestHandler(); requestHandler();
} }
@ -445,11 +541,67 @@ namespace Mist {
initialize(); initialize();
} }
if ( !sentHeader){ if ( !sentHeader){
DEBUG_MSG(DLVL_DONTEVEN, "sendHeader"); DONTEVEN_MSG("sendHeader");
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){ 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(); sendNext();
}else{ }else{
if (!onFinish()){ 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(); 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(); statsPage.finish();
myConn.close(); myConn.close();
return 0; return 0;
@ -473,212 +628,222 @@ namespace Mist {
return 0; return 0;
} }
for (std::set<long unsigned int>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ 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 *it;
} }
} }
return *(selectedTracks.begin()); return *(selectedTracks.begin());
} }
void Output::prepareNext(){ 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 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);
}
}
static unsigned int emptyCount = 0; static unsigned int emptyCount = 0;
if (!buffer.size()){ if (!buffer.size()){
thisPacket.null(); thisPacket.null();
DEBUG_MSG(DLVL_DEVEL, "Buffer completely played out"); INFO_MSG("Buffer completely played out");
onFinish(); return true;
return;
} }
sortedPageInfo nxt = *(buffer.begin()); 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 (!myMeta.tracks.count(nxt.tid)){
dropTrack(nxt.tid, "disappeared from metadata", true);
return false;
}
if (nxt.offset >= curPage[nxt.tid].len){ 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()); nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
nxt.offset = 0; nxt.offset = 0;
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){ if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){ if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){
ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid); dropTrack(nxt.tid, "time going backwards");
}else{ }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); buffer.insert(nxt);
} }
prepareNext(); }else{
return; dropTrack(nxt.tid, "page load failure", true);
} }
} return false;
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;
} }
//have we arrived at the end of the memory page? (4 zeroes mark the end) //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 we don't currently know where we are, we're lost. We should drop the track.
if (!nxt.time){ if (!nxt.time){
DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid); dropTrack(nxt.tid, "timeless empty packet");
prepareNext(); return false;
return;
} }
//if this is a live stream, we might have just reached the live point. //if this is a live stream, we might have just reached the live point.
//check where the next key is //check where the next key is
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.
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()); 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.
if (myMeta.live && currKeyOpen.count(nxt.tid) && (currKeyOpen[nxt.tid] == (unsigned int)nextPage || nextPage == -1)){
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 (emptyCount % 4 == 0){
updateMeta();
}
}
}else{
//after ~25 seconds, give up and drop the track.
dropTrack(nxt.tid, "could not reload empty packet");
}
return false;
}
//We've simply reached the end of the page. Load the next key = next page.
loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]);
nxt.offset = 0; nxt.offset = 0;
if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){ if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){
unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset); unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
if (nextTime && nextTime < nxt.time){ if (nextTime && nextTime < nxt.time){
DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid); dropTrack(nxt.tid, "time going backwards");
}else{ }else{
if (nextTime){ if (nextTime){
nxt.time = nextTime; nxt.time = nextTime;
} }
//swap out the next object in the buffer with a new one
buffer.erase(buffer.begin());
buffer.insert(nxt); buffer.insert(nxt);
DEBUG_MSG(DLVL_MEDIUM, "Next page for track %u starts at %llu.", nxt.tid, nxt.time); MEDIUM_MSG("Next page for track %u starts at %llu.", nxt.tid, nxt.time);
} }
}else{ }else{
DEBUG_MSG(DLVL_DEVEL, "Could not load next memory page for track %u - dropping track.", nxt.tid); dropTrack(nxt.tid, "page load failure");
}
return false;
}
//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());
} }
} }
prepareNext();
return; //when live, every keyframe, check correctness of the keyframe number
} if (myMeta.live && thisPacket.getFlag("keyframe")){
thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); //Check whether returned keyframe is correct. If not, wait for approximately 10 seconds while checking.
if (thisPacket){ //Failure here will cause tracks to drop due to inconsistent internal state.
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()); nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime());
DEBUG_MSG(DLVL_VERYHIGH, "Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]); int counter = 0;
} while(counter < 40 && myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){
emptyCount = 0; if (counter++){
} //Only sleep 250ms if this is not the first updatemeta try
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;
}
}
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); Util::wait(250);
}
updateMeta(); 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]);
} }
if (timeoutTries<=0){
if (!completeKeyReadyTimeOut){ //always assume we're not at the live point
INFO_MSG("Wait for keyframe Timeout triggered! Ended to avoid endless loops"); atLivePoint = false;
} //we assume the next packet is the next on this same page
completeKeyReadyTimeOut = true; nxt.offset += thisPacket.getDataLen();
}else{ if (nxt.offset < nProxy.curPage[nxt.tid].len){
//untimeout handling unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset);
completeKeyReadyTimeOut = 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){ if (nextTime){
nxt.time = nextTime; nxt.time = nextTime;
}else{ }else{
++nxt.time; ++nxt.time;
//no packet -> we are at the live point
atLivePoint = true;
} }
} }
buffer.insert(nxt);
}
stats();
}
void Output::stats(){ //exchange the current packet in the buffer for the next one
static bool setHost = true; buffer.erase(buffer.begin());
if (!isInitialized){ buffer.insert(nxt);
return;
return true;
} }
if (statsPage.getData()){
/// 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(); unsigned long long int now = Util::epoch();
if (now != lastStats){ if (now == lastStats && !force){return;}
lastStats = now; lastStats = now;
EXTREME_MSG("Writing stats: %s, %s, %lu", getConnectedHost().c_str(), streamName.c_str(), crc & 0xFFFFFFFFu);
if (statsPage.getData()){
IPC::statExchange tmpEx(statsPage.getData()); IPC::statExchange tmpEx(statsPage.getData());
tmpEx.now(now); tmpEx.now(now);
if (setHost){ 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.host(getConnectedBinHost());
setHost = false;
} }
tmpEx.crc(crc); tmpEx.crc(crc);
tmpEx.streamName(streamName); tmpEx.streamName(streamName);
tmpEx.connector(capa["name"].asString()); tmpEx.connector(getStatsName());
tmpEx.up(myConn.dataUp()); tmpEx.up(myConn.dataUp());
tmpEx.down(myConn.dataDown()); tmpEx.down(myConn.dataDown());
tmpEx.time(now - myConn.connTime()); tmpEx.time(now - myConn.connTime());
@ -689,34 +854,32 @@ namespace Mist {
} }
statsPage.keepAlive(); statsPage.keepAlive();
} }
}
int tNum = 0; int tNum = 0;
if (!userClient.getData()){ if (!nProxy.userClient.getData()){
char userPageName[NAME_BUFFER_SIZE]; char userPageName[NAME_BUFFER_SIZE];
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str());
userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true);
if (!userClient.getData()){ if (!nProxy.userClient.getData()){
DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output"); WARN_MSG("Player connection failure - aborting output");
myConn.close(); myConn.close();
return; 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++){ for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){
unsigned int tId = *it; userConn.setTrackId(tNum, *it);
char * thisData = userClient.getData() + (6 * tNum); userConn.setKeynum(tNum, nxtKeyNum[*it]);
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);
tNum ++; tNum ++;
} }
} }
userClient.keepAlive(); nProxy.userClient.keepAlive();
if (tNum > SIMUL_TRACKS){ 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 //just set the sentHeader bool to true, by default
sentHeader = true; 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;
}
} }

View file

@ -37,13 +37,12 @@ namespace Mist {
public: public:
//constructor and destructor //constructor and destructor
Output(Socket::Connection & conn); Output(Socket::Connection & conn);
virtual ~Output();
//static members for initialization and capabilities //static members for initialization and capabilities
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
static JSON::Value capa; static JSON::Value capa;
//non-virtual generic functions //non-virtual generic functions
int run(); int run();
void stats(); void stats(bool force = false);
void seek(unsigned long long pos); void seek(unsigned long long pos);
bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false); bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false);
void stop(); void stop();
@ -51,10 +50,13 @@ namespace Mist {
long unsigned int getMainSelectedTrack(); long unsigned int getMainSelectedTrack();
void updateMeta(); void updateMeta();
void selectDefaultTracks(); void selectDefaultTracks();
bool connectToFile(std::string file);
static bool listenMode(){return true;} static bool listenMode(){return true;}
virtual bool isReadyForPlay();
//virtuals. The optional virtuals have default implementations that do as little as possible. //virtuals. The optional virtuals have default implementations that do as little as possible.
virtual void sendNext() {}//REQUIRED! Others are optional. 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 void onRequest();
virtual bool onFinish() { virtual bool onFinish() {
return false; return false;
@ -68,21 +70,21 @@ namespace Mist {
std::map<unsigned long, unsigned int> currKeyOpen; std::map<unsigned long, unsigned int> currKeyOpen;
void loadPageForKey(long unsigned int trackId, long long int keyNum); void loadPageForKey(long unsigned int trackId, long long int keyNum);
int pageNumForKey(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. 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. 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::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. 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 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 protected://these are to be messed with by child classes
virtual std::string getConnectedHost(); virtual std::string getConnectedHost();
virtual std::string getConnectedBinHost(); virtual std::string getConnectedBinHost();
virtual std::string getStatsName();
virtual bool hasSessionIDs(){return false;}
IPC::sharedClient statsPage;///< Shared memory used for statistics reporting. IPC::sharedClient statsPage;///< Shared memory used for statistics reporting.
bool isBlocking;///< If true, indicates that myConn is blocking. 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); unsigned int getKeyForTime(long unsigned int trackId, long long timeStamp);
//stream delaying variables //stream delaying variables
@ -103,3 +105,4 @@ namespace Mist {
}; };
} }

View file

@ -159,7 +159,7 @@ namespace Mist {
capa["codecs"][0u][1u].append("G711mu"); capa["codecs"][0u][1u].append("G711mu");
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "flash/11"; capa["methods"][0u]["type"] = "flash/11";
capa["methods"][0u]["priority"] = 7ll; capa["methods"][0u]["priority"] = 6ll;
capa["methods"][0u]["player_url"] = "/flashplayer.swf"; capa["methods"][0u]["player_url"] = "/flashplayer.swf";
} }
@ -229,7 +229,7 @@ namespace Mist {
myConn.close(); myConn.close();
break; break;
} }
Util::sleep(500); Util::wait(500);
updateMeta(); updateMeta();
} }
mstime = myMeta.tracks[tid].getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()).getTime(); mstime = myMeta.tracks[tid].getKey(myMeta.tracks[tid].fragments[fragNum - myMeta.tracks[tid].missedFrags].getNumber()).getTime();

View file

@ -3,17 +3,26 @@
#include <unistd.h> #include <unistd.h>
namespace Mist { 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. ///\brief Builds an index file for HTTP Live streaming.
///\return The index file for HTTP Live Streaming. ///\return The index file for HTTP Live Streaming.
std::string OutHLS::liveIndex(){ std::string OutHLS::liveIndex(){
std::stringstream result; std::stringstream result;
result << "#EXTM3U\r\n"; result << "#EXTM3U\r\n";
int audioId = -1; int audioId = -1;
std::string audioName;
for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ for (std::map<unsigned int,DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){
if (it->second.codec == "AAC"){ if (it->second.codec == "AAC"){
audioId = it->first; audioId = it->first;
audioName = it->second.getIdentifier();
break; break;
} }
} }
@ -33,7 +42,7 @@ namespace Mist {
if (audioId != -1){ if (audioId != -1){
result << "_" << audioId; result << "_" << audioId;
} }
result << "/index.m3u8\r\n"; result << "/index.m3u8?sessId=" << getpid() << "\r\n";
} }
} }
if (!vidTracks && audioId){ if (!vidTracks && audioId){
@ -44,13 +53,13 @@ namespace Mist {
return result.str(); return result.str();
} }
std::string OutHLS::liveIndex(int tid){ std::string OutHLS::liveIndex(int tid, std::string & sessId) {
updateMeta(); updateMeta();
std::stringstream result; std::stringstream result;
//parse single track //parse single track
int longestFragment = 0; int longestFragment = 0;
if (!myMeta.tracks[tid].fragments.size()){ 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 ""; return "";
} }
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); (it + 1) != myMeta.tracks[tid].fragments.end(); it++){ 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; std::deque<std::string> lines;
for (std::deque<DTSC::Fragment>::iterator it = myMeta.tracks[tid].fragments.begin(); it != myMeta.tracks[tid].fragments.end(); it++){ 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(); long long int starttime = myMeta.tracks[tid].getKey(it->getNumber()).getTime();
std::stringstream line;
long long duration = it->getDuration(); long long duration = it->getDuration();
if (duration <= 0){ if (duration <= 0){
duration = myMeta.tracks[tid].lastms - starttime; duration = myMeta.tracks[tid].lastms - starttime;
} }
line << "#EXTINF:" << ((duration + 500) / 1000) << ", no desc\r\n" << starttime << "_" << duration + starttime << ".ts\r\n"; char lineBuf[400];
lines.push_back(line.str()); 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; unsigned int skippedLines = 0;
if (myMeta.live){ if (myMeta.live){
//only print the last segment when VoD //only print the last segment when VoD
@ -135,7 +147,7 @@ namespace Mist {
void OutHLS::onHTTP(){ void OutHLS::onHTTP(){
std::string method = H.method; std::string method = H.method;
std::string sessId = H.GetVar("sessId");
if (H.url == "/crossdomain.xml"){ if (H.url == "/crossdomain.xml"){
H.Clean(); H.Clean();
@ -153,11 +165,23 @@ namespace Mist {
return; return;
} //crossdomain.xml } //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){ if (H.url.find("hls") == std::string::npos){
myConn.close(); myConn.close();
return; 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); 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; bool VLCworkaround = false;
if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){ if (H.GetHeader("User-Agent").substr(0, 3) == "VLC"){
@ -167,6 +191,7 @@ namespace Mist {
VLCworkaround = true; VLCworkaround = true;
} }
} }
initialize(); initialize();
if (H.url.find(".m3u") == std::string::npos){ if (H.url.find(".m3u") == std::string::npos){
std::string tmpStr = H.getUrl().substr(5+streamName.size()); std::string tmpStr = H.getUrl().substr(5+streamName.size());
@ -189,6 +214,11 @@ namespace Mist {
selectedTracks.insert(vidTrack); selectedTracks.insert(vidTrack);
selectedTracks.insert(audTrack); 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){ if (myMeta.live){
unsigned int timeout = 0; unsigned int timeout = 0;
@ -202,7 +232,7 @@ namespace Mist {
myConn.close(); myConn.close();
break; break;
} }
Util::sleep(500); Util::wait(500);
updateMeta(); updateMeta();
} }
}while (myConn && seekable > 0); }while (myConn && seekable > 0);
@ -266,7 +296,7 @@ namespace Mist {
manifest = liveIndex(); manifest = liveIndex();
}else{ }else{
int selectId = atoi(request.substr(0,request.find("/")).c_str()); int selectId = atoi(request.substr(0,request.find("/")).c_str());
manifest = liveIndex(selectId); manifest = liveIndex(selectId, sessId);
} }
H.SetBody(manifest); H.SetBody(manifest);
H.SendResponse("200", "OK", myConn); H.SendResponse("200", "OK", myConn);

View file

@ -9,9 +9,11 @@ namespace Mist {
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void sendTS(const char * tsData, unsigned int len=188); void sendTS(const char * tsData, unsigned int len=188);
void onHTTP(); void onHTTP();
bool isReadyForPlay();
protected: protected:
bool hasSessionIDs(){return true;}
std::string liveIndex(); std::string liveIndex();
std::string liveIndex(int tid); std::string liveIndex(int tid, std::string & sessId);
int canSeekms(unsigned int ms); int canSeekms(unsigned int ms);
int keysToSend; int keysToSend;
unsigned int vidTrack; unsigned int vidTrack;

View file

@ -5,6 +5,7 @@
#include <mist/mp4_generic.h> #include <mist/mp4_generic.h>
#include <mist/http_parser.h> #include <mist/http_parser.h>
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/bitfields.h>
#include <mist/checksum.h> #include <mist/checksum.h>
#include <unistd.h> #include <unistd.h>
@ -56,11 +57,9 @@ namespace Mist {
capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/application/vnd.ms-ss"; capa["methods"][0u]["type"] = "html5/application/vnd.ms-ss";
capa["methods"][0u]["priority"] = 9ll; capa["methods"][0u]["priority"] = 9ll;
capa["methods"][0u]["nolive"] = 1;
capa["methods"][1u]["handler"] = "http"; capa["methods"][1u]["handler"] = "http";
capa["methods"][1u]["type"] = "silverlight"; capa["methods"][1u]["type"] = "silverlight";
capa["methods"][1u]["priority"] = 1ll; capa["methods"][1u]["priority"] = 1ll;
capa["methods"][1u]["nolive"] = 1;
} }
void OutHSS::sendNext() { void OutHSS::sendNext() {
@ -132,7 +131,7 @@ namespace Mist {
myConn.close(); myConn.close();
break; break;
} }
Util::sleep(500); Util::wait(500);
updateMeta(); updateMeta();
} }
}while (myConn && seekable > 0); }while (myConn && seekable > 0);
@ -201,11 +200,11 @@ namespace Mist {
//Wrap everything in mp4 boxes //Wrap everything in mp4 boxes
MP4::MFHD mfhd_box; 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; MP4::TFHD tfhd_box;
tfhd_box.setFlags(MP4::tfhdSampleFlag); 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") { if (myMeta.tracks[tid].type == "video") {
tfhd_box.setDefaultSampleFlags(0x00004001); tfhd_box.setDefaultSampleFlags(0x00004001);
} else { } else {
@ -254,19 +253,24 @@ namespace Mist {
//If the stream is live, we want to have a fragref box if possible //If the stream is live, we want to have a fragref box if possible
//////HEREHEREHERE //////HEREHEREHERE
if (myMeta.live) { 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; MP4::UUID_TrackFragmentReference fragref_box;
fragref_box.setVersion(1); fragref_box.setVersion(1);
fragref_box.setFragmentCount(0); fragref_box.setFragmentCount(0);
int fragCount = 0; int fragCount = 0;
for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) { for (unsigned int i = 0; fragCount < 2 && i < myMeta.tracks[tid].keys.size() - 1; i++) {
if (myMeta.tracks[tid].keys[i].getTime() > seekTime) { 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.setTime(fragCount, myMeta.tracks[tid].keys[i].getTime() * 10000);
fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000); fragref_box.setDuration(fragCount, myMeta.tracks[tid].keys[i].getLength() * 10000);
fragref_box.setFragmentCount(++fragCount); fragref_box.setFragmentCount(++fragCount);
} }
} }
traf_box.setContent(fragref_box, 3); traf_box.setContent(fragref_box, 4);
} }
MP4::MOOF moof_box; MP4::MOOF moof_box;
@ -467,9 +471,4 @@ namespace Mist {
sendHeader(); sendHeader();
} }
} }
void OutHSS::initialize() {
Output::initialize();
}
} }

View file

@ -9,10 +9,8 @@ namespace Mist {
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void onHTTP(); void onHTTP();
void sendNext(); void sendNext();
void initialize();/*LTS*/
void sendHeader(); void sendHeader();
protected: protected:
JSON::Value encryption;
std::string smoothIndex(); std::string smoothIndex();
int canSeekms(unsigned int ms); int canSeekms(unsigned int ms);
int keysToSend; int keysToSend;

View file

@ -2,6 +2,7 @@
#include "output_http.h" #include "output_http.h"
#include <mist/stream.h> #include <mist/stream.h>
#include <mist/checksum.h> #include <mist/checksum.h>
#include <set>
namespace Mist { namespace Mist {
HTTPOutput::HTTPOutput(Socket::Connection & conn) : Output(conn) { HTTPOutput::HTTPOutput(Socket::Connection & conn) : Output(conn) {
@ -104,9 +105,9 @@ namespace Mist {
} }
//loop over the connectors //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(); 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"); DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors");
unsigned int capa_ctr = capa.getSize(); unsigned int capa_ctr = capa.getSize();
for (unsigned int i = 0; i < capa_ctr; ++i){ for (unsigned int i = 0; i < capa_ctr; ++i){
@ -171,7 +172,7 @@ namespace Mist {
if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){ 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()); 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"); streamName = H.GetVar("stream");
userClient.finish(); nProxy.userClient.finish();
statsPage.finish(); statsPage.finish();
reConnector(handler); reConnector(handler);
H.Clean(); H.Clean();
@ -209,8 +210,19 @@ namespace Mist {
void HTTPOutput::onRequest(){ void HTTPOutput::onRequest(){
while (H.Read(myConn)){ while (H.Read(myConn)){
std::string ua = H.GetHeader("User-Agent"); if (hasSessionIDs()){
if (H.GetVar("sessId").size()){
std::string ua = H.GetVar("sessId");
crc = checksum::crc32(0, ua.data(), ua.size()); 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()); INFO_MSG("Received request %s", H.getUrl().c_str());
selectedTracks.clear(); selectedTracks.clear();
if (H.GetVar("audio") != ""){ if (H.GetVar("audio") != ""){
@ -239,6 +251,7 @@ namespace Mist {
for (std::set<unsigned long>::iterator it = toRemove.begin(); it != toRemove.end(); it++){ for (std::set<unsigned long>::iterator it = toRemove.begin(); it != toRemove.end(); it++){
selectedTracks.erase(*it); selectedTracks.erase(*it);
} }
onHTTP(); onHTTP();
if (!H.bufferChunks){ if (!H.bufferChunks){
H.Clean(); H.Clean();
@ -275,9 +288,9 @@ namespace Mist {
for (int i=0; i<20; i++){argarr[i] = 0;} for (int i=0; i<20; i++){argarr[i] = 0;}
int id = -1; 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(); 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"); DTSC::Scan prots = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("config").getMember("protocols");
unsigned int prots_ctr = prots.getSize(); unsigned int prots_ctr = prots.getSize();

View file

@ -248,9 +248,9 @@ namespace Mist {
std::string port, url_rel; 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(); 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 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"); DTSC::Scan capa = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("capabilities").getMember("connectors").getMember("RTMP");
unsigned int pro_cnt = prtcls.getSize(); 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"; response = "// Generating info code for stream " + streamName + "\n\nif (!mistvideo){var mistvideo = {};}\n";
JSON::Value json_resp; JSON::Value json_resp;
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
IPC::semaphore metaLocker(std::string("liveMeta@" + streamName).c_str(), 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; bool metaLock = false;
configLock.wait(); 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"); DTSC::Scan strm = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName).getMember("meta");
IPC::sharedPage streamIndex; IPC::sharedPage streamIndex;
if (!strm){ if (!strm){
@ -333,7 +335,7 @@ namespace Mist {
if (Util::startInput(streamName)){ if (Util::startInput(streamName)){
char pageId[NAME_BUFFER_SIZE]; char pageId[NAME_BUFFER_SIZE];
snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); 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){ if (streamIndex.mapped){
metaLock = true; metaLock = true;
metaLocker.wait(); metaLocker.wait();

View file

@ -25,13 +25,19 @@ namespace Mist {
} }
void OutHTTPTS::onHTTP(){ void OutHTTPTS::onHTTP(){
std::string method = H.method;
initialize(); initialize();
H.Clean();
H.SetHeader("Content-Type", "video/mp2t"); 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); H.StartResponse(H, myConn);
parseData = true; parseData = true;
wantRequest = false; wantRequest = false;
H.Clean(); //clean for any possible next requests
} }
void OutHTTPTS::sendTS(const char * tsData, unsigned int len){ void OutHTTPTS::sendTS(const char * tsData, unsigned int len){

View file

@ -9,13 +9,6 @@ namespace Mist {
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void onHTTP(); void onHTTP();
void sendTS(const char * tsData, unsigned int len=188); 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;
}; };
} }

View file

@ -2,7 +2,6 @@
namespace Mist { namespace Mist {
OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){} OutProgressiveFLV::OutProgressiveFLV(Socket::Connection & conn) : HTTPOutput(conn){}
OutProgressiveFLV::~OutProgressiveFLV() {}
void OutProgressiveFLV::init(Util::Config * cfg){ void OutProgressiveFLV::init(Util::Config * cfg){
HTTPOutput::init(cfg); HTTPOutput::init(cfg);

View file

@ -5,7 +5,6 @@ namespace Mist {
class OutProgressiveFLV : public HTTPOutput { class OutProgressiveFLV : public HTTPOutput {
public: public:
OutProgressiveFLV(Socket::Connection & conn); OutProgressiveFLV(Socket::Connection & conn);
~OutProgressiveFLV();
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void onHTTP(); void onHTTP();
void sendNext(); void sendNext();

View file

@ -2,7 +2,6 @@
namespace Mist { namespace Mist {
OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){} OutProgressiveMP3::OutProgressiveMP3(Socket::Connection & conn) : HTTPOutput(conn){}
OutProgressiveMP3::~OutProgressiveMP3(){}
void OutProgressiveMP3::init(Util::Config * cfg){ void OutProgressiveMP3::init(Util::Config * cfg){
HTTPOutput::init(cfg); HTTPOutput::init(cfg);

View file

@ -5,7 +5,6 @@ namespace Mist {
class OutProgressiveMP3 : public HTTPOutput { class OutProgressiveMP3 : public HTTPOutput {
public: public:
OutProgressiveMP3(Socket::Connection & conn); OutProgressiveMP3(Socket::Connection & conn);
~OutProgressiveMP3();
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void onHTTP(); void onHTTP();
void sendNext(); void sendNext();

View file

@ -4,11 +4,11 @@ namespace Mist {
OutRaw::OutRaw(Socket::Connection & conn) : Output(conn) { OutRaw::OutRaw(Socket::Connection & conn) : Output(conn) {
streamName = config->getString("streamname"); streamName = config->getString("streamname");
initialize(); initialize();
selectedTracks.clear();
std::string tracks = config->getString("tracks"); std::string tracks = config->getString("tracks");
if (tracks.size()){
selectedTracks.clear();
unsigned int currTrack = 0; unsigned int currTrack = 0;
//loop over tracks, add any found track IDs to selectedTracks //loop over tracks, add any found track IDs to selectedTracks
if (tracks != ""){
for (unsigned int i = 0; i < tracks.size(); ++i){ for (unsigned int i = 0; i < tracks.size(); ++i){
if (tracks[i] >= '0' && tracks[i] <= '9'){ if (tracks[i] >= '0' && tracks[i] <= '9'){
currTrack = currTrack*10 + (tracks[i] - '0'); 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"]["help"] = "The time in milliseconds to seek to, 0 by default.";
capa["optional"]["seek"]["type"] = "int"; capa["optional"]["seek"]["type"] = "int";
capa["optional"]["seek"]["option"] = "--seek"; capa["optional"]["seek"]["option"] = "--seek";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("*");
capa["codecs"][0u][1u].append("AAC");
cfg->addOption("streamname", cfg->addOption("streamname",
JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}"));
cfg->addOption("tracks", cfg->addOption("tracks",
@ -68,3 +67,4 @@ namespace Mist {
} }
} }

View file

@ -10,10 +10,10 @@
namespace Mist { namespace Mist {
OutRTMP::OutRTMP(Socket::Connection & conn) : Output(conn) { OutRTMP::OutRTMP(Socket::Connection & conn) : Output(conn) {
setBlocking(true); setBlocking(true);
while (!conn.Received().available(1537) && conn.connected()) { while (!conn.Received().available(1537) && conn.connected() && config->is_active) {
conn.spool(); conn.spool();
} }
if (!conn){ if (!conn || !config->is_active){
return; return;
} }
RTMPStream::handshake_in.append(conn.Received().remove(1537)); RTMPStream::handshake_in.append(conn.Received().remove(1537));
@ -21,21 +21,41 @@ namespace Mist {
if (RTMPStream::doHandshake()) { if (RTMPStream::doHandshake()) {
conn.SendNow(RTMPStream::handshake_out); 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.spool();
} }
conn.Received().remove(1536); conn.Received().remove(1536);
RTMPStream::rec_cnt += 1536; RTMPStream::rec_cnt += 1536;
DEBUG_MSG(DLVL_HIGH, "Handshake success!"); HIGH_MSG("Handshake success");
} else { } else {
DEBUG_MSG(DLVL_DEVEL, "Handshake fail!"); MEDIUM_MSG("Handshake fail (this is not a problem, usually)");
} }
setBlocking(false); setBlocking(false);
maxSkipAhead = 1500; maxSkipAhead = 1500;
minSkipAhead = 500; 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){ void OutRTMP::parseVars(std::string data){
std::string varname; std::string varname;
@ -94,7 +114,7 @@ namespace Mist {
void OutRTMP::init(Util::Config * cfg) { void OutRTMP::init(Util::Config * cfg) {
Output::init(cfg); Output::init(cfg);
capa["name"] = "RTMP"; 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["deps"] = "";
capa["url_rel"] = "/play/$"; capa["url_rel"] = "/play/$";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
@ -114,13 +134,28 @@ namespace Mist {
capa["codecs"][0u][1u].append("G711mu"); capa["codecs"][0u][1u].append("G711mu");
capa["methods"][0u]["handler"] = "rtmp"; capa["methods"][0u]["handler"] = "rtmp";
capa["methods"][0u]["type"] = "flash/10"; capa["methods"][0u]["type"] = "flash/10";
capa["methods"][0u]["priority"] = 6ll; capa["methods"][0u]["priority"] = 7ll;
capa["methods"][0u]["player_url"] = "/flashplayer.swf"; capa["methods"][0u]["player_url"] = "/flashplayer.swf";
cfg->addConnectorOptions(1935, capa); cfg->addConnectorOptions(1935, capa);
config = cfg; config = cfg;
} }
void OutRTMP::sendNext() { 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 char rtmpheader[] = {0, //byte 0 = cs_id | ch_type
0, 0, 0, //bytes 1-3 = timestamp 0, 0, 0, //bytes 1-3 = timestamp
0, 0, 0, //bytes 4-6 = length 0, 0, 0, //bytes 4-6 = length
@ -312,9 +347,7 @@ namespace Mist {
///\param messageType The type of message. ///\param messageType The type of message.
///\param streamId The ID of the AMF stream. ///\param streamId The ID of the AMF stream.
void OutRTMP::sendCommand(AMF::Object & amfReply, int messageType, int streamId) { void OutRTMP::sendCommand(AMF::Object & amfReply, int messageType, int streamId) {
#if DEBUG >= 8 HIGH_MSG("Sending: %s", amfReply.Print().c_str());
std::cerr << amfReply.Print() << std::endl;
#endif
if (messageType == 17) { if (messageType == 17) {
myConn.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack())); myConn.SendNow(RTMPStream::SendChunk(3, messageType, streamId, (char)0 + amfReply.Pack()));
} else { } else {
@ -327,38 +360,13 @@ namespace Mist {
///\param messageType The type of message. ///\param messageType The type of message.
///\param streamId The ID of the AMF stream. ///\param streamId The ID of the AMF stream.
void OutRTMP::parseAMFCommand(AMF::Object & amfData, int messageType, int streamId) { void OutRTMP::parseAMFCommand(AMF::Object & amfData, int messageType, int streamId) {
#if DEBUG >= 5 MEDIUM_MSG("Received command: %s", amfData.Print().c_str());
fprintf(stderr, "Received command: %s\n", amfData.Print().c_str()); HIGH_MSG("AMF0 command: %s", amfData.getContentP(0)->StrValue().c_str());
#endif
#if DEBUG >= 8
fprintf(stderr, "AMF0 command: %s\n", amfData.getContentP(0)->StrValue().c_str());
#endif
if (amfData.getContentP(0)->StrValue() == "connect") { if (amfData.getContentP(0)->StrValue() == "connect") {
double objencoding = 0; double objencoding = 0;
if (amfData.getContentP(2)->getContentP("objectEncoding")) { if (amfData.getContentP(2)->getContentP("objectEncoding")) {
objencoding = amfData.getContentP(2)->getContentP("objectEncoding")->NumValue(); 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 = amfData.getContentP(2)->getContentP("tcUrl")->StrValue();
app_name = app_name.substr(app_name.find('/', 7) + 1); app_name = app_name.substr(app_name.find('/', 7) + 1);
RTMPStream::chunk_snd_max = 4096; RTMPStream::chunk_snd_max = 4096;
@ -467,7 +475,7 @@ namespace Mist {
} //getStreamLength } //getStreamLength
if ((amfData.getContentP(0)->StrValue() == "publish")) { if ((amfData.getContentP(0)->StrValue() == "publish")) {
if (amfData.getContentP(3)) { if (amfData.getContentP(3)) {
streamName = amfData.getContentP(3)->StrValue(); streamName = Encodings::URL::decode(amfData.getContentP(3)->StrValue());
if (streamName.find('/')){ if (streamName.find('/')){
streamName = streamName.substr(0, streamName.find('/')); streamName = streamName.substr(0, streamName.find('/'));
@ -485,32 +493,33 @@ namespace Mist {
Util::sanitizeName(streamName); Util::sanitizeName(streamName);
//pull the server configuration //pull the server configuration
IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE); ///< Contains server configuration and capabilities
IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1);
configLock.wait(); configLock.wait();
DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName); DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(streamName);
if (streamCfg){ if (streamCfg){
if (streamCfg.getMember("source").asString().substr(0, 7) != "push://"){ 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(); myConn.close();
}else{ }else{
std::string source = streamCfg.getMember("source").asString().substr(7); std::string source = streamCfg.getMember("source").asString().substr(7);
std::string IP = source.substr(0, source.find('@')); std::string IP = source.substr(0, source.find('@'));
if (IP != ""){ if (IP != ""){
if (!myConn.isAddress(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(); myConn.close();
} }
} }
} }
}else{ }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(); myConn.close();
} }
configLock.post(); configLock.post();
configLock.close(); configLock.close();
if (!myConn){return;}//do not initialize if rejected if (!myConn){return;}//do not initialize if rejected
isPushing = true;
initialize(); initialize();
} }
//send a _result reply //send a _result reply
@ -698,16 +707,22 @@ namespace Mist {
} }
return; return;
} //seek } //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 WARN_MSG("AMF0 command not processed: %s", amfData.Print().c_str());
fprintf(stderr, "AMF0 command not processed!\n%s\n", amfData.Print().c_str());
#endif
//send a _result reply //send a _result reply
AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER); AMF::Object amfReply("container", AMF::AMF0_DDV_CONTAINER);
amfReply.addContent(AMF::Object("", "_error")); //result success amfReply.addContent(AMF::Object("", "_error")); //result success
amfReply.addContent(amfData.getContent(1)); //same transaction ID amfReply.addContent(amfData.getContent(1)); //same transaction ID
amfReply.addContent(AMF::Object("", (double)0, AMF::AMF0_NULL)); //null - command info amfReply.addContent(AMF::Object("", amfData.getContentP(0)->StrValue())); //null - command info
amfReply.addContent(AMF::Object("Command not implemented or recognized", "")); //stream ID? amfReply.addContent(AMF::Object("", "Command not implemented or recognized")); //stream ID?
sendCommand(amfReply, messageType, streamId); sendCommand(amfReply, messageType, streamId);
} //parseAMFCommand } //parseAMFCommand
@ -734,9 +749,7 @@ namespace Mist {
switch (next.msg_type_id) { switch (next.msg_type_id) {
case 0: //does not exist case 0: //does not exist
#if DEBUG >= 2 WARN_MSG("UNKN: Received a zero-type message. Possible data corruption? Aborting!");
fprintf(stderr, "UNKN: Received a zero-type message. Possible data corruption? Aborting!\n");
#endif
while (inputBuffer.size()) { while (inputBuffer.size()) {
inputBuffer.get().clear(); inputBuffer.get().clear();
} }
@ -745,20 +758,14 @@ namespace Mist {
break; //happens when connection breaks unexpectedly break; //happens when connection breaks unexpectedly
case 1: //set chunk size case 1: //set chunk size
RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str()); RTMPStream::chunk_rec_max = ntohl(*(int *)next.data.c_str());
#if DEBUG >= 5 MEDIUM_MSG("CTRL: Set chunk size: %i", RTMPStream::chunk_rec_max);
fprintf(stderr, "CTRL: Set chunk size: %i\n", RTMPStream::chunk_rec_max);
#endif
break; break;
case 2: //abort message - we ignore this one case 2: //abort message - we ignore this one
#if DEBUG >= 5 MEDIUM_MSG("CTRL: Abort message");
fprintf(stderr, "CTRL: Abort message\n");
#endif
//4 bytes of stream id to drop //4 bytes of stream id to drop
break; break;
case 3: //ack case 3: //ack
#if DEBUG >= 8 VERYHIGH_MSG("CTRL: Acknowledgement");
fprintf(stderr, "CTRL: Acknowledgement\n");
#endif
RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str()); RTMPStream::snd_window_at = ntohl(*(int *)next.data.c_str());
RTMPStream::snd_window_at = RTMPStream::snd_cnt; RTMPStream::snd_window_at = RTMPStream::snd_cnt;
break; break;
@ -773,49 +780,43 @@ namespace Mist {
//6 = pingrequest, 4 bytes data //6 = pingrequest, 4 bytes data
//7 = pingresponse, 4 bytes data //7 = pingresponse, 4 bytes data
//we don't need to process this //we don't need to process this
#if DEBUG >= 5
short int ucmtype = ntohs(*(short int *)next.data.c_str()); short int ucmtype = ntohs(*(short int *)next.data.c_str());
switch (ucmtype) { switch (ucmtype) {
case 0: 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; break;
case 1: 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; break;
case 2: 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; break;
case 3: 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; break;
case 4: 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; break;
case 6: 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; break;
case 7: 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; break;
default: default:
fprintf(stderr, "CTRL: UCM Unknown (%hi)\n", ucmtype); MEDIUM_MSG("CTRL: UCM Unknown (%hi)", ucmtype);
break; break;
} }
#endif
} }
break; break;
case 5: //window size of other end case 5: //window size of other end
#if DEBUG >= 5 MEDIUM_MSG("CTRL: Window size");
fprintf(stderr, "CTRL: Window size\n");
#endif
RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str()); RTMPStream::rec_window_size = ntohl(*(int *)next.data.c_str());
RTMPStream::rec_window_at = RTMPStream::rec_cnt; RTMPStream::rec_window_at = RTMPStream::rec_cnt;
myConn.SendNow(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3) myConn.SendNow(RTMPStream::SendCTL(3, RTMPStream::rec_cnt)); //send ack (msg 3)
break; break;
case 6: case 6:
#if DEBUG >= 5 MEDIUM_MSG("CTRL: Set peer bandwidth");
fprintf(stderr, "CTRL: Set peer bandwidth\n");
#endif
//4 bytes window size, 1 byte limit type (ignored) //4 bytes window size, 1 byte limit type (ignored)
RTMPStream::snd_window_size = ntohl(*(int *)next.data.c_str()); 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) 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) )); 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 ( !pack_out.isNull()){
if (!userClient.getData()){ if (!nProxy.userClient.getData()){
char userPageName[NAME_BUFFER_SIZE]; char userPageName[NAME_BUFFER_SIZE];
snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); 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()); continueNegotiate(pack_out["trackid"].asInt());
nProxy.streamName = streamName;
bufferLivePacket(pack_out); bufferLivePacket(pack_out);
} }
break; break;
} }
case 15: case 15:
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 data message"); MEDIUM_MSG("Received AMF3 data message");
break; break;
case 16: case 16:
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 shared object"); MEDIUM_MSG("Received AMF3 shared object");
break; break;
case 17: { case 17: {
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3 command message"); MEDIUM_MSG("Received AMF3 command message");
if (next.data[0] != 0) { if (next.data[0] != 0) {
next.data = next.data.substr(1); next.data = next.data.substr(1);
amf3data = AMF::parse3(next.data); amf3data = AMF::parse3(next.data);
#if DEBUG >= 5 MEDIUM_MSG("AMF3: %s", amf3data.Print().c_str());
amf3data.Print();
#endif
} else { } else {
DEBUG_MSG(DLVL_MEDIUM, "Received AMF3-0 command message"); MEDIUM_MSG("Received AMF3-0 command message");
next.data = next.data.substr(1); next.data = next.data.substr(1);
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
parseAMFCommand(amfdata, 17, next.msg_stream_id); parseAMFCommand(amfdata, 17, next.msg_stream_id);
@ -871,7 +871,7 @@ namespace Mist {
} }
break; break;
case 19: case 19:
DEBUG_MSG(DLVL_MEDIUM, "Received AMF0 shared object"); MEDIUM_MSG("Received AMF0 shared object");
break; break;
case 20: { //AMF0 command message case 20: { //AMF0 command message
amfdata = AMF::parse(next.data); amfdata = AMF::parse(next.data);
@ -879,10 +879,10 @@ namespace Mist {
} }
break; break;
case 22: case 22:
DEBUG_MSG(DLVL_MEDIUM, "Received aggregate message"); MEDIUM_MSG("Received aggregate message");
break; break;
default: 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; break;
} }
} }

View file

@ -9,17 +9,19 @@ namespace Mist {
class OutRTMP : public Output { class OutRTMP : public Output {
public: public:
OutRTMP(Socket::Connection & conn); OutRTMP(Socket::Connection & conn);
~OutRTMP();
static void init(Util::Config * cfg); static void init(Util::Config * cfg);
void onRequest(); void onRequest();
void sendNext(); void sendNext();
void sendHeader(); void sendHeader();
bool isReadyForPlay();
protected: protected:
bool isPushing;
void parseVars(std::string data); void parseVars(std::string data);
std::string app_name; std::string app_name;
void parseChunk(Socket::Buffer & inputBuffer); void parseChunk(Socket::Buffer & inputBuffer);
void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId); void parseAMFCommand(AMF::Object & amfData, int messageType, int streamId);
void sendCommand(AMF::Object & amfReply, int messageType, int streamId); void sendCommand(AMF::Object & amfReply, int messageType, int streamId);
virtual std::string getStatsName();
}; };
} }

View file

@ -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"]["help"] = "What streamname to serve. For multiple streams, add this protocol multiple times using different ports.";
capa["required"]["streamname"]["type"] = "str"; capa["required"]["streamname"]["type"] = "str";
capa["required"]["streamname"]["option"] = "--stream"; capa["required"]["streamname"]["option"] = "--stream";
capa["required"]["streamname"]["short"] = "s";
capa["optional"]["tracks"]["name"] = "Tracks"; 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"]["help"] = "The track IDs of the stream that this connector will transmit separated by spaces";
capa["optional"]["tracks"]["type"] = "str"; capa["optional"]["tracks"]["type"] = "str";
capa["optional"]["tracks"]["option"] = "--tracks"; capa["optional"]["tracks"]["option"] = "--tracks";
capa["optional"]["tracks"]["short"] = "t";
capa["optional"]["tracks"]["default"] = "";
capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][1u].append("AAC"); capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3"); 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); cfg->addConnectorOptions(8888, capa);
config = cfg; config = cfg;
} }

View file

@ -32,7 +32,7 @@ namespace Mist {
if (packData.getBytesFree() == 184){ if (packData.getBytesFree() == 184){
packData.clear(); packData.clear();
packData.setPID(0x100 - 1 + thisPacket.getTrackId()); packData.setPID(thisPacket.getTrackId());
packData.setContinuityCounter(++contCounters[packData.getPID()]); packData.setContinuityCounter(++contCounters[packData.getPID()]);
if (first[thisPacket.getTrackId()]){ if (first[thisPacket.getTrackId()]){
packData.setUnitStart(1); packData.setUnitStart(1);
@ -125,7 +125,7 @@ namespace Mist {
break; break;
} }
if (alreadySent + 4 > watKunnenWeIn1Ding){ if (alreadySent + 4 > watKunnenWeIn1Ding){
nalLead = 4 - watKunnenWeIn1Ding-alreadySent; nalLead = 4 - (watKunnenWeIn1Ding-alreadySent);
fillPacket("\000\000\000\001",watKunnenWeIn1Ding-alreadySent); fillPacket("\000\000\000\001",watKunnenWeIn1Ding-alreadySent);
i += watKunnenWeIn1Ding-alreadySent; i += watKunnenWeIn1Ding-alreadySent;
alreadySent += watKunnenWeIn1Ding-alreadySent; alreadySent += watKunnenWeIn1Ding-alreadySent;