Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Corrupt .webm file in version 4 #2942

Open
AbolfazlAkhtari opened this issue Oct 28, 2024 · 2 comments
Open

Corrupt .webm file in version 4 #2942

AbolfazlAkhtari opened this issue Oct 28, 2024 · 2 comments

Comments

@AbolfazlAkhtari
Copy link

Your environment.

  • Version: v4.0.1
  • Browser: -
  • Other Information: -

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
Copy link

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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants