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
Reference in a new issue