Skip to content

Commit

Permalink
hls: support generating streams with multiple audio tracks (#2728)
Browse files Browse the repository at this point in the history
  • Loading branch information
aler9 committed Oct 3, 2024
1 parent 8960cba commit ff372bc
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 116 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/abema/go-mp4 v1.2.0
github.com/alecthomas/kong v1.2.1
github.com/asticode/go-astits v1.13.0
github.com/bluenviron/gohlslib v1.4.0
github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8
github.com/bluenviron/gortsplib/v4 v4.10.6
github.com/bluenviron/mediacommon v1.12.4
github.com/datarhei/gosrt v0.7.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ github.com/asticode/go-astits v1.13.0 h1:XOgkaadfZODnyZRR5Y0/DWkA9vrkLLPLeeOvDwf
github.com/asticode/go-astits v1.13.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c h1:8XZeJrs4+ZYhJeJ2aZxADI2tGADS15AzIF8MQ8XAhT4=
github.com/benburkert/openpgp v0.0.0-20160410205803-c2471f86866c/go.mod h1:x1vxHcL/9AVzuk5HOloOEPrtJY0MaalYr78afXZ+pWI=
github.com/bluenviron/gohlslib v1.4.0 h1:3a9W1x8eqlxJUKt1sJCunPGtti5ALIY2ik4GU0RVe7E=
github.com/bluenviron/gohlslib v1.4.0/go.mod h1:q5ZElzNw5GRbV1VEI45qkcPbKBco6BP58QEY5HyFsmo=
github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8 h1:OQeYfxJg5otVKa33HWJ63E+IxCJ5Ty0qwCBPD2JcIso=
github.com/bluenviron/gohlslib/v2 v2.0.0-20241003172246-076f27fbe0f8/go.mod h1:DVvQIj+MjYydWuYDCgP+s0/GplDgUSpDNXCA/BVLhu4=
github.com/bluenviron/gortsplib/v4 v4.10.6 h1:KMvVcU21xxQQu1Jqn6D/z/FoIMn+QEKE1dBDWt4aWvg=
github.com/bluenviron/gortsplib/v4 v4.10.6/go.mod h1:/7C8qoGEsIQupuVw8YnXANpqBMNBpZ+51xFreLGiN2g=
github.com/bluenviron/mediacommon v1.12.4 h1:7VrA/W/iDB7VELquXqRjgjzUSJT3llZYgXjFN9WkByo=
Expand Down
2 changes: 1 addition & 1 deletion internal/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"strings"
"time"

"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/v2"
"github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/auth"

Expand Down
2 changes: 1 addition & 1 deletion internal/conf/hls_variant.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"

"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/v2"
)

// HLSVariant is the hlsVariant parameter.
Expand Down
202 changes: 108 additions & 94 deletions internal/protocols/hls/from_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"errors"
"fmt"

"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gohlslib/v2"
"github.com/bluenviron/gohlslib/v2/pkg/codecs"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/asyncwriter"
"github.com/bluenviron/mediamtx/internal/logger"
Expand All @@ -22,186 +22,196 @@ func setupVideoTrack(
stream *stream.Stream,
writer *asyncwriter.Writer,
muxer *gohlslib.Muxer,
) format.Format {
setuppedFormats map[format.Format]struct{},
) {
var videoFormatAV1 *format.AV1
videoMedia := stream.Desc().FindFormat(&videoFormatAV1)

if videoFormatAV1 != nil {
track := &gohlslib.Track{
Codec: &codecs.AV1{},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[videoFormatAV1] = struct{}{}

Check warning on line 36 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L31-L36

Added lines #L31 - L36 were not covered by tests
stream.AddReader(writer, videoMedia, videoFormatAV1, func(u unit.Unit) error {
tunit := u.(*unit.AV1)

if tunit.TU == nil {
return nil
}

err := muxer.WriteAV1(tunit.NTP, tunit.PTS, tunit.TU)
err := muxer.WriteAV1(track, tunit.NTP, tunit.PTS, tunit.TU)

Check warning on line 44 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L44

Added line #L44 was not covered by tests
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

muxer.VideoTrack = &gohlslib.Track{
Codec: &codecs.AV1{},
}
return videoFormatAV1
return

Check warning on line 52 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L52

Added line #L52 was not covered by tests
}

var videoFormatVP9 *format.VP9
videoMedia = stream.Desc().FindFormat(&videoFormatVP9)

if videoFormatVP9 != nil {
track := &gohlslib.Track{
Codec: &codecs.VP9{},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[videoFormatVP9] = struct{}{}

stream.AddReader(writer, videoMedia, videoFormatVP9, func(u unit.Unit) error {
tunit := u.(*unit.VP9)

if tunit.Frame == nil {
return nil
}

err := muxer.WriteVP9(tunit.NTP, tunit.PTS, tunit.Frame)
err := muxer.WriteVP9(track, tunit.NTP, tunit.PTS, tunit.Frame)

Check warning on line 72 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L72

Added line #L72 was not covered by tests
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

muxer.VideoTrack = &gohlslib.Track{
Codec: &codecs.VP9{},
}
return videoFormatVP9
return
}

var videoFormatH265 *format.H265
videoMedia = stream.Desc().FindFormat(&videoFormatH265)

if videoFormatH265 != nil {
vps, sps, pps := videoFormatH265.SafeParams()
track := &gohlslib.Track{
Codec: &codecs.H265{
VPS: vps,
SPS: sps,
PPS: pps,
},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[videoFormatH265] = struct{}{}

Check warning on line 97 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L87-L97

Added lines #L87 - L97 were not covered by tests
stream.AddReader(writer, videoMedia, videoFormatH265, func(u unit.Unit) error {
tunit := u.(*unit.H265)

if tunit.AU == nil {
return nil
}

err := muxer.WriteH265(tunit.NTP, tunit.PTS, tunit.AU)
err := muxer.WriteH265(track, tunit.NTP, tunit.PTS, tunit.AU)

Check warning on line 105 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L105

Added line #L105 was not covered by tests
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

vps, sps, pps := videoFormatH265.SafeParams()

muxer.VideoTrack = &gohlslib.Track{
Codec: &codecs.H265{
VPS: vps,
SPS: sps,
PPS: pps,
},
}
return videoFormatH265
return

Check warning on line 113 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L113

Added line #L113 was not covered by tests
}

var videoFormatH264 *format.H264
videoMedia = stream.Desc().FindFormat(&videoFormatH264)

if videoFormatH264 != nil {
sps, pps := videoFormatH264.SafeParams()
track := &gohlslib.Track{
Codec: &codecs.H264{
SPS: sps,
PPS: pps,
},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[videoFormatH264] = struct{}{}

Check warning on line 129 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L120-L129

Added lines #L120 - L129 were not covered by tests
stream.AddReader(writer, videoMedia, videoFormatH264, func(u unit.Unit) error {
tunit := u.(*unit.H264)

if tunit.AU == nil {
return nil
}

err := muxer.WriteH264(tunit.NTP, tunit.PTS, tunit.AU)
err := muxer.WriteH264(track, tunit.NTP, tunit.PTS, tunit.AU)

Check warning on line 137 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L137

Added line #L137 was not covered by tests
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

sps, pps := videoFormatH264.SafeParams()

muxer.VideoTrack = &gohlslib.Track{
Codec: &codecs.H264{
SPS: sps,
PPS: pps,
},
}
return videoFormatH264
return

Check warning on line 145 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L145

Added line #L145 was not covered by tests
}

return nil
}

func setupAudioTrack(
func setupAudioTracks(
stream *stream.Stream,
writer *asyncwriter.Writer,
muxer *gohlslib.Muxer,
) format.Format {
var audioFormatOpus *format.Opus
audioMedia := stream.Desc().FindFormat(&audioFormatOpus)

if audioFormatOpus != nil {
stream.AddReader(writer, audioMedia, audioFormatOpus, func(u unit.Unit) error {
tunit := u.(*unit.Opus)

err := muxer.WriteOpus(
tunit.NTP,
tunit.PTS,
tunit.Packets)
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

muxer.AudioTrack = &gohlslib.Track{
Codec: &codecs.Opus{
ChannelCount: audioFormatOpus.ChannelCount,
},
}
return audioFormatOpus
}
setuppedFormats map[format.Format]struct{},
) {
for _, media := range stream.Desc().Medias {
for _, forma := range media.Formats {
switch forma := forma.(type) {
case *format.Opus:
track := &gohlslib.Track{
Codec: &codecs.Opus{
ChannelCount: forma.ChannelCount,
},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[forma] = struct{}{}

Check warning on line 165 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L158-L165

Added lines #L158 - L165 were not covered by tests

var audioFormatMPEG4Audio *format.MPEG4Audio
audioMedia = stream.Desc().FindFormat(&audioFormatMPEG4Audio)
stream.AddReader(writer, media, forma, func(u unit.Unit) error {
tunit := u.(*unit.Opus)

Check warning on line 168 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L167-L168

Added lines #L167 - L168 were not covered by tests

if audioFormatMPEG4Audio != nil {
co := audioFormatMPEG4Audio.GetConfig()
if co != nil {
stream.AddReader(writer, audioMedia, audioFormatMPEG4Audio, func(u unit.Unit) error {
tunit := u.(*unit.MPEG4Audio)
err := muxer.WriteOpus(
track,
tunit.NTP,
tunit.PTS,
tunit.Packets)
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

Check warning on line 177 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L170-L177

Added lines #L170 - L177 were not covered by tests

if tunit.AUs == nil {
return nil
})

case *format.MPEG4Audio:
co := forma.GetConfig()
if co != nil {
track := &gohlslib.Track{
Codec: &codecs.MPEG4Audio{
Config: *co,
},
}
muxer.Tracks = append(muxer.Tracks, track)
setuppedFormats[forma] = struct{}{}

stream.AddReader(writer, media, forma, func(u unit.Unit) error {
tunit := u.(*unit.MPEG4Audio)

if tunit.AUs == nil {
return nil
}

Check warning on line 198 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L182-L198

Added lines #L182 - L198 were not covered by tests

err := muxer.WriteMPEG4Audio(
track,
tunit.NTP,
tunit.PTS,
tunit.AUs)
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

Check warning on line 207 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L200-L207

Added lines #L200 - L207 were not covered by tests

return nil

Check warning on line 209 in internal/protocols/hls/from_stream.go

View check run for this annotation

Codecov / codecov/patch

internal/protocols/hls/from_stream.go#L209

Added line #L209 was not covered by tests
})
}

err := muxer.WriteMPEG4Audio(
tunit.NTP,
tunit.PTS,
tunit.AUs)
if err != nil {
return fmt.Errorf("muxer error: %w", err)
}

return nil
})

muxer.AudioTrack = &gohlslib.Track{
Codec: &codecs.MPEG4Audio{
Config: *co,
},
}
return audioFormatMPEG4Audio
}
}

return nil
}

// FromStream maps a MediaMTX stream to a HLS muxer.
Expand All @@ -211,26 +221,30 @@ func FromStream(
muxer *gohlslib.Muxer,
l logger.Writer,
) error {
videoFormat := setupVideoTrack(
setuppedFormats := make(map[format.Format]struct{})

setupVideoTrack(
stream,
writer,
muxer,
setuppedFormats,
)

audioFormat := setupAudioTrack(
setupAudioTracks(
stream,
writer,
muxer,
setuppedFormats,
)

if videoFormat == nil && audioFormat == nil {
if len(muxer.Tracks) == 0 {
return ErrNoSupportedCodecs
}

n := 1
for _, media := range stream.Desc().Medias {
for _, forma := range media.Formats {
if forma != videoFormat && forma != audioFormat {
if _, ok := setuppedFormats[forma]; !ok {
l.Log(logger.Warn, "skipping track %d (%s)", n, forma.Codec())
}
n++
Expand Down
6 changes: 4 additions & 2 deletions internal/protocols/hls/from_stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"testing"

"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/v2"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/asyncwriter"
Expand Down Expand Up @@ -32,7 +32,9 @@ func TestFromStreamNoSupportedCodecs(t *testing.T) {
t.Error("should not happen")
})

err = FromStream(stream, writer, nil, l)
m := &gohlslib.Muxer{}

err = FromStream(stream, writer, m, l)
require.Equal(t, ErrNoSupportedCodecs, err)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/protocols/hls/to_stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package hls
import (
"time"

"github.com/bluenviron/gohlslib"
"github.com/bluenviron/gohlslib/pkg/codecs"
"github.com/bluenviron/gohlslib/v2"
"github.com/bluenviron/gohlslib/v2/pkg/codecs"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/mediamtx/internal/stream"
Expand Down
Loading

0 comments on commit ff372bc

Please sign in to comment.