适配引擎改造

This commit is contained in:
dexter
2022-02-17 21:50:31 +08:00
parent b46cf00f65
commit 48649e394b
8 changed files with 343 additions and 270 deletions

18
amf.go
View File

@@ -1,8 +1,8 @@
package rtmp package rtmp
import ( import (
"github.com/Monibuca/engine/v4/util" "github.com/Monibuca/engine/v4/util"
"go.uber.org/zap"
) )
// Action Message Format -- AMF 0 // Action Message Format -- AMF 0
@@ -97,14 +97,14 @@ func (amf *AMF) decodeObject() (obj AMFValue) {
case AMF0_OBJECT: case AMF0_OBJECT:
return amf.readObject() return amf.readObject()
case AMF0_MOVIECLIP: case AMF0_MOVIECLIP:
plugin.Println("This type is not supported and is reserved for future use.(AMF0_MOVIECLIP)") plugin.Error("This type is not supported and is reserved for future use.(AMF0_MOVIECLIP)")
case AMF0_NULL: case AMF0_NULL:
return amf.readNull() return amf.readNull()
case AMF0_UNDEFINED: case AMF0_UNDEFINED:
amf.ReadByte() amf.ReadByte()
return Undefined return Undefined
case AMF0_REFERENCE: case AMF0_REFERENCE:
plugin.Println("reference-type.(AMF0_REFERENCE)") plugin.Error("reference-type.(AMF0_REFERENCE)")
case AMF0_ECMA_ARRAY: case AMF0_ECMA_ARRAY:
return amf.readECMAArray() return amf.readECMAArray()
case AMF0_END_OBJECT: case AMF0_END_OBJECT:
@@ -118,15 +118,15 @@ func (amf *AMF) decodeObject() (obj AMFValue) {
AMF0_XML_DOCUMENT: AMF0_XML_DOCUMENT:
return amf.readLongString() return amf.readLongString()
case AMF0_UNSUPPORTED: case AMF0_UNSUPPORTED:
plugin.Println("If a type cannot be serialized a special unsupported marker can be used in place of the type.(AMF0_UNSUPPORTED)") plugin.Error("If a type cannot be serialized a special unsupported marker can be used in place of the type.(AMF0_UNSUPPORTED)")
case AMF0_RECORDSET: case AMF0_RECORDSET:
plugin.Println("This type is not supported and is reserved for future use.(AMF0_RECORDSET)") plugin.Error("This type is not supported and is reserved for future use.(AMF0_RECORDSET)")
case AMF0_TYPED_OBJECT: case AMF0_TYPED_OBJECT:
plugin.Println("If a strongly typed object has an alias registered for its class then the type name will also be serialized. Typed objects are considered complex types and reoccurring instances can be sent by reference.(AMF0_TYPED_OBJECT)") plugin.Error("If a strongly typed object has an alias registered for its class then the type name will also be serialized. Typed objects are considered complex types and reoccurring instances can be sent by reference.(AMF0_TYPED_OBJECT)")
case AMF0_AVMPLUS_OBJECT: case AMF0_AVMPLUS_OBJECT:
plugin.Println("AMF0_AVMPLUS_OBJECT") plugin.Error("AMF0_AVMPLUS_OBJECT")
default: default:
plugin.Warnf("Unsupported type %v", t) plugin.Error("Unsupported type", zap.Uint8("type", t))
} }
return nil return nil
} }
@@ -280,4 +280,4 @@ func (amf *AMF) writeObjectBool(key string, f bool) {
func (amf *AMF) writeObjectNumber(key string, value float64) { func (amf *AMF) writeObjectNumber(key string, value float64) {
amf.writeKey(key) amf.writeKey(key)
amf.writeNumber(value) amf.writeNumber(value)
} }

179
client.go
View File

@@ -8,24 +8,21 @@ import (
"github.com/Monibuca/engine/v4" "github.com/Monibuca/engine/v4"
"github.com/Monibuca/engine/v4/util" "github.com/Monibuca/engine/v4/util"
"go.uber.org/zap"
) )
type RTMPClient struct { func NewRTMPClient(addr string) (client *NetConnection) {
NetConnection
}
func (client *RTMPClient) Connect(addr string) bool {
u, err := url.Parse(addr) u, err := url.Parse(addr)
if err != nil { if err != nil {
plugin.Error(err) plugin.Error("connect url parse", zap.Error(err))
return false return
} }
conn, err := net.Dial("tcp", u.Host) conn, err := net.Dial("tcp", u.Host)
if err != nil { if err != nil {
plugin.Error(err) plugin.Error("dial tcp", zap.String("host", u.Host), zap.Error(err))
return false return
} }
client.NetConnection = NetConnection{ client = &NetConnection{
TCPConn: conn.(*net.TCPConn), TCPConn: conn.(*net.TCPConn),
Reader: bufio.NewReader(conn), Reader: bufio.NewReader(conn),
writeChunkSize: RTMP_DEFAULT_CHUNK_SIZE, writeChunkSize: RTMP_DEFAULT_CHUNK_SIZE,
@@ -36,10 +33,10 @@ func (client *RTMPClient) Connect(addr string) bool {
tmpBuf: make([]byte, 4), tmpBuf: make([]byte, 4),
// subscribers: make(map[uint32]*engine.Subscriber), // subscribers: make(map[uint32]*engine.Subscriber),
} }
err = client.Handshake() err = client.ClientHandshake()
if err != nil { if err != nil {
plugin.Error(err) plugin.Error("handshake", zap.Error(err))
return false return nil
} }
connectArg := make(AMFObject) connectArg := make(AMFObject)
connectArg["swfUrl"] = addr connectArg["swfUrl"] = addr
@@ -51,7 +48,7 @@ func (client *RTMPClient) Connect(addr string) bool {
for { for {
msg, err := client.RecvMessage() msg, err := client.RecvMessage()
if err != nil { if err != nil {
return false return nil
} }
switch msg.MessageTypeID { switch msg.MessageTypeID {
case RTMP_MSG_AMF0_COMMAND: case RTMP_MSG_AMF0_COMMAND:
@@ -60,96 +57,140 @@ func (client *RTMPClient) Connect(addr string) bool {
case "_result": case "_result":
response := msg.MsgData.(*ResponseMessage) response := msg.MsgData.(*ResponseMessage)
if response.Infomation["code"] == NetConnection_Connect_Success { if response.Infomation["code"] == NetConnection_Connect_Success {
return true return
} else { } else {
return false return nil
} }
} }
} }
} }
} }
var _ engine.IPusher = (*RTMPPusher)(nil)
var _ engine.IPuller = (*RTMPPuller)(nil)
type RTMPPusher struct { type RTMPPusher struct {
RTMPSender
engine.Pusher engine.Pusher
RTMPClient }
func (pusher *RTMPPusher) OnEvent(event any) any {
pusher.RTMPSender.OnEvent(event)
switch event.(type) {
case *engine.Stream:
pusher.NetConnection = NewRTMPClient(pusher.RemoteURL)
if pusher.NetConnection != nil {
pusher.SendCommand(SEND_CREATE_STREAM_MESSAGE, nil)
go pusher.push()
}
case engine.PushEvent:
pusher.PushCount++
if pusher.Stream == nil {
if plugin.Subscribe(pusher.StreamPath, pusher) {
}
}
}
return event
} }
func (pusher *RTMPPusher) push() { func (pusher *RTMPPusher) push() {
SendMedia(&pusher.NetConnection, &pusher.Subscriber) defer pusher.Unsubscribe()
pusher.NetConnection.Close() for {
} msg, err := pusher.RecvMessage()
if err != nil {
break
}
switch msg.MessageTypeID {
case RTMP_MSG_AMF0_COMMAND:
cmd := msg.MsgData.(Commander).GetCommand()
switch cmd.CommandName {
case "_result":
if response, ok := msg.MsgData.(*ResponseCreateStreamMessage); ok {
pusher.StreamID = response.StreamId
m := &PublishMessage{
CURDStreamMessage{
CommandMessage{
"publish",
0,
},
response.StreamId,
},
pusher.Stream.StreamName,
"live",
}
pusher.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
} else if response, ok := msg.MsgData.(*ResponsePublishMessage); ok {
if response.Infomation["code"] == "NetStream.Publish.Start" {
func (pusher *RTMPPusher) Push(count int) { } else {
if pusher.Connect(pusher.RemoteURL) { return
pusher.SendCommand(SEND_CREATE_STREAM_MESSAGE, nil)
for {
msg, err := pusher.RecvMessage()
if err != nil {
return
}
switch msg.MessageTypeID {
case RTMP_MSG_AMF0_COMMAND:
cmd := msg.MsgData.(Commander).GetCommand()
switch cmd.CommandName {
case "_result":
if response, ok := msg.MsgData.(*ResponseCreateStreamMessage); ok {
arg := make(AMFObject)
arg["streamid"] = response.StreamId
pusher.SendCommand(SEND_PUBLISH_START_MESSAGE, arg)
} else if response, ok := msg.MsgData.(*ResponsePublishMessage); ok {
if response.Infomation["code"] == "NetStream.Publish.Start" {
go pusher.push()
} else {
return
}
} }
} }
} }
} }
} }
if !pusher.Stream.IsClosed() && pusher.Reconnect() {
pusher.OnEvent(engine.PullEvent(pusher.PushCount))
}
} }
type RTMPPuller struct { type RTMPPuller struct {
RTMPReceiver
engine.Puller engine.Puller
RTMPClient }
func (puller *RTMPPuller) OnEvent(event any) any {
puller.RTMPReceiver.OnEvent(event)
switch event.(type) {
case *engine.Stream:
puller.NetConnection = NewRTMPClient(puller.RemoteURL)
if puller.NetConnection != nil {
puller.absTs = make(map[uint32]uint32)
puller.SendCommand(SEND_CREATE_STREAM_MESSAGE, nil)
go puller.pull()
break
}
case engine.PullEvent:
puller.PullCount++
if puller.Stream == nil {
if plugin.Publish(puller.StreamPath, puller) {
break
}
}
}
return event
} }
func (puller *RTMPPuller) pull() { func (puller *RTMPPuller) pull() {
defer puller.Unpublish()
for { for {
msg, err := puller.RecvMessage() msg, err := puller.RecvMessage()
if err != nil { if err != nil {
return break
} }
switch msg.MessageTypeID { switch msg.MessageTypeID {
case RTMP_MSG_AUDIO: case RTMP_MSG_AUDIO:
puller.ReceiveAudio(msg) puller.ReceiveAudio(msg)
case RTMP_MSG_VIDEO: case RTMP_MSG_VIDEO:
puller.ReceiveVideo(msg) puller.ReceiveVideo(msg)
// case RTMP_MSG_AMF0_COMMAND: case RTMP_MSG_AMF0_COMMAND:
// cmd := msg.MsgData.(Commander).GetCommand() cmd := msg.MsgData.(Commander).GetCommand()
// switch cmd.CommandName { switch cmd.CommandName {
// case "_result": case "_result":
// if response, ok := msg.MsgData.(*ResponsePlayMessage); ok { if response, ok := msg.MsgData.(*ResponseCreateStreamMessage); ok {
// if response.Object["code"] == "NetStream.Play.Start" { puller.StreamID = response.StreamId
m := &PlayMessage{}
m.CommandMessage.CommandName = "play"
m.StreamName = puller.Stream.StreamName
puller.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
// if response, ok := msg.MsgData.(*ResponsePlayMessage); ok {
// if response.Object["code"] == "NetStream.Play.Start" {
// } else if response.Object["level"] == Level_Error { // } else if response.Object["level"] == Level_Error {
// return errors.New(response.Object["code"].(string)) // return errors.New(response.Object["code"].(string))
// } // }
// } else { // } else {
// return errors.New("pull faild") // return errors.New("pull faild")
// } // }
// } }
// } }
} }
} }
} }
func (puller *RTMPPuller) Pull(count int) {
if puller.Connect(puller.RemoteURL) {
puller.SendCommand(SEND_PLAY_MESSAGE, AMFObject{"StreamPath": puller.StreamName})
puller.MediaReceiver = NewMediaReceiver(&puller.Publisher)
go puller.pull()
}
}

View File

@@ -90,7 +90,7 @@ func (nc *NetConnection) Handshake() error {
return nc.complex_handshake(C1) return nc.complex_handshake(C1)
} }
func (client *RTMPClient) Handshake() error { func (client *NetConnection) ClientHandshake() error {
C0C1 := make([]byte, 1536+1) C0C1 := make([]byte, 1536+1)
C0C1[0] = RTMP_HANDSHAKE_VERSION C0C1[0] = RTMP_HANDSHAKE_VERSION
client.Write(C0C1) client.Write(C0C1)

33
main.go
View File

@@ -2,11 +2,10 @@ package rtmp
import ( import (
"context" "context"
"errors"
. "github.com/Monibuca/engine/v4" . "github.com/Monibuca/engine/v4"
"github.com/Monibuca/engine/v4/config" "github.com/Monibuca/engine/v4/config"
. "github.com/logrusorgru/aurora" "go.uber.org/zap"
) )
type RTMPConfig struct { type RTMPConfig struct {
@@ -18,13 +17,15 @@ type RTMPConfig struct {
ChunkSize int ChunkSize int
} }
var _ PullPlugin = (*RTMPConfig)(nil)
func (config *RTMPConfig) Update(override config.Config) { func (config *RTMPConfig) Update(override config.Config) {
plugin.Info(Green("server rtmp start at"), BrightBlue(config.ListenAddr)) plugin.Info("server rtmp start at", zap.String("listen addr", config.ListenAddr))
err := config.Listen(plugin, config) err := config.Listen(plugin, config)
if err == context.Canceled { if err == context.Canceled {
plugin.Println(err) plugin.Info("rtmp listen shutdown")
} else { } else {
plugin.Fatal(err) plugin.Fatal("rtmp server", zap.Error(err))
} }
} }
@@ -33,22 +34,16 @@ var plugin = InstallPlugin(&RTMPConfig{
TCP: config.TCP{ListenAddr: ":1935"}, TCP: config.TCP{ListenAddr: ":1935"},
}) })
func (config *RTMPConfig) PullStream(streamPath string, puller Puller) error { func (config *RTMPConfig) PullStream(puller Puller) {
var client RTMPPuller client := RTMPPuller{
client.Puller = puller Puller: puller,
if client.Publish(streamPath, &client, config.Publish) {
return nil
} else {
return errors.New("publish faild")
} }
client.OnEvent(PullEvent(0))
} }
func (config *RTMPConfig) PushStream(stream *Stream, pusher Pusher) error { func (config *RTMPConfig) PushStream(pusher Pusher) {
var client RTMPPusher client := RTMPPusher{
client.ID = "RTMPPusher" Pusher: pusher,
client.Pusher = pusher
if client.Subscribe(stream.Path,config.Subscribe) {
client.Pusher.Push(&client, config.Push)
} }
return nil client.OnEvent(PushEvent(0))
} }

142
media.go
View File

@@ -3,55 +3,125 @@ package rtmp
import ( import (
"net" "net"
"github.com/Monibuca/engine/v4"
. "github.com/Monibuca/engine/v4" . "github.com/Monibuca/engine/v4"
"github.com/Monibuca/engine/v4/codec" "github.com/Monibuca/engine/v4/codec"
"github.com/Monibuca/engine/v4/track" "github.com/Monibuca/engine/v4/track"
"github.com/Monibuca/engine/v4/util"
) )
func SendMedia(nc *NetConnection, sub *Subscriber) (err error) { type RTMPSender struct {
vt, at := sub.WaitVideoTrack(), sub.WaitAudioTrack() Subscriber
if vt != nil { NetStream
frame := vt.DecoderConfiguration }
err = nc.sendAVMessage(0, net.Buffers(frame.AVCC), false, true)
sub.OnVideo = func(frame *engine.VideoFrame) error { func (rtmp *RTMPSender) OnEvent(event any) any {
return nc.sendAVMessage(frame.DeltaTime, frame.AVCC, false, false) rtmp.Subscriber.OnEvent(event)
} switch v := event.(type) {
} case TrackRemoved:
if at != nil { //TODO
sub.OnAudio = func(frame *engine.AudioFrame) (err error) { case *track.Audio:
if at.CodecID == codec.CodecID_AAC { isPlaying := rtmp.IsPlaying()
frame := at.DecoderConfiguration if rtmp.AddTrack(v) {
err = nc.sendAVMessage(0, net.Buffers{frame.AVCC}, true, true) if v.CodecID == codec.CodecID_AAC {
} else { rtmp.sendAVMessage(0, net.Buffers{rtmp.Subscriber.AudioTrack.DecoderConfiguration.AVCC}, false, true)
err = nc.sendAVMessage(0, frame.AVCC, true, true)
} }
sub.OnAudio = func(frame *engine.AudioFrame) error { // 如果不订阅视频则遇到音频也播放,否则需要等视频先播放
return nc.sendAVMessage(frame.DeltaTime, frame.AVCC, true, false) if !isPlaying && !rtmp.Config.SubVideo {
go rtmp.Play()
} }
return
} }
case *track.Video:
isPlaying := rtmp.IsPlaying()
if rtmp.AddTrack(v) {
rtmp.sendAVMessage(0, net.Buffers(rtmp.Subscriber.VideoTrack.DecoderConfiguration.AVCC), true, true)
if !isPlaying {
go rtmp.Play()
}
}
case *AudioFrame:
rtmp.sendAVMessage(v.DeltaTime, v.AVCC, true, false)
case *VideoFrame:
rtmp.sendAVMessage(v.DeltaTime, v.AVCC, false, false)
} }
sub.Play(at, vt) return event
}
// 当发送音视频数据的时候,当块类型为12的时候,Chunk Message Header有一个字段TimeStamp,指明一个时间
// 当块类型为4,8的时候,Chunk Message Header有一个字段TimeStamp Delta,记录与上一个Chunk的时间差值
// 当块类型为0的时候,Chunk Message Header没有时间字段,与上一个Chunk时间值相同
func (sender *RTMPSender) sendAVMessage(ts uint32, payload net.Buffers, isAudio bool, isFirst bool) (err error) {
if sender.writeSeqNum > sender.bandwidth {
sender.totalWrite += sender.writeSeqNum
sender.writeSeqNum = 0
sender.SendMessage(RTMP_MSG_ACK, Uint32Message(sender.totalWrite))
sender.SendStreamID(RTMP_USER_PING_REQUEST, 0)
}
var head *ChunkHeader
if isAudio {
head = newRtmpHeader(RTMP_CSID_AUDIO, ts, uint32(util.SizeOfBuffers(payload)), RTMP_MSG_AUDIO, sender.StreamID, 0)
} else {
head = newRtmpHeader(RTMP_CSID_VIDEO, ts, uint32(util.SizeOfBuffers(payload)), RTMP_MSG_VIDEO, sender.StreamID, 0)
}
// 第一次是发送关键帧,需要完整的消息头(Chunk Basic Header(1) + Chunk Message Header(11) + Extended Timestamp(4)(可能会要包括))
// 后面开始,就是直接发送音视频数据,那么直接发送,不需要完整的块(Chunk Basic Header(1) + Chunk Message Header(7))
// 当Chunk Type为0时(即Chunk12),
var chunk1 net.Buffers
if isFirst {
chunk1 = append(chunk1, sender.encodeChunk12(head))
} else {
chunk1 = append(chunk1, sender.encodeChunk8(head))
}
chunks := util.SplitBuffers(payload, sender.writeChunkSize)
chunk1 = append(chunk1, chunks[0]...)
sender.writeSeqNum += uint32(util.SizeOfBuffers(chunk1))
_, err = chunk1.WriteTo(sender.NetConnection)
// 如果在音视频数据太大,一次发送不完,那么这里进行分割(data + Chunk Basic Header(1))
for _, chunk := range chunks[1:] {
chunk1 = net.Buffers{sender.encodeChunk1(head)}
chunk1 = append(chunk1, chunk...)
sender.writeSeqNum += uint32(util.SizeOfBuffers(chunk1))
_, err = chunk1.WriteTo(sender.NetConnection)
}
return nil return nil
} }
func NewMediaReceiver(pub *Publisher) *MediaReceiver { func (r *RTMPSender) Response(code, level string) error {
return &MediaReceiver{ m := new(ResponsePlayMessage)
pub, m.CommandName = Response_OnStatus
make(map[uint32]uint32), pub.NewVideoTrack(), pub.NewAudioTrack(), m.TransactionId = 0
m.Object = AMFObject{
"code": code,
"level": level,
"clientid": 1,
} }
m.StreamID = r.StreamID
return r.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
} }
type MediaReceiver struct { type RTMPReceiver struct {
*Publisher Publisher
absTs map[uint32]uint32 NetStream
vt *track.UnknowVideo absTs map[uint32]uint32
at *track.UnknowAudio
} }
func (r *MediaReceiver) ReceiveAudio(msg *Chunk) { func (r *RTMPReceiver) Response(code, level string) error {
plugin.Tracef("rec_audio chunkType:%d chunkStreamID:%d ts:%d", msg.ChunkType, msg.ChunkStreamID, msg.Timestamp) m := new(ResponsePublishMessage)
m.CommandName = Response_OnStatus
m.TransactionId = 0
m.Infomation = AMFObject{
"code": code,
"level": level,
"clientid": 1,
}
m.StreamID = r.StreamID
return r.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
}
func (r *RTMPReceiver) ReceiveAudio(msg *Chunk) {
// plugin.Tracef("rec_audio chunkType:%d chunkStreamID:%d ts:%d", msg.ChunkType, msg.ChunkStreamID, msg.Timestamp)
if msg.ChunkType == 0 { if msg.ChunkType == 0 {
r.absTs[msg.ChunkStreamID] = 0 r.absTs[msg.ChunkStreamID] = 0
} }
@@ -60,10 +130,10 @@ func (r *MediaReceiver) ReceiveAudio(msg *Chunk) {
} else { } else {
r.absTs[msg.ChunkStreamID] += msg.Timestamp r.absTs[msg.ChunkStreamID] += msg.Timestamp
} }
r.at.WriteAVCC(r.absTs[msg.ChunkStreamID], msg.Body) r.AudioTrack.WriteAVCC(r.absTs[msg.ChunkStreamID], msg.Body)
} }
func (r *MediaReceiver) ReceiveVideo(msg *Chunk) { func (r *RTMPReceiver) ReceiveVideo(msg *Chunk) {
plugin.Tracef("rev_video chunkType:%d chunkStreamID:%d ts:%d", msg.ChunkType, msg.ChunkStreamID, msg.Timestamp) // plugin.Tracef("rev_video chunkType:%d chunkStreamID:%d ts:%d", msg.ChunkType, msg.ChunkStreamID, msg.Timestamp)
if msg.ChunkType == 0 { if msg.ChunkType == 0 {
r.absTs[msg.ChunkStreamID] = 0 r.absTs[msg.ChunkStreamID] = 0
} }
@@ -72,5 +142,5 @@ func (r *MediaReceiver) ReceiveVideo(msg *Chunk) {
} else { } else {
r.absTs[msg.ChunkStreamID] += msg.Timestamp r.absTs[msg.ChunkStreamID] += msg.Timestamp
} }
r.vt.WriteAVCC(r.absTs[msg.ChunkStreamID], msg.Body) r.VideoTrack.WriteAVCC(r.absTs[msg.ChunkStreamID], msg.Body)
} }

35
msg.go
View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/Monibuca/engine/v4/util" "github.com/Monibuca/engine/v4/util"
"go.uber.org/zap"
) )
const ( const (
@@ -217,6 +218,7 @@ func decodeCommandAMF0(chunk *Chunk) {
chunk.MsgData = &CreateStreamMessage{ chunk.MsgData = &CreateStreamMessage{
cmdMsg, amf.readObject(), cmdMsg, amf.readObject(),
} }
case "play": case "play":
amf.readNull() amf.readNull()
chunk.MsgData = &PlayMessage{ chunk.MsgData = &PlayMessage{
@@ -239,7 +241,10 @@ func decodeCommandAMF0(chunk *Chunk) {
case "publish": case "publish":
amf.readNull() amf.readNull()
chunk.MsgData = &PublishMessage{ chunk.MsgData = &PublishMessage{
cmdMsg, CURDStreamMessage{
cmdMsg,
chunk.MessageStreamID,
},
amf.readString(), amf.readString(),
amf.readString(), amf.readString(),
} }
@@ -286,13 +291,14 @@ func decodeCommandAMF0(chunk *Chunk) {
amf.readObject(), amf.readObject(),
amf.readObject(), "", amf.readObject(), "",
} }
codef := zap.String("code", response.Infomation["code"].(string))
switch response.Infomation["level"] { switch response.Infomation["level"] {
case Level_Status: case Level_Status:
plugin.Infof("_result :", response.Infomation["code"]) plugin.Info("_result :", codef)
case Level_Warning: case Level_Warning:
plugin.Warnf("_result :", response.Infomation["code"]) plugin.Warn("_result :", codef)
case Level_Error: case Level_Error:
plugin.Errorf("_result :", response.Infomation["code"]) plugin.Error("_result :", codef)
} }
if strings.HasPrefix(response.Infomation["code"].(string), "NetStream.Publish") { if strings.HasPrefix(response.Infomation["code"].(string), "NetStream.Publish") {
chunk.MsgData = &ResponsePublishMessage{ chunk.MsgData = &ResponsePublishMessage{
@@ -313,7 +319,7 @@ func decodeCommandAMF0(chunk *Chunk) {
} }
case "FCPublish", "FCUnpublish": case "FCPublish", "FCUnpublish":
default: default:
plugin.Println("decode command amf0 cmd:", cmd) plugin.Info("decode command amf0 ", zap.String("cmd", cmd))
} }
} }
@@ -477,7 +483,7 @@ type PlayMessage struct {
StreamName string StreamName string
Start uint64 Start uint64
Duration uint64 Duration uint64
Rest bool Reset bool
} }
// 命令名 -> 命令名,设置为”play” // 命令名 -> 命令名,设置为”play”
@@ -502,7 +508,7 @@ func (msg *PlayMessage) Encode() []byte {
amf.writeNumber(float64(msg.Duration)) amf.writeNumber(float64(msg.Duration))
} }
amf.writeBool(msg.Rest) amf.writeBool(msg.Reset)
return amf.Buffer return amf.Buffer
} }
@@ -532,6 +538,10 @@ type CURDStreamMessage struct {
StreamId uint32 StreamId uint32
} }
func (msg *CURDStreamMessage) GetStreamID() uint32 {
return msg.StreamId
}
func (msg *CURDStreamMessage) Encode0() { func (msg *CURDStreamMessage) Encode0() {
} }
@@ -557,7 +567,7 @@ func (msg *ReceiveAVMessage) Encode0() {
// The client sends the publish command to publish a named stream to the server. Using this name, // The client sends the publish command to publish a named stream to the server. Using this name,
// any client can play this stream and receive the published audio, video, and data messages // any client can play this stream and receive the published audio, video, and data messages
type PublishMessage struct { type PublishMessage struct {
CommandMessage CURDStreamMessage
PublishingName string PublishingName string
PublishingType string PublishingType string
} }
@@ -572,7 +582,14 @@ type PublishMessage struct {
// “append”:流被发布并且附加到一个文件之后.如果没有发现文件则创建一个文件. // “append”:流被发布并且附加到一个文件之后.如果没有发现文件则创建一个文件.
// “live”:发布直播数据而不录制到文件 // “live”:发布直播数据而不录制到文件
func (msg *PublishMessage) Encode0() { func (msg *PublishMessage) Encode0() []byte {
var amf AMF
amf.writeString(msg.CommandName)
amf.writeNumber(float64(msg.TransactionId))
amf.writeNull()
amf.writeString(msg.PublishingName)
amf.writeString(msg.PublishingType)
return amf.Buffer
} }
// Seek Message // Seek Message

View File

@@ -28,7 +28,6 @@ const (
SEND_CONNECT_RESPONSE_MESSAGE = "Send Connect Response Message" SEND_CONNECT_RESPONSE_MESSAGE = "Send Connect Response Message"
SEND_CREATE_STREAM_MESSAGE = "Send Create Stream Message" SEND_CREATE_STREAM_MESSAGE = "Send Create Stream Message"
SEND_CREATE_STREAM_RESPONSE_MESSAGE = "Send Create Stream Response Message"
SEND_PLAY_MESSAGE = "Send Play Message" SEND_PLAY_MESSAGE = "Send Play Message"
SEND_PLAY_RESPONSE_MESSAGE = "Send Play Response Message" SEND_PLAY_RESPONSE_MESSAGE = "Send Play Response Message"
@@ -76,8 +75,6 @@ func newPlayResponseMessageData(streamid uint32, code, level string) (amfobj AMF
} }
type NetConnection struct { type NetConnection struct {
*MediaReceiver
subscribers map[uint32]*Subscriber
*bufio.Reader *bufio.Reader
*net.TCPConn *net.TCPConn
bandwidth uint32 bandwidth uint32
@@ -88,35 +85,30 @@ type NetConnection struct {
writeChunkSize int writeChunkSize int
readChunkSize int readChunkSize int
incompleteRtmpBody map[uint32]util.Buffer // 完整的RtmpBody,在网络上是被分成一块一块的,需要将其组装起来 incompleteRtmpBody map[uint32]util.Buffer // 完整的RtmpBody,在网络上是被分成一块一块的,需要将其组装起来
streamID uint32 // 流ID
rtmpHeader map[uint32]*ChunkHeader // RtmpHeader rtmpHeader map[uint32]*ChunkHeader // RtmpHeader
objectEncoding float64 objectEncoding float64
appName string appName string
tmpBuf []byte //用来接收小数据,复用内存 tmpBuf []byte //用来接收小数据,复用内存
} }
func (conn *NetConnection) Close() {
if conn.MediaReceiver != nil {
conn.UnPublish()
}
conn.TCPConn.Close()
for _, s := range conn.subscribers {
s.Close()
}
}
func (conn *NetConnection) ReadFull(buf []byte) (n int, err error) { func (conn *NetConnection) ReadFull(buf []byte) (n int, err error) {
return io.ReadFull(conn.Reader, buf) return io.ReadFull(conn.Reader, buf)
} }
func (conn *NetConnection) SendStreamID0(eventType uint16) (err error) { func (conn *NetConnection) SendStreamID(eventType uint16, streamID uint32) (err error) {
return conn.SendMessage(RTMP_MSG_USER_CONTROL, &StreamIDMessage{UserControlMessage{EventType: eventType}, 0}) return conn.SendMessage(RTMP_MSG_USER_CONTROL, &StreamIDMessage{UserControlMessage{EventType: eventType}, streamID})
}
func (conn *NetConnection) SendStreamID(eventType uint16) (err error) {
return conn.SendMessage(RTMP_MSG_USER_CONTROL, &StreamIDMessage{UserControlMessage{EventType: eventType}, conn.streamID})
} }
func (conn *NetConnection) SendUserControl(eventType uint16) error { func (conn *NetConnection) SendUserControl(eventType uint16) error {
return conn.SendMessage(RTMP_MSG_USER_CONTROL, &UserControlMessage{EventType: eventType}) return conn.SendMessage(RTMP_MSG_USER_CONTROL, &UserControlMessage{EventType: eventType})
} }
func (conn *NetConnection) ResponseCreateStream(tid uint64, streamID uint32) error {
m := &ResponseCreateStreamMessage{}
m.CommandName = Response_Result
m.TransactionId = tid
m.StreamId = streamID
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
}
func (conn *NetConnection) SendCommand(message string, args any) error { func (conn *NetConnection) SendCommand(message string, args any) error {
switch message { switch message {
// case SEND_SET_BUFFER_LENGTH_MESSAGE: // case SEND_SET_BUFFER_LENGTH_MESSAGE:
@@ -137,16 +129,6 @@ func (conn *NetConnection) SendCommand(message string, args any) error {
m.CommandName = "createStream" m.CommandName = "createStream"
m.TransactionId = 2 m.TransactionId = 2
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m) return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
case SEND_CREATE_STREAM_RESPONSE_MESSAGE:
tid, ok := args.(uint64)
if !ok {
return errors.New(SEND_CREATE_STREAM_RESPONSE_MESSAGE + ", The parameter only one(TransactionId uint64)!")
}
m := &ResponseCreateStreamMessage{}
m.CommandName = Response_Result
m.TransactionId = tid
m.StreamId = conn.streamID
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
case SEND_PLAY_MESSAGE: case SEND_PLAY_MESSAGE:
data, ok := args.(AMFObject) data, ok := args.(AMFObject)
if !ok { if !ok {
@@ -156,14 +138,14 @@ func (conn *NetConnection) SendCommand(message string, args any) error {
m.CommandName = "play" m.CommandName = "play"
m.TransactionId = 1 m.TransactionId = 1
for i, v := range data { for i, v := range data {
if i == "StreamPath" { if i == "StreamName" {
m.StreamName = v.(string) m.StreamName = v.(string)
} else if i == "Start" { } else if i == "Start" {
m.Start = v.(uint64) m.Start = v.(uint64)
} else if i == "Duration" { } else if i == "Duration" {
m.Duration = v.(uint64) m.Duration = v.(uint64)
} else if i == "Rest" { } else if i == "Reset" {
m.Rest = v.(bool) m.Reset = v.(bool)
} }
} }
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m) return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
@@ -246,16 +228,7 @@ func (conn *NetConnection) SendCommand(message string, args any) error {
m.Object = obj m.Object = obj
m.Optional = info m.Optional = info
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m) return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
case SEND_PUBLISH_RESPONSE_MESSAGE, SEND_PUBLISH_START_MESSAGE:
info := args.(AMFObject)
info["clientid"] = 1
m := new(ResponsePublishMessage)
m.CommandName = Response_OnStatus
m.TransactionId = 0
m.Infomation = info
m.StreamID = info["streamid"].(uint32)
delete(info, "streamid")
return conn.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
case SEND_UNPUBLISH_RESPONSE_MESSAGE: case SEND_UNPUBLISH_RESPONSE_MESSAGE:
data, ok := args.(AMFObject) data, ok := args.(AMFObject)
if !ok { if !ok {
@@ -269,48 +242,6 @@ func (conn *NetConnection) SendCommand(message string, args any) error {
return errors.New("send message no exist") return errors.New("send message no exist")
} }
// 当发送音视频数据的时候,当块类型为12的时候,Chunk Message Header有一个字段TimeStamp,指明一个时间
// 当块类型为4,8的时候,Chunk Message Header有一个字段TimeStamp Delta,记录与上一个Chunk的时间差值
// 当块类型为0的时候,Chunk Message Header没有时间字段,与上一个Chunk时间值相同
func (conn *NetConnection) sendAVMessage(ts uint32, payload net.Buffers, isAudio bool, isFirst bool) (err error) {
if conn.writeSeqNum > conn.bandwidth {
conn.totalWrite += conn.writeSeqNum
conn.writeSeqNum = 0
conn.SendMessage(RTMP_MSG_ACK, Uint32Message(conn.totalWrite))
conn.SendStreamID0(RTMP_USER_PING_REQUEST)
}
var head *ChunkHeader
if isAudio {
head = newRtmpHeader(RTMP_CSID_AUDIO, ts, uint32(util.SizeOfBuffers(payload)), RTMP_MSG_AUDIO, conn.streamID, 0)
} else {
head = newRtmpHeader(RTMP_CSID_VIDEO, ts, uint32(util.SizeOfBuffers(payload)), RTMP_MSG_VIDEO, conn.streamID, 0)
}
// 第一次是发送关键帧,需要完整的消息头(Chunk Basic Header(1) + Chunk Message Header(11) + Extended Timestamp(4)(可能会要包括))
// 后面开始,就是直接发送音视频数据,那么直接发送,不需要完整的块(Chunk Basic Header(1) + Chunk Message Header(7))
// 当Chunk Type为0时(即Chunk12),
var chunk1 net.Buffers
if isFirst {
chunk1 = append(chunk1, conn.encodeChunk12(head))
} else {
chunk1 = append(chunk1, conn.encodeChunk8(head))
}
chunks := util.SplitBuffers(payload, conn.writeChunkSize)
chunk1 = append(chunk1, chunks[0]...)
conn.writeSeqNum += uint32(util.SizeOfBuffers(chunk1))
_, err = chunk1.WriteTo(conn)
// 如果在音视频数据太大,一次发送不完,那么这里进行分割(data + Chunk Basic Header(1))
for _, chunk := range chunks[1:] {
chunk1 = net.Buffers{conn.encodeChunk1(head)}
chunk1 = append(chunk1, chunk...)
conn.writeSeqNum += uint32(util.SizeOfBuffers(chunk1))
_, err = chunk1.WriteTo(conn)
}
return nil
}
func (conn *NetConnection) readChunk() (msg *Chunk, err error) { func (conn *NetConnection) readChunk() (msg *Chunk, err error) {
head, err := conn.ReadByte() head, err := conn.ReadByte()
conn.readSeqNum++ conn.readSeqNum++
@@ -357,7 +288,6 @@ func (conn *NetConnection) readChunk() (msg *Chunk, err error) {
needRead = unRead needRead = unRead
} }
if n, err := conn.ReadFull(currentBody.Malloc(needRead)); err != nil { if n, err := conn.ReadFull(currentBody.Malloc(needRead)); err != nil {
plugin.Error(err)
return nil, err return nil, err
} else { } else {
conn.readSeqNum += uint32(n) conn.readSeqNum += uint32(n)
@@ -550,7 +480,7 @@ func (conn *NetConnection) SendMessage(t byte, msg RtmpMessage) (err error) {
conn.totalWrite += conn.writeSeqNum conn.totalWrite += conn.writeSeqNum
conn.writeSeqNum = 0 conn.writeSeqNum = 0
err = conn.SendMessage(RTMP_MSG_ACK, Uint32Message(conn.totalWrite)) err = conn.SendMessage(RTMP_MSG_ACK, Uint32Message(conn.totalWrite))
err = conn.SendStreamID0(RTMP_USER_PING_REQUEST) err = conn.SendStreamID(RTMP_USER_PING_REQUEST, 0)
} }
var chunk = net.Buffers{conn.encodeChunk12(head)} var chunk = net.Buffers{conn.encodeChunk12(head)}
if len(body) > conn.writeChunkSize { if len(body) > conn.writeChunkSize {

View File

@@ -2,17 +2,30 @@ package rtmp
import ( import (
"bufio" "bufio"
"context"
"fmt" "fmt"
"net" "net"
"sync/atomic" "sync/atomic"
"github.com/Monibuca/engine/v4" "github.com/Monibuca/engine/v4"
"github.com/Monibuca/engine/v4/util" "github.com/Monibuca/engine/v4/util"
"go.uber.org/zap"
) )
type NetStream struct {
*NetConnection
StreamID uint32
}
func (ns *NetStream) Begin() {
ns.SendStreamID(RTMP_USER_STREAM_BEGIN, ns.StreamID)
}
var gstreamid = uint32(64) var gstreamid = uint32(64)
func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) { func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
senders := make(map[uint32]*RTMPSender)
receivers := make(map[uint32]*RTMPReceiver)
nc := NetConnection{ nc := NetConnection{
TCPConn: conn, TCPConn: conn,
Reader: bufio.NewReader(conn), Reader: bufio.NewReader(conn),
@@ -22,12 +35,13 @@ func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
incompleteRtmpBody: make(map[uint32]util.Buffer), incompleteRtmpBody: make(map[uint32]util.Buffer),
bandwidth: RTMP_MAX_CHUNK_SIZE << 3, bandwidth: RTMP_MAX_CHUNK_SIZE << 3,
tmpBuf: make([]byte, 4), tmpBuf: make([]byte, 4),
subscribers: make(map[uint32]*engine.Subscriber),
} }
ctx, cancel := context.WithCancel(engine.Engine)
defer nc.Close() defer nc.Close()
defer cancel()
/* Handshake */ /* Handshake */
if err := nc.Handshake(); err != nil { if err := nc.Handshake(); err != nil {
plugin.Error("handshake", err) plugin.Error("handshake", zap.Error(err))
return return
} }
for { for {
@@ -41,7 +55,7 @@ func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
break break
} }
cmd := msg.MsgData.(Commander).GetCommand() cmd := msg.MsgData.(Commander).GetCommand()
plugin.Debugf("recv cmd '%s'", cmd.CommandName) plugin.Debug("recv cmd", zap.String("commandName", cmd.CommandName), zap.Uint32("streamID", msg.MessageStreamID))
switch cmd.CommandName { switch cmd.CommandName {
case "connect": case "connect":
connect := msg.MsgData.(*CallMessage) connect := msg.MsgData.(*CallMessage)
@@ -51,7 +65,7 @@ func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
nc.objectEncoding = objectEncoding.(float64) nc.objectEncoding = objectEncoding.(float64)
} }
nc.appName = app.(string) nc.appName = app.(string)
plugin.Infof("connect app:'%s',objectEncoding:%v", nc.appName, objectEncoding) plugin.Info("connect", zap.String("appName", nc.appName), zap.Float64("objectEncoding", nc.objectEncoding))
err = nc.SendMessage(RTMP_MSG_ACK_SIZE, Uint32Message(512<<10)) err = nc.SendMessage(RTMP_MSG_ACK_SIZE, Uint32Message(512<<10))
nc.writeChunkSize = config.ChunkSize nc.writeChunkSize = config.ChunkSize
err = nc.SendMessage(RTMP_MSG_CHUNK_SIZE, Uint32Message(config.ChunkSize)) err = nc.SendMessage(RTMP_MSG_CHUNK_SIZE, Uint32Message(config.ChunkSize))
@@ -59,59 +73,61 @@ func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
AcknowledgementWindowsize: uint32(512 << 10), AcknowledgementWindowsize: uint32(512 << 10),
LimitType: byte(2), LimitType: byte(2),
}) })
err = nc.SendStreamID(RTMP_USER_STREAM_BEGIN) err = nc.SendStreamID(RTMP_USER_STREAM_BEGIN, 0)
err = nc.SendCommand(SEND_CONNECT_RESPONSE_MESSAGE, nc.objectEncoding) err = nc.SendCommand(SEND_CONNECT_RESPONSE_MESSAGE, nc.objectEncoding)
case "createStream": case "createStream":
nc.streamID = atomic.AddUint32(&gstreamid, 1) streamId := atomic.AddUint32(&gstreamid, 1)
plugin.Info("createStream:", nc.streamID) plugin.Info("createStream:", zap.Uint32("streamId", streamId))
err = nc.SendCommand(SEND_CREATE_STREAM_RESPONSE_MESSAGE, cmd.TransactionId) nc.ResponseCreateStream(cmd.TransactionId, streamId)
if err != nil {
plugin.Error(err)
return
}
case "publish": case "publish":
pm := msg.MsgData.(*PublishMessage) pm := msg.MsgData.(*PublishMessage)
var puber engine.Publisher receiver := &RTMPReceiver{
if puber.Publish(nc.appName+"/"+pm.PublishingName, &nc, config.Publish) { NetStream: NetStream{
nc.MediaReceiver = NewMediaReceiver(&puber) NetConnection: &nc,
nc.SendStreamID(RTMP_USER_STREAM_BEGIN) StreamID: pm.StreamId,
err = nc.SendCommand(SEND_PUBLISH_START_MESSAGE, newPublishResponseMessageData(nc.streamID, NetStream_Publish_Start, Level_Status)) },
}
receiver.OnEvent(ctx)
if plugin.Publish(nc.appName+"/"+pm.PublishingName, receiver) {
receiver.absTs = make(map[uint32]uint32)
receiver.Begin()
err = receiver.Response(NetStream_Publish_Start, Level_Status)
} else { } else {
err = nc.SendCommand(SEND_PUBLISH_RESPONSE_MESSAGE, newPublishResponseMessageData(nc.streamID, NetStream_Publish_BadName, Level_Error)) err = receiver.Response(NetStream_Publish_BadName, Level_Error)
} }
case "play": case "play":
pm := msg.MsgData.(*PlayMessage) pm := msg.MsgData.(*PlayMessage)
streamPath := nc.appName + "/" + pm.StreamName streamPath := nc.appName + "/" + pm.StreamName
subscriber := &engine.Subscriber{ sender := &RTMPSender{
Type: "RTMP", NetStream: NetStream{
ID: fmt.Sprintf("%s|%d", conn.RemoteAddr().String(), nc.streamID), NetConnection: &nc,
StreamID: msg.MessageStreamID,
},
} }
if subscriber.Subscribe(streamPath, config.Subscribe) { sender.OnEvent(ctx)
nc.subscribers[nc.streamID] = subscriber sender.ID = fmt.Sprintf("%s|%d", conn.RemoteAddr().String(), sender.StreamID)
err = nc.SendStreamID(RTMP_USER_STREAM_IS_RECORDED) if plugin.Subscribe(streamPath, sender) {
err = nc.SendStreamID(RTMP_USER_STREAM_BEGIN) senders[msg.MessageStreamID] = sender
err = nc.SendCommand(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Reset, Level_Status)) err = nc.SendStreamID(RTMP_USER_STREAM_IS_RECORDED, msg.MessageStreamID)
err = nc.SendCommand(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Start, Level_Status)) sender.Begin()
go func() { sender.Response(NetStream_Play_Reset, Level_Status)
SendMedia(&nc, subscriber) sender.Response(NetStream_Play_Start, Level_Status)
err = nc.SendCommand(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Stop, Level_Status))
err = nc.SendCommand(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Complete, Level_Status))
}()
} else { } else {
err = nc.SendCommand(SEND_PLAY_RESPONSE_MESSAGE, newPlayResponseMessageData(nc.streamID, NetStream_Play_Failed, Level_Error)) sender.Response(NetStream_Play_Failed, Level_Error)
} }
case "closeStream": case "closeStream":
cm := msg.MsgData.(*CURDStreamMessage) cm := msg.MsgData.(*CURDStreamMessage)
if stream, ok := nc.subscribers[cm.StreamId]; ok { if stream, ok := senders[cm.StreamId]; ok {
stream.Close() stream.Unsubscribe()
delete(nc.subscribers, cm.StreamId) delete(senders, cm.StreamId)
} }
case "releaseStream": case "releaseStream":
cm := msg.MsgData.(*ReleaseStreamMessage) cm := msg.MsgData.(*ReleaseStreamMessage)
amfobj := make(AMFObject) amfobj := make(AMFObject)
if nc.Stream != nil && nc.Stream.AppName == nc.appName && nc.Stream.StreamName == cm.StreamName { p, ok := receivers[msg.MessageStreamID]
if ok {
amfobj["level"] = "_result" amfobj["level"] = "_result"
nc.Stream.UnPublish() p.Unpublish()
} else { } else {
amfobj["level"] = "_error" amfobj["level"] = "_error"
} }
@@ -119,12 +135,16 @@ func (config *RTMPConfig) ServeTCP(conn *net.TCPConn) {
err = nc.SendCommand(SEND_UNPUBLISH_RESPONSE_MESSAGE, amfobj) err = nc.SendCommand(SEND_UNPUBLISH_RESPONSE_MESSAGE, amfobj)
} }
case RTMP_MSG_AUDIO: case RTMP_MSG_AUDIO:
nc.ReceiveAudio(msg) if r, ok := receivers[msg.MessageStreamID]; ok {
r.ReceiveAudio(msg)
}
case RTMP_MSG_VIDEO: case RTMP_MSG_VIDEO:
nc.ReceiveVideo(msg) if r, ok := receivers[msg.MessageStreamID]; ok {
r.ReceiveVideo(msg)
}
} }
} else { } else {
plugin.Error(err) plugin.Error("receive", zap.Error(err))
return return
} }
} }