11/17/2011

如何正確停止「live555 RTSP串流伺服器」

前一篇文章裡,我介紹如何利用live555撰寫RTSP串流伺服器。文章的原始碼範例,並無針對一些例外訊號例如:「Ctrl+C(SIGINT)」進行捕捉處理。 在Linux中,這些例外訊號若沒有處理,其預設行為將是立刻終止程式,進而導致資源遺漏(resource leak)。
在Linux裡,我們可以透過signal()於程式中設立處理常式(signal handler),於對應之處理常式中,我們將可正確地停止RTSP伺服器。

 在live55裡,<RTSPServer>、<ServerMediaSession>、<ServerMediaSubSession>都是<Medium>的子類別。由於Medium類別之解構子被宣告為protected,所以Medium物件之回收無法直接由delete運算子處理,必須經由Medium類別提供之close()釋放。這樣的手法,在live555裡隨處可見。
class Medium {
public:
  static Boolean lookupByName(UsageEnvironment& env,
         char const* mediumName,
         Medium*& resultMedium);
  static void close(UsageEnvironment& env, char const* mediumName);
  static void close(Medium* medium); // alternative close() method using ptrs
      // (has no effect if medium == NULL)

  UsageEnvironment& envir() const {return fEnviron;}

  char const* name() const {return fMediumName;}

  // Test for specific types of media:
  virtual Boolean isSource() const;
  virtual Boolean isSink() const;
  virtual Boolean isRTCPInstance() const;
  virtual Boolean isRTSPClient() const;
  virtual Boolean isRTSPServer() const;
  virtual Boolean isMediaSession() const;
  virtual Boolean isServerMediaSession() const;
  virtual Boolean isDarwinInjector() const;

protected:
  Medium(UsageEnvironment& env); // abstract base class
  virtual ~Medium(); // instances are deleted using close() only

  TaskToken& nextTask() {
 return fNextTask;
  }

private:
  friend class MediaLookupTable;
  UsageEnvironment& fEnviron;
  char fMediumName[mediumNameMaxLen];
  TaskToken fNextTask;
};
目前live55採取event loop驅動的模式。當程式初始化完畢,進入event loop開始運作時,除非發生例外事件,否則將不會輕易離開event loop。若要取得event loop控制權,必須在進入迴圈時就設立變數,並透過該變數中斷event loop。
class TaskScheduler {
  ... ... ...

  virtual void doEventLoop(char* watchVariable = NULL) = 0;
      // Causes further execution to take place within the event loop.
      // Delayed tasks, background I/O handling,
      // and other events are handled, sequentially (as a single thread of control).
      // (If "watchVariable" is not NULL,
      // then we return from this routine when *watchVariable != 0)

  ... ... ...
};
程式範例:
#include <signal.h>

#include <livemedia.hh>
#include <basicusageenvironment.hh>


static char stopEventLoop = 0;
static RTSPServer* rtspServer = NULL;
static UsageEnvironment* env = NULL;

static void announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
                           char const* streamName, char const* inputFileName);
static void handleSignal(int);


int main(int argc, char** argv)
{
    TaskScheduler* scheduler = BasicTaskScheduler::createNew();
    env = BasicUsageEnvironment::createNew(*scheduler);

    // 1. Create the RTSP server:
    rtspServer = RTSPServer::createNew(*env, 8554, NULL);
    if (rtspServer == NULL) {
        *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
        exit(1);
    }

    // 2. Add a H.264 video elementary stream to RTSP server
    {
        char const* streamName = "h264";
        char const* inputFileName = "test.h264";

        ServerMediaSession* sms =
                ServerMediaSession::createNew(*env,
                                              streamName, streamName,
                                              "H.264 video");
        sms->addSubsession(
                H264VideoFileServerMediaSubsession::createNew(*env,
                                inputFileName, False));
        rtspServer->addServerMediaSession(sms);

        announceStream(rtspServer, sms, streamName, inputFileName);
    }

    signal(SIGINT, handleSignal);
    *env << "Press Ctrl+C to stop RTSP server\n";

    // 3. Kick-off the server
    env->taskScheduler().doEventLoop(&stopEventLoop);

    *env << "RTSP server stops\n";
    env->reclaim();
    delete scheduler;

    return 0;
}

static void  announceStream(RTSPServer* rtspServer, ServerMediaSession* sms,
                            char const* streamName, char const* inputFileName)
{
    char* url = rtspServer->rtspURL(sms);

    *env << "\n\"" << streamName << "\" stream, from the file \""
        << inputFileName << "\"\n";
    *env << "Play this stream using the URL \"" << url << "\"\n";

    delete[] url;
}

static void handleSignal(int)
{
    *env << "SIGINT received\n";

    Medium::close(rtspServer);

    stopEventLoop = 1;
}
執行結果:
$ ./h264RtspServer 
"h264" stream, from the file "test.h264"
Play this stream using the URL "rtsp://192.168.11.111:8554/h264"
Press Ctrl+C to stop RTSP server
^CSIGINT received
RTSP server stops
$
有時妥善的管理程式資源需要多花費一些心力,但是可以確保伺服器長久運行無誤。live555是一個優秀的開放原始碼專案,可惜文件有限,所以有時需要追蹤其原始碼,整這個過程可以讓我們更加瞭解其設計架構及理念。

3 comments:

  1. Hello sir,
    I used your idea but my server crashes at the line Medium::close(rtspServer);,some heap corrouption message is displayed

    ReplyDelete
  2. you can check my code here http://stackoverflow.com/questions/14337755/how-to-create-and-destroy-rtsp-server-again-and-again-live-555

    ReplyDelete
  3. Hi if i wan to pass live stream as an input where you have pass test.h264 as input filename then what i have to do ? Can u plz guide me ?

    ReplyDelete