main.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package fmp4
  2. import (
  3. "io"
  4. "net/http"
  5. "strings"
  6. "github.com/Eyevinn/mp4ff/aac"
  7. "github.com/Eyevinn/mp4ff/mp4"
  8. . "m7s.live/engine/v4"
  9. "m7s.live/engine/v4/codec"
  10. "m7s.live/engine/v4/config"
  11. "m7s.live/engine/v4/track"
  12. )
  13. type Fmp4Config struct {
  14. config.HTTP
  15. config.Subscribe
  16. }
  17. type MediaContext interface {
  18. io.Writer
  19. GetSeqNumber() uint32
  20. }
  21. type TrackContext struct {
  22. TrackId uint32
  23. fragment *mp4.Fragment
  24. ts uint32 // 每个小片段起始时间戳
  25. abs uint32 // 绝对起始时间戳
  26. absSet bool // 是否设置过abs
  27. }
  28. func (m *TrackContext) Push(ctx MediaContext, dt uint32, dur uint32, data []byte, flags uint32) {
  29. if !m.absSet {
  30. m.abs = dt
  31. m.absSet = true
  32. }
  33. dt -= m.abs
  34. if m.fragment != nil && dt-m.ts > 1000 {
  35. m.fragment.Encode(ctx)
  36. m.fragment = nil
  37. }
  38. if m.fragment == nil {
  39. m.fragment, _ = mp4.CreateFragment(ctx.GetSeqNumber(), m.TrackId)
  40. m.ts = dt
  41. }
  42. m.fragment.AddFullSample(mp4.FullSample{
  43. Data: data,
  44. DecodeTime: uint64(dt),
  45. Sample: mp4.Sample{
  46. Flags: flags,
  47. Dur: dur,
  48. Size: uint32(len(data)),
  49. },
  50. })
  51. }
  52. func (c *Fmp4Config) OnEvent(event any) {
  53. switch event.(type) {
  54. case FirstConfig:
  55. }
  56. }
  57. var Fmp4Plugin = InstallPlugin(new(Fmp4Config))
  58. type Fmp4Subscriber struct {
  59. Subscriber
  60. initSegment *mp4.InitSegment `json:"-" yaml:"-"`
  61. ftyp *mp4.FtypBox
  62. video TrackContext
  63. audio TrackContext
  64. seqNumber uint32
  65. avccOffset int // mp4 写入的 avcc 的偏移量即,从第几个字节开始写入,前面是头,仅供 rtmp 协议使用
  66. }
  67. func (sub *Fmp4Subscriber) GetSeqNumber() uint32 {
  68. sub.seqNumber++
  69. return sub.seqNumber
  70. }
  71. func (sub *Fmp4Subscriber) OnEvent(event any) {
  72. switch v := event.(type) {
  73. case ISubscriber:
  74. sub.ftyp.Encode(sub)
  75. sub.initSegment.Moov.Encode(sub)
  76. case *track.Video:
  77. moov := sub.initSegment.Moov
  78. trackID := moov.Mvhd.NextTrackID
  79. moov.Mvhd.NextTrackID++
  80. newTrak := mp4.CreateEmptyTrak(trackID, 1000, "video", "chi")
  81. moov.AddChild(newTrak)
  82. moov.Mvex.AddChild(mp4.CreateTrex(trackID))
  83. sub.video.TrackId = trackID
  84. switch v.CodecID {
  85. case codec.CodecID_H264:
  86. sub.avccOffset = 5
  87. sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
  88. "isom", "iso2", "avc1", "mp41",
  89. })
  90. newTrak.SetAVCDescriptor("avc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], true)
  91. case codec.CodecID_H265:
  92. sub.avccOffset = 8
  93. sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
  94. "isom", "iso2", "hvc1", "mp41",
  95. })
  96. newTrak.SetHEVCDescriptor("hvc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], v.ParamaterSets[2:3], nil, true)
  97. case codec.CodecID_AV1:
  98. sub.avccOffset = 5
  99. sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
  100. "isom", "iso2", "av01", "mp41",
  101. })
  102. }
  103. sub.AddTrack(v)
  104. case *track.Audio:
  105. moov := sub.initSegment.Moov
  106. trackID := moov.Mvhd.NextTrackID
  107. moov.Mvhd.NextTrackID++
  108. newTrak := mp4.CreateEmptyTrak(trackID, 1000, "audio", "chi")
  109. moov.AddChild(newTrak)
  110. moov.Mvex.AddChild(mp4.CreateTrex(trackID))
  111. sub.audio.TrackId = trackID
  112. switch v.CodecID {
  113. case codec.CodecID_AAC:
  114. switch v.AudioObjectType {
  115. case 1:
  116. newTrak.SetAACDescriptor(aac.HEAACv1, int(v.SampleRate))
  117. case 2:
  118. newTrak.SetAACDescriptor(aac.AAClc, int(v.SampleRate))
  119. case 3:
  120. newTrak.SetAACDescriptor(aac.HEAACv2, int(v.SampleRate))
  121. }
  122. case codec.CodecID_PCMA:
  123. stsd := newTrak.Mdia.Minf.Stbl.Stsd
  124. pcma := mp4.CreateAudioSampleEntryBox("pcma",
  125. uint16(v.Channels),
  126. uint16(v.SampleSize), uint16(v.SampleRate), nil)
  127. stsd.AddChild(pcma)
  128. case codec.CodecID_PCMU:
  129. stsd := newTrak.Mdia.Minf.Stbl.Stsd
  130. pcmu := mp4.CreateAudioSampleEntryBox("pcmu",
  131. uint16(v.Channels),
  132. uint16(v.SampleSize), uint16(v.SampleRate), nil)
  133. stsd.AddChild(pcmu)
  134. }
  135. sub.AddTrack(v)
  136. case AudioFrame:
  137. sub.audio.Push(sub, v.AbsTime, v.DeltaTime, v.AUList.ToBytes(), mp4.SyncSampleFlags)
  138. case VideoFrame:
  139. flags := mp4.NonSyncSampleFlags
  140. if v.IFrame {
  141. flags = mp4.SyncSampleFlags
  142. }
  143. if data := v.AVCC.ToBytes(); len(data) > sub.avccOffset {
  144. sub.video.Push(sub, v.AbsTime, v.DeltaTime, data[sub.avccOffset:], flags)
  145. }
  146. default:
  147. sub.Subscriber.OnEvent(event)
  148. }
  149. }
  150. func (*Fmp4Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  151. streamPath := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".mp4")
  152. if r.URL.RawQuery != "" {
  153. streamPath += "?" + r.URL.RawQuery
  154. }
  155. w.Header().Set("Transfer-Encoding", "chunked")
  156. w.Header().Set("Content-Type", "video/mp4")
  157. sub := &Fmp4Subscriber{
  158. initSegment: mp4.CreateEmptyInit(),
  159. }
  160. sub.initSegment.Moov.Mvhd.NextTrackID = 1
  161. sub.ID = r.RemoteAddr
  162. sub.SetIO(w)
  163. sub.SetParentCtx(r.Context())
  164. if err := Fmp4Plugin.SubscribeBlock(streamPath, sub, SUBTYPE_RAW); err != nil {
  165. http.Error(w, err.Error(), http.StatusBadRequest)
  166. }
  167. }