Skip to content

Commit

Permalink
support muxing and reading VP9 tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Aug 6, 2023
1 parent 3df19dd commit a33a05a
Show file tree
Hide file tree
Showing 20 changed files with 262 additions and 127 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Client features:
|Read fMP4 streams|OK|
|Read Low-latency streams|TODO|
|Read AV1 tracks|OK|
|Read VP9 tracks|OK|
|Read H265 tracks|OK|
|Read H264 tracks|OK|
|Read Opus tracks|OK|
Expand All @@ -33,6 +34,7 @@ Muxer features:
|Generate fMP4 streams|OK|
|Generate Low-latency streams|OK|
|Write AV1 tracks|OK|
|Write VP9 tracks|OK|
|Write H265 tracks|OK|
|Write H264 tracks|OK|
|Write Opus tracks|OK|
Expand Down
8 changes: 8 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type ClientOnTracksFunc func([]*Track) error
// ClientOnDataAV1Func is the prototype of the function passed to OnDataAV1().
type ClientOnDataAV1Func func(pts time.Duration, obus [][]byte)

// ClientOnDataVP9Func is the prototype of the function passed to OnDataVP9().
type ClientOnDataVP9Func func(pts time.Duration, frame []byte)

// ClientOnDataH26xFunc is the prototype of the function passed to OnDataH26x().
type ClientOnDataH26xFunc func(pts time.Duration, dts time.Duration, au [][]byte)

Expand Down Expand Up @@ -147,6 +150,11 @@ func (c *Client) OnDataAV1(forma *Track, cb ClientOnDataAV1Func) {
c.onData[forma] = cb
}

// OnDataVP9 sets a callback that is called when data from a VP9 track is received.
func (c *Client) OnDataVP9(forma *Track, cb ClientOnDataVP9Func) {
c.onData[forma] = cb

Check warning on line 155 in client.go

View check run for this annotation

Codecov / codecov/patch

client.go#L154-L155

Added lines #L154 - L155 were not covered by tests
}

// OnDataH26x sets a callback that is called when data from an H26x track is received.
func (c *Client) OnDataH26x(forma *Track, cb ClientOnDataH26xFunc) {
c.onData[forma] = cb
Expand Down
18 changes: 14 additions & 4 deletions client_processor_fmp4.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import (
func fmp4PickLeadingTrack(init *fmp4.Init) int {
// pick first video track
for _, track := range init.Tracks {
switch track.Codec.(type) {
case *fmp4.CodecAV1, *fmp4.CodecH264, *fmp4.CodecH265:
if track.Codec.IsVideo() {
return track.ID
}
}
Expand Down Expand Up @@ -211,6 +210,17 @@ func (p *clientProcessorFMP4) initializeTrackProcs(ctx context.Context, track *f
return nil
}

case *codecs.VP9:
var onDataCasted ClientOnDataVP9Func = func(pts time.Duration, frame []byte) {}
if onData != nil {
onDataCasted = onData.(ClientOnDataVP9Func)
}

Check warning on line 217 in client_processor_fmp4.go

View check run for this annotation

Codecov / codecov/patch

client_processor_fmp4.go#L213-L217

Added lines #L213 - L217 were not covered by tests

postProcess = func(pts time.Duration, dts time.Duration, sample *fmp4.PartSample) error {
onDataCasted(pts, sample.Payload)
return nil
}

Check warning on line 222 in client_processor_fmp4.go

View check run for this annotation

Codecov / codecov/patch

client_processor_fmp4.go#L219-L222

Added lines #L219 - L222 were not covered by tests

case *codecs.H265, *codecs.H264:
var onDataCasted ClientOnDataH26xFunc = func(pts time.Duration, dts time.Duration, au [][]byte) {}
if onData != nil {
Expand All @@ -234,7 +244,7 @@ func (p *clientProcessorFMP4) initializeTrackProcs(ctx context.Context, track *f
}

postProcess = func(pts time.Duration, dts time.Duration, sample *fmp4.PartSample) error {
onDataCasted(pts, [][]byte{sample.GetAudio()})
onDataCasted(pts, [][]byte{sample.Payload})

Check warning on line 247 in client_processor_fmp4.go

View check run for this annotation

Codecov / codecov/patch

client_processor_fmp4.go#L247

Added line #L247 was not covered by tests
return nil
}

Expand All @@ -245,7 +255,7 @@ func (p *clientProcessorFMP4) initializeTrackProcs(ctx context.Context, track *f
}

postProcess = func(pts time.Duration, dts time.Duration, sample *fmp4.PartSample) error {
onDataCasted(pts, [][]byte{sample.GetAudio()})
onDataCasted(pts, [][]byte{sample.Payload})

Check warning on line 258 in client_processor_fmp4.go

View check run for this annotation

Codecov / codecov/patch

client_processor_fmp4.go#L258

Added line #L258 was not covered by tests
return nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.18
require (
github.com/aler9/writerseeker v0.0.0-20220601075008-6f0e685b9c82
github.com/asticode/go-astits v1.12.0
github.com/bluenviron/mediacommon v0.7.1-0.20230805234008-34d20294a26b
github.com/bluenviron/mediacommon v0.7.1-0.20230806181841-a2766dec314f
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflx
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astits v1.12.0 h1:BiefTgVEyPgEB8nT6J+Sys/uxE4H/a04SW/aedpOpPc=
github.com/asticode/go-astits v1.12.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
github.com/bluenviron/mediacommon v0.7.1-0.20230805234008-34d20294a26b h1:/csYQQDmZyXw0CVATJSPBkwT3JYap418W7LLX7mscxw=
github.com/bluenviron/mediacommon v0.7.1-0.20230805234008-34d20294a26b/go.mod h1:LR4w8cpvzo2ZcmBwXcentvBj7ZlyF9g9xP4dDbt8uJw=
github.com/bluenviron/mediacommon v0.7.1-0.20230806181841-a2766dec314f h1:hVo5b6WSVT0+p43GpYv0e4cLtmpkhJp3kD6Ef83jzUM=
github.com/bluenviron/mediacommon v0.7.1-0.20230806181841-a2766dec314f/go.mod h1:LR4w8cpvzo2ZcmBwXcentvBj7ZlyF9g9xP4dDbt8uJw=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
Expand Down
95 changes: 85 additions & 10 deletions muxer.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/bluenviron/mediacommon/pkg/codecs/av1"
"github.com/bluenviron/mediacommon/pkg/codecs/h264"
"github.com/bluenviron/mediacommon/pkg/codecs/h265"
"github.com/bluenviron/mediacommon/pkg/codecs/vp9"

"github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gohlslib/pkg/storage"
Expand Down Expand Up @@ -198,7 +199,7 @@ func (m *Muxer) WriteAV1(ntp time.Time, pts time.Duration, obus [][]byte) error
codec := m.VideoTrack.Codec.(*codecs.AV1)
update := false
sequenceHeader := codec.SequenceHeader
sequenceHeaderPresent := false
randomAccess := false

Check warning on line 202 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L202

Added line #L202 was not covered by tests

for _, obu := range obus {
var h av1.OBUHeader
Expand All @@ -212,7 +213,7 @@ func (m *Muxer) WriteAV1(ntp time.Time, pts time.Duration, obus [][]byte) error
update = true
sequenceHeader = obu
}
sequenceHeaderPresent = true
randomAccess = true

Check warning on line 216 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L216

Added line #L216 was not covered by tests
}
}

Expand All @@ -230,17 +231,91 @@ func (m *Muxer) WriteAV1(ntp time.Time, pts time.Duration, obus [][]byte) error
}

forceSwitch := false
if sequenceHeaderPresent && m.forceSwitch {
if randomAccess && m.forceSwitch {

Check warning on line 234 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L234

Added line #L234 was not covered by tests
m.forceSwitch = false
forceSwitch = true
}

return m.segmenter.writeAV1(ntp, pts, obus, sequenceHeaderPresent, forceSwitch)
return m.segmenter.writeAV1(ntp, pts, obus, randomAccess, forceSwitch)

Check warning on line 239 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L239

Added line #L239 was not covered by tests
}

// WriteVP9 writes a VP9 frame.
func (m *Muxer) WriteVP9(ntp time.Time, pts time.Duration, frame []byte) error {
var h vp9.Header
err := h.Unmarshal(frame)
if err != nil {
return err
}

Check warning on line 248 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L243-L248

Added lines #L243 - L248 were not covered by tests

codec := m.VideoTrack.Codec.(*codecs.VP9)
randomAccess := false
update := false
width := codec.Width
height := codec.Height
profile := codec.Profile
bitDepth := codec.BitDepth
chromaSubsampling := codec.ChromaSubsampling
colorRange := codec.ColorRange

if h.FrameType == vp9.FrameTypeKeyFrame {
randomAccess = true

if v := h.Width(); v != width {
update = true
width = v
}
if v := h.Height(); v != height {
update = true
height = v
}
if h.Profile != profile {
update = true
profile = h.Profile
}
if h.ColorConfig.BitDepth != bitDepth {
update = true
bitDepth = h.ColorConfig.BitDepth
}
if v := h.ChromaSubsampling(); v != chromaSubsampling {
update = true
chromaSubsampling = v
}
if h.ColorConfig.ColorRange != colorRange {
update = true
colorRange = h.ColorConfig.ColorRange
}

Check warning on line 286 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L250-L286

Added lines #L250 - L286 were not covered by tests
}

if update {
err := func() error {
m.server.mutex.Lock()
defer m.server.mutex.Unlock()
codec.Width = width
codec.Height = height
codec.Profile = profile
codec.BitDepth = bitDepth
codec.ChromaSubsampling = chromaSubsampling
codec.ColorRange = colorRange
return m.server.generateInitFile()
}()
if err != nil {
return fmt.Errorf("unable to generate init.mp4: %v", err)
}
m.forceSwitch = true

Check warning on line 304 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L289-L304

Added lines #L289 - L304 were not covered by tests
}

forceSwitch := false
if randomAccess && m.forceSwitch {
m.forceSwitch = false
forceSwitch = true
}

Check warning on line 311 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L307-L311

Added lines #L307 - L311 were not covered by tests

return m.segmenter.writeVP9(ntp, pts, frame, randomAccess, forceSwitch)

Check warning on line 313 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L313

Added line #L313 was not covered by tests
}

// WriteH26x writes an H264 or an H265 access unit.
func (m *Muxer) WriteH26x(ntp time.Time, pts time.Duration, au [][]byte) error {
randomAccessPresent := false
randomAccess := false

switch codec := m.VideoTrack.Codec.(type) {
case *codecs.H265:
Expand All @@ -254,7 +329,7 @@ func (m *Muxer) WriteH26x(ntp time.Time, pts time.Duration, au [][]byte) error {

switch typ {
case h265.NALUType_IDR_W_RADL, h265.NALUType_IDR_N_LP, h265.NALUType_CRA_NUT:
randomAccessPresent = true
randomAccess = true

Check warning on line 332 in muxer.go

View check run for this annotation

Codecov / codecov/patch

muxer.go#L332

Added line #L332 was not covered by tests

case h265.NALUType_VPS_NUT:
if !bytes.Equal(vps, nalu) {
Expand Down Expand Up @@ -302,7 +377,7 @@ func (m *Muxer) WriteH26x(ntp time.Time, pts time.Duration, au [][]byte) error {

switch typ {
case h264.NALUTypeIDR:
randomAccessPresent = true
randomAccess = true

case h264.NALUTypeNonIDR:
nonIDRPresent = true
Expand Down Expand Up @@ -335,18 +410,18 @@ func (m *Muxer) WriteH26x(ntp time.Time, pts time.Duration, au [][]byte) error {
m.forceSwitch = true
}

if !randomAccessPresent && !nonIDRPresent {
if !randomAccess && !nonIDRPresent {
return nil
}
}

forceSwitch := false
if randomAccessPresent && m.forceSwitch {
if randomAccess && m.forceSwitch {
m.forceSwitch = false
forceSwitch = true
}

return m.segmenter.writeH26x(ntp, pts, au, randomAccessPresent, forceSwitch)
return m.segmenter.writeH26x(ntp, pts, au, randomAccess, forceSwitch)
}

// WriteOpus writes Opus packets.
Expand Down
1 change: 1 addition & 0 deletions muxer_segmenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
type muxerSegmenter interface {
close()
writeAV1(time.Time, time.Duration, [][]byte, bool, bool) error
writeVP9(time.Time, time.Duration, []byte, bool, bool) error
writeH26x(time.Time, time.Duration, [][]byte, bool, bool) error
writeOpus(time.Time, time.Duration, [][]byte) error
writeMPEG4Audio(time.Time, time.Duration, [][]byte) error
Expand Down
Loading

0 comments on commit a33a05a

Please sign in to comment.