Golang ストリーミング メディアの実践 5: ラル プッシュ ストリーミング サービスのソース コードの読み取り

私のGitHubへようこそ

以下は、Xinchen のすべてのオリジナル作品の分類と要約です (サポートするソース コードを含む): https://github.com/zq2599/blog_demos

シリーズ「Golang Streaming Media Combat」へのリンク

  1. オープンソース プロジェクトを体験してください。
  2. ソースに戻る
  3. リツイートして記録する
  4. Lalserver起動ソースコード読み込み

準備

  • この記事で学ぶべきことは、rtmp ストリーミングを処理する lalserver の機能コードであるため、rtmp プロトコル、少なくともハンドシェイク、チャンク、メッセージ、メッセージ タイプ、および amf0 コマンドの基本的な概念を理解する必要があります。はすでにインターネット上で非常に優れています. ここでは拡張しません. 参考としてウィキを提供します:
  • lalserver ソース ウェアハウス: https://github.com/q191201771/lal
  • ソースコードに多くのロジックと分岐がある場合、lal ログを組み合わせて実際の実行シーケンスを決定し、よりリッチな内容のログを出力することができるため、ストリームは最初にここにプッシュされ、lal のログが取得されます。プッシュ操作は記事「Project lal」で「オープンソースを体験する」に記載されていますが、コマンドは以下の通りです。
./ffmpeg \
-re \
-stream_loop -1 \
-i ../videos/sample.mp4 \
-c copy \
-f flv \
'rtmp://127.0.0.1:1935/live/test110'

この記事の概要

  • ストリーミング メディア技術の基本機能であるプッシュ プル ストリーム、この記事では lal のソース コードを読むことでプッシュ ストリーム機能の具体的な実装を理解します。
  • 今回はrtmpストリーミングサーバーのソースコードを学んでいます.大まかなストリーミング処理の流れは以下の通りです.
  1. TCP接続を受信
  2. 握手
  3. チャンク パッケージを受け取り、メッセージを作成する
  4. messageType に従ってメッセージを個別に処理する
  5. amf0 タイプのメッセージについても、異なる amf0 コマンドに従って個別に処理されます。
  6. ストリーミング終了時の関連操作
  • 次に、lal のストリーミング ソース コードを一緒に学習し、これを使用してストリーミング機能の理解を深めましょう。

rtmp ストリーミング リクエストを処理するためのエントリ

  • lalserver 側では、開始点は rtmp サーバーがリモート TCP 接続 (デフォルトではポート 1935) を受信することです。lal がそれをどのように処理するか、つまり rtmp サーバーの処理ロジックを見てみましょう。コードは lal/pkg にあります。 /rtmp/server.go _
func (server *Server) RunLoop() error {
    
    
	for {
    
    
		conn, err := server.ln.Accept()
		if err != nil {
    
    
			return err
		}
		go server.handleTcpConnect(conn)
	}
}
  • 上記のコードからわかるように、TCP 接続が受信されるたびに、handleTcpConnect メソッドを使用して、コルーチンで接続を処理します. handleTcpConnect メソッドには詳細は含まれず、内容は非常に単純です: TCP 接続し、特定の処理を ServerSession オブジェクトに引き渡す実装
func (server *Server) handleTcpConnect(conn net.Conn) {
    
    
	Log.Infof("accept a rtmp connection. remoteAddr=%s", conn.RemoteAddr().String())
	session := NewServerSession(server, conn)
	_ = session.RunLoop()

	if session.DisposeByObserverFlag {
    
    
		return
	}
	switch session.sessionStat.BaseType() {
    
    
	case base.SessionBaseTypePubStr:
		server.observer.OnDelRtmpPubSession(session)
	case base.SessionBaseTypeSubStr:
		server.observer.OnDelRtmpSubSession(session)
	}
}
  • 上記のコードからわかるように、メインのビジネス ロジックは ServerSession オブジェクトの RunLoop メソッドにあり、展開後は次のように示され、最初に握手 (handshake) し、次に特定のロジックを実行 (runReadLoop) します。
func (s *ServerSession) RunLoop() (err error) {
    
    
	if err = s.handshake(); err != nil {
    
    
		_ = s.dispose(err)
		return err
	}

	err = s.runReadLoop()
	_ = s.dispose(err)

	return err
}

握手

  • ハンドシェイク コードを見る前に、rtmp ハンドシェイク プロセスを確認してください。
    ここに画像の説明を挿入
  • ハンドシェイクのソースコードを見ると一目瞭然ですが、コードは写真とは少し異なります: lal のコードでは、S0S1S2 がバーストで送信されます。
func (s *ServerSession) handshake() error {
    
    
	if err := s.hs.ReadC0C1(s.conn); err != nil {
    
    
		return err
	}
	Log.Infof("[%s] < R Handshake C0+C1.", s.UniqueKey())

	Log.Infof("[%s] > W Handshake S0+S1+S2.", s.UniqueKey())
	if err := s.hs.WriteS0S1S2(s.conn); err != nil {
    
    
		return err
	}

	if err := s.hs.ReadC2(s.conn); err != nil {
    
    
		return err
	}
	Log.Infof("[%s] < R Handshake C2.", s.UniqueKey())
	return nil
}
  • C0、C1、および C2 のデータを読み取る特定のロジックを見たいですか? 下の図に示すように、最も一般的な io read は
    ここに画像の説明を挿入

チャンクの読み取りと処理

  • ハンドシェイクが成功した後、lal/pkg/rtmp/chunk_composer.go#RunLoop を直接呼び出す lal/pkg/rtmp/server_session.go#runReadLoop メソッドは次のとおりです。チャンク 完全なメッセージの後のコールバック メソッド (つまり、lal/pkg/rtmp/server_session.go#doMsg) も重要なポイントです。
func (s *ServerSession) runReadLoop() error {
    
    
	return s.chunkComposer.RunLoop(s.conn, s.doMsg)
}
  • 本当のコア コードが到着しました. ストリーミング クライアントと lalserver が正常に握手した後、ストリーミング操作のすべてのロジックは次のとおりです: lal/pkg/rtmp/chunk_composer.go#RunLoop、ここのコードはいくつかの部分に分割され、コードは少し長いので、もう投稿しません。いくつかの重要なロジックを見てみましょう
  • 最初に表示されるのは無限ループです.1 つのチャンク パッケージを処理した後、次のチャンク パッケージを処理し続けます。
    ここに画像の説明を挿入
  • 1 つ以上のチャンク パッケージで構成されるチャンク ストリーム ID (csid) に従って、現在のパッケージに対応するメッセージを決定します。
    ここに画像の説明を挿入
  • 次のパラグラフはより重要です. 各メッセージには独自の csid があり, これはストリーム オブジェクトに対応します. メッセージに対応するすべてのパッケージはストリームのメンバ変数に格納され, 保存された長さが記録されます. 保存されたコンテンツがlength がメッセージ長に達した場合、メッセージに対応するすべてのデータが取得されたことを意味し、メッセージを処理するためのコードを実行できます。
    ここに画像の説明を挿入
  • 上図の赤い矢印 2 で示される Flush メソッドは、実際にはメモリやハードディスクの読み書き操作を行わず、位置変数を変更するだけであり、バッファ内のものが正式なメッセージの内容であることを示しています。学ぶ
  • チャンクから完全なメッセージを取得した後、次のステップはメッセージを処理するロジックを実行することです。下の図に示すように、2 つのコールバック コードがある理由は、メッセージ タイプが集約メッセージ (Aggregate Message) である場合です。 、22)、つまり、チャンクで取得されたメッセージは実際には複数のメッセージであり、処理のために分割して1つずつコールバックする必要があります
    ここに画像の説明を挿入
  • 上記はチャンクの処理ロジックです. チャンクから完全なメッセージを取得したので、メッセージの処理ロジックを見ていきます.

メッセージを処理する

  • 前の図からわかるように、メッセージを処理するためのコードは赤い矢印で示されたcb(stream)であり、実際の対応するコードはserver_session.go#doMsgです。
  • doMsg のコードは単純明快であり、異なるメッセージ タイプに応じて異なる操作が実行されます。
func (s *ServerSession) doMsg(stream *Stream) error {
    
    
	if err := s.writeAcknowledgementIfNeeded(stream); err != nil {
    
    
		return err
	}

	//log.Debugf("%d %d %v", stream.header.msgTypeId, stream.msgLen, stream.header)
	switch stream.header.MsgTypeId {
    
    
	case base.RtmpTypeIdWinAckSize:
		return s.doWinAckSize(stream)
	case base.RtmpTypeIdSetChunkSize:
		// noop
		// 因为底层的 chunk composer 已经处理过了,这里就不用处理
	case base.RtmpTypeIdCommandMessageAmf0:
		return s.doCommandMessage(stream)
	case base.RtmpTypeIdCommandMessageAmf3:
		return s.doCommandAmf3Message(stream)
	case base.RtmpTypeIdMetadata:
		return s.doDataMessageAmf0(stream)
	case base.RtmpTypeIdAck:
		return s.doAck(stream)
	case base.RtmpTypeIdUserControl:
		s.doUserControl(stream)
	case base.RtmpTypeIdAudio:
		fallthrough
	case base.RtmpTypeIdVideo:
		if s.sessionStat.BaseType() != base.SessionBaseTypePubStr {
    
    
			return nazaerrors.Wrap(base.ErrRtmpUnexpectedMsg)
		}
		s.avObserver.OnReadRtmpAvMsg(stream.toAvMsg())
	default:
		Log.Warnf("[%s] read unknown message. typeid=%d, %s", s.UniqueKey(), stream.header.MsgTypeId, stream.toDebugString())

	}
	return nil
}
  • 問題は、ストリーミングの知識を習得するには、各メッセージの処理ロジック コードを読み取る必要があるかということです。少し多すぎるように思えますし、この順序で読むとメッセージ間の順序や依存関係がわからないので、この時点で怠惰になる必要があります...
  • ストリーミング時のプロトコル相互作用の特定の状況を理解するために、doMsg メソッドを少し変更し、下図の黄色い矢印のコード行を追加してから、コンパイルして実行します。
    ここに画像の説明を挿入
  • FFmpeg を使用してプッシュ ストリームを再度実行し、新しいログを取得します。
  • ストリーミング後のログは次のとおりです (grep コマンドを使用して、ログの上記の行のみを確認してください)。
msg header {
    
    Csid:3 MsgLen:139 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:2 MsgLen:4 MsgTypeId:1 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:3 MsgLen:36 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:3 MsgLen:32 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:3 MsgLen:25 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:8 MsgLen:37 MsgTypeId:20 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:388 MsgTypeId:18 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:43 MsgTypeId:9 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:4 MsgTypeId:8 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:105227 MsgTypeId:9 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:969 MsgTypeId:8 MsgStreamId:1 TimestampAbs:0} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:1013 MsgTypeId:8 MsgStreamId:1 TimestampAbs:21} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:1559 MsgTypeId:9 MsgStreamId:1 TimestampAbs:40} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:1028 MsgTypeId:8 MsgStreamId:1 TimestampAbs:43} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:1032 MsgTypeId:8 MsgStreamId:1 TimestampAbs:64} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:2158 MsgTypeId:9 MsgStreamId:1 TimestampAbs:80} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:992 MsgTypeId:8 MsgStreamId:1 TimestampAbs:85} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:960 MsgTypeId:8 MsgStreamId:1 TimestampAbs:107} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:2213 MsgTypeId:9 MsgStreamId:1 TimestampAbs:120} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:975 MsgTypeId:8 MsgStreamId:1 TimestampAbs:128} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:991 MsgTypeId:8 MsgStreamId:1 TimestampAbs:149} - server_session.go:215
msg header {
    
    Csid:6 MsgLen:2528 MsgTypeId:9 MsgStreamId:1 TimestampAbs:160} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:1011 MsgTypeId:8 MsgStreamId:1 TimestampAbs:171} - server_session.go:215
msg header {
    
    Csid:4 MsgLen:1002 MsgTypeId:8 MsgStreamId:1 TimestampAbs:192} - server_session.go:215
  • wikiのメッセージタイプの説明を見てみましょう
    ここに画像の説明を挿入
  • プロトコルとログを組み合わせると、ストリーミングが開始された後、2 番目の設定チャンクを除いて、その他は主に AMF0 によってエンコードされた RTMP コマンド メッセージと、オーディオおよびビデオ データ パケットであることがわかります。
  • ログには MsgTypeId が 20 に等しいメッセージが多数あり、対応する 16 進数は 0x14 であり、これが AMF0 コマンドです。したがって、このようなメッセージの処理ロジックに注目する必要があります。そのようなメッセージを処理するメソッドがdoCommandMessageである doMsg コード
func (s *ServerSession) doCommandMessage(stream *Stream) error {
    
    
	cmd, err := stream.msg.readStringWithType()
	if err != nil {
    
    
		return err
	}
	tid, err := stream.msg.readNumberWithType()
	if err != nil {
    
    
		return err
	}

	switch cmd {
    
    
	case "connect":
		return s.doConnect(tid, stream)
	case "createStream":
		return s.doCreateStream(tid, stream)
	case "publish":
		return s.doPublish(tid, stream)
	case "play":
		return s.doPlay(tid, stream)
	case "releaseStream":
		fallthrough
	case "FCPublish":
		fallthrough
	case "FCUnpublish":
		fallthrough
	case "getStreamLength":
		fallthrough
	case "deleteStream":
		Log.Debugf("[%s] read command message, ignore it. cmd=%s, %s", s.UniqueKey(), cmd, stream.toDebugString())
	default:
		Log.Errorf("[%s] read unknown command message. cmd=%s, %s", s.UniqueKey(), cmd, stream.toDebugString())
	}
	return nil
}
  • doConnectdoCreateStreamdoPublishdoPlayの各コマンドの処理方法には、内部にそれぞれの特徴を表すログが含まれているため、ログの内容から、ストリーミング時に受信したコマンドの順序を簡単に整理すると、次のようになります。
connect 
-> 
releaseStream 
-> 
FCPublish 
-> 
createStream
-> 
publish
-> 
MetaDzta
-> 
然后就是音频视频的chunk包
  • 次のように、doConnect メソッドを開いて、FFmpeg から接続コマンドを受信した後に lalserver が何をしたかを確認します。 connect コマンドのパラメーター :1935/live)、コマンドの形式でメッセージを FFmpeg に継続的に返信します。
func (s *ServerSession) doConnect(tid int, stream *Stream) error {
    
    
	val, err := stream.msg.readObjectWithType()
	if err != nil {
    
    
		return err
	}
	s.appName, err = val.FindString("app")
	if err != nil {
    
    
		return err
	}
	s.tcUrl, err = val.FindString("tcUrl")
	if err != nil {
    
    
		Log.Warnf("[%s] tcUrl not exist.", s.UniqueKey())
	}
	Log.Infof("[%s] < R connect('%s'). tcUrl=%s", s.UniqueKey(), s.appName, s.tcUrl)

	s.observer.OnRtmpConnect(s, val)

	Log.Infof("[%s] > W Window Acknowledgement Size %d.", s.UniqueKey(), windowAcknowledgementSize)
	if err := s.packer.writeWinAckSize(s.conn, windowAcknowledgementSize); err != nil {
    
    
		return err
	}

	Log.Infof("[%s] > W Set Peer Bandwidth.", s.UniqueKey())
	if err := s.packer.writePeerBandwidth(s.conn, peerBandwidth, peerBandwidthLimitTypeDynamic); err != nil {
    
    
		return err
	}

	Log.Infof("[%s] > W SetChunkSize %d.", s.UniqueKey(), LocalChunkSize)
	if err := s.packer.writeChunkSize(s.conn, LocalChunkSize); err != nil {
    
    
		return err
	}

	Log.Infof("[%s] > W _result('NetConnection.Connect.Success').", s.UniqueKey())
	oe, err := val.FindNumber("objectEncoding")
	if oe != 0 && oe != 3 {
    
    
		oe = 0
	}
	if err := s.packer.writeConnectResult(s.conn, tid, oe); err != nil {
    
    
		return err
	}
	return nil
}
  • 接続が完了したら createStream コマンドです. 対応する doCreateStream メソッドは次のとおりです. すぐに返信するという非常に単純な方法であり, メッセージタイプは引き続き createStream. それについても考えてみてください. 関連する情報ストリームは lal 側で準備ができており、ストリームを作成するコマンドも受信されます。アクションは不要です。
func (s *ServerSession) doCreateStream(tid int, stream *Stream) error {
    
    
	Log.Infof("[%s] < R createStream().", s.UniqueKey())
	Log.Infof("[%s] > W _result().", s.UniqueKey())
	if err := s.packer.writeCreateStreamResult(s.conn, tid); err != nil {
    
    
		return err
	}
	return nil
}
  • 次のステップは publish コマンドです。コードは投稿されません。主にストリーム名の取得、onStatus の応答、接続のタイムアウト時間の設定などです。さらに、オブザーバーが存在する場合は、publish- も送信します。関連するイベントをそれに関連付けて、publish イベントを消費するコードも一見の価値があります lal/pkg/logic/server_manager__.go#OnNewRtmpPubSession では、次のように、主に認証、フローに関連するグループ処理が含まれていることがわかります。 , 外部監視の通知. さらに, 設定で記録機能が有効になっている場合, group.AddRtmpPubSession メソッドで, 関連する初期化操作が行われます.
func (sm *ServerManager) OnNewRtmpPubSession(session *rtmp.ServerSession) error {
    
    
	sm.mutex.Lock()
	defer sm.mutex.Unlock()

	info := base.Session2PubStartInfo(session)

	// 先做simple auth鉴权
	if err := sm.option.Authentication.OnPubStart(info); err != nil {
    
    
		return err
	}

	group := sm.getOrCreateGroup(session.AppName(), session.StreamName())
	if err := group.AddRtmpPubSession(session); err != nil {
    
    
		return err
	}

	info.HasInSession = group.HasInSession()
	info.HasOutSession = group.HasOutSession()

	sm.option.NotifyHandler.OnPubStart(info)
	return nil
}
  • 次はMetadata型のメッセージ処理です. メッセージにはプッシュするストリームの幅や高さ, ビットレート, フレームレートなどの詳細なパラメータが含まれています. 対応するメソッドは doDataMessageAmf0. このメソッドで最も重要なのはNotified を実行するには、対応するメソッドが lal/pkg/logic/group__core_streaming.go#OnReadRtmpAvMsg にあります。
  • OnReadRtmpAvMsgメソッドは、重要なポイントであるbroadcastByRtmpMsgを呼び出します.グループに保存されたサブスクライバーに従ってブロードキャストの送信を開始し、すべてのサブスクライバーがメディアストリームの詳細なパラメーターを取得できるようにします(ここのコードは非常に複雑で、さまざまなプロトコルが含まれます)録画、リツイート、ストリーミングなど)、ストリーミング シーンでは、このコードに焦点を当てることができます。
    ここに画像の説明を挿入
  • 上記のすべてのコマンドが応答されるまで待ちます。つまり、準備作業が完了し、メディア ストリーム データの到着を待つことができます。

音声およびビデオ メッセージの処理

  • 次の図に示すように、doMsg メソッドに戻ってオーディオ メッセージとビデオ メッセージの処理を確認します。まず基本的なチェックを行い、処理のために lal/pkg/logic/group__core_streaming.go#OnReadRtmpAvMsg に渡します。
    ここに画像の説明を挿入
  • 再び OnReadRtmpAvMsg メソッドです. 以前はメタデータに応答したメソッドでした. 今でもオーディオ メッセージとビデオ メッセージを処理するメソッドです. コードを読むのは簡単ですが、処理のためにブロードキャストByRtmpMsg メソッドに引き渡されます.
  • broadcastByRtmpMsg で、seqheader とキー フレームのコンテンツをキャッシュする lal/pkg/remux/gop_cache.go#Feed に注目します。
  • broadcastByRtmpMsg メソッドには、この記事で最も重要なコードが 2 つあります (そう思う)。
  • 最初に、下の図に示すように、新しいストリーミング リクエストの場合、メディア ストリームの属性がストリーミング エンドに書き込まれ、キー フレームとその seqheader 情報がキャッシュされるだけで、ストリーミング エンドが接続を確立したばかりです。ストリーマーによってプッシュされた最新のキー フレームを待たずに、キー フレームを取得できます (最初のフレームの効果をすばやく取得します)。
    ここに画像の説明を挿入
  • 2 番目のキー コードは次の図に示すように、write2RtmpSubSessions を呼び出して、今回受信したオーディオ メッセージとビデオ メッセージを転送します。
    ここに画像の説明を挿入
  • write2RtmpSubSessions を展開して、ふと気がついたのですが、個人的には、これがプッシュプル ストリームのコア コードだと思います。受信した各オーディオ データとビデオ データを、プル ストリーム エンドの TCP 接続から直接書き込みます (session.Write メソッドを展開して表示します)。
func (group *Group) write2RtmpSubSessions(b []byte) {
    
    
	for session := range group.rtmpSubSessionSet {
    
    
		if session.IsFresh || session.ShouldWaitVideoKeyFrame {
    
    
			continue
		}
		_ = session.Write(b)
	}
}

ストリーミング終了の扱い

  • Ctrl+C を使用して FFmpeg ストリーミングを終了すると、lal はどうなりますか? 怠け者で、最初にログを読むほうがよいでしょう。
2023/04/02 09:47:24.346491 ^[[22;36m INFO ^[[0mmsg header {
    
    Csid:4 MsgLen:986 MsgTypeId:8 MsgStreamId:1 TimestampAbs:9323} - server_session.go:215
2023/04/02 09:47:24.346558 ^[[22;36m INFO ^[[0mmsg header {
    
    Csid:4 MsgLen:950 MsgTypeId:8 MsgStreamId:1 TimestampAbs:9344} - server_session.go:215
2023/04/02 09:47:24.346605 ^[[22;36m INFO ^[[0mmsg header {
    
    Csid:6 MsgLen:5 MsgTypeId:9 MsgStreamId:1 TimestampAbs:9320} - server_session.go:215
2023/04/02 09:47:24.346654 ^[[22;33m WARN ^[[0m[RTMP2MPEGTS1] rtmp msg too short, ignore. header={
    
    Csid:6 MsgLen:5 MsgTypeId:9 MsgStreamId:1 TimestampAbs:9320}, payload=00000000  17 02 00 00 00                                    |.....|
 - rtmp2mpegts.go:196
2023/04/02 09:47:24.346681 ^[[22;33m WARN ^[[0mrtmp msg too short, ignore. header={
    
    Csid:6 MsgLen:5 MsgTypeId:9 MsgStreamId:1 TimestampAbs:9320}, payload=00000000  17 02 00 00 00                                    |.....|
 - rtmp2rtsp.go:102
2023/04/02 09:47:24.346958 ^[[22;36m INFO ^[[0mmsg header {
    
    Csid:3 MsgLen:34 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
2023/04/02 09:47:24.346987 ^[[22;34mDEBUG ^[[0m[RTMPPUBSUB1] read command message, ignore it. cmd=FCUnpublish, header={
    
    Csid:3 MsgLen:34 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0}, b=len(core)=128, rpos=23, wpos=34, hex=00000000  05 02 00 07 74 65 73 74  31 31 30                 |....test110|
 - server_session.go:357
2023/04/02 09:47:24.347012 ^[[22;36m INFO ^[[0mmsg header {
    
    Csid:3 MsgLen:34 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0} - server_session.go:215
2023/04/02 09:47:24.347028 ^[[22;34mDEBUG ^[[0m[RTMPPUBSUB1] read command message, ignore it. cmd=deleteStream, header={
    
    Csid:3 MsgLen:34 MsgTypeId:20 MsgStreamId:0 TimestampAbs:0}, b=len(core)=128, rpos=24, wpos=34, hex=00000000  05 00 3f f0 00 00 00 00  00 00                    |..?.......|
 - server_session.go:357
2023/04/02 09:47:24.347050 ^[[22;34mDEBUG ^[[0m[NAZACONN1] close once. err=EOF - connection.go:504
2023/04/02 09:47:24.347168 ^[[22;36m INFO ^[[0m[RTMPPUBSUB1] lifecycle dispose rtmp ServerSession. err=EOF - server_session.go:538
2023/04/02 09:47:24.347183 ^[[22;34mDEBUG ^[[0m[NAZACONN1] Close. - connection.go:376
2023/04/02 09:47:24.347199 ^[[22;34mDEBUG ^[[0m[GROUP1] [RTMPPUBSUB1] del rtmp PubSession from group. - group__in.go:318
2023/04/02 09:47:24.347303 ^[[22;36m INFO ^[[0m[HLSMUXER1] lifecycle dispose hls muxer. - muxer.go:126
2023/04/02 09:47:24.570509 ^[[22;36m INFO ^[[0merase inactive group. [GROUP1] - server_manager__.go:299
2023/04/02 09:47:24.570639 ^[[22;36m INFO ^[[0m[GROUP1] lifecycle dispose group. - group__.go:207
  • 上記のログからわかるように、lal は FFmpeg から FCUnpublish、deleteStream などのコマンドを受け取りますが、lal はこれらのコマンドを無視しますが、下図に示すように、TCP 接続で EOF エラーが発生するまで待機し、エラー
    ここに画像の説明を挿入
  • 具体的には、TCP エラーを処理してストリーミングを終了するためのコードを次の図に示します。
    ここに画像の説明を挿入
  • ここまでで、ストリーミング サービスの関連ソース コードの学習は完了しました. lal の助けを借りて、rtmp ストリーミング サービスの詳細を初めて学びました. 基本的なスキルはしっかりしており、次の学習はさらに多くのことを学びます.次に、もう 1 つの基本機能である rtmp ストリーミングに挑戦してみましょう。

あなたは一人じゃない、Xinchen Originalはずっとあなたと一緒

  1. ジャワシリーズ
  2. 春シリーズ
  3. ドッカーシリーズ
  4. kubernetes シリーズ
  5. データベース+ミドルウェアシリーズ
  6. DevOps シリーズ

おすすめ

転載: blog.csdn.net/boling_cavalry/article/details/129912667