mirror of
https://github.com/langhuihui/monibuca.git
synced 2025-10-05 15:57:03 +08:00
feat: add hdl play
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
global:
|
global:
|
||||||
loglevel: debug
|
loglevel: debug
|
||||||
rtmp:
|
rtmp:
|
||||||
publish:
|
subscribe:
|
||||||
# pubvideo: false
|
# submode: 1
|
||||||
|
subaudio: false
|
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"m7s.live/m7s/v5"
|
"m7s.live/m7s/v5"
|
||||||
_ "m7s.live/m7s/v5/plugin/rtmp"
|
_ "m7s.live/m7s/v5/plugin/rtmp"
|
||||||
|
_ "m7s.live/m7s/v5/plugin/hdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@@ -95,6 +95,7 @@ type IAVFrame interface {
|
|||||||
GetTimestamp() time.Duration
|
GetTimestamp() time.Duration
|
||||||
Recycle()
|
Recycle()
|
||||||
IsIDR() bool
|
IsIDR() bool
|
||||||
|
Print() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nalu [][]byte
|
type Nalu [][]byte
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
type Pool[T any] struct {
|
type Pool[T any] struct {
|
||||||
pool []T
|
pool []T
|
||||||
@@ -43,3 +45,27 @@ func (r *RecyclableMemory) Recycle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BytesPool struct {
|
||||||
|
Pool[[]byte]
|
||||||
|
ItemSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bp *BytesPool) GetN(size int) []byte {
|
||||||
|
if size != bp.ItemSize {
|
||||||
|
return make([]byte, size)
|
||||||
|
}
|
||||||
|
ret := bp.Pool.Get()
|
||||||
|
if ret == nil {
|
||||||
|
return make([]byte, size)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bp *BytesPool) Put(b []byte) {
|
||||||
|
if cap(b) != bp.ItemSize {
|
||||||
|
bp.ItemSize = cap(b)
|
||||||
|
bp.Clear()
|
||||||
|
}
|
||||||
|
bp.Pool.Put(b)
|
||||||
|
}
|
||||||
|
65
plugin.go
65
plugin.go
@@ -3,6 +3,7 @@ package m7s
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -72,7 +73,7 @@ func (plugin *PluginMeta) Init(s *Server, userConfig map[string]any) {
|
|||||||
}
|
}
|
||||||
p.Info("init", "version", plugin.Version)
|
p.Info("init", "version", plugin.Version)
|
||||||
instance.OnInit()
|
instance.OnInit()
|
||||||
go p.Start()
|
p.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
type iPlugin interface {
|
type iPlugin interface {
|
||||||
@@ -145,8 +146,8 @@ func (p *Plugin) GetCommonConf() *config.Common {
|
|||||||
return &p.config
|
return &p.config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Plugin) settingPath() string {
|
func (p *Plugin) settingPath() string {
|
||||||
return filepath.Join(opt.server.SettingDir, strings.ToLower(opt.Meta.Name)+".yaml")
|
return filepath.Join(p.server.SettingDir, strings.ToLower(p.Meta.Name)+".yaml")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) assign() {
|
func (p *Plugin) assign() {
|
||||||
@@ -160,7 +161,7 @@ func (p *Plugin) assign() {
|
|||||||
}
|
}
|
||||||
p.Config.ParseModifyFile(modifyConfig)
|
p.Config.ParseModifyFile(modifyConfig)
|
||||||
}
|
}
|
||||||
// p.registerHandler()
|
p.registerHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Plugin) Stop(err error) {
|
func (p *Plugin) Stop(err error) {
|
||||||
@@ -172,14 +173,14 @@ func (p *Plugin) Stop(err error) {
|
|||||||
func (p *Plugin) Start() {
|
func (p *Plugin) Start() {
|
||||||
httpConf := p.config.HTTP
|
httpConf := p.config.HTTP
|
||||||
if httpConf.ListenAddrTLS != "" && (httpConf.ListenAddrTLS != p.server.config.HTTP.ListenAddrTLS) {
|
if httpConf.ListenAddrTLS != "" && (httpConf.ListenAddrTLS != p.server.config.HTTP.ListenAddrTLS) {
|
||||||
go func() {
|
|
||||||
p.Info("https listen at ", "addr", httpConf.ListenAddrTLS)
|
p.Info("https listen at ", "addr", httpConf.ListenAddrTLS)
|
||||||
|
go func() {
|
||||||
p.Stop(httpConf.ListenTLS())
|
p.Stop(httpConf.ListenTLS())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if httpConf.ListenAddr != "" && (httpConf.ListenAddr != p.server.config.HTTP.ListenAddr) {
|
if httpConf.ListenAddr != "" && (httpConf.ListenAddr != p.server.config.HTTP.ListenAddr) {
|
||||||
go func() {
|
|
||||||
p.Info("http listen at ", "addr", httpConf.ListenAddr)
|
p.Info("http listen at ", "addr", httpConf.ListenAddr)
|
||||||
|
go func() {
|
||||||
p.Stop(httpConf.Listen())
|
p.Stop(httpConf.Listen())
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -191,21 +192,23 @@ func (p *Plugin) Start() {
|
|||||||
|
|
||||||
if p.config.TCP.ListenAddr != "" {
|
if p.config.TCP.ListenAddr != "" {
|
||||||
p.Info("listen tcp", "addr", tcpConf.ListenAddr)
|
p.Info("listen tcp", "addr", tcpConf.ListenAddr)
|
||||||
|
go func() {
|
||||||
err := tcpConf.Listen(tcphandler.OnTCPConnect)
|
err := tcpConf.Listen(tcphandler.OnTCPConnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error("listen tcp", "addr", tcpConf.ListenAddr, "error", err)
|
p.Error("listen tcp", "addr", tcpConf.ListenAddr, "error", err)
|
||||||
p.Stop(err)
|
p.Stop(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
if tcpConf.ListenAddrTLS != "" {
|
if tcpConf.ListenAddrTLS != "" {
|
||||||
p.Info("listen tcp tls", "addr", tcpConf.ListenAddrTLS)
|
p.Info("listen tcp tls", "addr", tcpConf.ListenAddrTLS)
|
||||||
|
go func() {
|
||||||
err := tcpConf.ListenTLS(tcphandler.OnTCPConnect)
|
err := tcpConf.ListenTLS(tcphandler.OnTCPConnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.Error("listen tcp tls", "addr", tcpConf.ListenAddrTLS, "error", err)
|
p.Error("listen tcp tls", "addr", tcpConf.ListenAddrTLS, "error", err)
|
||||||
p.Stop(err)
|
p.Stop(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,3 +247,49 @@ func (p *Plugin) Subscribe(streamPath string, options ...any) (subscriber *Subsc
|
|||||||
err = sendPromiseToServer(p.server, subscriber)
|
err = sendPromiseToServer(p.server, subscriber)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) registerHandler() {
|
||||||
|
t := reflect.TypeOf(p.handler)
|
||||||
|
v := reflect.ValueOf(p.handler)
|
||||||
|
// 注册http响应
|
||||||
|
for i, j := 0, t.NumMethod(); i < j; i++ {
|
||||||
|
name := t.Method(i).Name
|
||||||
|
if name == "ServeHTTP" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch handler := v.Method(i).Interface().(type) {
|
||||||
|
case func(http.ResponseWriter, *http.Request):
|
||||||
|
patten := strings.ToLower(strings.ReplaceAll(name, "_", "/"))
|
||||||
|
p.handle(patten, http.HandlerFunc(handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rootHandler, ok := p.handler.(http.Handler); ok {
|
||||||
|
p.handle("/", rootHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) logHandler(handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
p.Debug("visit", "path", r.URL.String(), "remote", r.RemoteAddr)
|
||||||
|
name := strings.ToLower(p.Meta.Name)
|
||||||
|
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/"+name)
|
||||||
|
handler.ServeHTTP(rw, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) handle(pattern string, handler http.Handler) {
|
||||||
|
if p == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(pattern, "/") {
|
||||||
|
pattern = "/" + pattern
|
||||||
|
}
|
||||||
|
handler = p.logHandler(handler)
|
||||||
|
p.GetCommonConf().Handle(pattern, handler)
|
||||||
|
if p.server != nil {
|
||||||
|
pattern = "/" + strings.ToLower(p.Meta.Name) + pattern
|
||||||
|
p.Debug("http handle added to server", "pattern", pattern)
|
||||||
|
p.server.GetCommonConf().Handle(pattern, handler)
|
||||||
|
}
|
||||||
|
// apiList = append(apiList, pattern)
|
||||||
|
}
|
||||||
|
@@ -6,7 +6,7 @@ import (
|
|||||||
"m7s.live/m7s/v5"
|
"m7s.live/m7s/v5"
|
||||||
"m7s.live/m7s/v5/pkg"
|
"m7s.live/m7s/v5/pkg"
|
||||||
"m7s.live/m7s/v5/pkg/util"
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
rtmp "m7s.live/m7s/v5/plugin/rtmp/pkg"
|
"m7s.live/m7s/v5/plugin/rtmp/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AnnexB struct {
|
type AnnexB struct {
|
||||||
@@ -48,13 +48,13 @@ type DemoPlugin struct {
|
|||||||
m7s.Plugin
|
m7s.Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DemoPlugin) OnInit() {
|
// func (p *DemoPlugin) OnInit() {
|
||||||
publisher, err := p.Publish("live/demo")
|
// publisher, err := p.Publish("live/demo")
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
var annexB AnnexB
|
// var annexB AnnexB
|
||||||
publisher.WriteVideo(&annexB)
|
// publisher.WriteVideo(&annexB)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (p *DemoPlugin) OnPublish(publisher *m7s.Publisher) {
|
func (p *DemoPlugin) OnPublish(publisher *m7s.Publisher) {
|
||||||
subscriber, err := p.Subscribe(publisher.StreamPath)
|
subscriber, err := p.Subscribe(publisher.StreamPath)
|
||||||
|
116
plugin/hdl/index.go
Normal file
116
plugin/hdl/index.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package plugin_hdl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"m7s.live/m7s/v5"
|
||||||
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
|
. "m7s.live/m7s/v5/plugin/hdl/pkg"
|
||||||
|
rtmp "m7s.live/m7s/v5/plugin/rtmp/pkg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HDLPlugin struct {
|
||||||
|
m7s.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = m7s.InstallPlugin[HDLPlugin]()
|
||||||
|
|
||||||
|
func (p *HDLPlugin) WriteFlvHeader(sub *m7s.Subscriber, w io.Writer) {
|
||||||
|
// at, vt := sub.Publisher, sub.Video
|
||||||
|
// hasAudio, hasVideo := at != nil, vt != nil
|
||||||
|
// var amf rtmp.AMF
|
||||||
|
// amf.Marshal("onMetaData")
|
||||||
|
// metaData := rtmp.EcmaArray{
|
||||||
|
// "MetaDataCreator": "m7s" + m7s.Version,
|
||||||
|
// "hasVideo": hasVideo,
|
||||||
|
// "hasAudio": hasAudio,
|
||||||
|
// "hasMatadata": true,
|
||||||
|
// "canSeekToEnd": false,
|
||||||
|
// "duration": 0,
|
||||||
|
// "hasKeyFrames": 0,
|
||||||
|
// "framerate": 0,
|
||||||
|
// "videodatarate": 0,
|
||||||
|
// "filesize": 0,
|
||||||
|
// }
|
||||||
|
var flags byte
|
||||||
|
// if hasAudio {
|
||||||
|
flags |= (1 << 2)
|
||||||
|
// metaData["audiocodecid"] = int(at.CodecID)
|
||||||
|
// metaData["audiosamplerate"] = at.SampleRate
|
||||||
|
// metaData["audiosamplesize"] = at.SampleSize
|
||||||
|
// metaData["stereo"] = at.Channels == 2
|
||||||
|
// }
|
||||||
|
// if hasVideo {
|
||||||
|
flags |= 1
|
||||||
|
// metaData["videocodecid"] = int(vt.CodecID)
|
||||||
|
// metaData["width"] = vt.SPSInfo.Width
|
||||||
|
// metaData["height"] = vt.SPSInfo.Height
|
||||||
|
// }
|
||||||
|
// amf.Marshal(metaData)
|
||||||
|
// 写入FLV头
|
||||||
|
w.Write([]byte{'F', 'L', 'V', 0x01, flags, 0, 0, 0, 9, 0, 0, 0, 0})
|
||||||
|
// codec.WriteFLVTag(w, codec.FLV_TAG_TYPE_SCRIPT, 0, amf.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HDLPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
streamPath := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".flv")
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
streamPath += "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, err := p.Subscribe(streamPath, w, r.Context())
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "video/x-flv")
|
||||||
|
w.Header().Set("Transfer-Encoding", "identity")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
wto := p.GetCommonConf().WriteTimeout
|
||||||
|
var gotFlvTag func(tag *net.Buffers) error
|
||||||
|
if hijacker, ok := w.(http.Hijacker); ok && wto > 0 {
|
||||||
|
conn, _, _ := hijacker.Hijack()
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(wto))
|
||||||
|
sub.Closer = conn
|
||||||
|
p.WriteFlvHeader(sub, conn)
|
||||||
|
gotFlvTag = func(tag *net.Buffers) (err error) {
|
||||||
|
conn.SetWriteDeadline(time.Now().Add(wto))
|
||||||
|
_, err = tag.WriteTo(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
p.WriteFlvHeader(sub, w)
|
||||||
|
gotFlvTag = func(tag *net.Buffers) (err error) {
|
||||||
|
_, err = tag.WriteTo(w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b := util.Buffer(make([]byte, 0, 15))
|
||||||
|
var flv net.Buffers
|
||||||
|
sub.Handle(func(audio *rtmp.RTMPAudio) error {
|
||||||
|
b.Reset()
|
||||||
|
b.WriteByte(FLV_TAG_TYPE_AUDIO)
|
||||||
|
dataSize := audio.Length
|
||||||
|
b.WriteUint24(uint32(dataSize))
|
||||||
|
b.WriteUint24(audio.Timestamp)
|
||||||
|
b.WriteByte(byte(audio.Timestamp >> 24))
|
||||||
|
b.WriteUint24(0)
|
||||||
|
flv = append(append(append(flv, b), audio.Buffers.Buffers...), util.PutBE(b.Malloc(4), dataSize+11))
|
||||||
|
return gotFlvTag(&flv)
|
||||||
|
}, func(video *rtmp.RTMPVideo) error {
|
||||||
|
b.Reset()
|
||||||
|
b.WriteByte(FLV_TAG_TYPE_VIDEO)
|
||||||
|
dataSize := video.Length
|
||||||
|
b.WriteUint24(uint32(dataSize))
|
||||||
|
b.WriteUint24(video.Timestamp)
|
||||||
|
b.WriteByte(byte(video.Timestamp >> 24))
|
||||||
|
b.WriteUint24(0)
|
||||||
|
flv = append(append(append(flv, b), video.Buffers.Buffers...), util.PutBE(b.Malloc(4), dataSize+11))
|
||||||
|
return gotFlvTag(&flv)
|
||||||
|
})
|
||||||
|
}
|
8
plugin/hdl/pkg/flv.go
Normal file
8
plugin/hdl/pkg/flv.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package hdl
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FLV Tag Type
|
||||||
|
FLV_TAG_TYPE_AUDIO = 0x08
|
||||||
|
FLV_TAG_TYPE_VIDEO = 0x09
|
||||||
|
FLV_TAG_TYPE_SCRIPT = 0x12
|
||||||
|
)
|
@@ -1,4 +1,4 @@
|
|||||||
package rtmp
|
package plugin_rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -146,8 +146,7 @@ func (p *RTMPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
|||||||
err = receiver.Response(cmd.TransactionId, NetStream_Publish_BadName, Level_Error)
|
err = receiver.Response(cmd.TransactionId, NetStream_Publish_BadName, Level_Error)
|
||||||
} else {
|
} else {
|
||||||
receivers[cmd.StreamId] = receiver
|
receivers[cmd.StreamId] = receiver
|
||||||
receiver.Begin()
|
err = receiver.BeginPublish(cmd.TransactionId)
|
||||||
err = receiver.Response(cmd.TransactionId, NetStream_Publish_Start, Level_Status)
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("sendMessage publish", "error", err)
|
logger.Error("sendMessage publish", "error", err)
|
||||||
@@ -164,9 +163,7 @@ func (p *RTMPPlugin) OnTCPConnect(conn *net.TCPConn) {
|
|||||||
err = sender.Response(cmd.TransactionId, NetStream_Play_Failed, Level_Error)
|
err = sender.Response(cmd.TransactionId, NetStream_Play_Failed, Level_Error)
|
||||||
} else {
|
} else {
|
||||||
senders[sender.StreamID] = sender
|
senders[sender.StreamID] = sender
|
||||||
sender.Begin()
|
sender.BeginPlay(cmd.TransactionId)
|
||||||
err = sender.Response(cmd.TransactionId, NetStream_Play_Reset, Level_Status)
|
|
||||||
err = sender.Response(cmd.TransactionId, NetStream_Play_Start, Level_Status)
|
|
||||||
sender.Init()
|
sender.Init()
|
||||||
go sender.Handle(sender.SendAudio, sender.SendVideo)
|
go sender.Handle(sender.SendAudio, sender.SendVideo)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "m7s.live/m7s/v5/pkg"
|
. "m7s.live/m7s/v5/pkg"
|
||||||
@@ -45,7 +45,8 @@ func (avcc *RTMPAudio) DecodeConfig(track *AVTrack) error {
|
|||||||
ctx.FrameLengthFlag = (b1 >> 2) & 0x01
|
ctx.FrameLengthFlag = (b1 >> 2) & 0x01
|
||||||
ctx.DependsOnCoreCoder = (b1 >> 1) & 0x01
|
ctx.DependsOnCoreCoder = (b1 >> 1) & 0x01
|
||||||
ctx.ExtensionFlag = b1 & 0x01
|
ctx.ExtensionFlag = b1 & 0x01
|
||||||
ctx.SequenceFrame = avcc
|
ctx.SequenceFrame = &RTMPAudio{}
|
||||||
|
ctx.SequenceFrame.ReadFromBytes(avcc.ToBytes())
|
||||||
track.ICodecCtx = &ctx
|
track.ICodecCtx = &ctx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"m7s.live/m7s/v5/pkg/util"
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
@@ -24,6 +25,10 @@ type RTMPData struct {
|
|||||||
util.RecyclableMemory
|
util.RecyclableMemory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (avcc *RTMPData) Print() string {
|
||||||
|
return fmt.Sprintf("% 02X", avcc.Buffers.Buffers[0][:5])
|
||||||
|
}
|
||||||
|
|
||||||
func (avcc *RTMPData) GetTimestamp() time.Duration {
|
func (avcc *RTMPData) GetTimestamp() time.Duration {
|
||||||
return time.Duration(avcc.Timestamp) * time.Millisecond
|
return time.Duration(avcc.Timestamp) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
// http://help.adobe.com/zh_CN/AIR/1.5/jslr/flash/events/NetStatusEvent.html
|
// http://help.adobe.com/zh_CN/AIR/1.5/jslr/flash/events/NetStatusEvent.html
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@@ -55,6 +55,7 @@ func (av *AVSender) sendFrame(frame *RTMPData) (err error) {
|
|||||||
for r.Length > 0 {
|
for r.Length > 0 {
|
||||||
item := util.Buffer(av.byte16Pool.GetN(16))
|
item := util.Buffer(av.byte16Pool.GetN(16))
|
||||||
defer av.byte16Pool.Put(item)
|
defer av.byte16Pool.Put(item)
|
||||||
|
// item := util.Buffer(make([]byte, 16))
|
||||||
av.WriteTo(RTMP_CHUNK_HEAD_1, &item)
|
av.WriteTo(RTMP_CHUNK_HEAD_1, &item)
|
||||||
// 如果在音视频数据太大,一次发送不完,那么这里进行分割(data + Chunk Basic Header(1))
|
// 如果在音视频数据太大,一次发送不完,那么这里进行分割(data + Chunk Basic Header(1))
|
||||||
chunk = append(chunk, item)
|
chunk = append(chunk, item)
|
||||||
@@ -114,33 +115,7 @@ func (r *RTMPSender) SendVideo(video *RTMPVideo) error {
|
|||||||
return r.video.sendFrame(&video.RTMPData)
|
return r.video.sendFrame(&video.RTMPData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RTMPSender) Response(tid uint64, code, level string) error {
|
|
||||||
m := new(ResponsePlayMessage)
|
|
||||||
m.CommandName = Response_OnStatus
|
|
||||||
m.TransactionId = tid
|
|
||||||
m.Infomation = map[string]any{
|
|
||||||
"code": code,
|
|
||||||
"level": level,
|
|
||||||
"description": "",
|
|
||||||
}
|
|
||||||
m.StreamID = r.StreamID
|
|
||||||
return r.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
type RTMPReceiver struct {
|
type RTMPReceiver struct {
|
||||||
*m7s.Publisher
|
*m7s.Publisher
|
||||||
NetStream
|
NetStream
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RTMPReceiver) Response(tid uint64, code, level string) error {
|
|
||||||
m := new(ResponsePublishMessage)
|
|
||||||
m.CommandName = Response_OnStatus
|
|
||||||
m.TransactionId = tid
|
|
||||||
m.Infomation = map[string]any{
|
|
||||||
"code": code,
|
|
||||||
"level": level,
|
|
||||||
"description": "",
|
|
||||||
}
|
|
||||||
m.StreamID = r.StreamID
|
|
||||||
return r.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
|
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"m7s.live/m7s/v5/pkg/util"
|
"m7s.live/m7s/v5/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -45,30 +45,6 @@ const (
|
|||||||
SEND_FULL_VDIEO_MESSAGE = "Send Full Video Message"
|
SEND_FULL_VDIEO_MESSAGE = "Send Full Video Message"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BytesPool struct {
|
|
||||||
util.Pool[[]byte]
|
|
||||||
ItemSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *BytesPool) GetN(size int) []byte {
|
|
||||||
if size != bp.ItemSize {
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
ret := bp.Pool.Get()
|
|
||||||
if ret == nil {
|
|
||||||
return make([]byte, size)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *BytesPool) Put(b []byte) {
|
|
||||||
if cap(b) != bp.ItemSize {
|
|
||||||
bp.ItemSize = cap(b)
|
|
||||||
bp.Clear()
|
|
||||||
}
|
|
||||||
bp.Pool.Put(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetConnection struct {
|
type NetConnection struct {
|
||||||
*slog.Logger `json:"-" yaml:"-"`
|
*slog.Logger `json:"-" yaml:"-"`
|
||||||
*bufio.Reader `json:"-" yaml:"-"`
|
*bufio.Reader `json:"-" yaml:"-"`
|
||||||
@@ -85,8 +61,8 @@ type NetConnection struct {
|
|||||||
AppName string
|
AppName string
|
||||||
tmpBuf util.Buffer //用来接收/发送小数据,复用内存
|
tmpBuf util.Buffer //用来接收/发送小数据,复用内存
|
||||||
chunkHeader util.Buffer
|
chunkHeader util.Buffer
|
||||||
byteChunkPool BytesPool
|
byteChunkPool util.BytesPool
|
||||||
byte16Pool BytesPool
|
byte16Pool util.BytesPool
|
||||||
writing atomic.Bool // false 可写,true 不可写
|
writing atomic.Bool // false 可写,true 不可写
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +150,7 @@ func (conn *NetConnection) readChunk() (msg *Chunk, err error) {
|
|||||||
if unRead := msgLen - chunk.AVData.Length; unRead < needRead {
|
if unRead := msgLen - chunk.AVData.Length; unRead < needRead {
|
||||||
needRead = unRead
|
needRead = unRead
|
||||||
}
|
}
|
||||||
|
// mem := make([]byte, needRead)
|
||||||
mem := conn.byteChunkPool.GetN(needRead)
|
mem := conn.byteChunkPool.GetN(needRead)
|
||||||
if n, err := conn.ReadFull(mem); err != nil {
|
if n, err := conn.ReadFull(mem); err != nil {
|
||||||
conn.byteChunkPool.Put(mem)
|
conn.byteChunkPool.Put(mem)
|
||||||
|
@@ -1,10 +1,40 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
type NetStream struct {
|
type NetStream struct {
|
||||||
*NetConnection
|
*NetConnection
|
||||||
StreamID uint32
|
StreamID uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *NetStream) Begin() {
|
func (ns *NetStream) Response(tid uint64, code, level string) error {
|
||||||
ns.SendStreamID(RTMP_USER_STREAM_BEGIN, ns.StreamID)
|
m := new(ResponsePlayMessage)
|
||||||
|
m.CommandName = Response_OnStatus
|
||||||
|
m.TransactionId = tid
|
||||||
|
m.Infomation = map[string]any{
|
||||||
|
"code": code,
|
||||||
|
"level": level,
|
||||||
|
"description": "",
|
||||||
|
}
|
||||||
|
m.StreamID = ns.StreamID
|
||||||
|
return ns.SendMessage(RTMP_MSG_AMF0_COMMAND, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NetStream) BeginPublish(tid uint64) error {
|
||||||
|
err := ns.SendStreamID(RTMP_USER_STREAM_BEGIN, ns.StreamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ns.Response(tid, NetStream_Publish_Start, Level_Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *NetStream) BeginPlay(tid uint64) (err error) {
|
||||||
|
err = ns.SendStreamID(RTMP_USER_STREAM_BEGIN, ns.StreamID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ns.Response(tid, NetStream_Play_Reset, Level_Status)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = ns.Response(tid, NetStream_Play_Start, Level_Status)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package pkg
|
package rtmp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -36,8 +36,8 @@ func (avcc *RTMPVideo) DecodeConfig(track *AVTrack) error {
|
|||||||
ctx.NalulenSize = int(info.LengthSizeMinusOne&3 + 1)
|
ctx.NalulenSize = int(info.LengthSizeMinusOne&3 + 1)
|
||||||
ctx.SPS = info.SequenceParameterSetNALUnit
|
ctx.SPS = info.SequenceParameterSetNALUnit
|
||||||
ctx.PPS = info.PictureParameterSetNALUnit
|
ctx.PPS = info.PictureParameterSetNALUnit
|
||||||
avcc.IPool = nil
|
ctx.SequenceFrame = &RTMPVideo{}
|
||||||
ctx.SequenceFrame = avcc
|
ctx.SequenceFrame.ReadFromBytes(avcc.ToBytes())
|
||||||
track.ICodecCtx = &ctx
|
track.ICodecCtx = &ctx
|
||||||
}
|
}
|
||||||
case "h265":
|
case "h265":
|
||||||
|
@@ -93,6 +93,9 @@ func (p *Publisher) writeAV(t *AVTrack, data IAVFrame) {
|
|||||||
t.Value.Wrap = data
|
t.Value.Wrap = data
|
||||||
t.Value.Timestamp = data.GetTimestamp()
|
t.Value.Timestamp = data.GetTimestamp()
|
||||||
t.Step()
|
t.Step()
|
||||||
|
if t.Value.Wrap != nil {
|
||||||
|
t.Value.Wrap.Recycle()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Publisher) WriteVideo(data IAVFrame) (err error) {
|
func (p *Publisher) WriteVideo(data IAVFrame) (err error) {
|
||||||
@@ -122,7 +125,7 @@ func (p *Publisher) WriteVideo(data IAVFrame) (err error) {
|
|||||||
p.GOP = int(t.Value.Sequence - t.IDRing.Value.Sequence)
|
p.GOP = int(t.Value.Sequence - t.IDRing.Value.Sequence)
|
||||||
if t.HistoryRing == nil {
|
if t.HistoryRing == nil {
|
||||||
if l := t.Size - p.GOP; l > 12 {
|
if l := t.Size - p.GOP; l > 12 {
|
||||||
t.Debug("resize", "before", t.Size, "after", t.Size-5)
|
t.Debug("resize", "gop", p.GOP, "before", t.Size, "after", t.Size-5)
|
||||||
t.Reduce(5) //缩小缓冲环节省内存
|
t.Reduce(5) //缩小缓冲环节省内存
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
server.go
18
server.go
@@ -3,6 +3,7 @@ package m7s
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -58,6 +59,7 @@ func (s *Server) Run(ctx context.Context, conf any) (err error) {
|
|||||||
s.Context, s.CancelCauseFunc = context.WithCancelCause(ctx)
|
s.Context, s.CancelCauseFunc = context.WithCancelCause(ctx)
|
||||||
s.config.HTTP.ListenAddrTLS = ":8443"
|
s.config.HTTP.ListenAddrTLS = ":8443"
|
||||||
s.config.HTTP.ListenAddr = ":8080"
|
s.config.HTTP.ListenAddr = ":8080"
|
||||||
|
s.handler = s
|
||||||
s.Info("start")
|
s.Info("start")
|
||||||
|
|
||||||
var cg map[string]map[string]any
|
var cg map[string]map[string]any
|
||||||
@@ -90,6 +92,19 @@ func (s *Server) Run(ctx context.Context, conf any) (err error) {
|
|||||||
var lv slog.LevelVar
|
var lv slog.LevelVar
|
||||||
lv.UnmarshalText([]byte(s.LogLevel))
|
lv.UnmarshalText([]byte(s.LogLevel))
|
||||||
slog.SetLogLoggerLevel(lv.Level())
|
slog.SetLogLoggerLevel(lv.Level())
|
||||||
|
s.registerHandler()
|
||||||
|
if s.config.HTTP.ListenAddrTLS != "" {
|
||||||
|
s.Info("https listen at ", "addr", s.config.HTTP.ListenAddrTLS)
|
||||||
|
go func() {
|
||||||
|
s.Stop(s.config.HTTP.ListenTLS())
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if s.config.HTTP.ListenAddr != "" {
|
||||||
|
s.Info("http listen at ", "addr", s.config.HTTP.ListenAddr)
|
||||||
|
go func() {
|
||||||
|
s.Stop(s.config.HTTP.Listen())
|
||||||
|
}()
|
||||||
|
}
|
||||||
for _, plugin := range plugins {
|
for _, plugin := range plugins {
|
||||||
plugin.Init(s, cg[strings.ToLower(plugin.Name)])
|
plugin.Init(s, cg[strings.ToLower(plugin.Name)])
|
||||||
}
|
}
|
||||||
@@ -241,3 +256,6 @@ func (s *Server) OnSubscribe(subscriber *Subscriber) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
||||||
|
@@ -71,10 +71,10 @@ func (s *Subscriber) Handle(audioHandler, videoHandler any) {
|
|||||||
subMode, _ = strconv.Atoi(s.Args.Get(s.SubModeArgName))
|
subMode, _ = strconv.Atoi(s.Args.Get(s.SubModeArgName))
|
||||||
}
|
}
|
||||||
var audioFrame, videoFrame, lastSentAF, lastSentVF *AVFrame
|
var audioFrame, videoFrame, lastSentAF, lastSentVF *AVFrame
|
||||||
if audioHandler != nil {
|
if audioHandler != nil && s.SubAudio {
|
||||||
a1 = reflect.TypeOf(audioHandler).In(0)
|
a1 = reflect.TypeOf(audioHandler).In(0)
|
||||||
}
|
}
|
||||||
if videoHandler != nil {
|
if videoHandler != nil && s.SubVideo {
|
||||||
v1 = reflect.TypeOf(videoHandler).In(0)
|
v1 = reflect.TypeOf(videoHandler).In(0)
|
||||||
}
|
}
|
||||||
createAudioReader := func() {
|
createAudioReader := func() {
|
||||||
@@ -111,13 +111,19 @@ func (s *Subscriber) Handle(audioHandler, videoHandler any) {
|
|||||||
}()
|
}()
|
||||||
sendAudioFrame := func() {
|
sendAudioFrame := func() {
|
||||||
lastSentAF = audioFrame
|
lastSentAF = audioFrame
|
||||||
s.Debug("send audio frame", "frame", audioFrame.Sequence)
|
s.Debug("send audio frame", "seq", audioFrame.Sequence)
|
||||||
ah.Call([]reflect.Value{reflect.ValueOf(audioFrame.Wrap)})
|
res := ah.Call([]reflect.Value{reflect.ValueOf(audioFrame.Wrap)})
|
||||||
|
if len(res) > 0 && !res[0].IsNil() {
|
||||||
|
s.Stop(res[0].Interface().(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sendVideoFrame := func() {
|
sendVideoFrame := func() {
|
||||||
lastSentVF = videoFrame
|
lastSentVF = videoFrame
|
||||||
s.Debug("send video frame", "frame", videoFrame.Sequence)
|
s.Debug("send video frame", "seq", videoFrame.Sequence, "data", videoFrame.Wrap.Print())
|
||||||
vh.Call([]reflect.Value{reflect.ValueOf(videoFrame.Wrap)})
|
res := vh.Call([]reflect.Value{reflect.ValueOf(videoFrame.Wrap)})
|
||||||
|
if len(res) > 0 && !res[0].IsNil() {
|
||||||
|
s.Stop(res[0].Interface().(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for err := s.Err(); err == nil; err = s.Err() {
|
for err := s.Err(); err == nil; err = s.Err() {
|
||||||
if vr != nil {
|
if vr != nil {
|
||||||
@@ -135,7 +141,7 @@ func (s *Subscriber) Handle(audioHandler, videoHandler any) {
|
|||||||
// fmt.Println("video", s.VideoReader.Track.PreFrame().Sequence-frame.Sequence)
|
// fmt.Println("video", s.VideoReader.Track.PreFrame().Sequence-frame.Sequence)
|
||||||
if videoFrame.Wrap.IsIDR() && vr.DecConfChanged() {
|
if videoFrame.Wrap.IsIDR() && vr.DecConfChanged() {
|
||||||
vr.LastCodecCtx = vr.Track.ICodecCtx
|
vr.LastCodecCtx = vr.Track.ICodecCtx
|
||||||
s.Debug("video codec changed")
|
s.Debug("video codec changed", "data", vr.Track.ICodecCtx.GetSequenceFrame().Print())
|
||||||
vh.Call([]reflect.Value{reflect.ValueOf(vr.Track.ICodecCtx.GetSequenceFrame())})
|
vh.Call([]reflect.Value{reflect.ValueOf(vr.Track.ICodecCtx.GetSequenceFrame())})
|
||||||
}
|
}
|
||||||
if ar != nil {
|
if ar != nil {
|
||||||
|
Reference in New Issue
Block a user