第12回 Raspberry Pi で監視カメラを作ろう! ~動画配信(RTSP)編~


Raspberry Piと専用のカメラモジュールを使用し、ONVIF対応の監視カメラを作成するシリーズ記事です。
本記事はリンク情報システム株式会社の有志が作成しています。


動画配信の方法としては、古くから使われるRTSPや、Apple関係で標準となっているHLS (Http Live Streaming)が考えられます。

HLSは名前の通りHTTPを使用しているため、インターネット環境での親和性が高いのですが、動画ファイルを数秒単位の長さに分割し、細切れに送信するため、再生時には秒単位でのバッファリングが必要となります。
その為、カメラからの動画をリアルタイムで表示する用途には向きません。

今回は、監視カメラとして、ある程度のリアルタイム性も確保したいと考えているため、RTSPを使用して動画配信を作ります。

動画配信 (RTSP)

live555を使用してRTSP配信

RTSPによる動画配信を実現するためにオープンソースのlive555を使用します。
また、後述のONVIFで利用するために、RTSP over UDP の他、RTSP over TCP の通信を実現可能にする必要があります。

live555のサンプルには、OnDemandServer(TCP接続を受け付ける代わりにファイルに保存された動画ファイルを配信する)とPassiveServerMediaSubsession(UDPのみサポートだが、カメラから取得した動画をLIVE配信できる)がありましたが、TCP接続でカメラから取得した動画をLIVE配信するような仕組みはありませんでした。

そこでlive555に用意されている機能を組み合わせて、RTSP over TCPの仕組みを用意しようと考えた場合、TCP接続で動画データを渡せるように、一旦動画データをバッファリングする必要が発生します。

RTSP over TCPの仕組みを実装する上でポイントとなる処理を解説します。

・TCP接続で動画配信できるクラスを定義する

TCP接続に対応している、OnDemandServerMediaSubsessionを継承したクラス(名称はMH_VideoServerMediaSubsessionとしました)を作成し、createNewStreamSource関数とcreateNewRTPSink関数を実装します。
またそれぞれの関数を実装するときは、H.264対応のH264VideoFileServerMediaSubsessionを参考にしました。

class MH_VideoServerMediaSubsession : public OnDemandServerMediaSubsession
{
public:
    :
private:
    virtual FramedSource* createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate);
    virtual RTPSink* createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource);
};
・createNewStreamSource関数の実装

FramedSourceを継承したクラス(名称はMH_MediaPacketFramedSourceとしました)を作成し、配信する動画データを一時的にバッファリングできるようにします。

FramedSource* MH_VideoServerMediaSubsession::createNewStreamSource(unsigned clientSessionId, unsigned& estBitrate)
{
    FramedSource* source = MH_MediaPacketFramedSource::createNew(envir(), m_src);

    // Create a framer for the Video Elementary Stream:
    H264VideoStreamDiscreteFramer* videoSource = H264VideoStreamDiscreteFramer::createNew(envir(), source);

    estBitrate = m_src->GetBitrate() / 1000;            // kbps

    return videoSource;
}

今回の動画データは、元々 H.264のNALユニット単位でデータを扱っているので、live555のライブラリの時点で、再度NALユニットの単位を解析する必要はありません。
そのため、H264VideoFileServerMediaSubsessionのように、H264VideoStreamFramerではなく、H264VideoStreamDiscreteFramerを使用するのが適しています。

・createNewRTPSink関数の実装

今回の動画データはH.264のみの対応と考えているので、H264VideoFileServerMediaSubsessionと同様に、H264VideoRTPSinkのインスタンスを生成します。

RTPSink* MH_VideoServerMediaSubsession::createNewRTPSink(Groupsock* rtpGroupsock, unsigned char rtpPayloadTypeIfDynamic, FramedSource* inputSource)
{
    OutPacketBuffer::maxSize = m_src->GetMaxFrameSize();
    RTPSink* sink = H264VideoRTPSink::createNew(envir(), rtpGroupsock, rtpPayloadTypeIfDynamic);
    return sink;
}

以上の実装を行う事で、 RTSP over TCPをサポートした動画配信を実現する事が可能になります。

live555のマルチスレッド対応強化

taskSchedulerのdoEventLoop関数を実行しているスレッドとは異なるスレッドから、scheduleDelayedTask関数を実行した場合、プログラムの異常終了が発生する事があります。

原因としては、live555のDelayQueueクラスに排他制御の仕組みが存在していないため、キューの整合性に問題が発生しているようです。

そこで、DelayQueueクラスに排他制御の仕組みを追加しました。

これによって、複数スレッドからtaskSchedulerの関数が実行できるようになるため、プログラムをシンプルに作成できるようになります。

改修したlive555ソースは以下のサーバーにて公開しております。

https://github.com/lis-hanzomon/RedBrick

まとめ

live555に含まれるTCP接続のサーバーとUDP接続のサーバーの処理を組み合わせることで、RTSP over TCP接続のサーバーを作成する事ができました。
TCP接続の場合は、UDP接続に比べて通信の信頼性が上がり、パケットロストが防げる為、動画の再生が安定します。しかし通信速度が十分では無い場合は、リアルタイム性を犠牲にして、ある程度のバッファリングを行わないと、再生が追いつかなくなる可能性があります。
実際に運用する場合は、使用される環境において、バッファリングのサイズと動画のリアルタイム性を天秤にかけながら、チューニングする必要があるでしょう。


インデックス記事へ
第11回記事へ
第13回記事へ


リンク情報システム株式会社では一緒に働く仲間を随時募集しています!
また、お仕事のご依頼、ビジネスパートナー様も募集しております。お気軽にご連絡ください。