main.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package record
  2. import (
  3. _ "embed"
  4. "errors"
  5. "crypto/md5"
  6. "encoding/hex"
  7. "io"
  8. "sync"
  9. "time"
  10. "fmt"
  11. "strconv"
  12. "strings"
  13. "go.uber.org/zap"
  14. "gorm.io/gorm"
  15. . "m7s.live/engine/v4"
  16. "m7s.live/engine/v4/codec"
  17. "m7s.live/engine/v4/config"
  18. "m7s.live/engine/v4/util"
  19. "m7s.live/engine/v4/db"
  20. )
  21. type RecordConfig struct {
  22. config.Subscribe
  23. Ffmpeg string `json:"ffmpeg"`
  24. Flv Record `desc:"flv录制配置"`
  25. Mp4 Record `desc:"mp4录制配置"`
  26. Fmp4 Record `desc:"fmp4录制配置"`
  27. Hls Record `desc:"hls录制配置"`
  28. Raw Record `desc:"视频裸流录制配置"`
  29. RawAudio Record `desc:"音频裸流录制配置"`
  30. recordings sync.Map
  31. }
  32. func getIsRecordByChannelId(channelId string) int {
  33. database := db.DB()
  34. isRecord := 0
  35. err := database.Raw("select is_record from t_sdp_delivery where channel_id=?",channelId).Find(&isRecord).Error
  36. if err != nil{
  37. if err == gorm.ErrRecordNotFound{
  38. return 0
  39. }
  40. return 1
  41. }
  42. return isRecord
  43. }
  44. //go:embed default.yaml
  45. var defaultYaml DefaultYaml
  46. var ErrRecordExist = errors.New("recorder exist")
  47. var RecordPluginConfig = &RecordConfig{
  48. Ffmpeg:"ffmpeg",
  49. Flv: Record{
  50. Path: "record/flv",
  51. Ext: ".flv",
  52. GetDurationFn: getFLVDuration,
  53. },
  54. Fmp4: Record{
  55. Path: "record/fmp4",
  56. Ext: ".mp4",
  57. },
  58. Mp4: Record{
  59. Path: "record/mp4",
  60. Ext: ".mp4",
  61. },
  62. Hls: Record{
  63. Path: "record/hls",
  64. Ext: ".m3u8",
  65. },
  66. Raw: Record{
  67. Path: "record/raw",
  68. Ext: ".", // 默认h264扩展名为.h264,h265扩展名为.h265
  69. },
  70. RawAudio: Record{
  71. Path: "record/raw",
  72. Ext: ".", // 默认aac扩展名为.aac,pcma扩展名为.pcma,pcmu扩展名为.pcmu
  73. },
  74. }
  75. var plugin = InstallPlugin(RecordPluginConfig, defaultYaml)
  76. func (conf *RecordConfig) OnEvent(event any) {
  77. switch v := event.(type) {
  78. case FirstConfig, config.Config:
  79. conf.Flv.Init()
  80. conf.Mp4.Init()
  81. conf.Fmp4.Init()
  82. conf.Hls.Init()
  83. conf.Raw.Init()
  84. conf.RawAudio.Init()
  85. case SEpublish:
  86. streamPath := v.Target.Path
  87. deviceId := v.Target.AppName
  88. if deviceId != "record"{
  89. // 获取的是录像,不录
  90. isRecord := getIsRecordByChannelId(strings.TrimSuffix(v.Target.StreamName, "/rtsp"))
  91. // 对steam path自动加上签名
  92. if EngineConfig.Subscribe.Key != ""{
  93. nowTime := time.Now().Unix()
  94. expireTime := nowTime + 315360000
  95. expireTime16 := strconv.FormatInt(expireTime, 16)
  96. m := md5.New()
  97. m.Write([]byte(EngineConfig.Subscribe.Key +streamPath + expireTime16))
  98. streamPathList := strings.Split(streamPath,"?")
  99. if len(streamPathList) > 0 {
  100. streamPath = fmt.Sprintf("%s?%s=%s&%s=%s",streamPathList[0],EngineConfig.Subscribe.SecretArgName,hex.EncodeToString(m.Sum(nil)),EngineConfig.Subscribe.ExpireArgName,expireTime16)
  101. }
  102. }
  103. plugin.Info("是否需要开启录像",zap.String("streamPath",streamPath),zap.Int("是否录像",isRecord))
  104. //fmt.Println("发布流:",streamPath," 是否需要录像:",isRecord)
  105. if isRecord == 1 {
  106. if conf.Flv.NeedRecord(streamPath) {
  107. go NewFLVRecorder().Start(streamPath)
  108. }
  109. //fmt.Println("是否需要mp4录像:",conf.Mp4.NeedRecord(streamPath))
  110. if conf.Mp4.NeedRecord(streamPath) {
  111. go NewMP4Recorder().Start(streamPath)
  112. }
  113. if conf.Fmp4.NeedRecord(streamPath) {
  114. go NewFMP4Recorder().Start(streamPath)
  115. }
  116. if conf.Hls.NeedRecord(streamPath) {
  117. go NewHLSRecorder().Start(streamPath)
  118. }
  119. if conf.Raw.NeedRecord(streamPath) {
  120. go NewRawRecorder().Start(streamPath)
  121. }
  122. if conf.RawAudio.NeedRecord(streamPath) {
  123. go NewRawAudioRecorder().Start(streamPath)
  124. }
  125. }
  126. }
  127. }
  128. }
  129. func (conf *RecordConfig) getRecorderConfigByType(t string) (recorder *Record) {
  130. switch t {
  131. case "flv":
  132. recorder = &conf.Flv
  133. case "mp4":
  134. recorder = &conf.Mp4
  135. case "fmp4":
  136. recorder = &conf.Fmp4
  137. case "hls":
  138. recorder = &conf.Hls
  139. case "raw":
  140. recorder = &conf.Raw
  141. case "raw_audio":
  142. recorder = &conf.RawAudio
  143. }
  144. return
  145. }
  146. func getFLVDuration(file io.ReadSeeker) uint32 {
  147. _, err := file.Seek(-4, io.SeekEnd)
  148. if err == nil {
  149. var tagSize uint32
  150. if tagSize, err = util.ReadByteToUint32(file, true); err == nil {
  151. _, err = file.Seek(-int64(tagSize)-4, io.SeekEnd)
  152. if err == nil {
  153. _, timestamp, _, err := codec.ReadFLVTag(file)
  154. if err == nil {
  155. return timestamp
  156. }
  157. }
  158. }
  159. }
  160. return 0
  161. }