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
This commit is contained in:
		
							parent
							
								
									bd34bafc03
								
							
						
					
					
						commit
						b8415d09c6
					
				
					 8 changed files with 237 additions and 162 deletions
				
			
		
							
								
								
									
										22
									
								
								lib/h265.cpp
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								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<bool> profilePresent; | ||||
|     std::deque<bool> 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; | ||||
|  |  | |||
|  | @ -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{ | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ | |||
| #include "shared_memory.h" | ||||
| #include "socket.h" | ||||
| #include "triggers.h" //LTS
 | ||||
| #include "h265.h" | ||||
| #include "mp4_generic.h" | ||||
| #include <semaphore.h> | ||||
| #include <stdlib.h> | ||||
| #include <sys/stat.h> | ||||
|  | @ -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);} | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -541,6 +541,17 @@ namespace Mist{ | |||
|               if (strRef[shift] == '+'){multiSel = true; ++shift;} | ||||
|               for (std::set<unsigned long>::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; | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ | |||
| #include <mist/mp4_dash.h> | ||||
| #include <mist/checksum.h> | ||||
| #include <mist/timing.h> | ||||
| #include <mist/stream.h> | ||||
| #include <iomanip> | ||||
| 
 | ||||
| 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<DTSC::Fragment>::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<unsigned int, DTSC::Track>::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<unsigned long>::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 << "  <ProgramInformation><Title>" << streamName << "</Title></ProgramInformation>" << std::endl; | ||||
|     r << "  <Period "; | ||||
|     if (myMeta.live){ | ||||
|       r << "id=\"0\" "; | ||||
|       r << "start=\"0\" "; | ||||
|     } | ||||
|     r << ">" << std::endl; | ||||
|     if (vidInitTrack){ | ||||
|       DTSC::Track & trackRef = myMeta.tracks[vidInitTrack]; | ||||
|       r << "    <AdaptationSet group=\"1\" id=\"9998\" mimeType=\"video/mp4\" width=\"" << trackRef.width << "\" height=\"" << trackRef.height << "\" frameRate=\"" << trackRef.fpks / 1000 << "\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\">" << std::endl; | ||||
|       r << "      <SegmentTemplate presentationTimeOffset=\"" << trackRef.firstms << "\" timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
|       r << "        <SegmentTimeline>" << std::endl; | ||||
|       addSegmentTimeline(r, trackRef, myMeta.live); | ||||
|       r << "        </SegmentTimeline>" << std::endl; | ||||
|       r << "      </SegmentTemplate>" << std::endl; | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.codec == "H264"){ | ||||
|           r << "      <Representation id=\"" << it->first << "\" "; | ||||
|           r << "codecs=\"avc1." << h264init(it->second.init) << "\" "; | ||||
|       for (std::set<unsigned long>::iterator it = selectedTracks.begin(); it != selectedTracks.end(); ++it){ | ||||
|         if (myMeta.tracks[*it].codec == "H264"){ | ||||
|           r << "      <Representation 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; | ||||
|         } | ||||
|         if (it->second.codec == "HEVC"){ | ||||
|         if (myMeta.tracks[*it].codec == "HEVC"){ | ||||
|           r << "      <Representation "; | ||||
|           r << "id=\"" << it->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 << "    <AdaptationSet group=\"2\" id=\"9999\" mimeType=\"audio/mp4\" segmentAlignment=\"true\" startWithSAP=\"1\" subsegmentAlignment=\"true\" subsegmentStartsWithSAP=\"1\" >" << std::endl; | ||||
|       r << "      <Role schemeIdUri=\"urn:mpeg:dash:role:2011\" value=\"main\"/>" << std::endl; | ||||
|       r << "      <SegmentTemplate presentationTimeOffset=\"" << trackRef.firstms << "\" timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
|       r << "      <SegmentTemplate timescale=\"1000\" media=\"chunk_$RepresentationID$_$Time$.m4s\" initialization=\"chunk_$RepresentationID$_init.m4s\">" << std::endl; | ||||
| 
 | ||||
|       r << "        <SegmentTimeline>" << std::endl; | ||||
|       addSegmentTimeline(r, trackRef, myMeta.live); | ||||
|       r << "        </SegmentTimeline>" << std::endl; | ||||
|       r << "      </SegmentTemplate>" << std::endl; | ||||
|   | ||||
|       for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++){ | ||||
|         if (it->second.codec == "AAC" || it->second.codec == "MP3" || it->second.codec == "AC3"){ | ||||
|           r << "      <Representation id=\"" << it->first << "\" "; | ||||
|       for (std::set<unsigned long>::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 << "      <Representation id=\"" << *it << "\" "; | ||||
|           // (see RFC6381): sample description entry , ObjectTypeIndication [MP4RA, RFC], ObjectTypeIndication [MP4A ISO/IEC 14496-3:2009]
 | ||||
|           if (it->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 << "        <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" << it->second.channels << "\" />" << std::endl; | ||||
|           r << "bandwidth=\"" << (myMeta.tracks[*it].bps*8) << "\">" << std::endl; | ||||
|           r << "        <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"" << myMeta.tracks[*it].channels << "\" />" << std::endl; | ||||
|           r << "      </Representation>" << std::endl; | ||||
|         } | ||||
|       } | ||||
|  | @ -464,13 +437,13 @@ namespace Mist{ | |||
|     } | ||||
| 
 | ||||
|     if(subInitTrack){ | ||||
|       for (std::map<unsigned int, DTSC::Track>::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 << "<AdaptationSet id=\"" << it->first << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang <<  "\">"; | ||||
|           r << " <Representation id=\"" << it->first << "\" bandwidth=\"256\">"; | ||||
|           r <<   " <BaseURL>../../" << streamName << ".vtt?track=" << it->first << "</BaseURL>"; | ||||
|       for (std::set<unsigned long>::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 << "<AdaptationSet id=\"" << *it << "\" group=\"3\" mimeType=\"text/vtt\" lang=\"" << lang <<  "\">"; | ||||
|           r << " <Representation id=\"" << *it << "\" bandwidth=\"256\">"; | ||||
|           r <<   " <BaseURL>../../" << streamName << ".vtt?track=" << *it << "</BaseURL>"; | ||||
|           r << " </Representation></AdaptationSet>" << std::endl; | ||||
|         } | ||||
|       } | ||||
|  | @ -490,18 +463,20 @@ 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/\"]]]"); | ||||
|     //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.\"}")); | ||||
|  |  | |||
|  | @ -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<unsigned int, DTSC::Track>::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<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) { | ||||
|       if (it->second.codec == "subtitle"){ | ||||
|         hasSubs = true; | ||||
|         break; | ||||
|     for (std::set<unsigned long>::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<unsigned int, DTSC::Track>::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<unsigned long>::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); | ||||
|           } | ||||
|         result << Util::codecString(myMeta.tracks[*it].codec, myMeta.tracks[*it].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 << "," << 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"; | ||||
|  |  | |||
|  | @ -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<JSON::Value, sourceCompare> & 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--; | ||||
|                           } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Thulinma
						Thulinma