mistserver/src/input/input_flv.cpp
Eli Mallon e324c2ee58 refactor: capitalize Input classes, rename srt to subrip in source as well
Co-authored-by: Thulinma <jaron@vietors.com>
2024-05-16 16:07:49 +02:00

176 lines
6 KiB
C++

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <mist/defines.h>
#include <mist/stream.h>
#include <mist/util.h>
#include <string>
#include <sys/stat.h> //for stat
#include <sys/types.h> //for stat
#include <unistd.h> //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