diff --git a/Makefile b/Makefile index d7528157..1df5176e 100644 --- a/Makefile +++ b/Makefile @@ -152,6 +152,12 @@ MistOutRaw: override CPPFLAGS += "-DOUTPUTTYPE=\"output_raw.h\"" MistOutRaw: src/output/mist_out.cpp src/output/output.cpp src/output/output_raw.cpp $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ +outputs: MistOutHTTPTS +MistOutHTTPTS: override LDLIBS += $(THREADLIB) +MistOutHTTPTS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_httpts.h\"" +MistOutHTTPTS: src/output/mist_out_http.cpp src/output/output.cpp src/output/output_httpts.cpp + $(CXX) $(LDFLAGS) $(CPPFLAGS) $^ $(LDLIBS) -o $@ + outputs: MistOutTS MistOutTS: override LDLIBS += $(THREADLIB) MistOutTS: override CPPFLAGS += "-DOUTPUTTYPE=\"output_ts.h\"" diff --git a/src/output/output.cpp b/src/output/output.cpp index 0379df29..c3c59c34 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -68,7 +68,6 @@ namespace Mist { if (streamIndex.mapped){ DTSC::Packet tmpMeta(streamIndex.mapped, streamIndex.len, true); if (tmpMeta.getVersion()){ - /// \todo Make sure this doesn't go wrong when overwritten by MistInBuffer during parse myMeta.reinit(tmpMeta); } } @@ -232,7 +231,7 @@ namespace Mist { return; } isInitialized = true; - streamIndex.init(streamName,8 * 1024 * 1024); + streamIndex.init(streamName, 8 * 1024 * 1024); if (!streamIndex.mapped){ DEBUG_MSG(DLVL_FAIL, "Could not connect to server for %s\n", streamName.c_str()); onFail(); diff --git a/src/output/output_httpts.cpp b/src/output/output_httpts.cpp new file mode 100644 index 00000000..427a7e4d --- /dev/null +++ b/src/output/output_httpts.cpp @@ -0,0 +1,168 @@ +#include "output_httpts.h" +#include +#include +#include +#include + +namespace Mist { + OutHTTPTS::OutHTTPTS(Socket::Connection & conn) : Output(conn) { + haveAvcc = false; + myConn.setHost(config->getString("ip")); + myConn.setBlocking(true); + streamName = config->getString("streamname"); + } + + OutHTTPTS::~OutHTTPTS() {} + + void OutHTTPTS::onFail(){ + HTTP_S.Clean(); //make sure no parts of old requests are left in any buffers + HTTP_S.SetBody("Stream not found. Sorry, we tried."); + HTTP_S.SendResponse("404", "Stream not found", myConn); + Output::onFail(); + } + + void OutHTTPTS::init(Util::Config * cfg){ + Output::init(cfg); + capa["name"] = "HTTPTS"; + capa["desc"] = "Enables HTTP protocol MPEG2/TS pseudostreaming."; + capa["deps"] = "HTTP"; + capa["url_rel"] = "/$.ts"; + capa["url_match"] = "/$.ts"; + capa["socket"] = "http_ts"; + capa["codecs"][0u][0u].append("H264"); + capa["codecs"][0u][1u].append("AAC"); + capa["codecs"][0u][1u].append("MP3"); + capa["methods"][0u]["handler"] = "http"; + capa["methods"][0u]["type"] = "html5/video/mp2t"; + capa["methods"][0u]["priority"] = 1ll; + cfg->addBasicConnectorOptions(capa); + config = cfg; + } + + ///this function generates the PMT packet + std::string OutHTTPTS::createPMT(){ + TS::ProgramMappingTable PMT; + PMT.PID(4096); + PMT.setTableId(2); + PMT.setSectionLength(0xB017); + PMT.setProgramNumber(1); + PMT.setVersionNumber(0); + PMT.setCurrentNextIndicator(0); + PMT.setSectionNumber(0); + PMT.setLastSectionNumber(0); + PMT.setPCRPID(0x100 + (*(selectedTracks.begin())) - 1); + PMT.setProgramInfoLength(0); + short id = 0; + //for all selected tracks + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); it++){ + if (myMeta.tracks[*it].codec == "H264"){ + PMT.setStreamType(0x1B,id); + }else if (myMeta.tracks[*it].codec == "AAC"){ + PMT.setStreamType(0x0F,id); + }else if (myMeta.tracks[*it].codec == "MP3"){ + PMT.setStreamType(0x03,id); + } + PMT.setElementaryPID(0x100 + (*it) - 1, id); + PMT.setESInfoLength(0,id); + id++; + } + PMT.calcCRC(); + return PMT.getStrBuf(); + } + + void OutHTTPTS::fillPacket(bool & first, const char * data, size_t dataLen, char & ContCounter){ + if (!PackData.BytesFree()){ + HTTP_S.Chunkify(PackData.ToString(), 188, myConn); + PacketNumber ++; + PackData.Clear(); + if (PacketNumber % 42 == 0){ + HTTP_S.Chunkify(TS::PAT, 188, myConn); + std::string PMT = createPMT(); + HTTP_S.Chunkify(PMT, myConn); + PacketNumber += 2; + } + } + if (!dataLen){return;} + if (PackData.BytesFree() == 184){ + PackData.PID(0x100 - 1 + currentPacket.getTrackId()); + PackData.ContinuityCounter(ContCounter++); + if (first){ + PackData.UnitStart(1); + if (currentPacket.getInt("keyframe")){ + PackData.RandomAccess(1); + PackData.PCR(currentPacket.getTime() * 27000); + } + first = false; + } + } + int tmp = PackData.FillFree(data, dataLen); + if (tmp != dataLen){ + fillPacket(first, data+tmp, dataLen-tmp, ContCounter); + } + } + + void OutHTTPTS::sendNext(){ + bool first = true; + char * ContCounter = 0; + char * dataPointer = 0; + unsigned int dataLen = 0; + currentPacket.getString("data", dataPointer, dataLen); //data + + std::string bs; + //prepare bufferstring + if (myMeta.tracks[currentPacket.getTrackId()].type == "video"){ + bs = TS::Packet::getPESVideoLeadIn(0ul, currentPacket.getTime() * 90); + fillPacket(first, bs.data(), bs.size(), VideoCounter); + + if (currentPacket.getInt("keyframe")){ + if (!haveAvcc){ + avccbox.setPayload(myMeta.tracks[currentPacket.getTrackId()].init); + haveAvcc = true; + } + bs = avccbox.asAnnexB(); + fillPacket(first, bs.data(), bs.size(), VideoCounter); + } + + unsigned int i = 0; + while (i + 4 < (unsigned int)dataLen){ + unsigned int ThisNaluSize = (dataPointer[i] << 24) + (dataPointer[i+1] << 16) + (dataPointer[i+2] << 8) + dataPointer[i+3]; + if (ThisNaluSize + i + 4 > (unsigned int)dataLen){ + DEBUG_MSG(DLVL_WARN, "Too big NALU detected (%u > %d) - skipping!", ThisNaluSize + i + 4, dataLen); + break; + } + fillPacket(first, "\000\000\000\001",4, VideoCounter); + fillPacket(first, dataPointer+i+4,ThisNaluSize, VideoCounter); + i += ThisNaluSize+4; + } + if (PackData.BytesFree() < 184){ + PackData.AddStuffing(); + fillPacket(first, 0, 0, VideoCounter); + } + }else if (myMeta.tracks[currentPacket.getTrackId()].type == "audio"){ + bs = TS::Packet::getPESAudioLeadIn(7+dataLen, currentPacket.getTime() * 90); + fillPacket(first, bs.data(), bs.size(), AudioCounter); + bs = TS::GetAudioHeader(dataLen, myMeta.tracks[currentPacket.getTrackId()].init); + fillPacket(first, bs.data(), bs.size(), AudioCounter); + ContCounter = &AudioCounter; + fillPacket(first, dataPointer,dataLen, AudioCounter); + if (PackData.BytesFree() < 184){ + PackData.AddStuffing(); + fillPacket(first, 0, 0, AudioCounter); + } + } + } + + void OutHTTPTS::onRequest(){ + while (HTTP_R.Read(myConn)){ + DEBUG_MSG(DLVL_MEDIUM, "Received request: %s", HTTP_R.getUrl().c_str()); + initialize(); + HTTP_S.Clean(); + HTTP_S.SetHeader("Content-Type", "video/mp2t"); + HTTP_S.StartResponse(HTTP_R, myConn); + PacketNumber = 0; + parseData = true; + wantRequest = false; + HTTP_R.Clean(); //clean for any possible next requests + } + } +} diff --git a/src/output/output_httpts.h b/src/output/output_httpts.h new file mode 100644 index 00000000..75530da9 --- /dev/null +++ b/src/output/output_httpts.h @@ -0,0 +1,37 @@ +#include "output.h" +#include +#include +#include +#include + +namespace Mist { + class OutHTTPTS : public Output { + public: + OutHTTPTS(Socket::Connection & conn); + ~OutHTTPTS(); + static void init(Util::Config * cfg); + void onRequest(); + void onFail(); + void sendNext(); + protected: + HTTP::Parser HTTP_S; + HTTP::Parser HTTP_R; + std::string createPMT(); + void fillPacket(bool & first, const char * data, size_t dataLen, char & ContCounter); + int keysToSend; + long long int playUntil; + TS::Packet PackData; + unsigned int PacketNumber; + bool haveAvcc; + char VideoCounter; + char AudioCounter; + MP4::AVCC avccbox; + bool AppleCompat; + long long unsigned int lastVid; + long long unsigned int until; + unsigned int vidTrack; + unsigned int audTrack; + }; +} + +typedef Mist::OutHTTPTS mistOut;