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

support muxing and reading VP9 tracks #71

Merged
merged 1 commit into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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 @@
// 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 @@
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 @@
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 @@
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 @@
}

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 @@
}

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 @@
"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 @@
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 @@
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 @@
}

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 @@

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 @@

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

case h264.NALUTypeNonIDR:
nonIDRPresent = true
Expand Down Expand Up @@ -335,18 +410,18 @@
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
Loading