diff --git a/.gitignore b/.gitignore index b1739226..34faf555 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,5 @@ build.ninja rules.ninja .ninja_log .ninja_deps +aes_ctr128 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9635144f..f05b4c72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ set(libHeaders ${SOURCE_DIR}/lib/defines.h ${SOURCE_DIR}/lib/dtsc.h ${SOURCE_DIR}/lib/flv_tag.h + ${SOURCE_DIR}/lib/h264.h ${SOURCE_DIR}/lib/http_parser.h ${SOURCE_DIR}/lib/json.h ${SOURCE_DIR}/lib/mp4_adobe.h @@ -120,6 +121,7 @@ set(libHeaders ${SOURCE_DIR}/lib/timing.h ${SOURCE_DIR}/lib/tinythread.h ${SOURCE_DIR}/lib/ts_packet.h + ${SOURCE_DIR}/lib/util.h ${SOURCE_DIR}/lib/vorbis.h ) @@ -136,6 +138,7 @@ set(libSources ${SOURCE_DIR}/lib/dtsc.cpp ${SOURCE_DIR}/lib/dtscmeta.cpp ${SOURCE_DIR}/lib/flv_tag.cpp + ${SOURCE_DIR}/lib/h264.cpp ${SOURCE_DIR}/lib/http_parser.cpp ${SOURCE_DIR}/lib/json.cpp ${SOURCE_DIR}/lib/mp4_adobe.cpp @@ -153,6 +156,7 @@ set(libSources ${SOURCE_DIR}/lib/timing.cpp ${SOURCE_DIR}/lib/tinythread.cpp ${SOURCE_DIR}/lib/ts_packet.cpp + ${SOURCE_DIR}/lib/util.cpp ${SOURCE_DIR}/lib/vorbis.cpp ) @@ -245,9 +249,9 @@ makeInput(Buffer buffer) ######################################## macro(makeOutput outputName format) #Parse all extra arguments, for http and ts flags + SET (tsBaseClass Output) if (";${ARGN};" MATCHES ";http;") SET(httpOutput src/output/output_http.cpp) - SET(tsBaseClass Output) if (";${ARGN};" MATCHES ";ts;") SET(tsBaseClass HTTPOutput) endif() diff --git a/Doxyfile.in b/Doxyfile.in index 2c30f767..ca69803f 100644 --- a/Doxyfile.in +++ b/Doxyfile.in @@ -228,7 +228,7 @@ TAB_SIZE = 2 # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines. -ALIASES = "api=\xrefitem api \"API call\" \"API calls\"" +ALIASES = "api=\xrefitem api \"API call\" \"API calls\"" "triggers=\xrefitem triggers \"Trigger\" \"Triggers\"" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt deleted file mode 100644 index 4b84051d..00000000 --- a/lib/CMakeLists.txt +++ /dev/null @@ -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 -) diff --git a/lib/bitfields.h b/lib/bitfields.h index 0ca119a4..8fb8dc98 100644 --- a/lib/bitfields.h +++ b/lib/bitfields.h @@ -15,7 +15,7 @@ namespace Bit{ //Host to binary/binary to host functions - similar to kernel ntoh/hton functions. /// Retrieves a short in network order from the pointer p. - inline unsigned short btohs(char * p) { + inline unsigned short btohs(const char * p) { return ((unsigned short)p[0] << 8) | p[1]; } diff --git a/lib/config.cpp b/lib/config.cpp index 15629e4d..7f3990c8 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -32,6 +32,7 @@ #include #include #include //for getMyExec +#include "procs.h" bool Util::Config::is_active = false; unsigned int Util::Config::printDebugLevel = DEBUG;// @@ -69,8 +70,6 @@ Util::Config::Config(std::string cmd) { /// { /// "short":"o", //The short option letter /// "long":"onName", //The long option -/// "short_off":"n", //The short option-off letter -/// "long_off":"offName", //The long option-off /// "arg":"integer", //The type of argument, if required. /// "value":[], //The default value(s) for this option if it is not given on the commandline. /// "arg_num":1, //The count this value has on the commandline, after all the options have been processed. @@ -88,9 +87,6 @@ void Util::Config::addOption(std::string optname, JSON::Value option) { if (it->isMember("long")) { long_count++; } - if (it->isMember("long_off")) { - long_count++; - } } } @@ -110,12 +106,6 @@ void Util::Config::printHelp(std::ostream & output) { longest = current; } current = 0; - if (it->isMember("long_off")) { - current += (*it)["long_off"].asString().size() + 4; - } - if (it->isMember("short_off")) { - current += (*it)["short_off"].asString().size() + 3; - } if (current > longest) { longest = current; } @@ -158,26 +148,6 @@ void Util::Config::printHelp(std::ostream & output) { output << f << (*it)["help"].asString() << std::endl; } } - if (it->isMember("long_off") || it->isMember("short_off")) { - if (it->isMember("long_off") && it->isMember("short_off")) { - f = "--" + (*it)["long_off"].asString() + ", -" + (*it)["short_off"].asString(); - } else { - if (it->isMember("long_off")) { - f = "--" + (*it)["long_off"].asString(); - } - if (it->isMember("short_off")) { - f = "-" + (*it)["short_off"].asString(); - } - } - while (f.size() < longest) { - f.append(" "); - } - if (it->isMember("arg")) { - output << f << "(" << (*it)["arg"].asString() << ") " << (*it)["help"].asString() << std::endl; - } else { - output << f << (*it)["help"].asString() << std::endl; - } - } if (it->isMember("arg_num")) { f = it.key(); while (f.size() < longest) { @@ -204,12 +174,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { shortopts += ":"; } } - if (it->isMember("short_off")) { - shortopts += (*it)["short_off"].asString(); - if (it->isMember("arg")) { - shortopts += ":"; - } - } if (it->isMember("long")) { longOpts[long_i].name = (*it)["long"].asStringRef().c_str(); longOpts[long_i].val = (*it)["short"].asString()[0]; @@ -218,14 +182,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { } long_i++; } - if (it->isMember("long_off")) { - longOpts[long_i].name = (*it)["long_off"].asStringRef().c_str(); - longOpts[long_i].val = (*it)["short_off"].asString()[0]; - if (it->isMember("arg")) { - longOpts[long_i].has_arg = 1; - } - long_i++; - } if (it->isMember("arg_num") && !(it->isMember("value") && (*it)["value"].size())) { if ((*it)["arg_num"].asInt() > arg_count) { arg_count = (*it)["arg_num"].asInt(); @@ -263,9 +219,6 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { } break; } - if (it->isMember("short_off") && (*it)["short_off"].asString()[0] == opt) { - (*it)["value"].append((long long int)0); - } } break; } @@ -289,6 +242,10 @@ bool Util::Config::parseArgs(int & argc, char ** & argv) { return true; } +bool Util::Config::hasOption(const std::string & optname) { + return vals.isMember(optname); +} + /// Returns a reference to the current value of an option or default if none was set. /// If the option does not exist, this exits the application with a return code of 37. JSON::Value & Util::Config::getOption(std::string optname, bool asArray) { @@ -298,12 +255,18 @@ JSON::Value & Util::Config::getOption(std::string optname, bool asArray) { } if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()) { vals[optname]["value"].append(JSON::Value()); + vals[optname]["value"].shrink(0); } if (asArray) { return vals[optname]["value"]; } else { int n = vals[optname]["value"].size(); - return vals[optname]["value"][n - 1]; + if (!n){ + static JSON::Value empty = ""; + return empty; + }else{ + return vals[optname]["value"][n - 1]; + } } } @@ -341,6 +304,7 @@ static void callThreadCallback(void * cDataArg) { } int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { + Util::Procs::socketList.insert(server_socket.getSocket()); while (is_active && server_socket.connected()) { Socket::Connection S = server_socket.accept(); if (S.connected()) { //check if the new connection is valid @@ -356,11 +320,13 @@ int Util::Config::threadServer(Socket::Server & server_socket, int (*callback)(S Util::sleep(10); //sleep 10ms } } + Util::Procs::socketList.erase(server_socket.getSocket()); server_socket.close(); return 0; } int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Socket::Connection &)) { + Util::Procs::socketList.insert(server_socket.getSocket()); while (is_active && server_socket.connected()) { Socket::Connection S = server_socket.accept(); if (S.connected()) { //check if the new connection is valid @@ -376,6 +342,7 @@ int Util::Config::forkServer(Socket::Server & server_socket, int (*callback)(Soc Util::sleep(10); //sleep 10ms } } + Util::Procs::socketList.erase(server_socket.getSocket()); server_socket.close(); return 0; } @@ -385,8 +352,8 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)) { if (vals.isMember("socket")) { server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); } - if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { - server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + if (vals.isMember("port") && vals.isMember("interface")) { + server_socket = Socket::Server(getInteger("port"), getString("interface"), false); } if (!server_socket.connected()) { DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); @@ -402,8 +369,8 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) { if (vals.isMember("socket")) { server_socket = Socket::Server(Util::getTmpFolder() + getString("socket")); } - if (vals.isMember("listen_port") && vals.isMember("listen_interface")) { - server_socket = Socket::Server(getInteger("listen_port"), getString("listen_interface"), false); + if (vals.isMember("port") && vals.isMember("interface")) { + server_socket = Socket::Server(getInteger("port"), getString("interface"), false); } if (!server_socket.connected()) { DEBUG_MSG(DLVL_DEVEL, "Failure to open socket"); @@ -416,7 +383,6 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection & S)) { /// Activated the stored config. This will: /// - Drop permissions to the stored "username", if any. -/// - Daemonize the process if "daemonize" exists and is true. /// - Set is_active to true. /// - Set up a signal handler to set is_active to false for the SIGINT, SIGHUP and SIGTERM signals. void Util::Config::activate() { @@ -424,14 +390,6 @@ void Util::Config::activate() { setUser(getString("username")); vals.removeMember("username"); } - if (vals.isMember("daemonize") && getBool("daemonize")) { - if (vals.isMember("logfile") && getString("logfile") != "") { - Daemonize(true); - } else { - Daemonize(false); - } - vals.removeMember("daemonize"); - } struct sigaction new_action; struct sigaction cur_action; new_action.sa_sigaction = signal_handler; @@ -476,33 +434,78 @@ void Util::Config::signal_handler(int signum, siginfo_t * sigInfo, void * ignore } } //signal_handler + +/// Adds the options from the given JSON capabilities structure. +/// Recurses into optional and required, added options as needed. +void Util::Config::addOptionsFromCapabilities(const JSON::Value & capa){ + //First add the required options. + if (capa.isMember("required") && capa["required"].size()){ + jsonForEachConst(capa["required"], it){ + if (!it->isMember("short") || !it->isMember("option") || !it->isMember("type")){ + FAIL_MSG("Incomplete required option: %s", it.key().c_str()); + continue; + } + JSON::Value opt; + opt["short"] = (*it)["short"]; + opt["long"] = (*it)["option"].asStringRef().substr(2); + if (it->isMember("type")){ + //int, uint, debug, select, str + if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){ + opt["arg"] = "integer"; + }else{ + opt["arg"] = "string"; + } + } + if (it->isMember("default")){ + opt["value"].append((*it)["default"]); + } + opt["help"] = (*it)["help"]; + addOption(it.key(), opt); + } + } + //Then, the optionals. + if (capa.isMember("optional") && capa["optional"].size()){ + jsonForEachConst(capa["optional"], it){ + if (it.key() == "debug"){continue;} + if (!it->isMember("short") || !it->isMember("option") || !it->isMember("default")){ + FAIL_MSG("Incomplete optional option: %s", it.key().c_str()); + continue; + } + JSON::Value opt; + opt["short"] = (*it)["short"]; + opt["long"] = (*it)["option"].asStringRef().substr(2); + if (it->isMember("type")){ + //int, uint, debug, select, str + if ((*it)["type"].asStringRef() == "int" || (*it)["type"].asStringRef() == "uint"){ + opt["arg"] = "integer"; + }else{ + opt["arg"] = "string"; + } + } + if (it->isMember("default")){ + opt["value"].append((*it)["default"]); + } + opt["help"] = (*it)["help"]; + addOption(it.key(), opt); + } + } +} + /// Adds the default connector options. Also updates the capabilities structure with the default options. /// Besides the options addBasicConnectorOptions adds, this function also adds port and interface options. void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { - JSON::Value option; - option.null(); - option["long"] = "port"; - option["short"] = "p"; - option["arg"] = "integer"; - option["help"] = "TCP port to listen on"; - option["value"].append((long long)port); - addOption("listen_port", option); capabilities["optional"]["port"]["name"] = "TCP port"; - capabilities["optional"]["port"]["help"] = "TCP port to listen on - default if unprovided is " + option["value"][0u].asString(); + capabilities["optional"]["port"]["help"] = "TCP port to listen on"; capabilities["optional"]["port"]["type"] = "uint"; + capabilities["optional"]["port"]["short"] = "p"; capabilities["optional"]["port"]["option"] = "--port"; - capabilities["optional"]["port"]["default"] = option["value"][0u]; + capabilities["optional"]["port"]["default"] = (long long)port; - option.null(); - option["long"] = "interface"; - option["short"] = "i"; - option["arg"] = "string"; - option["help"] = "Interface address to listen on, or 0.0.0.0 for all available interfaces."; - option["value"].append("0.0.0.0"); - addOption("listen_interface", option); capabilities["optional"]["interface"]["name"] = "Interface"; - capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on - default if unprovided is all interfaces"; + capabilities["optional"]["interface"]["help"] = "Address of the interface to listen on"; + capabilities["optional"]["interface"]["default"] = "0.0.0.0"; capabilities["optional"]["interface"]["option"] = "--interface"; + capabilities["optional"]["interface"]["short"] = "i"; capabilities["optional"]["interface"]["type"] = "str"; addBasicConnectorOptions(capabilities); @@ -510,38 +513,16 @@ void Util::Config::addConnectorOptions(int port, JSON::Value & capabilities) { /// Adds the default connector options. Also updates the capabilities structure with the default options. void Util::Config::addBasicConnectorOptions(JSON::Value & capabilities) { - JSON::Value option; - option.null(); - option["long"] = "username"; - option["short"] = "u"; - option["arg"] = "string"; - option["help"] = "Username to drop privileges to, or root to not drop provileges."; - option["value"].append("root"); - addOption("username", option); capabilities["optional"]["username"]["name"] = "Username"; capabilities["optional"]["username"]["help"] = "Username to drop privileges to - default if unprovided means do not drop privileges"; capabilities["optional"]["username"]["option"] = "--username"; + capabilities["optional"]["username"]["short"] = "u"; + capabilities["optional"]["username"]["default"] = "root"; capabilities["optional"]["username"]["type"] = "str"; + addOptionsFromCapabilities(capabilities); - if (capabilities.isMember("socket")) { - option.null(); - option["arg"] = "string"; - option["help"] = "Socket name that can be connected to for this connector."; - option["value"].append(capabilities["socket"]); - addOption("socket", option); - } - - option.null(); - option["long"] = "daemon"; - option["short"] = "d"; - option["long_off"] = "nodaemon"; - option["short_off"] = "n"; - option["help"] = "Whether or not to daemonize the process after starting."; - option["value"].append(0ll); - addOption("daemonize", option); - - option.null(); + JSON::Value option; option["long"] = "json"; option["short"] = "j"; option["help"] = "Output connector info in JSON format, then exit."; @@ -631,17 +612,3 @@ void Util::setUser(std::string username) { } } -/// Will turn the current process into a daemon. -/// Works by calling daemon(1,0): -/// Does not change directory to root. -/// Does redirect output to /dev/null -void Util::Daemonize(bool notClose) { - DEBUG_MSG(DLVL_DEVEL, "Going into background mode..."); - int noClose = 0; - if (notClose) { - noClose = 1; - } - if (daemon(1, noClose) < 0) { - DEBUG_MSG(DLVL_ERROR, "Failed to daemonize: %s", strerror(errno)); - } -} diff --git a/lib/config.h b/lib/config.h index af3d2c26..bdefe569 100644 --- a/lib/config.h +++ b/lib/config.h @@ -30,6 +30,7 @@ namespace Util { void addOption(std::string optname, JSON::Value option); void printHelp(std::ostream & output); bool parseArgs(int & argc, char ** & argv); + bool hasOption(const std::string & optname); JSON::Value & getOption(std::string optname, bool asArray = false); std::string getString(std::string optname); long long int getInteger(std::string optname); @@ -40,6 +41,7 @@ namespace Util { int serveThreadedSocket(int (*callback)(Socket::Connection & S)); int serveForkedSocket(int (*callback)(Socket::Connection & S)); int servePlainSocket(int (*callback)(Socket::Connection & S)); + void addOptionsFromCapabilities(const JSON::Value & capabilities); void addBasicConnectorOptions(JSON::Value & capabilities); void addConnectorOptions(int port, JSON::Value & capabilities); }; @@ -53,7 +55,4 @@ namespace Util { /// Will set the active user to the named username. void setUser(std::string user); - /// Will turn the current process into a daemon. - void Daemonize(bool notClose = false); - } diff --git a/lib/defines.h b/lib/defines.h index 917f9fe5..ea7583bf 100644 --- a/lib/defines.h +++ b/lib/defines.h @@ -57,11 +57,28 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", " #endif + +#ifndef SHM_DATASIZE +#define SHM_DATASIZE 25 +#endif + + +#ifndef STATS_DELAY +#define STATS_DELAY 15 +#endif + +#ifndef INPUT_TIMEOUT +#define INPUT_TIMEOUT STATS_DELAY +#endif + /// The size used for stream header pages under Windows, where they cannot be size-detected. #define DEFAULT_META_PAGE_SIZE 16 * 1024 * 1024 +/// The size used for stream header pages under Windows, where they cannot be size-detected. +#define DEFAULT_STRM_PAGE_SIZE 4 * 1024 * 1024 + /// The size used for stream data pages under Windows, where they cannot be size-detected. -#define DEFAULT_DATA_PAGE_SIZE 25 * 1024 * 1024 +#define DEFAULT_DATA_PAGE_SIZE SHM_DATASIZE * 1024 * 1024 /// The size used for server configuration pages. #define DEFAULT_CONF_PAGE_SIZE 4 * 1024 * 1024 @@ -72,10 +89,15 @@ static const char * DBG_LVL_LIST[] = {"NONE", "FAIL", "ERROR", "WARN", "INFO", " #define SHM_STREAM_INDEX "MstSTRM%s" //%s stream name #define SHM_TRACK_META "MstTRAK%s@%lu" //%s stream name, %lu track ID #define SHM_TRACK_INDEX "MstTRID%s@%lu" //%s stream name, %lu track ID +#define SHM_TRACK_INDEX_SIZE 8192 #define SHM_TRACK_DATA "MstDATA%s@%lu_%lu" //%s stream name, %lu track ID, %lu page # #define SHM_STATISTICS "MstSTAT" #define SHM_USERS "MstUSER%s" //%s stream name -#define SEM_LIVE "MstLIVE%s" //%s stream name +#define SHM_TRIGGER "MstTRIG%s" //%s trigger name +#define SEM_LIVE "/MstLIVE%s" //%s stream name +#define SEM_INPUT "/MstInpt%s" //%s stream name +#define SEM_CONF "/MstConfLock" +#define SHM_CONF "MstConf" #define NAME_BUFFER_SIZE 200 //char buffer size for snprintf'ing shm filenames #define SIMUL_TRACKS 10 diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index 4f25c07c..ad02c164 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -9,6 +9,7 @@ char DTSC::Magic_Header[] = "DTSC"; char DTSC::Magic_Packet[] = "DTPD"; char DTSC::Magic_Packet2[] = "DTP2"; +char DTSC::Magic_Command[] = "DTCM"; DTSC::File::File() { F = 0; @@ -32,8 +33,7 @@ DTSC::File & DTSC::File::operator =(const File & rhs) { if (rhs.myPack) { myPack = rhs.myPack; } - metaStorage = rhs.metaStorage; - metadata = metaStorage; + metadata = rhs.metadata; currtime = rhs.currtime; lastreadpos = rhs.lastreadpos; headerSize = rhs.headerSize; @@ -67,7 +67,7 @@ DTSC::File::File(std::string filename, bool create) { } created = create; if (!F) { - DEBUG_MSG(DLVL_ERROR, "Could not open file %s", filename.c_str()); + HIGH_MSG("Could not open file %s", filename.c_str()); return; } fseek(F, 0, SEEK_END); @@ -83,7 +83,7 @@ DTSC::File::File(std::string filename, bool create) { return; } if (memcmp(buffer, DTSC::Magic_Header, 4) != 0) { - if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && memcmp(buffer, DTSC::Magic_Packet, 4) != 0) { + if (memcmp(buffer, DTSC::Magic_Packet2, 4) != 0 && memcmp(buffer, DTSC::Magic_Packet, 4) != 0 && memcmp(buffer, DTSC::Magic_Command, 4) != 0) { DEBUG_MSG(DLVL_ERROR, "%s is not a valid DTSC file", filename.c_str()); fclose(F); F = 0; @@ -113,8 +113,7 @@ DTSC::File::File(std::string filename, bool create) { fseek(F, 0, SEEK_SET); File Fhead(filename + ".dtsh"); if (Fhead) { - metaStorage = Fhead.metaStorage; - metadata = metaStorage; + metadata = Fhead.metadata; } } currframe = 0; @@ -346,8 +345,9 @@ void DTSC::File::seekNext() { } void DTSC::File::parseNext(){ + char header_buffer[4] = {0, 0, 0, 0}; lastreadpos = ftell(F); - if (fread(buffer, 4, 1, F) != 1) { + if (fread(header_buffer, 4, 1, F) != 1) { if (feof(F)) { DEBUG_MSG(DLVL_DEVEL, "End of file reached @ %d", (int)lastreadpos); } else { @@ -356,55 +356,26 @@ void DTSC::File::parseNext(){ myPack.null(); return; } - if (memcmp(buffer, DTSC::Magic_Header, 4) == 0) { - if (lastreadpos != 0) { - readHeader(lastreadpos); - std::string tmp = metaStorage.toNetPacked(); - myPack.reInit(tmp.data(), tmp.size()); - DEBUG_MSG(DLVL_DEVEL, "Read another header"); - } else { - if (fread(buffer, 4, 1, F) != 1) { - DEBUG_MSG(DLVL_ERROR, "Could not read header size @ %d", (int)lastreadpos); - myPack.null(); - return; - } - long packSize = ntohl(((unsigned long *)buffer)[0]); - std::string strBuffer = "DTSC"; - strBuffer.append((char *)buffer, 4); - strBuffer.resize(packSize + 8); - if (fread((void *)(strBuffer.c_str() + 8), packSize, 1, F) != 1) { - DEBUG_MSG(DLVL_ERROR, "Could not read header @ %d", (int)lastreadpos); - myPack.null(); - return; - } - myPack.reInit(strBuffer.data(), strBuffer.size()); - } - return; - } long long unsigned int version = 0; - if (memcmp(buffer, DTSC::Magic_Packet, 4) == 0) { + if (memcmp(header_buffer, DTSC::Magic_Packet, 4) == 0 || memcmp(header_buffer, DTSC::Magic_Command, 4) == 0 || memcmp(header_buffer, DTSC::Magic_Header, 4) == 0) { version = 1; } - if (memcmp(buffer, DTSC::Magic_Packet2, 4) == 0) { + if (memcmp(header_buffer, DTSC::Magic_Packet2, 4) == 0) { version = 2; } if (version == 0) { - DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x - %.4s != %.4s @ %d", (unsigned int)lastreadpos, (char *)buffer, DTSC::Magic_Packet2, (int)lastreadpos); + DEBUG_MSG(DLVL_ERROR, "Invalid packet header @ %#x: %.4s", (unsigned int)lastreadpos, (char *)buffer); myPack.null(); return; } if (fread(buffer, 4, 1, F) != 1) { - DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %d", (int)lastreadpos); + DEBUG_MSG(DLVL_ERROR, "Could not read packet size @ %#x", (unsigned int)lastreadpos); myPack.null(); return; } long packSize = ntohl(((unsigned long *)buffer)[0]); char * packBuffer = (char *)malloc(packSize + 8); - if (version == 1) { - memcpy(packBuffer, "DTPD", 4); - } else { - memcpy(packBuffer, "DTP2", 4); - } + memcpy(packBuffer, header_buffer, 4); memcpy(packBuffer + 4, buffer, 4); if (fread((void *)(packBuffer + 8), packSize, 1, F) != 1) { DEBUG_MSG(DLVL_ERROR, "Could not read packet @ %d", (int)lastreadpos); @@ -432,6 +403,11 @@ bool DTSC::File::seek_time(unsigned int ms, unsigned int trackNo, bool forceSeek if (!forceSeek && myPack && ms >= myPack.getTime() && trackNo >= myPack.getTrackId()) { tmpPos.seekTime = myPack.getTime(); tmpPos.bytePos = getBytePos(); + /* + if (trackNo == myPack.getTrackId()){ + tmpPos.bytePos += myPack.getDataLen(); + } + */ } else { tmpPos.seekTime = 0; tmpPos.bytePos = 0; diff --git a/lib/dtsc.h b/lib/dtsc.h index 4b2de033..5d70e893 100644 --- a/lib/dtsc.h +++ b/lib/dtsc.h @@ -34,6 +34,7 @@ namespace DTSC { extern char Magic_Header[]; ///< The magic bytes for a DTSC header extern char Magic_Packet[]; ///< The magic bytes for a DTSC packet extern char Magic_Packet2[]; ///< The magic bytes for a DTSC packet version 2 + extern char Magic_Command[]; ///< The magic bytes for a DTCM packet ///\brief A simple structure used for ordering byte seek positions. struct seekPos { @@ -61,7 +62,8 @@ namespace DTSC { DTSC_INVALID, DTSC_HEAD, DTSC_V1, - DTSC_V2 + DTSC_V2, + DTCM }; /// This class allows scanning through raw binary format DTSC data. @@ -107,6 +109,7 @@ namespace DTSC { void operator = (const Packet & rhs); operator bool() const; packType getVersion() const; + void reInit(Socket::Connection & src); void reInit(const char * data_, unsigned int len, bool noCopy = false); void genericFill(long long packTime, long long packOffset, long long packTrack, const char * packData, long long packDataSize, long long packBytePos, bool isKeyframe); void getString(const char * identifier, char *& result, unsigned int & len) const; @@ -205,6 +208,17 @@ namespace DTSC { char * getData(); void toPrettyString(std::ostream & str, int indent = 0); private: +#ifdef BIGMETA +#define PACKED_KEY_SIZE 25 + ///\brief Data storage for this Key. + /// + /// - 8 bytes: MSB storage of the position of the first packet of this keyframe within the file. + /// - 3 bytes: MSB storage of the duration of this keyframe. + /// - 4 bytes: MSB storage of the number of this keyframe. + /// - 2 bytes: MSB storage of the amount of parts in this keyframe. + /// - 8 bytes: MSB storage of the timestamp associated with this keyframe's first packet. +#else +#define PACKED_KEY_SIZE 16 ///\brief Data storage for this Key. /// /// - 5 bytes: MSB storage of the position of the first packet of this keyframe within the file. @@ -212,7 +226,8 @@ namespace DTSC { /// - 2 bytes: MSB storage of the number of this keyframe. /// - 2 bytes: MSB storage of the amount of parts in this keyframe. /// - 4 bytes: MSB storage of the timestamp associated with this keyframe's first packet. - char data[16]; +#endif + char data[PACKED_KEY_SIZE]; }; ///\brief Basic class for storage of data associated with fragments. @@ -229,13 +244,24 @@ namespace DTSC { char * getData(); void toPrettyString(std::ostream & str, int indent = 0); private: - ///\Brief Data storage for this Fragment. +#ifdef BIGMETA +#define PACKED_FRAGMENT_SIZE 13 + ///\brief Data storage for this Fragment. + /// + /// - 4 bytes: duration (in milliseconds) + /// - 1 byte: length (amount of keyframes) + /// - 4 bytes: number of first keyframe in fragment + /// - 4 bytes: size of fragment in bytes +#else +#define PACKED_FRAGMENT_SIZE 11 + ///\brief Data storage for this Fragment. /// /// - 4 bytes: duration (in milliseconds) /// - 1 byte: length (amount of keyframes) /// - 2 bytes: number of first keyframe in fragment /// - 4 bytes: size of fragment in bytes - char data[11]; +#endif + char data[PACKED_FRAGMENT_SIZE]; }; ///\brief Class for storage of track data @@ -249,10 +275,10 @@ namespace DTSC { return (parts.size() && keySizes.size() && (keySizes.size() == keys.size())); } void update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size = 5000); - int getSendLen(); - void send(Socket::Connection & conn); + int getSendLen(bool skipDynamic = false); + void send(Socket::Connection & conn, bool skipDynamic = false); void writeTo(char *& p); - JSON::Value toJSON(); + JSON::Value toJSON(bool skipDynamic = false); std::deque fragments; std::deque keys; std::deque keySizes; @@ -302,8 +328,8 @@ namespace DTSC { void updatePosOverride(DTSC::Packet & pack, unsigned long bpos); void update(JSON::Value & pack, unsigned long segment_size = 5000); void update(long long packTime, long long packOffset, long long packTrack, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize = 0, unsigned long segment_size = 5000); - unsigned int getSendLen(); - void send(Socket::Connection & conn); + unsigned int getSendLen(bool skipDynamic = false, std::set selectedTracks = std::set()); + void send(Socket::Connection & conn, bool skipDynamic = false, std::set selectedTracks = std::set()); void writeTo(char * p); JSON::Value toJSON(); void reset(); @@ -348,7 +374,6 @@ namespace DTSC { long int endPos; void readHeader(int pos); DTSC::Packet myPack; - JSON::Value metaStorage; Meta metadata; std::map trackMapping; long long int currtime; diff --git a/lib/dtscmeta.cpp b/lib/dtscmeta.cpp index d431f222..1913925e 100644 --- a/lib/dtscmeta.cpp +++ b/lib/dtscmeta.cpp @@ -109,6 +109,32 @@ namespace DTSC { } } + void Packet::reInit(Socket::Connection & src) { + int sleepCount = 0; + null(); + int toReceive = 0; + while (src.connected()){ + if (!toReceive && src.Received().available(8)){ + if (src.Received().copy(2) != "DT"){ + INFO_MSG("Invalid DTSC Packet header encountered (%s)", src.Received().copy(4).c_str()); + break; + } + toReceive = Bit::btohl(src.Received().copy(8).data() + 4); + } + if (toReceive && src.Received().available(toReceive + 8)){ + std::string dataBuf = src.Received().remove(toReceive + 8); + reInit(dataBuf.data(), dataBuf.size()); + return; + } + if(!src.spool()){ + if (sleepCount++ > 60){ + return; + } + Util::sleep(100); + } + } + } + ///\brief Initializes a packet with new data ///\param data_ The new data for the packet ///\param len The length of the data pointed to by data_ @@ -159,11 +185,15 @@ namespace DTSC { if (!memcmp(data, Magic_Header, 4)) { version = DTSC_HEAD; } else { + if (!memcmp(data, Magic_Command, 4)) { + version = DTCM; + } else { DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with invalid header"); return; } } } + } } else { DEBUG_MSG(DLVL_FAIL, "ReInit received a packet with size < 4"); return; @@ -248,7 +278,7 @@ namespace DTSC { if (p[0] == DTSC_OBJ || p[0] == DTSC_CON) { p++; //object, scan contents - while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE) + while (p < max && p[0] + p[1] != 0) { //while not encountering 0x0000 (we assume 0x0000EE) if (p + 2 >= max) { return 0;//out of packet! } @@ -264,7 +294,7 @@ namespace DTSC { if (p[0] == DTSC_ARR) { p++; //array, scan contents - while (p[0] + p[1] != 0 && p < max) { //while not encountering 0x0000 (we assume 0x0000EE) + while (p < max && p[0] + p[1] != 0) { //while not encountering 0x0000 (we assume 0x0000EE) //search through contents... p = skipDTSC(p, max); if (!p) { @@ -844,53 +874,93 @@ namespace DTSC { ///\brief Returns the byteposition of a keyframe unsigned long long Key::getBpos() { +#ifdef BIGMETA + return Bit::btohll(data); +#else return (((unsigned long long)data[0] << 32) | (data[1] << 24) | (data[2] << 16) | (data[3] << 8) | data[4]); +#endif } void Key::setBpos(unsigned long long newBpos) { +#ifdef BIGMETA + Bit::htobll(data, newBpos); +#else data[4] = newBpos & 0xFF; data[3] = (newBpos >> 8) & 0xFF; data[2] = (newBpos >> 16) & 0xFF; data[1] = (newBpos >> 24) & 0xFF; data[0] = (newBpos >> 32) & 0xFF; +#endif } unsigned long Key::getLength() { +#ifdef BIGMETA + return Bit::btoh24(data+8); +#else return Bit::btoh24(data+5); +#endif } void Key::setLength(unsigned long newLength) { +#ifdef BIGMETA + Bit::htob24(data+8, newLength); +#else Bit::htob24(data+5, newLength); +#endif } ///\brief Returns the number of a keyframe unsigned long Key::getNumber() { +#ifdef BIGMETA + return Bit::btohl(data + 11); +#else return Bit::btohs(data + 8); +#endif } ///\brief Sets the number of a keyframe void Key::setNumber(unsigned long newNumber) { +#ifdef BIGMETA + Bit::htobl(data + 11, newNumber); +#else Bit::htobs(data + 8, newNumber); +#endif } ///\brief Returns the number of parts of a keyframe unsigned short Key::getParts() { +#ifdef BIGMETA + return Bit::btohs(data + 15); +#else return Bit::btohs(data + 10); +#endif } ///\brief Sets the number of parts of a keyframe void Key::setParts(unsigned short newParts) { +#ifdef BIGMETA + Bit::htobs(data + 15, newParts); +#else Bit::htobs(data + 10, newParts); +#endif } ///\brief Returns the timestamp of a keyframe unsigned long long Key::getTime() { +#ifdef BIGMETA + return Bit::btohll(data + 17); +#else return Bit::btohl(data + 12); +#endif } ///\brief Sets the timestamp of a keyframe void Key::setTime(unsigned long long newTime) { +#ifdef BIGMETA + Bit::htobll(data + 17, newTime); +#else Bit::htobl(data + 12, newTime); +#endif } ///\brief Returns the data of this keyframe struct @@ -927,22 +997,38 @@ namespace DTSC { ///\brief Returns the number of the first keyframe in this fragment unsigned long Fragment::getNumber() { +#ifdef BIGMETA + return Bit::btohl(data + 5); +#else return Bit::btohs(data + 5); +#endif } ///\brief Sets the number of the first keyframe in this fragment void Fragment::setNumber(unsigned long newNumber) { +#ifdef BIGMETA + Bit::htobl(data + 5, newNumber); +#else Bit::htobs(data + 5, newNumber); +#endif } ///\brief Returns the size of a fragment unsigned long Fragment::getSize() { +#ifdef BIGMETA + return Bit::btohl(data + 9); +#else return Bit::btohl(data + 7); +#endif } ///\brief Sets the size of a fragement void Fragment::setSize(unsigned long newSize) { +#ifdef BIGMETA + Bit::htobl(data + 9, newSize); +#else Bit::htobl(data + 7, newSize); +#endif } ///\brief Returns thte data of this fragment structure @@ -976,11 +1062,11 @@ namespace DTSC { Track::Track(JSON::Value & trackRef) { if (trackRef.isMember("fragments") && trackRef["fragments"].isString()) { Fragment * tmp = (Fragment *)trackRef["fragments"].asStringRef().data(); - fragments = std::deque(tmp, tmp + (trackRef["fragments"].asStringRef().size() / 11)); + fragments = std::deque(tmp, tmp + (trackRef["fragments"].asStringRef().size() / PACKED_FRAGMENT_SIZE)); } if (trackRef.isMember("keys") && trackRef["keys"].isString()) { Key * tmp = (Key *)trackRef["keys"].asStringRef().data(); - keys = std::deque(tmp, tmp + (trackRef["keys"].asStringRef().size() / 16)); + keys = std::deque(tmp, tmp + (trackRef["keys"].asStringRef().size() / PACKED_KEY_SIZE)); } if (trackRef.isMember("parts") && trackRef["parts"].isString()) { Part * tmp = (Part *)trackRef["parts"].asStringRef().data(); @@ -1018,13 +1104,13 @@ namespace DTSC { char * tmp = 0; unsigned int tmplen = 0; trackRef.getMember("fragments").getString(tmp, tmplen); - fragments = std::deque((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / 11)); + fragments = std::deque((Fragment *)tmp, ((Fragment *)tmp) + (tmplen / PACKED_FRAGMENT_SIZE)); } if (trackRef.getMember("keys").getType() == DTSC_STR) { char * tmp = 0; unsigned int tmplen = 0; trackRef.getMember("keys").getString(tmp, tmplen); - keys = std::deque((Key *)tmp, ((Key *)tmp) + (tmplen / 16)); + keys = std::deque((Key *)tmp, ((Key *)tmp) + (tmplen / PACKED_KEY_SIZE)); } if (trackRef.getMember("parts").getType() == DTSC_STR) { char * tmp = 0; @@ -1064,7 +1150,13 @@ namespace DTSC { ///Will also insert keyframes on non-video tracks, and creates fragments void Track::update(long long packTime, long long packOffset, long long packDataSize, long long packBytePos, bool isKeyframe, long long packSendSize, unsigned long segment_size) { if ((unsigned long long)packTime < lastms) { - DEBUG_MSG(DLVL_WARN, "Received packets for track %u in wrong order (%lld < %llu) - ignoring!", trackID, packTime, lastms); + static bool warned = false; + if (!warned){ + ERROR_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further messages on HIGH level.", trackID, packTime, lastms); + warned = true; + }else{ + HIGH_MSG("Received packets for track %u in wrong order (%lld < %llu) - ignoring! Further messages on HIGH level.", trackID, packTime, lastms); + } return; } Part newPart; @@ -1384,20 +1476,22 @@ namespace DTSC { } ///\brief Determines the "packed" size of a track - int Track::getSendLen() { - int result = 146 + init.size() + codec.size() + type.size() + getWritableIdentifier().size(); - result += fragments.size() * 11; - result += keys.size() * 16; + int Track::getSendLen(bool skipDynamic) { + int result = 107 + init.size() + codec.size() + type.size() + getWritableIdentifier().size(); + if (!skipDynamic){ + result += fragments.size() * PACKED_FRAGMENT_SIZE + 16; + result += keys.size() * PACKED_KEY_SIZE + 11; if (keySizes.size()){ - result += 11 + (keySizes.size() * 4) + 4; + result += (keySizes.size() * 4) + 15; + } + result += parts.size() * 9 + 12; } - result += parts.size() * 9; if (type == "audio") { result += 49; } else if (type == "video") { result += 48; } - if (missedFrags) { + if (!skipDynamic && missedFrags) { result += 23; } return result; @@ -1420,19 +1514,23 @@ namespace DTSC { ///\brief Writes a track to a pointer void Track::writeTo(char *& p) { + std::deque::iterator firstFrag = fragments.begin(); + if (fragments.size() && (&firstFrag) == 0){ + return; + } std::string trackIdent = getWritableIdentifier(); writePointer(p, convertShort(trackIdent.size()), 2); writePointer(p, trackIdent); writePointer(p, "\340", 1);//Begin track object writePointer(p, "\000\011fragments\002", 12); - writePointer(p, convertInt(fragments.size() * 11), 4); + writePointer(p, convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4); for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { - writePointer(p, it->getData(), 11); + writePointer(p, it->getData(), PACKED_FRAGMENT_SIZE); } writePointer(p, "\000\004keys\002", 7); - writePointer(p, convertInt(keys.size() * 16), 4); + writePointer(p, convertInt(keys.size() * PACKED_KEY_SIZE), 4); for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { - writePointer(p, it->getData(), 16); + writePointer(p, it->getData(), PACKED_KEY_SIZE); } writePointer(p, "\000\010keysizes\002,", 11); writePointer(p, convertInt(keySizes.size() * 4), 4); @@ -1490,19 +1588,20 @@ namespace DTSC { } ///\brief Writes a track to a socket - void Track::send(Socket::Connection & conn) { + void Track::send(Socket::Connection & conn, bool skipDynamic) { conn.SendNow(convertShort(getWritableIdentifier().size()), 2); conn.SendNow(getWritableIdentifier()); conn.SendNow("\340", 1);//Begin track object + if (!skipDynamic){ conn.SendNow("\000\011fragments\002", 12); - conn.SendNow(convertInt(fragments.size() * 11), 4); + conn.SendNow(convertInt(fragments.size() * PACKED_FRAGMENT_SIZE), 4); for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { - conn.SendNow(it->getData(), 11); + conn.SendNow(it->getData(), PACKED_FRAGMENT_SIZE); } conn.SendNow("\000\004keys\002", 7); - conn.SendNow(convertInt(keys.size() * 16), 4); + conn.SendNow(convertInt(keys.size() * PACKED_KEY_SIZE), 4); for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { - conn.SendNow(it->getData(), 16); + conn.SendNow(it->getData(), PACKED_KEY_SIZE); } conn.SendNow("\000\010keysizes\002,", 11); conn.SendNow(convertInt(keySizes.size() * 4), 4); @@ -1520,9 +1619,10 @@ namespace DTSC { for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { conn.SendNow(it->getData(), 9); } + } conn.SendNow("\000\007trackid\001", 10); conn.SendNow(convertLongLong(trackID), 8); - if (missedFrags) { + if (!skipDynamic && missedFrags) { conn.SendNow("\000\014missed_frags\001", 15); conn.SendNow(convertLongLong(missedFrags), 8); } @@ -1560,10 +1660,12 @@ namespace DTSC { } ///\brief Determines the "packed" size of a meta object - unsigned int Meta::getSendLen() { + unsigned int Meta::getSendLen(bool skipDynamic, std::set selectedTracks) { unsigned int dataLen = 16 + (vod ? 14 : 0) + (live ? 15 : 0) + (merged ? 17 : 0) + (bufferWindow ? 24 : 0) + 21; for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { - dataLen += it->second.getSendLen(); + if (!selectedTracks.size() || selectedTracks.count(it->first)){ + dataLen += it->second.getSendLen(skipDynamic); + } } return dataLen + 8; //add 8 bytes header } @@ -1600,13 +1702,15 @@ namespace DTSC { } ///\brief Writes a meta object to a socket - void Meta::send(Socket::Connection & conn) { - int dataLen = getSendLen() - 8; //strip 8 bytes header + void Meta::send(Socket::Connection & conn, bool skipDynamic, std::set selectedTracks) { + int dataLen = getSendLen(skipDynamic, selectedTracks) - 8; //strip 8 bytes header conn.SendNow(DTSC::Magic_Header, 4); conn.SendNow(convertInt(dataLen), 4); conn.SendNow("\340\000\006tracks\340", 10); for (std::map::iterator it = tracks.begin(); it != tracks.end(); it++) { - it->second.send(conn); + if (!selectedTracks.size() || selectedTracks.count(it->first)){ + it->second.send(conn, skipDynamic); + } } conn.SendNow("\000\000\356", 3);//End tracks object if (vod) { @@ -1631,35 +1735,38 @@ namespace DTSC { } ///\brief Converts a track to a JSON::Value - JSON::Value Track::toJSON() { + JSON::Value Track::toJSON(bool skipDynamic) { JSON::Value result; std::string tmp; - tmp.reserve(fragments.size() * 11); - for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { - tmp.append(it->getData(), 11); + if (!skipDynamic) { + tmp.reserve(fragments.size() * PACKED_FRAGMENT_SIZE); + for (std::deque::iterator it = fragments.begin(); it != fragments.end(); it++) { + tmp.append(it->getData(), PACKED_FRAGMENT_SIZE); + } + result["fragments"] = tmp; + tmp = ""; + tmp.reserve(keys.size() * PACKED_KEY_SIZE); + for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { + tmp.append(it->getData(), PACKED_KEY_SIZE); + } + result["keys"] = tmp; + tmp = ""; + tmp.reserve(keySizes.size() * 4); + for (unsigned int i = 0; i < keySizes.size(); i++){ + tmp += (char)((keySizes[i] >> 24)); + tmp += (char)((keySizes[i] >> 16)); + tmp += (char)((keySizes[i] >> 8)); + tmp += (char)(keySizes[i]); + } + result["keysizes"] = tmp; + tmp = ""; + tmp.reserve(parts.size() * 9); + for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { + tmp.append(it->getData(), 9); + } + result["parts"] = tmp; } - result["fragments"] = tmp; - tmp = ""; - tmp.reserve(keys.size() * 16); - for (std::deque::iterator it = keys.begin(); it != keys.end(); it++) { - tmp.append(it->getData(), 16); - } - result["keys"] = tmp; - tmp = ""; - tmp.reserve(keySizes.size() * 4); - for (unsigned int i = 0; i < keySizes.size(); i++){ - tmp += (char)((keySizes[i] >> 24)); - tmp += (char)((keySizes[i] >> 16)); - tmp += (char)((keySizes[i] >> 8)); - tmp += (char)(keySizes[i]); - } - result["keysizes"] = tmp; - tmp = ""; - tmp.reserve(parts.size() * 9); - for (std::deque::iterator it = parts.begin(); it != parts.end(); it++) { - tmp.append(it->getData(), 9); - } - result["parts"] = tmp; + result["init"] = init; result["trackid"] = trackID; result["firstms"] = (long long)firstms; result["lastms"] = (long long)lastms; @@ -1669,7 +1776,6 @@ namespace DTSC { } result["codec"] = codec; result["type"] = type; - result["init"] = init; if (type == "audio") { result["rate"] = rate; result["size"] = size; diff --git a/lib/flv_tag.cpp b/lib/flv_tag.cpp index 9d2289e5..939a96c6 100644 --- a/lib/flv_tag.cpp +++ b/lib/flv_tag.cpp @@ -11,6 +11,9 @@ #include //memcpy #include + +#include "h264.h" //Needed for init data parsing in case of invalid values from FLV init + /// Holds the last FLV header parsed. /// Defaults to a audio+video header on FLV version 0x01 if no header received yet. char FLV::Header[13] = {'F', 'L', 'V', 0x01, 0x05, 0, 0, 0, 0x09, 0, 0, 0, 0}; @@ -1099,6 +1102,15 @@ JSON::Value FLV::Tag::toJSON(DTSC::Meta & metadata, AMF::Object & amf_storage, u } metadata.tracks[reTrack].init = std::string((char *)data + 12, (size_t)len - 16); } + ///this is a hacky way around invalid FLV data (since it gets ignored nearly everywhere, but we do need correct data... + if (!metadata.tracks[reTrack].width || !metadata.tracks[reTrack].height || !metadata.tracks[reTrack].fpks){ + h264::sequenceParameterSet sps; + sps.fromDTSCInit(metadata.tracks[reTrack].init); + h264::SPSMeta spsChar = sps.getCharacteristics(); + metadata.tracks[reTrack].width = spsChar.width; + metadata.tracks[reTrack].height = spsChar.height; + metadata.tracks[reTrack].fpks = spsChar.fps * 1000; + } pack_out.null(); return pack_out; //skip rest of parsing, get next tag. } diff --git a/lib/h264.cpp b/lib/h264.cpp index 6d6efd3b..a256b61e 100644 --- a/lib/h264.cpp +++ b/lib/h264.cpp @@ -81,6 +81,12 @@ namespace h264 { sequenceParameterSet::sequenceParameterSet(const char * _data, unsigned long _dataLen) : data(_data), dataLen(_dataLen) {} + //DTSC Initdata is the payload for an avcc box. init[8+] is data, init[6-7] is a network-encoded length + void sequenceParameterSet::fromDTSCInit(const std::string & dtscInit){ + data = dtscInit.data() + 8; + dataLen = Bit::btohs(dtscInit.data() + 6); + } + SPSMeta sequenceParameterSet::getCharacteristics() const { SPSMeta result; @@ -107,8 +113,10 @@ namespace h264 { } char profileIdc = bs.get(8); + result.profile = profileIdc; //Start skipping unused data - bs.skip(16); + bs.skip(8); + result.level = bs.get(8); bs.getUExpGolomb(); if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 || profileIdc == 44 || profileIdc == 83 || profileIdc == 86 || profileIdc == 118 || profileIdc == 128) { //chroma format idc diff --git a/lib/h264.h b/lib/h264.h index 28cc693b..48fcfcc9 100644 --- a/lib/h264.h +++ b/lib/h264.h @@ -12,6 +12,8 @@ namespace h264 { unsigned int width; unsigned int height; double fps; + uint8_t profile; + uint8_t level; }; ///Class for analyzing generic nal units @@ -50,7 +52,8 @@ namespace h264 { class sequenceParameterSet { public: - sequenceParameterSet(const char * _data, unsigned long _dataLen); + sequenceParameterSet(const char * _data = NULL, unsigned long _dataLen = 0); + void fromDTSCInit(const std::string & dtscInit); SPSMeta getCharacteristics() const; private: const char * data; diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 33b9cb53..d7efe6a6 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -181,13 +181,11 @@ void HTTP::Parser::StartResponse(HTTP::Parser & request, Socket::Connection & co StartResponse("200", "OK", request, conn, bufferAllChunks); } -/// After receiving a header with this object, this function call will: -/// - Forward the headers to the 'to' Socket::Connection. +/// After receiving a header with this object, and after a call with SendResponse/SendRequest with this object, this function call will: /// - Retrieve all the body from the 'from' Socket::Connection. /// - Forward those contents as-is to the 'to' Socket::Connection. /// It blocks until completed or either of the connections reaches an error state. void HTTP::Parser::Proxy(Socket::Connection & from, Socket::Connection & to) { - SendResponse(url, method, to); if (getChunks) { unsigned int proxyingChunk = 0; while (to.connected() && from.connected()) { @@ -315,6 +313,20 @@ std::string HTTP::Parser::GetVar(std::string i) { return vars[i]; } +std::string HTTP::Parser::allVars(){ + std::string ret; + if (!vars.size()){return ret;} + for (std::map::iterator it = vars.begin(); it != vars.end(); ++it){ + if (ret.size() > 1){ + ret += "&"; + }else{ + ret += "?"; + } + ret += it->first + "=" + Encodings::URL::encode(it->second); + } + return ret; +} + /// Sets header i to string value v. void HTTP::Parser::SetHeader(std::string i, std::string v) { Trim(i); diff --git a/lib/http_parser.h b/lib/http_parser.h index c728565c..4e304038 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -19,6 +19,7 @@ namespace HTTP { std::string GetHeader(std::string i); std::string GetVar(std::string i); std::string getUrl(); + std::string allVars(); void SetHeader(std::string i, std::string v); void SetHeader(std::string i, long long v); void setCORSHeaders(); @@ -44,11 +45,13 @@ namespace HTTP { unsigned int length; bool headerOnly; ///< If true, do not parse body if the length is a known size. bool bufferChunks; + //this bool was private + bool sendingChunks; + private: bool seenHeaders; bool seenReq; bool getChunks; - bool sendingChunks; unsigned int doingChunk; bool parse(std::string & HTTPbuffer); void parseVars(std::string data); diff --git a/lib/mp4.cpp b/lib/mp4.cpp index 307bac7d..64c4a039 100644 --- a/lib/mp4.cpp +++ b/lib/mp4.cpp @@ -104,6 +104,7 @@ namespace MP4 { } } else if (size == 0) { fseek(newData, 0, SEEK_END); + return true; } DONTEVEN_MSG("skipping size 0x%.8lX", size); if (fseek(newData, pos + size, SEEK_SET) == 0) { @@ -132,6 +133,10 @@ namespace MP4 { return false; } } + if (size == 0){//no else if, because the extended size MAY be 0... + fseek(newData, 0, SEEK_END); + size = ftell(newData) - pos; + } fseek(newData, pos, SEEK_SET); data = (char *)realloc(data, size); data_size = size; @@ -160,6 +165,9 @@ namespace MP4 { return false; } } + if (size == 0){ + size = newData.size(); + } if (newData.size() >= size) { data = (char *)realloc(data, size); data_size = size; @@ -383,9 +391,10 @@ namespace MP4 { default: break; } - std::string retval = std::string(indent, ' ') + "Unimplemented pretty-printing for box " + std::string(data + 4, 4) + "\n"; + std::stringstream retval; + retval << std::string(indent, ' ') << "Unimplemented pretty-printing for box " << std::string(data + 4, 4) << " (" << ntohl(((int*)data)[0]) << ")\n"; /// \todo Implement hexdump for unimplemented boxes? - return retval; + return retval.str(); } /// Sets the 8 bits integer at the given index. @@ -807,4 +816,5 @@ namespace MP4 { } return r.str(); } + } diff --git a/lib/mp4_generic.cpp b/lib/mp4_generic.cpp index d4612b26..0c9c8591 100644 --- a/lib/mp4_generic.cpp +++ b/lib/mp4_generic.cpp @@ -1091,10 +1091,15 @@ namespace MP4 { return; } } + memset(data + payloadOffset + 4, 0, 4); memcpy(data + payloadOffset + 4, newMinorVersion, 4); } std::string FTYP::getMinorVersion() { + static char zero[4] = {0,0,0,0}; + if (memcmp(zero, data+payloadOffset+4, 4) == 0){ + return ""; + } return std::string(data + payloadOffset + 4, 4); } @@ -2335,7 +2340,8 @@ namespace MP4 { setEntryCount(no + 1); } setInt32(newCTTSEntry.sampleCount, 8 + no * 8); - setInt32(newCTTSEntry.sampleOffset, 8 + (no * 8) + 4); + setInt32(*(reinterpret_cast(&newCTTSEntry.sampleOffset)), 8 + (no * 8) + 4); + } CTTSEntry CTTS::getCTTSEntry(uint32_t no) { @@ -2345,7 +2351,8 @@ namespace MP4 { return inval; } retval.sampleCount = getInt32(8 + (no * 8)); - retval.sampleOffset = getInt32(8 + (no * 8) + 4); + uint32_t tmp = getInt32(8 + (no * 8) + 4); + retval.sampleOffset = *(reinterpret_cast(&tmp)); return retval; } diff --git a/lib/mp4_generic.h b/lib/mp4_generic.h index ad66fcf6..1f760f5e 100644 --- a/lib/mp4_generic.h +++ b/lib/mp4_generic.h @@ -486,7 +486,7 @@ namespace MP4 { struct CTTSEntry { uint32_t sampleCount; - uint32_t sampleOffset; + int32_t sampleOffset; }; class CTTS: public fullBox { @@ -714,3 +714,4 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; } + diff --git a/lib/mp4_ms.cpp b/lib/mp4_ms.cpp index 47424428..fa8e61b2 100644 --- a/lib/mp4_ms.cpp +++ b/lib/mp4_ms.cpp @@ -127,6 +127,9 @@ namespace MP4 { if (UUID == "d4807ef2-ca39-4695-8e54-26cb9e46a79f") { return ((UUID_TrackFragmentReference *)this)->toPrettyString(indent); } + if (UUID == "6d1d9b05-42d5-44e6-80e2-141daff757b2") { + return ((UUID_TFXD *)this)->toPrettyString(indent); + } std::stringstream r; r << std::string(indent, ' ') << "[uuid] Extension box (" << boxedSize() << ")" << std::endl; r << std::string(indent + 1, ' ') << "UUID: " << UUID << std::endl; @@ -205,4 +208,68 @@ namespace MP4 { } return r.str(); } + + UUID_TFXD::UUID_TFXD() { + setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2"); + setVersion(0); + setFlags(0); + } + + void UUID_TFXD::setVersion(uint32_t newVersion) { + setInt8(newVersion, 16); + } + + uint32_t UUID_TFXD::getVersion() { + return getInt8(16); + } + + void UUID_TFXD::setFlags(uint32_t newFlags) { + setInt24(newFlags, 17); + } + + uint32_t UUID_TFXD::getFlags() { + return getInt24(17); + } + + void UUID_TFXD::setTime(uint64_t newTime) { + if (getVersion() == 0) { + setInt32(newTime, 20); + } else { + setInt64(newTime, 20); + } + } + + uint64_t UUID_TFXD::getTime() { + if (getVersion() == 0) { + return getInt32(20); + } else { + return getInt64(20); + } + } + + void UUID_TFXD::setDuration(uint64_t newDuration) { + if (getVersion() == 0) { + setInt32(newDuration, 24); + } else { + setInt64(newDuration, 28); + } + } + + uint64_t UUID_TFXD::getDuration() { + if (getVersion() == 0) { + return getInt32(24); + } else { + return getInt64(28); + } + } + + std::string UUID_TFXD::toPrettyString(uint32_t indent) { + std::stringstream r; + setUUID((std::string)"6d1d9b05-42d5-44e6-80e2-141daff757b2"); + r << std::string(indent, ' ') << "[6d1d9b05-42d5-44e6-80e2-141daff757b2] TFXD Box (" << boxedSize() << ")" << std::endl; + r << std::string(indent + 1, ' ') << "Version: " << getVersion() << std::endl; + r << std::string(indent + 1, ' ') << "Time = " << getTime() << std::endl; + r << std::string(indent + 1, ' ') << "Duration = " << getDuration() << std::endl; + return r.str(); + } } diff --git a/lib/mp4_ms.h b/lib/mp4_ms.h index 882f01dc..e00b1340 100644 --- a/lib/mp4_ms.h +++ b/lib/mp4_ms.h @@ -37,4 +37,17 @@ namespace MP4 { std::string toPrettyString(uint32_t indent = 0); }; + class UUID_TFXD: public UUID { + public: + UUID_TFXD(); + void setVersion(uint32_t newVersion); + uint32_t getVersion(); + void setFlags(uint32_t newFlags); + uint32_t getFlags(); + void setTime(uint64_t newTime); + uint64_t getTime(); + void setDuration(uint64_t newDuration); + uint64_t getDuration(); + std::string toPrettyString(uint32_t indent = 0); + }; } diff --git a/lib/procs.cpp b/lib/procs.cpp index 9d51b3d4..ba984b10 100644 --- a/lib/procs.cpp +++ b/lib/procs.cpp @@ -22,13 +22,31 @@ #include "timing.h" std::set Util::Procs::plist; +std::set Util::Procs::socketList; bool Util::Procs::handler_set = false; +bool Util::Procs::thread_handler = false; +tthread::mutex Util::Procs::plistMutex; +tthread::thread * Util::Procs::reaper_thread = 0; - -static bool childRunning(pid_t p) { - pid_t ret = waitpid(p, 0, WNOHANG); +/// Local-only function. Attempts to reap child and returns current running status. +bool Util::Procs::childRunning(pid_t p) { + int status; + pid_t ret = waitpid(p, &status, WNOHANG); if (ret == p) { + tthread::lock_guard guard(plistMutex); + int exitcode = -1; + if (WIFEXITED(status)) { + exitcode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitcode = -WTERMSIG(status); + } + if (plist.count(ret)) { + HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode); + plist.erase(ret); + } else { + HIGH_MSG("Child process %d exited with code %d", ret, exitcode); + } return false; } if (ret < 0 && errno == EINTR) { @@ -48,7 +66,17 @@ bool Util::Procs::isRunning(pid_t pid){ /// all remaining children. Waits one more second for cleanup to finish, then exits. void Util::Procs::exit_handler() { int waiting = 0; - std::set listcopy = plist; + std::set listcopy; + { + tthread::lock_guard guard(plistMutex); + listcopy = plist; + thread_handler = false; + } + if (reaper_thread){ + reaper_thread->join(); + delete reaper_thread; + reaper_thread = 0; + } std::set::iterator it; if (listcopy.empty()) { return; @@ -62,7 +90,7 @@ void Util::Procs::exit_handler() { break; } if (!listcopy.empty()) { - Util::sleep(20); + Util::wait(20); ++waiting; } } @@ -71,7 +99,7 @@ void Util::Procs::exit_handler() { return; } - DEBUG_MSG(DLVL_DEVEL, "Sending SIGINT to remaining %d children", (int)listcopy.size()); + WARN_MSG("Sending SIGINT to remaining %d children", (int)listcopy.size()); //send sigint to all remaining if (!listcopy.empty()) { for (it = listcopy.begin(); it != listcopy.end(); it++) { @@ -80,7 +108,7 @@ void Util::Procs::exit_handler() { } } - DEBUG_MSG(DLVL_DEVEL, "Waiting up to 5 seconds for %d children to terminate.", (int)listcopy.size()); + INFO_MSG("Waiting up to 5 seconds for %d children to terminate.", (int)listcopy.size()); waiting = 0; //wait up to 5 seconds for applications to shut down while (!listcopy.empty() && waiting <= 250) { @@ -90,7 +118,7 @@ void Util::Procs::exit_handler() { break; } if (!listcopy.empty()) { - Util::sleep(20); + Util::wait(20); ++waiting; } } @@ -99,7 +127,7 @@ void Util::Procs::exit_handler() { return; } - DEBUG_MSG(DLVL_DEVEL, "Sending SIGKILL to remaining %d children", (int)listcopy.size()); + ERROR_MSG("Sending SIGKILL to remaining %d children", (int)listcopy.size()); //send sigkill to all remaining if (!listcopy.empty()) { for (it = listcopy.begin(); it != listcopy.end(); it++) { @@ -108,7 +136,7 @@ void Util::Procs::exit_handler() { } } - DEBUG_MSG(DLVL_DEVEL, "Waiting up to a second for %d children to terminate.", (int)listcopy.size()); + INFO_MSG("Waiting up to a second for %d children to terminate.", (int)listcopy.size()); waiting = 0; //wait up to 1 second for applications to shut down while (!listcopy.empty() && waiting <= 50) { @@ -118,7 +146,7 @@ void Util::Procs::exit_handler() { break; } if (!listcopy.empty()) { - Util::sleep(20); + Util::wait(20); ++waiting; } } @@ -126,14 +154,17 @@ void Util::Procs::exit_handler() { if (listcopy.empty()) { return; } - DEBUG_MSG(DLVL_DEVEL, "Giving up with %d children left.", (int)listcopy.size()); - + FAIL_MSG("Giving up with %d children left.", (int)listcopy.size()); } /// Sets up exit and childsig handlers. +/// Spawns grim_reaper. exit handler despawns grim_reaper /// Called by every Start* function. void Util::Procs::setHandler() { + tthread::lock_guard guard(plistMutex); if (!handler_set) { + thread_handler = true; + reaper_thread = new tthread::thread(grim_reaper, 0); struct sigaction new_action; new_action.sa_handler = childsig_handler; sigemptyset(&new_action.sa_mask); @@ -144,19 +175,21 @@ void Util::Procs::setHandler() { } } - -/// Used internally to capture child signals and update plist. -void Util::Procs::childsig_handler(int signum) { - if (signum != SIGCHLD) { - return; - } +///Thread that loops until thread_handler is false. +///Reaps available children and then sleeps for a second. +///Not done in signal handler so we can use a mutex to prevent race conditions. +void Util::Procs::grim_reaper(void * n){ + VERYHIGH_MSG("Grim reaper start"); + while (thread_handler){ + { + tthread::lock_guard guard(plistMutex); int status; pid_t ret = -1; while (ret != 0) { ret = waitpid(-1, &status, WNOHANG); if (ret <= 0) { //ignore, would block otherwise if (ret == 0 || errno != EINTR) { - return; + break; } continue; } @@ -166,20 +199,25 @@ void Util::Procs::childsig_handler(int signum) { } else if (WIFSIGNALED(status)) { exitcode = -WTERMSIG(status); } else { // not possible - return; + break; } - + if (plist.count(ret)) { + HIGH_MSG("Process %d fully terminated with code %d", ret, exitcode); plist.erase(ret); -#if DEBUG >= DLVL_HIGH - if (!isActive(pname)) { - DEBUG_MSG(DLVL_HIGH, "Process %d fully terminated", ret); } else { - DEBUG_MSG(DLVL_HIGH, "Child process %d exited", ret); + HIGH_MSG("Child process %d exited with code %d", ret, exitcode); } -#endif - } } + Util::sleep(500); + } + VERYHIGH_MSG("Grim reaper stop"); +} + +/// Ignores everything. Separate thread handles waiting for children. +void Util::Procs::childsig_handler(int signum) { + return; +} /// Runs the given command and returns the stdout output as a string. @@ -187,7 +225,7 @@ std::string Util::Procs::getOutputOf(char * const * argv) { std::string ret; int fin = 0, fout = -1, ferr = 0; pid_t myProc = StartPiped(argv, &fin, &fout, &ferr); - while (isActive(myProc)) { + while (childRunning(myProc)) { Util::sleep(100); } FILE * outFile = fdopen(fout, "r"); @@ -201,6 +239,31 @@ std::string Util::Procs::getOutputOf(char * const * argv) { return ret; } + +///This function prepares a deque for getOutputOf and automatically inserts a NULL at the end of the char* const* +char* const* Util::Procs::dequeToArgv(std::deque & argDeq){ + char** ret = (char**)malloc((argDeq.size()+1)*sizeof(char*)); + for (int i = 0; i & 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 & argDeq, int * fdin, int * fdout, int * fderr) { + pid_t ret; + char* const* argv = dequeToArgv(argDeq);//Note: Do not edit deque before executing command + ret = Util::Procs::StartPiped(argv, fdin, fdout, fderr); + return ret; +} + /// Starts a new process with given fds if the name is not already active. /// \return 0 if process was not started, process PID otherwise. /// \arg argv Command for this process. @@ -258,6 +321,10 @@ pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int } pid = fork(); if (pid == 0) { //child + //Close all sockets in the socketList + for (std::set::iterator it = Util::Procs::socketList.begin(); it != Util::Procs::socketList.end(); ++it){ + close(*it); + } if (!fdin) { dup2(devnull, STDIN_FILENO); } else if (*fdin == -1) { @@ -319,7 +386,10 @@ pid_t Util::Procs::StartPiped(char * const * argv, int * fdin, int * fdout, int } return 0; } else { //parent + { + tthread::lock_guard guard(plistMutex); plist.insert(pid); + } DEBUG_MSG(DLVL_HIGH, "Piped process %s started, PID %d", argv[0], pid); if (devnull != -1) { close(devnull); @@ -354,7 +424,11 @@ void Util::Procs::Murder(pid_t name) { /// (Attempts to) stop all running child processes. void Util::Procs::StopAll() { - std::set listcopy = plist; + std::set listcopy; + { + tthread::lock_guard guard(plistMutex); + listcopy = plist; + } std::set::iterator it; for (it = listcopy.begin(); it != listcopy.end(); it++) { Stop(*it); @@ -363,11 +437,13 @@ void Util::Procs::StopAll() { /// Returns the number of active child processes. int Util::Procs::Count() { + tthread::lock_guard guard(plistMutex); return plist.size(); } /// Returns true if a process with this PID is currently active. bool Util::Procs::isActive(pid_t name) { + tthread::lock_guard guard(plistMutex); return (plist.count(name) == 1) && (kill(name, 0) == 0); } diff --git a/lib/procs.h b/lib/procs.h index 4a06c5f0..b74ed258 100644 --- a/lib/procs.h +++ b/lib/procs.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include "tinythread.h" /// Contains utility code, not directly related to streaming media namespace Util { @@ -13,21 +15,30 @@ namespace Util { /// Deals with spawning, monitoring and stopping child processes class Procs { private: - static std::set plist; ///< Holds active processes + static bool childRunning(pid_t p); + static tthread::mutex plistMutex; + static tthread::thread * reaper_thread; + static std::set plist; ///< Holds active process list. static bool handler_set; ///< If true, the sigchld handler has been setup. + static bool thread_handler;///< True while thread handler should be running. static void childsig_handler(int signum); static void exit_handler(); static void runCmd(std::string & cmd); static void setHandler(); + static char* const* dequeToArgv(std::deque & argDeq); + static void grim_reaper(void * n); public: static std::string getOutputOf(char * const * argv); + static std::string getOutputOf(std::deque & argDeq); static pid_t StartPiped(char * const * argv, int * fdin, int * fdout, int * fderr); + static pid_t StartPiped(std::deque & argDeq, int * fdin, int * fdout, int * fderr); static void Stop(pid_t name); static void Murder(pid_t name); static void StopAll(); static int Count(); static bool isActive(pid_t name); static bool isRunning(pid_t pid); + static std::set socketList; ///< Holds sockets that should be closed before forking }; - } + diff --git a/lib/rtmpchunks.cpp b/lib/rtmpchunks.cpp index 16db90e5..25b2795b 100644 --- a/lib/rtmpchunks.cpp +++ b/lib/rtmpchunks.cpp @@ -7,10 +7,6 @@ #include "timing.h" #include "auth.h" -#ifndef FILLER_DATA -#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio." -#endif - std::string RTMPStream::handshake_in; ///< Input for the handshake. std::string RTMPStream::handshake_out; ///< Output for the handshake. @@ -205,7 +201,7 @@ RTMPStream::Chunk::Chunk() { std::string & RTMPStream::SendChunk(unsigned int cs_id, unsigned char msg_type_id, unsigned int msg_stream_id, std::string data) { static RTMPStream::Chunk ch; ch.cs_id = cs_id; - ch.timestamp = Util::getMS(); + ch.timestamp = 0; ch.len = data.size(); ch.real_len = data.size(); ch.len_left = 0; @@ -458,16 +454,16 @@ bool RTMPStream::Chunk::Parse(Socket::Buffer & buffer) { DEBUG_MSG(DLVL_DONTEVEN, "Parsing RTMP chunk result: len_left=%d, real_len=%d", len_left, real_len); - //read extended timestamp, if neccesary - if (ts_header == 0x00ffffff) { + //read extended timestamp, if necessary + if (ts_header == 0x00ffffff && headertype != 0xC0) { if (!buffer.available(i + 4)) { return false; } //can't read timestamp indata = buffer.copy(i + 4); - timestamp += indata[i++ ]; + timestamp = indata[i++ ]; timestamp += indata[i++ ] * 256; timestamp += indata[i++ ] * 256 * 256; - timestamp = indata[i++ ] * 256 * 256 * 256; + timestamp += indata[i++ ] * 256 * 256 * 256; ts_delta = timestamp; } diff --git a/lib/rtmpchunks.h b/lib/rtmpchunks.h index 88080d74..b5de6d3b 100644 --- a/lib/rtmpchunks.h +++ b/lib/rtmpchunks.h @@ -10,6 +10,10 @@ #include #include "socket.h" +#ifndef FILLER_DATA +#define FILLER_DATA "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent commodo vulputate urna eu commodo. Cras tempor velit nec nulla placerat volutpat. Proin eleifend blandit quam sit amet suscipit. Pellentesque vitae tristique lorem. Maecenas facilisis consequat neque, vitae iaculis eros vulputate ut. Suspendisse ut arcu non eros vestibulum pulvinar id sed erat. Nam dictum tellus vel tellus rhoncus ut mollis tellus fermentum. Fusce volutpat consectetur ante, in mollis nisi euismod vulputate. Curabitur vitae facilisis ligula. Sed sed gravida dolor. Integer eu eros a dolor lobortis ullamcorper. Mauris interdum elit non neque interdum dictum. Suspendisse imperdiet eros sed sapien cursus pulvinar. Vestibulum ut dolor lectus, id commodo elit. Cras convallis varius leo eu porta. Duis luctus sapien nec dui adipiscing quis interdum nunc congue. Morbi pharetra aliquet mauris vitae tristique. Etiam feugiat sapien quis augue elementum id ultricies magna vulputate. Phasellus luctus, leo id egestas consequat, eros tortor commodo neque, vitae hendrerit nunc sem ut odio." +#endif + //forward declaration of FLV::Tag to avoid circular dependencies. namespace FLV { class Tag; diff --git a/lib/shared_memory.cpp b/lib/shared_memory.cpp index d23fd443..78ad91b0 100644 --- a/lib/shared_memory.cpp +++ b/lib/shared_memory.cpp @@ -95,13 +95,13 @@ namespace IPC { ///\param oflag The flags with which to open the semaphore ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise ///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise - semaphore::semaphore(const char * name, int oflag, mode_t mode, unsigned int value) { + semaphore::semaphore(const char * name, int oflag, mode_t mode, unsigned int value, bool noWait) { #if defined(__CYGWIN__) || defined(_WIN32) mySem = 0; #else mySem = SEM_FAILED; #endif - open(name, oflag, mode, value); + open(name, oflag, mode, value, noWait); } ///\brief The deconstructor @@ -126,13 +126,13 @@ namespace IPC { ///\param oflag The flags with which to open the semaphore ///\param mode The mode in which to create the semaphore, if O_CREAT is given in oflag, ignored otherwise ///\param value The initial value of the semaphore if O_CREAT is given in oflag, ignored otherwise - void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value) { + void semaphore::open(const char * name, int oflag, mode_t mode, unsigned int value, bool noWait) { close(); int timer = 0; while (!(*this) && timer++ < 10) { #if defined(__CYGWIN__) || defined(_WIN32) std::string semaName = "Global\\"; - semaName += name; + semaName += (name+1); if (oflag & O_CREAT) { if (oflag & O_EXCL) { //attempt opening, if succes, close handle and return false; @@ -165,7 +165,7 @@ namespace IPC { mySem = sem_open(name, oflag); } if (!(*this)) { - if (errno == ENOENT) { + if (errno == ENOENT && !noWait) { Util::wait(500); } else { break; @@ -405,7 +405,7 @@ namespace IPC { int i = 0; do { if (i != 0) { - Util::sleep(1000); + Util::wait(1000); } handle = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str()); i++; @@ -438,14 +438,14 @@ namespace IPC { int i = 0; while (i < 10 && handle == -1 && autoBackoff) { i++; - Util::sleep(1000); + Util::wait(1000); handle = shm_open(name.c_str(), O_RDWR, ACCESSPERMS); } } } if (handle == -1) { if (!master_ && autoBackoff) { - FAIL_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno)); + HIGH_MSG("shm_open for page %s failed: %s", name.c_str(), strerror(errno)); } return; } @@ -558,7 +558,7 @@ namespace IPC { int i = 0; while (i < 10 && handle == -1 && autoBackoff) { i++; - Util::sleep(1000); + Util::wait(1000); handle = open(std::string(Util::getTmpFolder() + name).c_str(), O_RDWR, (mode_t)0600); } } @@ -676,7 +676,7 @@ namespace IPC { if (splitChar != std::string::npos) { name[splitChar] = '+'; } - memcpy(data + 48, name.c_str(), std::min((int)name.size(), 100)); + snprintf(data+48, 100, "%s", name.c_str()); } ///\brief Gets the name of the stream this user is viewing @@ -686,7 +686,7 @@ namespace IPC { ///\brief Sets the name of the connector through which this user is viewing void statExchange::connector(std::string name) { - memcpy(data + 148, name.c_str(), std::min((int)name.size(), 20)); + snprintf(data+148, 20, "%s", name.c_str()); } ///\brief Gets the name of the connector through which this user is viewing @@ -706,6 +706,16 @@ namespace IPC { return result; } + ///\brief Sets checksum field + void statExchange::setSync(char s) { + data[172] = s; + } + + ///\brief Gets checksum field + char statExchange::getSync() { + return data[172]; + } + ///\brief Creates a semaphore guard, locks the semaphore on call semGuard::semGuard(semaphore * thisSemaphore) : mySemaphore(thisSemaphore) { mySemaphore->wait(); @@ -762,6 +772,7 @@ namespace IPC { ///\brief The deconstructor sharedServer::~sharedServer() { + finishEach(); mySemaphore.close(); mySemaphore.unlink(); } @@ -818,6 +829,62 @@ namespace IPC { return false; } + ///Disconnect all connected users + void sharedServer::finishEach(){ + if (!hasCounter){ + return; + } + for (std::set::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::iterator it = myPages.begin(); it != myPages.end(); it++) { + if (!it->mapped || !it->len) { + DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?"); + return 0; + } + unsigned int offset = 0; + while (offset + payLen + (hasCounter ? 1 : 0) <= it->len) { + if (id == requestId){ + if (hasCounter) { + if (it->mapped[offset] != 0) { + return it->mapped + offset + 1; + }else{ + return 0; + } + } else { + if (memcmp(empty, it->mapped + offset, payLen)) { + return it->mapped + offset; + }else{ + return 0; + } + } + } + offset += payLen + (hasCounter ? 1 : 0); + id ++; + } + } + return 0; + } + ///\brief Parse each of the possible payload pieces, and runs a callback on it if in use. void sharedServer::parseEach(void (*callback)(char * data, size_t len, unsigned int id)) { char * empty = 0; @@ -829,6 +896,7 @@ namespace IPC { unsigned int id = 0; unsigned int userCount = 0; unsigned int emptyCount = 0; + connectedUsers = 0; for (std::set::iterator it = myPages.begin(); it != myPages.end(); it++) { if (!it->mapped || !it->len) { DEBUG_MSG(DLVL_FAIL, "Something went terribly wrong?"); @@ -842,28 +910,25 @@ namespace IPC { char * counter = it->mapped + offset; //increase the count if needed ++userCount; + if (*counter & 0x80){ + connectedUsers++; + } if (id >= amount) { amount = id + 1; - DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount); } - unsigned short tmpPID = *((unsigned short *)(it->mapped + 1 + offset + payLen - 2)); - if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127 || *counter == 254 || *counter == 255)) { - WARN_MSG("process disappeared, timing out. (pid %d)", tmpPID); + uint32_t tmpPID = *((uint32_t *)(it->mapped + 1 + offset + payLen - 4)); + if (!Util::Procs::isRunning(tmpPID) && !(*counter == 126 || *counter == 127)){ + WARN_MSG("process disappeared, timing out. (pid %lu)", tmpPID); *counter = 126; //if process is already dead, instant timeout. } callback(it->mapped + offset + 1, payLen, id); switch (*counter) { case 127: - DEBUG_MSG(DLVL_HIGH, "Client %u requested disconnect", id); + HIGH_MSG("Client %u requested disconnect", id); break; case 126: - DEBUG_MSG(DLVL_WARN, "Client %u timed out", id); - break; - case 255: - DEBUG_MSG(DLVL_HIGH, "Client %u disconnected on request", id); - break; - case 254: - DEBUG_MSG(DLVL_WARN, "Client %u disconnect timed out", id); + HIGH_MSG("Client %u timed out", id); break; default: #ifndef NOCRASHCHECK @@ -883,7 +948,7 @@ namespace IPC { #endif break; } - if (*counter == 127 || *counter == 126 || *counter == 255 || *counter == 254) { + if (*counter == 127 || *counter == 126){ memset(it->mapped + offset + 1, 0, payLen); it->mapped[offset] = 0; } else { @@ -895,7 +960,7 @@ namespace IPC { //bring the counter down if this was the last element if (id == amount - 1) { amount = id; - DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount); } //stop, we're guaranteed no more pages are full at this point break; @@ -907,7 +972,7 @@ namespace IPC { //increase the count if needed if (id >= amount) { amount = id + 1; - DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount); } callback(it->mapped + offset, payLen, id); } else { @@ -916,7 +981,7 @@ namespace IPC { //bring the counter down if this was the last element if (id == amount - 1) { amount = id; - DEBUG_MSG(DLVL_VERYHIGH, "Shared memory %s is now at count %u", baseName.c_str(), amount); + VERYHIGH_MSG("Shared memory %s is now at count %u", baseName.c_str(), amount); } //stop, we're guaranteed no more pages are full at this point if (empty) { @@ -952,12 +1017,14 @@ namespace IPC { hasCounter = 0; payLen = 0; offsetOnPage = 0; + countAsViewer= true; } ///\brief Copy constructor for sharedClients ///\param rhs The client ro copy sharedClient::sharedClient(const sharedClient & rhs) { + countAsViewer = rhs.countAsViewer; baseName = rhs.baseName; payLen = rhs.payLen; hasCounter = rhs.hasCounter; @@ -978,6 +1045,7 @@ namespace IPC { ///\brief Assignment operator void sharedClient::operator =(const sharedClient & rhs) { + countAsViewer = rhs.countAsViewer; baseName = rhs.baseName; payLen = rhs.payLen; hasCounter = rhs.hasCounter; @@ -1001,6 +1069,7 @@ namespace IPC { ///\param len The size of the payload to allocate ///\param withCounter Whether or not this payload has a counter sharedClient::sharedClient(std::string name, int len, bool withCounter) : baseName("/" + name), payLen(len), offsetOnPage(-1), hasCounter(withCounter) { + countAsViewer = true; #ifdef __APPLE__ //note: O_CREAT is only needed for mac, probably mySemaphore.open(baseName.c_str(), O_RDWR | O_CREAT, 0); @@ -1035,7 +1104,7 @@ namespace IPC { offsetOnPage = offset; if (hasCounter) { myPage.mapped[offset] = 1; - *((unsigned short *)(myPage.mapped + 1 + offset + len - 2)) = getpid(); + *((uint32_t *)(myPage.mapped + 1 + offset + len - 4)) = getpid(); } break; } @@ -1058,6 +1127,8 @@ namespace IPC { ///\brief The deconstructor sharedClient::~sharedClient() { mySemaphore.close(); + + } ///\brief Writes data to the shared data @@ -1079,7 +1150,7 @@ namespace IPC { } if (myPage.mapped) { semGuard tmpGuard(&mySemaphore); - myPage.mapped[offsetOnPage] = 127; + myPage.mapped[offsetOnPage] = 126; } } @@ -1089,13 +1160,20 @@ namespace IPC { DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element without counters"); return; } - if (myPage.mapped[offsetOnPage] < 128) { - myPage.mapped[offsetOnPage] = 1; + if ((myPage.mapped[offsetOnPage] & 0x7F) < 126) { + myPage.mapped[offsetOnPage] = (countAsViewer ? 0x81 : 0x01); } else { DEBUG_MSG(DLVL_WARN, "Trying to keep-alive an element that needs to timeout, ignoring"); } } + bool sharedClient::isAlive() { + if (!hasCounter) { + return true; + } + return (myPage.mapped[offsetOnPage] & 0x7F) < 126; + } + ///\brief Get a pointer to the data of this client char * sharedClient::getData() { if (!myPage.mapped) { @@ -1104,8 +1182,21 @@ namespace IPC { return (myPage.mapped + offsetOnPage + (hasCounter ? 1 : 0)); } + int sharedClient::getCounter() { + if (!hasCounter){ + return -1; + } + if (!myPage.mapped) { + return 0; + } + return *(myPage.mapped + offsetOnPage); + } + userConnection::userConnection(char * _data) { data = _data; + if (!data){ + WARN_MSG("userConnection created with null pointer!"); + } } unsigned long userConnection::getTrackId(size_t offset) const { diff --git a/lib/shared_memory.h b/lib/shared_memory.h index dd189670..bc8906f2 100644 --- a/lib/shared_memory.h +++ b/lib/shared_memory.h @@ -11,7 +11,7 @@ #include #endif -#define STAT_EX_SIZE 174 +#define STAT_EX_SIZE 177 #define PLAY_EX_SIZE 2+6*SIMUL_TRACKS namespace IPC { @@ -37,6 +37,8 @@ namespace IPC { void connector(std::string name); std::string connector(); void crc(unsigned int sum); + char getSync(); + void setSync(char s); unsigned int crc(); private: ///\brief The payload for the stat exchange @@ -49,6 +51,8 @@ namespace IPC { /// - 100 byte - streamName (name of the stream peer is viewing) /// - 20 byte - connector (name of the connector the peer is using) /// - 4 byte - CRC32 of user agent (or zero if none) + /// - 1 byte sync (was seen by controller yes/no) + /// - (implicit 4 bytes: PID) char * data; }; @@ -56,10 +60,10 @@ namespace IPC { class semaphore { public: semaphore(); - semaphore(const char * name, int oflag, mode_t mode, unsigned int value); + semaphore(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0, bool noWait = false); ~semaphore(); operator bool() const; - void open(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0); + void open(const char * name, int oflag, mode_t mode = 0, unsigned int value = 0, bool noWait = false); int getVal() const; void post(); void wait(); @@ -177,9 +181,11 @@ namespace IPC { void init(std::string name, int len, bool withCounter = false); ~sharedServer(); void parseEach(void (*callback)(char * data, size_t len, unsigned int id)); + char * getIndex(unsigned int id); operator bool() const; ///\brief The amount of connected clients unsigned int amount; + unsigned int connectedUsers; private: bool isInUse(unsigned int id); void newPage(); @@ -194,6 +200,7 @@ namespace IPC { semaphore mySemaphore; ///\brief Whether the payload has a counter, if so, it is added in front of the payload bool hasCounter; + void finishEach(); }; ///\brief The client part of a server/client model for shared memory. @@ -215,7 +222,10 @@ namespace IPC { void write(char * data, int len); void finish(); void keepAlive(); + bool isAlive(); char * getData(); + int getCounter(); + bool countAsViewer; private: ///\brief The basename of the shared pages. std::string baseName; diff --git a/lib/socket.cpp b/lib/socket.cpp index a8561962..dbad1fa2 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -510,11 +510,8 @@ unsigned int Socket::Connection::iwrite(const void * buffer, int len) { return 0; break; default: - if (errno != EPIPE && errno != ECONNRESET) { Error = true; - remotehost = strerror(errno); - DEBUG_MSG(DLVL_WARN, "Could not iwrite data! Error: %s", remotehost.c_str()); - } + INSANE_MSG("Could not iwrite data! Error: %s", strerror(errno)); close(); return 0; break; @@ -555,11 +552,8 @@ int Socket::Connection::iread(void * buffer, int len, int flags) { return 0; break; default: - if (errno != EPIPE) { Error = true; - remotehost = strerror(errno); - DEBUG_MSG(DLVL_WARN, "Could not iread data! Error: %s", remotehost.c_str()); - } + INSANE_MSG("Could not iread data! Error: %s", strerror(errno)); close(); return 0; break; diff --git a/lib/stream.cpp b/lib/stream.cpp index 27422962..bed76b43 100644 --- a/lib/stream.cpp +++ b/lib/stream.cpp @@ -73,10 +73,37 @@ void Util::sanitizeName(std::string & streamname) { } } +JSON::Value Util::getStreamConfig(std::string streamname){ + JSON::Value result; + if (streamname.size() > 100){ + FAIL_MSG("Stream opening denied: %s is longer than 100 characters (%lu).", streamname.c_str(), streamname.size()); + return result; + } + IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE); + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); + configLock.wait(); + DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len); + + sanitizeName(streamname); + std::string smp = streamname.substr(0, streamname.find_first_of("+ ")); + //check if smp (everything before + or space) exists + DTSC::Scan stream_cfg = config.getMember("streams").getMember(smp); + if (!stream_cfg){ + DEBUG_MSG(DLVL_MEDIUM, "Stream %s not configured", streamname.c_str()); + }else{ + result = stream_cfg.asJSON(); + } + configLock.post();//unlock the config semaphore + return result; +} + /// Checks if the given streamname has an active input serving it. Returns true if this is the case. /// Assumes the streamname has already been through sanitizeName()! bool Util::streamAlive(std::string & streamname){ - IPC::semaphore playerLock(std::string("/lock_" + streamname).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); + char semName[NAME_BUFFER_SIZE]; + snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamname.c_str()); + IPC::semaphore playerLock(semName, O_RDWR, ACCESSPERMS, 1, true); + if (!playerLock){return false;} if (!playerLock.tryWait()) { playerLock.close(); return true; @@ -109,8 +136,8 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir } //Attempt to load up configuration and find this stream - IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE); - IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); + IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE); + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); //Lock the config to prevent race conditions and corruption issues while reading configLock.wait(); DTSC::Scan config = DTSC::Scan(mistConfOut.mapped, mistConfOut.len); @@ -149,7 +176,21 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir input = inputs.getIndice(i); //if match voor current stream && priority is hoger dan wat we al hebben - if (curPrio < input.getMember("priority").asInt()){ + if (input.getMember("source_match") && curPrio < input.getMember("priority").asInt()){ + if (input.getMember("source_match").getSize()){ + for(unsigned int j = 0; j < input.getMember("source_match").getSize(); ++j){ + std::string source = input.getMember("source_match").getIndice(j).asString(); + std::string front = source.substr(0,source.find('*')); + std::string back = source.substr(source.find('*')+1); + MEDIUM_MSG("Checking input %s: %s (%s)", inputs.getIndiceName(i).c_str(), input.getMember("name").asString().c_str(), source.c_str()); + + if (filename.substr(0,front.size()) == front && filename.substr(filename.size()-back.size()) == back){ + player_bin = Util::getMyPath() + "MistIn" + input.getMember("name").asString(); + curPrio = input.getMember("priority").asInt(); + selected = true; + } + } + }else{ std::string source = input.getMember("source_match").asString(); std::string front = source.substr(0,source.find('*')); std::string back = source.substr(source.find('*')+1); @@ -160,6 +201,8 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir curPrio = input.getMember("priority").asInt(); selected = true; } + } + } } @@ -230,5 +273,11 @@ bool Util::startInput(std::string streamname, std::string filename, bool forkFir FAIL_MSG("Starting process %s for stream %s failed: %s", argv[0], streamname.c_str(), strerror(errno)); _exit(42); } - return true; + + unsigned int waiting = 0; + while (!streamAlive(streamname) && ++waiting < 40){ + Util::wait(250); + } + return streamAlive(streamname); } + diff --git a/lib/stream.h b/lib/stream.h index 07c935ea..6b2e7759 100644 --- a/lib/stream.h +++ b/lib/stream.h @@ -4,10 +4,13 @@ #pragma once #include #include "socket.h" +#include "json.h" namespace Util { std::string getTmpFolder(); void sanitizeName(std::string & streamname); bool streamAlive(std::string & streamname); bool startInput(std::string streamname, std::string filename = "", bool forkFirst = true); + JSON::Value getStreamConfig(std::string streamname); } + diff --git a/lib/ts_packet.cpp b/lib/ts_packet.cpp index ac41247c..b127a35f 100644 --- a/lib/ts_packet.cpp +++ b/lib/ts_packet.cpp @@ -25,6 +25,11 @@ namespace TS { clear(); } + Packet::Packet(const Packet & rhs){ + memcpy(strBuf, rhs.strBuf, 188); + pos = 188; + } + /// This function fills a Packet from a file. /// It fills the content with the next 188 bytes int he file. /// \param Data The data to be read into the packet. @@ -34,11 +39,11 @@ namespace TS { if (!fread((void *)strBuf, 188, 1, data)) { return false; } - pos=188; if (strBuf[0] != 0x47){ HIGH_MSG("Failed to read a good packet on pos %lld", bPos); return false; } + pos=188; return true; } @@ -412,7 +417,7 @@ namespace TS { /// \return A character pointer to the internal packet buffer data const char * Packet::checkAndGetBuffer() const{ if (pos != 188) { - DEBUG_MSG(DLVL_ERROR, "Size invalid (%d) - invalid data from this point on", pos); + DEBUG_MSG(DLVL_HIGH, "Size invalid (%d) - invalid data from this point on", pos); } return strBuf; } @@ -564,6 +569,11 @@ namespace TS { } + ProgramAssociationTable & ProgramAssociationTable::operator = (const Packet & rhs){ + memcpy(strBuf, rhs.checkAndGetBuffer(), 188); + pos = 188; + return *this; + } ///Retrieves the current addStuffingoffset value for a PAT char ProgramAssociationTable::getOffset() const{ unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); @@ -680,6 +690,10 @@ namespace TS { return data[0]; } + void ProgramMappingEntry::setStreamType(int newType){ + data[0] = newType; + } + std::string ProgramMappingEntry::getCodec() const{ switch (getStreamType()){ case 0x01: @@ -730,10 +744,25 @@ namespace TS { return ((data[1] << 8) | data[2]) & 0x1FFF; } + void ProgramMappingEntry::setElementaryPid(int newElementaryPid) { + data[1] = newElementaryPid >> 8 & 0x1F; + data[2] = newElementaryPid & 0xFF; + } + int ProgramMappingEntry::getESInfoLength() const{ return ((data[3] << 8) | data[4]) & 0x0FFF; } + const char * ProgramMappingEntry::getESInfo() const{ + return data + 5; + } + + void ProgramMappingEntry::setESInfo(const std::string & newInfo){ + data[3] = (newInfo.size() >> 8) & 0x0F; + data[4] = newInfo.size() & 0xFF; + memcpy(data + 5, newInfo.data(), newInfo.size()); + } + void ProgramMappingEntry::advance(){ if (!(*this)) { return; @@ -749,6 +778,12 @@ namespace TS { pos=4; } + ProgramMappingTable & ProgramMappingTable::operator = (const Packet & rhs) { + memcpy(strBuf, rhs.checkAndGetBuffer(), 188); + pos = 188; + return *this; + } + char ProgramMappingTable::getOffset() const{ unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0); return strBuf[loc]; @@ -868,14 +903,6 @@ namespace TS { strBuf[loc+1] = (char)newVal; } - short ProgramMappingTable::getProgramCount() const{ - return (getSectionLength() - 13) / 5; - } - - void ProgramMappingTable::setProgramCount(short newVal) { - setSectionLength(newVal * 5 + 13); - } - ProgramMappingEntry ProgramMappingTable::getEntry(int index) const{ int dataOffset = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset(); ProgramMappingEntry res((char*)(strBuf + dataOffset + 13 + getProgramInfoLength()), (char*)(strBuf + dataOffset + getSectionLength()) ); @@ -885,59 +912,6 @@ namespace TS { return res; } - char ProgramMappingTable::getStreamType(short index) const{ - if (index > getProgramCount()) { - return 0; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); - return strBuf[loc + (index * 5)]; - } - - void ProgramMappingTable::setStreamType(char newVal, short index) { - if (index > getProgramCount()) { - return; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); //TODO - updPos(loc+(index*5)+1); - strBuf[loc + (index * 5)] = newVal; - } - - short ProgramMappingTable::getElementaryPID(short index) const{ - if (index > getProgramCount()) { - return 0; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); - return (((short)strBuf[loc + (index * 5) + 1] & 0x1F) << 8) | strBuf[loc + (index * 5) + 2]; - } - - void ProgramMappingTable::setElementaryPID(short newVal, short index) { - if (index > getProgramCount()) { - return; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); - updPos(loc+(index*5)+3); - strBuf[loc + (index * 5)+1] = ((newVal >> 8) & 0x1F )| 0xE0; - strBuf[loc + (index * 5)+2] = (char)newVal; - } - - short ProgramMappingTable::getESInfoLength(short index) const{ - if (index > getProgramCount()) { - return 0; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); - return (((short)strBuf[loc + (index * 5) + 3] & 0x0F) << 8) | strBuf[loc + (index * 5) + 4]; - } - - void ProgramMappingTable::setESInfoLength(short newVal, short index) { - if (index > getProgramCount()) { - return; - } - unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + 13 + getProgramInfoLength(); - updPos(loc+(index*5)+5); - strBuf[loc + (index * 5)+3] = ((newVal >> 8) & 0x0F) | 0xF0; - strBuf[loc + (index * 5)+4] = (char)newVal; - } - int ProgramMappingTable::getCRC() const{ unsigned int loc = 4 + (getAdaptationField() > 1 ? getAdaptationFieldLen() + 1 : 0) + getOffset() + getSectionLength(); return ((int)(strBuf[loc]) << 24) | ((int)(strBuf[loc + 1]) << 16) | ((int)(strBuf[loc + 2]) << 8) | strBuf[loc + 3]; @@ -995,7 +969,14 @@ namespace TS { PMT.setPID(4096); PMT.setTableId(2); //section length met 2 tracks: 0xB017 - PMT.setSectionLength(0xB00D + (selectedTracks.size() * 5)); + int sectionLen = 0; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + sectionLen += 5; + if (myMeta.tracks[*it].codec == "ID3"){ + sectionLen += myMeta.tracks[*it].init.size(); + } + } + PMT.setSectionLength(0xB00D + sectionLen); PMT.setProgramNumber(1); PMT.setVersionNumber(0); PMT.setCurrentNextIndicator(0); @@ -1012,20 +993,20 @@ namespace TS { if (vidTrack == -1){ vidTrack = *(selectedTracks.begin()); } - PMT.setPCRPID(0x100 + vidTrack - 1); + PMT.setPCRPID(vidTrack); PMT.setProgramInfoLength(0); short id = 0; + ProgramMappingEntry entry = PMT.getEntry(0); for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + entry.setElementaryPid(*it); if (myMeta.tracks[*it].codec == "H264"){ - PMT.setStreamType(0x1B,id); + entry.setStreamType(0x1B); }else if (myMeta.tracks[*it].codec == "AAC"){ - PMT.setStreamType(0x0F,id); + entry.setStreamType(0x0F); }else if (myMeta.tracks[*it].codec == "MP3"){ - PMT.setStreamType(0x03,id); + entry.setStreamType(0x03); } - PMT.setElementaryPID(0x100 + (*it) - 1, id); - PMT.setESInfoLength(0,id); - id++; + entry.advance(); } PMT.calcCRC(); return PMT.checkAndGetBuffer(); diff --git a/lib/ts_packet.h b/lib/ts_packet.h index 7c0d370c..7c6e3b4f 100644 --- a/lib/ts_packet.h +++ b/lib/ts_packet.h @@ -22,6 +22,7 @@ namespace TS { public: //Constructors and fillers Packet(); + Packet(const Packet & rhs); ~Packet(); bool FromPointer(const char * data); bool FromFile(FILE * data); @@ -83,6 +84,7 @@ namespace TS { class ProgramAssociationTable : public Packet { public: + ProgramAssociationTable & operator = (const Packet & rhs); char getOffset() const; char getTableId() const; short getSectionLength() const; @@ -105,11 +107,14 @@ namespace TS { operator bool() const; int getStreamType() const; + void setStreamType(int newType); std::string getCodec() const; std::string getStreamTypeString() const; int getElementaryPid() const; + void setElementaryPid(int newElementaryPid); int getESInfoLength() const; - char * getESInfo() const; + const char * getESInfo() const; + void setESInfo(const std::string & newInfo); void advance(); private: char* data; @@ -119,6 +124,7 @@ namespace TS { class ProgramMappingTable : public Packet { public: ProgramMappingTable(); + ProgramMappingTable & operator = (const Packet & rhs); char getOffset() const; void setOffset(char newVal); char getTableId() const; @@ -139,15 +145,7 @@ namespace TS { void setPCRPID(short newVal); short getProgramInfoLength() const; void setProgramInfoLength(short newVal); - short getProgramCount() const; - void setProgramCount(short newVal); ProgramMappingEntry getEntry(int index) const; - void setStreamType(char newVal, short index); - char getStreamType(short index) const; - void setElementaryPID(short newVal, short index); - short getElementaryPID(short index) const; - void setESInfoLength(short newVal,short index); - short getESInfoLength(short index) const; int getCRC() const; void calcCRC(); std::string toPrettyString(size_t indent) const; diff --git a/lib/util.cpp b/lib/util.cpp new file mode 100644 index 00000000..2c44cf47 --- /dev/null +++ b/lib/util.cpp @@ -0,0 +1,40 @@ +#include "util.h" +#include + +namespace Util { + bool stringScan(const std::string & src, const std::string & pattern, std::deque & result){ + result.clear(); + std::deque 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::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::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(); + } +} + diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 00000000..df0a27b9 --- /dev/null +++ b/lib/util.h @@ -0,0 +1,6 @@ +#include +#include + +namespace Util { + bool stringScan(const std::string & src, const std::string & pattern, std::deque & result); +} diff --git a/lsp/mist.js b/lsp/mist.js index 29233b25..5cbfa602 100644 --- a/lsp/mist.js +++ b/lsp/mist.js @@ -1924,7 +1924,7 @@ var UI = { help: 'You can set the amount of debug information MistServer saves in the log. A full reboot of MistServer is required before some components of MistServer can post debug information.' },{ type: 'checkbox', - label: 'Force JSON file save', + label: 'Force configurations save', pointer: { main: mist.data, index: 'save' @@ -3464,7 +3464,7 @@ var UI = { },{ label: 'Target', type: 'str', - help: 'Where the stream will be pushed to.
Valid formats:
  • '+target_match.join('
  • ')+'
', + help: 'Where the stream will be pushed to.
Valid formats:
  • '+target_match.join('
  • ')+'
Valid text replacements:
  • $stream - inserts the stream name used to push to MistServer
  • $day - inserts the current day number
  • $month - inserts the current month number
  • $year - inserts the current year number
  • $hour - inserts the hour timestamp when stream was received
  • $minute - inserts the minute timestamp the stream was received
  • $seconds - inserts the seconds timestamp when the stream was received
  • $datetime - inserts $year.$month.$day.$hour.$minute.$seconds timestamp when the stream was received
  • ', pointer: { main: saveas, index: 'target' diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 99e57029..00000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/src/analysers/dtsc_analyser.cpp b/src/analysers/dtsc_analyser.cpp index e9462b78..c7fd822a 100644 --- a/src/analysers/dtsc_analyser.cpp +++ b/src/analysers/dtsc_analyser.cpp @@ -23,7 +23,10 @@ namespace Analysers { std::cerr << "Not a valid DTSC file" << std::endl; return 1; } - F.getMeta().toPrettyString(std::cout,0, 0x03); + + if (F.getMeta().vod || F.getMeta().live){ + F.getMeta().toPrettyString(std::cout,0, 0x03); + } int bPos = 0; F.seek_bpos(0); @@ -42,6 +45,10 @@ namespace Analysers { std::cout << "DTSC header: " << F.getPacket().getScan().toPrettyString() << std::endl; break; } + case DTSC::DTCM: { + std::cout << "DTCM command: " << F.getPacket().getScan().toPrettyString() << std::endl; + break; + } default: DEBUG_MSG(DLVL_WARN,"Invalid dtsc packet @ bpos %d", bPos); break; diff --git a/src/analysers/mp4_analyser.cpp b/src/analysers/mp4_analyser.cpp index bc268e47..ca4ad72c 100644 --- a/src/analysers/mp4_analyser.cpp +++ b/src/analysers/mp4_analyser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include ///\brief Holds everything unique to the analysers. namespace Analysers { @@ -25,9 +26,15 @@ namespace Analysers { mp4Buffer.erase(mp4Buffer.size() - 1, 1); MP4::Box mp4Data; + int dataSize = mp4Buffer.size(); + int curPos = 0; while (mp4Data.read(mp4Buffer)){ + DEBUG_MSG(DLVL_DEVEL, "Read a box at position %d", curPos); std::cerr << mp4Data.toPrettyString(0) << std::endl; + curPos += dataSize - mp4Buffer.size(); + dataSize = mp4Buffer.size(); } + DEBUG_MSG(DLVL_DEVEL, "Stopped parsing at position %d", curPos); return 0; } } diff --git a/src/controller/controller.cpp b/src/controller/controller.cpp index 05e1809f..67f3af71 100644 --- a/src/controller/controller.cpp +++ b/src/controller/controller.cpp @@ -1,4 +1,5 @@ /// \page api API calls +/// \brief Listing of all controller API calls. /// The controller listens for commands through a JSON-based API. This page describes the API in full. /// /// A default interface implementing this API as a single HTML page is included in the controller itself. This default interface will be send for invalid API requests, and is thus triggered by default when a browser attempts to access the API port directly. @@ -20,7 +21,9 @@ /// /// You may also include a `"callback"` or `"jsonp"` HTTP variable, to trigger JSONP compatibility mode. JSONP is useful for getting around the cross-domain scripting protection in most modern browsers. Developers creating non-JavaScript applications will most likely not want to use JSONP mode, though nothing is stopping you if you really want to. /// -/// \brief Listing of all controller API calls. + + + /// \file controller.cpp /// Contains all code for the controller executable. @@ -88,6 +91,7 @@ void createAccount (std::string account){ /// Status monitoring thread. /// Will check outputs, inputs and converters every five seconds void statusMonitor(void * np){ + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); while (Controller::conf.is_active){ //this scope prevents the configMutex from being locked constantly { @@ -99,19 +103,20 @@ void statusMonitor(void * np){ changed |= Controller::CheckAllStreams(Controller::Storage["streams"]); //check if the config semaphore is stuck, by trying to lock it for 5 attempts of 1 second... - IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); if (!configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond() && !configLock.tryWaitOneSecond()){ //that failed. We now unlock it, no matter what - and print a warning that it was stuck. WARN_MSG("Configuration semaphore was stuck. Force-unlocking it and re-writing config."); changed = true; } configLock.post(); - if (changed){ + if (changed || Controller::configChanged){ Controller::writeConfig(); + Controller::configChanged = false; } } Util::wait(5000);//wait at least 5 seconds } + configLock.unlink(); } ///\brief The main entry point for the controller. @@ -134,10 +139,9 @@ int main(int argc, char ** argv){ stored_user["default"] = "root"; } Controller::conf = Util::Config(argv[0]); - Controller::conf.addOption("listen_port", stored_port); - Controller::conf.addOption("listen_interface", stored_interface); + Controller::conf.addOption("port", stored_port); + Controller::conf.addOption("interface", stored_interface); Controller::conf.addOption("username", stored_user); - Controller::conf.addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":0, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Turns deamon mode on (-d) or off (-n). -d runs quietly in background, -n (default) enables verbose in foreground.\"}")); Controller::conf.addOption("account", JSON::fromString("{\"long\":\"account\", \"short\":\"a\", \"arg\":\"string\" \"default\":\"\", \"help\":\"A username:password string to create a new account with.\"}")); Controller::conf.addOption("logfile", JSON::fromString("{\"long\":\"logfile\", \"short\":\"L\", \"arg\":\"string\" \"default\":\"\",\"help\":\"Redirect all standard output to a log file, provided with an argument\"}")); Controller::conf.addOption("configFile", JSON::fromString("{\"long\":\"config\", \"short\":\"c\", \"arg\":\"string\" \"default\":\"config.json\", \"help\":\"Specify a config file other than default.\"}")); @@ -183,14 +187,19 @@ int main(int argc, char ** argv){ //check for port, interface and username in arguments //if they are not there, take them from config file, if there if (Controller::Storage["config"]["controller"]["port"]){ - Controller::conf.getOption("listen_port", true)[0u] = Controller::Storage["config"]["controller"]["port"]; + Controller::conf.getOption("port", true)[0u] = Controller::Storage["config"]["controller"]["port"]; } if (Controller::Storage["config"]["controller"]["interface"]){ - Controller::conf.getOption("listen_interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; + Controller::conf.getOption("interface", true)[0u] = Controller::Storage["config"]["controller"]["interface"]; } if (Controller::Storage["config"]["controller"]["username"]){ Controller::conf.getOption("username", true)[0u] = Controller::Storage["config"]["controller"]["username"]; } + { + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); + configLock.unlink(); + } + Controller::writeConfig(); Controller::checkAvailProtocols(); createAccount(Controller::conf.getString("account")); @@ -244,16 +253,16 @@ int main(int argc, char ** argv){ }else{//logfile is enabled //check for username if ( !Controller::Storage.isMember("account") || Controller::Storage["account"].size() < 1){ - std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("listen_port") << " and follow the instructions." << std::endl; + std::cout << "No login configured. To create one, attempt to login through the web interface on port " << Controller::conf.getInteger("port") << " and follow the instructions." << std::endl; } //check for protocols if ( !Controller::Storage.isMember("config") || !Controller::Storage["config"].isMember("protocols") || Controller::Storage["config"]["protocols"].size() < 1){ - std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl; + std::cout << "No protocols enabled, remember to set them up through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl; } } //check for streams - regardless of logfile setting if ( !Controller::Storage.isMember("streams") || Controller::Storage["streams"].size() < 1){ - std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("listen_port") << " or API." << std::endl; + std::cout << "No streams configured, remember to set up streams through the web interface on port " << Controller::conf.getInteger("port") << " or API." << std::endl; } }//connected to a terminal @@ -274,8 +283,8 @@ int main(int argc, char ** argv){ }else{ shutdown_reason = "socket problem (API port closed)"; } - Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason); Controller::conf.is_active = false; + Controller::Log("CONF", "Controller shutting down because of "+shutdown_reason); //join all joinable threads statsThread.join(); monitorThread.join(); @@ -300,3 +309,4 @@ int main(int argc, char ** argv){ std::cout << "Killed all processes, wrote config to disk. Exiting." << std::endl; return 0; } + diff --git a/src/controller/controller_api.cpp b/src/controller/controller_api.cpp index 96e3fe23..f0558421 100644 --- a/src/controller/controller_api.cpp +++ b/src/controller/controller_api.cpp @@ -58,9 +58,6 @@ /// ~~~~~~~~~~~~~~~ void Controller::checkConfig(JSON::Value & in, JSON::Value & out){ out = in; - if (out["basepath"].asString()[out["basepath"].asString().size() - 1] == '/'){ - out["basepath"] = out["basepath"].asString().substr(0, out["basepath"].asString().size() - 1); - } if (out.isMember("debug")){ if (Util::Config::printDebugLevel != out["debug"].asInt()){ Util::Config::printDebugLevel = out["debug"].asInt(); @@ -169,6 +166,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ H.SetHeader("X-Info", "To force an API response, request the file /api"); H.SetHeader("Server", "MistServer/" PACKAGE_VERSION); H.SetHeader("Content-Length", server_html_len); + H.SetHeader("X-UA-Compatible","IE=edge;chrome=1"); H.SendResponse("200", "OK", conn); conn.SendNow(server_html, server_html_len); H.Clean(); @@ -247,6 +245,7 @@ int Controller::handleAPIConnection(Socket::Connection & conn){ /// ] /// ] /// ~~~~~~~~~~~~~~~ + /// if(Request.isMember("browse")){ if(Request["browse"] == ""){ Request["browse"] = "."; diff --git a/src/controller/controller_capabilities.cpp b/src/controller/controller_capabilities.cpp index bf5f712c..bf3d3caf 100644 --- a/src/controller/controller_capabilities.cpp +++ b/src/controller/controller_capabilities.cpp @@ -278,7 +278,11 @@ namespace Controller { unsigned long long c_user, c_nice, c_syst, c_idle, c_total; if (sscanf(line, "cpu %Lu %Lu %Lu %Lu", &c_user, &c_nice, &c_syst, &c_idle) == 4){ c_total = c_user + c_nice + c_syst + c_idle; + if (c_total - cl_total > 0){ capa["cpu_use"] = (long long int)(1000 - ((c_idle - cl_idle) * 1000) / (c_total - cl_total)); + }else{ + capa["cpu_use"] = 0ll; + } cl_total = c_total; cl_idle = c_idle; break; diff --git a/src/controller/controller_statistics.cpp b/src/controller/controller_statistics.cpp index 6ec23ee3..71a2a1ee 100644 --- a/src/controller/controller_statistics.cpp +++ b/src/controller/controller_statistics.cpp @@ -2,6 +2,7 @@ #include #include #include "controller_statistics.h" +#include "controller_storage.h" // These are used to store "clients" field requests in a bitfield for speedup. #define STAT_CLI_HOST 1 @@ -21,6 +22,8 @@ #define STAT_TOT_BPS_UP 4 #define STAT_TOT_ALL 0xFF +#define COUNTABLE_BYTES 128*1024 + std::map Controller::sessions; ///< list of sessions that have statistics data available std::map Controller::connToSession; ///< Map of socket IDs to session info. @@ -37,6 +40,12 @@ Controller::sessIndex::sessIndex(){ crc = 0; } +std::string Controller::sessIndex::toStr(){ + std::stringstream s; + s << host << " " << crc << " " << streamName << " " << connector; + return s.str(); +} + /// Initializes a sessIndex from a statExchange object, converting binary format IP addresses into strings. /// This extracts the host, stream name, connector and crc field, ignoring everything else. Controller::sessIndex::sessIndex(IPC::statExchange & data){ @@ -80,6 +89,9 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{ return !(*this < b); } +/// \todo Make this prettier. +IPC::sharedServer * statPointer = 0; + /// This function runs as a thread and roughly once per second retrieves /// statistics from all connected clients, as well as wipes @@ -87,9 +99,12 @@ bool Controller::sessIndex::operator>= (const Controller::sessIndex &b) const{ void Controller::SharedMemStats(void * config){ DEBUG_MSG(DLVL_HIGH, "Starting stats thread"); IPC::sharedServer statServer(SHM_STATISTICS, STAT_EX_SIZE, true); + statPointer = &statServer; + std::set inactiveStreams; while(((Util::Config*)config)->is_active){ { - tthread::lock_guard guard(statsMutex); + tthread::lock_guard guard(Controller::configMutex); + tthread::lock_guard guard2(statsMutex); //parse current users statServer.parseEach(parseStatistics); //wipe old statistics @@ -108,8 +123,9 @@ void Controller::SharedMemStats(void * config){ } } } - Util::sleep(1000); + Util::wait(1000); } + statPointer = 0; DEBUG_MSG(DLVL_HIGH, "Stopping stats thread"); } @@ -147,6 +163,18 @@ void Controller::statSession::wipeOld(unsigned long long cutOff){ oldConns.pop_front(); } } + if (curConns.size()){ + for (std::map::iterator it = curConns.begin(); it != curConns.end(); ++it){ + while (it->second.log.size() > 1 && it->second.log.begin()->first < cutOff){ + it->second.log.erase(it->second.log.begin()); + } + if (it->second.log.size()){ + if (firstSec > it->second.log.begin()->first){ + firstSec = it->second.log.begin()->first; + } + } + } + } } /// Archives the given connection. @@ -361,6 +389,11 @@ long long Controller::statSession::getBpsUp(unsigned long long t){ } } +Controller::statStorage::statStorage(){ + removeDown = 0; + removeUp = 0; +} + /// Returns true if there is data available for timestamp t. bool Controller::statStorage::hasDataFor(unsigned long long t) { if (!log.size()){return false;} @@ -390,8 +423,13 @@ void Controller::statStorage::update(IPC::statExchange & data) { statLog tmp; tmp.time = data.time(); tmp.lastSecond = data.lastSecond(); - tmp.down = data.down(); - tmp.up = data.up(); + tmp.down = data.down() - removeDown; + tmp.up = data.up() - removeUp; + if (!log.size() && tmp.down + tmp.up > COUNTABLE_BYTES){ + //substract the start values if they are too high - this is a resumed connection of some sort + removeDown = tmp.down; + removeUp = tmp.up; + } log[data.now()] = tmp; //wipe data older than approx. STAT_CUTOFF seconds /// \todo Remove least interesting data first. @@ -420,7 +458,7 @@ void Controller::parseStatistics(char * data, size_t len, unsigned int id){ sessions[idx].update(id, tmpEx); //check validity of stats data char counter = (*(data - 1)); - if (counter == 126 || counter == 127 || counter == 254 || counter == 255){ + if (counter == 126 || counter == 127){ //the data is no longer valid - connection has gone away, store for later sessions[idx].finish(id); connToSession.erase(id); @@ -432,7 +470,7 @@ bool Controller::hasViewers(std::string streamName){ if (sessions.size()){ long long currTime = Util::epoch(); for (std::map::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; } } diff --git a/src/controller/controller_statistics.h b/src/controller/controller_statistics.h index cebf1163..96121d0e 100644 --- a/src/controller/controller_statistics.h +++ b/src/controller/controller_statistics.h @@ -1,3 +1,4 @@ +#pragma once #include #include #include @@ -36,11 +37,16 @@ namespace Controller { bool operator<= (const sessIndex &o) const; bool operator< (const sessIndex &o) const; bool operator>= (const sessIndex &o) const; + std::string toStr(); }; class statStorage { + private: + long long removeUp; + long long removeDown; public: + statStorage(); void update(IPC::statExchange & data); bool hasDataFor(unsigned long long); statLog & getDataFor(unsigned long long); diff --git a/src/controller/controller_storage.cpp b/src/controller/controller_storage.cpp index fab608cb..89a271eb 100644 --- a/src/controller/controller_storage.cpp +++ b/src/controller/controller_storage.cpp @@ -15,6 +15,8 @@ namespace Controller { JSON::Value Storage; ///< Global storage of data. tthread::mutex configMutex; tthread::mutex logMutex; + bool configChanged = false; + ///\brief Store and print a log message. ///\param kind The type of message. ///\param message The message to be logged. @@ -70,6 +72,7 @@ namespace Controller { printf("%s", buf); } } + Log("LOG", "Logger exiting"); fclose(output); close((long long int)err); } @@ -95,8 +98,8 @@ namespace Controller { } if (!changed){return;}//cancel further processing if no changes - static IPC::sharedPage mistConfOut("!mistConfig", DEFAULT_CONF_PAGE_SIZE, true); - IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); + static IPC::sharedPage mistConfOut(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, true); + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); //lock semaphore configLock.wait(); //write config diff --git a/src/controller/controller_storage.h b/src/controller/controller_storage.h index 98820af0..bd65fff6 100644 --- a/src/controller/controller_storage.h +++ b/src/controller/controller_storage.h @@ -8,6 +8,7 @@ namespace Controller { extern JSON::Value Storage; ///< Global storage of data. extern tthread::mutex logMutex;///< Mutex for log thread. extern tthread::mutex configMutex;///< Mutex for server config access. + extern bool configChanged; ///< Bool that indicates config must be written to SHM. /// Store and print a log message. void Log(std::string kind, std::string message); diff --git a/src/controller/controller_streams.cpp b/src/controller/controller_streams.cpp index cc54bb89..768242eb 100644 --- a/src/controller/controller_streams.cpp +++ b/src/controller/controller_streams.cpp @@ -207,7 +207,7 @@ namespace Controller { //actually delete the streams while (toDelete.size() > 0){ std::string deleting = *(toDelete.begin()); - out.removeMember(deleting); + deleteStream(deleting, out); toDelete.erase(deleting); } @@ -226,4 +226,13 @@ namespace Controller { } + void deleteStream(const std::string & name, JSON::Value & out) { + if (!out.isMember(name)){ + return; + } + Log("STRM", std::string("Deleted stream ") + name); + out.removeMember(name); + } + } //Controller namespace + diff --git a/src/controller/controller_streams.h b/src/controller/controller_streams.h index 3a1c9abc..7ef29c92 100644 --- a/src/controller/controller_streams.h +++ b/src/controller/controller_streams.h @@ -6,9 +6,11 @@ namespace Controller { bool CheckAllStreams(JSON::Value & data); void CheckStreams(JSON::Value & in, JSON::Value & out); void AddStreams(JSON::Value & in, JSON::Value & out); + void deleteStream(const std::string & name, JSON::Value & out); struct liveCheck { long long int lastms; long long int last_active; }; + } //Controller namespace diff --git a/src/input/input.cpp b/src/input/input.cpp index 9ba9d7ad..2203394d 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "input.h" #include @@ -69,7 +70,7 @@ namespace Mist { } void Input::checkHeaderTimes(std::string streamFile){ - if ( streamFile == "-" ){ + if (streamFile == "-" || streamFile == "push://") { return; } std::string headerFile = streamFile + ".dtsh"; @@ -99,15 +100,22 @@ namespace Mist { } int Input::run() { - streamName = config->getString("streamname"); if (config->getBool("json")) { std::cout << capa.toString() << std::endl; return 0; } + + if (streamName != "") { + config->getOption("streamname") = streamName; + } + streamName = config->getString("streamname"); + nProxy.streamName = streamName; + if (!setup()){ std::cerr << config->getString("cmd") << " setup failed." << std::endl; return 0; } + checkHeaderTimes(config->getString("input")); if (!readHeader()){ std::cerr << "Reading header for " << config->getString("input") << " failed." << std::endl; @@ -115,7 +123,7 @@ namespace Mist { } parseHeader(); - if (!config->getString("streamname").size()){ + if (!streamName.size()) { convert(); }else{ serve(); @@ -155,28 +163,32 @@ namespace Mist { } void Input::serve(){ - char userPageName[NAME_BUFFER_SIZE]; - snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - userPage.init(userPageName, PLAY_EX_SIZE, true); if (!isBuffer){ for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ bufferFrame(it->first, 1); } } + char userPageName[NAME_BUFFER_SIZE]; + snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); + userPage.init(userPageName, PLAY_EX_SIZE, true); DEBUG_MSG(DLVL_DEVEL,"Input for stream %s started", streamName.c_str()); long long int activityCounter = Util::bootSecs(); - while ((Util::bootSecs() - activityCounter) < 10 && config->is_active){//10 second timeout - Util::wait(1000); - removeUnused(); + while ((Util::bootSecs() - activityCounter) < INPUT_TIMEOUT && config->is_active) { //15 second timeout userPage.parseEach(callbackWrapper); - if (userPage.amount){ + removeUnused(); + if (userPage.connectedUsers) { + if (myMeta.tracks.size()){ activityCounter = Util::bootSecs(); - DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.amount); + } + DEBUG_MSG(DLVL_INSANE, "Connected users: %d", userPage.connectedUsers); }else{ DEBUG_MSG(DLVL_INSANE, "Timer running"); } + if (config->is_active){ + Util::wait(1000); + } } finish(); DEBUG_MSG(DLVL_DEVEL,"Input for stream %s closing clean", streamName.c_str()); @@ -191,7 +203,7 @@ namespace Mist { } removeUnused(); if (standAlone){ - for (std::map::iterator it = metaPages.begin(); it != metaPages.end(); it++){ + for (std::map::iterator it = nProxy.metaPages.begin(); it != nProxy.metaPages.end(); it++) { it->second.master = true; } } @@ -210,9 +222,9 @@ namespace Mist { bufferRemove(it->first, it2->first); pageCounter[it->first].erase(it2->first); for (int i = 0; i < 8192; i += 8){ - unsigned int thisKeyNum = ntohl(((((long long int *)(metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF); + unsigned int thisKeyNum = ntohl(((((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) >> 32) & 0xFFFFFFFF); if (thisKeyNum == it2->first){ - (((long long int *)(metaPages[it->first].mapped + i))[0]) = 0; + (((long long int *)(nProxy.metaPages[it->first].mapped + i))[0]) = 0; } } change = true; @@ -253,13 +265,13 @@ namespace Mist { for (int i = 0; i < it->second.keys.size(); i++){ if (newData){ //i+1 because keys are 1-indexed - pagesByTrack[it->first][i+1].firstTime = it->second.keys[i].getTime(); + nProxy.pagesByTrack[it->first][i + 1].firstTime = it->second.keys[i].getTime(); newData = false; } - pagesByTrack[it->first].rbegin()->second.keyNum++; - pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts(); - pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i]; - if (pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE){ + nProxy.pagesByTrack[it->first].rbegin()->second.keyNum++; + nProxy.pagesByTrack[it->first].rbegin()->second.partNum += it->second.keys[i].getParts(); + nProxy.pagesByTrack[it->first].rbegin()->second.dataSize += it->second.keySizes[i]; + if (nProxy.pagesByTrack[it->first].rbegin()->second.dataSize > FLIP_DATA_PAGE_SIZE) { newData = true; } } @@ -292,7 +304,7 @@ namespace Mist { } if (myMeta.tracks[tid].keys[bookKeeping[tid].curKey].getParts() + 1 == curData[tid].partNum){ if (curData[tid].dataSize > FLIP_DATA_PAGE_SIZE) { - pagesByTrack[tid][bookKeeping[tid].first] = curData[tid]; + nProxy.pagesByTrack[tid][bookKeeping[tid].first] = curData[tid]; bookKeeping[tid].first += curData[tid].keyNum; curData[tid].keyNum = 0; curData[tid].dataSize = 0; @@ -309,17 +321,17 @@ namespace Mist { getNext(false); } for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - if (curData.count(it->first) && !pagesByTrack[it->first].count(bookKeeping[it->first].first)){ - pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first]; + if (curData.count(it->first) && !nProxy.pagesByTrack[it->first].count(bookKeeping[it->first].first)) { + nProxy.pagesByTrack[it->first][bookKeeping[it->first].first] = curData[it->first]; } } } for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (!pagesByTrack.count(it->first)){ + if (!nProxy.pagesByTrack.count(it->first)) { DEBUG_MSG(DLVL_WARN, "No pages for track %d found", it->first); }else{ - DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), pagesByTrack[it->first].size()); - for (std::map::iterator it2 = pagesByTrack[it->first].begin(); it2 != pagesByTrack[it->first].end(); it2++){ + DEBUG_MSG(DLVL_MEDIUM, "Track %d (%s) split into %lu pages", it->first, myMeta.tracks[it->first].codec.c_str(), nProxy.pagesByTrack[it->first].size()); + for (std::map::iterator it2 = nProxy.pagesByTrack[it->first].begin(); it2 != nProxy.pagesByTrack[it->first].end(); it2++) { DEBUG_MSG(DLVL_VERYHIGH, "Page %lu-%lu, (%llu bytes)", it2->first, it2->first + it2->second.keyNum - 1, it2->second.dataSize); } } @@ -328,29 +340,38 @@ namespace Mist { bool Input::bufferFrame(unsigned int track, unsigned int keyNum){ - VERYHIGH_MSG("bufferFrame for stream %s, track %u, key %u", streamName.c_str(), track, keyNum); + VERYHIGH_MSG("Buffering stream %s, track %u, key %u", streamName.c_str(), track, keyNum); if (keyNum > myMeta.tracks[track].keys.size()){ //End of movie here, returning true to avoid various error messages - VERYHIGH_MSG("Key number is higher than total key count. Cancelling bufferFrame"); + WARN_MSG("Key %llu is higher than total (%llu). Cancelling buffering.", keyNum, myMeta.tracks[track].keys.size()); return true; } - if (keyNum < 1){keyNum = 1;} - //abort in case already buffered - int pageNumber = bufferedOnPage(track, keyNum); - if (pageNumber){ + if (keyNum < 1) { + keyNum = 1; + } + if (nProxy.isBuffered(track, keyNum)) { + //get corresponding page number + int pageNumber = 0; + for (std::map::iterator it = nProxy.pagesByTrack[track].begin(); it != nProxy.pagesByTrack[track].end(); it++) { + if (it->first <= keyNum) { + pageNumber = it->first; + } else { + break; + } + } pageCounter[track][pageNumber] = 15; VERYHIGH_MSG("Track %u, key %u is already buffered in page %d. Cancelling bufferFrame", track, keyNum, pageNumber); return true; } - if (!pagesByTrack.count(track)){ + if (!nProxy.pagesByTrack.count(track)) { WARN_MSG("No pages for track %u found! Cancelling bufferFrame", track); return false; } //Update keynum to point to the corresponding page - INFO_MSG("Loading key %u from page %lu", keyNum, (--(pagesByTrack[track].upper_bound(keyNum)))->first); - keyNum = (--(pagesByTrack[track].upper_bound(keyNum)))->first; + INFO_MSG("Loading key %u from page %lu", keyNum, (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first); + keyNum = (--(nProxy.pagesByTrack[track].upper_bound(keyNum)))->first; if (!bufferStart(track, keyNum)){ - WARN_MSG("bufferStart failed! Cancelling bufferFrame", track); + WARN_MSG("bufferStart failed! Cancelling bufferFrame"); return false; } @@ -359,8 +380,8 @@ namespace Mist { trackSelect(trackSpec.str()); seek(myMeta.tracks[track].keys[keyNum - 1].getTime()); long long unsigned int stopTime = myMeta.tracks[track].lastms + 1; - if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + pagesByTrack[track][keyNum].keyNum){ - stopTime = myMeta.tracks[track].keys[keyNum - 1 + pagesByTrack[track][keyNum].keyNum].getTime(); + if ((int)myMeta.tracks[track].keys.size() > keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum) { + stopTime = myMeta.tracks[track].keys[keyNum - 1 + nProxy.pagesByTrack[track][keyNum].keyNum].getTime(); } DEBUG_MSG(DLVL_HIGH, "Playing from %llu to %llu", myMeta.tracks[track].keys[keyNum - 1].getTime(), stopTime); getNext(); diff --git a/src/input/input.h b/src/input/input.h index 725051fe..1c5c2df5 100644 --- a/src/input/input.h +++ b/src/input/input.h @@ -20,7 +20,11 @@ namespace Mist { public: Input(Util::Config * cfg); virtual int run(); + virtual void onCrash(){} + virtual void argumentsParsed(){} virtual ~Input() {}; + + virtual bool needsLock(){return true;} protected: static void callbackWrapper(char * data, size_t len, unsigned int id); virtual bool setup() = 0; @@ -29,6 +33,9 @@ namespace Mist { virtual void getNext(bool smart = true) {}; virtual void seek(int seekTime){}; virtual void finish(); + virtual bool openStreamSource() { return false; }; + virtual void closeStreamSource() {}; + virtual void parseStreamHeader() {}; void play(int until = 0); void playOnce(); void quitPlay(); @@ -36,11 +43,11 @@ namespace Mist { virtual void removeUnused(); virtual void trackSelect(std::string trackSpec){}; virtual void userCallback(char * data, size_t len, unsigned int id); - - void serve(); - void convert(); + virtual void convert(); + virtual void serve(); - void parseHeader(); + + virtual void parseHeader(); bool bufferFrame(unsigned int track, unsigned int keyNum); unsigned int packTime;///Media-timestamp of the last packet. diff --git a/src/input/input_buffer.cpp b/src/input/input_buffer.cpp index 2edfc307..7871a951 100644 --- a/src/input/input_buffer.cpp +++ b/src/input/input_buffer.cpp @@ -13,7 +13,7 @@ #include "input_buffer.h" #ifndef TIMEOUTMULTIPLIER -#define TIMEOUTMULTIPLIER 10 +#define TIMEOUTMULTIPLIER 2 #endif namespace Mist { @@ -41,6 +41,8 @@ namespace Mist { singleton = this; bufferTime = 50000; cutTime = 0; + hasPush = false; + resumeMode = false; } inputBuffer::~inputBuffer() { @@ -48,16 +50,111 @@ namespace Mist { if (myMeta.tracks.size()) { DEBUG_MSG(DLVL_DEVEL, "Cleaning up, removing last keyframes"); for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - while (removeKey(it->first)) {} + std::map & 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::iterator it2 = locations.begin(); it2 != locations.end(); it2++) { + char thisPageName[NAME_BUFFER_SIZE]; + snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), it->first, it2->first); + IPC::sharedPage erasePage(thisPageName, 20971520); + erasePage.master = true; + } + } + } + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); + IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1); + liveMeta.unlink(); + } + + + ///Cleans up any left-over data for the current stream + void inputBuffer::onCrash(){ + WARN_MSG("Buffer crashed. Cleaning."); + streamName = config->getString("streamname"); + char pageName[NAME_BUFFER_SIZE]; + + //Set userpage to all 0xFF's, will disconnect all current clients. + snprintf(pageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); + std::string baseName = pageName; + for (long unsigned i = 0; i < 15; ++i){ + unsigned int size = std::min(((8192 * 2) << i), (32 * 1024 * 1024)); + IPC::sharedPage tmp(std::string(baseName + (char)(i + (int)'A')), size, false, false); + if (tmp.mapped){ + tmp.master = true; + WARN_MSG("Wiping %s", std::string(baseName + (char)(i + (int)'A')).c_str()); + memset(tmp.mapped, 0xFF, size); + } + } + + { + //Delete the live stream semaphore, if any. + snprintf(pageName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); + IPC::semaphore liveMeta(pageName, O_CREAT | O_RDWR, ACCESSPERMS, 1); + liveMeta.unlink(); + }{ + //Delete the stream index metapage. + snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); + IPC::sharedPage erasePage(pageName, DEFAULT_STRM_PAGE_SIZE, false, false); + erasePage.master = true; + } + //Delete most if not all temporary track metadata pages. + for (long unsigned i = 1001; i <= 1024; ++i){ + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), i); + IPC::sharedPage erasePage(pageName, 1024, false, false); + erasePage.master = true; + } + //Delete most if not all track indexes and data pages. + for (long unsigned i = 1; i <= 24; ++i){ + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), i); + IPC::sharedPage indexPage(pageName, SHM_TRACK_INDEX_SIZE, false, false); + indexPage.master = true; + if (indexPage.mapped){ + char * mappedPointer = indexPage.mapped; + for (int j = 0; j < 8192; j += 8) { + int * tmpOffset = (int *)(mappedPointer + j); + if (tmpOffset[0] == 0 && tmpOffset[1] == 0){ + continue; + } + unsigned long keyNum = ntohl(tmpOffset[0]); + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), i, keyNum); + IPC::sharedPage erasePage(pageName, 1024, false, false); + erasePage.master = true; + } + } + } } + /// \triggers + /// The `"STREAM_BUFFER"` trigger is stream-specific, and is ran whenever the buffer changes state between playable (FULL) or not (EMPTY). It cannot be cancelled. It is possible to receive multiple EMPTY calls without FULL calls in between, as EMPTY is always generated when a stream is unloaded from memory, even if this stream never reached playable state in the first place (e.g. a broadcast was cancelled before filling enough buffer to be playable). Its payload is: + /// ~~~~~~~~~~~~~~~ + /// streamname + /// FULL or EMPTY (depending on current state) + /// ~~~~~~~~~~~~~~~ void inputBuffer::updateMeta() { long long unsigned int firstms = 0xFFFFFFFFFFFFFFFFull; long long unsigned int lastms = 0; for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - if (it->second.type == "meta" || !it->second.type.size()){continue;} + if (it->second.type == "meta" || !it->second.type.size()) { + continue; + } if (it->second.init.size()){ if (!initData.count(it->first) || initData[it->first] != it->second.init){ initData[it->first] = it->second.init; @@ -81,14 +178,14 @@ namespace Mist { snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1); liveMeta.wait(); - if (!metaPages.count(0) || !metaPages[0].mapped) { + if (!nProxy.metaPages.count(0) || !nProxy.metaPages[0].mapped) { char pageName[NAME_BUFFER_SIZE]; snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - metaPages[0].init(pageName, DEFAULT_META_PAGE_SIZE, true); - metaPages[0].master = false; + nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true); + nProxy.metaPages[0].master = false; } - myMeta.writeTo(metaPages[0].mapped); - memset(metaPages[0].mapped + myMeta.getSendLen(), 0, (metaPages[0].len > myMeta.getSendLen() ? std::min(metaPages[0].len - myMeta.getSendLen(), 4ll) : 0)); + myMeta.writeTo(nProxy.metaPages[0].mapped); + memset(nProxy.metaPages[0].mapped + myMeta.getSendLen(), 0, (nProxy.metaPages[0].len > myMeta.getSendLen() ? std::min(nProxy.metaPages[0].len - myMeta.getSendLen(), 4ll) : 0)); liveMeta.post(); } @@ -118,30 +215,15 @@ namespace Mist { if (bufferLocations[tid].size() > 1) { //Check if the first key starts on the second page or higher if (myMeta.tracks[tid].keys[0].getNumber() >= (++(bufferLocations[tid].begin()))->first || !config->is_active) { - //Find page in indexpage and null it - for (int i = 0; i < 8192; i += 8) { - unsigned int thisKeyNum = ((((long long int *)(metaPages[tid].mapped + i))[0]) >> 32) & 0xFFFFFFFF; - if (thisKeyNum == htonl(bufferLocations[tid].begin()->first) && ((((long long int *)(metaPages[tid].mapped + i))[0]) != 0)){ - (((long long int *)(metaPages[tid].mapped + i))[0]) = 0; - } - } DEBUG_MSG(DLVL_DEVEL, "Erasing track %d, keys %lu-%lu from buffer", tid, bufferLocations[tid].begin()->first, bufferLocations[tid].begin()->first + bufferLocations[tid].begin()->second.keyNum - 1); bufferRemove(tid, bufferLocations[tid].begin()->first); - for (int i = 0; i < 1024; i++) { - int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8)); - int tmpNum = ntohl(tmpOffset[0]); - if (tmpNum == bufferLocations[tid].begin()->first) { - tmpOffset[0] = 0; - tmpOffset[1] = 0; - } - } - curPageNum.erase(tid); + nProxy.curPageNum.erase(tid); char thisPageName[NAME_BUFFER_SIZE]; snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first); - curPage[tid].init(thisPageName, 20971520); - curPage[tid].master = true; - curPage.erase(tid); + nProxy.curPage[tid].init(thisPageName, 20971520); + nProxy.curPage[tid].master = true; + nProxy.curPage.erase(tid); bufferLocations[tid].erase(bufferLocations[tid].begin()); } else { @@ -158,13 +240,13 @@ namespace Mist { for (std::map::iterator it = bufferLocations[tid].begin(); it != bufferLocations[tid].end(); it++){ char thisPageName[NAME_BUFFER_SIZE]; snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tid, it->first); - curPage[tid].init(thisPageName, 20971520, false, false); - curPage[tid].master = true; - curPage.erase(tid); + nProxy.curPage[tid].init(thisPageName, 20971520, false, false); + nProxy.curPage[tid].master = true; + nProxy.curPage.erase(tid); } bufferLocations.erase(tid); - metaPages[tid].master = true; - metaPages.erase(tid); + nProxy.metaPages[tid].master = true; + nProxy.metaPages.erase(tid); } void inputBuffer::finish() { @@ -189,11 +271,13 @@ namespace Mist { long long unsigned int time = Util::bootSecs(); long long unsigned int compareFirst = 0xFFFFFFFFFFFFFFFFull; long long unsigned int compareLast = 0; + std::set activeTypes; //for tracks that were updated in the last 5 seconds, get the first and last ms edges. for (std::map::iterator it2 = myMeta.tracks.begin(); it2 != myMeta.tracks.end(); it2++) { if ((time - lastUpdated[it2->first]) > 5) { continue; } + activeTypes.insert(it2->second.type); if (it2->second.lastms > compareLast) { compareLast = it2->second.lastms; } @@ -204,7 +288,7 @@ namespace Mist { for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { //if not updated for an entire buffer duration, or last updated track and this track differ by an entire buffer duration, erase the track. if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000) || - (compareLast && (long long int)(time - lastUpdated[it->first]) > 5 && ( + (compareLast && activeTypes.count(it->second.type) && (long long int)(time - lastUpdated[it->first]) > 5 && ( (compareLast < it->second.firstms && (long long int)(it->second.firstms - compareLast) > bufferTime) || (compareFirst > it->second.lastms && (long long int)(compareFirst - it->second.lastms) > bufferTime) @@ -213,26 +297,26 @@ namespace Mist { unsigned int tid = it->first; //erase this track if ((long long int)(time - lastUpdated[it->first]) > (long long int)(bufferTime / 1000)){ - INFO_MSG("Erasing track %d because not updated for %ds (> %ds)", it->first, (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000)); + WARN_MSG("Erasing %s track %d (%s/%s) because not updated for %ds (> %ds)", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), (long long int)(time - lastUpdated[it->first]), (long long int)(bufferTime / 1000)); }else{ - INFO_MSG("Erasing inactive track %u because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", it->first, it->second.firstms / 1000, it->second.lastms / 1000, compareFirst/1000, compareLast/1000, bufferTime / 1000); + WARN_MSG("Erasing %s inactive track %u (%s/%s) because it was inactive for 5+ seconds and contains data (%us - %us), while active tracks are (%us - %us), which is more than %us seconds apart.", streamName.c_str(), it->first, it->second.type.c_str(), it->second.codec.c_str(), it->second.firstms / 1000, it->second.lastms / 1000, compareFirst / 1000, compareLast / 1000, bufferTime / 1000); } lastUpdated.erase(tid); /// \todo Consider replacing with eraseTrackDataPages(it->first)? while (bufferLocations[tid].size()){ char thisPageName[NAME_BUFFER_SIZE]; snprintf(thisPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), (unsigned long)tid, bufferLocations[tid].begin()->first); - curPage[tid].init(thisPageName, 20971520); - curPage[tid].master = true; - curPage.erase(tid); + nProxy.curPage[tid].init(thisPageName, 20971520); + nProxy.curPage[tid].master = true; + nProxy.curPage.erase(tid); bufferLocations[tid].erase(bufferLocations[tid].begin()); } if (pushLocation.count(it->first)){ pushLocation.erase(it->first); } - curPageNum.erase(it->first); - metaPages[it->first].master = true; - metaPages.erase(it->first); + nProxy.curPageNum.erase(it->first); + nProxy.metaPages[it->first].master = true; + nProxy.metaPages.erase(it->first); activeTracks.erase(it->first); myMeta.tracks.erase(it->first); changed = true; @@ -251,8 +335,9 @@ namespace Mist { } for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { //non-video tracks need to have a second keyframe that is <= firstVideo + //firstVideo = 1 happens when there are no tracks, in which case we don't care any more if (it->second.type != "video") { - if (it->second.keys.size() < 2 || it->second.keys[1].getTime() > firstVideo) { + if (it->second.keys.size() < 2 || (it->second.keys[1].getTime() > firstVideo && firstVideo != 1)){ continue; } } @@ -270,6 +355,14 @@ namespace Mist { } } updateMeta(); + static bool everHadPush = false; + if (hasPush) { + hasPush = false; + everHadPush = true; + } else if (everHadPush && !resumeMode && config->is_active) { + INFO_MSG("Shutting down buffer because resume mode is disabled and the source disconnected"); + config->is_active = false; + } } void inputBuffer::userCallback(char * data, size_t len, unsigned int id) { @@ -278,10 +371,10 @@ namespace Mist { //Get the counter of this user char counter = (*(data - 1)); //Each user can have at maximum SIMUL_TRACKS elements in their userpage. + IPC::userConnection userConn(data); for (int index = 0; index < SIMUL_TRACKS; index++) { - char * thisData = data + (index * 6); //Get the track id from the current element - unsigned long value = ((long)(thisData[0]) << 24) | ((long)(thisData[1]) << 16) | ((long)(thisData[2]) << 8) | thisData[3]; + unsigned long value = userConn.getTrackId(index); //Skip value 0xFFFFFFFF as this indicates a previously declined track if (value == 0xFFFFFFFF) { continue; @@ -294,12 +387,10 @@ namespace Mist { //If the current value indicates a valid trackid, and it is pushed from this user if (pushLocation[value] == data) { //Check for timeouts, and erase the track if necessary - if (counter == 126 || counter == 127 || counter == 254 || counter == 255) { + if (counter == 126 || counter == 127){ pushLocation.erase(value); if (negotiatingTracks.count(value)) { negotiatingTracks.erase(value); - metaPages[value].master = true; - metaPages.erase(value); } if (activeTracks.count(value)) { updateMeta(); @@ -307,8 +398,8 @@ namespace Mist { activeTracks.erase(value); bufferLocations.erase(value); } - metaPages[value].master = true; - metaPages.erase(value); + nProxy.metaPages[value].master = true; + nProxy.metaPages.erase(value); continue; } } @@ -320,28 +411,24 @@ namespace Mist { //Add the temporary track id to the list of tracks that are currently being negotiated negotiatingTracks.insert(tempMapping); //Write the temporary id to the userpage element - thisData[0] = (tempMapping >> 24) & 0xFF; - thisData[1] = (tempMapping >> 16) & 0xFF; - thisData[2] = (tempMapping >> 8) & 0xFF; - thisData[3] = (tempMapping) & 0xFF; + userConn.setTrackId(index, tempMapping); //Obtain the original track number for the pushing process - unsigned long originalTrack = ((long)(thisData[4]) << 8) | thisData[5]; + unsigned long originalTrack = userConn.getKeynum(index); //Overwrite it with 0xFFFF - thisData[4] = 0xFF; - thisData[5] = 0xFF; + userConn.setKeynum(index, 0xFFFF); DEBUG_MSG(DLVL_HIGH, "Incoming track %lu from pushing process %d has now been assigned temporary id %llu", originalTrack, id, tempMapping); } //The track id is set to the value of a track that we are currently negotiating about if (negotiatingTracks.count(value)) { //If the metadata page for this track is not yet registered, initialize it - if (!metaPages.count(value) || !metaPages[value].mapped) { + if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped) { char tempMetaName[NAME_BUFFER_SIZE]; snprintf(tempMetaName, NAME_BUFFER_SIZE, SHM_TRACK_META, config->getString("streamname").c_str(), value); - metaPages[value].init(tempMetaName, 8388608, false, false); + nProxy.metaPages[value].init(tempMetaName, 8388608, false, false); } //If this tracks metdata page is not initialize, skip the entire element for now. It will be instantiated later - if (!metaPages[value].mapped) { + if (!nProxy.metaPages[value].mapped) { //remove the negotiation if it has timed out if (++negotiationTimeout[value] >= 1000){ negotiatingTracks.erase(value); @@ -353,13 +440,13 @@ namespace Mist { //The page exist, now we try to read in the metadata of the track //Store the size of the dtsc packet to read. - unsigned int len = ntohl(((int *)metaPages[value].mapped)[1]); + unsigned int len = ntohl(((int *)nProxy.metaPages[value].mapped)[1]); //Temporary variable, won't be used again unsigned int tempForReadingMeta = 0; //Read in the metadata through a temporary JSON object ///\todo Optimize this part. Find a way to not have to store the metadata in JSON first, but read it from the page immediately JSON::Value tempJSONForMeta; - JSON::fromDTMI((const unsigned char *)metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta); + JSON::fromDTMI((const unsigned char *)nProxy.metaPages[value].mapped + 8, len, tempForReadingMeta, tempJSONForMeta); //Construct a metadata object for the current track DTSC::Meta trackMeta(tempJSONForMeta); //If the track metadata does not contain the negotiated track, assume the metadata is currently being written, and skip the element for now. It will be instantiated in the next call. @@ -368,8 +455,8 @@ namespace Mist { if (++negotiationTimeout[value] >= 1000){ negotiatingTracks.erase(value); //Set master to true before erasing the page, because we are responsible for cleaning up unused pages - metaPages[value].master = true; - metaPages.erase(value); + nProxy.metaPages[value].master = true; + nProxy.metaPages.erase(value); negotiationTimeout.erase(value); } continue; @@ -381,8 +468,8 @@ namespace Mist { //Remove the "negotiate" status in either case negotiatingTracks.erase(value); //Set master to true before erasing the page, because we are responsible for cleaning up unused pages - metaPages[value].master = true; - metaPages.erase(value); + nProxy.metaPages[value].master = true; + nProxy.metaPages.erase(value); int finalMap = 3; if (trackMeta.tracks.find(value)->second.type == "video"){finalMap = 1;} @@ -402,8 +489,8 @@ namespace Mist { //Set master to true before erasing the page, because we are responsible for cleaning up unused pages updateMeta(); eraseTrackDataPages(value); - metaPages[finalMap].master = true; - metaPages.erase(finalMap); + nProxy.metaPages[finalMap].master = true; + nProxy.metaPages.erase(finalMap); bufferLocations.erase(finalMap); } @@ -415,43 +502,39 @@ namespace Mist { pushLocation[finalMap] = data; //Initialize the metadata for this track if it was not in place yet. if (!myMeta.tracks.count(finalMap)) { - DEBUG_MSG(DLVL_HIGH, "Inserting metadata for track number %d", finalMap); + DEBUG_MSG(DLVL_MEDIUM, "Inserting metadata for track number %d", finalMap); myMeta.tracks[finalMap] = trackMeta.tracks.begin()->second; myMeta.tracks[finalMap].trackID = finalMap; } - //Write the final mapped track number to the user page element - thisData[0] = (finalMap >> 24) & 0xFF; - thisData[1] = (finalMap >> 16) & 0xFF; - thisData[2] = (finalMap >> 8) & 0xFF; - thisData[3] = (finalMap) & 0xFF; - //Write the key number to start pushing from to to the userpage element. + //Write the final mapped track number and keyframe number to the user page element //This is used to resume pushing as well as pushing new tracks - unsigned long keyNum = myMeta.tracks[finalMap].keys.size(); - thisData[4] = (keyNum >> 8) & 0xFF; - thisData[5] = keyNum & 0xFF; + userConn.setTrackId(index, finalMap); + userConn.setKeynum(index, myMeta.tracks[finalMap].keys.size()); //Update the metadata to reflect all changes updateMeta(); } //If the track is active, and this is the element responsible for pushing it if (activeTracks.count(value) && pushLocation[value] == data) { //Open the track index page if we dont have it open yet - if (!metaPages.count(value) || !metaPages[value].mapped) { + if (!nProxy.metaPages.count(value) || !nProxy.metaPages[value].mapped) { char firstPage[NAME_BUFFER_SIZE]; snprintf(firstPage, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, config->getString("streamname").c_str(), value); - metaPages[value].init(firstPage, 8192, false, false); + nProxy.metaPages[value].init(firstPage, SHM_TRACK_INDEX_SIZE, false, false); } - if (metaPages[value].mapped) { + if (nProxy.metaPages[value].mapped) { //Update the metadata for this track updateTrackMeta(value); + hasPush = true; } } } } void inputBuffer::updateTrackMeta(unsigned long tNum) { + VERYHIGH_MSG("Updating meta for track %d", tNum); //Store a reference for easier access std::map & locations = bufferLocations[tNum]; - char * mappedPointer = metaPages[tNum].mapped; + char * mappedPointer = nProxy.metaPages[tNum].mapped; //First detect all entries on metaPage for (int i = 0; i < 8192; i += 8) { @@ -460,6 +543,7 @@ namespace Mist { continue; } unsigned long keyNum = ntohl(tmpOffset[0]); + INSANE_MSG("Page %d detected, with %d keys", keyNum, ntohl(tmpOffset[1])); //Add an entry into bufferLocations[tNum] for the pages we haven't handled yet. if (!locations.count(keyNum)) { @@ -468,7 +552,6 @@ namespace Mist { locations[keyNum].pageNum = keyNum; locations[keyNum].keyNum = ntohl(tmpOffset[1]); } - //Since the map is ordered by keynumber, this loop updates the metadata for each page from oldest to newest for (std::map::iterator pageIt = locations.begin(); pageIt != locations.end(); pageIt++) { updateMetaFromPage(tNum, pageIt->first); @@ -477,6 +560,7 @@ namespace Mist { } void inputBuffer::updateMetaFromPage(unsigned long tNum, unsigned long pageNum) { + VERYHIGH_MSG("Updating meta for track %d page %d", tNum, pageNum); DTSCPageData & pageData = bufferLocations[tNum][pageNum]; //If the current page is over its 8mb "splitting" boundary @@ -491,27 +575,27 @@ namespace Mist { //Otherwise open and parse the page //Open the page if it is not yet open - if (!curPageNum.count(tNum) || curPageNum[tNum] != pageNum || !curPage[tNum].mapped){ + if (!nProxy.curPageNum.count(tNum) || nProxy.curPageNum[tNum] != pageNum || !nProxy.curPage[tNum].mapped) { //DO NOT ERASE THE PAGE HERE, master is not set to true - curPageNum.erase(tNum); + nProxy.curPageNum.erase(tNum); char nextPageName[NAME_BUFFER_SIZE]; snprintf(nextPageName, NAME_BUFFER_SIZE, SHM_TRACK_DATA, config->getString("streamname").c_str(), tNum, pageNum); - curPage[tNum].init(nextPageName, 20971520); + nProxy.curPage[tNum].init(nextPageName, 20971520); //If the page can not be opened, stop here - if (!curPage[tNum].mapped) { + if (!nProxy.curPage[tNum].mapped) { WARN_MSG("Could not open page: %s", nextPageName); return; } - curPageNum[tNum] = pageNum; + nProxy.curPageNum[tNum] = pageNum; } DTSC::Packet tmpPack; - if (!curPage[tNum].mapped[pageData.curOffset]){ + if (!nProxy.curPage[tNum].mapped[pageData.curOffset]) { VERYHIGH_MSG("No packet on page %lu for track %lu, waiting...", pageNum, tNum); return; } - tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0); + tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0); //No new data has been written on the page since last update if (!tmpPack) { return; @@ -527,7 +611,7 @@ namespace Mist { //Update the offset on the page with the size of the current packet pageData.curOffset += tmpPack.getDataLen(); //Attempt to read in the next packet - tmpPack.reInit(curPage[tNum].mapped + pageData.curOffset, 0); + tmpPack.reInit(nProxy.curPage[tNum].mapped + pageData.curOffset, 0); } } @@ -535,8 +619,8 @@ namespace Mist { std::string strName = config->getString("streamname"); Util::sanitizeName(strName); strName = strName.substr(0, (strName.find_first_of("+ "))); - IPC::sharedPage serverCfg("!mistConfig", DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities - IPC::semaphore configLock("!mistConfLock", O_CREAT | O_RDWR, ACCESSPERMS, 1); + IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); configLock.wait(); DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(strName); long long tmpNum; @@ -553,7 +637,9 @@ namespace Mist { tmpNum = config->getOption("bufferTime").asInt(); } } - if (tmpNum < 1000){tmpNum = 1000;} + if (tmpNum < 1000) { + tmpNum = 1000; + } //if the new value is different, print a message and apply it if (bufferTime != tmpNum) { DEBUG_MSG(DLVL_DEVEL, "Setting bufferTime from %u to new value of %lli", bufferTime, tmpNum); diff --git a/src/input/input_buffer.h b/src/input/input_buffer.h index c7d55616..914cc14c 100644 --- a/src/input/input_buffer.h +++ b/src/input/input_buffer.h @@ -7,9 +7,12 @@ namespace Mist { public: inputBuffer(Util::Config * cfg); ~inputBuffer(); + void onCrash(); private: unsigned int bufferTime; unsigned int cutTime; + bool hasPush; + bool resumeMode; protected: //Private Functions bool setup(); @@ -33,8 +36,7 @@ namespace Mist { std::map > bufferLocations; std::map pushLocation; inputBuffer * singleton; - - //This is used for an ugly fix to prevent metadata from dissapearing in some cases. + //This is used for an ugly fix to prevent metadata from disappearing in some cases. std::map initData; }; } diff --git a/src/input/input_dtsc.cpp b/src/input/input_dtsc.cpp index 165c1b5a..fbdc439b 100644 --- a/src/input/input_dtsc.cpp +++ b/src/input/input_dtsc.cpp @@ -7,6 +7,9 @@ #include #include +#include +#include + #include "input_dtsc.h" namespace Mist { @@ -14,7 +17,8 @@ namespace Mist { capa["name"] = "DTSC"; capa["desc"] = "Enables DTSC Input"; capa["priority"] = 9ll; - capa["source_match"] = "/*.dtsc"; + capa["source_match"].append("/*.dtsc"); + capa["source_match"].append("dtsc://*"); capa["codecs"][0u][0u].append("H264"); capa["codecs"][0u][0u].append("H263"); capa["codecs"][0u][0u].append("VP6"); @@ -24,7 +28,142 @@ namespace Mist { capa["codecs"][0u][1u].append("vorbis"); } + bool inputDTSC::needsLock(){ + return config->getString("input").substr(0, 7) != "dtsc://"; + } + + void parseDTSCURI(const std::string & src, std::string & host, uint16_t & port, std::string & password, std::string & streamName) { + host = ""; + port = 4200; + password = ""; + streamName = ""; + std::deque 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::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + continueNegotiate(it->first, true); + } + break; + } + }else{ + INFO_MSG("Received a wrong type of packet - '%s'", srcConn.Received().copy(4).c_str()); + break; + } + } + } + } + + bool inputDTSC::openStreamSource() { + std::string source = config->getString("input"); + if (source.find("dtsc://") == 0) { + source.erase(0, 7); + } + std::string host; + uint16_t port; + std::string password; + std::string streamName; + parseDTSCURI(source, host, port, password, streamName); + std::string givenStream = config->getString("streamname"); + if (streamName == "") { + streamName = givenStream; + }else{ + if (givenStream.find("+") != std::string::npos){ + streamName += givenStream.substr(givenStream.find("+")); + } + } + srcConn = Socket::Connection(host, port, true); + if (!srcConn.connected()){ + return false; + } + JSON::Value prep; + prep["cmd"] = "play"; + prep["version"] = "MistServer " PACKAGE_VERSION; + prep["stream"] = streamName; + srcConn.SendNow("DTCM"); + char sSize[4] = {0, 0, 0, 0}; + Bit::htobl(sSize, prep.packedSize()); + srcConn.SendNow(sSize, 4); + prep.sendTo(srcConn); + return true; + } + + void inputDTSC::closeStreamSource(){ + srcConn.close(); + } + bool inputDTSC::setup() { + if (!needsLock()) { + return true; + } else { if (config->getString("input") == "-") { std::cerr << "Input from stdin not yet supported" << std::endl; return false; @@ -46,10 +185,14 @@ namespace Mist { if (!inFile) { return false; } + } return true; } bool inputDTSC::readHeader() { + if (!needsLock()) { + return true; + } if (!inFile) { return false; } @@ -69,12 +212,62 @@ namespace Mist { } void inputDTSC::getNext(bool smart) { + if (!needsLock()){ + thisPacket.reInit(srcConn); + if (thisPacket.getVersion() == DTSC::DTCM){ + std::string cmd; + thisPacket.getString("cmd", cmd); + if (cmd == "reset"){ + //Read next packet + thisPacket.reInit(srcConn); + if (thisPacket.getVersion() == DTSC::DTSC_HEAD){ + DTSC::Meta newMeta; + newMeta.reinit(thisPacket); + //Detect new tracks + std::set newTracks; + for (std::map::iterator it = newMeta.tracks.begin(); it != newMeta.tracks.end(); it++){ + if (!myMeta.tracks.count(it->first)){ + newTracks.insert(it->first); + } + } + + for (std::set::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 deletedTracks; + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (!newMeta.tracks.count(it->first)){ + deletedTracks.insert(it->first); + } + } + + for(std::set::iterator it = deletedTracks.begin(); it != deletedTracks.end(); it++){ + INFO_MSG("Deleting track %d from internal metadata", *it); + myMeta.tracks.erase(*it); + } + + //Read next packet before returning + thisPacket.reInit(srcConn); + }else{ + myMeta = DTSC::Meta(); + } + }else{ + //Read next packet before returning + thisPacket.reInit(srcConn); + } + } + }else{ if (smart){ inFile.seekNext(); }else{ inFile.parseNext(); } thisPacket = inFile.getPacket(); + } } void inputDTSC::seek(int seekTime) { diff --git a/src/input/input_dtsc.h b/src/input/input_dtsc.h index 9a9f12db..8d1e5991 100644 --- a/src/input/input_dtsc.h +++ b/src/input/input_dtsc.h @@ -5,8 +5,12 @@ namespace Mist { class inputDTSC : public Input { public: inputDTSC(Util::Config * cfg); + bool needsLock(); protected: //Private Functions + bool openStreamSource(); + void closeStreamSource(); + void parseStreamHeader(); bool setup(); bool readHeader(); void getNext(bool smart = true); @@ -14,6 +18,8 @@ namespace Mist { void trackSelect(std::string trackSpec); DTSC::File inFile; + + Socket::Connection srcConn; }; } diff --git a/src/input/input_mp3.cpp b/src/input/input_mp3.cpp index 3cfa23be..7cf0b21d 100644 --- a/src/input/input_mp3.cpp +++ b/src/input/input_mp3.cpp @@ -122,7 +122,7 @@ namespace Mist { } } if (!offset){ - DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %llu", filePos); + DEBUG_MSG(DLVL_FAIL, "Sync byte not found from offset %lu", filePos); return; } filePos += offset; diff --git a/src/input/mist_in.cpp b/src/input/mist_in.cpp index 162cd11c..1fa81f91 100644 --- a/src/input/mist_in.cpp +++ b/src/input/mist_in.cpp @@ -16,24 +16,34 @@ int main(int argc, char * argv[]) { mistIn conv(&conf); if (conf.parseArgs(argc, argv)) { std::string streamName = conf.getString("streamname"); + conv.argumentsParsed(); + IPC::semaphore playerLock; - if (streamName.size()){ - playerLock.open(std::string("/lock_" + streamName).c_str(), O_CREAT | O_RDWR, ACCESSPERMS, 1); - if (!playerLock.tryWait()){ - DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str()); - return 1; + if (conv.needsLock()){ + if (streamName.size()){ + char semName[NAME_BUFFER_SIZE]; + snprintf(semName, NAME_BUFFER_SIZE, SEM_INPUT, streamName.c_str()); + playerLock.open(semName, O_CREAT | O_RDWR, ACCESSPERMS, 1); + if (!playerLock.tryWait()){ + DEBUG_MSG(DLVL_DEVEL, "A player for stream %s is already running", streamName.c_str()); + return 1; + } } } conf.activate(); while (conf.is_active){ pid_t pid = fork(); if (pid == 0){ - playerLock.close(); + if (conv.needsLock()){ + playerLock.close(); + } return conv.run(); } if (pid == -1){ DEBUG_MSG(DLVL_FAIL, "Unable to spawn player process"); - playerLock.post(); + if (conv.needsLock()){ + playerLock.post(); + } return 2; } //wait for the process to exit @@ -50,6 +60,11 @@ int main(int argc, char * argv[]) { DEBUG_MSG(DLVL_MEDIUM, "Input for stream %s shut down cleanly", streamName.c_str()); break; } +#if DEBUG >= DLVL_DEVEL + WARN_MSG("Aborting autoclean; this is a development build."); +#else + conv.onCrash(); +#endif if (DEBUG >= DLVL_DEVEL){ DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Aborting restart; this is a development build.", streamName.c_str()); break; @@ -57,8 +72,11 @@ int main(int argc, char * argv[]) { DEBUG_MSG(DLVL_DEVEL, "Input for stream %s uncleanly shut down! Restarting...", streamName.c_str()); } } - playerLock.post(); - playerLock.close(); + if (conv.needsLock()){ + playerLock.post(); + playerLock.unlink(); + playerLock.close(); + } } return 0; } diff --git a/src/io.cpp b/src/io.cpp index 81570549..57fe1c71 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -1,4 +1,9 @@ +#include +#include +#include +#include #include +#include #include "io.h" namespace Mist { @@ -11,12 +16,29 @@ namespace Mist { //Open the page for the metadata char pageName[NAME_BUFFER_SIZE]; snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - metaPages[0].init(pageName, myMeta.getSendLen(), true); + nProxy.metaPages[0].init(pageName, DEFAULT_STRM_PAGE_SIZE, true); //Make sure we don't delete it on accident - metaPages[0].master = false; + nProxy.metaPages[0].master = false; //Write the metadata to the page - myMeta.writeTo(metaPages[0].mapped); + myMeta.writeTo(nProxy.metaPages[0].mapped); + + } + + bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) { + VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber); + //Initialize the stream metadata if it does not yet exist + if (!nProxy.metaPages.count(0)) { + initiateMeta(); + } + //If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with. + if (standAlone) { + if (!nProxy.trackMap.count(tid)) { + nProxy.trackMap[tid] = tid; + } + } + //Negotiate the requested track if needed. + return nProxy.bufferStart(tid, pageNumber, myMeta); } ///Starts the buffering of a new page. @@ -26,30 +48,38 @@ namespace Mist { ///Buffering itself is done by bufferNext(). ///\param tid The trackid of the page to start buffering ///\param pageNumber The number of the page to start buffering - bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) { - VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber); - //Initialize the stream metadata if it does not yet exist - if (!metaPages.count(0)) { - initiateMeta(); - } - //If we are a stand-alone player skip track negotiation, as there will be nothing to negotiate with. - if (standAlone) { - if (!trackMap.count(tid)) { - trackMap[tid] = tid; - } - } + bool negotiationProxy::bufferStart(unsigned long tid, unsigned long pageNumber, DTSC::Meta & myMeta) { //Negotiate the requested track if needed. - continueNegotiate(tid); + continueNegotiate(tid, myMeta); //If the negotation state for this track is not 'Accepted', stop buffering this page, maybe try again later. if (trackState[tid] != FILL_ACC) { ///\return false if the track has not been accepted (yet) return false; } - //If the track is accepted, we will have a mapped tid unsigned long mapTid = trackMap[tid]; + //Before we start a new page, make sure we can be heard by the buffer about this. + //Otherwise, it might linger forever as a nasty data leak. + //Nobody likes nasty data leaks. + { + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), mapTid); + IPC::sharedPage checkPage(pageName, SHM_TRACK_INDEX_SIZE, false, false); + if (!checkPage.mapped){ + WARN_MSG("Buffer deleted %s@%lu (%s) index. Re-negotiating...", streamName.c_str(), mapTid, myMeta.tracks[tid].codec.c_str()); + trackState.erase(tid); + trackMap.erase(tid); + trackOffset.erase(tid); + pagesByTrack.erase(tid); + metaPages.erase(tid); + curPageNum.erase(tid); + curPage.erase(tid); + return bufferStart(tid, pageNumber, myMeta); + } + } + //If we are currently buffering a page, abandon it completely and print a message about this //This page will NEVER be deleted, unless we open it again later. if (curPage.count(tid)) { @@ -94,6 +124,9 @@ namespace Mist { //Initialize the bookkeeping entry, and set the current offset to 0, to allow for using it in bufferNext() pagesByTrack[tid][pageNumber].curOffset = 0; + HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid); + + if (myMeta.live){ //Register this page on the meta page //NOTE: It is important that this only happens if the stream is live.... @@ -102,18 +135,17 @@ namespace Mist { int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8)); if ((tmpOffset[0] == 0 && tmpOffset[1] == 0)) { tmpOffset[0] = htonl(curPageNum[tid]); - if (pagesByTrack[tid][pageNumber].dataSize == (25 * 1024 * 1024)){ - tmpOffset[1] = htonl(1000); - } else { - tmpOffset[1] = htonl(pagesByTrack[tid][pageNumber].keyNum); - } + tmpOffset[1] = htonl(1000); inserted = true; break; } } + if (!inserted){ + FAIL_MSG("Could not insert page in track index. Aborting."); + return false; + } } - HIGH_MSG("Start buffering page %lu on track %lu~>%lu successful", pageNumber, tid, mapTid); ///\return true if everything was successful return true; } @@ -128,13 +160,28 @@ namespace Mist { //A different process will handle this for us return; } - unsigned long mapTid = trackMap[tid]; - if (!pagesByTrack.count(tid)){ + unsigned long mapTid = nProxy.trackMap[tid]; + + DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid); + int i = 0; + for (; i < 1024; i++) { + int * tmpOffset = (int *)(nProxy.metaPages[tid].mapped + (i * 8)); + if (ntohl(tmpOffset[0]) == pageNumber) { + tmpOffset[0] = 0; + tmpOffset[1] = 0; + break; + } + } + if (i == 1024){ + FAIL_MSG("Could not erase page %lu for track %lu->%lu stream %s from track index!", pageNumber, tid, mapTid, streamName.c_str()); + } + + if (!nProxy.pagesByTrack.count(tid)){ // If there is no pagesByTrack entry, the pages are managed in local code and not through io.cpp (e.g.: MistInBuffer) return; } //If the given pagenumber is not a valid page on this track, do nothing - if (!pagesByTrack[tid].count(pageNumber)){ + if (!nProxy.pagesByTrack[tid].count(pageNumber)){ INFO_MSG("Can't remove page %lu on track %lu~>%lu as it is not a valid page number.", pageNumber, tid, mapTid); return; } @@ -146,23 +193,14 @@ namespace Mist { #ifdef __CYGWIN__ toErase.init(pageName, 26 * 1024 * 1024, false); #else - toErase.init(pageName, pagesByTrack[tid][pageNumber].dataSize, false); + toErase.init(pageName, nProxy.pagesByTrack[tid][pageNumber].dataSize, false); #endif //Set the master flag so that the page will be destroyed once it leaves scope #if defined(__CYGWIN__) || defined(_WIN32) IPC::releasePage(pageName); #endif toErase.master = true; - //Remove the page from the tracks index page - DEBUG_MSG(DLVL_HIGH, "Removing page %lu on track %lu~>%lu from the corresponding metaPage", pageNumber, tid, mapTid); - for (int i = 0; i < 1024; i++) { - int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8)); - if (ntohl(tmpOffset[0]) == pageNumber) { - tmpOffset[0] = 0; - tmpOffset[1] = 0; - } - } //Leaving scope here, the page will now be destroyed } @@ -170,7 +208,7 @@ namespace Mist { ///Checks whether a key is buffered ///\param tid The trackid on which to locate the key ///\param keyNum The number of the keyframe to find - bool InOutBase::isBuffered(unsigned long tid, unsigned long keyNum) { + bool negotiationProxy::isBuffered(unsigned long tid, unsigned long keyNum) { ///\return The result of bufferedOnPage(tid, keyNum) return bufferedOnPage(tid, keyNum); } @@ -178,24 +216,24 @@ namespace Mist { ///Returns the pagenumber where this key is buffered on ///\param tid The trackid on which to locate the key ///\param keyNum The number of the keyframe to find - unsigned long InOutBase::bufferedOnPage(unsigned long tid, unsigned long keyNum) { + unsigned long negotiationProxy::bufferedOnPage(unsigned long tid, unsigned long keyNum) { //Check whether the track is accepted if (!trackMap.count(tid) || !metaPages.count(tid) || !metaPages[tid].mapped) { ///\return 0 if the page has not been mapped yet return 0; } //Loop over the index page - for (int i = 0; i < 1024; i++) { + int len = metaPages[tid].len / 8; + for (int i = 0; i < len; ++i) { int * tmpOffset = (int *)(metaPages[tid].mapped + (i * 8)); - int pageNum = ntohl(tmpOffset[0]); - int keyAmount = ntohl(tmpOffset[1]); + unsigned int keyAmount = ntohl(tmpOffset[1]); + if (keyAmount == 0){continue;} //Check whether the key is on this page + unsigned int pageNum = ntohl(tmpOffset[0]); if (pageNum <= keyNum && keyNum < pageNum + keyAmount) { - ///\return The pagenumber of the page the key is located on, if the page is registered on the track index page return pageNum; } } - ///\return 0 if the key was not found return 0; } @@ -205,12 +243,16 @@ namespace Mist { std::string packData = pack.toNetPacked(); DTSC::Packet newPack(packData.data(), packData.size()); ///\note Internally calls bufferNext(DTSC::Packet & pack) - bufferNext(newPack); + nProxy.bufferNext(newPack, myMeta); } ///Buffers the next packet on the currently opened page ///\param pack The packet to buffer void InOutBase::bufferNext(DTSC::Packet & pack) { + nProxy.bufferNext(pack, myMeta); + } + + void negotiationProxy::bufferNext(DTSC::Packet & pack, DTSC::Meta & myMeta) { //Save the trackid of the track for easier access unsigned long tid = pack.getTrackId(); unsigned long mapTid = trackMap[tid]; @@ -261,6 +303,10 @@ namespace Mist { ///Registers the data page on the track index page as well ///\param tid The trackid of the page to finalize void InOutBase::bufferFinalize(unsigned long tid) { + nProxy.bufferFinalize(tid, myMeta); + } + + void negotiationProxy::bufferFinalize(unsigned long tid, DTSC::Meta & myMeta){ unsigned long mapTid = trackMap[tid]; //If no page is open, do nothing if (!curPage.count(tid)) { @@ -342,6 +388,10 @@ namespace Mist { ///Initiates/continues negotiation with the buffer as well ///\param packet The packet to buffer void InOutBase::bufferLivePacket(DTSC::Packet & packet){ + nProxy.bufferLivePacket(packet, myMeta); + } + + void negotiationProxy::bufferLivePacket(DTSC::Packet & packet, DTSC::Meta & myMeta){ myMeta.vod = false; myMeta.live = true; //Store the trackid for easier access @@ -353,31 +403,13 @@ namespace Mist { } //If the track is not negotiated yet, start the negotiation if (!trackState.count(tid)) { - continueNegotiate(tid); + continueNegotiate(tid, myMeta); } //If the track is declined, stop here if (trackState[tid] == FILL_DEC) { INFO_MSG("Track %lu Declined", tid); return; } - //Check if a different track is already accepted - bool shouldBlock = true; - if (pagesByTrack.count(tid) && pagesByTrack[tid].size()) { - for (std::map::iterator it = trackState.begin(); it != trackState.end(); it++) { - if (it->second == FILL_ACC) { - //If so, we do not block here - shouldBlock = false; - } - } - } - //Block if no tracks are accepted yet, until we have a definite state - if (shouldBlock) { - while (trackState[tid] != FILL_DEC && trackState[tid] != FILL_ACC) { - INFO_MSG("Blocking on track %lu", tid); - continueNegotiate(tid); - Util::sleep(500); - } - } //This update needs to happen whether the track is accepted or not. ///\todo Figure out how to act with declined track here bool isKeyframe = false; @@ -398,21 +430,21 @@ namespace Mist { } //Determine if we need to open the next page int nextPageNum = -1; - if (isKeyframe || !pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) { + if (isKeyframe && trackState[tid] == FILL_ACC) { //If there is no page, create it if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0) { nextPageNum = 1; - pagesByTrack[tid][1].dataSize = (25 * 1024 * 1024);//Initialize op 25mb + pagesByTrack[tid][1].dataSize = DEFAULT_DATA_PAGE_SIZE;//Initialize op 25mb pagesByTrack[tid][1].pageNum = 1; } //Take the last allocated page std::map::reverse_iterator tmpIt = pagesByTrack[tid].rbegin(); //Compare on 8 mb boundary - if (tmpIt->second.curOffset > (8 * 1024 * 1024)) { + if (tmpIt->second.curOffset > FLIP_DATA_PAGE_SIZE) { //Create the book keeping data for the new page nextPageNum = tmpIt->second.pageNum + tmpIt->second.keyNum; INFO_MSG("We should go to next page now, transition from %lu to %d", tmpIt->second.pageNum, nextPageNum); - pagesByTrack[tid][nextPageNum].dataSize = (25 * 1024 * 1024); + pagesByTrack[tid][nextPageNum].dataSize = DEFAULT_DATA_PAGE_SIZE; pagesByTrack[tid][nextPageNum].pageNum = nextPageNum; } pagesByTrack[tid].rbegin()->second.lastKeyTime = packet.getTime(); @@ -426,6 +458,10 @@ namespace Mist { nextPageNum = 1; } } + //If we have no pages by track, we have not received a starting keyframe yet. Drop this packet. + if (!pagesByTrack.count(tid) || pagesByTrack[tid].size() == 0){ + return; + } //At this point we can stop parsing when the track is not accepted if (trackState[tid] != FILL_ACC) { return; @@ -435,16 +471,20 @@ namespace Mist { if (!curPageNum.count(tid) || nextPageNum != curPageNum[tid]) { if (curPageNum.count(tid)) { //Close the currently opened page when it exists - bufferFinalize(tid); + bufferFinalize(tid, myMeta); } //Open the new page - bufferStart(tid, nextPageNum); + bufferStart(tid, nextPageNum, myMeta); } //Buffer the packet - bufferNext(packet); + bufferNext(packet, myMeta); } - void InOutBase::continueNegotiate(unsigned long tid) { + void InOutBase::continueNegotiate(unsigned long tid, bool quickNegotiate) { + nProxy.continueNegotiate(tid, myMeta, quickNegotiate); + } + + void negotiationProxy::continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate) { if (!tid) { return; } @@ -457,7 +497,7 @@ namespace Mist { trackState[tid] = FILL_ACC; char pageName[NAME_BUFFER_SIZE]; snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), tid); - metaPages[tid].init(pageName, 8 * 1024 * 1024, true); + metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true); metaPages[tid].master = false; return; } @@ -490,7 +530,7 @@ namespace Mist { if (!userClient.getData()){ char userPageName[100]; sprintf(userPageName, SHM_USERS, streamName.c_str()); - userClient = IPC::sharedClient(userPageName, 30, true); + userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); } char * tmp = userClient.getData(); if (!tmp) { @@ -500,12 +540,47 @@ namespace Mist { unsigned long offset = 6 * trackOffset[tid]; //If we have a new track to negotiate if (!trackState.count(tid)) { - INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]); memset(tmp + offset, 0, 4); - tmp[offset] = 0x80; - tmp[offset + 4] = ((tid >> 8) & 0xFF); - tmp[offset + 5] = (tid & 0xFF); - trackState[tid] = FILL_NEW; + if (quickNegotiate){ + unsigned long finalTid = tid; + unsigned short firstPage = 1; + MEDIUM_MSG("Buffer has indicated that incoming track %lu should start writing on track %lu, page %lu", tid, finalTid, firstPage); + trackMap[tid] = finalTid; + if (myMeta.tracks.count(finalTid) && myMeta.tracks[finalTid].lastms){ + myMeta.tracks[finalTid].lastms = 0; + } + trackState[tid] = FILL_ACC; + + + + char pageName[NAME_BUFFER_SIZE]; + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_META, streamName.c_str(), finalTid); + metaPages[tid].init(pageName, 8 * 1024 * 1024, true); + metaPages[tid].master = false; + DTSC::Meta tmpMeta; + tmpMeta.tracks[finalTid] = myMeta.tracks[tid]; + tmpMeta.tracks[finalTid].trackID = finalTid; + JSON::Value tmpVal = tmpMeta.toJSON(); + std::string tmpStr = tmpVal.toNetPacked(); + memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size()); + + + + + + snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid); + metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true); + metaPages[tid].master = false; + Bit::htobl(tmp + offset, finalTid | 0xC0000000); + Bit::htobs(tmp + offset + 4, firstPage); + }else{ + INFO_MSG("Starting negotiation for incoming track %lu, at offset %lu", tid, trackOffset[tid]); + memset(tmp + offset, 0, 4); + tmp[offset] = 0x80; + tmp[offset + 4] = ((tid >> 8) & 0xFF); + tmp[offset + 5] = (tid & 0xFF); + trackState[tid] = FILL_NEW; + } return; } #if defined(__CYGWIN__) || defined(_WIN32) @@ -579,7 +654,7 @@ namespace Mist { trackState[tid] = FILL_ACC; char pageName[NAME_BUFFER_SIZE]; snprintf(pageName, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), finalTid); - metaPages[tid].init(pageName, 8 * 1024 * 1024, true); + metaPages[tid].init(pageName, SHM_TRACK_INDEX_SIZE, true); metaPages[tid].master = false; break; } diff --git a/src/io.h b/src/io.h index b4bdc342..707484fb 100644 --- a/src/io.h +++ b/src/io.h @@ -25,6 +25,36 @@ namespace Mist { unsigned long lastKeyTime;/// > pagesByTrack;/// trackOffset; ///< Offset of data on user page + std::map trackState; ///< State of the negotiation for tracks + std::map trackMap;/// metaPages;///< For each track, holds the page that describes which dataPages are mapped + std::map curPageNum;///< For each track, holds the number page that is currently being written. + std::map curPage;///< For each track, holds the page that is currently being written. + + IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process. + + std::string streamName;///< Name of the stream to connect to + + void continueNegotiate(unsigned long tid, DTSC::Meta & myMeta, bool quickNegotiate = false); + }; + ///\brief Class containing all basic input and output functions. class InOutBase { public: @@ -36,32 +66,23 @@ namespace Mist { void bufferRemove(unsigned long tid, unsigned long pageNumber); void bufferLivePacket(JSON::Value & packet); void bufferLivePacket(DTSC::Packet & packet); - bool isBuffered(unsigned long tid, unsigned long keyNum); - unsigned long bufferedOnPage(unsigned long tid, unsigned long keyNum); protected: + void continueNegotiate(unsigned long tid, bool quickNegotiate = false); + + + bool standAlone; static Util::Config * config; - void continueNegotiate(unsigned long tid); + negotiationProxy nProxy; DTSC::Packet thisPacket;//The current packet that is being parsed - std::string streamName;///< Name of the stream to connect to - IPC::sharedClient userClient;///< Shared memory used for connection to Mixer process. + std::string streamName; - DTSC::Meta myMeta;///< Stores either the input or output metadata std::set selectedTracks;///< Stores the track id's that are either selected for playback or input - std::map > pagesByTrack;/// trackOffset; ///< Offset of data on user page - std::map trackState; ///< State of the negotiation for tracks - std::map trackMap;/// metaPages;///< For each track, holds the page that describes which dataPages are mapped - std::map curPageNum;///< For each track, holds the number page that is currently being written. - std::map curPage;///< For each track, holds the page that is currently being written. - std::map > trackBuffer; ///< Buffer to be used during active track negotiation + DTSC::Meta myMeta;///< Stores either the input or output metadata }; } diff --git a/src/output/mist_out.cpp b/src/output/mist_out.cpp index 6337a3ad..697bbdfd 100644 --- a/src/output/mist_out.cpp +++ b/src/output/mist_out.cpp @@ -1,6 +1,7 @@ #include OUTPUTTYPE #include #include +#include int spawnForked(Socket::Connection & S){ mistOut tmp(S); @@ -15,6 +16,7 @@ int main(int argc, char * argv[]) { std::cout << mistOut::capa.toString() << std::endl; return -1; } + conf.activate(); if (mistOut::listenMode()){ conf.serveForkedSocket(spawnForked); }else{ diff --git a/src/output/output.cpp b/src/output/output.cpp index ed5ba792..d230934f 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -44,7 +44,6 @@ namespace Mist { maxSkipAhead = 7500; minSkipAhead = 5000; realTime = 1000; - completeKeyReadyTimeOut = false; if (myConn){ setBlocking(true); }else{ @@ -57,26 +56,31 @@ namespace Mist { isBlocking = blocking; myConn.setBlocking(isBlocking); } - - Output::~Output(){} void Output::updateMeta(){ //read metadata from page to myMeta variable + if (nProxy.metaPages[0].mapped){ + IPC::semaphore * liveSem = 0; + if (!myMeta.vod){ static char liveSemName[NAME_BUFFER_SIZE]; snprintf(liveSemName, NAME_BUFFER_SIZE, SEM_LIVE, streamName.c_str()); - IPC::semaphore liveMeta(liveSemName, O_CREAT | O_RDWR, ACCESSPERMS, 1); - bool lock = myMeta.live; - if (lock){ - liveMeta.wait(); - } - if (metaPages[0].mapped){ - DTSC::Packet tmpMeta(metaPages[0].mapped, metaPages[0].len, true); + liveSem = new IPC::semaphore(liveSemName, O_RDWR, ACCESSPERMS, 1); + if (*liveSem){ + liveSem->wait(); + }else{ + delete liveSem; + liveSem = 0; + } + } + DTSC::Packet tmpMeta(nProxy.metaPages[0].mapped, nProxy.metaPages[0].len, true); if (tmpMeta.getVersion()){ myMeta.reinit(tmpMeta); } - } - if (lock){ - liveMeta.post(); + if (liveSem){ + liveSem->post(); + delete liveSem; + liveSem = 0; + } } } @@ -95,7 +99,7 @@ namespace Mist { if (isInitialized){ return; } - if (metaPages[0].mapped){ + if (nProxy.metaPages[0].mapped){ return; } if (streamName.size() < 1){ @@ -120,23 +124,24 @@ namespace Mist { return myConn.getBinHost(); } + bool Output::isReadyForPlay() { + if (myMeta.tracks.size()){ + for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ + if (it->second.keys.size() >= 2){ + return true; + } + } + } + return false; + } /// Connects or reconnects to the stream. /// Assumes streamName class member has been set already. /// Will start input if not currently active, calls onFail() if this does not succeed. - /// After assuring stream is online, clears metaPages, then sets metaPages[0], statsPage and userClient to (hopefully) valid handles. - /// Finally, calls updateMeta() and stats() + /// After assuring stream is online, clears nProxy.metaPages, then sets nProxy.metaPages[0], statsPage and nProxy.userClient to (hopefully) valid handles. + /// Finally, calls updateMeta() void Output::reconnect(){ if (!Util::startInput(streamName)){ - DEBUG_MSG(DLVL_FAIL, "Opening stream failed - aborting initalization"); - onFail(); - return; - } - char pageId[NAME_BUFFER_SIZE]; - snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); - metaPages.clear(); - metaPages[0].init(pageId, DEFAULT_META_PAGE_SIZE); - if (!metaPages[0].mapped){ - FAIL_MSG("Could not connect to server for %s", streamName.c_str()); + FAIL_MSG("Opening stream %s failed - aborting initalization", streamName.c_str()); onFail(); return; } @@ -144,14 +149,35 @@ namespace Mist { statsPage.finish(); } statsPage = IPC::sharedClient(SHM_STATISTICS, STAT_EX_SIZE, true); - if (userClient.getData()){ - userClient.finish(); + if (nProxy.userClient.getData()){ + nProxy.userClient.finish(); } char userPageName[NAME_BUFFER_SIZE]; snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - stats(); + nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); + char pageId[NAME_BUFFER_SIZE]; + snprintf(pageId, NAME_BUFFER_SIZE, SHM_STREAM_INDEX, streamName.c_str()); + nProxy.metaPages.clear(); + nProxy.metaPages[0].init(pageId, DEFAULT_STRM_PAGE_SIZE); + if (!nProxy.metaPages[0].mapped){ + FAIL_MSG("Could not connect to server for %s", streamName.c_str()); + onFail(); + return; + } + stats(true); updateMeta(); + if (myMeta.live && !isReadyForPlay()){ + unsigned long long waitUntil = Util::epoch() + 15; + while (!isReadyForPlay()){ + if (Util::epoch() > waitUntil){ + INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), getConnectedHost().c_str()); + break; + } + Util::wait(750); + stats(); + updateMeta(); + } + } } void Output::selectDefaultTracks(){ @@ -192,10 +218,12 @@ namespace Mist { } if (!found){ for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ - if (trit->second.codec == (*itc).asStringRef()){ + if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){ genCounter++; found = true; - break; + if ((*itc).asStringRef() != "*"){ + break; + } } } } @@ -206,16 +234,16 @@ namespace Mist { if (selCounter + genCounter > bestSoFarCount){ bestSoFarCount = selCounter + genCounter; bestSoFar = index; - DEBUG_MSG(DLVL_HIGH, "Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str()); + HIGH_MSG("Match (%u/%u): %s", selCounter, selCounter+genCounter, (*it).toString().c_str()); } }else{ - DEBUG_MSG(DLVL_VERYHIGH, "Not a match for currently selected tracks: %s", (*it).toString().c_str()); + VERYHIGH_MSG("Not a match for currently selected tracks: %s", (*it).toString().c_str()); } } index++; } - DEBUG_MSG(DLVL_MEDIUM, "Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str()); + MEDIUM_MSG("Trying to fill: %s", capa["codecs"][bestSoFar].toString().c_str()); //try to fill as many codecs simultaneously as possible if (capa["codecs"][bestSoFar].size() > 0){ jsonForEach(capa["codecs"][bestSoFar], itb) { @@ -233,10 +261,12 @@ namespace Mist { } if (!found){ for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ - if (trit->second.codec == (*itc).asStringRef()){ + if (trit->second.codec == (*itc).asStringRef() || (*itc).asStringRef() == "*"){ selectedTracks.insert(trit->first); found = true; - break; + if ((*itc).asStringRef() != "*"){ + break; + } } } } @@ -259,6 +289,37 @@ namespace Mist { DEBUG_MSG(DLVL_MEDIUM, "Selected tracks: %s (%lu)", selected.str().c_str(), selectedTracks.size()); } + if (selectedTracks.size() == 0) { + INSANE_MSG("We didn't find any tracks which that we can use. selectedTrack.size() is 0."); + for (std::map::iterator trit = myMeta.tracks.begin(); trit != myMeta.tracks.end(); trit++){ + INSANE_MSG("Found track/codec: %s", trit->second.codec.c_str()); + } + static std::string source; + if (!source.size()){ + IPC::sharedPage serverCfg(SHM_CONF, DEFAULT_CONF_PAGE_SIZE, false, false); ///< Contains server configuration and capabilities + IPC::semaphore configLock(SEM_CONF, O_CREAT | O_RDWR, ACCESSPERMS, 1); + configLock.wait(); + std::string smp = streamName.substr(0, streamName.find_first_of("+ ")); + //check if smp (everything before + or space) exists + DTSC::Scan streamCfg = DTSC::Scan(serverCfg.mapped, serverCfg.len).getMember("streams").getMember(smp); + if (streamCfg){ + source = streamCfg.getMember("source").asString(); + } + configLock.post(); + configLock.close(); + } + if (!myMeta.tracks.size() && (source.find("dtsc://") == 0)){ + //Wait 5 seconds and try again. Keep a counter, try at most 3 times + static int counter = 0; + if (counter++ < 10){ + Util::wait(1000); + nProxy.userClient.keepAlive(); + stats(); + updateMeta(); + selectDefaultTracks(); + } + } + } } /// Clears the buffer, sets parseData to false, and generally makes not very much happen at all. @@ -287,14 +348,15 @@ namespace Mist { } int Output::pageNumForKey(long unsigned int trackId, long long int keyNum){ - if (!metaPages.count(trackId)){ + if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){ char id[NAME_BUFFER_SIZE]; snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId); - metaPages[trackId].init(id, 8 * 1024); + nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE); } - int len = metaPages[trackId].len / 8; + if (!nProxy.metaPages[trackId].mapped){return -1;} + int len = nProxy.metaPages[trackId].len / 8; for (int i = 0; i < len; i++){ - int * tmpOffset = (int *)(metaPages[trackId].mapped + (i * 8)); + int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8)); long amountKey = ntohl(tmpOffset[1]); if (amountKey == 0){continue;} long tmpKey = ntohl(tmpOffset[0]); @@ -304,19 +366,40 @@ namespace Mist { } return -1; } + + /// Gets the highest page number available for the given trackId. + int Output::pageNumMax(long unsigned int trackId){ + if (!nProxy.metaPages.count(trackId) || !nProxy.metaPages[trackId].mapped){ + char id[NAME_BUFFER_SIZE]; + snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_INDEX, streamName.c_str(), trackId); + nProxy.metaPages[trackId].init(id, SHM_TRACK_INDEX_SIZE); + } + if (!nProxy.metaPages[trackId].mapped){return -1;} + int len = nProxy.metaPages[trackId].len / 8; + int highest = -1; + for (int i = 0; i < len; i++){ + int * tmpOffset = (int *)(nProxy.metaPages[trackId].mapped + (i * 8)); + long amountKey = ntohl(tmpOffset[1]); + if (amountKey == 0){continue;} + long tmpKey = ntohl(tmpOffset[0]); + if (tmpKey > highest){highest = tmpKey;} + } + return highest; + } void Output::loadPageForKey(long unsigned int trackId, long long int keyNum){ if (myMeta.vod && keyNum > myMeta.tracks[trackId].keys.rbegin()->getNumber()){ - curPage.erase(trackId); + INFO_MSG("Seek in track %lu to key %lld aborted, is > %lld", trackId, keyNum, myMeta.tracks[trackId].keys.rbegin()->getNumber()); + nProxy.curPage.erase(trackId); currKeyOpen.erase(trackId); return; } - DEBUG_MSG(DLVL_VERYHIGH, "Loading track %lu, containing key %lld", trackId, keyNum); + VERYHIGH_MSG("Loading track %lu, containing key %lld", trackId, keyNum); unsigned int timeout = 0; unsigned long pageNum = pageNumForKey(trackId, keyNum); while (pageNum == -1){ if (!timeout){ - DEBUG_MSG(DLVL_HIGH, "Requesting page with key %lu:%lld", trackId, keyNum); + HIGH_MSG("Requesting page with key %lu:%lld", trackId, keyNum); } ++timeout; //if we've been waiting for this page for 3 seconds, reconnect to the stream - something might be going wrong... @@ -325,8 +408,8 @@ namespace Mist { reconnect(); } if (timeout > 100){ - DEBUG_MSG(DLVL_FAIL, "Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId); - curPage.erase(trackId); + FAIL_MSG("Timeout while waiting for requested page %lld for track %lu. Aborting.", keyNum, trackId); + nProxy.curPage.erase(trackId); currKeyOpen.erase(trackId); return; } @@ -335,7 +418,7 @@ namespace Mist { }else{ nxtKeyNum[trackId] = 0; } - stats(); + stats(true); Util::wait(100); pageNum = pageNumForKey(trackId, keyNum); } @@ -345,20 +428,20 @@ namespace Mist { }else{ nxtKeyNum[trackId] = 0; } - stats(); - nxtKeyNum[trackId] = pageNum; + stats(true); if (currKeyOpen.count(trackId) && currKeyOpen[trackId] == (unsigned int)pageNum){ return; } char id[NAME_BUFFER_SIZE]; snprintf(id, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), trackId, pageNum); - curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE); - if (!(curPage[trackId].mapped)){ - DEBUG_MSG(DLVL_FAIL, "Initializing page %s failed", curPage[trackId].name.c_str()); + nProxy.curPage[trackId].init(id, DEFAULT_DATA_PAGE_SIZE); + if (!(nProxy.curPage[trackId].mapped)){ + FAIL_MSG("Initializing page %s failed", nProxy.curPage[trackId].name.c_str()); return; } currKeyOpen[trackId] = pageNum; + VERYHIGH_MSG("Page %s loaded for %s", id, streamName.c_str()); } /// Prepares all tracks from selectedTracks for seeking to the specified ms position. @@ -373,44 +456,58 @@ namespace Mist { if (myMeta.live){ updateMeta(); } - DEBUG_MSG(DLVL_MEDIUM, "Seeking to %llums", pos); + MEDIUM_MSG("Seeking to %llums", pos); for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - seek(*it, pos); + if (myMeta.tracks.count(*it)){ + seek(*it, pos); + } } } bool Output::seek(unsigned int tid, unsigned long long pos, bool getNextKey){ - loadPageForKey(tid, getKeyForTime(tid, pos) + (getNextKey?1:0)); - if (!curPage.count(tid) || !curPage[tid].mapped){ - INFO_MSG("Aborting seek to %llums in track %u, not available.", pos, tid); + if (myMeta.tracks[tid].lastms < pos){ + INFO_MSG("Aborting seek to %llums in track %u: past end of track (= %llums).", pos, tid, myMeta.tracks[tid].lastms); + return false; + } + unsigned int keyNum = getKeyForTime(tid, pos); + if (myMeta.tracks[tid].getKey(keyNum).getTime() > pos){ + if (myMeta.live){ + INFO_MSG("Actually seeking to %d, for %d is not available any more", myMeta.tracks[tid].getKey(keyNum).getTime(), pos); + pos = myMeta.tracks[tid].getKey(keyNum).getTime(); + } + } + loadPageForKey(tid, keyNum + (getNextKey?1:0)); + if (!nProxy.curPage.count(tid) || !nProxy.curPage[tid].mapped){ + INFO_MSG("Aborting seek to %llums in track %u: not available.", pos, tid); return false; } sortedPageInfo tmp; tmp.tid = tid; tmp.offset = 0; DTSC::Packet tmpPack; - tmpPack.reInit(curPage[tid].mapped + tmp.offset, 0, true); + tmpPack.reInit(nProxy.curPage[tid].mapped + tmp.offset, 0, true); tmp.time = tmpPack.getTime(); - char * mpd = curPage[tid].mapped; + char * mpd = nProxy.curPage[tid].mapped; while ((long long)tmp.time < pos && tmpPack){ tmp.offset += tmpPack.getDataLen(); tmpPack.reInit(mpd + tmp.offset, 0, true); tmp.time = tmpPack.getTime(); } if (tmpPack){ + HIGH_MSG("Sought to time %d in %s@%u", tmp.time, streamName.c_str(), tid); buffer.insert(tmp); return true; }else{ //don't print anything for empty packets - not sign of corruption, just unfinished stream. - if (curPage[tid].mapped[tmp.offset] != 0){ - DEBUG_MSG(DLVL_FAIL, "Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid); + if (nProxy.curPage[tid].mapped[tmp.offset] != 0){ + FAIL_MSG("Noes! Couldn't find packet on track %d because of some kind of corruption error or somesuch.", tid); }else{ VERYHIGH_MSG("Track %d no data (key %u @ %u) - waiting...", tid, getKeyForTime(tid, pos) + (getNextKey?1:0), tmp.offset); unsigned int i = 0; - while (curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){ + while (nProxy.curPage[tid].mapped[tmp.offset] == 0 && ++i < 42){ Util::wait(100); } - if (curPage[tid].mapped[tmp.offset] == 0){ + if (nProxy.curPage[tid].mapped[tmp.offset] == 0){ FAIL_MSG("Track %d no data (key %u) - timeout", tid, getKeyForTime(tid, pos) + (getNextKey?1:0)); }else{ return seek(tid, pos, getNextKey); @@ -434,9 +531,8 @@ namespace Mist { } int Output::run() { - DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler started"); - while (config->is_active && myConn.connected() && (wantRequest || parseData)){ - stats(); + DONTEVEN_MSG("MistOut client handler started"); + while (config->is_active && myConn && (wantRequest || parseData)){ if (wantRequest){ requestHandler(); } @@ -445,11 +541,67 @@ namespace Mist { initialize(); } if ( !sentHeader){ - DEBUG_MSG(DLVL_DONTEVEN, "sendHeader"); + DONTEVEN_MSG("sendHeader"); sendHeader(); } - prepareNext(); + if (!sought){ + if (myMeta.live){ + long unsigned int mainTrack = getMainSelectedTrack(); + //cancel if there are no keys in the main track + if (!myMeta.tracks.count(mainTrack) || !myMeta.tracks[mainTrack].keys.size()){break;} + //seek to the newest keyframe, unless that is <5s, then seek to the oldest keyframe + unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime(); + if (seekPos < 5000){ + seekPos = myMeta.tracks[mainTrack].keys.begin()->getTime(); + } + seek(seekPos); + }else{ + seek(0); + } + } + if (prepareNext()){ if (thisPacket){ + + + //slow down processing, if real time speed is wanted + if (realTime){ + while (thisPacket.getTime() > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) { + Util::sleep(std::min(thisPacket.getTime() - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime, 1000llu)); + stats(); + } + } + + //delay the stream until its current keyframe is complete, if only complete keys wanted + if (completeKeysOnly){ + bool completeKeyReady = false; + int timeoutTries = 40;//wait default 250ms*40=10 seconds + for (std::map::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::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= thisPacket.getTime() ){ + completeKeyReady = false; + break; + } + } + if (!completeKeyReady){ + timeoutTries--;//we count down + stats(); + Util::wait(250); + updateMeta(); + } + } + if (timeoutTries<=0){ + WARN_MSG("Waiting for key frame timed out"); + completeKeysOnly = false; + } + } + sendNext(); }else{ if (!onFinish()){ @@ -458,9 +610,12 @@ namespace Mist { } } } - DEBUG_MSG(DLVL_MEDIUM, "MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data"); stats(); - userClient.finish(); + } + MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", parseData ? "parsing_data" : "not_parsing_data"); + + stats(true); + nProxy.userClient.finish(); statsPage.finish(); myConn.close(); return 0; @@ -473,250 +628,258 @@ namespace Mist { return 0; } for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (myMeta.tracks[*it].type == "video"){ + if (myMeta.tracks.count(*it) && myMeta.tracks[*it].type == "video"){ return *it; } } return *(selectedTracks.begin()); } - void Output::prepareNext(){ - static int nonVideoCount = 0; - if (!sought){ - if (myMeta.live){ - long unsigned int mainTrack = getMainSelectedTrack(); - if (myMeta.tracks[mainTrack].keys.size() < 2){ - if (!myMeta.tracks[mainTrack].keys.size()){ - myConn.close(); - return; - }else{ - seek(myMeta.tracks[mainTrack].keys.begin()->getTime()); - prepareNext(); - return; - } - } - unsigned long long seekPos = myMeta.tracks[mainTrack].keys.rbegin()->getTime(); - if (seekPos < 5000){ - seekPos = 0; - } - seek(seekPos); - }else{ - seek(0); + void Output::dropTrack(uint32_t trackId, std::string reason, bool probablyBad){ + //depending on whether this is probably bad and the current debug level, print a message + unsigned int printLevel = DLVL_INFO; + if (probablyBad){ + printLevel = DLVL_WARN; + } + DEBUG_MSG(printLevel, "Dropping %s (%s) track %lu@k%lu (nextP=%d, lastP=%d): %s", streamName.c_str(), myMeta.tracks[trackId].codec.c_str(), (long unsigned)trackId, nxtKeyNum[trackId]+1, pageNumForKey(trackId, nxtKeyNum[trackId]+1), pageNumMax(trackId), reason.c_str()); + //now actually drop the track from the buffer + for (std::set::iterator it = buffer.begin(); it != buffer.end(); ++it){ + if (it->tid == trackId){ + buffer.erase(it); + break; } } + selectedTracks.erase(trackId); + } + + ///Attempts to prepare a new packet for output. + ///If thisPacket evaluates to false, playback has completed. + ///Could be called repeatedly in a loop if you really really want a new packet. + /// \returns true if thisPacket was filled with the next packet. + /// \returns false if we could not reliably determine the next packet yet. + bool Output::prepareNext(){ + static bool atLivePoint = false; + static int nonVideoCount = 0; static unsigned int emptyCount = 0; if (!buffer.size()){ thisPacket.null(); - DEBUG_MSG(DLVL_DEVEL, "Buffer completely played out"); - onFinish(); - return; + INFO_MSG("Buffer completely played out"); + return true; } sortedPageInfo nxt = *(buffer.begin()); - buffer.erase(buffer.begin()); - DEBUG_MSG(DLVL_DONTEVEN, "Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time); - - if (nxt.offset >= curPage[nxt.tid].len){ + if (!myMeta.tracks.count(nxt.tid)){ + dropTrack(nxt.tid, "disappeared from metadata", true); + return false; + } + + DONTEVEN_MSG("Loading track %u (next=%lu), %llu ms", nxt.tid, nxtKeyNum[nxt.tid], nxt.time); + + //if we're going to read past the end of the data page, load the next page + //this only happens for VoD + if (nxt.offset >= nProxy.curPage[nxt.tid].len){ nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); nxt.offset = 0; - if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){ - if (getDTSCTime(curPage[nxt.tid].mapped, nxt.offset) < nxt.time){ - ERROR_MSG("Time going backwards in track %u - dropping track.", nxt.tid); + if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){ + if (getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset) < nxt.time){ + dropTrack(nxt.tid, "time going backwards"); }else{ - nxt.time = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset); + nxt.time = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); + //swap out the next object in the buffer with a new one + buffer.erase(buffer.begin()); buffer.insert(nxt); } - prepareNext(); - return; + }else{ + dropTrack(nxt.tid, "page load failure", true); } - } - - if (!curPage.count(nxt.tid) || !curPage[nxt.tid].mapped){ - //mapping failure? Drop this track and go to next. - //not an error - usually means end of stream. - DEBUG_MSG(DLVL_DEVEL, "Track %u no page - dropping track.", nxt.tid); - prepareNext(); - return; + return false; } //have we arrived at the end of the memory page? (4 zeroes mark the end) - if (!memcmp(curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){ + if (!memcmp(nProxy.curPage[nxt.tid].mapped + nxt.offset, "\000\000\000\000", 4)){ //if we don't currently know where we are, we're lost. We should drop the track. if (!nxt.time){ - DEBUG_MSG(DLVL_DEVEL, "Timeless empty packet on track %u - dropping track.", nxt.tid); - prepareNext(); - return; + dropTrack(nxt.tid, "timeless empty packet"); + return false; } //if this is a live stream, we might have just reached the live point. //check where the next key is + nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); int nextPage = pageNumForKey(nxt.tid, nxtKeyNum[nxt.tid]+1); - //are we live, and the next key hasn't shown up on another page? then we're waiting. + //are we live, and the next key hasn't shown up on another page, then we're waiting. if (myMeta.live && currKeyOpen.count(nxt.tid) && (currKeyOpen[nxt.tid] == (unsigned int)nextPage || nextPage == -1)){ - if (myMeta && ++emptyCount < 42){ - //we're waiting for new data. Simply retry. - buffer.insert(nxt); - }else{ - //after ~10 seconds, give up and drop the track. - DEBUG_MSG(DLVL_DEVEL, "Empty packet on track %u @ key %lu (next=%d) - could not reload, dropping track.", nxt.tid, nxtKeyNum[nxt.tid]+1, nextPage); - } - //keep updating the metadata at 250ms intervals while waiting for more data - Util::sleep(250); - updateMeta(); - }else{ - //if we're not live, we've simply reached the end of the page. Load the next key. - nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); - loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); - nxt.offset = 0; - if (curPage.count(nxt.tid) && curPage[nxt.tid].mapped){ - unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset); - if (nextTime && nextTime < nxt.time){ - DEBUG_MSG(DLVL_DEVEL, "Time going backwards in track %u - dropping track.", nxt.tid); + if (++emptyCount < 100){ + Util::wait(250); + //we're waiting for new data to show up + if (emptyCount % 8 == 0){ + reconnect();//reconnect every 2 seconds }else{ - if (nextTime){ - nxt.time = nextTime; + if (emptyCount % 4 == 0){ + updateMeta(); } - buffer.insert(nxt); - DEBUG_MSG(DLVL_MEDIUM, "Next page for track %u starts at %llu.", nxt.tid, nxt.time); } }else{ - DEBUG_MSG(DLVL_DEVEL, "Could not load next memory page for track %u - dropping track.", nxt.tid); + //after ~25 seconds, give up and drop the track. + dropTrack(nxt.tid, "could not reload empty packet"); } + return false; } - prepareNext(); - return; - } - thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); - if (thisPacket){ - if (thisPacket.getTime() != nxt.time && nxt.time){ - WARN_MSG("Loaded track %ld@%llu instead of %ld@%llu", thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time); - } - if ((myMeta.tracks[nxt.tid].type == "video" && thisPacket.getFlag("keyframe")) || (++nonVideoCount % 30 == 0)){ - if (myMeta.live){ - updateMeta(); - } - nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); - DEBUG_MSG(DLVL_VERYHIGH, "Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]); - } - emptyCount = 0; - } - nxt.offset += thisPacket.getDataLen(); - if (realTime){ - while (nxt.time > (((Util::getMS() - firstTime)*1000)+maxSkipAhead)/realTime) { - Util::sleep(nxt.time - (((Util::getMS() - firstTime)*1000)+minSkipAhead)/realTime); - } - } - //delay the stream until its current keyframe is complete - if (completeKeysOnly){ - bool completeKeyReady = false; - int timeoutTries = 40;//attempts to updateMeta before timeOut and moving on; default is approximately 10 seconds - for (std::map::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::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ - if (!myMeta.tracks[*it].keys.size() || myMeta.tracks[*it].keys.rbegin()->getTime() + myMeta.tracks[*it].keys.rbegin()->getLength() <= nxt.time ){ - completeKeyReady = false; - break; + + //We've simply reached the end of the page. Load the next key = next page. + loadPageForKey(nxt.tid, ++nxtKeyNum[nxt.tid]); + nxt.offset = 0; + if (nProxy.curPage.count(nxt.tid) && nProxy.curPage[nxt.tid].mapped){ + unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); + if (nextTime && nextTime < nxt.time){ + dropTrack(nxt.tid, "time going backwards"); + }else{ + if (nextTime){ + nxt.time = nextTime; } + //swap out the next object in the buffer with a new one + buffer.erase(buffer.begin()); + buffer.insert(nxt); + MEDIUM_MSG("Next page for track %u starts at %llu.", nxt.tid, nxt.time); } - if (!completeKeyReady){ - if (completeKeyReadyTimeOut){ - INSANE_MSG("Complete Key not ready and time-out is being skipped"); - timeoutTries = 0; - }else{ - INSANE_MSG("Timeout: %d",timeoutTries); - timeoutTries--;//we count down - stats(); - Util::wait(250); - updateMeta(); - } - } - } - if (timeoutTries<=0){ - if (!completeKeyReadyTimeOut){ - INFO_MSG("Wait for keyframe Timeout triggered! Ended to avoid endless loops"); - } - completeKeyReadyTimeOut = true; }else{ - //untimeout handling - completeKeyReadyTimeOut = false; + dropTrack(nxt.tid, "page load failure"); } + return false; } - if (curPage[nxt.tid]){ - if (nxt.offset < curPage[nxt.tid].len){ - unsigned long long nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset); - if (nextTime){ - nxt.time = nextTime; - }else{ - ++nxt.time; + + //we've handled all special cases - at this point the packet should exist + //let's load it + thisPacket.reInit(nProxy.curPage[nxt.tid].mapped + nxt.offset, 0, true); + //if it failed, drop the track and continue + if (!thisPacket){ + dropTrack(nxt.tid, "packet load failure"); + return false; + } + emptyCount = 0;//valid packet - reset empty counter + + //if there's a timestamp mismatch, print this. + //except for live, where we never know the time in advance + if (thisPacket.getTime() != nxt.time && nxt.time && !atLivePoint){ + static int warned = 0; + if (warned < 5){ + WARN_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str()); + if (++warned == 5){ + WARN_MSG("Further warnings about time mismatches printed on HIGH level."); } + }else{ + HIGH_MSG("Loaded %s track %ld@%llu in stead of %u@%llu (%dms, %s)", streamName.c_str(), thisPacket.getTrackId(), thisPacket.getTime(), nxt.tid, nxt.time, (int)((long long)thisPacket.getTime() - (long long)nxt.time), myMeta.tracks[nxt.tid].codec.c_str()); } - buffer.insert(nxt); } - stats(); + + //when live, every keyframe, check correctness of the keyframe number + if (myMeta.live && thisPacket.getFlag("keyframe")){ + //Check whether returned keyframe is correct. If not, wait for approximately 10 seconds while checking. + //Failure here will cause tracks to drop due to inconsistent internal state. + nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); + int counter = 0; + while(counter < 40 && myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){ + if (counter++){ + //Only sleep 250ms if this is not the first updatemeta try + Util::wait(250); + } + updateMeta(); + nxtKeyNum[nxt.tid] = getKeyForTime(nxt.tid, thisPacket.getTime()); + } + if (myMeta.tracks[nxt.tid].getKey(nxtKeyNum[nxt.tid]).getTime() != thisPacket.getTime()){ + WARN_MSG("Keyframe value is not correct - state will now be inconsistent."); + } + EXTREME_MSG("Track %u @ %llums = key %lu", nxt.tid, thisPacket.getTime(), nxtKeyNum[nxt.tid]); + } + + //always assume we're not at the live point + atLivePoint = false; + //we assume the next packet is the next on this same page + nxt.offset += thisPacket.getDataLen(); + if (nxt.offset < nProxy.curPage[nxt.tid].len){ + unsigned long long nextTime = getDTSCTime(nProxy.curPage[nxt.tid].mapped, nxt.offset); + if (nextTime){ + nxt.time = nextTime; + }else{ + ++nxt.time; + //no packet -> we are at the live point + atLivePoint = true; + } + } + + //exchange the current packet in the buffer for the next one + buffer.erase(buffer.begin()); + buffer.insert(nxt); + + return true; } - void Output::stats(){ - static bool setHost = true; - if (!isInitialized){ - return; + /// Returns the name as it should be used in statistics. + /// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name. + /// The default implementation is usually good enough for all the non-INPUT types. + std::string Output::getStatsName(){ + if (config->hasOption("target") && config->getString("target").size()){ + return "OUTPUT"; + }else{ + return capa["name"].asStringRef(); } + } + + void Output::stats(bool force){ + //cancel stats update if not initialized + if (!isInitialized){return;} + //also cancel if it has been less than a second since the last update + //unless force is set to true + unsigned long long int now = Util::epoch(); + if (now == lastStats && !force){return;} + lastStats = now; + + EXTREME_MSG("Writing stats: %s, %s, %lu", getConnectedHost().c_str(), streamName.c_str(), crc & 0xFFFFFFFFu); if (statsPage.getData()){ - unsigned long long int now = Util::epoch(); - if (now != lastStats){ - lastStats = now; - IPC::statExchange tmpEx(statsPage.getData()); - tmpEx.now(now); - if (setHost){ - tmpEx.host(getConnectedBinHost()); - setHost = false; - } - tmpEx.crc(crc); - tmpEx.streamName(streamName); - tmpEx.connector(capa["name"].asString()); - tmpEx.up(myConn.dataUp()); - tmpEx.down(myConn.dataDown()); - tmpEx.time(now - myConn.connTime()); - if (thisPacket){ - tmpEx.lastSecond(thisPacket.getTime()); - }else{ - tmpEx.lastSecond(0); - } - statsPage.keepAlive(); + IPC::statExchange tmpEx(statsPage.getData()); + tmpEx.now(now); + if (tmpEx.host() == std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){ + tmpEx.host(getConnectedBinHost()); } + tmpEx.crc(crc); + tmpEx.streamName(streamName); + tmpEx.connector(getStatsName()); + tmpEx.up(myConn.dataUp()); + tmpEx.down(myConn.dataDown()); + tmpEx.time(now - myConn.connTime()); + if (thisPacket){ + tmpEx.lastSecond(thisPacket.getTime()); + }else{ + tmpEx.lastSecond(0); + } + statsPage.keepAlive(); } int tNum = 0; - if (!userClient.getData()){ + if (!nProxy.userClient.getData()){ char userPageName[NAME_BUFFER_SIZE]; snprintf(userPageName, NAME_BUFFER_SIZE, SHM_USERS, streamName.c_str()); - userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); - if (!userClient.getData()){ - DEBUG_MSG(DLVL_WARN, "Player connection failure - aborting output"); + nProxy.userClient = IPC::sharedClient(userPageName, PLAY_EX_SIZE, true); + if (!nProxy.userClient.getData()){ + WARN_MSG("Player connection failure - aborting output"); myConn.close(); return; } } - if (!trackMap.size()){ + if (!nProxy.userClient.isAlive()){ + myConn.close(); + return; + } + if (!nProxy.trackMap.size()){ + IPC::userConnection userConn(nProxy.userClient.getData()); for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end() && tNum < SIMUL_TRACKS; it++){ - unsigned int tId = *it; - char * thisData = userClient.getData() + (6 * tNum); - thisData[0] = ((tId >> 24) & 0xFF); - thisData[1] = ((tId >> 16) & 0xFF); - thisData[2] = ((tId >> 8) & 0xFF); - thisData[3] = ((tId) & 0xFF); - thisData[4] = ((nxtKeyNum[tId] >> 8) & 0xFF); - thisData[5] = ((nxtKeyNum[tId]) & 0xFF); + userConn.setTrackId(tNum, *it); + userConn.setKeynum(tNum, nxtKeyNum[*it]); tNum ++; } } - userClient.keepAlive(); + nProxy.userClient.keepAlive(); if (tNum > SIMUL_TRACKS){ - DEBUG_MSG(DLVL_WARN, "Too many tracks selected, using only first %d", SIMUL_TRACKS); + WARN_MSG("Too many tracks selected, using only first %d", SIMUL_TRACKS); } } @@ -730,4 +893,24 @@ namespace Mist { //just set the sentHeader bool to true, by default sentHeader = true; } + + bool Output::connectToFile(std::string file) { + int flags = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + int mode = O_RDWR | O_CREAT | O_TRUNC; + int outFile = open(file.c_str(), mode, flags); + if (outFile < 0) { + ERROR_MSG("Failed to open file %s, error: %s", file.c_str(), strerror(errno)); + return false; + } + + int r = dup2(outFile, myConn.getSocket()); + if (r == -1) { + ERROR_MSG("Failed to create an alias for the socket using dup2: %s.", strerror(errno)); + return false; + } + close(outFile); + return true; + } + } + diff --git a/src/output/output.h b/src/output/output.h index 30934090..17d95c60 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -37,13 +37,12 @@ namespace Mist { public: //constructor and destructor Output(Socket::Connection & conn); - virtual ~Output(); //static members for initialization and capabilities static void init(Util::Config * cfg); static JSON::Value capa; //non-virtual generic functions int run(); - void stats(); + void stats(bool force = false); void seek(unsigned long long pos); bool seek(unsigned int tid, unsigned long long pos, bool getNextKey = false); void stop(); @@ -51,10 +50,13 @@ namespace Mist { long unsigned int getMainSelectedTrack(); void updateMeta(); void selectDefaultTracks(); + bool connectToFile(std::string file); static bool listenMode(){return true;} + virtual bool isReadyForPlay(); //virtuals. The optional virtuals have default implementations that do as little as possible. virtual void sendNext() {}//REQUIRED! Others are optional. - virtual void prepareNext(); + bool prepareNext(); + virtual void dropTrack(uint32_t trackId, std::string reason, bool probablyBad = true); virtual void onRequest(); virtual bool onFinish() { return false; @@ -68,21 +70,21 @@ namespace Mist { std::map currKeyOpen; void loadPageForKey(long unsigned int trackId, long long int keyNum); int pageNumForKey(long unsigned int trackId, long long int keyNum); + int pageNumMax(long unsigned int trackId); unsigned int lastStats;///