#include #include #include #include #include #include #include #include #include #include #include //for stat #include //for stat #include //for stat #include "input_flv.h" namespace Mist{ InputFLV::InputFLV(Util::Config *cfg) : Input(cfg){ capa["name"] = "FLV"; capa["desc"] = "Allows loading FLV files for Video on Demand."; capa["source_match"] = "/*.flv"; capa["source_file"] = "$source"; capa["priority"] = 9; capa["codecs"]["video"].append("H264"); capa["codecs"]["video"].append("H263"); capa["codecs"]["video"].append("VP6"); capa["codecs"]["audio"].append("AAC"); capa["codecs"]["audio"].append("MP3"); } InputFLV::~InputFLV(){} bool InputFLV::checkArguments(){ if (config->getString("input") == "-"){ Util::logExitReason(ER_FORMAT_SPECIFIC, "Input from stdin not yet supported"); return false; } if (!config->getString("streamname").size()){ if (config->getString("output") == "-"){ Util::logExitReason(ER_FORMAT_SPECIFIC, "Output to stdout not yet supported"); return false; } }else{ if (config->getString("output") != "-"){ Util::logExitReason(ER_FORMAT_SPECIFIC, "File output in player mode not supported"); return false; } } return true; } bool InputFLV::preRun(){ // open File inFile = fopen(config->getString("input").c_str(), "r"); if (!inFile){ Util::logExitReason(ER_READ_START_FAILURE, "Opening input '%s' failed", config->getString("input").c_str()); return false; } struct stat statData; lastModTime = 0; if (stat(config->getString("input").c_str(), &statData) != -1){ lastModTime = statData.st_mtime; } return true; } /// Overrides the default keepRunning function to shut down /// if the file disappears or changes, by polling the file's mtime. /// If neither applies, calls the original function. bool InputFLV::keepRunning(){ struct stat statData; if (stat(config->getString("input").c_str(), &statData) == -1){ INFO_MSG("Shutting down because input file disappeared"); return false; } if (lastModTime != statData.st_mtime){ INFO_MSG("Shutting down because input file changed"); return false; } return Input::keepRunning(); } bool InputFLV::readHeader(){ if (!inFile){ Util::logExitReason(ER_READ_START_FAILURE, "Reading header for '%s' failed: Could not open input stream", config->getString("input").c_str()); return false; } meta.reInit(isSingular() ? streamName : ""); // Create header file from FLV data Util::fseek(inFile, 13, SEEK_SET); AMF::Object amf_storage; uint64_t lastBytePos = 13; uint64_t bench = Util::getMicros(); while (!feof(inFile) && !FLV::Parse_Error){ if (tmpTag.FileLoader(inFile)){ tmpTag.toMeta(meta, amf_storage); if (!tmpTag.getDataLen()){continue;} if (tmpTag.needsInitData() && tmpTag.isInitData()){continue;} size_t tNumber = meta.trackIDToIndex(tmpTag.getTrackID(), getpid()); if (tNumber != INVALID_TRACK_ID){ meta.update(tmpTag.tagTime(), tmpTag.offset(), tNumber, tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); } lastBytePos = Util::ftell(inFile); } } bench = Util::getMicros(bench); INFO_MSG("Header generated in %" PRIu64 " ms: @%" PRIu64 ", %s, %s", bench / 1000, lastBytePos, M.getVod() ? "VoD" : "NOVoD", M.getLive() ? "Live" : "NOLive"); if (FLV::Parse_Error){ tmpTag = FLV::Tag(); FLV::Parse_Error = false; ERROR_MSG("Stopping at FLV parse error @%" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str()); } Util::fseek(inFile, 13, SEEK_SET); return true; } void InputFLV::getNext(size_t idx){ uint64_t lastBytePos = Util::ftell(inFile); if (idx != INVALID_TRACK_ID){ uint8_t targetTag = 0x08; if (M.getType(idx) == "video"){targetTag = 0x09;} if (M.getType(idx) == "meta"){targetTag = 0x12;} FLV::seekToTagType(inFile, targetTag); } while (!feof(inFile) && !FLV::Parse_Error){ if (tmpTag.FileLoader(inFile)){ if (idx != INVALID_TRACK_ID && M.getID(idx) != tmpTag.getTrackID()){ lastBytePos = Util::ftell(inFile); continue; } break; } } if (feof(inFile)){ Util::logExitReason(ER_CLEAN_EOF, "Reached EOF"); thisPacket.null(); return; } if (FLV::Parse_Error){ FLV::Parse_Error = false; tmpTag = FLV::Tag(); Util::logExitReason(ER_FORMAT_SPECIFIC, "FLV error @ %" PRIu64 ": %s", lastBytePos, FLV::Error_Str.c_str()); thisPacket.null(); return; } if (!tmpTag.getDataLen() || (tmpTag.needsInitData() && tmpTag.isInitData())){ return getNext(idx); } thisIdx = meta.trackIDToIndex(tmpTag.getTrackID(), getpid()); thisTime = tmpTag.tagTime(); thisPacket.genericFill(thisTime, tmpTag.offset(), thisIdx, tmpTag.getData(), tmpTag.getDataLen(), lastBytePos, tmpTag.isKeyframe); if (M.getCodec(idx) == "PCM" && M.getSize(idx) == 16){ char *ptr = 0; size_t ptrSize = 0; thisPacket.getString("data", ptr, ptrSize); for (size_t i = 0; i < ptrSize; i += 2){ char tmpchar = ptr[i]; ptr[i] = ptr[i + 1]; ptr[i + 1] = tmpchar; } } } void InputFLV::seek(uint64_t seekTime, size_t idx){ // We will seek to the corresponding keyframe of the video track if selected, otherwise audio // keyframe. Flv files are never multi-track, so track 1 is video, track 2 is audio. size_t seekTrack = (idx == INVALID_TRACK_ID ? M.mainTrack() : idx); DTSC::Keys keys(M.keys(seekTrack)); uint32_t keyNum = M.getKeyNumForTime(seekTrack, seekTime); Util::fseek(inFile, keys.getBpos(keyNum), SEEK_SET); } }// namespace Mist