RTMP プロトコルの実装方法

rtmpを知っている

rtmp は Adob​​e が作成したストリーミング メディア伝送プロトコルです. その正式名称は Real Time Messaging Protocol です. これはリアルタイム メッセージ伝送プロトコルです. RTMP を学ぶには、重要なポイントであるメッセージを把握する必要があります.

rtmp プロトコルの原文は、Adobe の公式 Web サイトからダウンロードできます.内容は非常に簡潔です.原文を読むことをお勧めします.

rtmp の中核となるメッセージ交換は、TCP に基づくプロトコルであり、メッセージはメッセージ ブロック (チャンク) に分割され、TCP によって送信されます。各チャンクはチャンク ID と呼ばれる ID を持ち、受信側はチャンク ID に従ってチャンクを完全なメッセージに再構成します。同じチャンク ID を持つすべてのブロックは、論理ストリームである仮想チャンク ストリーム (ブロック ストリーム) を構成します。同時に、各メッセージにはメッセージ ストリーム ID もあり、同じメッセージ ストリーム ID を持つすべてのメッセージは、2 番目の論理フローであるメッセージ フローを形成します。

ここに画像の説明を挿入

メッセージ ストリームとチャンク ストリームの間に 1 対 1 の対応はありません。メッセージ ストリームは複数のチャンク ストリームを介して送信でき、異なるメッセージ ストリームが同じチャンク ストリームを再利用することもできます。There are many types of messages, and there is not one-to-one supported between message types and message stream. メッセージ ストリームはさまざまな種類のメッセージを送信できますが、通常、各種類のメッセージは排他的にチャンク ストリームを占有します。

ここに画像の説明を挿入

rtmp プロトコルのプロセスもハンドシェイクから始まります。握手後はメッセージ交換です。

労働者が良い仕事をしたいのなら、まず道具を研がなければならない

rtmp プロトコルの目的はストリーミング メディアの送信です. 効果を確認するには、 と を使用する必要があります.ffmpegこれらffplay2 つのプログラムはffmpeg の公式Web サイトで見つけることができます. 圧縮されたパッケージを直接ダウンロードして解凍して使用します. もちろん、利便性のためにパスに追加することもできます。他にもプッシュプル ストリーミング ツールがありますが、開発としては、この 2 つのコマンドで十分です。

ストリーミング コマンド:

ffmpeg -re -stream_loop -1 -i trailer.mp4 -codec copy -f flv rtmp://localhost/live/test

再生コマンド:

ffplay -autoexit rtmp://localhost/live/test

これら 2 つのコマンドは比較的長く、作業を簡素化するために make を使用できます。

push: trailer.mp4
  @ffmpeg -re -stream_loop -1 -i trailer.mp4 -codec copy -f flv rtmp://localhost/live/test

pull:
  @ffplay -autoexit rtmp://localhost/live/test

プロトコルは golang を使用して開発されており、具体的なコードの実装はGithubで詳しく説明されています。

握手

rtmp プロトコルはハンドシェイクで始まり、クライアントによって開始されます。クライアントとサーバーはそれぞれ 3 つのデータ ブロックを送信する必要があります.クライアントによって送信されるデータは C0、C1、および C2 と呼ばれ、サーバーによって送信されるデータは S0、S1、および S2 と呼ばれます。

C0 と S0 は同じ構造を持ち、C1 と S1 は同じ構造を持ち、C2 と S2 は同じ構造を持ちます。

ハンドシェイク プロセスは次のとおりです。

  1. クライアントは C0 と C1 を送信します
  2. C0 (または C1) を受信した後、サーバーは S0 と S1 を送信します。
  3. クライアントは S1 を受信した後、C2 を送信します。
  4. C1を受信した後、サーバーはS2を送信します
  5. クライアントが S2 を受信し、サーバーが C2 を受信し、ハンドシェイクが完了します。

データ形式

C0、S0ともに1バイトのみで、内容はプロトコルのバージョン番号です。

ここに画像の説明を挿入

C0 はクライアントが要求した RTMP のバージョン番号、S0 はサーバーが選択したバージョン番号です. 現在のバージョン番号は 3. 0-2 は初期のバージョンで破棄されました. 4-31 は将来のバージョンです. 32-255 は使用できません。これらは ASSIC コードの印刷可能な文字であり、他のプロトコルはバージョン番号として印刷可能な文字を使用することが多く、RTMP プロトコルは区別するために印刷可能な文字をバージョン番号として使用しないためです。サーバーがクライアントのバージョン番号を認識できない場合、応答は 3 で、クライアントはバージョン 3 にダウングレードするか、ハンドシェイクを断念します。

C1 と S1 はどちらも 1536 バイトで、形式は次のとおりです。

ここに画像の説明を挿入

timertmp の各メッセージにはタイムスタンプが付けられるため、このフィールドはメッセージの開始時刻を調整するために使用されます。rtmp の主な目的は、オーディオとビデオのデータを送信することであり、オーディオとビデオは時間関連の情報です。

zeroフィールドはすべてゼロでなければなりません。random bytesこれは乱数であり、任意のバイト コンテンツにすることができます。

C2 と S2 も 1536 バイトで、形式は次のとおりです。

ここに画像の説明を挿入

C2 のフィールドはtimeS1 から取得されtime、 S2 のフィールドはtimeC1 から取得されますtime

C2 のフィールドはtime2C1 から取得されtime、 S2 のフィールドはtime2S1 から取得されますtime

C2 のフィールドはrandom echoS1 から取得されrandom bytes、 S2 のフィールドはrandom echoC1 から取得されますrandom bytes

完全なハンドシェイク プロセスは次のとおりです。

ここに画像の説明を挿入

  • 初期化されていない: プロトコル バージョンはこの段階で送信されます。
  • Version Sent (バージョンが送信されました): C0 と S0 を送信した後、それぞれこの状態に入り、クライアントは S1 を待ち、サーバーは C1 を待ちます。
  • Ack Sent (確認が送信されました): C2 と S2 を送信した後、この状態に入ります。
  • Handshake Done: C2 と S2 を受信した後、この状態に入ります。

複雑な握手

以上が単純なハンドシェイクと呼ばれる rtmp プロトコルで記述されたハンドシェイク プロセスです。コンプレックスハンドシェイクと呼ばれる握手方法もあるが、公式な説明はなく、ネット上では伝説だけが流布している。

使用可能な rtmp サービスを実装したい場合は、複雑なハンドシェイクを実装する必要があります。これは、一部のクライアントが既に複雑なハンドシェイクを採用しており、ffplay を含む単純なハンドシェイクを拒否しているためです。

複雑なハンドシェイクと単純なハンドシェイクの違いは、複雑なハンドシェイクはrandom bytes単なるランダム バイトではなく、チェックサム情報を含むことです。C1とS1の構造比較を下図に示します。

ここに画像の説明を挿入

scheme 0複雑なハンドシェイクの C1とS1 には 2 つの構造がありscheme 1、それらの違いは合計が配置されるkey順序のみです。digest構造に関係なく、keyとの構造はdigest同じです。さらに、以前のzeroフィールドが変更されましたversion。C0 および S0 のバージョンと区別するように注意してください。単純なハンドシェイクでは、zeroフィールドはすべてゼロですが、複雑なハンドシェイクではzeroゼロではありません. これが、単純なハンドシェイクと複雑なハンドシェイクを区別する方法です.

keydigestsumフィールドoffsetは直接エンコードされたオフセットではなく、各バイトを加算して計算する必要があります。

  • キーオフセット:(offset[0] + offset[1] + offset[2] + offset[3]) % 632
  • ダイジェストオフセット:(offset[0] + offset[1] + offset[2] + offset[3]) % 728

keykeyおよび は132 バイトを使用するためoffset、最大オフセットは 764-132=632 バイトです。 、digestおよびdigestoffset36 バイトを使用する場合、最大オフセットは 764-36=728 バイトです。

サーバーは C1 を検証する必要があります.検証方法は、最初に C1 で 32 バイトを見つけてからdigest削除し、残りの部分で sha256 ハッシュを実行し、最後にdigestハッシュ結果を と比較します.

ここに画像の説明を挿入

ここでは、合計の区別方法がわかりませんscheme 0.scheme 1インターネット上のほとんどの人は、最初に検証用のスキーム構造を選択し、失敗した場合は検証用の別のスキームに変更すると言います. 成功した場合は、このスキームを意味します. それは機能しますが, 決して信頼できるものではありません. 現在のversionフィールドは 0 である必要はありません. 4 バイトは確実にいくつかの情報をエンコードします.versionスキームをフィールドにエンコードすることは確かに実行可能な解決策ですが, そうではありません.フィールドの意味の説明versionは推測としてのみ使用でき、さらに検証する必要があります。C1 の 128 バイトに関しては、key関連する使用方法の説明はありません。

S1 の場合、サーバーは、クライアントが検証するのと同じ方法でダイジェストを生成する必要があります。S2 の生成はより複雑です. 最初にC1 のdigestハッシュを取得しkey、次にkeyハッシュ S2 の最初の 1504 バイトを使用してそれを取得しsign、最後にsignそれを S2 の最後に配置して完全な S2 を形成します。

ここに画像の説明を挿入

上記は複雑なハンドシェイクのプロセスであり、ハッシュで使用されるキーと特定のコードの実装は参照用に利用できますhandshake.go

かたまり

rtmp プロトコルは、メッセージをブロックに分割してから送信します。ブロックには次の 2 つの目的があります。

  1. 小さくても重要なメッセージをブロックする、大きくて重要でないメッセージは避けてください。
  2. 同じメッセージ ヘッダーの繰り返し送信を減らします。

チャンクは rtmp の基本単位であり、各チャンクを完全に送信する必要があります。つまり、1 つのチャンクを送信する前に別のチャンクを送信することはできません。

チャンクは、次の図に示すように、Chunk Headerとに分割された 4 つの部分で構成されますChunk Data

ここに画像の説明を挿入

ベーシックヘッド

基本ヘッダーには、メッセージ ヘッダーの形式 ( fmt) とチャンク ストリーム ID (csid) の 2 つの情報が含まれます。fmtメッセージ ヘッダーの形式を示します。メッセージ ヘッダーには 4 つのタイプがあり、エンコードに 2 ビットが必要です。csid はチャンクが属するチャンク ストリームを識別し、受信側はそれに基づいてメッセージを組み立てる必要があります。

保存できるものは保存するという原則に基づいて、基本ヘッダーの長さは、チャンク ストリーム ID のサイズに応じて、1 バイト、2 バイト、および 3 バイトです。

最初のケースは、csid が 2 から 63 の間で、1 バイトでエンコードされている場合です。

ここに画像の説明を挿入

2 番目のケースは、csid が 64 から 319 の間で、2 バイトのエンコーディングを使用している場合です。

ここに画像の説明を挿入

3 番目のケースは、csid が 320 から 65599 の間で、3 バイトのエンコーディングを使用している場合です。

ここに画像の説明を挿入

この場合の csid の計算方法は であることに注意してください第三个字节 × 256 + 第二个字节 + 64。つまり、csid はリトル エンディアンでエンコードされます。

csid の範囲は 2 ~ 65599 で、0 と 1 は予約済みです。0 は基本ヘッダーが 2 バイトのエンコードを使用することを示し、1 は基本ヘッダーが 3 バイトのエンコードを使用することを示します。2 は、プロトコル制御メッセージとユーザー制御メッセージ専用の特別な csid でもあり、通常のメッセージの csid は 3 から始まります。

ヘッダ

メッセージのタイムスタンプ、長さ、タイプ、メッセージ フロー ID など、メッセージの関連情報がメッセージ ヘッダーに記録されます。fmt基本ヘッダーで指定された4 種類のメッセージ ヘッダーがあります。

タイプ0

タイプ 0 のメッセージ ヘッダーには、完全なヘッダー情報を含めて合計 11 バイトがあります。

ここに画像の説明を挿入

保存できるものは保存するという原則により、timestampメッセージヘッダーは3バイトしかないので、タイムスタンプが を超える場合は に0xFFFFFF設定し、 にリアルタイムスタンプを書き込む必要があり0xFFFFFFますChunk HeaderExterned Timestamp

message lengthまた、3 バイトしかないため、メッセージの最大長を超えることはできません0xFFFFFF

message type id(mtid) はメッセージの種類を示し、メッセージの種類によって負荷が異なります。これについては後で説明します。

message stream id(msid) は、メッセージが属するメッセージ ストリームを示し、これらのフィールドの中でリトル エンディアンでエンコードされた唯一のフィールドです。0 は、プロトコル制御メッセージとユーザー制御メッセージ専用の特別な msid です。

タイプ1

タイプ 1 のメッセージ ヘッダーには合計 7 バイトがあり、タイプ 0 に比べて不足していますmessage stream id

ここに画像の説明を挿入

メッセージが同じメッセージ フローに属している場合、後続のメッセージでメッセージ フロー ID を繰り返し送信する必要はありません。ここの最初の 3 バイトはタイムスタンプではなく、タイムスタンプの増分であることに注意してください。一部の rtmp 実装は常に最初の 3 バイトをタイムスタンプとして解釈しますが、これは実際には正しくありません。ほとんどの場合、メッセージのタイムスタンプはゼロから始まるため、問題なく動作します。タイムスタンプの増分がそれを超える場合は0xFFFFFF、 にエンコードする必要もありますExterned Timestamp

タイプ2

タイプ 2 のメッセージ ヘッダーには 3 バイトしか残っておらず、タイムスタンプの増分を設定するために使用されます。を超える場合は0xFFFFFF、 にエンコードする必要もありますExterned Timestamp

ここに画像の説明を挿入

タイプ 3

タイプ 3 のメッセージ ヘッダーは 0 バイトで、すべて保存されます。算術シーケンスの固定長とタイム スタンプを持つメッセージの場合、最初のブロックは 0 型のメッセージ ヘッダーを送信し、2 番目のブロックは 2 型のメッセージ ヘッダーを送信し、後続のメッセージは 3 型のメッセージ ヘッダーを送信できます。音声データなど。また、大きなメッセージを複数のチャンクに分割して送信する場合、最初のチャンクに加えて、次のチャンクでもビデオ データなどの 3 種類のメッセージ ヘッダーを送信できます。

メッセージを読むときは、タイムスタンプに注意してください。オーディオとビデオ メッセージの場合、タイム スタンプは非常に重要であり、再生に影響します。タイム スタンプが間違っていると、オーディオとビデオが同期していない可能性があります。著者の実装では、私はそのような間違いを犯しました。特にタイムスタンプのインクリメントの処理は、処理が正しくないと、再生が進むにつれてオーディオとビデオの同期はずれ現象が徐々に蓄積されていきます。

拡張タイムスタンプ

拡張タイムスタンプはオプションであり、0xFFFFFFメッセージ ヘッダーのタイムスタンプ (タイムスタンプの増分) がより大きい場合にのみ存在します。

ブロック負荷

チャンクのペイロード長 (チャンク サイズ) は固定されていませんが、128 バイト未満にすることはできません。ペイロードの長さはにChunk Header指定されていません。これはクライアント/サーバーの状態です。デフォルトは 128 バイトで、プロトコル制御メッセージを介して変更できます。読み取りと書き込みのチャンク サイズは個別に設定できます。

情報

rtmp には多くの種類のメッセージがあり、種類によってメッセージの形式と機能が異なります。

プロトコル制御メッセージ

プロトコル制御メッセージのメッセージ ストリーム ID は 0 である必要があり、チャンク ストリーム ID は 2 であり、主にチャンク ストリームの関連ステータスを設定するために使用されます。プロトコル制御メッセージのタイムスタンプはすべて 0 であり、すぐに有効にする必要があります。

チャンク サイズの設定

ここに画像の説明を挿入

mtid=1、ブロックのロード サイズを設定するために使用されます。読み込みと書き込みで異なるチャンクサイズを設定できるため、正確には相手の読み込みチャンクサイズと自分の書き込みチャンクサイズです。メッセージの負荷は 4 バイトで、有効なビットは 31 ビットのみです。つまり、チャンクの最大負荷は0x7FFFFFFFバイトです。

中止メッセージ

ここに画像の説明を挿入

mtid=2、サイズは 4 バイト、内容は csid で、指定されたチャンク ストリーム内のメッセージの読み取りを相手に断念するように伝えるために使用されます。たとえば、途中でメッセージを送信したくない場合は、このメッセージを使用してキャンセルできます。

了承

ここに画像の説明を挿入

mtid=3、rtmp はウィンドウ メカニズムも提供します。受信側がウィンドウ サイズのバイト数を受信すると、確認メッセージを送信する必要があります。確認メッセージの内容は、これまでに受信したバイト数であることに注意してください。

ウィンドウ確認サイズ

ここに画像の説明を挿入

mtid=5、ウィンドウ サイズの設定に使用されます。

ピア帯域幅の設​​定

ここに画像の説明を挿入

mtid=6 の場合、ウィンドウ サイズの設定に加えて、帯域幅モードも設定されます。合計 3 つのタイプがあります。

  • 0: 厳密。ウィンドウ サイズをメッセージで指定されたサイズに設定します。
  • 1: 緩い、このウィンドウ サイズを使用できます。前のウィンドウが小さい場合は、前のウィンドウを引き続き使用できます。
  • 2: 動的。厳密モードが以前に設定されている場合は、メッセージを厳密モードとして扱い、それ以外の場合はメッセージを無視します。

ユーザー制御メッセージ

ユーザー制御メッセージのメッセージ ストリーム ID も 0 である必要があり、チャンク ストリーム ID は 2 であり、主にメッセージ ストリームの関連ステータスを設定するために使用されます。

ユーザーコントロールメッセージのメッセージタイプは 4 種類で、その内容は Event Type と Event Data の合計 7 種類です。

ここに画像の説明を挿入

Stream Begin通常、ストリームの接続または作成後にサーバーからクライアントに送信されます。

コマンドメッセージ

メッセージ タイプ 17 と 20 はどちらもコマンド メッセージを表します。違いは、エンコード形式が異なり、17 は AMF3 でエンコードされ、20 は AMF0 でエンコードされます。コマンド メッセージは、主にストリーミング メディアの関連する状態を制御するためのものです。

AMF 形式とデコードについては、[Go] FLV ファイル解析 (2)を参照してください。

コマンド メッセージは、NetConnection コマンドと NetStream コマンドの 2 つのカテゴリに分類されます。

ここに画像の説明を挿入

プッシュでもプルでも、クライアントはconnect最初にコマンドを送信します。次に、ストリーミング エンドのコマンドが送信されます。クライアントによっては、コマンドがpublish存在する場合があります。ストリーミング エンドの場合は、コマンドが送信されます。FCPublishplay

すべてのコマンドの最初の 2 つの用語はCommandNameと でありTransactionID、その後の構造はコマンドごとに異なります。これらのコマンドの特定の構造については、ファイルに保存し、バイナリ ビューアーを使用して自分で確認することをお勧めします。vscode は優れています。

接続

connectコマンドメッセージの構造は次のとおりです。

ここに画像の説明を挿入

このうち、コマンド名はconnect、トランザクション ID は 1、ユーザー引数はオプションです。

サーバーはconnectコマンドを受信した後、応答を送信する必要があります. 応答も同じ構造のコマンド メッセージであり、コマンド名は_resultまたは であり_error、トランザクション ID は 1 に固定されています.

公開

publishこのコマンドはフローを公開するために使用され、フロー名とフロー タイプの 2 つの情報を伝達します。構造は次のとおりです。

ここに画像の説明を挿入

公開タイプには、次の 3 つのタイプがあります。

  • live: ファイルにデータを書き込みません。このタイプはライブ ブロードキャストに使用されます。
  • record: ファイルにデータを書き込みます。ファイルが既に存在する場合は、元のファイルを上書きします。
  • append: ファイルにデータを追加するか、ファイルが存在しない場合は作成します。

遊ぶ

playコマンドはストリームを再生するために使用され、構造は次のとおりです。

ここに画像の説明を挿入

ライブ ブロードキャストの場合、最も重要なのは Stream Name であり、Start はオンデマンド ブロードキャストに使用されます。

音声とビデオのメッセージ

rtmp プロトコルを使用する主な目的は、オーディオおよびビデオ データを送信することです. オーディオ メッセージのメッセージ タイプは 8 で、ビデオ メッセージのメッセージ タイプは 9 です. FLV ファイルの構造に精通している場合は、これらの数値になじみがあることがわかります。これらはすべて Adob​​e によって作成されているため、定義は同じです。さらに、タイプ 18 のビデオ メタデータ タグがあり、これは rtmp のメッセージ タイプ 18 および 15 のメッセージに対応し、18 は AMF0 エンコーディング、15 は AMF3 エンコーディングです。

オーディオ メッセージとビデオ メッセージの場合、ペイロードは FLV ファイルのタグ データ部分のコンテンツです。ライブ アプリケーションの場合は、直接キャッシュしてプレーヤーに送信できます。オンデマンド アプリケーションの場合は、タグ データを抽出してクライアントに送信する必要があります。

FLV ファイルには、スクリプト タグ、ビデオ タグ 0、オーディオ タグ 0 の 3 つの特別なタグがあります。プレイ中は、これら 3 つのタグを順番にプレイヤーに送信する必要があります。

FLV ファイルのフォーマットと解析については、[Go] FLV ファイルの解析 (1)を参照してください。

相互作用プロセス

押し流しでも引き流しでも、connectコマンドでcreateStream始まります。ここでのコマンドは、connectサーバーへの接続ではなく、アプリケーションへの接続であることに注意してください。ここで、下の図に示すように、rtmp のアドレス構造について説明する必要があります。

ここに画像の説明を挿入

rtmp のアドレスは 4 つの部分で構成され、connectコマンドはapplication情報を伝達します。andコマンドstreamNameについては、単純な文字列またはパラメーター付きのパスにすることができます。たとえば、サーバーの実装によって異なります。publishplaystream_name?secret=xxx&key=xxx

createStreamこのコマンドはメッセージ ストリームを作成し、後続のオーディオ メッセージとビデオ メッセージはこのメッセージ ストリームで送信されます。それに対応する別のコマンドをdeleteStream使用して、メッセージ ストリームを削除します。

ここに画像の説明を挿入

以上がconnectと の処理createStreamです. 上記の処理は必須ではありません. 単純化していくつかの処理を省けば正常に動作する場合もありますが,connectと の対応はcreateStream必須です.

プッシュストリーム

ストリーミングはpublishコマンドを使用し、一部のクライアントもFCPublishコマンドを起動しますが、後者は通常無視します。ストリーミングの大まかな流れは次のとおりです。

ここに画像の説明を挿入

この処理は厳密なものではありません.publish result必要な場合を除いて, 実際の処理は異なる場合があります. オーディオとビデオのデータを受信した後にキャッシュする方法は、プロトコル自体の内容を超えており、さまざまな実装があります.

ライブ ブロードキャスト サーバーとして、抽象的な観点から見ると、ストリーム バッファーは無限に長いキューである必要があり、パブリッシャーはデータをキューの最後に書き込みます。キューにいくつかのエントリがあり、プレイヤーはエントリからデータを読み取り始めますが、それらを削除しません。エントリは必ずしも行の先頭ではないことに注意してください。これらのエントリはキー フレームの位置に対応している必要があります。プレーヤーがキューの最後まで読み取ると、パブリッシャーがデータを書き込むまで待機する必要があります。

ただし、実際には、無限に長いキューを実装することはできませんが、代わりに循環キューを使用できます。2D 空間では円である 3D 空間の渦巻きばねを想像してみてください。また、読み取り時と書き込み時にロックできないことにも注意してください。読み取りは書き込みをブロックできないためです。つまり、ストリーミング エンドはストリーミング エンドに影響を与えることはできません。ストリーミング終了の読み取りが遅すぎると、フレーム損失メカニズムが開始されます。

ここに画像の説明を挿入

遊ぶ

コマンドで遊ぶplay再生の流れは以下の通りです。

ここに画像の説明を挿入

StreamIsRecorded上記のプロセスは厳密ではありません. たとえば、ライブブロードキャストの場合はメッセージを送信する必要はありません.playコマンドにフラグがない場合、サーバーは応答をreset送信する必要はありません. reset音声メッセージと動画メッセージを送信する前にMetadata、最初の動画メッセージは、動画のデコードに必要な SPS と PPS を含む動画タグ 0 でなければならず、2 番目の動画フレームはキー フレームでなければならないことに注意してください。そうしないと、デコードが失敗します。

サンプルプログラム

rtmp プロトコル自体の内容は多くなく、実装も難しくありません. http サービスのように rtmp サービスを使用したいと考えています. 以下は私が実装したライブブロードキャストの例です. 最初の 2 つのHandleCommandプロセスFCUpublishplayコマンドはHandleData、オーディオとビデオのデータを処理するために使用されます。

この記事では、rtmp を使用してプッシュプル ストリーミングを実装するための内容のみを紹介します.完全な rtmp プロトコルは、プロトコルの元のテキストを読み取ることができます.

package main

import (
  "fmt"
  "log"

  "github.com/chenyj/rtmp"
  "github.com/chenyj/rtmp/encoding/av"
)

func main() {
    
    
  // 流缓存器
  streams := map[string]rtmp.Streamer{
    
    }
  // 处理unpublished命令
  rtmp.HandleCommand(rtmp.CMD_FCUNPUBLISH, func(w rtmp.MessageWriter, r *rtmp.Request) error {
    
    
    s, ok := streams[r.StreamPath]
    if !ok {
    
    
      return nil
    }
    s.Write(nil)
    return nil
  })
  // 处理play命令
  rtmp.HandleCommand(rtmp.CMD_PLAY, func(w rtmp.MessageWriter, r *rtmp.Request) error {
    
    
    s, ok := streams[r.StreamPath]
    if !ok {
    
    
      return rtmp.ResponsePlay(w, false, "stream not found")
    }
    err := rtmp.ResponsePlay(w, true, "")
    if err != nil {
    
    
      return err
    }

    go func(it rtmp.Iterator) {
    
    
      for {
    
    
        p, err := it.Next()
        if err != nil {
    
    
          break
        }
        err = w.WriteMessage(rtmp.NewMessage(p))
        if err != nil {
    
    
          break
        }
      }
      fmt.Println("播放结束")
    }(s.Iterator())

    return nil
  })
  // 处理音视频数据
  rtmp.HandleData(func(app, path string, p *av.Packet) error {
    
    
    s, ok := streams[path]
    if !ok {
    
    
      s = rtmp.NewStream(3000)
      streams[path] = s
    }
    s.Write(p)
    return nil
  })
  // 使用默认端口启动rtmp服务
  err := rtmp.ListenAndServe("", nil)
  log.Fatal(err)
}

おすすめ

転載: blog.csdn.net/puss0/article/details/128841327