-
Notifications
You must be signed in to change notification settings - Fork 113
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The current implementation of the VP9 payloader produces payloads that are not compatible with iOS. This is because the payloader provides only the muxing strategy called "flexible mode". According to the VP9 RFC draft, there are two ways to wrap VP9 frames into RTP packets: the "flexible mode" and the "non-flexible mode", with the latter being the preferred one for live-streaming applications. In particular, all browsers encodes VP9 RTP packets in the "non-flexible mode", while iOS supports decoding RTP packets in this mode only, and this is probably a problem shared by other implementations. This patch improves the VP9 payloader by adding support for the "non-flexible mode". The "flexible mode" is retained and a flag is provided to perform the switch between the two modes.
- Loading branch information
Showing
5 changed files
with
598 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package vp9 | ||
|
||
import "errors" | ||
|
||
var errNotEnoughBits = errors.New("not enough bits") | ||
|
||
func hasSpace(buf []byte, pos int, n int) error { | ||
if n > ((len(buf) * 8) - pos) { | ||
return errNotEnoughBits | ||
} | ||
return nil | ||
} | ||
|
||
func readFlag(buf []byte, pos *int) (bool, error) { | ||
err := hasSpace(buf, *pos, 1) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return readFlagUnsafe(buf, pos), nil | ||
} | ||
|
||
func readFlagUnsafe(buf []byte, pos *int) bool { | ||
b := (buf[*pos>>0x03] >> (7 - (*pos & 0x07))) & 0x01 | ||
*pos++ | ||
return b == 1 | ||
} | ||
|
||
func readBits(buf []byte, pos *int, n int) (uint64, error) { | ||
err := hasSpace(buf, *pos, n) | ||
if err != nil { | ||
return 0, err | ||
} | ||
|
||
return readBitsUnsafe(buf, pos, n), nil | ||
} | ||
|
||
func readBitsUnsafe(buf []byte, pos *int, n int) uint64 { | ||
res := 8 - (*pos & 0x07) | ||
if n < res { | ||
v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<<n - 1)) | ||
*pos += n | ||
return v | ||
} | ||
|
||
v := uint64(buf[*pos>>0x03] & (1<<res - 1)) | ||
*pos += res | ||
n -= res | ||
|
||
for n >= 8 { | ||
v = (v << 8) | uint64(buf[*pos>>0x03]) | ||
*pos += 8 | ||
n -= 8 | ||
} | ||
|
||
if n > 0 { | ||
v = (v << n) | uint64(buf[*pos>>0x03]>>(8-n)) | ||
*pos += n | ||
} | ||
|
||
return v | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
// Package vp9 contains a VP9 header parser. | ||
package vp9 | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
var ( | ||
errInvalidFrameMarker = errors.New("invalid frame marker") | ||
errWrongFrameSyncByte0 = errors.New("wrong frame_sync_byte_0") | ||
errWrongFrameSyncByte1 = errors.New("wrong frame_sync_byte_1") | ||
errWrongFrameSyncByte2 = errors.New("wrong frame_sync_byte_2") | ||
) | ||
|
||
// HeaderColorConfig is the color_config member of an header. | ||
type HeaderColorConfig struct { | ||
TenOrTwelveBit bool | ||
BitDepth uint8 | ||
ColorSpace uint8 | ||
ColorRange bool | ||
SubsamplingX bool | ||
SubsamplingY bool | ||
} | ||
|
||
func (c *HeaderColorConfig) unmarshal(profile uint8, buf []byte, pos *int) error { | ||
if profile >= 2 { | ||
var err error | ||
c.TenOrTwelveBit, err = readFlag(buf, pos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if c.TenOrTwelveBit { | ||
c.BitDepth = 12 | ||
} else { | ||
c.BitDepth = 10 | ||
} | ||
} else { | ||
c.BitDepth = 8 | ||
} | ||
|
||
tmp, err := readBits(buf, pos, 3) | ||
if err != nil { | ||
return err | ||
} | ||
c.ColorSpace = uint8(tmp) | ||
|
||
if c.ColorSpace != 7 { | ||
var err error | ||
c.ColorRange, err = readFlag(buf, pos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if profile == 1 || profile == 3 { | ||
err := hasSpace(buf, *pos, 3) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
c.SubsamplingX = readFlagUnsafe(buf, pos) | ||
c.SubsamplingY = readFlagUnsafe(buf, pos) | ||
*pos++ | ||
} else { | ||
c.SubsamplingX = true | ||
c.SubsamplingY = true | ||
} | ||
} else { | ||
c.ColorRange = true | ||
|
||
if profile == 1 || profile == 3 { | ||
c.SubsamplingX = false | ||
c.SubsamplingY = false | ||
|
||
err := hasSpace(buf, *pos, 1) | ||
if err != nil { | ||
return err | ||
} | ||
*pos++ | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// HeaderFrameSize is the frame_size member of an header. | ||
type HeaderFrameSize struct { | ||
FrameWidthMinus1 uint16 | ||
FrameHeightMinus1 uint16 | ||
} | ||
|
||
func (s *HeaderFrameSize) unmarshal(buf []byte, pos *int) error { | ||
err := hasSpace(buf, *pos, 32) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
s.FrameWidthMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) | ||
s.FrameHeightMinus1 = uint16(readBitsUnsafe(buf, pos, 16)) | ||
return nil | ||
} | ||
|
||
// Header is a VP9 Frame header. | ||
// Specification: | ||
// https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.6-20160331-draft.pdf | ||
type Header struct { | ||
Profile uint8 | ||
ShowExistingFrame bool | ||
FrameToShowMapIdx uint8 | ||
NonKeyFrame bool | ||
ShowFrame bool | ||
ErrorResilientMode bool | ||
ColorConfig *HeaderColorConfig | ||
FrameSize *HeaderFrameSize | ||
} | ||
|
||
// Unmarshal decodes a Header. | ||
func (h *Header) Unmarshal(buf []byte) error { | ||
pos := 0 | ||
|
||
err := hasSpace(buf, pos, 4) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
frameMarker := readBitsUnsafe(buf, &pos, 2) | ||
if frameMarker != 2 { | ||
return errInvalidFrameMarker | ||
} | ||
|
||
profileLowBit := uint8(readBitsUnsafe(buf, &pos, 1)) | ||
profileHighBit := uint8(readBitsUnsafe(buf, &pos, 1)) | ||
h.Profile = profileHighBit<<1 + profileLowBit | ||
|
||
if h.Profile == 3 { | ||
err = hasSpace(buf, pos, 1) | ||
if err != nil { | ||
return err | ||
} | ||
pos++ | ||
} | ||
|
||
h.ShowExistingFrame, err = readFlag(buf, &pos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if h.ShowExistingFrame { | ||
var tmp uint64 | ||
tmp, err = readBits(buf, &pos, 3) | ||
if err != nil { | ||
return err | ||
} | ||
h.FrameToShowMapIdx = uint8(tmp) | ||
return nil | ||
} | ||
|
||
err = hasSpace(buf, pos, 3) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
h.NonKeyFrame = readFlagUnsafe(buf, &pos) | ||
h.ShowFrame = readFlagUnsafe(buf, &pos) | ||
h.ErrorResilientMode = readFlagUnsafe(buf, &pos) | ||
|
||
if !h.NonKeyFrame { | ||
err := hasSpace(buf, pos, 24) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
frameSyncByte0 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte0 != 0x49 { | ||
return errWrongFrameSyncByte0 | ||
} | ||
|
||
frameSyncByte1 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte1 != 0x83 { | ||
return errWrongFrameSyncByte1 | ||
} | ||
|
||
frameSyncByte2 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte2 != 0x42 { | ||
return errWrongFrameSyncByte2 | ||
} | ||
|
||
h.ColorConfig = &HeaderColorConfig{} | ||
err = h.ColorConfig.unmarshal(h.Profile, buf, &pos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
h.FrameSize = &HeaderFrameSize{} | ||
err = h.FrameSize.unmarshal(buf, &pos) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Width returns the video width. | ||
func (h Header) Width() uint16 { | ||
if h.FrameSize == nil { | ||
return 0 | ||
} | ||
return h.FrameSize.FrameWidthMinus1 + 1 | ||
} | ||
|
||
// Height returns the video height. | ||
func (h Header) Height() uint16 { | ||
if h.FrameSize == nil { | ||
return 0 | ||
} | ||
return h.FrameSize.FrameHeightMinus1 + 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> | ||
// SPDX-License-Identifier: MIT | ||
|
||
package vp9 | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestHeaderUnmarshal(t *testing.T) { | ||
cases := []struct { | ||
name string | ||
byts []byte | ||
sh Header | ||
width uint16 | ||
height uint16 | ||
}{ | ||
{ | ||
"chrome webrtc", | ||
[]byte{ | ||
0x82, 0x49, 0x83, 0x42, 0x00, 0x77, 0xf0, 0x32, | ||
0x34, 0x30, 0x38, 0x24, 0x1c, 0x19, 0x40, 0x18, | ||
0x03, 0x40, 0x5f, 0xb4, | ||
}, | ||
Header{ | ||
ShowFrame: true, | ||
ColorConfig: &HeaderColorConfig{ | ||
BitDepth: 8, | ||
SubsamplingX: true, | ||
SubsamplingY: true, | ||
}, | ||
FrameSize: &HeaderFrameSize{ | ||
FrameWidthMinus1: 1919, | ||
FrameHeightMinus1: 803, | ||
}, | ||
}, | ||
1920, | ||
804, | ||
}, | ||
{ | ||
"vp9 sample", | ||
[]byte{ | ||
0x82, 0x49, 0x83, 0x42, 0x40, 0xef, 0xf0, 0x86, | ||
0xf4, 0x04, 0x21, 0xa0, 0xe0, 0x00, 0x30, 0x70, | ||
0x00, 0x00, 0x00, 0x01, | ||
}, | ||
Header{ | ||
ShowFrame: true, | ||
ColorConfig: &HeaderColorConfig{ | ||
BitDepth: 8, | ||
ColorSpace: 2, | ||
SubsamplingX: true, | ||
SubsamplingY: true, | ||
}, | ||
FrameSize: &HeaderFrameSize{ | ||
FrameWidthMinus1: 3839, | ||
FrameHeightMinus1: 2159, | ||
}, | ||
}, | ||
3840, | ||
2160, | ||
}, | ||
} | ||
|
||
for _, ca := range cases { | ||
t.Run(ca.name, func(t *testing.T) { | ||
var sh Header | ||
err := sh.Unmarshal(ca.byts) | ||
if err != nil { | ||
t.Fatal("unexpected error") | ||
} | ||
|
||
if !reflect.DeepEqual(ca.sh, sh) { | ||
t.Fatalf("expected %#+v, got %#+v", ca.sh, sh) | ||
} | ||
if ca.width != sh.Width() { | ||
t.Fatalf("unexpected width") | ||
} | ||
if ca.height != sh.Height() { | ||
t.Fatalf("unexpected height") | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.