-
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
4 changed files
with
521 additions
and
64 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,62 @@ | ||
package vp9 | ||
|
||
import "fmt" | ||
|
||
func hasSpace(buf []byte, pos int, n int) error { | ||
if n > ((len(buf) * 8) - pos) { | ||
return fmt.Errorf("not enough bits") | ||
} | ||
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 { | ||
v := uint64(0) | ||
|
||
res := 8 - (*pos & 0x07) | ||
if n < res { | ||
v := uint64((buf[*pos>>0x03] >> (res - n)) & (1<<n - 1)) | ||
*pos += n | ||
return v | ||
} | ||
|
||
v = (v << res) | 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,216 @@ | ||
package vp9 | ||
|
||
import ( | ||
"fmt" | ||
) | ||
|
||
// Header_ColorConfig is the color_config member of an header. | ||
type Header_ColorConfig struct { //nolint:revive | ||
TenOrTwelveBit bool | ||
BitDepth uint8 | ||
ColorSpace uint8 | ||
ColorRange bool | ||
SubsamplingX bool | ||
SubsamplingY bool | ||
} | ||
|
||
func (c *Header_ColorConfig) 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 | ||
} | ||
|
||
// Header_FrameSize is the frame_size member of an header. | ||
type Header_FrameSize struct { //nolint:revive | ||
FrameWidthMinus1 uint16 | ||
FrameHeightMinus1 uint16 | ||
} | ||
|
||
func (s *Header_FrameSize) 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 | ||
IsKeyFrame bool | ||
ShowFrame bool | ||
ErrorResilientMode bool | ||
ColorConfig *Header_ColorConfig | ||
FrameSize *Header_FrameSize | ||
} | ||
|
||
// 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 fmt.Errorf("invalid frame marker") | ||
Check failure on line 120 in codecs/vp9/header.go GitHub Actions / lint / Go
|
||
} | ||
|
||
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 { | ||
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.IsKeyFrame = !readFlagUnsafe(buf, &pos) | ||
h.ShowFrame = readFlagUnsafe(buf, &pos) | ||
h.ErrorResilientMode = readFlagUnsafe(buf, &pos) | ||
|
||
if h.IsKeyFrame { | ||
err := hasSpace(buf, pos, 24) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
frameSyncByte0 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte0 != 0x49 { | ||
return fmt.Errorf("wrong frame_sync_byte_0") | ||
Check failure on line 167 in codecs/vp9/header.go GitHub Actions / lint / Go
|
||
} | ||
|
||
frameSyncByte1 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte1 != 0x83 { | ||
return fmt.Errorf("wrong frame_sync_byte_1") | ||
Check failure on line 172 in codecs/vp9/header.go GitHub Actions / lint / Go
|
||
} | ||
|
||
frameSyncByte2 := uint8(readBitsUnsafe(buf, &pos, 8)) | ||
if frameSyncByte2 != 0x42 { | ||
return fmt.Errorf("wrong frame_sync_byte_2") | ||
Check failure on line 177 in codecs/vp9/header.go GitHub Actions / lint / Go
|
||
} | ||
|
||
h.ColorConfig = &Header_ColorConfig{} | ||
err = h.ColorConfig.unmarshal(h.Profile, buf, &pos) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
h.FrameSize = &Header_FrameSize{} | ||
err = h.FrameSize.unmarshal(buf, &pos) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Width returns the video width. | ||
func (h Header) Width() int { | ||
return int(h.FrameSize.FrameWidthMinus1) + 1 | ||
} | ||
|
||
// Height returns the video height. | ||
func (h Header) Height() int { | ||
return int(h.FrameSize.FrameHeightMinus1) + 1 | ||
} | ||
|
||
// ChromaSubsampling returns the chroma subsampling format, in ISO-BMFF/vpcC format. | ||
func (h Header) ChromaSubsampling() uint8 { | ||
switch { | ||
case !h.ColorConfig.SubsamplingX && !h.ColorConfig.SubsamplingY: | ||
return 3 // 4:4:4 | ||
case h.ColorConfig.SubsamplingX && !h.ColorConfig.SubsamplingY: | ||
return 2 // 4:2:2 | ||
default: | ||
return 1 // 4:2:0 colocated with luma | ||
} | ||
} |
Oops, something went wrong.