123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package fmp4
- import (
- "io"
- "net/http"
- "strings"
- "github.com/Eyevinn/mp4ff/aac"
- "github.com/Eyevinn/mp4ff/mp4"
- . "m7s.live/engine/v4"
- "m7s.live/engine/v4/codec"
- "m7s.live/engine/v4/config"
- "m7s.live/engine/v4/track"
- )
- type Fmp4Config struct {
- config.HTTP
- config.Subscribe
- }
- type MediaContext interface {
- io.Writer
- GetSeqNumber() uint32
- }
- type TrackContext struct {
- TrackId uint32
- fragment *mp4.Fragment
- ts uint32 // 每个小片段起始时间戳
- abs uint32 // 绝对起始时间戳
- absSet bool // 是否设置过abs
- }
- func (m *TrackContext) Push(ctx MediaContext, dt uint32, dur uint32, data []byte, flags uint32) {
- if !m.absSet {
- m.abs = dt
- m.absSet = true
- }
- dt -= m.abs
- if m.fragment != nil && dt-m.ts > 1000 {
- m.fragment.Encode(ctx)
- m.fragment = nil
- }
- if m.fragment == nil {
- m.fragment, _ = mp4.CreateFragment(ctx.GetSeqNumber(), m.TrackId)
- m.ts = dt
- }
- m.fragment.AddFullSample(mp4.FullSample{
- Data: data,
- DecodeTime: uint64(dt),
- Sample: mp4.Sample{
- Flags: flags,
- Dur: dur,
- Size: uint32(len(data)),
- },
- })
- }
- func (c *Fmp4Config) OnEvent(event any) {
- switch event.(type) {
- case FirstConfig:
- }
- }
- var Fmp4Plugin = InstallPlugin(new(Fmp4Config))
- type Fmp4Subscriber struct {
- Subscriber
- initSegment *mp4.InitSegment `json:"-" yaml:"-"`
- ftyp *mp4.FtypBox
- video TrackContext
- audio TrackContext
- seqNumber uint32
- avccOffset int // mp4 写入的 avcc 的偏移量即,从第几个字节开始写入,前面是头,仅供 rtmp 协议使用
- }
- func (sub *Fmp4Subscriber) GetSeqNumber() uint32 {
- sub.seqNumber++
- return sub.seqNumber
- }
- func (sub *Fmp4Subscriber) OnEvent(event any) {
- switch v := event.(type) {
- case ISubscriber:
- sub.ftyp.Encode(sub)
- sub.initSegment.Moov.Encode(sub)
- case *track.Video:
- moov := sub.initSegment.Moov
- trackID := moov.Mvhd.NextTrackID
- moov.Mvhd.NextTrackID++
- newTrak := mp4.CreateEmptyTrak(trackID, 1000, "video", "chi")
- moov.AddChild(newTrak)
- moov.Mvex.AddChild(mp4.CreateTrex(trackID))
- sub.video.TrackId = trackID
- switch v.CodecID {
- case codec.CodecID_H264:
- sub.avccOffset = 5
- sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
- "isom", "iso2", "avc1", "mp41",
- })
- newTrak.SetAVCDescriptor("avc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], true)
- case codec.CodecID_H265:
- sub.avccOffset = 8
- sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
- "isom", "iso2", "hvc1", "mp41",
- })
- newTrak.SetHEVCDescriptor("hvc1", v.ParamaterSets[0:1], v.ParamaterSets[1:2], v.ParamaterSets[2:3], nil, true)
- case codec.CodecID_AV1:
- sub.avccOffset = 5
- sub.ftyp = mp4.NewFtyp("isom", 0x200, []string{
- "isom", "iso2", "av01", "mp41",
- })
- }
- sub.AddTrack(v)
- case *track.Audio:
- moov := sub.initSegment.Moov
- trackID := moov.Mvhd.NextTrackID
- moov.Mvhd.NextTrackID++
- newTrak := mp4.CreateEmptyTrak(trackID, 1000, "audio", "chi")
- moov.AddChild(newTrak)
- moov.Mvex.AddChild(mp4.CreateTrex(trackID))
- sub.audio.TrackId = trackID
- switch v.CodecID {
- case codec.CodecID_AAC:
- switch v.AudioObjectType {
- case 1:
- newTrak.SetAACDescriptor(aac.HEAACv1, int(v.SampleRate))
- case 2:
- newTrak.SetAACDescriptor(aac.AAClc, int(v.SampleRate))
- case 3:
- newTrak.SetAACDescriptor(aac.HEAACv2, int(v.SampleRate))
- }
- case codec.CodecID_PCMA:
- stsd := newTrak.Mdia.Minf.Stbl.Stsd
- pcma := mp4.CreateAudioSampleEntryBox("pcma",
- uint16(v.Channels),
- uint16(v.SampleSize), uint16(v.SampleRate), nil)
- stsd.AddChild(pcma)
- case codec.CodecID_PCMU:
- stsd := newTrak.Mdia.Minf.Stbl.Stsd
- pcmu := mp4.CreateAudioSampleEntryBox("pcmu",
- uint16(v.Channels),
- uint16(v.SampleSize), uint16(v.SampleRate), nil)
- stsd.AddChild(pcmu)
- }
- sub.AddTrack(v)
- case AudioFrame:
- sub.audio.Push(sub, v.AbsTime, v.DeltaTime, v.AUList.ToBytes(), mp4.SyncSampleFlags)
- case VideoFrame:
- flags := mp4.NonSyncSampleFlags
- if v.IFrame {
- flags = mp4.SyncSampleFlags
- }
- if data := v.AVCC.ToBytes(); len(data) > sub.avccOffset {
- sub.video.Push(sub, v.AbsTime, v.DeltaTime, data[sub.avccOffset:], flags)
- }
- default:
- sub.Subscriber.OnEvent(event)
- }
- }
- func (*Fmp4Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- streamPath := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/"), ".mp4")
- if r.URL.RawQuery != "" {
- streamPath += "?" + r.URL.RawQuery
- }
- w.Header().Set("Transfer-Encoding", "chunked")
- w.Header().Set("Content-Type", "video/mp4")
- sub := &Fmp4Subscriber{
- initSegment: mp4.CreateEmptyInit(),
- }
- sub.initSegment.Moov.Mvhd.NextTrackID = 1
- sub.ID = r.RemoteAddr
- sub.SetIO(w)
- sub.SetParentCtx(r.Context())
- if err := Fmp4Plugin.SubscribeBlock(streamPath, sub, SUBTYPE_RAW); err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- }
- }
|