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

Fix ivf timestamps #2987

Merged
merged 7 commits into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion pkg/media/ivfreader/ivfreader.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type IVFFrameHeader struct {
type IVFReader struct {
stream io.Reader
bytesReadSuccesfully int64
timebaseDenominator uint32
timebaseNumerator uint32
}

// NewWith returns a new IVF reader and IVF file header
Expand All @@ -69,6 +71,8 @@ func NewWith(in io.Reader) (*IVFReader, *IVFFileHeader, error) {
if err != nil {
return nil, nil, err
}
reader.timebaseDenominator = header.TimebaseDenominator
reader.timebaseNumerator = header.TimebaseNumerator
xdrudis marked this conversation as resolved.
Show resolved Hide resolved

return reader, header, nil
}
Expand All @@ -80,6 +84,10 @@ func (i *IVFReader) ResetReader(reset func(bytesRead int64) io.Reader) {
i.stream = reset(i.bytesReadSuccesfully)
}

func (i *IVFReader) ptsToTimestamp(pts uint64) uint64 {
return pts * uint64(i.timebaseDenominator) / uint64(i.timebaseNumerator)
}

// ParseNextFrame reads from stream and returns IVF frame payload, header,
// and an error if there is incomplete frame data.
// Returns all nil values when no more frames are available.
Expand All @@ -95,9 +103,10 @@ func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) {
return nil, nil, err
}

pts := binary.LittleEndian.Uint64(buffer[4:12])
header = &IVFFrameHeader{
FrameSize: binary.LittleEndian.Uint32(buffer[:4]),
Timestamp: binary.LittleEndian.Uint64(buffer[4:12]),
Timestamp: i.ptsToTimestamp(pts),
}

payload := make([]byte, header.FrameSize)
Expand Down
41 changes: 29 additions & 12 deletions pkg/media/ivfwriter/ivfwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ type IVFWriter struct {

isVP8, isAV1 bool

timebaseDenominator uint32
timebaseNumerator uint32
firstFrameTimestamp uint32
clockRate uint64

// VP8
currentFrame []byte

Expand Down Expand Up @@ -65,8 +70,11 @@ func NewWith(out io.Writer, opts ...Option) (*IVFWriter, error) {
}

writer := &IVFWriter{
ioWriter: out,
seenKeyFrame: false,
ioWriter: out,
seenKeyFrame: false,
timebaseDenominator: 30,
timebaseNumerator: 1,
clockRate: 90000,
}

for _, o := range opts {
Expand Down Expand Up @@ -98,21 +106,25 @@ func (i *IVFWriter) writeHeader() error {
copy(header[8:], "AV01")
}

binary.LittleEndian.PutUint16(header[12:], 640) // Width in pixels
binary.LittleEndian.PutUint16(header[14:], 480) // Height in pixels
binary.LittleEndian.PutUint32(header[16:], 30) // Framerate denominator
binary.LittleEndian.PutUint32(header[20:], 1) // Framerate numerator
binary.LittleEndian.PutUint32(header[24:], 900) // Frame count, will be updated on first Close() call
binary.LittleEndian.PutUint32(header[28:], 0) // Unused
binary.LittleEndian.PutUint16(header[12:], 640) // Width in pixels
binary.LittleEndian.PutUint16(header[14:], 480) // Height in pixels
binary.LittleEndian.PutUint32(header[16:], i.timebaseDenominator) // Framerate denominator
binary.LittleEndian.PutUint32(header[20:], i.timebaseNumerator) // Framerate numerator
binary.LittleEndian.PutUint32(header[24:], 900) // Frame count, will be updated on first Close() call
binary.LittleEndian.PutUint32(header[28:], 0) // Unused

_, err := i.ioWriter.Write(header)
return err
}

func (i *IVFWriter) timestampToPts(timestamp uint64) uint64 {
return timestamp * uint64(i.timebaseNumerator) / uint64(i.timebaseDenominator)
}

func (i *IVFWriter) writeFrame(frame []byte, timestamp uint64) error {
frameHeader := make([]byte, 12)
binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(frame))) // Frame length
binary.LittleEndian.PutUint64(frameHeader[4:], timestamp) // PTS
binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(frame))) // Frame length
binary.LittleEndian.PutUint64(frameHeader[4:], i.timestampToPts(timestamp)) // PTS
i.count++

if _, err := i.ioWriter.Write(frameHeader); err != nil {
Expand All @@ -130,6 +142,11 @@ func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
return nil
}

if i.count == 0 {
i.firstFrameTimestamp = packet.Header.Timestamp
}
relativeTstampMs := 1000 * uint64(packet.Header.Timestamp-i.firstFrameTimestamp) / i.clockRate

if i.isVP8 {
vp8Packet := codecs.VP8Packet{}
if _, err := vp8Packet.Unmarshal(packet.Payload); err != nil {
Expand All @@ -153,7 +170,7 @@ func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
return nil
}

if err := i.writeFrame(i.currentFrame, uint64(packet.Header.Timestamp)); err != nil {
if err := i.writeFrame(i.currentFrame, relativeTstampMs); err != nil {
return err
}
i.currentFrame = nil
Expand All @@ -169,7 +186,7 @@ func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
}

for j := range obus {
if err := i.writeFrame(obus[j], uint64(packet.Header.Timestamp)); err != nil {
if err := i.writeFrame(obus[j], relativeTstampMs); err != nil {
return err
}
}
Expand Down
23 changes: 8 additions & 15 deletions pkg/media/ivfwriter/ivfwriter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package ivfwriter

import (
"bytes"
"encoding/binary"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this test just undo https://github.com/pion/webrtc/pull/2853/files

"io"
"testing"

Expand Down Expand Up @@ -258,25 +257,19 @@ func TestIVFWriter_AV1(t *testing.T) {
t.Run("Unfragmented", func(t *testing.T) {
buffer := &bytes.Buffer{}

expectedTimestamp := uint32(3653407706)
writer, err := NewWith(buffer, WithCodec(mimeTypeAV1))
assert.NoError(t, err)

// the timestamp is an uint32, 4 bytes from offset 36
expectedPayloadWithTimestamp := []byte{
assert.NoError(t, writer.WriteRTP(&rtp.Packet{Payload: []byte{0x00, 0x01, 0xFF}}))
assert.NoError(t, writer.Close())
assert.Equal(t, buffer.Bytes(), []byte{
0x44, 0x4b, 0x49, 0x46, 0x0, 0x0, 0x20,
0x0, 0x41, 0x56, 0x30, 0x31, 0x80, 0x2,
0xe0, 0x1, 0x1e, 0x0, 0x0, 0x0, 0x1, 0x0,
0x0, 0x0, 0x84, 0x3, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0xda, 0x93, 0xc2,
0xd9, 0x0, 0x0, 0x0, 0x0, 0xff,
}

writer, err := NewWith(buffer, WithCodec(mimeTypeAV1))
assert.NoError(t, err)

assert.NoError(t, writer.WriteRTP(&rtp.Packet{Header: rtp.Header{Timestamp: expectedTimestamp}, Payload: []byte{0x00, 0x01, 0xFF}}))
assert.NoError(t, writer.Close())
assert.Equal(t, expectedPayloadWithTimestamp, buffer.Bytes())
assert.Equal(t, expectedTimestamp, binary.LittleEndian.Uint32(expectedPayloadWithTimestamp[36:40]))
0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
})
})

t.Run("Fragmented", func(t *testing.T) {
Expand Down
Loading