diff --git a/createhooks.sh b/createhooks.sh index 067aafa8..346bbe6a 100755 --- a/createhooks.sh +++ b/createhooks.sh @@ -2,7 +2,9 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" echo -e "#!/bin/bash\n[ -f configure ] && touch configure\n[ -f configure.ac ] && touch configure.ac" > $DIR/.git/hooks/post-commit echo -e "#!/bin/bash\n[ -f configure ] && touch configure\n[ -f configure.ac ] && touch configure.ac" > $DIR/.git/hooks/post-checkout +echo -e "#!/bin/bash\n[ -f configure ] && touch configure\n[ -f configure.ac ] && touch configure.ac" > $DIR/.git/hooks/post-merge chmod +x $DIR/.git/hooks/post-commit chmod +x $DIR/.git/hooks/post-checkout +chmod +x $DIR/.git/hooks/post-merge echo "Done! The version number should now auto-update whenever you commit or checkout." diff --git a/lib/Makefile.am b/lib/Makefile.am index da653e54..2e9ff296 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,6 +1,7 @@ lib_LTLIBRARIES=libmist-1.0.la libmist_1_0_la_SOURCES=amf.h amf.cpp auth.h auth.cpp base64.h base64.cpp config.h config.cpp crypto.h crypto.cpp dtsc.h dtsc.cpp flv_tag.h flv_tag.cpp http_parser.h http_parser.cpp json.h json.cpp procs.h procs.cpp rtmpchunks.h rtmpchunks.cpp socket.h socket.cpp mp4.h mp4.cpp ftp.h ftp.cpp filesystem.h filesystem.cpp libmist_1_0_la_LIBADD=-lssl -lcrypto +libmist_1_0_la_LDFLAGS = -version-info 1:0:0 pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = mist-1.0.pc diff --git a/lib/config.cpp b/lib/config.cpp index 194d85c2..99b1503a 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -28,30 +28,37 @@ bool Util::Config::is_active = false; Util::Config::Config(std::string cmd, std::string version){ vals.null(); long_count = 0; - vals["cmd"]["current"] = cmd; + vals["cmd"]["value"].append(cmd); vals["version"]["long"] = "version"; vals["version"]["short"] = "v"; vals["version"]["help"] = "Display library and application version, then exit."; vals["help"]["long"] = "help"; vals["help"]["short"] = "h"; vals["help"]["help"] = "Display usage and version information, then exit."; - vals["version"]["current"] = version; + vals["version"]["value"].append((std::string)PACKAGE_VERSION); + vals["version"]["value"].append(version); } /// Adds an option to the configuration parser. /// The option needs an unique name (doubles will overwrite the previous) and can contain the following in the option itself: +///\code /// { /// "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. -/// "default":1234, //The default value for this option if it is not given on the commandline. +/// "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. /// "help":"Blahblahblah" //The helptext for this option. /// } +///\endcode void Util::Config::addOption(std::string optname, JSON::Value option){ vals[optname] = option; + if (!vals[optname].isMember("value") && vals[optname].isMember("default")){ + vals[optname]["value"].append(vals[optname]["default"]); + vals[optname].removeMember("default"); + } long_count = 0; for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++){ if (it->second.isMember("long")){long_count++;} @@ -61,10 +68,10 @@ void Util::Config::addOption(std::string optname, JSON::Value option){ /// Prints a usage message to the given output. void Util::Config::printHelp(std::ostream & output){ - int longest = 0; + unsigned int longest = 0; std::map args; for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++){ - int current = 0; + unsigned int current = 0; if (it->second.isMember("long")){current += it->second["long"].asString().size() + 4;} if (it->second.isMember("short")){current += it->second["short"].asString().size() + 3;} if (current > longest){longest = current;} @@ -80,7 +87,7 @@ void Util::Config::printHelp(std::ostream & output){ } output << "Usage: " << getString("cmd") << " [options]"; for (std::map::iterator i = args.begin(); i != args.end(); i++){ - if (vals[i->second].isMember("default")){ + if (vals[i->second].isMember("value") && vals[i->second]["value"].size()){ output << " [" << i->second << "]"; }else{ output << " " << i->second; @@ -163,7 +170,7 @@ void Util::Config::parseArgs(int argc, char ** argv){ if (it->second.isMember("arg")){longOpts[long_i].has_arg = 1;} long_i++; } - if (it->second.isMember("arg_num") && !it->second.isMember("default")){ + if (it->second.isMember("arg_num") && !(it->second.isMember("value") && it->second["value"].size())){ if (it->second["arg_num"].asInt() > arg_count){ arg_count = it->second["arg_num"].asInt(); } @@ -183,14 +190,14 @@ void Util::Config::parseArgs(int argc, char ** argv){ for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++){ if (it->second.isMember("short") && it->second["short"].asString()[0] == opt){ if (it->second.isMember("arg")){ - it->second["current"] = (std::string)optarg; + it->second["value"].append((std::string)optarg); }else{ - it->second["current"] = 1; + it->second["value"].append((long long int)1); } break; } if (it->second.isMember("short_off") && it->second["short_off"].asString()[0] == opt){ - it->second["current"] = 0; + it->second["value"].append((long long int)0); } } break; @@ -201,7 +208,7 @@ void Util::Config::parseArgs(int argc, char ** argv){ while (optind < argc){//parse all remaining options, ignoring anything unexpected. for (JSON::ObjIter it = vals.ObjBegin(); it != vals.ObjEnd(); it++){ if (it->second.isMember("arg_num") && it->second["arg_num"].asInt() == long_i){ - it->second["current"] = (std::string)argv[optind]; + it->second["value"].append((std::string)argv[optind]); optind++; long_i++; break; @@ -217,15 +224,19 @@ void Util::Config::parseArgs(int argc, char ** argv){ /// 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){ +JSON::Value & Util::Config::getOption(std::string optname, bool asArray){ if (!vals.isMember(optname)){ std::cout << "Fatal error: a non-existent option '" << optname << "' was accessed." << std::endl; exit(37); } - if (vals[optname].isMember("current")){ - return vals[optname]["current"]; + if (!vals[optname].isMember("value") || !vals[optname]["value"].isArray()){ + vals[optname]["value"].append(JSON::Value()); + } + if (asArray){ + return vals[optname]["value"]; }else{ - return vals[optname]["default"]; + int n = vals[optname]["value"].size(); + return vals[optname]["value"][n-1]; } } @@ -292,11 +303,11 @@ void Util::Config::signal_handler(int signum){ /// Adds the default connector options to this Util::Config object. void Util::Config::addConnectorOptions(int port){ JSON::Value stored_port = JSON::fromString("{\"long\":\"port\", \"short\":\"p\", \"arg\":\"integer\", \"help\":\"TCP port to listen on.\"}"); - stored_port["default"] = port; + stored_port["value"].append((long long int)port); addOption("listen_port", stored_port); - addOption("listen_interface", JSON::fromString("{\"long\":\"interface\", \"default\":\"0.0.0.0\", \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}")); - addOption("username", JSON::fromString("{\"long\":\"username\", \"default\":\"root\", \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}")); - addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"default\":1, \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}")); + addOption("listen_interface", JSON::fromString("{\"long\":\"interface\", \"value\":[\"0.0.0.0\"], \"short\":\"i\", \"arg\":\"string\", \"help\":\"Interface address to listen on, or 0.0.0.0 for all available interfaces.\"}")); + addOption("username", JSON::fromString("{\"long\":\"username\", \"value\":[\"root\"], \"short\":\"u\", \"arg\":\"string\", \"help\":\"Username to drop privileges to, or root to not drop provileges.\"}")); + addOption("daemonize", JSON::fromString("{\"long\":\"daemon\", \"short\":\"d\", \"value\":[1], \"long_off\":\"nodaemon\", \"short_off\":\"n\", \"help\":\"Whether or not to daemonize the process after starting.\"}")); }//addConnectorOptions /// Sets the current process' running user diff --git a/lib/config.h b/lib/config.h index 5db09351..4ae72c40 100644 --- a/lib/config.h +++ b/lib/config.h @@ -22,7 +22,7 @@ namespace Util{ void addOption(std::string optname, JSON::Value option); void printHelp(std::ostream & output); void parseArgs(int argc, char ** argv); - JSON::Value & getOption(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); bool getBool(std::string optname); diff --git a/lib/dtsc.cpp b/lib/dtsc.cpp index 624e99bf..e18d7817 100644 --- a/lib/dtsc.cpp +++ b/lib/dtsc.cpp @@ -26,7 +26,7 @@ DTSC::Stream::Stream(unsigned int rbuffers){ /// Returns the time in milliseconds of the last received packet. /// This is _not_ the time this packet was received, only the stored time. unsigned int DTSC::Stream::getTime(){ - return buffers.front().getContentP("time")->NumValue(); + return buffers.front()["time"].asInt(); } /// Attempts to parse a packet from the given std::string buffer. @@ -40,20 +40,22 @@ bool DTSC::Stream::parsePacket(std::string & buffer){ if (memcmp(buffer.c_str(), DTSC::Magic_Header, 4) == 0){ len = ntohl(((uint32_t *)buffer.c_str())[1]); if (buffer.length() < len+8){return false;} - metadata = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len); + unsigned int i = 0; + metadata = JSON::fromDTMI((unsigned char*)buffer.c_str() + 8, len, i); buffer.erase(0, len+8); return false; } if (memcmp(buffer.c_str(), DTSC::Magic_Packet, 4) == 0){ len = ntohl(((uint32_t *)buffer.c_str())[1]); if (buffer.length() < len+8){return false;} - buffers.push_front(DTSC::DTMI("empty", DTMI_ROOT)); - buffers.front() = DTSC::parseDTMI((unsigned char*)buffer.c_str() + 8, len); + buffers.push_front(JSON::Value()); + unsigned int i = 0; + buffers.front() = JSON::fromDTMI((unsigned char*)buffer.c_str() + 8, len, i); datapointertype = INVALID; - if (buffers.front().getContentP("data")){ - datapointer = &(buffers.front().getContentP("data")->StrValue()); - if (buffers.front().getContentP("datatype")){ - std::string tmp = buffers.front().getContentP("datatype")->StrValue(); + if (buffers.front().isMember("data")){ + datapointer = &(buffers.front()["data"].strVal); + if (buffers.front().isMember("datatype")){ + std::string tmp = buffers.front()["datatype"].asString(); if (tmp == "video"){datapointertype = VIDEO;} if (tmp == "audio"){datapointertype = AUDIO;} if (tmp == "meta"){datapointertype = META;} @@ -91,7 +93,7 @@ std::string & DTSC::Stream::lastData(){ /// Returns the packed in this buffer number. /// \arg num Buffer number. -DTSC::DTMI & DTSC::Stream::getPacket(unsigned int num){ +JSON::Value & DTSC::Stream::getPacket(unsigned int num){ return buffers[num]; } @@ -102,29 +104,24 @@ DTSC::datatype DTSC::Stream::lastType(){ /// Returns true if the current stream contains at least one video track. bool DTSC::Stream::hasVideo(){ - return (metadata.getContentP("video") != 0); + return metadata.isMember("video"); } /// Returns true if the current stream contains at least one audio track. bool DTSC::Stream::hasAudio(){ - return (metadata.getContentP("audio") != 0); + return metadata.isMember("audio"); } /// Returns a packed DTSC packet, ready to sent over the network. std::string & DTSC::Stream::outPacket(unsigned int num){ static std::string emptystring; if (num >= buffers.size()) return emptystring; - buffers[num].Pack(true); - return buffers[num].packed; + return buffers[num].toNetPacked(); } /// Returns a packed DTSC header, ready to sent over the network. std::string & DTSC::Stream::outHeader(){ - if ((metadata.packed.length() < 4) || !metadata.netpacked){ - metadata.Pack(true); - metadata.packed.replace(0, 4, Magic_Header); - } - return metadata.packed; + return metadata.toNetPacked(); } /// advances all given out and internal Ring classes to point to the new buffer, after one has been added. @@ -142,7 +139,7 @@ void DTSC::Stream::advanceRings(){ dit->b++; if (dit->b >= buffers.size()){keyframes.erase(dit); break;} } - if ((lastType() == VIDEO) && (buffers.front().getContentP("keyframe"))){ + if ((lastType() == VIDEO) && (buffers.front().isMember("keyframe"))){ keyframes.push_front(DTSC::Ring(0)); } //increase buffer size if no keyframes available @@ -187,297 +184,6 @@ DTSC::Stream::~Stream(){ for (sit = rings.begin(); sit != rings.end(); sit++){delete (*sit);} } -/// Returns the std::string Indice for the current object, if available. -/// Returns an empty string if no indice exists. -std::string DTSC::DTMI::Indice(){return myIndice;}; - -/// Returns the DTSC::DTMItype AMF0 object type for this object. -DTSC::DTMItype DTSC::DTMI::GetType(){return myType;}; - -/// Returns the numeric value of this object, if available. -/// If this object holds no numeric value, 0 is returned. -uint64_t & DTSC::DTMI::NumValue(){return numval;}; - -/// Returns the std::string value of this object, if available. -/// If this object holds no string value, an empty string is returned. -std::string & DTSC::DTMI::StrValue(){return strval;}; - -/// Returns the C-string value of this object, if available. -/// If this object holds no string value, an empty C-string is returned. -const char * DTSC::DTMI::Str(){return strval.c_str();}; - -/// Returns a count of the amount of objects this object currently holds. -/// If this object is not a container type, this function will always return 0. -int DTSC::DTMI::hasContent(){return contents.size();}; - -/// Returns true if this DTSC::DTMI value is non-default. -/// Non-default means it is either not a root element or has content. -bool DTSC::DTMI::isEmpty(){ - if (myType != DTMI_ROOT){return false;} - return (hasContent() == 0); -}; - -/// Adds an DTSC::DTMI to this object. Works for all types, but only makes sense for container types. -/// This function resets DTMI::packed to an empty string, forcing a repack on the next call to DTMI::Pack. -/// If the indice name already exists, replaces the indice. -void DTSC::DTMI::addContent(DTSC::DTMI c){ - std::vector::iterator it; - for (it = contents.begin(); it != contents.end(); it++){ - if (it->Indice() == c.Indice()){ - contents.erase(it); - break; - } - } - contents.push_back(c); packed = ""; -}; - -/// Returns a pointer to the object held at indice i. -/// Returns null pointer if no object is held at this indice. -/// \param i The indice of the object in this container. -DTSC::DTMI* DTSC::DTMI::getContentP(int i){ - if (contents.size() <= (unsigned int)i){return 0;} - return &contents.at(i); -}; - -/// Returns a copy of the object held at indice i. -/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. -/// \param i The indice of the object in this container. -DTSC::DTMI DTSC::DTMI::getContent(int i){return contents.at(i);}; - -/// Returns a pointer to the object held at indice s. -/// Returns NULL if no object is held at this indice. -/// \param s The indice of the object in this container. -DTSC::DTMI* DTSC::DTMI::getContentP(std::string s){ - for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ - if (it->Indice() == s){return &(*it);} - } - return 0; -}; - -/// Returns a copy of the object held at indice s. -/// Returns a AMF::AMF0_DDV_CONTAINER of indice "error" if no object is held at this indice. -/// \param s The indice of the object in this container. -DTSC::DTMI DTSC::DTMI::getContent(std::string s){ - for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ - if (it->Indice() == s){return *it;} - } - return DTSC::DTMI("error", DTMI_ROOT); -}; - -/// Default constructor. -/// Simply fills the data with DTSC::DTMI("error", AMF0_DDV_CONTAINER) -DTSC::DTMI::DTMI(){ - *this = DTSC::DTMI("error", DTMI_ROOT); -};//default constructor - -/// Constructor for numeric objects. -/// The object type is by default DTMItype::DTMI_INT, but this can be forced to a different value. -/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. -/// \param val The numeric value of this object. Numeric objects only support uint64_t values. -/// \param setType The object type to force this object to. -DTSC::DTMI::DTMI(std::string indice, uint64_t val, DTSC::DTMItype setType){//num type initializer - myIndice = indice; - myType = setType; - strval = ""; - numval = val; -}; - -/// Constructor for string objects. -/// \param indice The string indice of this object in its container, or empty string if none. Numeric indices are automatic. -/// \param val The string value of this object. -/// \param setType The object type to force this object to. -DTSC::DTMI::DTMI(std::string indice, std::string val, DTSC::DTMItype setType){//str type initializer - myIndice = indice; - myType = setType; - strval = val; - numval = 0; -}; - -/// Constructor for container objects. -/// \param indice The string indice of this object in its container, or empty string if none. -/// \param setType The object type to force this object to. -DTSC::DTMI::DTMI(std::string indice, DTSC::DTMItype setType){//object type initializer - myIndice = indice; - myType = setType; - strval = ""; - numval = 0; -}; - -/// Prints the contents of this object to std::cerr. -/// If this object contains other objects, it will call itself recursively -/// and print all nested content in a nice human-readable format. -void DTSC::DTMI::Print(std::string indent){ - std::cerr << indent; - // print my type - switch (myType){ - case DTMI_INT: std::cerr << "Integer"; break; - case DTMI_STRING: std::cerr << "String"; break; - case DTMI_OBJECT: std::cerr << "Object"; break; - case DTMI_OBJ_END: std::cerr << "Object end"; break; - case DTMI_ROOT: std::cerr << "Root Node"; break; - } - // print my string indice, if available - std::cerr << " " << myIndice << " "; - // print my numeric or string contents - switch (myType){ - case DTMI_INT: std::cerr << numval; break; - case DTMI_STRING: - if (strval.length() > 200 || ((strval.length() > 1) && ( (strval[0] < 'A') || (strval[0] > 'z') ) )){ - std::cerr << strval.length() << " bytes of data"; - }else{ - std::cerr << strval; - } - break; - default: break;//we don't care about the rest, and don't want a compiler warning... - } - std::cerr << std::endl; - // if I hold other objects, print those too, recursively. - if (contents.size() > 0){ - for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){it->Print(indent+" ");} - } -};//print - -/// Packs the DTMI to a std::string for transfer over the network. -/// If a packed version already exists, does not regenerate it. -/// If the object is a container type, this function will call itself recursively and contain all contents. -/// \arg netpack If true, will pack as a full DTMI packet, if false only as the contents without header. -std::string DTSC::DTMI::Pack(bool netpack){ - if (packed != ""){ - if (netpacked == netpack){return packed;} - if (netpacked){ - packed.erase(0, 8); - }else{ - unsigned int size = htonl(packed.length()); - packed.insert(0, (char*)&size, 4); - packed.insert(0, Magic_Packet); - } - netpacked = !netpacked; - return packed; - } - std::string r = ""; - r += myType; - //output the properly formatted data stream for this object's contents. - switch (myType){ - case DTMI_INT: - r += *(((char*)&numval)+7); r += *(((char*)&numval)+6); - r += *(((char*)&numval)+5); r += *(((char*)&numval)+4); - r += *(((char*)&numval)+3); r += *(((char*)&numval)+2); - r += *(((char*)&numval)+1); r += *(((char*)&numval)); - break; - case DTMI_STRING: - r += strval.size() / (256*256*256); - r += strval.size() / (256*256); - r += strval.size() / 256; - r += strval.size() % 256; - r += strval; - break; - case DTMI_OBJECT: - case DTMI_ROOT: - if (contents.size() > 0){ - for (std::vector::iterator it = contents.begin(); it != contents.end(); it++){ - r += it->Indice().size() / 256; - r += it->Indice().size() % 256; - r += it->Indice(); - r += it->Pack(); - } - } - r += (char)0x0; r += (char)0x0; r += (char)0xEE; - break; - case DTMI_OBJ_END: - break; - } - packed = r; - netpacked = netpack; - if (netpacked){ - unsigned int size = htonl(packed.length()); - packed.insert(0, (char*)&size, 4); - packed.insert(0, Magic_Packet); - } - return packed; -};//pack - -/// Parses a single AMF0 type - used recursively by the AMF::parse() functions. -/// This function updates i every call with the new position in the data. -/// \param data The raw data to parse. -/// \param len The size of the raw data. -/// \param i Current parsing position in the raw data. -/// \param name Indice name for any new object created. -/// \returns A single DTSC::DTMI, parsed from the raw data. -DTSC::DTMI DTSC::parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name){ - unsigned int tmpi = 0; - unsigned char tmpdbl[8]; - uint64_t * d;// hack to work around strict aliasing - #if DEBUG >= 10 - fprintf(stderr, "Note: AMF type %hhx found. %i bytes left\n", data[i], len-i); - #endif - switch (data[i]){ - case DTMI_INT: - tmpdbl[7] = data[i+1]; - tmpdbl[6] = data[i+2]; - tmpdbl[5] = data[i+3]; - tmpdbl[4] = data[i+4]; - tmpdbl[3] = data[i+5]; - tmpdbl[2] = data[i+6]; - tmpdbl[1] = data[i+7]; - tmpdbl[0] = data[i+8]; - i+=9;//skip 8(an uint64_t)+1 forwards - d = (uint64_t*)tmpdbl; - return DTSC::DTMI(name, *d, DTMI_INT); - break; - case DTMI_STRING:{ - tmpi = data[i+1]*256*256*256+data[i+2]*256*256+data[i+3]*256+data[i+4];//set tmpi to UTF-8-long length - std::string tmpstr = std::string((const char *)data+i+5, (size_t)tmpi);//set the string data - i += tmpi + 5;//skip length+size+1 forwards - return DTSC::DTMI(name, tmpstr, DTMI_STRING); - } break; - case DTMI_ROOT:{ - ++i; - DTSC::DTMI ret(name, DTMI_ROOT); - while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE) - tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length - std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data - i += tmpi + 2;//skip length+size forwards - ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr - } - i += 3;//skip 0x0000EE - return ret; - } break; - case DTMI_OBJECT:{ - ++i; - DTSC::DTMI ret(name, DTMI_OBJECT); - while (data[i] + data[i+1] != 0){//while not encountering 0x0000 (we assume 0x0000EE) - tmpi = data[i]*256+data[i+1];//set tmpi to the UTF-8 length - std::string tmpstr = std::string((const char *)data+i+2, (size_t)tmpi);//set the string data - i += tmpi + 2;//skip length+size forwards - ret.addContent(parseOneDTMI(data, len, i, tmpstr));//add content, recursively parsed, updating i, setting indice to tmpstr - } - i += 3;//skip 0x0000EE - return ret; - } break; - } - #if DEBUG >= 2 - fprintf(stderr, "Error: Unimplemented DTMI type %hhx - returning.\n", data[i]); - #endif - return DTSC::DTMI("error", DTMI_ROOT); -}//parseOne - -/// Parses a C-string to a valid DTSC::DTMI. -/// This function will find one DTMI object in the string and return it. -DTSC::DTMI DTSC::parseDTMI(const unsigned char * data, unsigned int len){ - DTSC::DTMI ret;//container type - unsigned int i = 0; - ret = parseOneDTMI(data, len, i, ""); - ret.packed = std::string((char*)data, (size_t)len); - ret.netpacked = false; - return ret; -}//parse - -/// Parses a std::string to a valid DTSC::DTMI. -/// This function will find one DTMI object in the string and return it. -DTSC::DTMI DTSC::parseDTMI(std::string data){ - return parseDTMI((const unsigned char*)data.c_str(), data.size()); -}//parse - /// Open a filename for DTSC reading/writing. /// If create is true and file does not exist, attempt to create. DTSC::File::File(std::string filename, bool create){ @@ -511,7 +217,8 @@ DTSC::File::File(std::string filename, bool create){ fwrite(buffer, 4, 1, F);//write 4 zero-bytes headerSize = 0; }else{ - headerSize = ntohl(((uint32_t *)buffer)[0]); + uint32_t * ubuffer = (uint32_t *)buffer; + headerSize = ntohl(ubuffer[0]); } fseek(F, 8+headerSize, SEEK_SET); } @@ -520,9 +227,10 @@ DTSC::File::File(std::string filename, bool create){ /// Sets the file pointer to the first packet. std::string & DTSC::File::getHeader(){ fseek(F, 8, SEEK_SET); - strbuffer.reserve(headerSize); + strbuffer.resize(headerSize); fread((void*)strbuffer.c_str(), headerSize, 1, F); fseek(F, 8+headerSize, SEEK_SET); + return strbuffer; } /// (Re)writes the given string to the header area if the size is the same as the existing header. @@ -532,9 +240,9 @@ bool DTSC::File::writeHeader(std::string & header, bool force){ fprintf(stderr, "Could not overwrite header - not equal size\n"); return false; } - headerSize = header.size() - 8; - fseek(F, 0, SEEK_SET); - int ret = fwrite(header.c_str(), 8+headerSize, 1, F); + headerSize = header.size(); + fseek(F, 8, SEEK_SET); + int ret = fwrite(header.c_str(), headerSize, 1, F); fseek(F, 8+headerSize, SEEK_SET); return (ret == 1); } @@ -557,9 +265,10 @@ std::string & DTSC::File::getPacket(){ strbuffer = ""; return strbuffer; } - long packSize = ntohl(((uint32_t *)buffer)[0]); - strbuffer.reserve(packSize); - if (fread((void*)strbuffer.c_str(), packSize, 1, F)){ + uint32_t * ubuffer = (uint32_t *)buffer; + long packSize = ntohl(ubuffer[0]); + strbuffer.resize(packSize); + if (fread((void*)strbuffer.c_str(), packSize, 1, F) != 1){ fprintf(stderr, "Could not read packet\n"); strbuffer = ""; return strbuffer; diff --git a/lib/dtsc.h b/lib/dtsc.h index c9205952..2a6ca87d 100644 --- a/lib/dtsc.h +++ b/lib/dtsc.h @@ -9,19 +9,25 @@ #include #include #include //for FILE +#include "json.h" /// Holds all DDVTECH Stream Container classes and parsers. -///Video: +///length (int, length in seconds, if available) +///video: /// - codec (string: H264, H263, VP6) /// - width (int, pixels) /// - height (int, pixels) /// - fpks (int, frames per kilosecond (FPS * 1000)) /// - bps (int, bytes per second) /// - init (string, init data) +/// - keycount (int, count of keyframes) +/// - keyms (int, average ms per keyframe) +/// - keyvar (int, max ms per keyframe variance) +/// - keys (array of byte position ints - first is first keyframe, last is last keyframe, in between have ~equal spacing) /// -///Audio: +///audio: /// - codec (string: AAC, MP3) /// - rate (int, Hz) /// - size (int, bitsize) @@ -45,53 +51,6 @@ /// - offset (int, unsigned version of signed int! Holds the ms offset between timestamp and proper display time for B-frames) namespace DTSC{ - /// Enumerates all possible DTMI types. - enum DTMItype { - DTMI_INT = 0x01, ///< Unsigned 64-bit integer. - DTMI_STRING = 0x02, ///< String, equivalent to the AMF longstring type. - DTMI_OBJECT = 0xE0, ///< Object, equivalent to the AMF object type. - DTMI_OBJ_END = 0xEE, ///< End of object marker. - DTMI_ROOT = 0xFF ///< Root node for all DTMI data. - }; - - /// Recursive class that holds DDVTECH MediaInfo. - class DTMI { - public: - std::string Indice(); - DTMItype GetType(); - uint64_t & NumValue(); - std::string & StrValue(); - const char * Str(); - int hasContent(); - bool isEmpty(); - void addContent(DTMI c); - DTMI* getContentP(int i); - DTMI getContent(int i); - DTMI* getContentP(std::string s); - DTMI getContent(std::string s); - DTMI(); - DTMI(std::string indice, uint64_t val, DTMItype setType = DTMI_INT); - DTMI(std::string indice, std::string val, DTMItype setType = DTMI_STRING); - DTMI(std::string indice, DTMItype setType = DTMI_OBJECT); - void Print(std::string indent = ""); - std::string Pack(bool netpack = false); - bool netpacked; - std::string packed; - protected: - std::string myIndice; ///< Holds this objects indice, if any. - DTMItype myType; ///< Holds this objects AMF0 type. - std::string strval; ///< Holds this objects string value, if any. - uint64_t numval; ///< Holds this objects numeric value, if any. - std::vector contents; ///< Holds this objects contents, if any (for container types). - };//AMFType - - /// Parses a C-string to a valid DTSC::DTMI. - DTMI parseDTMI(const unsigned char * data, unsigned int len); - /// Parses a std::string to a valid DTSC::DTMI. - DTMI parseDTMI(std::string data); - /// Parses a single DTMI type - used recursively by the DTSC::parseDTMI() functions. - DTMI parseOneDTMI(const unsigned char *& data, unsigned int &len, unsigned int &i, std::string name); - /// This enum holds all possible datatypes for DTSC packets. enum datatype { AUDIO, ///< Stream Audio data @@ -114,7 +73,7 @@ namespace DTSC{ private: std::string strbuffer; FILE * F; - long headerSize; + unsigned long headerSize; char buffer[4]; };//FileWriter @@ -137,8 +96,8 @@ namespace DTSC{ Stream(); ~Stream(); Stream(unsigned int buffers); - DTSC::DTMI metadata; - DTSC::DTMI & getPacket(unsigned int num = 0); + JSON::Value metadata; + JSON::Value & getPacket(unsigned int num = 0); datatype lastType(); std::string & lastData(); bool hasVideo(); @@ -150,7 +109,7 @@ namespace DTSC{ unsigned int getTime(); void dropRing(Ring * ptr); private: - std::deque buffers; + std::deque buffers; std::set rings; std::deque keyframes; void advanceRings(); diff --git a/lib/flv_tag.cpp b/lib/flv_tag.cpp index 6a70cc06..89694a3f 100644 --- a/lib/flv_tag.cpp +++ b/lib/flv_tag.cpp @@ -257,14 +257,14 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ switch (S.lastType()){ case DTSC::VIDEO: len = S.lastData().length() + 16; - if (S.metadata.getContentP("video") && S.metadata.getContentP("video")->getContentP("codec")){ - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){len += 4;} + if (S.metadata.isMember("video") && S.metadata["video"].isMember("codec")){ + if (S.metadata["video"]["codec"].asString() == "H264"){len += 4;} } break; case DTSC::AUDIO: len = S.lastData().length() + 16; - if (S.metadata.getContentP("audio") && S.metadata.getContentP("audio")->getContentP("codec")){ - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){len += 1;} + if (S.metadata.isMember("audio") && S.metadata["audio"].isMember("codec")){ + if (S.metadata["audio"]["codec"].asString() == "AAC"){len += 1;} } break; case DTSC::META: @@ -289,18 +289,18 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ memcpy(data+12, S.lastData().c_str(), S.lastData().length()); }else{ memcpy(data+16, S.lastData().c_str(), S.lastData().length()); - if (S.getPacket().getContentP("nalu")){data[12] = 1;}else{data[12] = 2;} - int offset = S.getPacket().getContentP("offset")->NumValue(); + if (S.getPacket().isMember("nalu")){data[12] = 1;}else{data[12] = 2;} + int offset = S.getPacket()["offset"].asInt(); data[13] = (offset >> 16) & 0xFF; data[14] = (offset >> 8) & 0XFF; data[15] = offset & 0xFF; } data[11] = 0; - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){data[11] += 7;} - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){data[11] += 2;} - if (S.getPacket().getContentP("keyframe")){data[11] += 0x10;} - if (S.getPacket().getContentP("interframe")){data[11] += 0x20;} - if (S.getPacket().getContentP("disposableframe")){data[11] += 0x30;} + if (S.metadata["video"]["codec"].asString() == "H264"){data[11] += 7;} + if (S.metadata["video"]["codec"].asString() == "H263"){data[11] += 2;} + if (S.getPacket().isMember("keyframe")){data[11] += 0x10;} + if (S.getPacket().isMember("interframe")){data[11] += 0x20;} + if (S.getPacket().isMember("disposableframe")){data[11] += 0x30;} break; case DTSC::AUDIO:{ if ((unsigned int)len == S.lastData().length() + 16){ @@ -310,9 +310,9 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ data[12] = 1;//raw AAC data, not sequence header } data[11] = 0; - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;} - unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue(); + if (S.metadata["audio"]["codec"].asString() == "AAC"){data[11] += 0xA0;} + if (S.metadata["audio"]["codec"].asString() == "MP3"){data[11] += 0x20;} + unsigned int datarate = S.metadata["audio"]["rate"].asInt(); if (datarate >= 44100){ data[11] += 0x0C; }else if(datarate >= 22050){ @@ -320,8 +320,8 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ }else if(datarate >= 11025){ data[11] += 0x04; } - if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;} - if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} + if (S.metadata["audio"]["size"].asInt() == 16){data[11] += 0x02;} + if (S.metadata["audio"]["channels"].asInt() > 1){data[11] += 0x01;} break; } case DTSC::META: @@ -343,7 +343,7 @@ bool FLV::Tag::DTSCLoader(DTSC::Stream & S){ data[8] = 0; data[9] = 0; data[10] = 0; - tagTime(S.getPacket().getContentP("time")->NumValue()); + tagTime(S.getPacket()["time"].asInt()); return true; } @@ -364,8 +364,8 @@ void FLV::Tag::setLen(){ /// Takes the DTSC Video init data and makes it into FLV. /// Assumes init data is available - so check before calling! bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){ - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){ - len = S.metadata.getContentP("video")->getContentP("init")->StrValue().length() + 20; + if (S.metadata["video"]["codec"].asString() == "H264"){ + len = S.metadata["video"]["init"].asString().length() + 20; } if (len > 0){ if (!data){ @@ -377,7 +377,7 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){ buf = len; } } - memcpy(data+16, S.metadata.getContentP("video")->getContentP("init")->StrValue().c_str(), len-20); + memcpy(data+16, S.metadata["video"]["init"].asString().c_str(), len-20); data[12] = 0;//H264 sequence header data[13] = 0; data[14] = 0; @@ -401,8 +401,8 @@ bool FLV::Tag::DTSCVideoInit(DTSC::Stream & S){ /// Assumes init data is available - so check before calling! bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){ len = 0; - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){ - len = S.metadata.getContentP("audio")->getContentP("init")->StrValue().length() + 17; + if (S.metadata["audio"]["codec"].asString() == "AAC"){ + len = S.metadata["audio"]["init"].asString().length() + 17; } if (len > 0){ if (!data){ @@ -414,12 +414,12 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){ buf = len; } } - memcpy(data+13, S.metadata.getContentP("audio")->getContentP("init")->StrValue().c_str(), len-17); + memcpy(data+13, S.metadata["audio"]["init"].asString().c_str(), len-17); data[12] = 0;//AAC sequence header data[11] = 0; - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){data[11] += 0xA0;} - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){data[11] += 0x20;} - unsigned int datarate = S.metadata.getContentP("audio")->getContentP("rate")->NumValue(); + if (S.metadata["audio"]["codec"].asString() == "AAC"){data[11] += 0xA0;} + if (S.metadata["audio"]["codec"].asString() == "MP3"){data[11] += 0x20;} + unsigned int datarate = S.metadata["audio"]["rate"].asInt(); if (datarate >= 44100){ data[11] += 0x0C; }else if(datarate >= 22050){ @@ -427,8 +427,8 @@ bool FLV::Tag::DTSCAudioInit(DTSC::Stream & S){ }else if(datarate >= 11025){ data[11] += 0x04; } - if (S.metadata.getContentP("audio")->getContentP("size")->NumValue() == 16){data[11] += 0x02;} - if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){data[11] += 0x01;} + if (S.metadata["audio"]["size"].asInt() == 16){data[11] += 0x02;} + if (S.metadata["audio"]["channels"].asInt() > 1){data[11] += 0x01;} } setLen(); data[0] = 0x08; @@ -450,54 +450,54 @@ bool FLV::Tag::DTSCMetaInit(DTSC::Stream & S){ amfdata.addContent(AMF::Object("", "onMetaData")); amfdata.addContent(AMF::Object("", AMF::AMF0_ECMA_ARRAY)); - if (S.metadata.getContentP("video")){ + if (S.metadata.isMember("video")){ amfdata.getContentP(1)->addContent(AMF::Object("hasVideo", 1, AMF::AMF0_BOOL)); - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H264"){ + if (S.metadata["video"]["codec"].asString() == "H264"){ amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 7, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "VP6"){ + if (S.metadata["video"]["codec"].asString() == "VP6"){ amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 4, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("codec")->StrValue() == "H263"){ + if (S.metadata["video"]["codec"].asString() == "H263"){ amfdata.getContentP(1)->addContent(AMF::Object("videocodecid", 2, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("width")){ - amfdata.getContentP(1)->addContent(AMF::Object("width", S.metadata.getContentP("video")->getContentP("width")->NumValue(), AMF::AMF0_NUMBER)); + if (S.metadata["video"].isMember("width")){ + amfdata.getContentP(1)->addContent(AMF::Object("width", S.metadata["video"]["width"].asInt(), AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("height")){ - amfdata.getContentP(1)->addContent(AMF::Object("height", S.metadata.getContentP("video")->getContentP("height")->NumValue(), AMF::AMF0_NUMBER)); + if (S.metadata["video"].isMember("height")){ + amfdata.getContentP(1)->addContent(AMF::Object("height", S.metadata["video"]["height"].asInt(), AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("fpks")){ - amfdata.getContentP(1)->addContent(AMF::Object("framerate", (double)S.metadata.getContentP("video")->getContentP("fpks")->NumValue() / 1000.0, AMF::AMF0_NUMBER)); + if (S.metadata["video"].isMember("fpks")){ + amfdata.getContentP(1)->addContent(AMF::Object("framerate", (double)S.metadata["video"]["fpks"].asInt() / 1000.0, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("video")->getContentP("bps")){ - amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", ((double)S.metadata.getContentP("video")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER)); + if (S.metadata["video"].isMember("bps")){ + amfdata.getContentP(1)->addContent(AMF::Object("videodatarate", ((double)S.metadata["video"]["bps"].asInt() * 8.0) / 1024.0, AMF::AMF0_NUMBER)); } } - if (S.metadata.getContentP("audio")){ + if (S.metadata.isMember("audio")){ amfdata.getContentP(1)->addContent(AMF::Object("hasAudio", 1, AMF::AMF0_BOOL)); amfdata.getContentP(1)->addContent(AMF::Object("audiodelay", 0, AMF::AMF0_NUMBER)); - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "AAC"){ + if (S.metadata["audio"]["codec"].asString() == "AAC"){ amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 10, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("audio")->getContentP("codec")->StrValue() == "MP3"){ + if (S.metadata["audio"]["codec"].asString() == "MP3"){ amfdata.getContentP(1)->addContent(AMF::Object("audiocodecid", 2, AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("audio")->getContentP("channels")){ - if (S.metadata.getContentP("audio")->getContentP("channels")->NumValue() > 1){ + if (S.metadata["audio"].isMember("channels")){ + if (S.metadata["audio"]["channels"].asInt() > 1){ amfdata.getContentP(1)->addContent(AMF::Object("stereo", 1, AMF::AMF0_BOOL)); }else{ amfdata.getContentP(1)->addContent(AMF::Object("stereo", 0, AMF::AMF0_BOOL)); } } - if (S.metadata.getContentP("audio")->getContentP("rate")){ - amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", S.metadata.getContentP("audio")->getContentP("rate")->NumValue(), AMF::AMF0_NUMBER)); + if (S.metadata["audio"].isMember("rate")){ + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplerate", S.metadata["audio"]["rate"].asInt(), AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("audio")->getContentP("size")){ - amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", S.metadata.getContentP("audio")->getContentP("size")->NumValue(), AMF::AMF0_NUMBER)); + if (S.metadata["audio"].isMember("size")){ + amfdata.getContentP(1)->addContent(AMF::Object("audiosamplesize", S.metadata["audio"]["size"].asInt(), AMF::AMF0_NUMBER)); } - if (S.metadata.getContentP("audio")->getContentP("bps")){ - amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", ((double)S.metadata.getContentP("audio")->getContentP("bps")->NumValue() * 8.0) / 1024.0, AMF::AMF0_NUMBER)); + if (S.metadata["audio"].isMember("bps")){ + amfdata.getContentP(1)->addContent(AMF::Object("audiodatarate", ((double)S.metadata["audio"]["bps"].asInt() * 8.0) / 1024.0, AMF::AMF0_NUMBER)); } } @@ -627,79 +627,6 @@ bool FLV::Tag::MemLoader(char * D, unsigned int S, unsigned int & P){ }//Tag::MemLoader -/// Helper function for FLV::SockLoader. -/// This function will try to read count bytes from socket sock into buffer. -/// This function should be called repeatedly until true. -/// \param buffer The target buffer. -/// \param count Amount of bytes to read. -/// \param sofar Current amount read. -/// \param sock Socket to read from. -/// \return True if count bytes are read succesfully, false otherwise. -bool FLV::Tag::SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock){ - if (sofar >= count){return true;} - int r = 0; - r = sock.iread(buffer + sofar,count-sofar); - sofar += r; - if (sofar >= count){return true;} - return false; -}//Tag::SockReadUntil - -/// Try to load a tag from a socket. -/// This is a stateful function - if fed incorrect data, it will most likely never return true again! -/// While this function returns false, the Tag might not contain valid data. -/// \param sock The socket to read from. -/// \return True if a whole tag is succesfully read, false otherwise. -bool FLV::Tag::SockLoader(Socket::Connection sock){ - if (buf < 15){data = (char*)realloc(data, 15); buf = 15;} - if (done){ - if (SockReadUntil(data, 11, sofar, sock)){ - //if its a correct FLV header, throw away and read tag header - if (FLV::is_header(data)){ - if (SockReadUntil(data, 13, sofar, sock)){ - if (FLV::check_header(data)){ - sofar = 0; - memcpy(FLV::Header, data, 13); - }else{FLV::Parse_Error = true; Error_Str = "Invalid header received."; return false;} - } - }else{ - //if a tag header, calculate length and read tag body - len = data[3] + 15; - len += (data[2] << 8); - len += (data[1] << 16); - if (buf < len){data = (char*)realloc(data, len); buf = len;} - if (data[0] > 0x12){ - data[0] += 32; - FLV::Parse_Error = true; - Error_Str = "Invalid Tag received ("; - Error_Str += data[0]; - Error_Str += ")."; - return false; - } - done = false; - } - } - }else{ - //read tag body - if (SockReadUntil(data, len, sofar, sock)){ - //calculate keyframeness, next time read header again, return true - if ((data[0] == 0x09) && (((data[11] & 0xf0) >> 4) == 1)){isKeyframe = true;}else{isKeyframe = false;} - done = true; - sofar = 0; - return true; - } - } - return false; -}//Tag::SockLoader - -/// Try to load a tag from a socket. -/// This is a stateful function - if fed incorrect data, it will most likely never return true again! -/// While this function returns false, the Tag might not contain valid data. -/// \param sock The socket to read from. -/// \return True if a whole tag is succesfully read, false otherwise. -bool FLV::Tag::SockLoader(int sock){ - return SockLoader(Socket::Connection(sock)); -}//Tag::SockLoader - /// Helper function for FLV::FileLoader. /// This function will try to read count bytes from file f into buffer. /// This function should be called repeatedly until true. @@ -772,8 +699,8 @@ bool FLV::Tag::FileLoader(FILE * f){ return false; }//FLV_GetPacket -DTSC::DTMI FLV::Tag::toDTSC(DTSC::DTMI & metadata){ - DTSC::DTMI pack_out; // Storage for outgoing DTMI data. +JSON::Value FLV::Tag::toJSON(JSON::Value & metadata){ + JSON::Value pack_out; // Storage for outgoing metadata. if (data[0] == 0x12){ AMF::Object meta_in = AMF::parse((unsigned char*)data+11, len-15); @@ -781,45 +708,59 @@ DTSC::DTMI FLV::Tag::toDTSC(DTSC::DTMI & metadata){ AMF::Object * tmp = meta_in.getContentP(1); if (tmp->getContentP("videocodecid")){ switch ((unsigned int)tmp->getContentP("videocodecid")->NumValue()){ - case 2: Meta_Put(metadata, "video", "codec", "H263"); break; - case 4: Meta_Put(metadata, "video", "codec", "VP6"); break; - case 7: Meta_Put(metadata, "video", "codec", "H264"); break; - default: Meta_Put(metadata, "video", "codec", "?"); break; + case 2: metadata["video"]["codec"] = "H263"; break; + case 4: metadata["video"]["codec"] = "VP6"; break; + case 7: metadata["video"]["codec"] = "H264"; break; + default: metadata["video"]["codec"] = "?"; break; } } if (tmp->getContentP("audiocodecid")){ switch ((unsigned int)tmp->getContentP("audiocodecid")->NumValue()){ - case 2: Meta_Put(metadata, "audio", "codec", "MP3"); break; - case 10: Meta_Put(metadata, "audio", "codec", "AAC"); break; - default: Meta_Put(metadata, "audio", "codec", "?"); break; + case 2: metadata["audio"]["codec"] = "MP3"; break; + case 10: metadata["audio"]["codec"] = "AAC"; break; + default: metadata["audio"]["codec"] = "?"; break; } } if (tmp->getContentP("width")){ - Meta_Put(metadata, "video", "width", (unsigned long long int)tmp->getContentP("width")->NumValue()); + metadata["video"]["width"] = (long long int)tmp->getContentP("width")->NumValue(); } if (tmp->getContentP("height")){ - Meta_Put(metadata, "video", "height", (unsigned long long int)tmp->getContentP("height")->NumValue()); + metadata["video"]["height"] = (long long int)tmp->getContentP("height")->NumValue(); } if (tmp->getContentP("framerate")){ - Meta_Put(metadata, "video", "fpks", (unsigned long long int)tmp->getContentP("framerate")->NumValue()*1000); + metadata["video"]["fpks"] = (long long int)tmp->getContentP("framerate")->NumValue()*1000; } if (tmp->getContentP("videodatarate")){ - Meta_Put(metadata, "video", "bps", (unsigned long long int)(tmp->getContentP("videodatarate")->NumValue()*1024)/8); + metadata["video"]["bps"] = (long long int)(tmp->getContentP("videodatarate")->NumValue()*1024)/8; } if (tmp->getContentP("audiodatarate")){ - Meta_Put(metadata, "audio", "bps", (unsigned long long int)(tmp->getContentP("audiodatarate")->NumValue()*1024)/8); + metadata["audio"]["bps"] = (long long int)(tmp->getContentP("audiodatarate")->NumValue()*1024)/8; } if (tmp->getContentP("audiosamplerate")){ - Meta_Put(metadata, "audio", "rate", (unsigned long long int)tmp->getContentP("audiosamplerate")->NumValue()); + metadata["audio"]["rate"] = (long long int)tmp->getContentP("audiosamplerate")->NumValue(); } if (tmp->getContentP("audiosamplesize")){ - Meta_Put(metadata, "audio", "size", (unsigned long long int)tmp->getContentP("audiosamplesize")->NumValue()); + metadata["audio"]["size"] = (long long int)tmp->getContentP("audiosamplesize")->NumValue(); } if (tmp->getContentP("stereo")){ if (tmp->getContentP("stereo")->NumValue() == 1){ - Meta_Put(metadata, "audio", "channels", 2); + metadata["audio"]["channels"] = 2; }else{ - Meta_Put(metadata, "audio", "channels", 1); + metadata["audio"]["channels"] = 1; + } + } + } + if (!metadata.isMember("length")){metadata["length"] = 0;} + if (metadata.isMember("video")){ + if (!metadata["video"].isMember("width")){metadata["video"]["width"] = 0;} + if (!metadata["video"].isMember("height")){metadata["video"]["height"] = 0;} + if (!metadata["video"].isMember("fpks")){metadata["video"]["fpks"] = 0;} + if (!metadata["video"].isMember("bps")){metadata["video"]["bps"] = 0;} + if (!metadata["video"].isMember("keyms")){metadata["video"]["keyms"] = 0;} + if (!metadata["video"].isMember("keyvar")){metadata["video"]["keyvar"] = 0;} + if (!metadata["video"].isMember("keys")){ + while (metadata["video"]["keys"].size() < 100){ + metadata["video"]["keys"].append(JSON::Value((long long int)0)); } } } @@ -829,48 +770,47 @@ DTSC::DTMI FLV::Tag::toDTSC(DTSC::DTMI & metadata){ char audiodata = data[11]; if (needsInitData() && isInitData()){ if ((audiodata & 0xF0) == 0xA0){ - Meta_Put(metadata, "audio", "init", std::string((char*)data+13, (size_t)len-17)); + metadata["audio"]["init"] = std::string((char*)data+13, (size_t)len-17); }else{ - Meta_Put(metadata, "audio", "init", std::string((char*)data+12, (size_t)len-16)); + metadata["audio"]["init"] = std::string((char*)data+12, (size_t)len-16); } return pack_out;//skip rest of parsing, get next tag. } - pack_out = DTSC::DTMI("audio", DTSC::DTMI_ROOT); - pack_out.addContent(DTSC::DTMI("datatype", "audio")); - pack_out.addContent(DTSC::DTMI("time", tagTime())); - if (!Meta_Has(metadata, "audio", "codec")){ + pack_out["datatype"] = "audio"; + pack_out["time"] = tagTime(); + if (!metadata["audio"].isMember("codec") || metadata["audio"]["size"].asString() == ""){ switch (audiodata & 0xF0){ - case 0x20: Meta_Put(metadata, "audio", "codec", "MP3"); break; - case 0xA0: Meta_Put(metadata, "audio", "codec", "AAC"); break; - default: Meta_Put(metadata, "audio", "codec", "?"); break; + case 0x20: metadata["audio"]["codec"] = "MP3"; break; + case 0xA0: metadata["audio"]["codec"] = "AAC"; break; + default: metadata["audio"]["codec"] = "?"; break; } } - if (!Meta_Has(metadata, "audio", "rate")){ + if (!metadata["audio"].isMember("rate") || metadata["audio"]["rate"].asInt() < 1){ switch (audiodata & 0x0C){ - case 0x0: Meta_Put(metadata, "audio", "rate", 5512); break; - case 0x4: Meta_Put(metadata, "audio", "rate", 11025); break; - case 0x8: Meta_Put(metadata, "audio", "rate", 22050); break; - case 0xC: Meta_Put(metadata, "audio", "rate", 44100); break; + case 0x0: metadata["audio"]["rate"] = 5512; break; + case 0x4: metadata["audio"]["rate"] = 11025; break; + case 0x8: metadata["audio"]["rate"] = 22050; break; + case 0xC: metadata["audio"]["rate"] = 44100; break; } } - if (!Meta_Has(metadata, "audio", "size")){ + if (!metadata["audio"].isMember("size") || metadata["audio"]["size"].asInt() < 1){ switch (audiodata & 0x02){ - case 0x0: Meta_Put(metadata, "audio", "size", 8); break; - case 0x2: Meta_Put(metadata, "audio", "size", 16); break; + case 0x0: metadata["audio"]["size"] = 8; break; + case 0x2: metadata["audio"]["size"] = 16; break; } } - if (!Meta_Has(metadata, "audio", "channels")){ + if (!metadata["audio"].isMember("channels") || metadata["audio"]["channels"].asInt() < 1){ switch (audiodata & 0x01){ - case 0x0: Meta_Put(metadata, "audio", "channels", 1); break; - case 0x1: Meta_Put(metadata, "audio", "channels", 2); break; + case 0x0: metadata["audio"]["channels"] = 1; break; + case 0x1: metadata["audio"]["channels"] = 2; break; } } if ((audiodata & 0xF0) == 0xA0){ - if (len < 18){return DTSC::DTMI();} - pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+13, (size_t)len-17))); + if (len < 18){return JSON::Value();} + pack_out["data"] = std::string((char*)data+13, (size_t)len-17); }else{ - if (len < 17){return DTSC::DTMI();} - pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+12, (size_t)len-16))); + if (len < 17){return JSON::Value();} + pack_out["data"] = std::string((char*)data+12, (size_t)len-16); } return pack_out; } @@ -878,77 +818,46 @@ DTSC::DTMI FLV::Tag::toDTSC(DTSC::DTMI & metadata){ char videodata = data[11]; if (needsInitData() && isInitData()){ if ((videodata & 0x0F) == 7){ - if (len < 21){return DTSC::DTMI();} - Meta_Put(metadata, "video", "init", std::string((char*)data+16, (size_t)len-20)); + if (len < 21){return JSON::Value();} + metadata["video"]["init"] = std::string((char*)data+16, (size_t)len-20); }else{ - if (len < 17){return DTSC::DTMI();} - Meta_Put(metadata, "video", "init", std::string((char*)data+12, (size_t)len-16)); + if (len < 17){return JSON::Value();} + metadata["video"]["init"] = std::string((char*)data+12, (size_t)len-16); } return pack_out;//skip rest of parsing, get next tag. } - if (!Meta_Has(metadata, "video", "codec")){ + if (!metadata["video"].isMember("codec") || metadata["video"]["codec"].asString() == ""){ switch (videodata & 0x0F){ - case 2: Meta_Put(metadata, "video", "codec", "H263"); break; - case 4: Meta_Put(metadata, "video", "codec", "VP6"); break; - case 7: Meta_Put(metadata, "video", "codec", "H264"); break; - default: Meta_Put(metadata, "video", "codec", "?"); break; + case 2: metadata["video"]["codec"] = "H263"; break; + case 4: metadata["video"]["codec"] = "VP6"; break; + case 7: metadata["video"]["codec"] = "H264"; break; + default: metadata["video"]["codec"] = "?"; break; } } - pack_out = DTSC::DTMI("video", DTSC::DTMI_ROOT); - pack_out.addContent(DTSC::DTMI("datatype", "video")); + pack_out["datatype"] = "video"; switch (videodata & 0xF0){ - case 0x10: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; - case 0x20: pack_out.addContent(DTSC::DTMI("interframe", 1)); break; - case 0x30: pack_out.addContent(DTSC::DTMI("disposableframe", 1)); break; - case 0x40: pack_out.addContent(DTSC::DTMI("keyframe", 1)); break; - case 0x50: return DTSC::DTMI(); break;//the video info byte we just throw away - useless to us... + case 0x10: pack_out["keyframe"] = 1; break; + case 0x20: pack_out["interframe"] = 1; break; + case 0x30: pack_out["disposableframe"] = 1; break; + case 0x40: pack_out["keyframe"] = 1; break; + case 0x50: return JSON::Value(); break;//the video info byte we just throw away - useless to us... } - pack_out.addContent(DTSC::DTMI("time", tagTime())); + pack_out["time"] = tagTime(); if ((videodata & 0x0F) == 7){ switch (data[12]){ - case 1: pack_out.addContent(DTSC::DTMI("nalu", 1)); break; - case 2: pack_out.addContent(DTSC::DTMI("nalu_end", 1)); break; + case 1: pack_out["nalu"] = 1; break; + case 2: pack_out["nalu_end"] = 1; break; } int offset = (data[13] << 16) + (data[14] << 8) + data[15]; offset = (offset << 8) >> 8; - pack_out.addContent(DTSC::DTMI("offset", offset)); - if (len < 21){return DTSC::DTMI();} - pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+16, (size_t)len-20))); + pack_out["offset"] = offset; + if (len < 21){return JSON::Value();} + pack_out["data"] = std::string((char*)data+16, (size_t)len-20); }else{ - if (len < 17){return DTSC::DTMI();} - pack_out.addContent(DTSC::DTMI("data", std::string((char*)data+12, (size_t)len-16))); + if (len < 17){return JSON::Value();} + pack_out["data"] = std::string((char*)data+12, (size_t)len-16); } return pack_out; } return pack_out;//should never get here -}//FLV::Tag::toDTSC - -/// Inserts std::string type metadata into the passed DTMI object. -/// \arg meta The DTMI object to put the metadata into. -/// \arg cat Metadata category to insert into. -/// \arg elem Element name to put into the category. -/// \arg val Value to put into the element name. -void FLV::Tag::Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val){ - if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} - meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); -} - -/// Inserts uint64_t type metadata into the passed DTMI object. -/// \arg meta The DTMI object to put the metadata into. -/// \arg cat Metadata category to insert into. -/// \arg elem Element name to put into the category. -/// \arg val Value to put into the element name. -void FLV::Tag::Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val){ - if (meta.getContentP(cat) == 0){meta.addContent(DTSC::DTMI(cat));} - meta.getContentP(cat)->addContent(DTSC::DTMI(elem, val)); -} - -/// Returns true if the named category and elementname are available in the metadata. -/// \arg meta The DTMI object to check. -/// \arg cat Metadata category to check. -/// \arg elem Element name to check. -bool FLV::Tag::Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem){ - if (meta.getContentP(cat) == 0){return false;} - if (meta.getContentP(cat)->getContentP(elem) == 0){return false;} - return true; -} +}//FLV::Tag::toJSON diff --git a/lib/flv_tag.h b/lib/flv_tag.h index 6848995c..8de48eb5 100644 --- a/lib/flv_tag.h +++ b/lib/flv_tag.h @@ -4,6 +4,7 @@ #pragma once #include "socket.h" #include "dtsc.h" +#include "json.h" #include //forward declaration of RTMPStream::Chunk to avoid circular dependencies. @@ -43,10 +44,8 @@ namespace FLV { bool DTSCVideoInit(DTSC::Stream & S); bool DTSCAudioInit(DTSC::Stream & S); bool DTSCMetaInit(DTSC::Stream & S); - DTSC::DTMI toDTSC(DTSC::DTMI & metadata); + JSON::Value toJSON(JSON::Value & metadata); bool MemLoader(char * D, unsigned int S, unsigned int & P); - bool SockLoader(int sock); - bool SockLoader(Socket::Connection sock); bool FileLoader(FILE * f); protected: int buf; ///< Maximum length of buffer space. @@ -55,12 +54,11 @@ namespace FLV { void setLen(); //loader helper functions bool MemReadUntil(char * buffer, unsigned int count, unsigned int & sofar, char * D, unsigned int S, unsigned int & P); - bool SockReadUntil(char * buffer, unsigned int count, unsigned int & sofar, Socket::Connection & sock); bool FileReadUntil(char * buffer, unsigned int count, unsigned int & sofar, FILE * f); - //DTSC writer helpers - void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, std::string val); - void Meta_Put(DTSC::DTMI & meta, std::string cat, std::string elem, uint64_t val); - bool Meta_Has(DTSC::DTMI & meta, std::string cat, std::string elem); + //JSON writer helpers + void Meta_Put(JSON::Value & meta, std::string cat, std::string elem, std::string val); + void Meta_Put(JSON::Value & meta, std::string cat, std::string elem, uint64_t val); + bool Meta_Has(JSON::Value & meta, std::string cat, std::string elem); };//Tag };//FLV namespace diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index b132a9a1..2047be6d 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -30,7 +30,9 @@ std::string HTTP::Parser::BuildRequest(){ if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){protocol = "HTTP/1.0";} std::string tmp = method+" "+url+" "+protocol+"\n"; for (it=headers.begin(); it != headers.end(); it++){ - tmp += (*it).first + ": " + (*it).second + "\n"; + if ((*it).first != "" && (*it).second != ""){ + tmp += (*it).first + ": " + (*it).second + "\n"; + } } tmp += "\n" + body; return tmp; @@ -48,7 +50,11 @@ std::string HTTP::Parser::BuildResponse(std::string code, std::string message){ if (protocol.size() < 5 || protocol.substr(0, 4) != "HTTP"){protocol = "HTTP/1.0";} std::string tmp = protocol+" "+code+" "+message+"\n"; for (it=headers.begin(); it != headers.end(); it++){ - tmp += (*it).first + ": " + (*it).second + "\n"; + if ((*it).first != "" && (*it).second != ""){ + if ((*it).first != "Content-Length" || (*it).second != "0"){ + tmp += (*it).first + ": " + (*it).second + "\n"; + } + } } tmp += "\n"; tmp += body; @@ -144,6 +150,7 @@ bool HTTP::Parser::parse(std::string & HTTPbuffer){ protocol = tmpA; if (url.find('?') != std::string::npos){ parseVars(url.substr(url.find('?')+1)); //parse GET variables + url.resize(url.find('?')); } }else{seenReq = false;} }else{seenReq = false;} diff --git a/lib/json.cpp b/lib/json.cpp index 84a244ea..f5420d01 100644 --- a/lib/json.cpp +++ b/lib/json.cpp @@ -5,6 +5,8 @@ #include #include #include //for uint64_t +#include //for memcpy +#include //for htonl int JSON::Value::c2hex(int c){ if (c >= '0' && c <= '9') return c - '0'; @@ -345,6 +347,7 @@ JSON::Value & JSON::Value::operator[](unsigned int i){ /// Packs to a std::string for transfer over the network. /// If the object is a container type, this function will call itself recursively and contain all contents. +/// As a side effect, this function clear the internal buffer of any object-types. std::string JSON::Value::toPacked(){ std::string r; if (isInt() || isNull() || isBool()){ @@ -374,6 +377,7 @@ std::string JSON::Value::toPacked(){ } } r += (char)0x0; r += (char)0x0; r += (char)0xEE; + strVal.clear(); } if (isArray()){ r += 0x0A; @@ -386,6 +390,37 @@ std::string JSON::Value::toPacked(){ };//toPacked +/// Packs any object-type JSON::Value to a std::string for transfer over the network, including proper DTMI header. +/// Non-object-types will print an error to stderr and return an empty string. +/// This function returns a reference to an internal buffer where the prepared data is kept. +/// The internal buffer is *not* made stale if any changes occur inside the object - subsequent calls to toPacked() will clear the buffer. +std::string & JSON::Value::toNetPacked(){ + static std::string emptystring; + //check if this is legal + if (myType != OBJECT){ + fprintf(stderr, "Fatal error: Only objects may be NetPacked! Aborting.\n"); + return emptystring; + } + //if sneaky storage doesn't contain correct data, re-calculate it + if (strVal.size() == 0 || strVal[0] != 'D' || strVal[1] != 'T'){ + std::string packed = toPacked(); + strVal.resize(packed.size() + 8); + //insert proper header for this type of data + if (isMember("data")){ + memcpy((void*)strVal.c_str(), "DTPD", 4); + }else{ + memcpy((void*)strVal.c_str(), "DTSC", 4); + } + //insert the packet length at bytes 4-7 + unsigned int size = htonl(packed.size()); + memcpy((void*)(strVal.c_str() + 4), (void*)&size, 4); + //copy the rest of the string + memcpy((void*)(strVal.c_str() + 8), packed.c_str(), packed.size()); + } + return strVal; +} + + /// Converts this JSON::Value to valid JSON notation and returns it. /// Makes absolutely no attempts to pretty-print anything. :-) std::string JSON::Value::toString(){ @@ -445,7 +480,7 @@ std::string JSON::Value::toPrettyString(int indentation){ break; } case STRING: { - for (int i = 0; i < 5 && i < strVal.size(); ++i){ + for (unsigned int i = 0; i < 5 && i < strVal.size(); ++i){ if (strVal[i] < 32 || strVal[i] > 125){ return JSON::Value((long long int)strVal.size()).asString()+" bytes of binary data"; } diff --git a/lib/json.h b/lib/json.h index 00840ab8..fee192f2 100644 --- a/lib/json.h +++ b/lib/json.h @@ -6,6 +6,9 @@ #include #include +//empty definition of DTSC::Stream so it can be a friend. +namespace DTSC{class Stream;} + /// JSON-related classes and functions namespace JSON{ @@ -30,6 +33,8 @@ namespace JSON{ int c2hex(int c); static void skipToEnd(std::istream & fromstream); public: + //friends + friend class DTSC::Stream;//for access to strVal //constructors Value(); Value(std::istream & fromstream); @@ -60,6 +65,7 @@ namespace JSON{ Value & operator[](unsigned int i); //handy functions and others std::string toPacked(); + std::string & toNetPacked(); std::string toString(); std::string toPrettyString(int indentation = 0); void append(const Value & rhs); diff --git a/lib/mp4.cpp b/lib/mp4.cpp index 85ded767..c24c9e90 100644 --- a/lib/mp4.cpp +++ b/lib/mp4.cpp @@ -1,7 +1,8 @@ -#include "mp4.h" #include //for malloc and free #include //for memcpy #include //for htonl and friends +#include "mp4.h" +#include "json.h" /// Contains all MP4 format related code. namespace MP4{ @@ -95,6 +96,11 @@ void Box::ResetPayload( ) { ((unsigned int*)Payload)[0] = htonl(0); } +std::string Box::toPrettyString(int indent){ + return std::string(indent, ' ')+"Unimplemented pretty-printing for this box"; +} + + void ABST::SetBootstrapVersion( uint32_t Version ) { curBootstrapInfoVersion = Version; } @@ -254,6 +260,33 @@ void ABST::WriteContent( ) { SetPayload((uint32_t)4,Box::uint32_to_uint8(curBootstrapInfoVersion),4); } +std::string ABST::toPrettyString(int indent){ + std::string r; + r += std::string(indent, ' ')+"Bootstrap Info\n"; + if (isUpdate){ + r += std::string(indent+1, ' ')+"Update\n"; + }else{ + r += std::string(indent+1, ' ')+"Replacement or new table\n"; + } + if (isLive){ + r += std::string(indent+1, ' ')+"Live\n"; + }else{ + r += std::string(indent+1, ' ')+"Recorded\n"; + } + r += std::string(indent+1, ' ')+"Profile "+JSON::Value((long long int)curProfile).asString()+"\n"; + r += std::string(indent+1, ' ')+"Timescale "+JSON::Value((long long int)curTimeScale).asString()+"\n"; + r += std::string(indent+1, ' ')+"CurrMediaTime "+JSON::Value((long long int)curMediatime).asString()+"\n"; + r += std::string(indent+1, ' ')+"Segment Run Tables "+JSON::Value((long long int)SegmentRunTables.size()).asString()+"\n"; + for( uint32_t i = 0; i < SegmentRunTables.size(); i++ ) { + r += ((ASRT*)SegmentRunTables[i])->toPrettyString(indent+2)+"\n"; + } + r += std::string(indent+1, ' ')+"Fragment Run Tables "+JSON::Value((long long int)FragmentRunTables.size()).asString()+"\n"; + for( uint32_t i = 0; i < FragmentRunTables.size(); i++ ) { + r += ((AFRT*)FragmentRunTables[i])->toPrettyString(indent+2)+"\n"; + } + return r; +} + void AFRT::SetUpdate( bool Update ) { isUpdate = Update; } @@ -315,6 +348,26 @@ void AFRT::WriteContent( ) { SetPayload((uint32_t)4,Box::uint32_to_uint8((isUpdate ? 1 : 0))); } +std::string AFRT::toPrettyString(int indent){ + std::string r; + r += std::string(indent, ' ')+"Fragment Run Table\n"; + if (isUpdate){ + r += std::string(indent+1, ' ')+"Update\n"; + }else{ + r += std::string(indent+1, ' ')+"Replacement or new table\n"; + } + r += std::string(indent+1, ' ')+"Timescale "+JSON::Value((long long int)curTimeScale).asString()+"\n"; + r += std::string(indent+1, ' ')+"Qualities "+JSON::Value((long long int)QualitySegmentUrlModifiers.size()).asString()+"\n"; + for( uint32_t i = 0; i < QualitySegmentUrlModifiers.size(); i++ ) { + r += std::string(indent+2, ' ')+"\""+QualitySegmentUrlModifiers[i]+"\"\n"; + } + r += std::string(indent+1, ' ')+"Fragments "+JSON::Value((long long int)FragmentRunEntryTable.size()).asString()+"\n"; + for( uint32_t i = 0; i < FragmentRunEntryTable.size(); i ++ ) { + r += std::string(indent+2, ' ')+"Duration "+JSON::Value((long long int)FragmentRunEntryTable[i].FragmentDuration).asString()+", starting at "+JSON::Value((long long int)FragmentRunEntryTable[i].FirstFragment).asString()+" @ "+JSON::Value((long long int)FragmentRunEntryTable[i].FirstFragmentTimestamp).asString(); + } + return r; +} + void ASRT::SetUpdate( bool Update ) { isUpdate = Update; } @@ -363,40 +416,23 @@ void ASRT::WriteContent( ) { SetPayload((uint32_t)4,Box::uint32_to_uint8((isUpdate ? 1 : 0))); } -std::string GenerateLiveBootstrap( uint32_t CurMediaTime ) { - AFRT afrt; - afrt.SetUpdate(false); - afrt.SetTimeScale(1000); - afrt.AddQualityEntry(""); - afrt.AddFragmentRunEntry(1, 0 , 4000); //FirstFragment, FirstFragmentTimestamp,Fragment Duration in milliseconds - afrt.WriteContent(); - - ASRT asrt; - asrt.SetUpdate(false); - asrt.AddQualityEntry(""); - asrt.AddSegmentRunEntry(1, 199);//1 Segment, 199 Fragments - asrt.WriteContent(); - - ABST abst; - abst.AddFragmentRunTable(&afrt); - abst.AddSegmentRunTable(&asrt); - abst.SetBootstrapVersion(1); - abst.SetProfile(0); - abst.SetLive(true); - abst.SetUpdate(false); - abst.SetTimeScale(1000); - abst.SetMediaTime(0xFFFFFFFF); - abst.SetSMPTE(0); - abst.SetMovieIdentifier("fifa"); - abst.SetDRM(""); - abst.SetMetaData(""); - abst.AddServerEntry(""); - abst.AddQualityEntry(""); - abst.WriteContent(); - - std::string Result; - Result.append((char*)abst.GetBoxedData(), (int)abst.GetBoxedDataSize()); - return Result; +std::string ASRT::toPrettyString(int indent){ + std::string r; + r += std::string(indent, ' ')+"Segment Run Table\n"; + if (isUpdate){ + r += std::string(indent+1, ' ')+"Update\n"; + }else{ + r += std::string(indent+1, ' ')+"Replacement or new table\n"; + } + r += std::string(indent+1, ' ')+"Qualities "+JSON::Value((long long int)QualitySegmentUrlModifiers.size()).asString()+"\n"; + for( uint32_t i = 0; i < QualitySegmentUrlModifiers.size(); i++ ) { + r += std::string(indent+2, ' ')+"\""+QualitySegmentUrlModifiers[i]+"\"\n"; + } + r += std::string(indent+1, ' ')+"Segments "+JSON::Value((long long int)SegmentRunEntryTable.size()).asString()+"\n"; + for( uint32_t i = 0; i < SegmentRunEntryTable.size(); i ++ ) { + r += std::string(indent+2, ' ')+JSON::Value((long long int)SegmentRunEntryTable[i].FragmentsPerSegment).asString()+" fragments per, starting at "+JSON::Value((long long int)SegmentRunEntryTable[i].FirstSegment).asString(); + } + return r; } std::string mdatFold(std::string data){ diff --git a/lib/mp4.h b/lib/mp4.h index 0ef24659..d7354ad7 100644 --- a/lib/mp4.h +++ b/lib/mp4.h @@ -2,6 +2,7 @@ #include #include #include +#include "json.h" /// Contains all MP4 format related code. namespace MP4{ @@ -24,6 +25,7 @@ namespace MP4{ static uint8_t * uint16_to_uint8( uint16_t data ); static uint8_t * uint8_to_uint8( uint8_t data ); void ResetPayload( ); + std::string toPrettyString(int indent = 0); private: uint8_t * Payload; uint32_t PayloadSize; @@ -57,6 +59,7 @@ namespace MP4{ void AddFragmentRunTable( Box * newFragment, uint32_t Offset = 0 ); void SetVersion( bool NewVersion = 0 ); void WriteContent( ); + std::string toPrettyString(int indent = 0); private: void SetDefaults( ); void SetReserved( ); @@ -94,6 +97,7 @@ namespace MP4{ void AddQualityEntry( std::string Quality = "", uint32_t Offset = 0 ); void AddFragmentRunEntry( uint32_t FirstFragment = 0, uint32_t FirstFragmentTimestamp = 0, uint32_t FragmentsDuration = 1, uint8_t Discontinuity = 0, uint32_t Offset = 0 ); void WriteContent( ); + std::string toPrettyString(int indent = 0); private: void SetDefaults( ); bool isUpdate; @@ -116,6 +120,7 @@ namespace MP4{ void AddSegmentRunEntry( uint32_t FirstSegment = 0, uint32_t FragmentsPerSegment = 100, uint32_t Offset = 0 ); void WriteContent( ); void SetVersion( bool NewVersion = 0 ); + std::string toPrettyString(int indent = 0); private: void SetDefaults( ); bool isUpdate; @@ -125,7 +130,6 @@ namespace MP4{ Box * Container; };//ASRT Box - std::string GenerateLiveBootstrap( uint32_t CurMediaTime ); std::string mdatFold(std::string data); }; diff --git a/lib/procs.cpp b/lib/procs.cpp index 95ac9ad4..1e7c7afb 100644 --- a/lib/procs.cpp +++ b/lib/procs.cpp @@ -56,12 +56,12 @@ void Util::Procs::childsig_handler(int signum){ } #endif - TerminationNotifier tn = exitHandlers[ret]; - exitHandlers.erase(ret); - if (tn){ -#if DEBUG >= 2 + if (exitHandlers.count(ret) > 0){ + TerminationNotifier tn = exitHandlers[ret]; + exitHandlers.erase(ret); + #if DEBUG >= 2 std::cerr << "Calling termination handler for " << pname << std::endl; -#endif + #endif tn(ret, exitcode); } } diff --git a/lib/socket.cpp b/lib/socket.cpp index 1e4458f7..b3eb1f4b 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -160,55 +160,6 @@ Socket::Connection::Connection(std::string host, int port, bool nonblock){ } }//Socket::Connection TCP Contructor -/// Calls poll() on the socket, checking if data is available. -/// This function may return true even if there is no data, but never returns false when there is. -bool Socket::Connection::canRead(){ - struct pollfd PFD; - PFD.fd = sock; - PFD.events = POLLIN; - PFD.revents = 0; - poll(&PFD, 1, 5); - return (PFD.revents & POLLIN) == POLLIN; -} -/// Calls poll() on the socket, checking if data can be written. -bool Socket::Connection::canWrite(){ - struct pollfd PFD; - PFD.fd = sock; - PFD.events = POLLOUT; - PFD.revents = 0; - poll(&PFD, 1, 5); - return (PFD.revents & POLLOUT) == POLLOUT; -} - - -/// Returns the ready-state for this socket. -/// \returns 1 if data is waiting to be read, -1 if not connected, 0 otherwise. -signed int Socket::Connection::ready(){ - if (sock < 0) return -1; - char tmp; - int preflags = fcntl(sock, F_GETFL, 0); - int postflags = preflags | O_NONBLOCK; - fcntl(sock, F_SETFL, postflags); - int r = recv(sock, &tmp, 1, MSG_PEEK); - fcntl(sock, F_SETFL, preflags); - if (r < 0){ - if (errno == EAGAIN || errno == EWOULDBLOCK){ - return 0; - }else{ - #if DEBUG >= 2 - fprintf(stderr, "Socket ready error! Error: %s\n", strerror(errno)); - #endif - close(); - return -1; - } - } - if (r == 0){ - close(); - return -1; - } - return r; -} - /// Returns the connected-state for this socket. /// Note that this function might be slightly behind the real situation. /// The connection status is updated after every read/write attempt, when errors occur @@ -241,6 +192,17 @@ bool Socket::Connection::spool(){ return iread(downbuffer); } +/// Updates the downbuffer and upbuffer internal variables until upbuffer is empty. +/// Returns true if new data was received, false otherwise. +bool Socket::Connection::flush(){ + while (upbuffer.size() > 0 && connected()){ + iwrite(upbuffer); + usleep(5000);//sleep 5 ms + } + return iread(downbuffer); +} + + /// Returns a reference to the download buffer. std::string & Socket::Connection::Received(){ return downbuffer; @@ -251,81 +213,6 @@ void Socket::Connection::Send(std::string data){ upbuffer.append(data); } -/// Writes data to socket. This function blocks if the socket is blocking and all data cannot be written right away. -/// If the socket is nonblocking and not all data can be written, this function sets internal variable Blocking to true -/// and returns false. -/// \param buffer Location of the buffer to write from. -/// \param len Amount of bytes to write. -/// \returns True if the whole write was succesfull, false otherwise. -bool Socket::Connection::write(const void * buffer, int len){ - int sofar = 0; - if (sock < 0){return false;} - while (sofar != len){ - int r = send(sock, (char*)buffer + sofar, len-sofar, 0); - if (r <= 0){ - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not write data! Error: %s\n", strerror(errno)); - #endif - close(); - up += sofar; - return false; - }else{ - sofar += r; - } - } - up += sofar; - return true; -}//DDv::Socket::write - -/// Reads data from socket. This function blocks if the socket is blocking and all data cannot be read right away. -/// If the socket is nonblocking and not all data can be read, this function sets internal variable Blocking to true -/// and returns false. -/// \param buffer Location of the buffer to read to. -/// \param len Amount of bytes to read. -/// \returns True if the whole read was succesfull, false otherwise. -bool Socket::Connection::read(const void * buffer, int len){ - int sofar = 0; - if (sock < 0){return false;} - while (sofar != len){ - int r = recv(sock, (char*)buffer + sofar, len-sofar, 0); - if (r < 0){ - switch (errno){ - case EWOULDBLOCK: - down += sofar; - return 0; - break; - default: - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not read data! Error %i: %s\n", r, strerror(errno)); - #endif - close(); - down += sofar; - break; - } - return false; - }else{ - if (r == 0){ - Error = true; - close(); - down += sofar; - return false; - } - sofar += r; - } - } - down += sofar; - return true; -}//Socket::Connection::read - -/// Read call that is compatible with file access syntax. This function simply calls the other read function. -bool Socket::Connection::read(const void * buffer, int width, int count){return read(buffer, width*count);} -/// Write call that is compatible with file access syntax. This function simply calls the other write function. -bool Socket::Connection::write(const void * buffer, int width, int count){return write(buffer, width*count);} -/// Write call that is compatible with std::string. This function simply calls the other write function. -bool Socket::Connection::write(const std::string data){return write(data.c_str(), data.size());} - /// Incremental write call. This function tries to write len bytes to the socket from the buffer, /// returning the amount of bytes it actually wrote. /// \param buffer Location of the buffer to write from. @@ -336,12 +223,16 @@ int Socket::Connection::iwrite(const void * buffer, int len){ int r = send(sock, buffer, len, 0); if (r < 0){ switch (errno){ - case EWOULDBLOCK: return 0; break; + case EWOULDBLOCK: + return 0; + break; default: - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not iwrite data! Error: %s\n", strerror(errno)); - #endif + if (errno != EPIPE){ + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not iwrite data! Error: %s\n", strerror(errno)); + #endif + } close(); return 0; break; @@ -364,12 +255,16 @@ int Socket::Connection::iread(void * buffer, int len){ int r = recv(sock, buffer, len, 0); if (r < 0){ switch (errno){ - case EWOULDBLOCK: return 0; break; + case EWOULDBLOCK: + return 0; + break; default: - Error = true; - #if DEBUG >= 2 - fprintf(stderr, "Could not iread data! Error: %s\n", strerror(errno)); - #endif + if (errno != EPIPE){ + Error = true; + #if DEBUG >= 2 + fprintf(stderr, "Could not iread data! Error: %s\n", strerror(errno)); + #endif + } close(); return 0; break; @@ -382,23 +277,6 @@ int Socket::Connection::iread(void * buffer, int len){ return r; }//Socket::Connection::iread -/// Read call that is compatible with std::string. -/// Data is read using iread (which is nonblocking if the Socket::Connection itself is), -/// then appended to end of buffer. This functions reads at least one byte before returning. -/// \param buffer std::string to append data to. -/// \return True if new data arrived, false otherwise. -bool Socket::Connection::read(std::string & buffer){ - char cbuffer[5000]; - if (!read(cbuffer, 1)){return false;} - int num = iread(cbuffer+1, 4999); - if (num > 0){ - buffer.append(cbuffer, num+1); - }else{ - buffer.append(cbuffer, 1); - } - return true; -}//read - /// Read call that is compatible with std::string. /// Data is read using iread (which is nonblocking if the Socket::Connection itself is), /// then appended to end of buffer. @@ -425,23 +303,17 @@ bool Socket::Connection::iwrite(std::string & buffer){ return true; }//iwrite -/// Write call that is compatible with std::string. -/// Data is written using write (which is always blocking), -/// then removed from front of buffer. -/// \param buffer std::string to remove data from. -/// \return True if more data was sent, false otherwise. -bool Socket::Connection::swrite(std::string & buffer){ - if (buffer.size() < 1){return false;} - bool tmp = write((void*)buffer.c_str(), buffer.size()); - if (tmp){buffer = "";} - return tmp; -}//write - /// Gets hostname for connection, if available. std::string Socket::Connection::getHost(){ return remotehost; } +/// Sets hostname for connection manually. +/// Overwrites the detected host, thus possibily making it incorrect. +void Socket::Connection::setHost(std::string host){ + remotehost = host; +} + /// Returns true if these sockets are the same socket. /// Does not check the internal stats - only the socket itself. bool Socket::Connection::operator== (const Connection &B) const{ @@ -705,7 +577,7 @@ int Socket::Server::getSocket(){return sock;} /// converting all letters to lowercase. /// If a '?' character is found, everything following that character is deleted. Socket::Connection Socket::getStream(std::string streamname){ - //strip anything that isn't numbers, digits or underscores + //strip anything that isn't a number, alpha or underscore for (std::string::iterator i=streamname.end()-1; i>=streamname.begin(); --i){ if (*i == '?'){streamname.erase(i, streamname.end()); break;} if (!isalpha(*i) && !isdigit(*i) && *i != '_'){ diff --git a/lib/socket.h b/lib/socket.h index 0d33573b..db902c66 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -14,6 +14,8 @@ #include #include +//for being friendly with Socket::Connection down below +namespace Buffer{class user;}; ///Holds Socket tools. namespace Socket{ @@ -28,34 +30,32 @@ namespace Socket{ unsigned int conntime; std::string downbuffer; ///< Stores temporary data coming in. std::string upbuffer; ///< Stores temporary data going out. + int iread(void * buffer, int len); ///< Incremental read call. + int iwrite(const void * buffer, int len); ///< Incremental write call. + bool iread(std::string & buffer); ///< Incremental write call that is compatible with std::string. + bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string. public: + //friends + friend class Buffer::user; + //constructors Connection(); ///< Create a new disconnected base socket. Connection(int sockNo); ///< Create a new base socket. Connection(std::string hostname, int port, bool nonblock); ///< Create a new TCP socket. Connection(std::string adres, bool nonblock = false); ///< Create a new Unix Socket. - void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false). - bool canRead(); ///< Calls poll() on the socket, checking if data is available. - bool canWrite(); ///< Calls poll() on the socket, checking if data can be written. - signed int ready(); ///< Returns the ready-state for this socket. - bool connected() const; ///< Returns the connected-state for this socket. - bool read(const void * buffer, int len); ///< Reads data from socket. - bool read(const void * buffer, int width, int count); ///< Read call that is compatible with file access syntax. - bool write(const void * buffer, int len); ///< Writes data to socket. - bool write(const void * buffer, int width, int count); ///< Write call that is compatible with file access syntax. - bool write(const std::string data); ///< Write call that is compatible with std::string. - int iwrite(const void * buffer, int len); ///< Incremental write call. - int iread(void * buffer, int len); ///< Incremental read call. - bool read(std::string & buffer); ///< Read call that is compatible with std::string. - bool swrite(std::string & buffer); ///< Write call that is compatible with std::string. - bool iread(std::string & buffer); ///< Incremental write call that is compatible with std::string. - bool iwrite(std::string & buffer); ///< Write call that is compatible with std::string. - bool spool(); ///< Updates the downbuffer and upbuffer internal variables. - std::string & Received(); ///< Returns a reference to the download buffer. - void Send(std::string data); ///< Appends data to the upbuffer. + //generic methods void close(); ///< Close connection. + void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false). std::string getHost(); ///< Gets hostname for connection, if available. + void setHost(std::string host); ///< Sets hostname for connection manually. int getSocket(); ///< Returns internal socket number. std::string getError(); ///< Returns a string describing the last error that occured. + bool connected() const; ///< Returns the connected-state for this socket. + //buffered i/o methods + bool spool(); ///< Updates the downbuffer and upbuffer internal variables. + bool flush(); ///< Updates the downbuffer and upbuffer internal variables until upbuffer is empty. + std::string & Received(); ///< Returns a reference to the download buffer. + void Send(std::string data); ///< Appends data to the upbuffer. + //stats related methods unsigned int dataUp(); ///< Returns total amount of bytes sent. unsigned int dataDown(); ///< Returns total amount of bytes received. std::string getStats(std::string C); ///< Returns a std::string of stats, ended by a newline.