pion / webrtc

Pure Go implementation of the WebRTC API
https://pion.ly
MIT License
13.87k stars 1.66k forks source link

Corrupt .webm file in version 4 #2942

Open AbolfazlAkhtari opened 3 weeks ago

AbolfazlAkhtari commented 3 weeks ago

Your environment.

What did you do?

I've set up a webrtc media server using your package. Peers connect to this server and send their tracks to the server and the server broadcasts the received track to other peers. I've followed Save To Disk Example that you have provided to set up a webm saver. This was working correctly until today when I decided to upgrade pion/webrtc/v3 into pion/webrtc/v4

Now with v4 all set up. Everything is working as before. Peers connect to the server and can send media, the media will show correctly on other peers' screens. However, the saved file is broken (please see the attached image to know what I mean by broken)

image

These are my saver file codes:

package webm_saver

import (
    "fmt"
    "github.com/at-wat/ebml-go/webm"
    "github.com/pion/interceptor/pkg/jitterbuffer"
    "github.com/pion/rtp"
    "github.com/pion/rtp/codecs"
    "github.com/pion/webrtc/v4/pkg/media/samplebuilder"
    "os"
    "poc/pkg/exception"
    "time"
)

const (
    naluTypeBitmask = 0b11111
    naluTypeSPS     = 7
)

type WebmSaver struct {
    AudioWriter, VideoWriter       webm.BlockWriteCloser
    AudioBuilder, VideoBuilder     *samplebuilder.SampleBuilder
    AudioTimestamp, VideoTimestamp time.Duration

    h264JitterBuffer   *jitterbuffer.JitterBuffer
    lastVideoTimestamp uint32

    Status   bool
    UserId   uint
    RoomCode string
    FilePath *string
}

func NewWebmSaver(userId uint, roomCode string) *WebmSaver {
    return &WebmSaver{
        AudioBuilder:     samplebuilder.New(10, &codecs.OpusPacket{}, 48000),
        VideoBuilder:     samplebuilder.New(10, &codecs.VP8Packet{}, 90000),
        h264JitterBuffer: jitterbuffer.New(),
        Status:           true,
        UserId:           userId,
        RoomCode:         roomCode,
    }
}

func (s *WebmSaver) Close() {
    s.Status = false

    fmt.Printf("Finalizing webm...\n")
    if s.AudioWriter != nil {
        if err := s.AudioWriter.Close(); err != nil {
            exception.ReportException(err)
        }
    }
    if s.VideoWriter != nil {
        if err := s.VideoWriter.Close(); err != nil {
            exception.ReportException(err)
        }
    }
}

func (s *WebmSaver) PushOpus(rtpPacket *rtp.Packet) {
    s.AudioBuilder.Push(rtpPacket)

    for {
        sample := s.AudioBuilder.Pop()
        if sample == nil {
            return
        }
        if s.AudioWriter != nil {
            s.AudioTimestamp += sample.Duration
            if _, err := s.AudioWriter.Write(true, int64(s.AudioTimestamp/time.Millisecond), sample.Data); err != nil {
                exception.ReportException(err)
            }
        }
    }
}

func (s *WebmSaver) PushH264(rtpPacket *rtp.Packet) {
    s.h264JitterBuffer.Push(rtpPacket)

    pkt, err := s.h264JitterBuffer.Peek(true)
    if err != nil {
        return
    }

    pkts := []*rtp.Packet{pkt}
    for {
        pkt, err = s.h264JitterBuffer.PeekAtSequence(pkts[len(pkts)-1].SequenceNumber + 1)
        if err != nil {
            return
        }

        // We have popped a whole frame, lets write it
        if pkts[0].Timestamp != pkt.Timestamp {
            break
        }

        pkts = append(pkts, pkt)
    }

    h264Packet := &codecs.H264Packet{}
    data := []byte{}
    for i := range pkts {
        if _, err = s.h264JitterBuffer.PopAtSequence(pkts[i].SequenceNumber); err != nil {
            panic(err)
        }

        out, err := h264Packet.Unmarshal(pkts[i].Payload)
        if err != nil {
            panic(err)
        }
        data = append(data, out...)
    }

    videoKeyframe := (data[4] & naluTypeBitmask) == naluTypeSPS
    if s.VideoWriter == nil && videoKeyframe {
        if s.VideoWriter == nil || s.AudioWriter == nil {
            s.InitWriter(true, 1280, 720)
        }
    }

    samples := uint32(0)
    if s.lastVideoTimestamp != 0 {
        samples = pkts[0].Timestamp - s.lastVideoTimestamp
    }
    s.lastVideoTimestamp = pkts[0].Timestamp

    if s.VideoWriter != nil {
        s.VideoTimestamp += time.Duration(float64(samples) / float64(90000) * float64(time.Second))
        if _, err := s.VideoWriter.Write(videoKeyframe, int64(s.VideoTimestamp/time.Millisecond), data); err != nil {
            panic(err)
        }
    }
}

func (s *WebmSaver) PushVP8(rtpPacket *rtp.Packet) {
    s.VideoBuilder.Push(rtpPacket)

    for {
        sample := s.VideoBuilder.Pop()
        if sample == nil {
            return
        }
        // Read VP8 header.
        videoKeyframe := (sample.Data[0]&0x1 == 0)
        if videoKeyframe {
            // Keyframe has frame information.
            raw := uint(sample.Data[6]) | uint(sample.Data[7])<<8 | uint(sample.Data[8])<<16 | uint(sample.Data[9])<<24
            width := int(raw & 0x3FFF)
            height := int((raw >> 16) & 0x3FFF)

            if s.VideoWriter == nil || s.AudioWriter == nil {
                // Initialize WebM webm_saver using received frame size.
                s.InitWriter(false, width, height)
            }
        }
        if s.VideoWriter != nil {
            s.VideoTimestamp += sample.Duration
            if _, err := s.VideoWriter.Write(videoKeyframe, int64(s.VideoTimestamp/time.Millisecond), sample.Data); err != nil {
                exception.ReportException(err)
            }
        }
    }
}

func (s *WebmSaver) InitWriter(isH264 bool, width, height int) {
    filePath := fmt.Sprintf("storage/records/%v-%v-%v.webm", s.UserId, s.RoomCode, time.Now().Unix())
    w, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
    if err != nil {
        exception.ReportException(err)
    }
    s.FilePath = &filePath

    videoMimeType := "V_VP8"
    if isH264 {
        videoMimeType = "V_MPEG4/ISO/AVC"
    }

    ws, err := webm.NewSimpleBlockWriter(w,
        []webm.TrackEntry{
            {
                Name:            "Audio",
                TrackNumber:     1,
                TrackUID:        12345,
                CodecID:         "A_OPUS",
                TrackType:       2,
                DefaultDuration: 20000000,
                Audio: &webm.Audio{
                    SamplingFrequency: 48000.0,
                    Channels:          2,
                },
            }, {
                Name:            "Video",
                TrackNumber:     2,
                TrackUID:        67890,
                CodecID:         videoMimeType,
                TrackType:       1,
                DefaultDuration: 33333333,
                Video: &webm.Video{
                    PixelWidth:  uint64(width),
                    PixelHeight: uint64(height),
                },
            },
        })
    if err != nil {
        panic(err)
    }
    s.AudioWriter = ws[0]
    s.VideoWriter = ws[1]
}

As I know, the video is coming as a vp8 file.

Do you know why this problem happened and why it didn't happen while I was using version 3?

What did you expect?

I expected the video file to save correctly as it did with the previous version.

What happened?

The saved .webm file is corrupt

mladenovic-13 commented 4 days ago

I have the same issue... When only H264 recording is used, it works as expected. Does anyone know what the issue might be?

mladenovic-13 commented 3 days ago

If anyone encounters a similar issue, just implement a jitterBuffer for VP8. That should solve the problem.