From b8415d09c6bdeae1342242aec16ce14b826d656c Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 24 Apr 2019 14:22:07 +0200 Subject: [PATCH] HLS/DASH stream track selector support in index URLs, fixed source matching when multi-select or type-select is used, handle user agent exceptions in Output::selectDefaultTracks(), added Util::codecString to stream.h library, removed duplicate/wrong code from DASH/HLS outputs --- lib/h265.cpp | 22 +++-- lib/h265.h | 6 ++ lib/stream.cpp | 95 ++++++++++++++++++++++ lib/stream.h | 2 + src/output/output.cpp | 33 ++++++++ src/output/output_dash_mp4.cpp | 119 +++++++++++----------------- src/output/output_hls.cpp | 85 +++++++------------- src/output/output_http_internal.cpp | 37 +++------ 8 files changed, 237 insertions(+), 162 deletions(-) diff --git a/lib/h265.cpp b/lib/h265.cpp index 5cc7cc59..5d5e9d61 100644 --- a/lib/h265.cpp +++ b/lib/h265.cpp @@ -163,12 +163,18 @@ namespace h265{ return res; } - void skipProfileTierLevel(Utils::bitstream &bs, unsigned int maxSubLayersMinus1){ - bs.skip(8); - bs.skip(32); // general_profile_flags - bs.skip(4); - bs.skip(44); // reserverd_zero - bs.skip(8); + void profileTierLevel(Utils::bitstream &bs, unsigned int maxSubLayersMinus1, metaInfo &res){ + res.general_profile_space = bs.get(2); + res.general_tier_flag = bs.get(1); + res.general_profile_idc = bs.get(5); + res.general_profile_compatflags = bs.get(32); + res.constraint_flags[0] = bs.get(8); + res.constraint_flags[1] = bs.get(8); + res.constraint_flags[2] = bs.get(8); + res.constraint_flags[3] = bs.get(8); + res.constraint_flags[4] = bs.get(8); + res.constraint_flags[5] = bs.get(8); + res.general_level_idc = bs.get(8); std::deque profilePresent; std::deque levelPresent; for (size_t i = 0; i < maxSubLayersMinus1; i++){ @@ -220,7 +226,7 @@ namespace h265{ if (maxSubLayersMinus1){ for (int i = maxSubLayersMinus1; i < 8; i++){ - r << std::string(indent + 1, ' ') << "reserver_zero_2_bits[" << i << "]: " << bs.get(2) + r << std::string(indent + 1, ' ') << "reserved_zero_2_bits[" << i << "]: " << bs.get(2) << std::endl; } } @@ -521,7 +527,7 @@ namespace h265{ bs.skip(4); unsigned int maxSubLayersMinus1 = bs.get(3); bs.skip(1); - skipProfileTierLevel(bs, maxSubLayersMinus1); + profileTierLevel(bs, maxSubLayersMinus1, res); bs.getUExpGolomb(); uint64_t chromaFormatIdc = bs.getUExpGolomb(); bool separateColorPlane = false; diff --git a/lib/h265.h b/lib/h265.h index 7751c6f7..ccbf4913 100644 --- a/lib/h265.h +++ b/lib/h265.h @@ -22,6 +22,12 @@ namespace h265{ uint64_t width; uint64_t height; double fps; + uint8_t general_profile_space; + bool general_tier_flag; + uint8_t general_profile_idc; + uint32_t general_profile_compatflags; + uint8_t constraint_flags[6]; + uint8_t general_level_idc; }; class initData{ diff --git a/lib/stream.cpp b/lib/stream.cpp index dd5a58bd..f9820db8 100644 --- a/lib/stream.cpp +++ b/lib/stream.cpp @@ -10,6 +10,8 @@ #include "shared_memory.h" #include "socket.h" #include "triggers.h" //LTS +#include "h265.h" +#include "mp4_generic.h" #include #include #include @@ -37,6 +39,76 @@ static void replace(std::string &str, const std::string &from, const std::string } } +std::string Util::codecString(const std::string & codec, const std::string & initData){ + if (codec == "H264"){ + std::stringstream r; + MP4::AVCC avccBox; + avccBox.setPayload(initData); + r << "avc1."; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[1] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[2] << std::dec; + r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[3] << std::dec; + return r.str(); + } + if (codec == "HEVC"){ + h265::initData init(initData); + h265::metaInfo mInfo = init.getMeta(); + std::stringstream r; + r << "hev1."; + switch (mInfo.general_profile_space){ + case 0: break; + case 1: r << 'A'; break; + case 2: r << 'B'; break; + case 3: r << 'C'; break; + } + r << (unsigned long)mInfo.general_profile_idc << '.'; + uint32_t mappedFlags = 0; + if (mInfo.general_profile_compatflags & 0x00000001ul){mappedFlags += 0x80000000ul;} + if (mInfo.general_profile_compatflags & 0x00000002ul){mappedFlags += 0x40000000ul;} + if (mInfo.general_profile_compatflags & 0x00000004ul){mappedFlags += 0x20000000ul;} + if (mInfo.general_profile_compatflags & 0x00000008ul){mappedFlags += 0x10000000ul;} + if (mInfo.general_profile_compatflags & 0x00000010ul){mappedFlags += 0x08000000ul;} + if (mInfo.general_profile_compatflags & 0x00000020ul){mappedFlags += 0x04000000ul;} + if (mInfo.general_profile_compatflags & 0x00000040ul){mappedFlags += 0x02000000ul;} + if (mInfo.general_profile_compatflags & 0x00000080ul){mappedFlags += 0x01000000ul;} + if (mInfo.general_profile_compatflags & 0x00000100ul){mappedFlags += 0x00800000ul;} + if (mInfo.general_profile_compatflags & 0x00000200ul){mappedFlags += 0x00400000ul;} + if (mInfo.general_profile_compatflags & 0x00000400ul){mappedFlags += 0x00200000ul;} + if (mInfo.general_profile_compatflags & 0x00000800ul){mappedFlags += 0x00100000ul;} + if (mInfo.general_profile_compatflags & 0x00001000ul){mappedFlags += 0x00080000ul;} + if (mInfo.general_profile_compatflags & 0x00002000ul){mappedFlags += 0x00040000ul;} + if (mInfo.general_profile_compatflags & 0x00004000ul){mappedFlags += 0x00020000ul;} + if (mInfo.general_profile_compatflags & 0x00008000ul){mappedFlags += 0x00010000ul;} + if (mInfo.general_profile_compatflags & 0x00010000ul){mappedFlags += 0x00008000ul;} + if (mInfo.general_profile_compatflags & 0x00020000ul){mappedFlags += 0x00004000ul;} + if (mInfo.general_profile_compatflags & 0x00040000ul){mappedFlags += 0x00002000ul;} + if (mInfo.general_profile_compatflags & 0x00080000ul){mappedFlags += 0x00001000ul;} + if (mInfo.general_profile_compatflags & 0x00100000ul){mappedFlags += 0x00000800ul;} + if (mInfo.general_profile_compatflags & 0x00200000ul){mappedFlags += 0x00000400ul;} + if (mInfo.general_profile_compatflags & 0x00400000ul){mappedFlags += 0x00000200ul;} + if (mInfo.general_profile_compatflags & 0x00800000ul){mappedFlags += 0x00000100ul;} + if (mInfo.general_profile_compatflags & 0x01000000ul){mappedFlags += 0x00000080ul;} + if (mInfo.general_profile_compatflags & 0x02000000ul){mappedFlags += 0x00000040ul;} + if (mInfo.general_profile_compatflags & 0x04000000ul){mappedFlags += 0x00000020ul;} + if (mInfo.general_profile_compatflags & 0x08000000ul){mappedFlags += 0x00000010ul;} + if (mInfo.general_profile_compatflags & 0x10000000ul){mappedFlags += 0x00000008ul;} + if (mInfo.general_profile_compatflags & 0x20000000ul){mappedFlags += 0x00000004ul;} + if (mInfo.general_profile_compatflags & 0x40000000ul){mappedFlags += 0x00000002ul;} + if (mInfo.general_profile_compatflags & 0x80000000ul){mappedFlags += 0x00000001ul;} + r << std::hex << (unsigned long)mappedFlags << std::dec << '.'; + if (mInfo.general_tier_flag){r << 'H';}else{r << 'L';} + r << (unsigned long)mInfo.general_level_idc; + if (mInfo.constraint_flags[0]){ + r << '.' << std::hex << (unsigned long)mInfo.constraint_flags[0] << std::dec; + } + return r.str(); + } + if (codec == "AAC"){return "mp4a.40.2";} + if (codec == "MP3"){return "mp4a.40.34";} + if (codec == "AC3"){return "ec-3";} + return ""; +} + /// Replaces all stream-related variables in the given 'str' with their values. void Util::streamVariables(std::string &str, const std::string &streamname, const std::string &source){ @@ -493,6 +565,29 @@ uint8_t Util::getStreamStatus(const std::string &streamname){ return streamStatus.mapped[0]; } +/// Checks if a given user agent is allowed according to the given exception. +bool Util::checkException(const JSON::Value & ex, const std::string & useragent){ + //No user agent? Always allow everything. + if (!useragent.size()){return true;} + if (!ex.isArray() || !ex.size()){return true;} + bool ret = true; + jsonForEachConst(ex, e){ + if (!e->isArray() || !e->size()){continue;} + bool setTo = ((*e)[0u].asStringRef() == "whitelist"); + if (e->size() == 1){ + ret = setTo; + continue; + } + if (!(*e)[1].isArray()){continue;} + jsonForEachConst((*e)[1u], i){ + if (useragent.find(i->asStringRef()) != std::string::npos){ + ret = setTo; + } + } + } + return ret; +} + Util::DTSCShmReader::DTSCShmReader(const std::string &pageName){ rPage.init(pageName, 0, false, false); if (rPage){rAcc = Util::RelAccX(rPage.mapped);} diff --git a/lib/stream.h b/lib/stream.h index 939ac7b0..8a1b71f6 100644 --- a/lib/stream.h +++ b/lib/stream.h @@ -20,6 +20,8 @@ namespace Util { JSON::Value getInputBySource(const std::string & filename, bool isProvider = false); DTSC::Meta getStreamMeta(const std::string & streamname); uint8_t getStreamStatus(const std::string & streamname); + bool checkException(const JSON::Value & ex, const std::string & useragent); + std::string codecString(const std::string & codec, const std::string & initData = ""); class DTSCShmReader{ public: diff --git a/src/output/output.cpp b/src/output/output.cpp index 4ecdbd69..b4515d29 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -541,6 +541,17 @@ namespace Mist{ if (strRef[shift] == '+'){multiSel = true; ++shift;} for (std::set::iterator itd = selectedTracks.begin(); itd != selectedTracks.end(); itd++){ if ((!byType && myMeta.tracks[*itd].codec == strRef.substr(shift)) || (byType && myMeta.tracks[*itd].type == strRef.substr(shift)) || strRef.substr(shift) == "*"){ + //user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ + jsonForEach(capa["exceptions"], ex){ + if (ex.key() == "codec:"+strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){break;} selCounter++; if (!multiSel){ break; @@ -600,6 +611,17 @@ namespace Mist{ if (noSelAudio && trit->second.type == "audio"){continue;} if (noSelVideo && trit->second.type == "video"){continue;} if (noSelSub && (trit->second.type == "subtitle" || trit->second.codec == "subtitle")){continue;} + //user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ + jsonForEach(capa["exceptions"], ex){ + if (ex.key() == "codec:"+strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){continue;} /*LTS-END*/ selectedTracks.insert(trit->first); found = true; @@ -614,6 +636,17 @@ namespace Mist{ if (noSelAudio && trit->second.type == "audio"){continue;} if (noSelVideo && trit->second.type == "video"){continue;} if (noSelSub && (trit->second.type == "subtitle" || trit->second.codec == "subtitle")){continue;} + //user-agent-check + bool problems = false; + if (capa.isMember("exceptions") && capa["exceptions"].isObject() && capa["exceptions"].size()){ + jsonForEach(capa["exceptions"], ex){ + if (ex.key() == "codec:"+strRef.substr(shift)){ + problems = !Util::checkException(*ex, UA); + break; + } + } + } + if (problems){continue;} /*LTS-END*/ selectedTracks.insert(trit->first); found = true; diff --git a/src/output/output_dash_mp4.cpp b/src/output/output_dash_mp4.cpp index ef8887ec..20201dce 100644 --- a/src/output/output_dash_mp4.cpp +++ b/src/output/output_dash_mp4.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace Mist{ @@ -321,29 +322,6 @@ namespace Mist{ H.Chunkify(data, dataLen, myConn); } - std::string OutDashMP4::h264init(const std::string & initData){ - std::stringstream r; - MP4::AVCC avccBox; - avccBox.setPayload(initData); - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[1] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[2] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[3] << std::dec; - return r.str(); - } - - std::string OutDashMP4::h265init(const std::string & initData){ - std::stringstream r; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[1] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[6] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[7] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[8] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[9] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[10] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[11] << std::dec; - r << std::hex << std::setw(2) << std::setfill('0') << (int)initData[12] << std::dec; - return r.str(); - } - /// Examines Trk and adds playable fragments from it to r. void OutDashMP4::addSegmentTimeline(std::stringstream & r, DTSC::Track & Trk, bool live){ std::deque::iterator it = Trk.fragments.begin(); @@ -369,6 +347,7 @@ namespace Mist{ /// Returns a string with the full XML DASH manifest MPD file. std::string OutDashMP4::buildManifest(){ initialize(); + selectDefaultTracks(); uint64_t lastVidTime = 0; uint64_t vidInitTrack = 0; uint64_t lastAudTime = 0; @@ -376,17 +355,17 @@ namespace Mist{ uint64_t subInitTrack = 0; /// \TODO DASH pretends there is only one audio/video track, and then prints them all using the same timing information. This is obviously wrong if the tracks are not in sync. - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it ++){ - if ((it->second.codec == "H264" || it->second.codec == "HEVC") && it->second.lastms > lastVidTime){ - lastVidTime = it->second.lastms; - vidInitTrack = it->first; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (myMeta.tracks[*it].type == "video" && myMeta.tracks[*it].lastms > lastVidTime){ + lastVidTime = myMeta.tracks[*it].lastms; + vidInitTrack = *it; } - if ((it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3")&& it->second.lastms > lastAudTime){ - lastAudTime = it->second.lastms; - audInitTrack = it->first; + if (myMeta.tracks[*it].type == "audio" && myMeta.tracks[*it].lastms > lastAudTime){ + lastAudTime = myMeta.tracks[*it].lastms; + audInitTrack = *it; } - if(it->second.codec == "subtitle"){ - subInitTrack = it->first; + if(myMeta.tracks[*it].codec == "subtitle"){ + subInitTrack = *it; } } std::stringstream r; @@ -396,36 +375,36 @@ namespace Mist{ if (myMeta.vod){ r << "type=\"static\" mediaPresentationDuration=\"" << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - myMeta.tracks[getMainSelectedTrack()].firstms) << "\" minBufferTime=\"PT1.5S\" >" << std::endl; }else{ - r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\"" << Util::getUTCString(Util::epoch() - std::max(lastVidTime, lastAudTime)/1000) << "\" " << "timeShiftBufferDepth=\"" << makeTime(myMeta.tracks.begin()->second.lastms - myMeta.tracks.begin()->second.firstms) << "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" >" << std::endl; + r << "type=\"dynamic\" minimumUpdatePeriod=\"PT2.0S\" availabilityStartTime=\"" << Util::getUTCString(Util::epoch() - myMeta.tracks[getMainSelectedTrack()].lastms/1000) << "\" " << "timeShiftBufferDepth=\"" << makeTime(myMeta.tracks[getMainSelectedTrack()].lastms - myMeta.tracks[getMainSelectedTrack()].firstms) << "\" suggestedPresentationDelay=\"PT5.0S\" minBufferTime=\"PT2.0S\" publishTime=\"" << Util::getUTCString(Util::epoch()) << "\" >" << std::endl; } r << " " << streamName << "" << std::endl; r << " " << std::endl; if (vidInitTrack){ DTSC::Track & trackRef = myMeta.tracks[vidInitTrack]; r << " " << std::endl; - r << " " << std::endl; + r << " " << std::endl; r << " " << std::endl; addSegmentTimeline(r, trackRef, myMeta.live); r << " " << std::endl; r << " " << std::endl; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "H264"){ - r << " first << "\" "; - r << "codecs=\"avc1." << h264init(it->second.init) << "\" "; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (myMeta.tracks[*it].codec == "H264"){ + r << " second.bps*8) << "\" "; + r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\" "; r << "/>" << std::endl; } - if (it->second.codec == "HEVC"){ + if (myMeta.tracks[*it].codec == "HEVC"){ r << " first << "\" "; - r << "codecs=\"hev1." << h265init(it->second.init) << "\" "; + r << "id=\"" << *it << "\" "; + r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" "; //bandwidth is in bits per seconds, we have bytes, so times 8 - r << "bandwidth=\"" << (it->second.bps*8) << "\" "; + r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\" "; r << "/>" << std::endl; } } @@ -435,28 +414,22 @@ namespace Mist{ DTSC::Track & trackRef = myMeta.tracks[audInitTrack]; r << " " << std::endl; r << " " << std::endl; - r << " " << std::endl; + r << " " << std::endl; r << " " << std::endl; addSegmentTimeline(r, trackRef, myMeta.live); r << " " << std::endl; r << " " << std::endl; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){ - r << " first << "\" "; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (myMeta.tracks[*it].codec == "AAC" || myMeta.tracks[*it].codec == "MP3" || myMeta.tracks[*it].codec == "AC3"){ + r << " second.codec == "AAC" ){ - r << "codecs=\"mp4a.40.2\" "; - }else if (it->second.codec == "MP3" ){ - r << "codecs=\"mp4a.40.34\" "; - }else if (it->second.codec == "AC3" ){ - r << "codecs=\"ec-3\" "; - } - r << "audioSamplingRate=\"" << it->second.rate << "\" "; + r << "codecs=\"" << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init) << "\" "; + r << "audioSamplingRate=\"" << myMeta.tracks[*it].rate << "\" "; //bandwidth is in bits per seconds, we have bytes, so times 8 - r << "bandwidth=\"" << (it->second.bps*8) << "\">" << std::endl; - r << " second.channels << "\" />" << std::endl; + r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\">" << std::endl; + r << " " << std::endl; r << " " << std::endl; } } @@ -464,13 +437,13 @@ namespace Mist{ } if(subInitTrack){ - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ - if(it->second.codec == "subtitle"){ - subInitTrack = it->first; - std::string lang = (it->second.lang == "" ? "unknown" : it->second.lang); - r << "first << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang << "\">"; - r << " first << "\" bandwidth=\"256\">"; - r << " ../../" << streamName << ".vtt?track=" << it->first << ""; + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if(myMeta.tracks[*it].codec == "subtitle"){ + subInitTrack = *it; + std::string lang = (myMeta.tracks[*it].lang == "" ? "unknown" : myMeta.tracks[*it].lang); + r << ""; + r << " "; + r << " ../../" << streamName << ".vtt?track=" << *it << ""; r << " " << std::endl; } } @@ -490,19 +463,21 @@ namespace Mist{ capa["url_rel"] = "/dash/$/index.mpd"; capa["url_prefix"] = "/dash/$/"; capa["socket"] = "http_dash_mp4"; - capa["codecs"][0u][0u].append("H264"); - capa["codecs"][0u][0u].append("HEVC"); - capa["codecs"][0u][1u].append("AAC"); - capa["codecs"][0u][1u].append("AC3"); - capa["codecs"][0u][1u].append("MP3"); - capa["codecs"][0u][2u].append("subtitle"); + capa["codecs"][0u][0u].append("+H264"); + capa["codecs"][0u][1u].append("+HEVC"); + capa["codecs"][0u][2u].append("+AAC"); + capa["codecs"][0u][3u].append("+AC3"); + capa["codecs"][0u][4u].append("+MP3"); + capa["codecs"][0u][5u].append("+subtitle"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "dash/video/mp4"; //MP3 does not work in browsers capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); - capa["methods"][0u]["priority"] = 8; + //HEVC does not work in browsers + capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]]]"); + capa["methods"][0u]["priority"] = 8; cfg->addOption("nonchunked", JSON::fromString("{\"short\":\"C\",\"long\":\"nonchunked\",\"help\":\"Do not send chunked, but buffer whole segments.\"}")); capa["optional"]["nonchunked"]["name"] = "Send whole segments"; diff --git a/src/output/output_hls.cpp b/src/output/output_hls.cpp index fdd7c4b3..b93662d4 100644 --- a/src/output/output_hls.cpp +++ b/src/output/output_hls.cpp @@ -40,26 +40,19 @@ namespace Mist { ///\return The index file for HTTP Live Streaming. std::string OutHLS::liveIndex() { std::stringstream result; + selectDefaultTracks(); result << "#EXTM3U\r\n"; int audioId = -1; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3" || it->second.codec == "MP2") { - audioId = it->first; - break; - } - } unsigned int vidTracks = 0; bool hasSubs = false; - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - if (it->second.codec == "subtitle"){ - hasSubs = true; - break; - } + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (audioId == -1 && myMeta.tracks[*it].type == "audio"){audioId = *it;} + if (!hasSubs && myMeta.tracks[*it].codec == "subtitle"){hasSubs = true;} } - for (std::map::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { - if (it->second.codec == "H264" || it->second.codec == "HEVC" || it->second.codec == "MPEG2") { + for (std::set::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ + if (myMeta.tracks[*it].type == "video") { vidTracks++; - int bWidth = it->second.bps; + int bWidth = myMeta.tracks[*it].bps; if (bWidth < 5) { bWidth = 5; } @@ -67,33 +60,21 @@ namespace Mist { bWidth += myMeta.tracks[audioId].bps; } result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (bWidth * 8); - result << ",RESOLUTION=" << it->second.width << "x" << it->second.height; - if (it->second.fpks){ - result << ",FRAME-RATE=" << (float)it->second.fpks / 1000; + result << ",RESOLUTION=" << myMeta.tracks[*it].width << "x" << myMeta.tracks[*it].height; + if (myMeta.tracks[*it].fpks){ + result << ",FRAME-RATE=" << (float)myMeta.tracks[*it].fpks / 1000; } if (hasSubs){ result << ",SUBTITLES=\"sub1\""; } - if (it->second.codec == "H264" || it->second.codec == "HEVC"){ - result << ",CODECS=\""; - if (it->second.codec == "H264"){ - result << "avc1." << h264init(it->second.init); - }else{ - result << "hev1." << h265init(it->second.init); - } - if (audioId != -1){ - if (myMeta.tracks[audioId].codec == "AAC"){ - result << ",mp4a.40.2"; - }else if (myMeta.tracks[audioId].codec == "MP3" ){ - result << ",mp4a.40.34"; - }else if (myMeta.tracks[audioId].codec == "AC3" ){ - result << ",ec-3"; - } - } - result << "\""; + result << ",CODECS=\""; + result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].init); + if (audioId != -1){ + result << "," << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init); } + result << "\""; result <<"\r\n"; - result << it->first; + result << *it; if (audioId != -1) { result << "_" << audioId; } @@ -102,24 +83,18 @@ namespace Mist { }else{ result << "/index.m3u8\r\n"; } - }else if(it->second.codec == "subtitle"){ + }else if(myMeta.tracks[*it].codec == "subtitle"){ - if(it->second.lang.empty()){ - it->second.lang = "und"; + if(myMeta.tracks[*it].lang.empty()){ + myMeta.tracks[*it].lang = "und"; } - result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << it->second.lang << "\",NAME=\"" << Encodings::ISO639::decode(it->second.lang) << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << it->first << "/index.m3u8\"" << "\r\n"; + result << "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"sub1\",LANGUAGE=\"" << myMeta.tracks[*it].lang << "\",NAME=\"" << Encodings::ISO639::decode(myMeta.tracks[*it].lang) << "\",AUTOSELECT=NO,DEFAULT=NO,FORCED=NO,URI=\"" << *it << "/index.m3u8\"" << "\r\n"; } } if (!vidTracks && audioId) { result << "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" << (myMeta.tracks[audioId].bps * 8); - if (myMeta.tracks[audioId].codec == "AAC"){ - result << ",CODECS=\"mp4a.40.2\""; - }else if (myMeta.tracks[audioId].codec == "MP3" ){ - result << ",CODECS=\"mp4a.40.34\""; - }else if (myMeta.tracks[audioId].codec == "AC3" ){ - result << ",CODECS=\"ec-3\""; - } + result << ",CODECS=\"" << Util::codecString(myMeta.tracks[audioId].codec, myMeta.tracks[audioId].init) << "\""; result << "\r\n"; result << audioId << "/index.m3u8\r\n"; } @@ -354,19 +329,19 @@ namespace Mist { capa["url_rel"] = "/hls/$/index.m3u8"; capa["url_prefix"] = "/hls/$/"; capa["url_pushlist"] = "/hls/$/push/list"; - capa["codecs"][0u][0u].append("HEVC"); - capa["codecs"][0u][0u].append("H264"); - capa["codecs"][0u][0u].append("MPEG2"); - capa["codecs"][0u][1u].append("AAC"); - capa["codecs"][0u][1u].append("MP3"); - capa["codecs"][0u][1u].append("AC3"); - capa["codecs"][0u][1u].append("MP2"); + capa["codecs"][0u][0u].append("+HEVC"); + capa["codecs"][0u][1u].append("+H264"); + capa["codecs"][0u][2u].append("+MPEG2"); + capa["codecs"][0u][3u].append("+AAC"); + capa["codecs"][0u][4u].append("+MP3"); + capa["codecs"][0u][5u].append("+AC3"); + capa["codecs"][0u][6u].append("+MP2"); capa["methods"][0u]["handler"] = "http"; capa["methods"][0u]["type"] = "html5/application/vnd.apple.mpegurl"; capa["methods"][0u]["priority"] = 9; //MP3 only works on Edge/Apple - capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\"],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); - capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\"]]]"); + capa["exceptions"]["codec:MP3"] = JSON::fromString("[[\"blacklist\",[\"Mozilla/\"]],[\"whitelist\",[\"iPad\",\"iPhone\",\"iPod\",\"MacIntel\",\"Edge\"]]]"); + capa["exceptions"]["codec:HEVC"] = JSON::fromString("[[\"blacklist\"]]"); /*LTS-START*/ cfg->addOption("listlimit", JSON::fromString("{\"arg\":\"integer\",\"default\":0,\"short\":\"y\",\"long\":\"list-limit\",\"help\":\"Maximum number of parts in live playlists (0 = infinite).\"}")); capa["optional"]["listlimit"]["name"] = "Live playlist limit"; diff --git a/src/output/output_http_internal.cpp b/src/output/output_http_internal.cpp index c44ea733..af0f169d 100644 --- a/src/output/output_http_internal.cpp +++ b/src/output/output_http_internal.cpp @@ -211,34 +211,11 @@ namespace Mist { sources.insert(tmp); } - /// Checks if a given user agent is allowed according to the given exception. - bool checkException(const JSON::Value & ex, const std::string & useragent){ - //No user agent? Always allow everything. - if (!useragent.size()){return true;} - if (!ex.isArray() || !ex.size()){return true;} - bool ret = true; - jsonForEachConst(ex, e){ - if (!e->isArray() || !e->size()){continue;} - bool setTo = ((*e)[0u].asStringRef() == "whitelist"); - if (e->size() == 1){ - ret = setTo; - continue; - } - if (!(*e)[1].isArray()){continue;} - jsonForEachConst((*e)[1u], i){ - if (useragent.find(i->asStringRef()) != std::string::npos){ - ret = setTo; - } - } - } - return ret; - } - void addSources(std::string & streamname, std::set & sources, HTTP::URL url, JSON::Value & conncapa, JSON::Value & strmMeta, const std::string & useragent){ if (strmMeta.isMember("live") && conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ if (ex.key() == "live"){ - if (!checkException(*ex, useragent)){ + if (!Util::checkException(*ex, useragent)){ return; } } @@ -255,14 +232,20 @@ namespace Mist { unsigned int matches = 0; if ((*itb).size() > 0){ jsonForEach((*itb), itc) { + const std::string & strRef = (*itc).asStringRef(); + bool byType = false; + bool multiSel = false; + uint8_t shift = 0; + if (strRef[shift] == '@'){byType = true; ++shift;} + if (strRef[shift] == '+'){multiSel = true; ++shift;} jsonForEach(strmMeta["tracks"], trit) { - if ((*trit)["codec"].asStringRef() == (*itc).asStringRef()){ + if ((!byType && (*trit)["codec"].asStringRef() == strRef.substr(shift)) || (byType && (*trit)["type"].asStringRef() == strRef.substr(shift)) || strRef.substr(shift) == "*"){ matches++; total_matches++; if (conncapa.isMember("exceptions") && conncapa["exceptions"].isObject() && conncapa["exceptions"].size()){ jsonForEach(conncapa["exceptions"], ex){ - if (ex.key() == "codec:"+(*trit)["codec"].asStringRef()){ - if (!checkException(*ex, useragent)){ + if (ex.key() == "codec:"+strRef.substr(shift)){ + if (!Util::checkException(*ex, useragent)){ matches--; total_matches--; }