Various fixes, among which:
- Fixed segfault when attempting to initialseek on disconnected streams - Fix 100% CPU bug in controller's stats code - WebRTC UDP bind socket improvements - Several segfault fixes - Increased packet reordering buffer size from 30 to 150 packets - Tweaks to default output/buffer behaviour for incoming pushes - Added message for load balancer checks - Fixed HLS content type - Stats fixes - Exit reason fixes - Fixed socket IP address detection - Fixed non-string arguments for stream settings - Added caching for getConnectedBinHost() - Added WebRTC playback rate control - Added/completed VP8/VP9 support to WebRTC/RTSP - Added live seek option to WebRTC - Fixed seek to exactly newest timestamp - Fixed HLS input # Conflicts: # lib/defines.h # src/input/input.cpp
This commit is contained in:
		
							parent
							
								
									2b99f2f5ea
								
							
						
					
					
						commit
						0af992d405
					
				
					 75 changed files with 1512 additions and 790 deletions
				
			
		|  | @ -12,7 +12,7 @@ int spawnForked(Socket::Connection &S){ | |||
| void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){ | ||||
|   HIGH_MSG("USR1 received - triggering rolling restart"); | ||||
|   Util::Config::is_restarting = true; | ||||
|   Util::Config::logExitReason("setting is_active to false because of received USR1"); | ||||
|   Util::logExitReason("signal USR1"); | ||||
|   Util::Config::is_active = false; | ||||
| } | ||||
| 
 | ||||
|  | @ -47,5 +47,7 @@ int main(int argc, char *argv[]){ | |||
|       return tmp.run(); | ||||
|     } | ||||
|   } | ||||
|   INFO_MSG("Exit reason: %s", Util::exitReason); | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -48,7 +48,6 @@ namespace Mist{ | |||
| 
 | ||||
|   Output::Output(Socket::Connection &conn) : myConn(conn){ | ||||
|     pushing = false; | ||||
|     pushIsOngoing = false; | ||||
|     firstTime = 0; | ||||
|     firstPacketTime = 0xFFFFFFFFFFFFFFFFull; | ||||
|     lastPacketTime = 0; | ||||
|  | @ -67,6 +66,10 @@ namespace Mist{ | |||
|     lastRecv = Util::bootSecs(); | ||||
|     if (myConn){ | ||||
|       setBlocking(true); | ||||
|       //Make sure that if the socket is a non-stdio socket, we close it when forking
 | ||||
|       if (myConn.getSocket() > 2){ | ||||
|         Util::Procs::socketList.insert(myConn.getSocket()); | ||||
|       } | ||||
|     }else{ | ||||
|       WARN_MSG("Warning: MistOut created with closed socket!"); | ||||
|     } | ||||
|  | @ -129,6 +132,7 @@ namespace Mist{ | |||
|     }else{ | ||||
|       MEDIUM_MSG("onFail '%s': %s", streamName.c_str(), msg.c_str()); | ||||
|     } | ||||
|     Util::logExitReason(msg.c_str()); | ||||
|     isInitialized = false; | ||||
|     wantRequest = true; | ||||
|     parseData = false; | ||||
|  | @ -143,7 +147,7 @@ namespace Mist{ | |||
|     } | ||||
|     reconnect(); | ||||
|     // if the connection failed, fail
 | ||||
|     if (streamName.size() < 1){ | ||||
|     if (!meta || streamName.size() < 1){ | ||||
|       onFail("Could not connect to stream", true); | ||||
|       return; | ||||
|     } | ||||
|  | @ -181,19 +185,8 @@ namespace Mist{ | |||
|           if (shmSessions.mapped){ | ||||
|             char shmEmpty[SHM_SESSIONS_ITEM]; | ||||
|             memset(shmEmpty, 0, SHM_SESSIONS_ITEM); | ||||
|             std::string host = statComm.getHost(); | ||||
|             if (host.substr(0, 12) == | ||||
|                 std::string("\000\000\000\000\000\000\000\000\000\000\377\377", 12)){ | ||||
|               char tmpstr[16]; | ||||
|               snprintf(tmpstr, 16, "%hhu.%hhu.%hhu.%hhu", host[12], host[13], host[14], host[15]); | ||||
|               host = tmpstr; | ||||
|             }else{ | ||||
|               char tmpstr[40]; | ||||
|               snprintf(tmpstr, 40, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", | ||||
|                        host[0], host[1], host[2], host[3], host[4], host[5], host[6], host[7], | ||||
|                        host[8], host[9], host[10], host[11], host[12], host[13], host[14], host[15]); | ||||
|               host = tmpstr; | ||||
|             } | ||||
|             std::string host; | ||||
|             Socket::hostBytesToStr(statComm.getHost().data(), 16, host); | ||||
|             uint32_t shmOffset = 0; | ||||
|             const std::string &cName = capa["name"].asStringRef(); | ||||
|             while (shmOffset + SHM_SESSIONS_ITEM < SHM_SESSIONS_SIZE){ | ||||
|  | @ -259,12 +252,19 @@ namespace Mist{ | |||
| 
 | ||||
|   std::string Output::getConnectedHost(){return myConn.getHost();} | ||||
| 
 | ||||
|   std::string Output::getConnectedBinHost(){return myConn.getBinHost();} | ||||
|   std::string Output::getConnectedBinHost(){ | ||||
|     if (!prevHost.size()){ | ||||
|       if (myConn && myConn.getPureSocket() != -1){ | ||||
|         prevHost = myConn.getBinHost(); | ||||
|       } | ||||
|       if (!prevHost.size()){prevHost.assign("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001", 16);} | ||||
|     } | ||||
|     return prevHost; | ||||
|   } | ||||
| 
 | ||||
|   bool Output::isReadyForPlay(){ | ||||
|     // If a protocol does not support any codecs, we assume you know what you're doing
 | ||||
|     if (!capa.isMember("codecs")){return true;} | ||||
|     if (isPushing()){return true;} | ||||
|     if (!isInitialized){initialize();} | ||||
|     meta.refresh(); | ||||
|     if (getSupportedTracks().size()){ | ||||
|  | @ -363,27 +363,25 @@ namespace Mist{ | |||
|     while (!meta && ++attempts < 20 && Util::streamAlive(streamName)){ | ||||
|       meta.reInit(streamName, false); | ||||
|     } | ||||
|     if (!meta){ | ||||
|       onFail("Could not connect to stream data", true); | ||||
|       return; | ||||
|     } | ||||
|     if (!meta){return;} | ||||
|     meta.refresh(); | ||||
|     isInitialized = true; | ||||
|     statComm.reload(); | ||||
|     stats(true); | ||||
|     if (!pushing){selectDefaultTracks();} | ||||
|     if (!M.getVod() && !isReadyForPlay()){ | ||||
|       uint64_t waitUntil = Util::epoch() + 30; | ||||
|     if (isPushing()){return;} | ||||
|     if (!isRecording() && !M.getVod() && !isReadyForPlay()){ | ||||
|       uint64_t waitUntil = Util::bootSecs() + 45; | ||||
|       while (!M.getVod() && !isReadyForPlay()){ | ||||
|         if (Util::epoch() > waitUntil + 45 || (!userSelect.size() && Util::epoch() > waitUntil)){ | ||||
|           INFO_MSG("Giving up waiting for playable tracks. Stream: %s, IP: %s", streamName.c_str(), | ||||
|                    getConnectedHost().c_str()); | ||||
|         if (Util::bootSecs() > waitUntil || (!userSelect.size() && Util::bootSecs() > waitUntil)){ | ||||
|           INFO_MSG("Giving up waiting for playable tracks. IP: %s", getConnectedHost().c_str()); | ||||
|           break; | ||||
|         } | ||||
|         Util::wait(500); | ||||
|         meta.refresh(); | ||||
|         stats(); | ||||
|       } | ||||
|     } | ||||
|     selectDefaultTracks(); | ||||
|   } | ||||
| 
 | ||||
|   std::set<size_t> Output::getSupportedTracks(const std::string &type) const{ | ||||
|  | @ -403,22 +401,11 @@ namespace Mist{ | |||
|     bool autoSeek = buffer.size(); | ||||
|     uint64_t seekTarget = currentTime(); | ||||
|     std::set<size_t> newSelects = | ||||
|         Util::wouldSelect(myMeta, targetParams, capa, UA, autoSeek ? seekTarget : 0); | ||||
| 
 | ||||
|     std::set<size_t> oldSel; | ||||
|     for (std::set<unsigned long>::iterator selIt = selectedTracks.begin(); | ||||
|          selIt != selectedTracks.end(); ++selIt){ | ||||
|       oldSel.insert(*selIt); | ||||
|     } | ||||
| 
 | ||||
|     if (oldSel == newSelects){ | ||||
|       // No new selections? Do nothing, return no change.
 | ||||
|       return false; | ||||
|     } | ||||
|         Util::wouldSelect(M, targetParams, capa, UA, autoSeek ? seekTarget : 0); | ||||
| 
 | ||||
|     if (autoSeek){ | ||||
|       std::set<size_t> toRemove; | ||||
|       for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ | ||||
|       for (std::set<size_t>::iterator it = newSelects.begin(); it != newSelects.end(); it++){ | ||||
|         // autoSeeking and target not in bounds? Drop it too.
 | ||||
|         if (M.getLastms(*it) < std::max(seekTarget, (uint64_t)6000lu) - 6000){ | ||||
|           toRemove.insert(*it); | ||||
|  | @ -426,7 +413,7 @@ namespace Mist{ | |||
|       } | ||||
|       // remove those from selectedtracks
 | ||||
|       for (std::set<size_t>::iterator it = toRemove.begin(); it != toRemove.end(); it++){ | ||||
|         wouldSelect.erase(*it); | ||||
|         newSelects.erase(*it); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -435,7 +422,7 @@ namespace Mist{ | |||
|     userSelect.clear(); | ||||
| 
 | ||||
|     // Select tracks here!
 | ||||
|     for (std::set<size_t>::iterator it = wouldSelect.begin(); it != wouldSelect.end(); it++){ | ||||
|     for (std::set<size_t>::iterator it = newSelects.begin(); it != newSelects.end(); it++){ | ||||
|       userSelect[*it].reload(streamName, *it); | ||||
|     } | ||||
| 
 | ||||
|  | @ -453,6 +440,14 @@ namespace Mist{ | |||
|     parseData = false; | ||||
|   } | ||||
| 
 | ||||
|   ///Returns the timestamp of the next upcoming keyframe after thisPacket, or 0 if that cannot be determined (yet).
 | ||||
|   uint64_t Output::nextKeyTime(){ | ||||
|     DTSC::Keys keys(M.keys(getMainSelectedTrack())); | ||||
|     if (!keys.getValidCount()){return 0;} | ||||
|     size_t keyNum = keys.getNumForTime(lastPacketTime); | ||||
|     return keys.getTime(keyNum+1); | ||||
|   } | ||||
|    | ||||
|   uint64_t Output::pageNumForKey(size_t trackId, size_t keyNum){ | ||||
|     const Util::RelAccX &tPages = M.pages(trackId); | ||||
|     for (uint64_t i = tPages.getDeleted(); i < tPages.getEndPos(); i++){ | ||||
|  | @ -671,7 +666,7 @@ namespace Mist{ | |||
|     for (std::set<size_t>::iterator it = seekTracks.begin(); it != seekTracks.end(); it++){ | ||||
|       seek(*it, pos, false); | ||||
|     } | ||||
|     firstTime = Util::bootMS() - currentTime(); | ||||
|     firstTime = Util::bootMS() - (currentTime() * realTime / 1000); | ||||
|   } | ||||
| 
 | ||||
|   bool Output::seek(size_t tid, uint64_t pos, bool getNextKey){ | ||||
|  | @ -690,7 +685,7 @@ namespace Mist{ | |||
|         stats(); | ||||
|       } | ||||
|     } | ||||
|     if (meta.getLastms(tid) <= pos){ | ||||
|     if (meta.getLastms(tid) < pos){ | ||||
|       WARN_MSG("Aborting seek to %" PRIu64 "ms in track %zu: past end of track (= %" PRIu64 "ms).", | ||||
|                pos, tid, meta.getLastms(tid)); | ||||
|       userSelect.erase(tid); | ||||
|  | @ -751,7 +746,7 @@ namespace Mist{ | |||
|     if (curPage[tid].mapped[tmp.offset]){return seek(tid, pos, getNextKey);} | ||||
|     FAIL_MSG("Track %zu no data (key %zu@%" PRIu64 ") - timeout", tid, keyNum + (getNextKey ? 1 : 0), tmp.offset); | ||||
|     userSelect.erase(tid); | ||||
|     firstTime = Util::bootMS() - buffer.begin()->time; | ||||
|     firstTime = Util::bootMS() - (buffer.begin()->time * realTime / 1000); | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|  | @ -761,6 +756,7 @@ namespace Mist{ | |||
|   /// needsLookAhead+minKeepAway ms from the end. Unless lastms < 5000, then it seeks to the first
 | ||||
|   /// keyframe of the main track. Aborts if there is no main track or it has no keyframes.
 | ||||
|   void Output::initialSeek(){ | ||||
|     if (!meta){return;} | ||||
|     uint64_t seekPos = 0; | ||||
|     if (meta.getLive()){ | ||||
|       size_t mainTrack = getMainSelectedTrack(); | ||||
|  | @ -980,6 +976,7 @@ namespace Mist{ | |||
|   /// It seeks to the last sync'ed keyframe of the main track, no closer than needsLookAhead+minKeepAway ms from the end.
 | ||||
|   /// Aborts if not live, there is no main track or it has no keyframes.
 | ||||
|   bool Output::liveSeek(){ | ||||
|     if (!realTime){return false;}//Makes no sense when playing in turbo mode
 | ||||
|     static uint32_t seekCount = 2; | ||||
|     uint64_t seekPos = 0; | ||||
|     if (!meta.getLive()){return false;} | ||||
|  | @ -989,11 +986,15 @@ namespace Mist{ | |||
|     uint64_t cTime = thisPacket.getTime(); | ||||
|     uint64_t mKa = getMinKeepAway(); | ||||
|     if (!maxSkipAhead){ | ||||
|       bool noReturn = false; | ||||
|       uint64_t newSpeed = 1000; | ||||
|       if (lMs - mKa - needsLookAhead - extraKeepAway > cTime + 50){ | ||||
|         // We need to speed up!
 | ||||
|         uint64_t diff = (lMs - mKa - needsLookAhead - extraKeepAway) - cTime; | ||||
|         if (diff > 1000){ | ||||
|         if (diff > 3000){ | ||||
|           noReturn = true; | ||||
|           newSpeed = 1000; | ||||
|         }else if (diff > 1000){ | ||||
|           newSpeed = 750; | ||||
|         }else if (diff > 500){ | ||||
|           newSpeed = 900; | ||||
|  | @ -1002,13 +1003,11 @@ namespace Mist{ | |||
|         } | ||||
|       } | ||||
|       if (realTime != newSpeed){ | ||||
|         HIGH_MSG("Changing playback speed from %" PRIu64 " to %" PRIu64 "(%" PRIu64 | ||||
|                  " ms LA, %" PRIu64 " ms mKA, %lu eKA)", | ||||
|                  realTime, newSpeed, needsLookAhead, mKa, extraKeepAway); | ||||
|         firstTime = Util::bootMS() - cTime; | ||||
|         HIGH_MSG("Changing playback speed from %" PRIu64 " to %" PRIu64 "(%" PRIu64 " ms LA, %" PRIu64 " ms mKA, %lu eKA)", realTime, newSpeed, needsLookAhead, mKa, extraKeepAway); | ||||
|         firstTime = Util::bootMS() - (cTime * newSpeed / 1000); | ||||
|         realTime = newSpeed; | ||||
|       } | ||||
|       return false; | ||||
|       if (!noReturn){return false;} | ||||
|     } | ||||
|     // cancel if there are no keys in the main track
 | ||||
|     if (mainTrack == INVALID_TRACK_ID){return false;} | ||||
|  | @ -1167,8 +1166,14 @@ namespace Mist{ | |||
|     while (keepGoing() && (wantRequest || parseData)){ | ||||
|       if (wantRequest){requestHandler();} | ||||
|       if (parseData){ | ||||
|         if (!isInitialized){initialize();} | ||||
|         if (!sentHeader){ | ||||
|         if (!isInitialized){ | ||||
|           initialize(); | ||||
|           if (!isInitialized){ | ||||
|             onFail("Stream initialization failed"); | ||||
|             break; | ||||
|           } | ||||
|         } | ||||
|         if (!sentHeader && keepGoing()){ | ||||
|           DONTEVEN_MSG("sendHeader"); | ||||
|           sendHeader(); | ||||
|         } | ||||
|  | @ -1186,6 +1191,8 @@ namespace Mist{ | |||
|                 Util::sleep(std::min(thisPacket.getTime() - | ||||
|                                          ((((Util::bootMS() - firstTime) * 1000) + maxSkipAhead) / realTime), | ||||
|                                      1000ul)); | ||||
|                 //Make sure we stay responsive to requests and stats while waiting
 | ||||
|                 if (wantRequest){requestHandler();} | ||||
|                 stats(); | ||||
|               } | ||||
|             } | ||||
|  | @ -1217,6 +1224,8 @@ namespace Mist{ | |||
|                 }else{ | ||||
|                   playbackSleep(sleepTime); | ||||
|                 } | ||||
|                 //Make sure we stay responsive to requests and stats while waiting
 | ||||
|                 if (wantRequest){requestHandler();} | ||||
|                 stats(); | ||||
|               } | ||||
|               if (!timeoutTries){ | ||||
|  | @ -1237,6 +1246,7 @@ namespace Mist{ | |||
|                 INFO_MSG("Switching to next push target filename: %s", newTarget.c_str()); | ||||
|                 if (!connectToFile(newTarget)){ | ||||
|                   FAIL_MSG("Failed to open file, aborting: %s", newTarget.c_str()); | ||||
|                   Util::logExitReason("failed to open file, aborting: %s", newTarget.c_str()); | ||||
|                   onFinish(); | ||||
|                   break; | ||||
|                 } | ||||
|  | @ -1247,13 +1257,14 @@ namespace Mist{ | |||
|               }else{ | ||||
|                 if (!onFinish()){ | ||||
|                   INFO_MSG("Shutting down because planned stopping point reached"); | ||||
|                   Util::logExitReason("planned stopping point reached"); | ||||
|                   break; | ||||
|                 } | ||||
|               } | ||||
|             } | ||||
|             sendNext(); | ||||
|           }else{ | ||||
|             INFO_MSG("Shutting down because of stream end"); | ||||
|             Util::logExitReason("end of stream"); | ||||
|             /*LTS-START*/ | ||||
|             if (Triggers::shouldTrigger("CONN_STOP", streamName)){ | ||||
|               std::string payload = | ||||
|  | @ -1265,18 +1276,14 @@ namespace Mist{ | |||
|           } | ||||
|         } | ||||
|         if (!meta){ | ||||
|           Util::Config::logExitReason("No connection to the metadata"); | ||||
|           Util::logExitReason("lost internal connection to stream data"); | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|       stats(); | ||||
|     } | ||||
|     MEDIUM_MSG("MistOut client handler shutting down: %s, %s, %s", | ||||
|                myConn.connected() ? "conn_active" : "conn_closed", wantRequest ? "want_request" : "no_want_request", | ||||
|                parseData ? "parsing_data" : "not_parsing_data"); | ||||
|     if (Util::Config::exitReason.size()){ | ||||
|       INFO_MSG("Logged exit reason: %s", Util::Config::exitReason.c_str()); | ||||
|     } | ||||
|     if (!myConn){Util::logExitReason("remote connection closed");} | ||||
|     INFO_MSG("Client handler shutting down, exit reason: %s", Util::exitReason); | ||||
|     onFinish(); | ||||
| 
 | ||||
|     /*LTS-START*/ | ||||
|  | @ -1460,42 +1467,66 @@ namespace Mist{ | |||
| 
 | ||||
|     DTSC::Keys keys(M.keys(nxt.tid)); | ||||
|     size_t thisKey = keys.getNumForTime(nxt.time); | ||||
|     while (!nextTime && keepGoing()){ | ||||
|       // The next packet is either not available yet, or on another page
 | ||||
|       // Check if we have a next valid packet
 | ||||
|       if (memcmp(curPage[nxt.tid].mapped + nxt.offset + preLoad.getDataLen(), "\000\000\000\000", 4)){ | ||||
|         nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset + preLoad.getDataLen()); | ||||
|         // After 500ms, we assume the time will not update anymore
 | ||||
|         if (++emptyCount >= 20){break;} | ||||
| 
 | ||||
|     // Check if we have a next valid packet
 | ||||
|     if (memcmp(curPage[nxt.tid].mapped + nxt.offset + preLoad.getDataLen(), "\000\000\000\000", 4)){ | ||||
|       nextTime = getDTSCTime(curPage[nxt.tid].mapped, nxt.offset + preLoad.getDataLen()); | ||||
|       if (!nextTime){ | ||||
|         WARN_MSG("Next packet is available, but has no time. Please warn the developers if you see this message!"); | ||||
|         dropTrack(nxt.tid, "EOP: invalid next packet"); | ||||
|         return false; | ||||
|       } | ||||
|     }else{ | ||||
|       //no next packet yet!
 | ||||
|       //Check if this is the last packet of a VoD stream. Return success and drop the track.
 | ||||
|       if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){ | ||||
|         thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); | ||||
|         thisIdx = nxt.tid; | ||||
|         dropTrack(nxt.tid, "end of VoD track reached", false); | ||||
|         return true; | ||||
|       } | ||||
|       //Check if there exists a different page for the next key
 | ||||
|       size_t nextKeyPage = M.getPageNumberForKey(nxt.tid, thisKey + 1); | ||||
|       if (nextKeyPage != INVALID_KEY_NUM && nextKeyPage != currentPage[nxt.tid]){ | ||||
|         // If so, the next key is our next packet
 | ||||
|         nextTime = keys.getTime(thisKey + 1); | ||||
|       }else{ | ||||
|         if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){break;} | ||||
|         if (M.getPageNumberForKey(nxt.tid, thisKey + 1) != currentPage[nxt.tid]){ | ||||
|           // Check if its on a different page
 | ||||
|           nextTime = keys.getTime(thisKey + 1); | ||||
|         } | ||||
|         //Okay, there's no next page yet, and no next packet on this page either.
 | ||||
|         //That means we're waiting for data to show up, somewhere.
 | ||||
|         // after ~25 seconds, give up and drop the track.
 | ||||
|         if (++emptyCount >= 1000){ | ||||
|           // after ~25 seconds, give up and drop the track.
 | ||||
|           dropTrack(nxt.tid, "EOP: data wait timeout"); | ||||
|           return false; | ||||
|         } | ||||
|         Util::sleep(25); | ||||
|         // we're waiting for new data to show up
 | ||||
|         if (emptyCount % 640 == 0){ | ||||
|           reconnect(); // reconnect every 16 seconds
 | ||||
|           // if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
 | ||||
|           if (!meta){return false;} | ||||
|         //every ~1 second, check if the stream is not offline
 | ||||
|         if (emptyCount % 40 == 0 && M.getLive() && Util::getStreamStatus(streamName) == STRMSTAT_OFF){ | ||||
|           Util::logExitReason("Stream source shut down"); | ||||
|           thisPacket.null(); | ||||
|           return true; | ||||
|         } | ||||
|         //every ~16 seconds, reconnect to metadata
 | ||||
|         if (emptyCount % 640 == 0){ | ||||
|           reconnect(); | ||||
|           if (!meta){ | ||||
|             onFail("Could not connect to stream data", true); | ||||
|             thisPacket.null(); | ||||
|             return true; | ||||
|           } | ||||
|           // if we don't have a connection to the metadata here, this means the stream has gone offline in the meanwhile.
 | ||||
|           if (!meta){ | ||||
|             Util::logExitReason("Attempted reconnect to source failed"); | ||||
|             thisPacket.null(); | ||||
|             return true; | ||||
|           } | ||||
|           return false;//no sleep after reconnect
 | ||||
|         } | ||||
|         //Fine! We didn't want a packet, anyway. Let's try again later.
 | ||||
|         Util::sleep(25); | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|     if (M.getVod() && nxt.time >= M.getLastms(nxt.tid)){ | ||||
|       thisPacket.reInit(curPage[nxt.tid].mapped + nxt.offset, 0, true); | ||||
|       thisIdx = nxt.tid; | ||||
|       dropTrack(nxt.tid, "end of VoD track reached", false); | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     // If we don't have a timestamp at all, this is due to a different cause.
 | ||||
|     if (!nextTime && (emptyCount < 20)){return false;} | ||||
|     //If the next packet should've been before the current packet, something is wrong. Abort, abort!
 | ||||
|     if (nextTime < nxt.time){ | ||||
|       dropTrack(nxt.tid, "time going backwards"); | ||||
|       return false; | ||||
|  | @ -1513,7 +1544,7 @@ namespace Mist{ | |||
|     emptyCount = 0; // valid packet - reset empty counter
 | ||||
| 
 | ||||
|     if (!userSelect[nxt.tid].isAlive()){ | ||||
|       INFO_MSG("Track %zu  is not alive!", nxt.tid); | ||||
|       INFO_MSG("Track %zu is not alive!", nxt.tid); | ||||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1526,12 +1557,6 @@ namespace Mist{ | |||
| 
 | ||||
|     // exchange the current packet in the buffer for the next one
 | ||||
|     buffer.erase(buffer.begin()); | ||||
| 
 | ||||
|     if (M.getVod() && nxt.time > M.getLastms(nxt.tid)){ | ||||
|       dropTrack(nxt.tid, "detected last VoD packet"); | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     buffer.insert(nxt); | ||||
| 
 | ||||
|     return true; | ||||
|  | @ -1541,8 +1566,10 @@ namespace Mist{ | |||
|   /// Outputs used as an input should return INPUT, outputs used for automation should return OUTPUT, others should return their proper name.
 | ||||
|   /// The default implementation is usually good enough for all the non-INPUT types.
 | ||||
|   std::string Output::getStatsName(){ | ||||
|     if (isPushing()){return "INPUT";} | ||||
|     if (config->hasOption("target") && config->getString("target").size()){return "OUTPUT";} | ||||
|     if (isPushing()){return "INPUT:" + capa["name"].asStringRef();} | ||||
|     if (config->hasOption("target") && config->getString("target").size()){ | ||||
|       return "OUTPUT:" + capa["name"].asStringRef(); | ||||
|     } | ||||
|     return capa["name"].asStringRef(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -1554,6 +1581,25 @@ namespace Mist{ | |||
|     uint64_t now = Util::bootSecs(); | ||||
|     if (now == lastStats && !force){return;} | ||||
| 
 | ||||
|     if (isRecording()){ | ||||
|       static uint64_t lastPushUpdate = now; | ||||
|       if (lastPushUpdate + 5 <= now){ | ||||
|         JSON::Value pStat; | ||||
|         pStat["push_status_update"]["id"] = getpid(); | ||||
|         JSON::Value & pData = pStat["push_status_update"]["status"]; | ||||
|         pData["mediatime"] = currentTime(); | ||||
|         for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|           pData["tracks"].append(it->first); | ||||
|         } | ||||
|         pData["bytes"] = myConn.dataUp(); | ||||
|         pData["active_seconds"] = (now - myConn.connTime()); | ||||
|         Socket::UDPConnection uSock; | ||||
|         uSock.SetDestination("localhost", 4242); | ||||
|         uSock.SendNow(pStat.toString()); | ||||
|         lastPushUpdate = now; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (!statComm){statComm.reload();} | ||||
|     if (!statComm){return;} | ||||
| 
 | ||||
|  | @ -1562,16 +1608,13 @@ namespace Mist{ | |||
|     HIGH_MSG("Writing stats: %s, %s, %u, %lu, %lu", getConnectedHost().c_str(), streamName.c_str(), | ||||
|              crc & 0xFFFFFFFFu, myConn.dataUp(), myConn.dataDown()); | ||||
|     /*LTS-START*/ | ||||
|     if (statComm.getStatus() == COMM_STATUS_DISCONNECT){ | ||||
|     if (statComm.getStatus() == COMM_STATUS_REQDISCONNECT){ | ||||
|       onFail("Shutting down on controller request"); | ||||
|       return; | ||||
|     } | ||||
|     /*LTS-END*/ | ||||
|     statComm.setNow(now); | ||||
|     if (statComm.getHost() == | ||||
|         std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16)){ | ||||
|       statComm.setHost(getConnectedBinHost()); | ||||
|     } | ||||
|     statComm.setHost(getConnectedBinHost()); | ||||
|     statComm.setCRC(crc); | ||||
|     statComm.setStream(streamName); | ||||
|     statComm.setConnector(getStatsName()); | ||||
|  | @ -1597,16 +1640,38 @@ namespace Mist{ | |||
| 
 | ||||
|     doSync(); | ||||
| 
 | ||||
|     for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|       it->second.keepAlive(); | ||||
|       if (it->second.isAlive() && M.getLive() && it->second.getStatus() & COMM_STATUS_SOURCE){ | ||||
|         if (Util::bootSecs() - M.getLastUpdated(it->first) > 3){ | ||||
|           INFO_MSG("Not updating data for track %zu?", it->first); | ||||
|     if (isPushing()){ | ||||
|       for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|         it->second.keepAlive(); | ||||
|         if (it->second.getStatus() == COMM_STATUS_REQDISCONNECT){ | ||||
|           if (dropPushTrack(it->second.getTrack(), "disconnect request from buffer")){break;} | ||||
|         } | ||||
|         if (!it->second.isAlive()){ | ||||
|           if (dropPushTrack(it->second.getTrack(), "track mapping no longer valid")){break;} | ||||
|         } | ||||
|         //if (Util::bootSecs() - M.getLastUpdated(it->first) > 5){
 | ||||
|         //  if (dropPushTrack(it->second.getTrack(), "track updates being ignored by buffer")){break;}
 | ||||
|         //}
 | ||||
|       } | ||||
|     }else{ | ||||
|       for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|         it->second.keepAlive(); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   bool Output::dropPushTrack(uint32_t trackId, const std::string & dropReason){ | ||||
|     for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|       if (it->second.getTrack() == trackId){ | ||||
|         WARN_MSG("Dropping input track %" PRIu32 ": %s", trackId, dropReason.c_str()); | ||||
|         userSelect.erase(it); | ||||
|         return true; | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| 
 | ||||
|   void Output::onRequest(){ | ||||
|     // simply clear the buffer, we don't support any kind of input by default
 | ||||
|     myConn.Received().clear(); | ||||
|  | @ -1651,7 +1716,7 @@ namespace Mist{ | |||
|     // Initialize the stream source if needed, connect to it
 | ||||
|     waitForStreamPushReady(); | ||||
|     // pull the source setting from metadata
 | ||||
|     strmSource = meta.getSource(); | ||||
|     if (meta){strmSource = meta.getSource();} | ||||
| 
 | ||||
|     if (!strmSource.size()){ | ||||
|       FAIL_MSG("Push rejected - stream %s not configured or unavailable", streamName.c_str()); | ||||
|  | @ -1683,13 +1748,10 @@ namespace Mist{ | |||
|       } | ||||
|     } | ||||
| 
 | ||||
|     std::string smp = streamName.substr(0, streamName.find_first_of("+ ")); | ||||
|     if (Triggers::shouldTrigger("STREAM_PUSH", smp)){ | ||||
|       std::string payload = | ||||
|           streamName + "\n" + getConnectedHost() + "\n" + capa["name"].asStringRef() + "\n" + reqUrl; | ||||
|       if (!Triggers::doTrigger("STREAM_PUSH", payload, smp)){ | ||||
|         FAIL_MSG("Push from %s to %s rejected - STREAM_PUSH trigger denied the push", | ||||
|                  getConnectedHost().c_str(), streamName.c_str()); | ||||
|     if (Triggers::shouldTrigger("STREAM_PUSH", streamName)){ | ||||
|       std::string payload = streamName + "\n" + getConnectedHost() + "\n" + capa["name"].asStringRef() + "\n" + reqUrl; | ||||
|       if (!Triggers::doTrigger("STREAM_PUSH", payload, streamName)){ | ||||
|         WARN_MSG("Push from %s rejected by STREAM_PUSH trigger", getConnectedHost().c_str()); | ||||
|         pushing = false; | ||||
|         return false; | ||||
|       } | ||||
|  | @ -1698,8 +1760,7 @@ namespace Mist{ | |||
| 
 | ||||
|     if (IP != ""){ | ||||
|       if (!myConn.isAddress(IP)){ | ||||
|         FAIL_MSG("Push from %s to %s rejected - source host not whitelisted", | ||||
|                  getConnectedHost().c_str(), streamName.c_str()); | ||||
|         WARN_MSG("Push from %s rejected; not whitelisted", getConnectedHost().c_str()); | ||||
|         pushing = false; | ||||
|         return false; | ||||
|       } | ||||
|  | @ -1712,7 +1773,31 @@ namespace Mist{ | |||
|   void Output::waitForStreamPushReady(){ | ||||
|     uint8_t streamStatus = Util::getStreamStatus(streamName); | ||||
|     MEDIUM_MSG("Current status for %s buffer is %u", streamName.c_str(), streamStatus); | ||||
|     while (streamStatus != STRMSTAT_WAIT && streamStatus != STRMSTAT_READY && keepGoing()){ | ||||
|     if (streamStatus == STRMSTAT_READY){ | ||||
|       reconnect(); | ||||
|       std::set<size_t> vTracks = M.getValidTracks(true); | ||||
|       INFO_MSG("Stream already active (%zu valid tracks) - check if it's not shutting down...", vTracks.size()); | ||||
|       uint64_t oneTime = 0; | ||||
|       uint64_t twoTime = 0; | ||||
|       for (std::set<size_t>::iterator it = vTracks.begin(); it != vTracks.end(); ++it){ | ||||
|         if (M.getLastms(*it) > oneTime){oneTime = M.getLastms(*it);} | ||||
|       } | ||||
|       Util::wait(2000); | ||||
|       for (std::set<size_t>::iterator it = vTracks.begin(); it != vTracks.end(); ++it){ | ||||
|         if (M.getLastms(*it) > twoTime){twoTime = M.getLastms(*it);} | ||||
|       } | ||||
|       if (twoTime <= oneTime+500){ | ||||
|         disconnect(); | ||||
|         INFO_MSG("Waiting for stream reset before attempting push input accept (%" PRIu64 " <= %" PRIu64 "+500)", twoTime, oneTime); | ||||
|         while (streamStatus != STRMSTAT_OFF && keepGoing()){ | ||||
|           userSelect.clear(); | ||||
|           Util::wait(1000); | ||||
|           streamStatus = Util::getStreamStatus(streamName); | ||||
|         } | ||||
|         reconnect(); | ||||
|       } | ||||
|     } | ||||
|     while (((streamStatus != STRMSTAT_WAIT && streamStatus != STRMSTAT_READY) || !meta) && keepGoing()){ | ||||
|       INFO_MSG("Waiting for %s buffer to be ready... (%u)", streamName.c_str(), streamStatus); | ||||
|       disconnect(); | ||||
|       userSelect.clear(); | ||||
|  | @ -1720,11 +1805,15 @@ namespace Mist{ | |||
|       streamStatus = Util::getStreamStatus(streamName); | ||||
|       if (streamStatus == STRMSTAT_OFF || streamStatus == STRMSTAT_WAIT || streamStatus == STRMSTAT_READY){ | ||||
|         INFO_MSG("Reconnecting to %s buffer... (%u)", streamName.c_str(), streamStatus); | ||||
|         Util::wait(500); | ||||
|         reconnect(); | ||||
|         streamStatus = Util::getStreamStatus(streamName); | ||||
|       } | ||||
|     } | ||||
|     if (streamStatus == STRMSTAT_READY || streamStatus == STRMSTAT_WAIT){reconnect();} | ||||
|     if (!meta){ | ||||
|       onFail("Could not connect to stream data", true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void Output::selectAllTracks(){ | ||||
|  |  | |||
|  | @ -65,6 +65,7 @@ namespace Mist{ | |||
|     /// This function is called whenever a packet is ready for sending.
 | ||||
|     /// Inside it, thisPacket is guaranteed to contain a valid packet.
 | ||||
|     virtual void sendNext(){}// REQUIRED! Others are optional.
 | ||||
|     virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason); | ||||
|     bool getKeyFrame(); | ||||
|     bool prepareNext(); | ||||
|     virtual void dropTrack(size_t trackId, const std::string &reason, bool probablyBad = true); | ||||
|  | @ -105,6 +106,7 @@ namespace Mist{ | |||
|     std::set<sortedPageInfo> buffer; ///< A sorted list of next-to-be-loaded packets.
 | ||||
|     bool sought; ///< If a seek has been done, this is set to true. Used for seeking on
 | ||||
|                  ///< prepareNext().
 | ||||
|     std::string prevHost; ///< Old value for getConnectedBinHost, for caching
 | ||||
|   protected:     // these are to be messed with by child classes
 | ||||
|     virtual bool inlineRestartCapable() const{ | ||||
|       return false; | ||||
|  | @ -128,6 +130,7 @@ namespace Mist{ | |||
|     Comms::Statistics statComm; | ||||
|     bool isBlocking; ///< If true, indicates that myConn is blocking.
 | ||||
|     uint32_t crc;    ///< Checksum, if any, for usage in the stats.
 | ||||
|     uint64_t nextKeyTime(); | ||||
| 
 | ||||
|     // stream delaying variables
 | ||||
|     uint64_t maxSkipAhead;   ///< Maximum ms that we will go ahead of the intended timestamps.
 | ||||
|  | @ -148,7 +151,6 @@ namespace Mist{ | |||
|     virtual bool isPushing(){return pushing;}; | ||||
|     bool allowPush(const std::string &passwd); | ||||
|     void waitForStreamPushReady(); | ||||
|     bool pushIsOngoing; | ||||
| 
 | ||||
|     uint64_t firstPacketTime; | ||||
|     uint64_t lastPacketTime; | ||||
|  |  | |||
|  | @ -13,6 +13,8 @@ | |||
| #include <mist/stream.h> | ||||
| #include <mist/timing.h> | ||||
| 
 | ||||
| uint64_t bootMsOffset; | ||||
| 
 | ||||
| namespace Mist{ | ||||
| 
 | ||||
|   OutCMAF::OutCMAF(Socket::Connection &conn) : HTTPOutput(conn){ | ||||
|  | @ -68,6 +70,8 @@ namespace Mist{ | |||
| 
 | ||||
|   void OutCMAF::onHTTP(){ | ||||
|     initialize(); | ||||
|     bootMsOffset = 0; | ||||
|     if (M.getLive()){bootMsOffset = M.getBootMsOffset();} | ||||
| 
 | ||||
|     if (H.url.size() < streamName.length() + 7){ | ||||
|       H.Clean(); | ||||
|  | @ -440,6 +444,12 @@ namespace Mist{ | |||
|   } | ||||
| 
 | ||||
|   void hlsSegment(uint64_t start, uint64_t duration, std::stringstream &s, bool first){ | ||||
|     if (bootMsOffset){ | ||||
|       uint64_t unixMs = start + bootMsOffset + (Util::unixMS() - Util::bootMS()); | ||||
|       time_t uSecs = unixMs/1000; | ||||
|       struct tm * tVal = gmtime(&uSecs); | ||||
|       s << "#EXT-X-PROGRAM-DATE-TIME: " << (tVal->tm_year+1900) << "-" << std::setw(2) << std::setfill('0') << (tVal->tm_mon+1) << "-" << std::setw(2) << std::setfill('0') << tVal->tm_mday << "T" << std::setw(2) << std::setfill('0') << tVal->tm_hour << ":" << std::setw(2) << std::setfill('0') << tVal->tm_min << ":" << std::setw(2) << std::setfill('0') << tVal->tm_sec << "." << std::setw(3) << std::setfill('0') << (unixMs%1000) << "Z" << std::endl; | ||||
|     } | ||||
|     s << "#EXTINF:" << (((double)duration) / 1000) << ",\r\nchunk_" << start << ".m4s" << std::endl; | ||||
|   } | ||||
| 
 | ||||
|  | @ -537,7 +547,6 @@ namespace Mist{ | |||
| 
 | ||||
|     result << "#EXTM3U\r\n" | ||||
|               "#EXT-X-VERSION:7\r\n" | ||||
|               "#EXT-X-DISCONTINUITY\r\n" | ||||
|               "#EXT-X-TARGETDURATION:" | ||||
|            << targetDuration << "\r\n"; | ||||
|     if (M.getLive()){result << "#EXT-X-MEDIA-SEQUENCE:" << firstFragment << "\r\n";} | ||||
|  |  | |||
|  | @ -63,7 +63,7 @@ namespace Mist{ | |||
|     config = cfg; | ||||
|   } | ||||
| 
 | ||||
|   std::string OutDTSC::getStatsName(){return (pushing ? "INPUT" : "OUTPUT");} | ||||
|   std::string OutDTSC::getStatsName(){return (pushing ? "INPUT:DTSC" : "OUTPUT:DTSC");} | ||||
| 
 | ||||
|   /// Seeks to the first sync'ed keyframe of the main track.
 | ||||
|   /// Aborts if there is no main track or it has no keyframes.
 | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ | |||
| 
 | ||||
| namespace Mist{ | ||||
|   bool OutHLS::isReadyForPlay(){ | ||||
|     if (!isInitialized){initialize();} | ||||
|     meta.refresh(); | ||||
|     if (!M.getValidTracks().size()){return false;} | ||||
|     uint32_t mainTrack = M.mainTrack(); | ||||
|     if (mainTrack == INVALID_TRACK_ID){return false;} | ||||
|  | @ -244,7 +246,11 @@ namespace Mist{ | |||
|       bool isTS = (HTTP::URL(H.url).getExt().substr(0, 3) != "m3u"); | ||||
|       H.Clean(); | ||||
|       H.setCORSHeaders(); | ||||
|       H.SetHeader("Content-Type", "application/octet-stream"); | ||||
|       if (isTS){ | ||||
|         H.SetHeader("Content-Type", "video/mp2t"); | ||||
|       }else{ | ||||
|         H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); | ||||
|       } | ||||
|       if (isTS && !hasSessionIDs()){ | ||||
|         H.SetHeader("Cache-Control", "public, max-age=600, immutable"); | ||||
|         H.SetHeader("Pragma", ""); | ||||
|  | @ -343,6 +349,7 @@ namespace Mist{ | |||
|       std::string request = H.url.substr(H.url.find("/", 5) + 1); | ||||
|       H.Clean(); | ||||
|       H.setCORSHeaders(); | ||||
|       H.SetHeader("Content-Type", "application/vnd.apple.mpegurl"); | ||||
|       if (!M.getValidTracks().size()){ | ||||
|         H.SendResponse("404", "Not online or found", myConn); | ||||
|         H.Clean(); | ||||
|  | @ -378,16 +385,14 @@ namespace Mist{ | |||
|       wantRequest = true; | ||||
|       parseData = false; | ||||
| 
 | ||||
|       // Ensure alignment of contCounters for selected tracks, to prevent discontinuities.
 | ||||
|       for (std::map<size_t, Comms::Users>::iterator it = userSelect.begin(); it != userSelect.end(); it++){ | ||||
|         uint32_t pkgPid = 255 + it->first; | ||||
|         uint16_t &contPkg = contCounters[pkgPid]; | ||||
|         if (contPkg % 16 != 0){ | ||||
|       // Ensure alignment of contCounters, to prevent discontinuities.
 | ||||
|       for (std::map<size_t, uint16_t>::iterator it = contCounters.begin(); it != contCounters.end(); it++){ | ||||
|         if (it->second % 16 != 0){ | ||||
|           packData.clear(); | ||||
|           packData.setPID(pkgPid); | ||||
|           packData.setPID(it->first); | ||||
|           packData.addStuffing(); | ||||
|           while (contPkg % 16 != 0){ | ||||
|             packData.setContinuityCounter(++contPkg); | ||||
|           while (it->second % 16 != 0){ | ||||
|             packData.setContinuityCounter(++it->second); | ||||
|             sendTS(packData.checkAndGetBuffer()); | ||||
|           } | ||||
|           packData.clear(); | ||||
|  |  | |||
|  | @ -111,6 +111,7 @@ namespace Mist{ | |||
|     capa["provides"] = "HTTP"; | ||||
|     capa["protocol"] = "http://"; | ||||
|     capa["url_rel"] = "/$.html"; | ||||
|     capa["codecs"][0u][0u].append("+*"); | ||||
|     capa["url_match"].append("/crossdomain.xml"); | ||||
|     capa["url_match"].append("/clientaccesspolicy.xml"); | ||||
|     capa["url_match"].append("/$.html"); | ||||
|  | @ -1028,7 +1029,7 @@ namespace Mist{ | |||
|     snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_STATE, streamName.c_str()); | ||||
|     IPC::sharedPage streamStatus(pageName, 1, false, false); | ||||
|     uint8_t prevState, newState, pingCounter = 0; | ||||
|     uint64_t prevTracks; | ||||
|     std::set<size_t> prevTracks; | ||||
|     prevState = newState = STRMSTAT_INVALID; | ||||
|     while (keepGoing()){ | ||||
|       if (!streamStatus || !streamStatus.exists()){streamStatus.init(pageName, 1, false, false);} | ||||
|  | @ -1038,10 +1039,10 @@ namespace Mist{ | |||
|         newState = streamStatus.mapped[0]; | ||||
|       } | ||||
| 
 | ||||
|       if (newState != prevState || (newState == STRMSTAT_READY && M.getValidTracks().size() != prevTracks)){ | ||||
|       if (newState != prevState || (newState == STRMSTAT_READY && M.getValidTracks() != prevTracks)){ | ||||
|         if (newState == STRMSTAT_READY){ | ||||
|           reconnect(); | ||||
|           prevTracks = M.getValidTracks().size(); | ||||
|           prevTracks = M.getValidTracks(); | ||||
|         }else{ | ||||
|           disconnect(); | ||||
|         } | ||||
|  |  | |||
|  | @ -92,6 +92,7 @@ namespace Mist{ | |||
|         char error_buf[200]; | ||||
|         mbedtls_strerror(ret, error_buf, 200); | ||||
|         MEDIUM_MSG("Could not handshake, SSL error: %s (%d)", error_buf, ret); | ||||
|         Util::logExitReason("Could not handshake, SSL error: %s (%d)", error_buf, ret); | ||||
|         C.close(); | ||||
|         return; | ||||
|       }else{ | ||||
|  | @ -110,6 +111,7 @@ namespace Mist{ | |||
|     int fd[2]; | ||||
|     if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fd) != 0){ | ||||
|       FAIL_MSG("Could not open anonymous socket for SSL<->HTTP connection!"); | ||||
|       Util::logExitReason("Could not open anonymous socket for SSL<->HTTP connection!"); | ||||
|       return 1; | ||||
|     } | ||||
|     std::deque<std::string> args; | ||||
|  | @ -135,6 +137,7 @@ namespace Mist{ | |||
|     close(fd[1]); | ||||
|     if (http_proc < 2){ | ||||
|       FAIL_MSG("Could not spawn MistOutHTTP process for SSL connection!"); | ||||
|       Util::logExitReason("Could not spawn MistOutHTTP process for SSL connection!"); | ||||
|       return 1; | ||||
|     } | ||||
|     Socket::Connection http(fd[0]); | ||||
|  | @ -150,6 +153,7 @@ namespace Mist{ | |||
|       if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE){ | ||||
|         if (ret <= 0){ | ||||
|           HIGH_MSG("SSL disconnect!"); | ||||
|           Util::logExitReason("SSL client disconnected"); | ||||
|           break; | ||||
|         } | ||||
|         // we received ret bytes of data to pass on. Do so.
 | ||||
|  | @ -168,6 +172,7 @@ namespace Mist{ | |||
|             ret = mbedtls_ssl_write(&ssl, (const unsigned char *)http_buf.get().data() + done, toSend - done); | ||||
|             if (ret == MBEDTLS_ERR_NET_CONN_RESET || ret == MBEDTLS_ERR_SSL_CLIENT_RECONNECT){ | ||||
|               HIGH_MSG("SSL disconnect!"); | ||||
|               Util::logExitReason("SSL client disconnected"); | ||||
|               http.close(); | ||||
|               break; | ||||
|             } | ||||
|  |  | |||
|  | @ -1229,7 +1229,7 @@ namespace Mist{ | |||
|         static std::map<size_t, AMF::Object> pushMeta; | ||||
|         static std::map<size_t, uint64_t> lastTagTime; | ||||
|         static std::map<size_t, size_t> reTrackToID; | ||||
|         if (!isInitialized){ | ||||
|         if (!isInitialized || !meta){ | ||||
|           MEDIUM_MSG("Received useless media data"); | ||||
|           onFinish(); | ||||
|           break; | ||||
|  |  | |||
|  | @ -91,6 +91,8 @@ namespace Mist{ | |||
|     capa["codecs"][0u][0u].append("H264"); | ||||
|     capa["codecs"][0u][0u].append("HEVC"); | ||||
|     capa["codecs"][0u][0u].append("MPEG2"); | ||||
|     capa["codecs"][0u][0u].append("VP8"); | ||||
|     capa["codecs"][0u][0u].append("VP9"); | ||||
|     capa["codecs"][0u][1u].append("AAC"); | ||||
|     capa["codecs"][0u][1u].append("MP3"); | ||||
|     capa["codecs"][0u][1u].append("AC3"); | ||||
|  |  | |||
|  | @ -189,7 +189,7 @@ namespace Mist{ | |||
| 
 | ||||
|   std::string OutTS::getStatsName(){ | ||||
|     if (!parseData){ | ||||
|       return "INPUT"; | ||||
|       return "INPUT:" + capa["name"].asStringRef(); | ||||
|     }else{ | ||||
|       return Output::getStatsName(); | ||||
|     } | ||||
|  |  | |||
|  | @ -65,17 +65,12 @@ namespace Mist{ | |||
|     std::string type = M.getType(thisIdx); | ||||
|     std::string codec = M.getCodec(thisIdx); | ||||
|     bool video = (type == "video"); | ||||
| 
 | ||||
|     size_t pkgPid = M.getID(thisIdx); | ||||
|     if (pkgPid < 255){pkgPid += 255;} | ||||
| 
 | ||||
|     size_t pkgPid = TS::getUniqTrackID(M, thisIdx); | ||||
|     bool &firstPack = first[thisIdx]; | ||||
|     uint16_t &contPkg = contCounters[pkgPid]; | ||||
| 
 | ||||
|     uint64_t packTime = thisPacket.getTime(); | ||||
|     bool keyframe = thisPacket.getInt("keyframe"); | ||||
|     firstPack = true; | ||||
| 
 | ||||
|     char *dataPointer = 0; | ||||
|     size_t dataLen = 0; | ||||
|     thisPacket.getString("data", dataPointer, dataLen); // data
 | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
| #include <mist/procs.h> | ||||
| #include <mist/sdp.h> | ||||
| #include <mist/timing.h> | ||||
| #include <mist/url.h> | ||||
| #include <netdb.h> // ifaddr, listing ip addresses.
 | ||||
| 
 | ||||
| namespace Mist{ | ||||
|  | @ -31,10 +32,12 @@ namespace Mist{ | |||
|   /* ------------------------------------------------ */ | ||||
| 
 | ||||
|   OutWebRTC::OutWebRTC(Socket::Connection &myConn) : HTTPOutput(myConn){ | ||||
|     lastPackMs = 0; | ||||
|     vidTrack = INVALID_TRACK_ID; | ||||
|     prevVidTrack = INVALID_TRACK_ID; | ||||
|     audTrack = INVALID_TRACK_ID; | ||||
|     stayLive = true; | ||||
|     target_rate = 0.0; | ||||
|     firstKey = true; | ||||
|     repeatInit = true; | ||||
| 
 | ||||
|  | @ -95,6 +98,7 @@ namespace Mist{ | |||
|     capa["url_match"] = "/webrtc/$"; | ||||
|     capa["codecs"][0u][0u].append("H264"); | ||||
|     capa["codecs"][0u][0u].append("VP8"); | ||||
|     capa["codecs"][0u][0u].append("VP9"); | ||||
|     capa["codecs"][0u][1u].append("opus"); | ||||
|     capa["codecs"][0u][1u].append("ALAW"); | ||||
|     capa["codecs"][0u][1u].append("ULAW"); | ||||
|  | @ -107,7 +111,7 @@ namespace Mist{ | |||
|     capa["optional"]["preferredvideocodec"]["help"] = | ||||
|         "Comma separated list of video codecs you want to support in preferred order. e.g. " | ||||
|         "H264,VP8"; | ||||
|     capa["optional"]["preferredvideocodec"]["default"] = "H264,VP8"; | ||||
|     capa["optional"]["preferredvideocodec"]["default"] = "H264,VP9,VP8"; | ||||
|     capa["optional"]["preferredvideocodec"]["type"] = "string"; | ||||
|     capa["optional"]["preferredvideocodec"]["option"] = "--webrtc-video-codecs"; | ||||
|     capa["optional"]["preferredvideocodec"]["short"] = "V"; | ||||
|  | @ -129,14 +133,26 @@ namespace Mist{ | |||
|     capa["optional"]["bindhost"]["option"] = "--bindhost"; | ||||
|     capa["optional"]["bindhost"]["short"] = "B"; | ||||
| 
 | ||||
|     capa["optional"]["mergesessions"]["name"] = "Merge sessions"; | ||||
|     capa["optional"]["mergesessions"]["name"] = "merge sessions"; | ||||
|     capa["optional"]["mergesessions"]["help"] = | ||||
|         "If enabled, merges together all views from a single user into a single combined session. " | ||||
|         "If disabled, each view (reconnection of the signalling websocket) is a separate session."; | ||||
|         "if enabled, merges together all views from a single user into a single combined session. " | ||||
|         "if disabled, each view (reconnection of the signalling websocket) is a separate session."; | ||||
|     capa["optional"]["mergesessions"]["option"] = "--mergesessions"; | ||||
|     capa["optional"]["mergesessions"]["short"] = "m"; | ||||
|     capa["optional"]["mergesessions"]["default"] = 0; | ||||
| 
 | ||||
|     capa["optional"]["nackdisable"]["name"] = "Disallow NACKs for viewers"; | ||||
|     capa["optional"]["nackdisable"]["help"] = "Disallows viewers to send NACKs for lost packets"; | ||||
|     capa["optional"]["nackdisable"]["option"] = "--nackdisable"; | ||||
|     capa["optional"]["nackdisable"]["short"] = "n"; | ||||
|     capa["optional"]["nackdisable"]["default"] = 0; | ||||
| 
 | ||||
|     capa["optional"]["jitterlog"]["name"] = "Write jitter log"; | ||||
|     capa["optional"]["jitterlog"]["help"] = "Writes log of frame transmit jitter to /tmp/ for each outgoing connection"; | ||||
|     capa["optional"]["jitterlog"]["option"] = "--jitterlog"; | ||||
|     capa["optional"]["jitterlog"]["short"] = "J"; | ||||
|     capa["optional"]["jitterlog"]["default"] = 0; | ||||
| 
 | ||||
|     config->addOptionsFromCapabilities(capa); | ||||
|   } | ||||
| 
 | ||||
|  | @ -280,13 +296,61 @@ namespace Mist{ | |||
|         parseData = true; | ||||
|         selectDefaultTracks(); | ||||
|       } | ||||
|       stayLive = (endTime() < seek_time + 5000); | ||||
|       stayLive = (target_rate == 0.0) && (endTime() < seek_time + 5000); | ||||
|       if (command["seek_time"].asStringRef() == "live"){stayLive = true;} | ||||
|       if (stayLive){seek_time = endTime();} | ||||
|       seek(seek_time, true); | ||||
|       JSON::Value commandResult; | ||||
|       commandResult["type"] = "on_seek"; | ||||
|       commandResult["result"] = true; | ||||
|       if (M.getLive()){commandResult["live_point"] = stayLive;} | ||||
|       if (target_rate == 0.0){ | ||||
|         commandResult["play_rate_curr"] = "auto"; | ||||
|       }else{ | ||||
|         commandResult["play_rate_curr"] = target_rate; | ||||
|       } | ||||
|       webSock->sendFrame(commandResult.toString()); | ||||
|       onIdle(); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (command["type"] == "set_speed"){ | ||||
|       if (!command.isMember("play_rate")){ | ||||
|         sendSignalingError("on_speed", "Received a playback speed setting request but no `play_rate` property."); | ||||
|         return; | ||||
|       } | ||||
|       double set_rate = command["play_rate"].asDouble(); | ||||
|       if (!parseData){ | ||||
|         parseData = true; | ||||
|         selectDefaultTracks(); | ||||
|       } | ||||
|       JSON::Value commandResult; | ||||
|       commandResult["type"] = "on_speed"; | ||||
|       if (target_rate == 0.0){ | ||||
|         commandResult["play_rate_prev"] = "auto"; | ||||
|       }else{ | ||||
|         commandResult["play_rate_prev"] = target_rate; | ||||
|       } | ||||
|       if (set_rate == 0.0){ | ||||
|         commandResult["play_rate_curr"] = "auto"; | ||||
|       }else{ | ||||
|         commandResult["play_rate_curr"] = set_rate; | ||||
|       } | ||||
|       if (target_rate != set_rate){ | ||||
|         target_rate = set_rate; | ||||
|         if (target_rate == 0.0){ | ||||
|           realTime = 1000;//set playback speed to default
 | ||||
|           firstTime = Util::bootMS() - currentTime(); | ||||
|           maxSkipAhead = 0;//enabled automatic rate control
 | ||||
|         }else{ | ||||
|           stayLive = false; | ||||
|           //Set new realTime speed
 | ||||
|           realTime = 1000 / target_rate; | ||||
|           firstTime = Util::bootMS() - (currentTime() / target_rate); | ||||
|           maxSkipAhead = 1;//disable automatic rate control
 | ||||
|         } | ||||
|       } | ||||
|       if (M.getLive()){commandResult["live_point"] = stayLive;} | ||||
|       webSock->sendFrame(commandResult.toString()); | ||||
|       onIdle(); | ||||
|       return; | ||||
|  | @ -337,6 +401,15 @@ namespace Mist{ | |||
|     sendSignalingError(command["type"].asString(), "Unhandled command type: " + command["type"].asString()); | ||||
|   } | ||||
| 
 | ||||
|   bool OutWebRTC::dropPushTrack(uint32_t trackId, const std::string & dropReason){ | ||||
|     JSON::Value commandResult; | ||||
|     commandResult["type"] = "on_track_drop"; | ||||
|     commandResult["track"] = trackId; | ||||
|     commandResult["mediatype"] = M.getType(trackId); | ||||
|     webSock->sendFrame(commandResult.toString()); | ||||
|     return Output::dropPushTrack(trackId, dropReason); | ||||
|   } | ||||
| 
 | ||||
|   void OutWebRTC::sendSignalingError(const std::string &commandType, const std::string &errorMessage){ | ||||
|     JSON::Value commandResult; | ||||
|     commandResult["type"] = "on_error"; | ||||
|  | @ -357,6 +430,12 @@ namespace Mist{ | |||
|     initialize(); | ||||
|     selectDefaultTracks(); | ||||
| 
 | ||||
|     if (config && config->hasOption("jitterlog") && config->getBool("jitterlog")){ | ||||
|       std::string fileName = "/tmp/jitter_"+JSON::Value(getpid()).asString(); | ||||
|       jitterLog.open(fileName.c_str()); | ||||
|       lastPackMs = Util::bootMS(); | ||||
|     } | ||||
| 
 | ||||
|     if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);} | ||||
| 
 | ||||
|     std::string videoCodec; | ||||
|  | @ -389,8 +468,10 @@ namespace Mist{ | |||
|           return false; | ||||
|         } | ||||
|         videoTrack.rtpPacketizer = RTP::Packet(videoTrack.payloadType, rand(), 0, videoTrack.SSRC, 0); | ||||
|         // Enabled NACKs
 | ||||
|         sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK; | ||||
|         if (!config || !config->hasOption("nackdisable") || !config->getBool("nackdisable")){ | ||||
|           // Enable NACKs
 | ||||
|           sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK; | ||||
|         } | ||||
|         videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention; | ||||
|       } | ||||
|     } | ||||
|  | @ -500,7 +581,7 @@ namespace Mist{ | |||
| 
 | ||||
|     capa["codecs"].null(); | ||||
| 
 | ||||
|     const char *videoCodecPreference[] ={"H264", "VP8", NULL}; | ||||
|     const char *videoCodecPreference[] ={"H264", "VP9", "VP8", NULL}; | ||||
|     const char **videoCodec = videoCodecPreference; | ||||
|     SDP::Media *videoMediaOffer = sdpSession.getMediaForType("video"); | ||||
|     if (videoMediaOffer){ | ||||
|  | @ -535,12 +616,12 @@ namespace Mist{ | |||
| 
 | ||||
|     if (0 == udpPort){bindUDPSocketOnLocalCandidateAddress(0);} | ||||
| 
 | ||||
|     std::string prefVideoCodec = "VP8,H264"; | ||||
|     std::string prefVideoCodec = "VP9,VP8,H264"; | ||||
|     if (config && config->hasOption("preferredvideocodec")){ | ||||
|       prefVideoCodec = config->getString("preferredvideocodec"); | ||||
|       if (prefVideoCodec.empty()){ | ||||
|         WARN_MSG("No preferred video codec value set; resetting to default."); | ||||
|         prefVideoCodec = "VP8,H264"; | ||||
|         prefVideoCodec = "VP9,VP8,H264"; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|  | @ -579,10 +660,11 @@ namespace Mist{ | |||
| 
 | ||||
|       SDP::MediaFormat *fmtRED = sdpSession.getMediaFormatByEncodingName("video", "RED"); | ||||
|       SDP::MediaFormat *fmtULPFEC = sdpSession.getMediaFormatByEncodingName("video", "ULPFEC"); | ||||
|       if (fmtRED && fmtULPFEC){ | ||||
|       if (fmtRED || fmtULPFEC){ | ||||
|         videoTrack.ULPFECPayloadType = fmtULPFEC->payloadType; | ||||
|         videoTrack.REDPayloadType = fmtRED->payloadType; | ||||
|         payloadTypeToWebRTCTrack[fmtRED->payloadType] = videoTrack.payloadType; | ||||
|         payloadTypeToWebRTCTrack[fmtULPFEC->payloadType] = videoTrack.payloadType; | ||||
|       } | ||||
|       sdpAnswer.videoLossPrevention = SDP_LOSS_PREVENTION_NACK; | ||||
|       videoTrack.sorter.tmpVideoLossPrevention = sdpAnswer.videoLossPrevention; | ||||
|  | @ -594,7 +676,7 @@ namespace Mist{ | |||
| 
 | ||||
|       videoTrack.rtpToDTSC.setProperties(meta, vIdx); | ||||
|       videoTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback); | ||||
|       videoTrack.sorter.setCallback(vIdx, onRTPSorterHasPacketCallback); | ||||
|       videoTrack.sorter.setCallback(M.getID(vIdx), onRTPSorterHasPacketCallback); | ||||
| 
 | ||||
|       userSelect[vIdx].reload(streamName, vIdx, COMM_STATUS_SOURCE); | ||||
|       INFO_MSG("Video push received on track %zu", vIdx); | ||||
|  | @ -616,7 +698,7 @@ namespace Mist{ | |||
| 
 | ||||
|       audioTrack.rtpToDTSC.setProperties(meta, aIdx); | ||||
|       audioTrack.rtpToDTSC.setCallbacks(onDTSCConverterHasPacketCallback, onDTSCConverterHasInitDataCallback); | ||||
|       audioTrack.sorter.setCallback(aIdx, onRTPSorterHasPacketCallback); | ||||
|       audioTrack.sorter.setCallback(M.getID(aIdx), onRTPSorterHasPacketCallback); | ||||
| 
 | ||||
|       userSelect[aIdx].reload(streamName, aIdx, COMM_STATUS_SOURCE); | ||||
|       INFO_MSG("Audio push received on track %zu", aIdx); | ||||
|  | @ -639,13 +721,47 @@ namespace Mist{ | |||
|       return false; | ||||
|     } | ||||
| 
 | ||||
|     udpPort = | ||||
|         udp.bind(port, (config && config->hasOption("bindhost") && config->getString("bindhost").size()) | ||||
|                            ? config->getString("bindhost") | ||||
|                            : myConn.getBoundAddress()); | ||||
|     Util::Procs::socketList.insert(udp.getSock()); | ||||
|     sdpAnswer.setCandidate(externalAddr, udpPort); | ||||
|     std::string bindAddr; | ||||
|     //If a bind host has been put in as override, use it
 | ||||
|     if (config && config->hasOption("bindhost") && config->getString("bindhost").size()){ | ||||
|       bindAddr = config->getString("bindhost"); | ||||
|       udpPort = udp.bind(port, bindAddr); | ||||
|       if (!udpPort){ | ||||
|         WARN_MSG("UDP bind address not valid - ignoring setting and using best guess instead"); | ||||
|         bindAddr.clear(); | ||||
|       }else{ | ||||
|         INFO_MSG("Bound to pre-configured UDP bind address"); | ||||
|       } | ||||
|     } | ||||
|     //use the best IPv4 guess we have
 | ||||
|     if (!bindAddr.size()){ | ||||
|       bindAddr = Socket::resolveHostToBestExternalAddrGuess(externalAddr, AF_INET, myConn.getBoundAddress()); | ||||
|       if (!bindAddr.size()){ | ||||
|         WARN_MSG("UDP bind to best guess failed - using same address as incoming connection as a last resort"); | ||||
|         bindAddr.clear(); | ||||
|       }else{ | ||||
|         udpPort = udp.bind(port, bindAddr); | ||||
|         if (!udpPort){ | ||||
|           WARN_MSG("UDP bind to best guess failed - using same address as incoming connection as a last resort"); | ||||
|           bindAddr.clear(); | ||||
|         }else{ | ||||
|           INFO_MSG("Bound to public UDP bind address derived from hostname"); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     if (!bindAddr.size()){ | ||||
|       bindAddr = myConn.getBoundAddress(); | ||||
|       udpPort = udp.bind(port, bindAddr); | ||||
|       if (!udpPort){ | ||||
|         FAIL_MSG("UDP bind to connected address failed - we're out of options here, I'm afraid..."); | ||||
|         bindAddr.clear(); | ||||
|       }else{ | ||||
|         INFO_MSG("Bound to same UDP address as TCP address - this is potentially wrong, but used as a last resort"); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     Util::Procs::socketList.insert(udp.getSock()); | ||||
|     sdpAnswer.setCandidate(bindAddr, udpPort); | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|  | @ -813,8 +929,8 @@ namespace Mist{ | |||
| 
 | ||||
|       if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ | ||||
|         FAIL_MSG("Received an RTP packet for a track that we didn't prepare for. PayloadType is " | ||||
|                  "%" PRIu32, | ||||
|                  rtp_pkt.getPayloadType()); | ||||
|                  "%" PRIu32 ", idx %zu", | ||||
|                  rtp_pkt.getPayloadType(), idx); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|  | @ -830,6 +946,8 @@ namespace Mist{ | |||
|         FAIL_MSG("Failed to unprotect a RTP packet."); | ||||
|         return; | ||||
|       } | ||||
|       RTP::Packet unprotPack(udp.data, len); | ||||
|       DONTEVEN_MSG("%s", unprotPack.toString().c_str()); | ||||
| 
 | ||||
|       // Here follows a very rudimentary algo for requesting lost
 | ||||
|       // packets; I guess after some experimentation a better
 | ||||
|  | @ -843,11 +961,11 @@ namespace Mist{ | |||
| 
 | ||||
|       rtcTrack.prevReceivedSequenceNumber = currSeqNum; | ||||
| 
 | ||||
|       if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType){ | ||||
|       if (rtp_pkt.getPayloadType() == rtcTrack.REDPayloadType || rtp_pkt.getPayloadType() == rtcTrack.ULPFECPayloadType){ | ||||
|         rtcTrack.sorter.addREDPacket(udp.data, len, rtcTrack.payloadType, rtcTrack.REDPayloadType, | ||||
|                                      rtcTrack.ULPFECPayloadType); | ||||
|       }else{ | ||||
|         rtcTrack.sorter.addPacket(RTP::Packet(udp.data, len)); | ||||
|         rtcTrack.sorter.addPacket(unprotPack); | ||||
|       } | ||||
|     }else if ((pt >= 64) && (pt < 96)){ | ||||
| 
 | ||||
|  | @ -909,7 +1027,7 @@ namespace Mist{ | |||
|   void OutWebRTC::onDTSCConverterHasPacket(const DTSC::Packet &pkt){ | ||||
| 
 | ||||
|     // extract meta data (init data, width/height, etc);
 | ||||
|     size_t idx = pkt.getTrackId(); | ||||
|     size_t idx = M.trackIDToIndex(pkt.getTrackId(), getpid()); | ||||
|     std::string codec = M.getCodec(idx); | ||||
|     if (codec == "H264"){ | ||||
|       if (M.getInit(idx).empty()){ | ||||
|  | @ -919,9 +1037,10 @@ namespace Mist{ | |||
|     } | ||||
| 
 | ||||
|     if (codec == "VP8" && pkt.getFlag("keyframe")){extractFrameSizeFromVP8KeyFrame(pkt);} | ||||
|     if (codec == "VP9" && pkt.getFlag("keyframe")){extractFrameSizeFromVP8KeyFrame(pkt);} | ||||
| 
 | ||||
|     // create rtcp packet (set bitrate and request keyframe).
 | ||||
|     if (codec == "H264" || codec == "VP8"){ | ||||
|     if (codec == "H264" || codec == "VP8" || codec == "VP9"){ | ||||
|       uint64_t now = Util::bootMS(); | ||||
| 
 | ||||
|       if (now >= rtcpTimeoutInMillis){ | ||||
|  | @ -942,13 +1061,15 @@ namespace Mist{ | |||
|       INFO_MSG("Validated track %zu in meta", idx); | ||||
|       meta.validateTrack(idx); | ||||
|     } | ||||
|     DONTEVEN_MSG("DTSC: %s", pkt.toSummary().c_str()); | ||||
|     bufferLivePacket(pkt); | ||||
|   } | ||||
| 
 | ||||
|   void OutWebRTC::onDTSCConverterHasInitData(size_t idx, const std::string &initData){ | ||||
|   void OutWebRTC::onDTSCConverterHasInitData(size_t trackId, const std::string &initData){ | ||||
|     size_t idx = M.trackIDToIndex(trackId, getpid()); | ||||
|     if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ | ||||
|       ERROR_MSG( | ||||
|           "Recieved init data for a track that we don't manager. TrackID %zu /PayloadType: %zu", | ||||
|           "Recieved init data for a track that we don't manage. TrackID %zu /PayloadType: %zu", | ||||
|           idx, M.getID(idx)); | ||||
|       return; | ||||
|     } | ||||
|  | @ -972,9 +1093,10 @@ namespace Mist{ | |||
|     meta.setInit(idx, avccbox.payload(), avccbox.payloadSize()); | ||||
|   } | ||||
| 
 | ||||
|   void OutWebRTC::onRTPSorterHasPacket(size_t idx, const RTP::Packet &pkt){ | ||||
|   void OutWebRTC::onRTPSorterHasPacket(size_t trackId, const RTP::Packet &pkt){ | ||||
|     size_t idx = M.trackIDToIndex(trackId, getpid()); | ||||
|     if (idx == INVALID_TRACK_ID || !webrtcTracks.count(idx)){ | ||||
|       ERROR_MSG("Received a sorted RTP packet for track %zu but we don't manage this track.", idx); | ||||
|       ERROR_MSG("Received a sorted RTP packet for payload %zu (idx %zu) but we don't manage this track.", trackId, idx); | ||||
|       return; | ||||
|     } | ||||
|     webrtcTracks[idx].rtpToDTSC.addRTP(pkt); | ||||
|  | @ -989,7 +1111,7 @@ namespace Mist{ | |||
| 
 | ||||
|     int protectedSize = nbytes; | ||||
|     if (srtpWriter.protectRtp((uint8_t *)(void *)rtpOutBuffer, &protectedSize) != 0){ | ||||
|       ERROR_MSG("Failed to protect the RTCP message."); | ||||
|       ERROR_MSG("Failed to protect the RTP message."); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -1076,6 +1198,14 @@ namespace Mist{ | |||
|     // If we see this is audio or video, use the webrtc track we negotiated
 | ||||
|     if (M.getType(tid) == "video" && webrtcTracks.count(vidTrack)){ | ||||
|       trackPointer = &webrtcTracks[vidTrack]; | ||||
| 
 | ||||
|       if (lastPackMs){ | ||||
|         uint64_t newMs = Util::bootMS(); | ||||
|         jitterLog << (newMs - lastPackMs) << std::endl; | ||||
|         lastPackMs = newMs; | ||||
|       } | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
|     if (M.getType(tid) == "audio" && webrtcTracks.count(audTrack)){ | ||||
|       trackPointer = &webrtcTracks[audTrack]; | ||||
|  | @ -1093,19 +1223,17 @@ namespace Mist{ | |||
|     WebRTCTrack &rtcTrack = *trackPointer; | ||||
| 
 | ||||
|     uint64_t timestamp = thisPacket.getTime(); | ||||
|     rtcTrack.rtpPacketizer.setTimestamp(timestamp * SDP::getMultiplier(&M, thisIdx)); | ||||
|     uint64_t newTime = timestamp * SDP::getMultiplier(&M, thisIdx); | ||||
|     rtcTrack.rtpPacketizer.setTimestamp(newTime); | ||||
| 
 | ||||
|     bool isKeyFrame = thisPacket.getFlag("keyframe"); | ||||
|     didReceiveKeyFrame = isKeyFrame; | ||||
|     if (M.getCodec(thisIdx) == "H264"){ | ||||
|       if (isKeyFrame && firstKey){ | ||||
|         char *data; | ||||
|         size_t dataLen; | ||||
|         thisPacket.getString("data", data, dataLen); | ||||
|         size_t offset = 0; | ||||
|         while (offset + 4 < dataLen){ | ||||
|           size_t nalLen = Bit::btohl(data + offset); | ||||
|           uint8_t nalType = data[offset + 4] & 0x1F; | ||||
|           size_t nalLen = Bit::btohl(dataPointer + offset); | ||||
|           uint8_t nalType = dataPointer[offset + 4] & 0x1F; | ||||
|           if (nalType == 7 || nalType == 8){// Init data already provided in-band, skip repeating
 | ||||
|                                               // it.
 | ||||
|             repeatInit = false; | ||||
|  |  | |||
|  | @ -67,6 +67,7 @@ | |||
| #include <mist/stun.h> | ||||
| #include <mist/tinythread.h> | ||||
| #include <mist/websocket.h> | ||||
| #include <fstream> | ||||
| 
 | ||||
| #define NACK_BUFFER_SIZE 1024 | ||||
| 
 | ||||
|  | @ -130,6 +131,7 @@ namespace Mist{ | |||
|     virtual void sendNext(); | ||||
|     virtual void onWebsocketFrame(); | ||||
|     virtual void preWebsocketConnect(); | ||||
|     virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason); | ||||
|     void onIdle(); | ||||
|     bool onFinish(); | ||||
|     bool doesWebsockets(){return true;} | ||||
|  | @ -142,6 +144,8 @@ namespace Mist{ | |||
|     void onRTPPacketizerHasRTCPPacket(const char *data, uint32_t nbytes); | ||||
| 
 | ||||
|   private: | ||||
|     uint64_t lastPackMs; | ||||
|     std::ofstream jitterLog; | ||||
|     std::string externalAddr; | ||||
|     void ackNACK(uint32_t SSRC, uint16_t seq); | ||||
|     bool handleWebRTCInputOutput(); ///< Reads data from the UDP socket. Returns true when we read
 | ||||
|  | @ -198,6 +202,7 @@ namespace Mist{ | |||
|                            ///< the signaling channel. Defaults to 6mbit.
 | ||||
| 
 | ||||
|     size_t audTrack, vidTrack, prevVidTrack; | ||||
|     double target_rate; ///< Target playback speed rate (1.0 = normal, 0 = auto)
 | ||||
| 
 | ||||
|     bool didReceiveKeyFrame; /* TODO burst delay */ | ||||
|     int64_t packetOffset;    ///< For timestamp rewrite with BMO
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Thulinma
						Thulinma