EBML updates:
- AV1 support - Support for outputting fragments longer than 30 seconds in duration - Fixed FireFox support for Opus audio tracks - Added support for stdin live input of EBML - Fixed broken timestamps when seeking in VoD EBML files - Analyser now calculates offsets for (manual) double-checking - Added JSON track support to EBML input and output - Added basic input support for SRT/ASS/SSA subtitles - Opus CODECDELAY now actually calculated. - Fixed Opus in Firefox - Improved MP3 support, more robust handling of corruption, support for non-standard timescale sources
This commit is contained in:
		
							parent
							
								
									7f770b27b7
								
							
						
					
					
						commit
						68a1bff34f
					
				
					 8 changed files with 351 additions and 91 deletions
				
			
		|  | @ -1,6 +1,7 @@ | |||
| #include "output_ebml.h" | ||||
| #include <mist/ebml_socketglue.h> | ||||
| #include <mist/riff.h> | ||||
| #include <mist/opus.h> | ||||
| 
 | ||||
| namespace Mist{ | ||||
|   OutEBML::OutEBML(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|  | @ -15,6 +16,9 @@ namespace Mist{ | |||
|     if (config->getString("target").size()){ | ||||
|       if (config->getString("target").find(".webm") != std::string::npos){doctype = "webm";} | ||||
|       initialize(); | ||||
|       if (myMeta.vod){ | ||||
|         calcVodSizes(); | ||||
|       } | ||||
|       if (!streamName.size()){ | ||||
|         WARN_MSG("Recording unconnected EBML output to file! Cancelled."); | ||||
|         conn.close(); | ||||
|  | @ -55,6 +59,7 @@ namespace Mist{ | |||
|     capa["codecs"][0u][0u].append("VP9"); | ||||
|     capa["codecs"][0u][0u].append("theora"); | ||||
|     capa["codecs"][0u][0u].append("MPEG2"); | ||||
|     capa["codecs"][0u][0u].append("AV1"); | ||||
|     capa["codecs"][0u][1u].append("AAC"); | ||||
|     capa["codecs"][0u][1u].append("vorbis"); | ||||
|     capa["codecs"][0u][1u].append("opus"); | ||||
|  | @ -65,6 +70,7 @@ namespace Mist{ | |||
|     capa["codecs"][0u][1u].append("MP3"); | ||||
|     capa["codecs"][0u][1u].append("FLOAT"); | ||||
|     capa["codecs"][0u][1u].append("AC3"); | ||||
|     capa["codecs"][0u][2u].append("+JSON"); | ||||
|     capa["methods"][0u]["handler"] = "http"; | ||||
|     capa["methods"][0u]["type"] = "html5/video/webm"; | ||||
|     capa["methods"][0u]["priority"] = 11ll; | ||||
|  | @ -131,12 +137,13 @@ namespace Mist{ | |||
|       currentClusterTime = thisPacket.getTime(); | ||||
|       if (myMeta.vod){ | ||||
|         //In case of VoD, clusters are aligned with the main track fragments
 | ||||
|         //EXCEPT when they are more than 30 seconds long, because clusters are limited to -32 to 32 seconds.
 | ||||
|         DTSC::Track &Trk = myMeta.tracks[getMainSelectedTrack()]; | ||||
|         uint32_t fragIndice = Trk.timeToFragnum(currentClusterTime); | ||||
|         newClusterTime = Trk.getKey(Trk.fragments[fragIndice].getNumber()).getTime() + Trk.fragments[fragIndice].getDuration(); | ||||
|         //The last fragment should run until the end of time
 | ||||
|         if (fragIndice == Trk.fragments.size() - 1){ | ||||
|           newClusterTime = 0xFFFFFFFFFFFFFFFFull; | ||||
|         //Limit clusters to 30s, and the last fragment should always be 30s, just in case.
 | ||||
|         if ((newClusterTime - currentClusterTime > 30000) || (fragIndice == Trk.fragments.size() - 1)){ | ||||
|           newClusterTime = currentClusterTime + 30000; | ||||
|         } | ||||
|         EXTREME_MSG("Cluster: %llu - %llu (%lu/%lu) = %llu", currentClusterTime, newClusterTime, fragIndice, Trk.fragments.size(), clusterSize(currentClusterTime, newClusterTime)); | ||||
|       }else{ | ||||
|  | @ -157,6 +164,7 @@ namespace Mist{ | |||
|     if (Trk.codec == "HEVC"){return "V_MPEGH/ISO/HEVC";} | ||||
|     if (Trk.codec == "VP8"){return "V_VP8";} | ||||
|     if (Trk.codec == "VP9"){return "V_VP9";} | ||||
|     if (Trk.codec == "AV1"){return "V_AV1";} | ||||
|     if (Trk.codec == "AAC"){return "A_AAC";} | ||||
|     if (Trk.codec == "vorbis"){return "A_VORBIS";} | ||||
|     if (Trk.codec == "theora"){return "V_THEORA";} | ||||
|  | @ -168,6 +176,7 @@ namespace Mist{ | |||
|     if (Trk.codec == "ALAW"){return "A_MS/ACM";} | ||||
|     if (Trk.codec == "ULAW"){return "A_MS/ACM";} | ||||
|     if (Trk.codec == "FLOAT"){return "A_PCM/FLOAT/IEEE";} | ||||
|     if (Trk.codec == "JSON"){return "M_JSON";} | ||||
|     return "E_UNKNOWN"; | ||||
|   } | ||||
| 
 | ||||
|  | @ -185,10 +194,16 @@ namespace Mist{ | |||
|     }else{ | ||||
|       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus" && Trk.init.size() > 11){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "audio"){ | ||||
|  | @ -198,6 +213,9 @@ namespace Mist{ | |||
|       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|     sendLen += subLen; | ||||
| 
 | ||||
|     // Now actually send.
 | ||||
|  | @ -215,11 +233,17 @@ namespace Mist{ | |||
|     }else{ | ||||
|       if (Trk.init.size()){EBML::sendElemStr(myConn, EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 1); | ||||
|       EBML::sendElemHead(myConn, EBML::EID_VIDEO, subLen); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_PIXELWIDTH, Trk.width); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_PIXELHEIGHT, Trk.height); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_DISPLAYWIDTH, Trk.width); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_DISPLAYHEIGHT, Trk.height); | ||||
|     } | ||||
|     if (Trk.type == "audio"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 2); | ||||
|  | @ -228,6 +252,9 @@ namespace Mist{ | |||
|       EBML::sendElemDbl(myConn, EBML::EID_SAMPLINGFREQUENCY, Trk.rate); | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_BITDEPTH, Trk.size); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       EBML::sendElemUInt(myConn, EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   uint32_t OutEBML::sizeElemTrackEntry(const DTSC::Track &Trk){ | ||||
|  | @ -244,10 +271,16 @@ namespace Mist{ | |||
|     }else{ | ||||
|       if (Trk.init.size()){sendLen += EBML::sizeElemStr(EBML::EID_CODECPRIVATE, Trk.init);} | ||||
|     } | ||||
|     if (Trk.codec == "opus"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_CODECDELAY, Opus::getPreSkip(Trk.init.data())*1000000/48); | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_SEEKPREROLL, 80000000); | ||||
|     } | ||||
|     if (Trk.type == "video"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 1); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_PIXELWIDTH, Trk.width); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_PIXELHEIGHT, Trk.height); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYWIDTH, Trk.width); | ||||
|       subLen += EBML::sizeElemUInt(EBML::EID_DISPLAYHEIGHT, Trk.height); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_VIDEO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "audio"){ | ||||
|  | @ -257,6 +290,9 @@ namespace Mist{ | |||
|       subLen += EBML::sizeElemUInt(EBML::EID_BITDEPTH, Trk.size); | ||||
|       sendLen += EBML::sizeElemHead(EBML::EID_AUDIO, subLen); | ||||
|     } | ||||
|     if (Trk.type == "meta"){ | ||||
|       sendLen += EBML::sizeElemUInt(EBML::EID_TRACKTYPE, 3); | ||||
|     } | ||||
|     sendLen += subLen; | ||||
|     return EBML::sizeElemHead(EBML::EID_TRACKENTRY, sendLen) + sendLen; | ||||
|   } | ||||
|  | @ -296,14 +332,9 @@ namespace Mist{ | |||
|     if (myMeta.vod){ | ||||
|       EBML::sendElemHead(myConn, EBML::EID_CUES, cuesSize); | ||||
|       uint64_t tmpsegSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       uint32_t fragNo = 0; | ||||
|       for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){ | ||||
|         uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime(); | ||||
|         //The first fragment always starts at time 0, even if the main track does not.
 | ||||
|         if (!fragNo){clusterStart = 0;} | ||||
|         EBML::sendElemCuePoint(myConn, clusterStart, Trk.trackID, tmpsegSize, 0); | ||||
|         tmpsegSize += clusterSizes[fragNo]; | ||||
|         ++fragNo; | ||||
|       for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|         EBML::sendElemCuePoint(myConn, it->first, Trk.trackID, tmpsegSize, 0); | ||||
|         tmpsegSize += it->second; | ||||
|       } | ||||
|     } | ||||
|     sentHeader = true; | ||||
|  | @ -331,10 +362,10 @@ namespace Mist{ | |||
|     for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|       VERYHIGH_MSG("Cluster %llu (%llu bytes) -> %llu to go", it->first, it->second, startPos); | ||||
|       if (startPos < it->second){ | ||||
|         HIGH_MSG("Seek to fragment %llu (%llu ms)", it->first, Trk.getKey(Trk.fragments[it->first].getNumber()).getTime()); | ||||
|         HIGH_MSG("Seek to fragment at %llu ms", it->first); | ||||
|         myConn.skipBytes(startPos); | ||||
|         seek(Trk.getKey(Trk.fragments[it->first].getNumber()).getTime()); | ||||
|         newClusterTime = Trk.getKey(Trk.fragments[it->first].getNumber()).getTime(); | ||||
|         seek(it->first); | ||||
|         newClusterTime = it->first; | ||||
|         return; | ||||
|       } | ||||
|       startPos -= it->second; | ||||
|  | @ -484,10 +515,17 @@ namespace Mist{ | |||
|       uint64_t clusterEnd = clusterStart + it->getDuration(); | ||||
|       //The first fragment always starts at time 0, even if the main track does not.
 | ||||
|       if (!fragNo){clusterStart = 0;} | ||||
|       //The last fragment always ends at the end, even if the main track does not.
 | ||||
|       if (fragNo == Trk.fragments.size() - 1){clusterEnd = 0xFFFFFFFFFFFFFFFFull;} | ||||
|       uint64_t cSize = clusterSize(clusterStart, clusterEnd); | ||||
|       clusterSizes[fragNo] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize); | ||||
|       uint64_t clusterTmpEnd = clusterEnd; | ||||
|       do { | ||||
|         clusterTmpEnd = clusterEnd; | ||||
|         //The last fragment always ends at the end, even if the main track does not.
 | ||||
|         if (fragNo == Trk.fragments.size() - 1){clusterTmpEnd = clusterStart + 30000;} | ||||
|         //Limit clusters to 30 seconds.
 | ||||
|         if (clusterTmpEnd - clusterStart > 30000){clusterTmpEnd = clusterStart + 30000;} | ||||
|         uint64_t cSize = clusterSize(clusterStart, clusterTmpEnd); | ||||
|         clusterSizes[clusterStart] = cSize + EBML::sizeElemHead(EBML::EID_CLUSTER, cSize); | ||||
|         clusterStart = clusterTmpEnd;//Continue at the end of this cluster, if continuing.
 | ||||
|       }while(clusterTmpEnd < clusterEnd); | ||||
|       ++fragNo; | ||||
|     } | ||||
|     //Calculating Cues size
 | ||||
|  | @ -500,14 +538,9 @@ namespace Mist{ | |||
|       oldcuesSize = cuesSize; | ||||
|       segmentSize = infoSize + tracksSize + seekheadSize + cuesSize + EBML::sizeElemHead(EBML::EID_CUES, cuesSize); | ||||
|       uint32_t cuesInside = 0; | ||||
|       fragNo = 0; | ||||
|       for (std::deque<DTSC::Fragment>::iterator it = Trk.fragments.begin(); it != Trk.fragments.end(); ++it){ | ||||
|         uint64_t clusterStart = Trk.getKey(it->getNumber()).getTime(); | ||||
|         //The first fragment always starts at time 0, even if the main track does not.
 | ||||
|         if (!fragNo){clusterStart = 0;} | ||||
|         cuesInside += EBML::sizeElemCuePoint(clusterStart, Trk.trackID, segmentSize, 0); | ||||
|         segmentSize += clusterSizes[fragNo]; | ||||
|         ++fragNo; | ||||
|       for (std::map<uint64_t, uint64_t>::iterator it = clusterSizes.begin(); it != clusterSizes.end(); ++it){ | ||||
|         cuesInside += EBML::sizeElemCuePoint(it->first, Trk.trackID, segmentSize, 0); | ||||
|         segmentSize += it->second; | ||||
|       } | ||||
|       cuesSize = cuesInside; | ||||
|     }while(cuesSize != oldcuesSize); | ||||
|  |  | |||
|  | @ -26,7 +26,7 @@ namespace Mist{ | |||
|     uint32_t cuesSize;//size of Cues (excl. header)
 | ||||
|     uint32_t seekheadSize;//size of SeekHead (incl. header)
 | ||||
|     uint32_t seekSize;//size of contents of SeekHead (excl. header)
 | ||||
|     std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters (incl. header)
 | ||||
|     std::map<uint64_t, uint64_t> clusterSizes;//sizes of Clusters by start time (incl. header)
 | ||||
|     void byteSeek(uint64_t startPos); | ||||
|   }; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Thulinma
						Thulinma