diff --git a/CMakeLists.txt b/CMakeLists.txt index 3162985f..cbf7be2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,6 +282,7 @@ makeInput(MP3 mp3) makeInput(FLV flv) makeInput(OGG ogg) makeInput(Buffer buffer) +makeInput(H264 h264) ######################################## # MistServer - Outputs # diff --git a/src/input/input_h264.cpp b/src/input/input_h264.cpp new file mode 100644 index 00000000..0068008d --- /dev/null +++ b/src/input/input_h264.cpp @@ -0,0 +1,127 @@ +#include "input_h264.h" +#include <mist/h264.h> +#include <mist/mp4_generic.h> + +namespace Mist{ + InputH264::InputH264(Util::Config *cfg) : Input(cfg){ + capa["name"] = "H264"; + capa["desc"] = "H264 Annex B input"; + capa["source_match"] = "h264-exec:*"; + //May be set to always-on mode + capa["always_match"].append("h264-exec:*"); + capa["priority"] = 0ll; + capa["codecs"][0u][0u].append("H264"); + frameCount = 0; + startTime = Util::bootMS(); + inputProcess = 0; + } + + int InputH264::run(){ + if (config->getString("input") != "-"){ + std::string input = config->getString("input"); + const char *argv[2]; + input = input.substr(10); + + char *args[128]; + uint8_t argCnt = 0; + char *startCh = 0; + for (char *i = (char*)input.c_str(); i <= input.data() + input.size(); ++i){ + if (!*i){ + if (startCh){args[argCnt++] = startCh;} + break; + } + if (*i == ' '){ + if (startCh){ + args[argCnt++] = startCh; + startCh = 0; + *i = 0; + } + }else{ + if (!startCh){startCh = i;} + } + } + args[argCnt] = 0; + + int fin = -1, fout = -1, ferr = -1; + inputProcess = Util::Procs::StartPiped(args, &fin, &fout, &ferr); + myConn = Socket::Connection(-1, fout); + }else{ + myConn = Socket::Connection(fileno(stdout), fileno(stdin)); + } + myConn.Received().splitter.assign("\000\000\001", 3); + myMeta.vod = false; + myMeta.live = true; + myMeta.tracks[1].type = "video"; + myMeta.tracks[1].codec = "H264"; + myMeta.tracks[1].trackID = 1; + waitsSinceData = 0; + return Input::run(); + } + + bool InputH264::setup(){ + std::string input = config->getString("input"); + if (input != "-" && input.substr(0, 10) != "h264-exec:"){ + FAIL_MSG("Unsupported input type: %s", input.c_str()); + return false; + } + return true; + } + + void InputH264::getNext(bool smart){ + do{ + if (!myConn.spool()){ + Util::sleep(25); + ++waitsSinceData; + if (waitsSinceData > 5000 / 25 && (waitsSinceData % 40) == 0){ + WARN_MSG("No H264 data received for > 5s, killing source process"); + Util::Procs::Stop(inputProcess); + } + continue; + } + waitsSinceData = 0; + uint32_t bytesToRead = myConn.Received().bytesToSplit(); + if (!bytesToRead){continue;} + std::string NAL = myConn.Received().remove(bytesToRead); + uint32_t nalSize = NAL.size() - 3; + while (nalSize && NAL.data()[nalSize - 1] == 0){--nalSize;} + if (!nalSize){continue;} + uint8_t nalType = NAL.data()[0] & 0x1F; + INSANE_MSG("NAL unit, type %u, size %lu", nalType, nalSize); + if (nalType == 7 || nalType == 8){ + if (nalType == 7){spsInfo = NAL.substr(0, nalSize);} + if (nalType == 8){ppsInfo = NAL.substr(0, nalSize);} + if (!myMeta.tracks[1].init.size() && spsInfo.size() && ppsInfo.size()){ + h264::sequenceParameterSet sps(spsInfo.data(), spsInfo.size()); + h264::SPSMeta spsChar = sps.getCharacteristics(); + myMeta.tracks[1].width = spsChar.width; + myMeta.tracks[1].height = spsChar.height; + myMeta.tracks[1].fpks = spsChar.fps * 1000; + if (myMeta.tracks[1].fpks < 100 || myMeta.tracks[1].fpks > 1000000){ + myMeta.tracks[1].fpks = 0; + } + MP4::AVCC avccBox; + avccBox.setVersion(1); + avccBox.setProfile(spsInfo[1]); + avccBox.setCompatibleProfiles(spsInfo[2]); + avccBox.setLevel(spsInfo[3]); + avccBox.setSPSNumber(1); + avccBox.setSPS(spsInfo); + avccBox.setPPSNumber(1); + avccBox.setPPS(ppsInfo); + myMeta.tracks[1].init = std::string(avccBox.payload(), avccBox.payloadSize()); + } + continue; + } + if (myMeta.tracks[1].init.size()){ + uint64_t ts = Util::bootMS() - startTime; + if (myMeta.tracks[1].fpks){ts = frameCount * (1000000 / myMeta.tracks[1].fpks);} + thisPacket.genericFill(ts, 0, 1, 0, 0, 0, h264::isKeyframe(NAL.data(), nalSize)); + thisPacket.appendNal(NAL.data(), nalSize, nalSize); + ++frameCount; + return; + } + }while (myConn && (inputProcess == 0 || Util::Procs::childRunning(inputProcess))); + if (inputProcess){myConn.close();} + } +} + diff --git a/src/input/input_h264.h b/src/input/input_h264.h new file mode 100644 index 00000000..7b2f7b3c --- /dev/null +++ b/src/input/input_h264.h @@ -0,0 +1,33 @@ +#include "input.h" +#include <mist/dtsc.h> +#include <mist/procs.h> + +namespace Mist{ + class InputH264 : public Input{ + public: + InputH264(Util::Config *cfg); + int run(); + + protected: + bool setup(); + void getNext(bool smart = true); + Socket::Connection myConn; + std::string ppsInfo; + std::string spsInfo; + uint64_t frameCount; + // Empty defaults + bool readHeader(){return true;} + bool openStreamSource(){return true;} + void closeStreamSource(){} + void parseStreamHeader(){} + void seek(int seekTime){} + void trackSelect(std::string trackSpec){} + bool needsLock(){return false;} + uint64_t startTime; + pid_t inputProcess; + uint32_t waitsSinceData; + }; +} + +typedef Mist::InputH264 mistIn; +