diff --git a/src/analysers/Makefile.am b/src/analysers/Makefile.am index 4f31f0e9..0a7c1f7e 100644 --- a/src/analysers/Makefile.am +++ b/src/analysers/Makefile.am @@ -1,7 +1,8 @@ AM_CPPFLAGS = $(global_CFLAGS) $(MIST_CFLAGS) LDADD = $(MIST_LIBS) -bin_PROGRAMS=MistAnalyserRTMP MistAnalyserFLV MistAnalyserDTSC MistAnalyserAMF +bin_PROGRAMS=MistAnalyserRTMP MistAnalyserFLV MistAnalyserDTSC MistAnalyserAMF MistAnalyserMP4 MistAnalyserRTMP_SOURCES=rtmp_analyser.cpp MistAnalyserFLV_SOURCES=flv_analyser.cpp MistAnalyserDTSC_SOURCES=dtsc_analyser.cpp MistAnalyserAMF_SOURCES=amf_analyser.cpp +MistAnalyserMP4_SOURCES=mp4_analyser.cpp diff --git a/src/analysers/dtsc_analyser.cpp b/src/analysers/dtsc_analyser.cpp index c355586c..a0f5a21a 100644 --- a/src/analysers/dtsc_analyser.cpp +++ b/src/analysers/dtsc_analyser.cpp @@ -13,8 +13,7 @@ int main(int argc, char ** argv){ conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"arg\":\"string\", \"help\":\"Filename of the DTSC file to analyse.\"}")); conf.parseArgs(argc, argv); DTSC::File F(conf.getString("filename")); - std::string loader = F.getHeader(); - JSON::Value meta = JSON::fromDTMI(loader); + JSON::Value meta = F.getMeta(); std::cout << meta.toPrettyString() << std::endl; JSON::Value pack; diff --git a/src/analysers/mp4_analyser.cpp b/src/analysers/mp4_analyser.cpp new file mode 100644 index 00000000..19ad4292 --- /dev/null +++ b/src/analysers/mp4_analyser.cpp @@ -0,0 +1,29 @@ +/// \file mp4_analyser.cpp +/// Debugging tool for MP4 data. +/// Expects MP4 data through stdin, outputs human-readable information to stderr. + +#include +#include +#include +#include +#include +#include +#include + +/// Debugging tool for MP4 data. +/// Expects MP4 data through stdin, outputs human-readable information to stderr. +int main(int argc, char ** argv) { + Util::Config conf = Util::Config(argv[0], PACKAGE_VERSION); + conf.parseArgs(argc, argv); + + std::string temp; + while (std::cin.good()){temp += std::cin.get();}//read all of std::cin to temp + temp.erase(temp.size()-1, 1);//strip the invalid last character + + MP4::Box mp4data; + while (mp4data.read(temp)){ + std::cerr << mp4data.toPrettyString(0) << std::endl; + } + return 0; +} + diff --git a/src/conn_http_dynamic.cpp b/src/conn_http_dynamic.cpp index d711a7a7..7edddbc5 100644 --- a/src/conn_http_dynamic.cpp +++ b/src/conn_http_dynamic.cpp @@ -26,64 +26,94 @@ /// Holds everything unique to HTTP Dynamic Connector. namespace Connector_HTTP{ - std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime){ - MP4::AFRT afrt; - if (starttime == 0){ - afrt.SetUpdate(false); - }else{ - afrt.SetUpdate(true); - } - afrt.SetTimeScale(1000); - afrt.AddQualityEntry(""); - if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){ - //metadata["lasttime"].asInt()? - afrt.AddFragmentRunEntry(fragnum, starttime, 2000); //FirstFragment, FirstFragmentTimestamp,Fragment Duration in milliseconds - }else{ - afrt.AddFragmentRunEntry(fragnum, starttime, metadata["video"]["keyms"].asInt()); //FirstFragment, FirstFragmentTimestamp,Fragment Duration in milliseconds - } - afrt.WriteContent(); - + std::string GenerateBootstrap(std::string & MovieId, JSON::Value & metadata, int fragnum, int starttime, int endtime){ + std::string empty; + MP4::ASRT asrt; if (starttime == 0){ - asrt.SetUpdate(false); + asrt.setUpdate(false); }else{ - asrt.SetUpdate(true); + asrt.setUpdate(true); + } + asrt.setVersion(1); + asrt.setQualityEntry(empty, 0); + if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){ + asrt.setSegmentRun(1, 20000, 0); + }else{ + asrt.setSegmentRun(1, metadata["keytime"].size(), 0); + } + + + MP4::AFRT afrt; + if (starttime == 0){ + afrt.setUpdate(false); + }else{ + afrt.setUpdate(true); + } + afrt.setVersion(1); + afrt.setTimeScale(1000); + afrt.setQualityEntry(empty, 0); + MP4::afrt_runtable afrtrun; + if (!metadata.isMember("keytime") || metadata["keytime"].size() == 0){ + afrtrun.firstFragment = 1; + afrtrun.firstTimestamp = 0; + if (!metadata.isMember("video") || !metadata["video"].isMember("keyms") || metadata["video"]["keyms"].asInt() == 0){ + afrtrun.duration = 2000; + }else{ + afrtrun.duration = metadata["video"]["keyms"].asInt(); + } + afrt.setFragmentRun(afrtrun, 0); + }else{ + for (int i = 0; i < metadata["keytime"].size(); i++){ + afrtrun.firstFragment = i+1; + afrtrun.firstTimestamp = metadata["keytime"][i].asInt(); + if (i+1 < metadata["keytime"].size()){ + afrtrun.duration = metadata["keytime"][i+1].asInt() - metadata["keytime"][i].asInt(); + }else{ + if (metadata["lastms"].asInt()){ + afrtrun.duration = metadata["lastms"].asInt() - metadata["keytime"][i].asInt(); + }else{ + afrtrun.duration = 3000;//guess 3 seconds if unknown + } + } + afrt.setFragmentRun(afrtrun, i); + } } - asrt.AddQualityEntry(""); - /// \todo Actually use correct number of fragments. - asrt.AddSegmentRunEntry(1, 20000);//1 Segment, 20000 Fragments - asrt.WriteContent(); MP4::ABST abst; - abst.AddFragmentRunTable(&afrt); - abst.AddSegmentRunTable(&asrt); - abst.SetBootstrapVersion(1); - abst.SetProfile(0); - if (metadata.isMember("length") && metadata["length"].asInt() > 0){ - abst.SetLive(false); - abst.SetMediaTime(1000*metadata["length"].asInt()); - }else{ - abst.SetLive(true); - abst.SetMediaTime(0xFFFFFFFF);//metadata["lasttime"].asInt()? - } + abst.setVersion(1); + abst.setBootstrapinfoVersion(1); + abst.setProfile(0); if (starttime == 0){ - abst.SetUpdate(false); + abst.setUpdate(false); }else{ - abst.SetUpdate(true); + abst.setUpdate(true); } - abst.SetTimeScale(1000); - abst.SetSMPTE(0); - abst.SetMovieIdentifier(MovieId); - abst.SetDRM(""); - abst.SetMetaData(""); - abst.AddServerEntry(""); - abst.AddQualityEntry(""); - abst.WriteContent(); + abst.setTimeScale(1000); + if (metadata.isMember("length") && metadata["length"].asInt() > 0){ + abst.setLive(false); + if (metadata["lastms"].asInt()){ + abst.setCurrentMediaTime(metadata["lastms"].asInt()); + }else{ + abst.setCurrentMediaTime(1000*metadata["length"].asInt()); + } + }else{ + abst.setLive(true); + abst.setCurrentMediaTime(0xFFFFFFFF); + } + abst.setSmpteTimeCodeOffset(0); + abst.setMovieIdentifier(MovieId); + abst.setServerEntry(empty, 0); + abst.setQualityEntry(empty, 0); + abst.setDrmData(empty); + abst.setMetaData(empty); + abst.setSegmentRunTable(asrt, 0); + abst.setFragmentRunTable(afrt, 0); //#if DEBUG >= 8 std::cout << "Sending bootstrap:" << std::endl << abst.toPrettyString(0) << std::endl; //#endif - return std::string((char*)abst.GetBoxedData(), (int)abst.GetBoxedDataSize()); + return std::string((char*)abst.asBox(), (int)abst.boxedSize()); } @@ -91,18 +121,19 @@ namespace Connector_HTTP{ std::string BuildManifest(std::string & MovieId, JSON::Value & metadata){ std::string Result; if (metadata.isMember("length") && metadata["length"].asInt() > 0){ - std::stringstream st; - st << ((double)metadata["video"]["keyms"].asInt() / 1000); Result="\n" "\n" "" + MovieId + "\n" - "" + metadata["length"].asString() + "\n" + "" + metadata["video"]["width"].asString() + "\n" + "" + metadata["video"]["height"].asString() + "\n" + "" + metadata["length"].asString() + ".000\n" "video/mp4\n" "recorded\n" "streaming\n" - "\n" - "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0)) + "\n" - "\n" + "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "\n" + "\n" + "AgAKb25NZXRhRGF0YQgAAAAAAAl0cmFja2luZm8KAAAAAgMACXRpbWVzY2FsZQBA+GoAAAAAAAAGbGVuZ3RoAEGMcHoQAAAAAAhsYW5ndWFnZQIAA2VuZwARc2FtcGxlZGVzY3JpcHRpb24KAAAAAQMACnNhbXBsZXR5cGUCAARhdmMxAAAJAAAJAwAJdGltZXNjYWxlAEDncAAAAAAAAAZsZW5ndGgAQXtNvTAAAAAACGxhbmd1YWdlAgADZW5nABFzYW1wbGVkZXNjcmlwdGlvbgoAAAABAwAKc2FtcGxldHlwZQIABG1wNGEAAAkAAAkADWF1ZGlvY2hhbm5lbHMAQAAAAAAAAAAAD2F1ZGlvc2FtcGxlcmF0ZQBA53AAAAAAAAAOdmlkZW9mcmFtZXJhdGUAQDf/gi5SciUABmFhY2FvdABAAAAAAAAAAAAIYXZjbGV2ZWwAQD8AAAAAAAAACmF2Y3Byb2ZpbGUAQFNAAAAAAAAADGF1ZGlvY29kZWNpZAIABG1wNGEADHZpZGVvY29kZWNpZAIABGF2YzEABXdpZHRoAECQ4AAAAAAAAAZoZWlnaHQAQIMAAAAAAAAACmZyYW1lV2lkdGgAQJDgAAAAAAAAC2ZyYW1lSGVpZ2h0AECDAAAAAAAAAAxkaXNwbGF5V2lkdGgAQJDgAAAAAAAADWRpc3BsYXlIZWlnaHQAQIMAAAAAAAAADG1vb3Zwb3NpdGlvbgBBmxq2uAAAAAAIZHVyYXRpb24AQIKjqW3oyhIAAAk=\n" + "\n" "\n"; }else{ Result="\n" @@ -111,7 +142,7 @@ namespace Connector_HTTP{ "video/mp4\n" "live\n" "streaming\n" - "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0)) + "\n" + "" + Base64::encode(GenerateBootstrap(MovieId, metadata, 1, 0, 0)) + "\n" "\n" "\n"; } @@ -273,26 +304,26 @@ namespace Connector_HTTP{ #if DEBUG >= 3 fprintf(stderr, "Sending a fragment..."); #endif - static std::string btstrp; - btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime); + //static std::string btstrp; + //btstrp = GenerateBootstrap(streamname, Strm.metadata, ReqFragment, FlashBufTime, Strm.getPacket(0)["time"]); HTTP_S.Clean(); HTTP_S.SetHeader("Content-Type", "video/mp4"); HTTP_S.SetBody(""); - HTTP_S.SetHeader("Content-Length", FlashBufSize+32+33+btstrp.size()); + HTTP_S.SetHeader("Content-Length", FlashBufSize+8);//32+33+btstrp.size()); conn.SendNow(HTTP_S.BuildResponse("200", "OK")); - conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21); - unsigned long tmptime = htonl(FlashBufTime << 32); - conn.SendNow((char*)&tmptime, 4); - tmptime = htonl(FlashBufTime & 0xFFFFFFFF); - conn.SendNow((char*)&tmptime, 4); - tmptime = htonl(65); - conn.SendNow((char*)&tmptime, 4); + //conn.SendNow("\x00\x00\x00\x21" "afra\x00\x00\x00\x00\x00\x00\x00\x03\xE8\x00\x00\x00\x01", 21); + //unsigned long tmptime = htonl(FlashBufTime << 32); + //conn.SendNow((char*)&tmptime, 4); + //tmptime = htonl(FlashBufTime & 0xFFFFFFFF); + //conn.SendNow((char*)&tmptime, 4); + //tmptime = htonl(65); + //conn.SendNow((char*)&tmptime, 4); - conn.SendNow(btstrp); + //conn.SendNow(btstrp); - conn.SendNow("\x00\x00\x00\x18moof\x00\x00\x00\x10mfhd\x00\x00\x00\x00", 20); - unsigned long fragno = htonl(ReqFragment); - conn.SendNow((char*)&fragno, 4); + //conn.SendNow("\x00\x00\x00\x18moof\x00\x00\x00\x10mfhd\x00\x00\x00\x00", 20); + //unsigned long fragno = htonl(ReqFragment); + //conn.SendNow((char*)&fragno, 4); unsigned long size = htonl(FlashBufSize+8); conn.SendNow((char*)&size, 4); conn.SendNow("mdat", 4); @@ -313,11 +344,13 @@ namespace Connector_HTTP{ //fill buffer with init data, if needed. if (Strm.metadata.isMember("audio") && Strm.metadata["audio"].isMember("init")){ tmp.DTSCAudioInit(Strm); + tmp.tagTime(Strm.getPacket(0)["time"].asInt()); FlashBuf.push_back(std::string(tmp.data, tmp.len)); FlashBufSize += tmp.len; } if (Strm.metadata.isMember("video") && Strm.metadata["video"].isMember("init")){ tmp.DTSCVideoInit(Strm); + tmp.tagTime(Strm.getPacket(0)["time"].asInt()); FlashBuf.push_back(std::string(tmp.data, tmp.len)); FlashBufSize += tmp.len; } diff --git a/src/controller.cpp b/src/controller.cpp index c6ebbfc6..fc3213b7 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -215,7 +215,6 @@ void CheckConfig(JSON::Value & in, JSON::Value & out){ } } out = in; - out["version"] = PACKAGE_VERSION; } bool streamsEqual(JSON::Value & one, JSON::Value & two){ @@ -558,6 +557,7 @@ int main(int argc, char ** argv){ //sent current configuration, no matter if it was changed or not //Response["streams"] = Storage["streams"]; Response["config"] = Connector::Storage["config"]; + Response["config"]["version"] = PACKAGE_VERSION "/" + Util::Config::libver; Response["streams"] = Connector::Storage["streams"]; //add required data to the current unix time to the config, for syncing reasons Response["config"]["time"] = Util::epoch(); diff --git a/src/converters/dtscfix.cpp b/src/converters/dtscfix.cpp index fabddc75..29c6c49d 100644 --- a/src/converters/dtscfix.cpp +++ b/src/converters/dtscfix.cpp @@ -12,10 +12,21 @@ namespace Converters{ /// Reads an DTSC file and attempts to fix the metadata in it. int DTSCFix(Util::Config & conf) { DTSC::File F(conf.getString("filename")); - std::string loader = F.getHeader(); - JSON::Value meta = JSON::fromDTMI(loader); + JSON::Value oriheader = F.getMeta(); + JSON::Value meta = oriheader; JSON::Value pack; + if (!oriheader.isMember("moreheader")){ + std::cerr << "This file is not DTSCFix'able. Please re-convert and try again." << std::endl; + return 1; + } + if (oriheader["moreheader"].asInt() > 0){ + std::cerr << "Warning: This file has already been DTSCFix'ed. Doing this multiple times makes the file larger for no reason." << std::endl; + } + meta.removeMember("keytime"); + meta.removeMember("keybpos"); + meta.removeMember("moreheader"); + long long unsigned int firstpack = 0; long long unsigned int nowpack = 0; long long unsigned int lastaudio = 0; @@ -54,6 +65,8 @@ namespace Converters{ if (bps > vid_max){vid_max = bps;} } if (F.getJSON()["keyframe"].asInt() != 0){ + meta["keytime"].append(F.getJSON()["time"]); + meta["keybpos"].append(F.getLastReadPos()); if (lastkey != 0){ bps = nowpack - lastkey; if (bps < key_min){key_min = bps;} @@ -73,12 +86,10 @@ namespace Converters{ F.seekNext(); } - std::cout << std::endl << "Summary:" << std::endl; meta["length"] = (long long int)((nowpack - firstpack)/1000); + meta["lastms"] = (long long int)nowpack; if (meta.isMember("audio")){ meta["audio"]["bps"] = (long long int)(totalaudio / ((lastaudio - firstpack) / 1000)); - std::cout << " Audio: " << meta["audio"]["codec"].asString() << std::endl; - std::cout << " Bitrate: " << meta["audio"]["bps"].asInt() << std::endl; } if (meta.isMember("video")){ meta["video"]["bps"] = (long long int)(totalvideo / ((lastvideo - firstpack) / 1000)); @@ -88,16 +99,20 @@ namespace Converters{ }else{ meta["video"]["keyvar"] = (long long int)(key_max - meta["video"]["keyms"].asInt()); } - std::cout << " Video: " << meta["video"]["codec"].asString() << std::endl; - std::cout << " Bitrate: " << meta["video"]["bps"].asInt() << std::endl; - std::cout << " Keyframes: " << meta["video"]["keyms"].asInt() << "~" << meta["video"]["keyvar"].asInt() << std::endl; - std::cout << " B-frames: " << bfrm_min << " - " << bfrm_max << std::endl; } + std::cerr << "Appending new header..." << std::endl; + std::string loader = meta.toPacked(); + long long int newHPos = F.addHeader(loader); + if (!newHPos){ + std::cerr << "Failure appending new header. Cancelling." << std::endl; + return 1; + } std::cerr << "Re-writing header..." << std::endl; - - loader = meta.toPacked(); + oriheader["moreheader"] = newHPos; + loader = oriheader.toPacked(); if (F.writeHeader(loader)){ + std::cerr << "Metadata is now: " << meta.toPrettyString(0) << std::endl; return 0; }else{ return -1; diff --git a/src/converters/flv2dtsc.cpp b/src/converters/flv2dtsc.cpp index d8d7f04e..efea24fe 100644 --- a/src/converters/flv2dtsc.cpp +++ b/src/converters/flv2dtsc.cpp @@ -36,6 +36,7 @@ namespace Converters{ counter++; if (counter > 8){ sending = true; + meta_out["moreheader"] = 0LL; std::string packed_header = meta_out.toPacked(); unsigned int size = htonl(packed_header.size()); std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header; @@ -59,12 +60,13 @@ namespace Converters{ // if the FLV input is very short, do output it correctly... if (!sending){ std::cerr << "EOF - outputting buffer..." << std::endl; + meta_out["moreheader"] = 0LL; std::string packed_header = meta_out.toPacked(); unsigned int size = htonl(packed_header.size()); std::cout << std::string(DTSC::Magic_Header, 4) << std::string((char*)&size, 4) << packed_header; std::cout << prebuffer.rdbuf(); } - std::cerr << "Done!" << std::endl; + std::cerr << "Done! If you output this data to a file, don't forget to run MistDTSCFix next." << std::endl; return 0; }//FLV2DTSC diff --git a/src/player.cpp b/src/player.cpp index 12e1f5e7..260b42c1 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -69,7 +69,7 @@ int main(int argc, char** argv){ DTSC::File source = DTSC::File(conf.getString("filename")); Socket::Connection in_out = Socket::Connection(fileno(stdout), fileno(stdin)); - std::string meta_str = source.getHeader(); + JSON::Value meta = source.getMeta(); JSON::Value pausemark; pausemark["datatype"] = "pause_marker"; pausemark["time"] = (long long int)0; @@ -78,14 +78,9 @@ int main(int argc, char** argv){ int lasttime = Util::epoch();//time last packet was sent //send the header - { - in_out.Send("DTSC"); - unsigned int size = htonl(meta_str.size()); - in_out.Send((char*)&size, 4); - in_out.Send(meta_str); - } + std::string meta_str = meta.toNetPacked(); + in_out.Send(meta_str); - JSON::Value meta = JSON::fromDTMI(meta_str); if (meta["video"]["keyms"].asInt() < 11){ meta["video"]["keyms"] = (long long int)1000; } @@ -132,6 +127,10 @@ int main(int argc, char** argv){ json_sts["vod"]["start"] = Util::epoch() - sts.conntime; if (!meta_sent){ json_sts["vod"]["meta"] = meta; + json_sts["vod"]["meta"]["audio"].removeMember("init"); + json_sts["vod"]["meta"]["video"].removeMember("init"); + json_sts["vod"]["meta"].removeMember("keytime"); + json_sts["vod"]["meta"].removeMember("keybpos"); meta_sent = true; } StatsSocket.Send(json_sts.toString().c_str()); @@ -181,17 +180,16 @@ int main(int argc, char** argv){ } lastTime = now; if (playing > 0){--playing;} - if (playing == 0){ - #if DEBUG >= 4 - std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl; - #endif - pausemark["time"] = (long long int)now; - pausemark.toPacked(); - in_out.SendNow(pausemark.toNetPacked()); - in_out.setBlocking(true); - } } - if (playing != 0){ + if (playing == 0){ + #if DEBUG >= 4 + std::cerr << "Sending pause_marker (" << (Util::getMS() - bench) << "ms)" << std::endl; + #endif + pausemark["time"] = source.getJSON()["time"]; + pausemark.toPacked(); + in_out.SendNow(pausemark.toNetPacked()); + in_out.setBlocking(true); + }else{ lasttime = Util::epoch(); //insert proper header for this type of data in_out.Send("DTPD");