yapingcat / gomedia

golang library for rtmp, mpeg-ts,mpeg-ps,flv,mp4,ogg,rtsp
MIT License
362 stars 63 forks source link

Flv To mp4 #95

Open hasnhasan opened 10 months ago

hasnhasan commented 10 months ago

Hello, The example in the file "example_covert_flv_to_mp4.go" creates and saves a new mp4 file.

I want to save it as a continuation of an existing mp4 file. Is this possible?

yapingcat commented 10 months ago

you should use fragment mp4.

please refer to this example flv to fmp4

hasnhasan commented 10 months ago

I tried this way but without success.

I'm just trying to do this. If the test2_fmp4.mp4 file already exists (For example, there is a 1 minute video recording.) it should append to it and continue recording.


import (
    "encoding/binary"
    "fmt"
    "net"
    "os"
    "time"

    "github.com/notedit/rtmp/av"
    "github.com/notedit/rtmp/format/rtmp"
    "github.com/yapingcat/gomedia/go-codec"
    "github.com/yapingcat/gomedia/go-mp4"
)

func startRtmp() {

    lis, err := net.Listen("tcp", ":1935")
    if err != nil {
        panic(err)
    }

    s := rtmp.NewServer()

    s.LogEvent = func(c *rtmp.Conn, nc net.Conn, e int) {
        es := rtmp.EventString[e]
        fmt.Println(nc.LocalAddr(), nc.RemoteAddr(), es)
    }

    s.HandleConn = func(c *rtmp.Conn, nc net.Conn) {

        fmt.Println(c.URL.Path)
        if c.Publishing {
            mp4filename := "test2_fmp4.mp4"
            mp4file, err := os.OpenFile(mp4filename, os.O_CREATE|os.O_RDWR, 0666)
            if err != nil {
                fmt.Println(err)
                return
            }
            defer mp4file.Close()

            i := 0
            muxer, err := mp4.CreateMp4Muxer(mp4file, mp4.WithMp4Flag(mp4.MP4_FLAG_DASH))
            muxer.OnNewFragment(func(duration uint32, firstPts, firstDts uint64) {
                fmt.Println("on segment", duration)
                if i == 0 {
                    initFile, _ := os.OpenFile("init.mp4", os.O_CREATE|os.O_RDWR, 0666)
                    muxer.WriteInitSegment(initFile)
                    initFile.Close()
                }
                i++
                muxer.ReBindWriter(mp4file)
            })

            if err != nil {
                fmt.Println(err)
                return
            }
            hasVideo := false
            hasAudio := false
            var vtid uint32
            var atid uint32
            defer muxer.WriteTrailer()
            var asc []byte
            for {
                nc.SetReadDeadline(time.Now().Add(time.Second))
                pkt, err := c.ReadPacket()
                if err != nil {
                    fmt.Println(err)
                    return
                }
                pts := pkt.CTime.Milliseconds() + pkt.Time.Milliseconds()
                dts := pkt.Time.Milliseconds()
                switch pkt.Type {
                case av.H264DecoderConfig:
                    if !hasVideo {
                        vtid = muxer.AddVideoTrack(mp4.MP4_CODEC_H264)
                        hasVideo = true
                    }
                    tmpspss, tmpppss := codec.CovertExtradata(pkt.Data)
                    //fmt.Println(len(tmpspss), len(tmpppss))
                    pts := pkt.CTime + pkt.Time
                    frame := make([]byte, 0, len(tmpspss[0])+len(tmpppss[0]))
                    frame = append(frame, tmpspss[0]...)
                    frame = append(frame, tmpppss[0]...)
                    //codec.ShowPacketHexdump(tmpspss[0])
                    //fmt.Println(codec.GetSPSIdWithStartCode(tmpspss[0]))
                    muxer.Write(vtid, frame, uint64(pts), uint64(dts))
                case av.H264:
                    for len(pkt.Data) > 0 {
                        naluSize := binary.BigEndian.Uint32(pkt.Data[:4])
                        //codec.ShowPacketHexdump(pkt.Data[:6])
                        codec.CovertAVCCToAnnexB(pkt.Data)
                        muxer.Write(vtid, pkt.Data[:4+naluSize], uint64(pts), uint64(dts))
                        pkt.Data = pkt.Data[4+naluSize:]
                    }
                    //fmt.Println("h264 frame pts ", pts, "dts ", dts)
                case av.AACDecoderConfig:
                    if !hasAudio {
                        atid = muxer.AddAudioTrack(mp4.MP4_CODEC_AAC)
                        hasAudio = true
                    }

                    asc = make([]byte, len(pkt.Data))
                    copy(asc, pkt.Data)
                case av.AAC:
                    adts, err := codec.ConvertASCToADTS(asc, len(pkt.Data)+7)
                    if err != nil {
                        return
                    }
                    //fmt.Println("aac frame pts ", pts, "dts ", dts)
                    adts_frame := append(adts.Encode(), pkt.Data...)
                    muxer.Write(atid, adts_frame, uint64(pts), uint64(dts))
                }
            }

        }
    }

    for {
        nc, err := lis.Accept()
        if err != nil {
            time.Sleep(time.Second)
            continue
        }
        go s.HandleNetConn(nc)
    }
}

func main() {

    startRtmp()
}
yapingcat commented 10 months ago

https://github.com/yapingcat/gomedia/issues/57#issuecomment-1471481250

please according to this issue.

  1. create fmp4 muxer with memeory io handler,fmp4 binary data will be stored into memory buffer
  2. save data into fmp4 file
  3. reset memeory io handler
hasnhasan commented 10 months ago

The problem here is that I do not record the incoming broadcast continuously. I record it piece by piece. Sometimes during recording, the broadcast from RTMP is interrupted and comes again. When it comes, I try to make it continue from the file recorded until that section.

yapingcat commented 10 months ago

The problem here is that I do not record the incoming broadcast continuously. I record it piece by piece. Sometimes during recording, the broadcast from RTMP is interrupted and comes again. When it comes, I try to make it continue from the file recorded until that section.

fmp4 satisfy your Scenario . you can append media data to the end of the fmp4 file,just like flv file

cedricve commented 7 months ago

I have the same "request". Currently when making very long recordings, as shown by @hasnhasan above, it will load all bytes into memory and then write at once. This might create OOM for very long recordings. I have seen implementations dumping the content directly in a file, and creating the appropriate headers at start, and trailer at end.

Not sure if fragmented mp4 is a good solution, as these will create an init and individual segment files, where as for some usecases you would like to have one large mp4 containing all data. Not sure if this is possible with the current implementation.

cedricve commented 7 months ago

Nevermind I just fixed my "usecase" by passing in a file writer, so nothing in memory is stored, but dumped directly to filesystem. @hasnhasan I believe this would fix your issue as well.