1. 程式人生 > >live555 原始碼分析:ServerMediaSession

live555 原始碼分析:ServerMediaSession

在 live555 中,用一個 ServerMediaSession 表示流媒體會話,它連線了 RTSPServer 和下層流媒體傳輸邏輯。ServerMediaSessionServerMediaSubsession 共同用於執行底層流媒體傳輸和狀態維護。而 ServerMediaSession 則是在 GenericMediaServer 中,通過 HashTable 來維護的。

在分析 live555 中處理 DESCRIBE 請求的程式碼( live555 原始碼分析:DESCRIBE 的處理)時,我們曾看到 RTSPServer::RTSPClientConnection 通過它來產生 SDP 訊息。本文更詳細地分析這個類的定義和實現。

ServerMediaSession

首先看一下 ServerMediaSession 的定義:

class ServerMediaSubsession; // forward

class ServerMediaSession: public Medium {
public:
  static ServerMediaSession* createNew(UsageEnvironment& env,
      char const* streamName = NULL,
      char const* info = NULL,
      char const* description = NULL,
      Boolean isSSM = False,
      char
const* miscSDPLines = NULL); static Boolean lookupByName(UsageEnvironment& env, char const* mediumName, ServerMediaSession*& resultSession); char* generateSDPDescription(); // based on the entire session // Note: The caller is responsible for freeing the returned string char
const* streamName() const { return fStreamName; } Boolean addSubsession(ServerMediaSubsession* subsession); unsigned numSubsessions() const { return fSubsessionCounter; } void testScaleFactor(float& scale); // sets "scale" to the actual supported scale float duration() const; // a result == 0 means an unbounded session (the default) // a result < 0 means: subsession durations differ; the result is -(the largest). // a result > 0 means: this is the duration of a bounded session virtual void noteLiveness(); // called whenever a client - accessing this media - notes liveness. // The default implementation does nothing, but subclasses can redefine this - e.g., if you // want to remove long-unused "ServerMediaSession"s from the server. unsigned referenceCount() const { return fReferenceCount; } void incrementReferenceCount() { ++fReferenceCount; } void decrementReferenceCount() { if (fReferenceCount > 0) --fReferenceCount; } Boolean& deleteWhenUnreferenced() { return fDeleteWhenUnreferenced; } void deleteAllSubsessions(); // Removes and deletes all subsessions added by "addSubsession()", returning us to an 'empty' state // Note: If you have already added this "ServerMediaSession" to a "RTSPServer" then, before calling this function, // you must first close any client connections that use it, // by calling "RTSPServer::closeAllClientSessionsForServerMediaSession()". protected: ServerMediaSession(UsageEnvironment& env, char const* streamName, char const* info, char const* description, Boolean isSSM, char const* miscSDPLines); // called only by "createNew()" virtual ~ServerMediaSession(); private: // redefined virtual functions virtual Boolean isServerMediaSession() const; private: Boolean fIsSSM; // Linkage fields: friend class ServerMediaSubsessionIterator; ServerMediaSubsession* fSubsessionsHead; ServerMediaSubsession* fSubsessionsTail; unsigned fSubsessionCounter; char* fStreamName; char* fInfoSDPString; char* fDescriptionSDPString; char* fMiscSDPLines; struct timeval fCreationTime; unsigned fReferenceCount; Boolean fDeleteWhenUnreferenced; };

由這個定義,不難理解,它主要是 ServerMediaSubsession 的容器,並通過一個單向連結串列來維護它們。並提供了需要作用於整個流媒體會話所有子會話的操作,如產生 SDP 訊息的 generateSDPDescription() 和設定播放快慢的 testScaleFactor()

ServerMediaSession 物件的建立,就像 live555 中許多類的建立那樣,通過一個靜態的建立函式 createNew() 實現,該函式定義如下:

ServerMediaSession* ServerMediaSession
::createNew(UsageEnvironment& env,
    char const* streamName, char const* info,
    char const* description, Boolean isSSM, char const* miscSDPLines) {
  return new ServerMediaSession(env, streamName, info, description,
      isSSM, miscSDPLines);
}
. . . . . .
static char const* const libNameStr = "LIVE555 Streaming Media v";
char const* const libVersionStr = LIVEMEDIA_LIBRARY_VERSION_STRING;

ServerMediaSession::ServerMediaSession(UsageEnvironment& env,
    char const* streamName,
    char const* info,
    char const* description,
    Boolean isSSM, char const* miscSDPLines)
  : Medium(env), fIsSSM(isSSM), fSubsessionsHead(NULL),
    fSubsessionsTail(NULL), fSubsessionCounter(0),
    fReferenceCount(0), fDeleteWhenUnreferenced(False) {
  fStreamName = strDup(streamName == NULL ? "" : streamName);

  char* libNamePlusVersionStr = NULL; // by default
  if (info == NULL || description == NULL) {
    libNamePlusVersionStr = new char[strlen(libNameStr) + strlen(libVersionStr) + 1];
    sprintf(libNamePlusVersionStr, "%s%s", libNameStr, libVersionStr);
  }
  fInfoSDPString = strDup(info == NULL ? libNamePlusVersionStr : info);
  fDescriptionSDPString = strDup(description == NULL ? libNamePlusVersionStr : description);
  delete[] libNamePlusVersionStr;

  fMiscSDPLines = strDup(miscSDPLines == NULL ? "" : miscSDPLines);

  gettimeofday(&fCreationTime, NULL);
}

ServerMediaSession::~ServerMediaSession() {
  deleteAllSubsessions();
  delete[] fStreamName;
  delete[] fInfoSDPString;
  delete[] fDescriptionSDPString;
  delete[] fMiscSDPLines;
}

每個 ServerMediaSession 都由一個字串形式的 streamName 標識,這個標識也是在 GenericMediaServer 中,通過 HashTable 來維護時,所用的 key。streamName 由呼叫者在建立時傳入。對於 “LIVE555 Media Server” 而言,這個值為資源的路徑。

呼叫者還可以在建立時傳入 infodescription 提供更多關於這個會話的描述資訊。對於 “LIVE555 Media Server” 而言,info 同樣為資源的路徑,但 description 為含有流媒體型別的一個字串,格式為 "$MediaType, streamed by the LIVE555 Media Server",比如對於 H.264 視訊為 "H.264 Video, streamed by the LIVE555 Media Server"

建立物件時,主要是用呼叫者傳入的值來初始化狀態。

像許多其它 Medium 的子類一樣,ServerMediaSession 也提供了一個物件查詢函式:

Boolean ServerMediaSession
::lookupByName(UsageEnvironment& env, char const* mediumName,
    ServerMediaSession*& resultSession) {
  resultSession = NULL; // unless we succeed

  Medium* medium;
  if (!Medium::lookupByName(env, mediumName, medium)) return False;

  if (!medium->isServerMediaSession()) {
    env.setResultMsg(mediumName, " is not a 'ServerMediaSession' object");
    return False;
  }

  resultSession = (ServerMediaSession*)medium;
  return True;
}

作為 ServerMediaSubsession 的容器,ServerMediaSession 還提供了對 ServerMediaSubsession 的新增、管理等操作:

Boolean
ServerMediaSession::addSubsession(ServerMediaSubsession* subsession) {
  if (subsession->fParentSession != NULL) return False; // it's already used

  if (fSubsessionsTail == NULL) {
    fSubsessionsHead = subsession;
  } else {
    fSubsessionsTail->fNext = subsession;
  }
  fSubsessionsTail = subsession;

  subsession->fParentSession = this;
  subsession->fTrackNumber = ++fSubsessionCounter;
  return True;
}
. . . . . .
void ServerMediaSession::noteLiveness() {
  // default implementation: do nothing
}

void ServerMediaSession::deleteAllSubsessions() {
  Medium::close(fSubsessionsHead);
  fSubsessionsHead = fSubsessionsTail = NULL;
  fSubsessionCounter = 0;
}

Boolean ServerMediaSession::isServerMediaSession() const {
  return True;
}
. . . . . .

ServerMediaSession 中新增子會話時,新加入的子會話總是會被放在單向連結串列的表頭。新增子會話的時候,會根據子會話計數器 fSubsessionCounter 為子會話分配 track number。

用來產生 SDP 訊息的 generateSDPDescription()duration() 函式,及用來設定播放快慢的 testScaleFactor(float& scale) 函式,在 live555 原始碼分析:DESCRIBE 的處理live555 原始碼分析:PLAY 的處理 中已經有較為詳細地說明了,這裡不再贅述。

為了便於訪問 ServerMediaSubsession,live555 還提供了一個迭代器,該迭代器定義如下:

class ServerMediaSubsessionIterator {
public:
  ServerMediaSubsessionIterator(ServerMediaSession& session);
  virtual ~ServerMediaSubsessionIterator();

  ServerMediaSubsession* next(); // NULL if none
  void reset();

private:
  ServerMediaSession& fOurSession;
  ServerMediaSubsession* fNextPtr;
};

該迭代器實現如下:

ServerMediaSubsessionIterator
::ServerMediaSubsessionIterator(ServerMediaSession& session)
  : fOurSession(session) {
  reset();
}

ServerMediaSubsessionIterator::~ServerMediaSubsessionIterator() {
}

ServerMediaSubsession* ServerMediaSubsessionIterator::next() {
  ServerMediaSubsession* result = fNextPtr;

  if (fNextPtr != NULL) fNextPtr = fNextPtr->fNext;

  return result;
}

void ServerMediaSubsessionIterator::reset() {
  fNextPtr = fOurSession.fSubsessionsHead;
}

可以看到 ServerMediaSession 中並沒有太多的內容。大多數時候,它僅作為獲得 ServerMediaSubsession 的中介而存在。

ServerMediaSubsession

RTSPServer::RTSPClientSession 中處理 SETUP 請求的程式碼中,我們看到,它從 ServerMediaSession 獲得 ServerMediaSubsession 並接管了對它們的管理,並繞過 ServerMediaSession 直接對 ServerMediaSubsession 進行操作。執行流媒體單個會話的資料操作的入口也都在 ServerMediaSubsession

對於向 “LIVE555 Media Server” 伺服器請求 H.264 視訊檔案的情況,ServerMediaSubsession 的實際型別為 H264VideoFileServerMediaSubsession,我們以此為例來分析 ServerMediaSubsession

H264VideoFileServerMediaSubsessionDynamicRTSPServer 類的 lookupServerMediaSession() 中隨 ServerMediaSession 的建立一起建立,並直接被新增到 ServerMediaSession 中。

#define NEW_SMS(description) do {\
char const* descStr = description\
    ", streamed by the LIVE555 Media Server";\
sms = ServerMediaSession::createNew(env, fileName, fileName, descStr);\
} while(0)
. . . . . .
  } else if (strcmp(extension, ".264") == 0) {
    // Assumed to be a H.264 Video Elementary Stream file:
    NEW_SMS("H.264 Video");
    OutPacketBuffer::maxSize = 100000; // allow for some possibly large H.264 frames
    sms->addSubsession(H264VideoFileServerMediaSubsession::createNew(env, fileName, reuseSource));
  }

H264VideoFileServerMediaSubsession 有著如下圖所示的繼承體系:

在這個繼承體系中,ServerMediaSubsession 定義了可以對單個流執行的操作,類的定義如下:

class ServerMediaSubsession: public Medium {
public:
  unsigned trackNumber() const { return fTrackNumber; }
  char const* trackId();
  virtual char const* sdpLines() = 0;
  virtual void getStreamParameters(unsigned clientSessionId, // in
      netAddressBits clientAddress, // in
      Port const& clientRTPPort, // in
      Port const& clientRTCPPort, // in
      int tcpSocketNum, // in (-1 means use UDP, not TCP)
      unsigned char rtpChannelId, // in (used if TCP)
      unsigned char rtcpChannelId, // in (used if TCP)
      netAddressBits& destinationAddress, // in out
      u_int8_t& destinationTTL, // in out
      Boolean& isMulticast, // out
      Port& serverRTPPort, // out
      Port& serverRTCPPort, // out
      void*& streamToken // out
      ) = 0;
  virtual void startStream(unsigned clientSessionId, void* streamToken,
      TaskFunc* rtcpRRHandler,
      void* rtcpRRHandlerClientData,
      unsigned short& rtpSeqNum,
      unsigned& rtpTimestamp,
      ServerRequestAlternativeByteHandler* serverRequestAlternativeByteHandler,
      void* serverRequestAlternativeByteHandlerClientData) = 0;
  virtual void pauseStream(unsigned clientSessionId, void* streamToken);
  virtual void seekStream(unsigned clientSessionId, void* streamToken, double& seekNPT,
      double streamDuration, u_int64_t& numBytes);
     // This routine is used to seek by relative (i.e., NPT) time.
     // "streamDuration", if >0.0, specifies how much data to stream, past "seekNPT".  (If <=0.0, all remaining data is streamed.)
     // "numBytes" returns the size (in bytes) of the data to be streamed, or 0 if unknown or unlimited.
  virtual void seekStream(unsigned clientSessionId, void* streamToken, char*& absStart, char*& absEnd);
     // This routine is used to seek by 'absolute' time.
     // "absStart" should be a string of the form "YYYYMMDDTHHMMSSZ" or "YYYYMMDDTHHMMSS.<frac>Z".
     // "absEnd" should be either NULL (for no end time), or a string of the same form as "absStart".
     // These strings may be modified in-place, or can be reassigned to a newly-allocated value (after delete[]ing the original).
  virtual void nullSeekStream(unsigned clientSessionId, void* streamToken,
      double streamEndTime, u_int64_t& numBytes);
     // Called whenever we're handling a "PLAY" command without a specified start time.
  virtual void setStreamScale(unsigned clientSessionId, void* streamToken, float scale);
  virtual float getCurrentNPT(void* streamToken);
  virtual FramedSource* getStreamSource(void* streamToken);
  virtual void getRTPSinkandRTCP(void* streamToken,
      RTPSink const*& rtpSink, RTCPInstance const*& rtcp) = 0;
     // Returns pointers to the "RTPSink" and "RTCPInstance" objects for "streamToken".
     // (This can be useful if you want to get the associated 'Groupsock' objects, for example.)
     // You must not delete these objects, or start/stop playing them; instead, that is done
     // using the "startStream()" and "deleteStream()" functions.
  virtual void deleteStream(unsigned clientSessionId, void*& streamToken);

  virtual void testScaleFactor(float& scale); // sets "scale" to the actual supported scale
  virtual float duration() const;
    // returns 0 for an unbounded session (the default)
    // returns > 0 for a bounded session
  virtual void getAbsoluteTimeRange(char*& absStartTime, char*& absEndTime) const;
    // Subclasses can reimplement this iff they support seeking by 'absolute' time.

  // The following may be called by (e.g.) SIP servers, for which the
  // address and port number fields in SDP descriptions need to be non-zero:
  void setServerAddressAndPortForSDP(netAddressBits addressBits,
      portNumBits portBits);

protected: // we're a virtual base class
  ServerMediaSubsession(UsageEnvironment& env);
  virtual ~ServerMediaSubsession();

  char const* rangeSDPLine() const;
      // returns a string to be delete[]d

  ServerMediaSession* fParentSession;
  netAddressBits fServerAddressForSDP;
  portNumBits fPortNumForSDP;

private:
  friend class ServerMediaSession;
  friend class ServerMediaSubsessionIterator;
  ServerMediaSubsession* fNext;

  unsigned fTrackNumber; // within an enclosing ServerMediaSession
  char const* fTrackId;
};

這些操作可以分為幾類,一類是對播放進行控制的操作,包括 startStream()pauseStream()seekStream()nullSeekStream()setStreamScale()deleteStream()testScaleFactor() 等;另一類是獲得用於執行 I/O 操作的 FramedSourceRTPSinkgetStreamSource()getRTPSinkandRTCP()

ServerMediaSubsession 類提供了幾個子會話的通用操作的實現,這主要包括用於產生字串形式的 track id 的 trackId(),用於設定 SDP 伺服器地址和埠號的 setServerAddressAndPortForSDP(),以及用於生成子會話的 SDP 行的 rangeSDPLine()

ServerMediaSubsession::ServerMediaSubsession(UsageEnvironment& env)
  : Medium(env),
    fParentSession(NULL), fServerAddressForSDP(0), fPortNumForSDP(0),
    fNext(NULL), fTrackNumber(0), fTrackId(NULL) {
}

ServerMediaSubsession::~ServerMediaSubsession() {
  delete[] (char*)fTrackId;
  Medium::close(fNext);
}

char const* ServerMediaSubsession::trackId() {
  if (fTrackNumber == 0) return NULL; // not yet in a ServerMediaSession

  if (fTrackId == NULL) {
    char buf[100];
    sprintf(buf, "track%d", fTrackNumber);
    fTrackId = strDup(buf);
  }
  return fTrackId;
}
. . . . . .
void ServerMediaSubsession::setServerAddressAndPortForSDP(netAddressBits addressBits,
    portNumBits portBits) {
  fServerAddressForSDP = addressBits;
  fPortNumForSDP = portBits;
}

char const*
ServerMediaSubsession::rangeSDPLine() const {
  // First, check for the special case where we support seeking by 'absolute' time:
  char* absStart = NULL; char* absEnd = NULL;
  getAbsoluteTimeRange(absStart, absEnd);
  if (absStart != NULL) {
    char buf[100];

    if (absEnd != NULL) {
      sprintf(buf, "a=range:clock=%s-%s\r\n", absStart, absEnd);
    } else {
      sprintf(buf, "a=range:clock=%s-\r\n", absStart);
    }
    return strDup(buf);
  }

  if (fParentSession == NULL) return NULL;

  // If all of our parent's subsessions have the same duration
  // (as indicated by "fParentSession->duration() >= 0"), there's no "a=range:" line:
  if (fParentSession->duration() >= 0.0) return strDup("");

  // Use our own duration for a "a=range:" line:
  float ourDuration = duration();
  if (ourDuration == 0.0) {
    return strDup("a=range:npt=0-\r\n");
  } else {
    char buf[100];
    sprintf(buf, "a=range:npt=0-%.3f\r\n", ourDuration);
    return strDup(buf);
  }
}

這些操作都比較簡明,這裡不再贅述。

對於其它眾多操作單個流的介面,ServerMediaSubsession 類都只是提供了一個空的預設實現:

void ServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/,
    void* /*streamToken*/) {
  // default implementation: do nothing
}
void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
    void* /*streamToken*/, double& /*seekNPT*/, double /*streamDuration*/, u_int64_t& numBytes) {
  // default implementation: do nothing
  numBytes = 0;
}
void ServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
    void* /*streamToken*/, char*& absStart, char*& absEnd) {
  // default implementation: do nothing (but delete[] and assign "absStart" and "absEnd" to NULL, to show that we don't handle this)
  delete[] absStart; absStart = NULL;
  delete[] absEnd; absEnd = NULL;
}
void ServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/,
    void* /*streamToken*/, double streamEndTime, u_int64_t& numBytes) {
  // default implementation: do nothing
  numBytes = 0;
}
void ServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,
    void* /*streamToken*/, float /*scale*/) {
  // default implementation: do nothing
}
float ServerMediaSubsession::getCurrentNPT(void* /*streamToken*/) {
  // default implementation: return 0.0
  return 0.0;
}
FramedSource* ServerMediaSubsession::getStreamSource(void* /*streamToken*/) {
  // default implementation: return NULL
  return NULL;
}
void ServerMediaSubsession::deleteStream(unsigned /*clientSessionId*/,
    void*& /*streamToken*/) {
  // default implementation: do nothing
}

void ServerMediaSubsession::testScaleFactor(float& scale) {
  // default implementation: Support scale = 1 only
  scale = 1;
}

float ServerMediaSubsession::duration() const {
  // default implementation: assume an unbounded session:
  return 0.0;
}

void ServerMediaSubsession::getAbsoluteTimeRange(char*& absStartTime, char*& absEndTime) const {
  // default implementation: We don't support seeking by 'absolute' time, so indicate this by setting both parameters to NULL:
  absStartTime = absEndTime = NULL;
}

OnDemandServerMediaSubsession 實現由 ServerMediaSubsession 定義的流操作介面。為了實現這些操作,需要一些 I/O 操作,如解析流媒體檔案,收發 RTP/RTCP 包等。這些 I/O 操作將由於具體的流媒體源型別的不同而不同,因而不會直接在 OnDemandServerMediaSubsession 中實現。OnDemandServerMediaSubsession 定義了新的虛擬函式,以便從子類中獲得 FramedSourceRTPSink 物件,來執行 I/O 操作。

OnDemandServerMediaSubsession 的具體實現,暫時先不詳細說明。

H264VideoFileServerMediaSubsession 的類繼承層次結構中, FileServerMediaSubsession 用於維護資源的檔名,其定義如下:

class FileServerMediaSubsession: public OnDemandServerMediaSubsession {
protected: // we're a virtual base class
  FileServerMediaSubsession(UsageEnvironment& env, char const* fileName,
                Boolean reuseFirstSource);
  virtual ~FileServerMediaSubsession();

protected:
  char const* fFileName;
  u_int64_t fFileSize; // if known
};

這個定義非常簡單,其實現也很簡單:

FileServerMediaSubsession
::FileServerMediaSubsession(UsageEnvironment& env, char const* fileName,
                Boolean reuseFirstSource)
  : OnDemandServerMediaSubsession(env, reuseFirstSource),
    fFileSize(0) {
  fFileName = strDup(fileName);
}

FileServerMediaSubsession::~FileServerMediaSubsession() {
  delete[] (char*)fFileName;
}

H264VideoFileServerMediaSubsession 最主要的功能則是提供用於執行 I/O 操作的 FramedSourceRTPSink,其定義如下:

class H264VideoFileServerMediaSubsession: public FileServerMediaSubsession {
public:
  static H264VideoFileServerMediaSubsession*
  createNew(UsageEnvironment& env, char const* fileName, Boolean reuseFirstSource);

  // Used to implement "getAuxSDPLine()":
  void checkForAuxSDPLine1();
  void afterPlayingDummy1();

protected:
  H264VideoFileServerMediaSubsession(UsageEnvironment& env,
      char const* fileName, Boolean reuseFirstSource);
  // called only by createNew();
  virtual ~H264VideoFileServerMediaSubsession();

  void setDoneFlag() { fDoneFlag = ~0; }

protected: // redefined virtual functions
  virtual char const* getAuxSDPLine(RTPSink* rtpSink,
      FramedSource* inputSource);
  virtual FramedSource* createNewStreamSource(unsigned clientSessionId,
      unsigned& estBitrate);
  virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock,
      unsigned char rtpPayloadTypeIfDynamic,
      FramedSource* inputSource);

private:
  char* fAuxSDPLine;
  char fDoneFlag; // used when setting up "fAuxSDPLine"
  RTPSink* fDummyRTPSink; // ditto
};

H264VideoFileServerMediaSubsession 通過實現 createNewStreamSource()createNewRTPSink() 建立並返回 FramedSourceRTPSink

FramedSource* H264VideoFileServerMediaSubsession::createNewStreamSource(unsigned /*clientSessionId*/, unsigned& estBitrate) {
  estBitrate = 500; // kbps, estimate

  // Create the video source:
  ByteStreamFileSource* fileSource = ByteStreamFileSource::createNew(envir(), fFileName);
  if (fileSource == NULL) return NULL;
  fFileSize = fileSource->fileSize();

  // Create a framer for the Video Elementary Stream:
  return H264VideoStreamFramer::createNew(envir(), fileSource);
}

RTPSink* H264VideoFileServerMediaSubsession
::createNewRTPSink(Groupsock* rtpGroupsock,
    unsigned char rtpPayloadTypeIfDynamic,
    FramedSource* /*inputSource*/) {
  return H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
}

這裡返回的 FramedSourceH264VideoStreamFramer,返回的 RTPSinkH264VideoRTPSink

除此之外,H264VideoFileServerMediaSubsession 還提供了 AUX SDP line 有關的介面實現:

H264VideoFileServerMediaSubsession::H264VideoFileServerMediaSubsession(UsageEnvironment& env,
    char const* fileName, Boolean reuseFirstSource) :
    FileServerMediaSubsession(env, fileName, reuseFirstSource),
    fAuxSDPLine(NULL), fDoneFlag(0), fDummyRTPSink(NULL) {
}

H264VideoFileServerMediaSubsession::~H264VideoFileServerMediaSubsession() {
  delete[] fAuxSDPLine;
}

static void afterPlayingDummy(void* clientData) {
  H264VideoFileServerMediaSubsession* subsess = (H264VideoFileServerMediaSubsession*)clientData;
  subsess->afterPlayingDummy1();
}

void H264VideoFileServerMediaSubsession::afterPlayingDummy1() {
  // Unschedule any pending 'checking' task:
  envir().taskScheduler().unscheduleDelayedTask(nextTask());
  // Signal the event loop that we're done:
  setDoneFlag();
}

static void checkForAuxSDPLine(void* clientData) {
  H264VideoFileServerMediaSubsession* subsess = (H264VideoFileServerMediaSubsession*)clientData;
  subsess->checkForAuxSDPLine1();
}

void H264VideoFileServerMediaSubsession::checkForAuxSDPLine1() {
  nextTask() = NULL;

  char const* dasl;
  if (fAuxSDPLine != NULL) {
    // Signal the event loop that we're done:
    setDoneFlag();
  } else if (fDummyRTPSink != NULL && (dasl = fDummyRTPSink->auxSDPLine()) != NULL) {
    fAuxSDPLine = strDup(dasl);
    fDummyRTPSink = NULL;

    // Signal the event loop that we're done:
    setDoneFlag();
  } else if (!fDoneFlag) {
    // try again after a brief delay:
    int uSecsToDelay = 100000; // 100 ms
    nextTask() = envir().taskScheduler().scheduleDelayedTask(uSecsToDelay,
        (TaskFunc*) checkForAuxSDPLine, this);
  }
}

char const* H264VideoFileServerMediaSubsession::getAuxSDPLine(RTPSink* rtpSink, FramedSource* inputSource) {
  if (fAuxSDPLine != NULL) return fAuxSDPLine; // it's already been set up (for a previous client)

  if (fDummyRTPSink == NULL) { // we're not already setting it up for another, concurrent stream
    // Note: For H264 video files, the 'config' information ("profile-level-id" and "sprop-parameter-sets") isn't known
    // until we start reading the file.  This means that "rtpSink"s "auxSDPLine()" will be NULL initially,
    // and we need to start reading data from our file until this changes.
    fDummyRTPSink = rtpSink;

    // Start reading the file:
    fDummyRTPSink->startPlaying(*inputSource, afterPlayingDummy, this);

    // Check whether the sink's 'auxSDPLine()' is ready:
    checkForAuxSDPLine(this);
  }

  envir().taskScheduler().doEventLoop(&fDoneFlag);

  return fAuxSDPLine;
}

Done.

live555 原始碼分析系列文章