diff --git a/client.go b/client.go index 2eb2cfa..7d16d26 100644 --- a/client.go +++ b/client.go @@ -48,23 +48,23 @@ type ClientOnRequestFunc func(*http.Request) type ClientOnTracksFunc func([]*Track) error // ClientOnDataAV1Func is the prototype of the function passed to OnDataAV1(). -type ClientOnDataAV1Func func(pts time.Duration, tu [][]byte) +type ClientOnDataAV1Func func(pts int64, tu [][]byte) // ClientOnDataVP9Func is the prototype of the function passed to OnDataVP9(). -type ClientOnDataVP9Func func(pts time.Duration, frame []byte) +type ClientOnDataVP9Func func(pts int64, frame []byte) // ClientOnDataH26xFunc is the prototype of the function passed to OnDataH26x(). -type ClientOnDataH26xFunc func(pts time.Duration, dts time.Duration, au [][]byte) +type ClientOnDataH26xFunc func(pts int64, dts int64, au [][]byte) // ClientOnDataMPEG4AudioFunc is the prototype of the function passed to OnDataMPEG4Audio(). -type ClientOnDataMPEG4AudioFunc func(pts time.Duration, aus [][]byte) +type ClientOnDataMPEG4AudioFunc func(pts int64, aus [][]byte) // ClientOnDataOpusFunc is the prototype of the function passed to OnDataOpus(). -type ClientOnDataOpusFunc func(pts time.Duration, packets [][]byte) +type ClientOnDataOpusFunc func(pts int64, packets [][]byte) type clientOnStreamTracksFunc func(ctx context.Context, isLeading bool, tracks []*Track) ([]*clientTrack, bool) -type clientOnDataFunc func(pts time.Duration, dts time.Duration, data [][]byte) +type clientOnDataFunc func(pts int64, dts int64, data [][]byte) func clientAbsoluteURL(base *url.URL, relative string) (*url.URL, error) { u, err := url.Parse(relative) @@ -186,35 +186,35 @@ func (c *Client) Wait() chan error { // OnDataAV1 sets a callback that is called when data from an AV1 track is received. func (c *Client) OnDataAV1(track *Track, cb ClientOnDataAV1Func) { - c.tracks[track].onData = func(pts time.Duration, _ time.Duration, data [][]byte) { + c.tracks[track].onData = func(pts int64, _ int64, data [][]byte) { cb(pts, data) } } // OnDataVP9 sets a callback that is called when data from a VP9 track is received. func (c *Client) OnDataVP9(track *Track, cb ClientOnDataVP9Func) { - c.tracks[track].onData = func(pts time.Duration, _ time.Duration, data [][]byte) { + c.tracks[track].onData = func(pts int64, _ int64, data [][]byte) { cb(pts, data[0]) } } // OnDataH26x sets a callback that is called when data from an H26x track is received. func (c *Client) OnDataH26x(track *Track, cb ClientOnDataH26xFunc) { - c.tracks[track].onData = func(pts time.Duration, dts time.Duration, data [][]byte) { + c.tracks[track].onData = func(pts int64, dts int64, data [][]byte) { cb(pts, dts, data) } } // OnDataMPEG4Audio sets a callback that is called when data from a MPEG-4 Audio track is received. func (c *Client) OnDataMPEG4Audio(track *Track, cb ClientOnDataMPEG4AudioFunc) { - c.tracks[track].onData = func(pts time.Duration, _ time.Duration, data [][]byte) { + c.tracks[track].onData = func(pts int64, _ int64, data [][]byte) { cb(pts, data) } } // OnDataOpus sets a callback that is called when data from an Opus track is received. func (c *Client) OnDataOpus(track *Track, cb ClientOnDataOpusFunc) { - c.tracks[track].onData = func(pts time.Duration, _ time.Duration, data [][]byte) { + c.tracks[track].onData = func(pts int64, _ int64, data [][]byte) { cb(pts, data) } } @@ -267,7 +267,7 @@ func (c *Client) setTracks(tracks []*Track) (map[*Track]*clientTrack, error) { for _, track := range tracks { c.tracks[track] = &clientTrack{ track: track, - onData: func(_, _ time.Duration, _ [][]byte) {}, + onData: func(_, _ int64, _ [][]byte) {}, } } diff --git a/client_stream_processor_fmp4.go b/client_stream_processor_fmp4.go index 56198aa..cb9af2c 100644 --- a/client_stream_processor_fmp4.go +++ b/client_stream_processor_fmp4.go @@ -77,9 +77,11 @@ func (p *clientStreamProcessorFMP4) run(ctx context.Context) error { p.leadingTrackID = fmp4PickLeadingTrack(&p.init) tracks := make([]*Track, len(p.init.Tracks)) + for i, track := range p.init.Tracks { tracks[i] = &Track{ - Codec: codecs.FromFMP4(track.Codec), + Codec: codecs.FromFMP4(track.Codec), + ClockRate: int(track.TimeScale), } } @@ -121,26 +123,28 @@ func (p *clientStreamProcessorFMP4) processSegment(ctx context.Context, seg *seg ntpAvailable := false var ntpAbsolute time.Time - var ntpRelative time.Duration + var ntpRelative int64 + var leadingClockRate int - if p.trackProcessors == nil || seg.dateTime != nil { - partTrack := findFirstPartTrackOfLeadingTrack(parts, p.leadingTrackID) - if partTrack == nil { - return fmt.Errorf("could not find data of leading track") - } + partTrack := findFirstPartTrackOfLeadingTrack(parts, p.leadingTrackID) + if partTrack == nil { + return fmt.Errorf("could not find data of leading track") + } - if p.trackProcessors == nil { - err := p.initializeTrackProcessors(ctx, partTrack) - if err != nil { - return err - } + if p.trackProcessors == nil { + err := p.initializeTrackProcessors(ctx, partTrack) + if err != nil { + return err } + } - if seg.dateTime != nil { - ntpAvailable = true - ntpAbsolute = *seg.dateTime - ntpRelative = p.timeConv.convert(partTrack.BaseTime, p.timeConv.leadingTimeScale) - } + leadingTrackProc := p.trackProcessors[partTrack.ID] + leadingClockRate = leadingTrackProc.track.track.ClockRate + + if seg.dateTime != nil { + ntpAvailable = true + ntpAbsolute = *seg.dateTime + ntpRelative = p.timeConv.convert(int64(partTrack.BaseTime), leadingClockRate) } partTrackCount := 0 @@ -155,7 +159,7 @@ func (p *clientStreamProcessorFMP4) processSegment(ctx context.Context, seg *seg err := trackProc.push(ctx, &procEntryFMP4{ ntpAvailable: ntpAvailable, ntpAbsolute: ntpAbsolute, - ntpRelative: ntpRelative, + ntpRelative: multiplyAndDivide(ntpRelative, int64(trackProc.track.track.ClockRate), int64(leadingClockRate)), partTrack: partTrack, }) if err != nil { @@ -196,8 +200,8 @@ func (p *clientStreamProcessorFMP4) initializeTrackProcessors( timeScale := findTimeScaleOfLeadingTrack(p.init.Tracks, p.leadingTrackID) p.timeConv = &clientTimeConvFMP4{ - leadingTimeScale: timeScale, - initialBaseTime: partTrack.BaseTime, + leadingTimeScale: int64(timeScale), + leadingBaseTime: int64(partTrack.BaseTime), } p.timeConv.initialize() @@ -219,7 +223,6 @@ func (p *clientStreamProcessorFMP4) initializeTrackProcessors( for i, track := range p.clientStreamTracks { trackProc := &clientTrackProcessorFMP4{ track: track, - timeScale: p.init.Tracks[i].TimeScale, timeConv: p.timeConv, onPartTrackProcessed: p.onPartTrackProcessed, } diff --git a/client_stream_processor_mpegts.go b/client_stream_processor_mpegts.go index d8e4d9e..67be81e 100644 --- a/client_stream_processor_mpegts.go +++ b/client_stream_processor_mpegts.go @@ -162,9 +162,11 @@ func (p *clientStreamProcessorMPEGTS) initializeReader(ctx context.Context, firs leadingTrackID := mpegtsPickLeadingTrack(p.reader.Tracks()) tracks := make([]*Track, len(p.reader.Tracks())) + for i, mpegtsTrack := range p.reader.Tracks() { tracks[i] = &Track{ - Codec: codecs.FromMPEGTS(mpegtsTrack.Codec), + Codec: codecs.FromMPEGTS(mpegtsTrack.Codec), + ClockRate: 90000, } } @@ -180,7 +182,7 @@ func (p *clientStreamProcessorMPEGTS) initializeReader(ctx context.Context, firs ntpAvailable := false var ntpAbsolute time.Time - var ntpRelative time.Duration + var ntpRelative int64 for i, mpegtsTrack := range p.reader.Tracks() { track := p.clientStreamTracks[i] @@ -225,7 +227,9 @@ func (p *clientStreamProcessorMPEGTS) initializeReader(ctx context.Context, firs ntp := time.Time{} if ntpAvailable { - ntp = ntpAbsolute.Add(dts - ntpRelative) + diff := dts - ntpRelative + diffDur := timestampToDuration(diff, 90000) + ntp = ntpAbsolute.Add(diffDur) } return trackProc.push(ctx, &procEntryMPEGTS{ diff --git a/client_test.go b/client_test.go index e070984..c03c818 100644 --- a/client_test.go +++ b/client_test.go @@ -408,12 +408,20 @@ func TestClient(t *testing.T) { pps = testPPS } + var audioClockRate int + if format == "fmp4" { + audioClockRate = 44100 + } else { + audioClockRate = 90000 + } + require.Equal(t, []*Track{ { Codec: &codecs.H264{ SPS: sps, PPS: pps, }, + ClockRate: 90000, }, { Codec: &codecs.MPEG4Audio{ @@ -423,14 +431,15 @@ func TestClient(t *testing.T) { ChannelCount: 2, }, }, + ClockRate: audioClockRate, }, }, tracks) - c.OnDataH26x(tracks[0], func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(tracks[0], func(pts int64, dts int64, au [][]byte) { switch videoCount { case 0: - require.Equal(t, time.Duration(0), dts) - require.Equal(t, 2*time.Second, pts) + require.Equal(t, int64(0), dts) + require.Equal(t, int64(2*90000), pts) require.Equal(t, [][]byte{ {7, 1, 2, 3}, {8}, @@ -441,16 +450,16 @@ func TestClient(t *testing.T) { require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 2, 0, time.UTC), ntp) case 1: - require.Equal(t, 33333333*time.Nanosecond, dts) - require.Equal(t, 2*time.Second+33333333*time.Nanosecond, pts) + require.Equal(t, int64(3000), dts) + require.Equal(t, int64(2*90000+3000), pts) require.Equal(t, [][]byte{{1, 4, 5, 6}}, au) ntp, ok := c.AbsoluteTime(tracks[0]) require.Equal(t, true, ok) require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 2, 33333333, time.UTC), ntp) case 2: - require.Equal(t, 66666666*time.Nanosecond, dts) - require.Equal(t, 66666666*time.Nanosecond, pts) + require.Equal(t, int64(6000), dts) + require.Equal(t, int64(6000), pts) require.Equal(t, [][]byte{{4}}, au) _, ok := c.AbsoluteTime(tracks[0]) require.Equal(t, false, ok) @@ -459,17 +468,17 @@ func TestClient(t *testing.T) { videoCount++ }) - c.OnDataMPEG4Audio(tracks[1], func(pts time.Duration, aus [][]byte) { + c.OnDataMPEG4Audio(tracks[1], func(pts int64, aus [][]byte) { switch audioCount { case 0: - require.Equal(t, 0*time.Second, pts) + require.Equal(t, int64(0), pts) require.Equal(t, [][]byte{{1, 2, 3, 4}}, aus) ntp, ok := c.AbsoluteTime(tracks[1]) require.Equal(t, true, ok) require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 2, 0, time.UTC), ntp) case 1: - require.Equal(t, 33333333*time.Nanosecond, pts) + require.Equal(t, int64(0.0333336*float64(tracks[1].ClockRate)), pts) require.Equal(t, [][]byte{{5, 6, 7, 8}}, aus) ntp, ok := c.AbsoluteTime(tracks[1]) require.Equal(t, true, ok) @@ -516,6 +525,7 @@ func TestClientFMP4MultiRenditions(t *testing.T) { "#EXT-X-INDEPENDENT-SEGMENTS\n" + "#EXT-X-TARGETDURATION:2\n" + "#EXT-X-MAP:URI=\"init_video.mp4\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + "#EXTINF:2,\n" + "segment_video.mp4\n" + "#EXT-X-ENDLIST\n")) @@ -529,6 +539,7 @@ func TestClientFMP4MultiRenditions(t *testing.T) { "#EXT-X-INDEPENDENT-SEGMENTS\n" + "#EXT-X-TARGETDURATION:2\n" + "#EXT-X-MAP:URI=\"init_audio.mp4\"\n" + + "#EXT-X-PROGRAM-DATE-TIME:2015-02-05T01:02:02Z\n" + "#EXTINF:2,\n" + "segment_audio.mp4\n" + "#EXT-X-ENDLIST")) @@ -589,7 +600,8 @@ func TestClientFMP4MultiRenditions(t *testing.T) { err := mp4ToWriter(&fmp4.Part{ Tracks: []*fmp4.PartTrack{ { - ID: 1, + ID: 1, + BaseTime: 3000, Samples: []*fmp4.PartSample{{ Duration: 44100, Payload: []byte{1, 2, 3, 4}, @@ -627,30 +639,38 @@ func TestClientFMP4MultiRenditions(t *testing.T) { SPS: testSPS, PPS: testPPS, }, + ClockRate: 90000, }, { Codec: &codecs.MPEG4Audio{ Config: testConfig, }, + ClockRate: 44100, }, }, tracks) - c.OnDataH26x(tracks[0], func(pts time.Duration, dts time.Duration, au [][]byte) { - require.Equal(t, 3*time.Second, pts) - require.Equal(t, time.Duration(0), dts) + c.OnDataH26x(tracks[0], func(pts int64, dts int64, au [][]byte) { + require.Equal(t, int64(3*90000), pts) + require.Equal(t, int64(0), dts) require.Equal(t, [][]byte{ {7, 1, 2, 3}, {8}, {5}, }, au) + ntp, ok := c.AbsoluteTime(tracks[0]) + require.Equal(t, true, ok) + require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 2, 0, time.UTC), ntp) packetRecv <- struct{}{} }) - c.OnDataMPEG4Audio(tracks[1], func(pts time.Duration, aus [][]byte) { - require.Equal(t, 0*time.Second, pts) + c.OnDataMPEG4Audio(tracks[1], func(pts int64, aus [][]byte) { + require.Equal(t, int64(3000), pts) require.Equal(t, [][]byte{ {1, 2, 3, 4}, }, aus) + ntp, ok := c.AbsoluteTime(tracks[1]) + require.Equal(t, true, ok) + require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 2, 34693877, time.UTC), ntp) packetRecv <- struct{}{} }) @@ -820,17 +840,18 @@ func TestClientFMP4LowLatency(t *testing.T) { SPS: testSPS, PPS: testPPS, }, + ClockRate: 90000, }, }, tracks) - c.OnDataH26x(tracks[0], func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(tracks[0], func(pts int64, dts int64, au [][]byte) { switch recvCount { case 0: ntp, ok := c.AbsoluteTime(tracks[0]) require.Equal(t, true, ok) require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 4, 0, time.UTC), ntp) - require.Equal(t, 0*time.Second, pts) - require.Equal(t, time.Duration(0), dts) + require.Equal(t, int64(0), pts) + require.Equal(t, int64(0), dts) require.Equal(t, [][]byte{ {7, 1, 2, 3}, {8}, @@ -841,16 +862,16 @@ func TestClientFMP4LowLatency(t *testing.T) { ntp, ok := c.AbsoluteTime(tracks[0]) require.Equal(t, true, ok) require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 4, 33333333, time.UTC), ntp) - require.Equal(t, 33333333*time.Nanosecond, pts) - require.Equal(t, 33333333*time.Nanosecond, dts) + require.Equal(t, int64(3000), pts) + require.Equal(t, int64(3000), dts) require.Equal(t, [][]byte{{1, 4, 5, 6}}, au) case 2: ntp, ok := c.AbsoluteTime(tracks[0]) require.Equal(t, true, ok) require.Equal(t, time.Date(2015, time.February, 5, 1, 2, 4, 66666666, time.UTC), ntp) - require.Equal(t, 66666666*time.Nanosecond, pts) - require.Equal(t, 66666666*time.Nanosecond, dts) + require.Equal(t, int64(6000), pts) + require.Equal(t, int64(6000), dts) require.Equal(t, [][]byte{{1, 7, 8, 9}}, au) default: diff --git a/client_time_conv_fmp4.go b/client_time_conv_fmp4.go index cab913f..dbf547b 100644 --- a/client_time_conv_fmp4.go +++ b/client_time_conv_fmp4.go @@ -1,34 +1,13 @@ package gohlslib -import ( - "time" -) - -func durationGoToMp4(v time.Duration, timeScale uint32) uint64 { - timeScale64 := uint64(timeScale) - secs := v / time.Second - dec := v % time.Second - return uint64(secs)*timeScale64 + uint64(dec)*timeScale64/uint64(time.Second) -} - -func durationMp4ToGo(v uint64, timeScale uint32) time.Duration { - timeScale64 := uint64(timeScale) - secs := v / timeScale64 - dec := v % timeScale64 - return time.Duration(secs)*time.Second + time.Duration(dec)*time.Second/time.Duration(timeScale64) -} - type clientTimeConvFMP4 struct { - leadingTimeScale uint32 - initialBaseTime uint64 - - startDTS time.Duration + leadingTimeScale int64 + leadingBaseTime int64 } func (ts *clientTimeConvFMP4) initialize() { - ts.startDTS = durationMp4ToGo(ts.initialBaseTime, ts.leadingTimeScale) } -func (ts *clientTimeConvFMP4) convert(v uint64, timeScale uint32) time.Duration { - return durationMp4ToGo(v, timeScale) - ts.startDTS +func (ts *clientTimeConvFMP4) convert(v int64, clockRate int) int64 { + return v - multiplyAndDivide(ts.leadingBaseTime, int64(clockRate), ts.leadingTimeScale) } diff --git a/client_time_conv_mpegts.go b/client_time_conv_mpegts.go index 06d5516..a3614df 100644 --- a/client_time_conv_mpegts.go +++ b/client_time_conv_mpegts.go @@ -2,7 +2,6 @@ package gohlslib import ( "sync" - "time" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" ) @@ -10,15 +9,15 @@ import ( type clientTimeConvMPEGTS struct { startDTS int64 - td *mpegts.TimeDecoder + td *mpegts.TimeDecoder2 mutex sync.Mutex } func (ts *clientTimeConvMPEGTS) initialize() { - ts.td = mpegts.NewTimeDecoder(ts.startDTS) + ts.td = mpegts.NewTimeDecoder2(ts.startDTS) } -func (ts *clientTimeConvMPEGTS) convert(v int64) time.Duration { +func (ts *clientTimeConvMPEGTS) convert(v int64) int64 { ts.mutex.Lock() defer ts.mutex.Unlock() return ts.td.Decode(v) diff --git a/client_track.go b/client_track.go index 9ed3c3a..d59a13b 100644 --- a/client_track.go +++ b/client_track.go @@ -22,8 +22,8 @@ func (t *clientTrack) absoluteTime() (time.Time, bool) { func (t *clientTrack) handleData( ctx context.Context, - pts time.Duration, - dts time.Duration, + pts int64, + dts int64, ntp time.Time, data [][]byte, ) error { @@ -34,8 +34,9 @@ func (t *clientTrack) handleData( // synchronize time elapsed := time.Since(t.startRTC) - if dts > elapsed { - diff := dts - elapsed + dtsDuration := timestampToDuration(dts, t.track.ClockRate) + if dtsDuration > elapsed { + diff := dtsDuration - elapsed if diff > clientMaxDTSRTCDiff { return fmt.Errorf("difference between DTS and RTC is too big") } diff --git a/client_track_processor_fmp4.go b/client_track_processor_fmp4.go index 35d1a6c..5e53a9d 100644 --- a/client_track_processor_fmp4.go +++ b/client_track_processor_fmp4.go @@ -12,13 +12,12 @@ import ( type procEntryFMP4 struct { ntpAvailable bool ntpAbsolute time.Time - ntpRelative time.Duration + ntpRelative int64 partTrack *fmp4.PartTrack } type clientTrackProcessorFMP4 struct { track *clientTrack - timeScale uint32 timeConv *clientTimeConvFMP4 onPartTrackProcessed func(ctx context.Context) @@ -77,7 +76,7 @@ func (t *clientTrackProcessorFMP4) run(ctx context.Context) error { } func (t *clientTrackProcessorFMP4) process(ctx context.Context, entry *procEntryFMP4) error { - rawDTS := entry.partTrack.BaseTime + rawDTS := int64(entry.partTrack.BaseTime) for _, sample := range entry.partTrack.Samples { data, err := t.decodePayload(sample) @@ -85,13 +84,16 @@ func (t *clientTrackProcessorFMP4) process(ctx context.Context, entry *procEntry return err } - pts := t.timeConv.convert(rawDTS+uint64(sample.PTSOffset), t.timeScale) - dts := t.timeConv.convert(rawDTS, t.timeScale) - rawDTS += uint64(sample.Duration) + pts := t.timeConv.convert(rawDTS+int64(sample.PTSOffset), t.track.track.ClockRate) + dts := t.timeConv.convert(rawDTS, t.track.track.ClockRate) + rawDTS += int64(sample.Duration) ntp := time.Time{} if entry.ntpAvailable { - ntp = entry.ntpAbsolute.Add(dts - entry.ntpRelative) + trackNTPRelative := multiplyAndDivide(entry.ntpRelative, int64(t.track.track.ClockRate), t.timeConv.leadingTimeScale) + diff := dts - trackNTPRelative + diffDur := timestampToDuration(diff, t.track.track.ClockRate) + ntp = entry.ntpAbsolute.Add(diffDur) } err = t.track.handleData(ctx, pts, dts, ntp, data) diff --git a/client_track_processor_mpegts.go b/client_track_processor_mpegts.go index cad060c..dce6ac4 100644 --- a/client_track_processor_mpegts.go +++ b/client_track_processor_mpegts.go @@ -7,8 +7,8 @@ import ( ) type procEntryMPEGTS struct { - pts time.Duration - dts time.Duration + pts int64 + dts int64 ntp time.Time data [][]byte } diff --git a/examples/client-absolute-timestamp/main.go b/examples/client-absolute-timestamp/main.go index cb47630..3497678 100644 --- a/examples/client-absolute-timestamp/main.go +++ b/examples/client-absolute-timestamp/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "time" "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gohlslib/v2/pkg/codecs" @@ -28,28 +27,28 @@ func main() { // set a callback that is called when data is received switch track.Codec.(type) { case *codecs.AV1: - c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) { + c.OnDataAV1(track, func(pts int64, tu [][]byte) { ntp, ntpAvailable := c.AbsoluteTime(ttrack) log.Printf("received data from track %T, pts = %v, ntp available = %v, ntp = %v\n", ttrack.Codec, pts, ntpAvailable, ntp) }) case *codecs.H264, *codecs.H265: - c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(track, func(pts int64, dts int64, au [][]byte) { ntp, ntpAvailable := c.AbsoluteTime(ttrack) log.Printf("received data from track %T, pts = %v, ntp available = %v, ntp = %v\n", ttrack.Codec, pts, ntpAvailable, ntp) }) case *codecs.MPEG4Audio: - c.OnDataMPEG4Audio(track, func(pts time.Duration, aus [][]byte) { + c.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) { ntp, ntpAvailable := c.AbsoluteTime(ttrack) log.Printf("received data from track %T, pts = %v, ntp available = %v, ntp = %v\n", ttrack.Codec, pts, ntpAvailable, ntp) }) case *codecs.Opus: - c.OnDataOpus(track, func(pts time.Duration, packets [][]byte) { + c.OnDataOpus(track, func(pts int64, packets [][]byte) { ntp, ntpAvailable := c.AbsoluteTime(ttrack) log.Printf("received data from track %T, pts = %v, ntp available = %v, ntp = %v\n", ttrack.Codec, pts, ntpAvailable, ntp) diff --git a/examples/client-codec-h264-convert-to-jpeg/main.go b/examples/client-codec-h264-convert-to-jpeg/main.go index 14bcdc4..745075f 100644 --- a/examples/client-codec-h264-convert-to-jpeg/main.go +++ b/examples/client-codec-h264-convert-to-jpeg/main.go @@ -80,7 +80,7 @@ func main() { saveCount := 0 // set a callback that is called when data is received - c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(track, func(pts int64, dts int64, au [][]byte) { log.Printf("received access unit with pts = %v\n", pts) for _, nalu := range au { diff --git a/examples/client-codec-h264-save-to-disk/main.go b/examples/client-codec-h264-save-to-disk/main.go index ddb3caf..db9c213 100644 --- a/examples/client-codec-h264-save-to-disk/main.go +++ b/examples/client-codec-h264-save-to-disk/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "log" - "time" "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gohlslib/v2/pkg/codecs" @@ -49,7 +48,7 @@ func main() { } // set a callback that is called when data is received - c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(track, func(pts int64, dts int64, au [][]byte) { log.Printf("received access unit with pts = %v\n", pts) // send data to the MPEG-TS muxer diff --git a/examples/client-codec-h264-save-to-disk/mpegts_muxer.go b/examples/client-codec-h264-save-to-disk/mpegts_muxer.go index 62d03fb..475b8e8 100644 --- a/examples/client-codec-h264-save-to-disk/mpegts_muxer.go +++ b/examples/client-codec-h264-save-to-disk/mpegts_muxer.go @@ -3,16 +3,11 @@ package main import ( "bufio" "os" - "time" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" ) -func durationGoToMPEGTS(v time.Duration) int64 { - return int64(v.Seconds() * 90000) -} - // mpegtsMuxer allows to save a H264 stream into a MPEG-TS file. type mpegtsMuxer struct { fileName string @@ -23,7 +18,7 @@ type mpegtsMuxer struct { b *bufio.Writer w *mpegts.Writer track *mpegts.Track - dtsExtractor *h264.DTSExtractor + dtsExtractor *h264.DTSExtractor2 } // initialize initializes a mpegtsMuxer. @@ -51,7 +46,7 @@ func (e *mpegtsMuxer) close() { } // writeH264 writes a H264 access unit into MPEG-TS. -func (e *mpegtsMuxer) writeH264(au [][]byte, pts time.Duration) error { +func (e *mpegtsMuxer) writeH264(au [][]byte, pts int64) error { var filteredAU [][]byte nonIDRPresent := false @@ -92,22 +87,19 @@ func (e *mpegtsMuxer) writeH264(au [][]byte, pts time.Duration) error { au = append([][]byte{e.sps, e.pps}, au...) } - var dts time.Duration - if e.dtsExtractor == nil { // skip samples silently until we find one with a IDR if !idrPresent { return nil } - e.dtsExtractor = h264.NewDTSExtractor() + e.dtsExtractor = h264.NewDTSExtractor2() } - var err error - dts, err = e.dtsExtractor.Extract(au, pts) + dts, err := e.dtsExtractor.Extract(au, pts) if err != nil { return err } // encode into MPEG-TS - return e.w.WriteH264(e.track, durationGoToMPEGTS(pts), durationGoToMPEGTS(dts), idrPresent, au) + return e.w.WriteH264(e.track, pts, dts, idrPresent, au) } diff --git a/examples/client-codec-mpeg4audio-save-to-disk/main.go b/examples/client-codec-mpeg4audio-save-to-disk/main.go index 867be96..05e1126 100644 --- a/examples/client-codec-mpeg4audio-save-to-disk/main.go +++ b/examples/client-codec-mpeg4audio-save-to-disk/main.go @@ -14,6 +14,12 @@ import ( // 2. check if there's a MPEG-4 audio track // 2. save the MPEG-4 audio track to disk in MPEG-TS format +func multiplyAndDivide(v, m, d int64) int64 { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + func findMPEG4AudioTrack(tracks []*gohlslib.Track) *gohlslib.Track { for _, track := range tracks { if _, ok := track.Codec.(*codecs.MPEG4Audio); ok { @@ -34,7 +40,7 @@ func main() { // find the MPEG-4 Audio track track := findMPEG4AudioTrack(tracks) if track == nil { - return fmt.Errorf("H264 track not found") + return fmt.Errorf("MPEG-4 audio track not found") } // create the MPEG-TS muxer @@ -48,11 +54,11 @@ func main() { } // set a callback that is called when data is received - c.OnDataMPEG4Audio(track, func(pts time.Duration, aus [][]byte) { + c.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) { log.Printf("received access unit with pts = %v\n", pts) // send data to the MPEG-TS muxer - err := m.writeMPEG4Audio(aus, pts) + err := m.writeMPEG4Audio(aus, multiplyAndDivide(int64(pts), 90000, int64(time.Second))) if err != nil { panic(err) } diff --git a/examples/client-codec-mpeg4audio-save-to-disk/mpegts_muxer.go b/examples/client-codec-mpeg4audio-save-to-disk/mpegts_muxer.go index 347ffd1..f5955be 100644 --- a/examples/client-codec-mpeg4audio-save-to-disk/mpegts_muxer.go +++ b/examples/client-codec-mpeg4audio-save-to-disk/mpegts_muxer.go @@ -3,16 +3,11 @@ package main import ( "bufio" "os" - "time" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" ) -func durationGoToMPEGTS(v time.Duration) int64 { - return int64(v.Seconds() * 90000) -} - // mpegtsMuxer allows to save a MPEG4-audio stream into a MPEG-TS file. type mpegtsMuxer struct { fileName string @@ -51,6 +46,6 @@ func (e *mpegtsMuxer) close() { } // writeMPEG4Audio writes MPEG-4 audio access units into MPEG-TS. -func (e *mpegtsMuxer) writeMPEG4Audio(aus [][]byte, pts time.Duration) error { - return e.w.WriteMPEG4Audio(e.track, durationGoToMPEGTS(pts), aus) +func (e *mpegtsMuxer) writeMPEG4Audio(aus [][]byte, pts int64) error { + return e.w.WriteMPEG4Audio(e.track, pts, aus) } diff --git a/examples/client/main.go b/examples/client/main.go index 13d9a81..3c432a7 100644 --- a/examples/client/main.go +++ b/examples/client/main.go @@ -2,7 +2,6 @@ package main import ( "log" - "time" "github.com/bluenviron/gohlslib/v2" "github.com/bluenviron/gohlslib/v2/pkg/codecs" @@ -26,22 +25,22 @@ func main() { // set a callback that is called when data is received switch track.Codec.(type) { case *codecs.AV1: - c.OnDataAV1(track, func(pts time.Duration, tu [][]byte) { + c.OnDataAV1(track, func(pts int64, tu [][]byte) { log.Printf("received data from track %T, pts = %v\n", ttrack.Codec, pts) }) case *codecs.H264, *codecs.H265: - c.OnDataH26x(track, func(pts time.Duration, dts time.Duration, au [][]byte) { + c.OnDataH26x(track, func(pts int64, dts int64, au [][]byte) { log.Printf("received data from track %T, pts = %v\n", ttrack.Codec, pts) }) case *codecs.MPEG4Audio: - c.OnDataMPEG4Audio(track, func(pts time.Duration, aus [][]byte) { + c.OnDataMPEG4Audio(track, func(pts int64, aus [][]byte) { log.Printf("received data from track %T, pts = %v\n", ttrack.Codec, pts) }) case *codecs.Opus: - c.OnDataOpus(track, func(pts time.Duration, packets [][]byte) { + c.OnDataOpus(track, func(pts int64, packets [][]byte) { log.Printf("received data from track %T, pts = %v\n", ttrack.Codec, pts) }) } diff --git a/examples/muxer/main.go b/examples/muxer/main.go index dd2c3b9..f379d44 100644 --- a/examples/muxer/main.go +++ b/examples/muxer/main.go @@ -2,6 +2,7 @@ package main import ( _ "embed" + "fmt" "log" "net" "net/http" @@ -20,6 +21,15 @@ import ( //go:embed index.html var index []byte +func findH264Track(r *mpegts.Reader) *mpegts.Track { + for _, track := range r.Tracks() { + if _, ok := track.Codec.(*mpegts.CodecH264); ok { + return track + } + } + return nil +} + // handleIndex wraps an HTTP handler and serves the home page func handleIndex(wrapped http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { @@ -36,7 +46,8 @@ func handleIndex(wrapped http.HandlerFunc) http.HandlerFunc { func main() { videoTrack := &gohlslib.Track{ - Codec: &codecs.H264{}, + Codec: &codecs.H264{}, + ClockRate: 90000, } // create the HLS muxer @@ -75,34 +86,28 @@ func main() { panic(err) } - var timeDec *mpegts.TimeDecoder - // find the H264 track - found := false - for _, track := range r.Tracks() { - if _, ok := track.Codec.(*mpegts.CodecH264); ok { - // setup a callback that is called once a H264 access unit is received - r.OnDataH264(track, func(rawPTS int64, _ int64, au [][]byte) error { - // decode the time - if timeDec == nil { - timeDec = mpegts.NewTimeDecoder(rawPTS) - } - pts := timeDec.Decode(rawPTS) - - // pass the access unit to the HLS muxer - log.Printf("visit http://localhost:8080 - encoding access unit with PTS = %v", pts) - mux.WriteH264(videoTrack, time.Now(), pts, au) - - return nil - }) - found = true - break - } + track := findH264Track(r) + if track == nil { + panic(fmt.Errorf("H264 track not found")) } - if !found { - panic("H264 track not found") - } + var timeDec *mpegts.TimeDecoder2 + + // setup a callback that is called when a H264 access unit is received + r.OnDataH264(track, func(rawPTS int64, _ int64, au [][]byte) error { + // decode the time + if timeDec == nil { + timeDec = mpegts.NewTimeDecoder2(rawPTS) + } + pts := timeDec.Decode(rawPTS) + + // pass the access unit to the HLS muxer + log.Printf("visit http://localhost:8080 - encoding access unit with PTS = %v", pts) + mux.WriteH264(videoTrack, time.Now(), pts, au) + + return nil + }) // read from the MPEG-TS stream for { diff --git a/go.mod b/go.mod index 9be7081..1a9602b 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/asticode/go-astits v1.13.0 - github.com/bluenviron/mediacommon v1.12.4 + github.com/bluenviron/mediacommon v1.12.5-0.20241006182151-6b36e8902699 github.com/stretchr/testify v1.9.0 ) diff --git a/go.sum b/go.sum index 7b95c7c..f76a6af 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,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/bluenviron/mediacommon v1.12.4 h1:7VrA/W/iDB7VELquXqRjgjzUSJT3llZYgXjFN9WkByo= github.com/bluenviron/mediacommon v1.12.4/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec= +github.com/bluenviron/mediacommon v1.12.5-0.20241006182151-6b36e8902699 h1:B7Ae/628il4hCNu1SaAysFa2y/1cLh+DhPIBHKI0oeg= +github.com/bluenviron/mediacommon v1.12.5-0.20241006182151-6b36e8902699/go.mod h1:HDyW2CzjvhYJXtdxstdFPio3G0qSocPhqkhUt/qffec= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/muxer.go b/muxer.go index 8e16e82..319820c 100644 --- a/muxer.go +++ b/muxer.go @@ -331,7 +331,7 @@ func (m *Muxer) Close() { func (m *Muxer) WriteAV1( track *Track, ntp time.Time, - pts time.Duration, + pts int64, tu [][]byte, ) error { return m.segmenter.writeAV1(m.mtracksByTrack[track], ntp, pts, tu) @@ -341,7 +341,7 @@ func (m *Muxer) WriteAV1( func (m *Muxer) WriteVP9( track *Track, ntp time.Time, - pts time.Duration, + pts int64, frame []byte, ) error { return m.segmenter.writeVP9(m.mtracksByTrack[track], ntp, pts, frame) @@ -351,7 +351,7 @@ func (m *Muxer) WriteVP9( func (m *Muxer) WriteH265( track *Track, ntp time.Time, - pts time.Duration, + pts int64, au [][]byte, ) error { return m.segmenter.writeH265(m.mtracksByTrack[track], ntp, pts, au) @@ -361,7 +361,7 @@ func (m *Muxer) WriteH265( func (m *Muxer) WriteH264( track *Track, ntp time.Time, - pts time.Duration, + pts int64, au [][]byte, ) error { return m.segmenter.writeH264(m.mtracksByTrack[track], ntp, pts, au) @@ -371,7 +371,7 @@ func (m *Muxer) WriteH264( func (m *Muxer) WriteOpus( track *Track, ntp time.Time, - pts time.Duration, + pts int64, packets [][]byte, ) error { return m.segmenter.writeOpus(m.mtracksByTrack[track], ntp, pts, packets) @@ -381,7 +381,7 @@ func (m *Muxer) WriteOpus( func (m *Muxer) WriteMPEG4Audio( track *Track, ntp time.Time, - pts time.Duration, + pts int64, aus [][]byte, ) error { return m.segmenter.writeMPEG4Audio(m.mtracksByTrack[track], ntp, pts, aus) diff --git a/muxer_part.go b/muxer_part.go index dfc65ea..7f9d84c 100644 --- a/muxer_part.go +++ b/muxer_part.go @@ -42,7 +42,7 @@ func (p *muxerPart) finalize(endDTS time.Duration) error { if track.fmp4Samples != nil { part.Tracks = append(part.Tracks, &fmp4.PartTrack{ ID: 1 + i, - BaseTime: durationGoToMp4(track.fmp4StartDTS, track.fmp4TimeScale), + BaseTime: uint64(track.fmp4StartDTS), Samples: track.fmp4Samples, }) diff --git a/muxer_segment_mpegts.go b/muxer_segment_mpegts.go index e3d86b3..0a3cfa3 100644 --- a/muxer_segment_mpegts.go +++ b/muxer_segment_mpegts.go @@ -9,10 +9,6 @@ import ( "github.com/bluenviron/gohlslib/v2/pkg/storage" ) -func durationGoToMPEGTS(v time.Duration) int64 { - return int64(v.Seconds() * 90000) -} - type muxerSegmentMPEGTS struct { segmentMaxSize uint64 prefix string @@ -86,8 +82,8 @@ func (s *muxerSegmentMPEGTS) finalize(endDTS time.Duration) error { func (s *muxerSegmentMPEGTS) writeH264( track *muxerTrack, - pts time.Duration, - dts time.Duration, + pts int64, + dts int64, idrPresent bool, au [][]byte, ) error { @@ -102,8 +98,8 @@ func (s *muxerSegmentMPEGTS) writeH264( err := s.stream.mpegtsWriter.WriteH264( track.mpegtsTrack, - durationGoToMPEGTS(pts), - durationGoToMPEGTS(dts), + multiplyAndDivide(pts, 90000, int64(track.ClockRate)), + multiplyAndDivide(dts, 90000, int64(track.ClockRate)), idrPresent, au, ) @@ -111,14 +107,14 @@ func (s *muxerSegmentMPEGTS) writeH264( return err } - s.endDTS = dts + s.endDTS = timestampToDuration(dts, track.ClockRate) return nil } func (s *muxerSegmentMPEGTS) writeMPEG4Audio( track *muxerTrack, - pts time.Duration, + pts int64, aus [][]byte, ) error { size := uint64(0) @@ -133,7 +129,7 @@ func (s *muxerSegmentMPEGTS) writeMPEG4Audio( err := s.stream.mpegtsWriter.WriteMPEG4Audio( track.mpegtsTrack, - durationGoToMPEGTS(pts), + multiplyAndDivide(pts, 90000, int64(track.ClockRate)), aus, ) if err != nil { @@ -142,7 +138,7 @@ func (s *muxerSegmentMPEGTS) writeMPEG4Audio( if track.isLeading { s.audioAUCount++ - s.endDTS = pts + s.endDTS = timestampToDuration(pts, track.ClockRate) } return nil diff --git a/muxer_segmenter.go b/muxer_segmenter.go index ec2e745..1fba6d5 100644 --- a/muxer_segmenter.go +++ b/muxer_segmenter.go @@ -15,6 +15,26 @@ import ( "github.com/bluenviron/mediacommon/pkg/formats/fmp4" ) +func multiplyAndDivide(v, m, d int64) int64 { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +func multiplyAndDivide2(v, m, d time.Duration) time.Duration { + secs := v / d + dec := v % d + return (secs*m + dec*m/d) +} + +func durationToTimestamp(d time.Duration, clockRate int) int64 { + return multiplyAndDivide(int64(d), int64(clockRate), int64(time.Second)) +} + +func timestampToDuration(d int64, clockRate int) time.Duration { + return multiplyAndDivide2(time.Duration(d), time.Second, time.Duration(clockRate)) +} + func partDurationIsCompatible(partDuration time.Duration, sampleDuration time.Duration) bool { if sampleDuration > partDuration { return false @@ -53,7 +73,7 @@ func findCompatiblePartDuration( type fmp4AugmentedSample struct { fmp4.PartSample - dts time.Duration + dts int64 ntp time.Time } @@ -75,7 +95,7 @@ func (s *muxerSegmenter) initialize() { func (s *muxerSegmenter) writeAV1( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, tu [][]byte, ) error { codec := track.Codec.(*codecs.AV1) @@ -125,7 +145,7 @@ func (s *muxerSegmenter) writeAV1( func (s *muxerSegmenter) writeVP9( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, frame []byte, ) error { var h vp9.Header @@ -197,7 +217,7 @@ func (s *muxerSegmenter) writeVP9( func (s *muxerSegmenter) writeH265( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, au [][]byte, ) error { randomAccess := false @@ -243,7 +263,7 @@ func (s *muxerSegmenter) writeH265( } track.firstRandomAccessReceived = true - track.h265DTSExtractor = h265.NewDTSExtractor() + track.h265DTSExtractor = h265.NewDTSExtractor2() } dts, err := track.h265DTSExtractor.Extract(au, pts) @@ -252,7 +272,7 @@ func (s *muxerSegmenter) writeH265( } ps, err := fmp4.NewPartSampleH26x( - int32(durationGoToMp4(pts-dts, 90000)), + int32(pts-dts), randomAccess, au) if err != nil { @@ -273,7 +293,7 @@ func (s *muxerSegmenter) writeH265( func (s *muxerSegmenter) writeH264( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, au [][]byte, ) error { randomAccess := false @@ -321,7 +341,7 @@ func (s *muxerSegmenter) writeH264( } track.firstRandomAccessReceived = true - track.h264DTSExtractor = h264.NewDTSExtractor() + track.h264DTSExtractor = h264.NewDTSExtractor2() } dts, err := track.h264DTSExtractor.Extract(au, pts) @@ -331,16 +351,16 @@ func (s *muxerSegmenter) writeH264( if s.muxer.Variant == MuxerVariantMPEGTS { if track.stream.nextSegment == nil { - err := s.muxer.createFirstSegment(dts, ntp) + err := s.muxer.createFirstSegment(timestampToDuration(dts, track.ClockRate), ntp) if err != nil { return err } } else { // switch segment if randomAccess && - ((dts-track.stream.nextSegment.(*muxerSegmentMPEGTS).startDTS) >= s.muxer.SegmentMinDuration || + ((timestampToDuration(dts, track.ClockRate)-track.stream.nextSegment.(*muxerSegmentMPEGTS).startDTS) >= s.muxer.SegmentMinDuration || paramsChanged) { - err := s.muxer.rotateSegments(dts, ntp, false) + err := s.muxer.rotateSegments(timestampToDuration(dts, track.ClockRate), ntp, false) if err != nil { return err } @@ -361,7 +381,7 @@ func (s *muxerSegmenter) writeH264( return nil } else { ps, err := fmp4.NewPartSampleH26x( - int32(durationGoToMp4(pts-dts, 90000)), + int32(pts-dts), randomAccess, au) if err != nil { @@ -383,7 +403,7 @@ func (s *muxerSegmenter) writeH264( func (s *muxerSegmenter) writeOpus( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, packets [][]byte, ) error { for _, packet := range packets { @@ -405,7 +425,7 @@ func (s *muxerSegmenter) writeOpus( duration := opus.PacketDuration(packet) ntp = ntp.Add(duration) - pts += duration + pts += durationToTimestamp(duration, track.ClockRate) } return nil @@ -414,19 +434,19 @@ func (s *muxerSegmenter) writeOpus( func (s *muxerSegmenter) writeMPEG4Audio( track *muxerTrack, ntp time.Time, - pts time.Duration, + pts int64, aus [][]byte, ) error { if s.muxer.Variant == MuxerVariantMPEGTS { if track.isLeading { if track.stream.nextSegment == nil { - err := s.muxer.createFirstSegment(pts, ntp) + err := s.muxer.createFirstSegment(timestampToDuration(pts, track.ClockRate), ntp) if err != nil { return err } } else if track.stream.nextSegment.(*muxerSegmentMPEGTS).audioAUCount >= mpegtsSegmentMinAUCount && // switch segment - (pts-track.stream.nextSegment.(*muxerSegmentMPEGTS).startDTS) >= s.muxer.SegmentMinDuration { - err := s.muxer.rotateSegments(pts, ntp, false) + (timestampToDuration(pts, track.ClockRate)-track.stream.nextSegment.(*muxerSegmentMPEGTS).startDTS) >= s.muxer.SegmentMinDuration { + err := s.muxer.rotateSegments(timestampToDuration(pts, track.ClockRate), ntp, false) if err != nil { return err } @@ -445,13 +465,13 @@ func (s *muxerSegmenter) writeMPEG4Audio( return nil } else { - sampleRate := time.Duration(track.Codec.(*codecs.MPEG4Audio).Config.SampleRate) + sampleRate := track.Codec.(*codecs.MPEG4Audio).Config.SampleRate for i, au := range aus { auNTP := ntp.Add(time.Duration(i) * mpeg4audio.SamplesPerAccessUnit * - time.Second / sampleRate) - auPTS := pts + time.Duration(i)*mpeg4audio.SamplesPerAccessUnit* - time.Second/sampleRate + time.Second / time.Duration(sampleRate)) + auPTS := pts + int64(i)*mpeg4audio.SamplesPerAccessUnit* + int64(track.ClockRate)/int64(sampleRate) err := s.fmp4WriteSample( track, @@ -475,7 +495,7 @@ func (s *muxerSegmenter) writeMPEG4Audio( } // iPhone iOS fails if part durations are less than 85% of maximum part duration. -// find a part duration that is compatible with all received sample durations +// find a part duration that is compatible with all sample durations func (s *muxerSegmenter) fmp4AdjustPartDuration(sampleDuration time.Duration) { if s.muxer.Variant != MuxerVariantLowLatency || s.fmp4FreezeAdjustedPartDuration { return @@ -502,7 +522,7 @@ func (s *muxerSegmenter) fmp4WriteSample( sample *fmp4AugmentedSample, ) error { // add a starting DTS to avoid a negative BaseTime - sample.dts += fmp4StartDTS + sample.dts += durationToTimestamp(fmp4StartDTS, track.ClockRate) // BaseTime is still negative, this is not supported by fMP4. Reject the sample silently. if sample.dts < 0 { @@ -515,12 +535,12 @@ func (s *muxerSegmenter) fmp4WriteSample( return nil } duration := track.fmp4NextSample.dts - sample.dts - sample.Duration = uint32(durationGoToMp4(duration, track.fmp4TimeScale)) + sample.Duration = uint32(duration) if track.isLeading { // create first segment if track.stream.nextSegment == nil { - err := s.muxer.createFirstSegment(sample.dts, sample.ntp) + err := s.muxer.createFirstSegment(timestampToDuration(sample.dts, track.ClockRate), sample.ntp) if err != nil { return err } @@ -533,7 +553,7 @@ func (s *muxerSegmenter) fmp4WriteSample( } if track.isLeading { - s.fmp4AdjustPartDuration(duration) + s.fmp4AdjustPartDuration(timestampToDuration(duration, track.ClockRate)) } err := track.stream.nextSegment.(*muxerSegmentFMP4).writeSample( @@ -547,8 +567,8 @@ func (s *muxerSegmenter) fmp4WriteSample( if track.isLeading { // switch segment if randomAccess && (paramsChanged || - (track.fmp4NextSample.dts-track.stream.nextSegment.(*muxerSegmentFMP4).startDTS) >= s.muxer.SegmentMinDuration) { - err = s.muxer.rotateSegments(track.fmp4NextSample.dts, track.fmp4NextSample.ntp, paramsChanged) + (timestampToDuration(track.fmp4NextSample.dts, track.ClockRate)-track.stream.nextSegment.(*muxerSegmentFMP4).startDTS) >= s.muxer.SegmentMinDuration) { + err = s.muxer.rotateSegments(timestampToDuration(track.fmp4NextSample.dts, track.ClockRate), track.fmp4NextSample.ntp, paramsChanged) if err != nil { return err } @@ -563,8 +583,8 @@ func (s *muxerSegmenter) fmp4WriteSample( // switch part } else if (s.muxer.Variant == MuxerVariantLowLatency) && - (track.fmp4NextSample.dts-track.stream.nextPart.startDTS) >= s.fmp4AdjustedPartDuration { - err := s.muxer.rotateParts(track.fmp4NextSample.dts) + (timestampToDuration(track.fmp4NextSample.dts, track.ClockRate)-track.stream.nextPart.startDTS) >= s.fmp4AdjustedPartDuration { + err := s.muxer.rotateParts(timestampToDuration(track.fmp4NextSample.dts, track.ClockRate)) if err != nil { return err } diff --git a/muxer_server.go b/muxer_server.go index 74cbada..d115b76 100644 --- a/muxer_server.go +++ b/muxer_server.go @@ -104,7 +104,15 @@ func (s *muxerServer) handleMultivariantPlaylist(w http.ResponseWriter, r *http. s.muxer.mutex.Lock() defer s.muxer.mutex.Unlock() - for !s.muxer.closed && !s.muxer.streams[0].hasContent() { + for { + if s.muxer.closed { + return nil + } + + if s.muxer.streams[0].hasContent() { + break + } + s.muxer.cond.Wait() } diff --git a/muxer_stream.go b/muxer_stream.go index d6f4382..a300bb1 100644 --- a/muxer_stream.go +++ b/muxer_stream.go @@ -648,7 +648,16 @@ func (s *muxerStream) rotateParts(nextDTS time.Duration, createNew bool) error { s.muxer.server.pathHandlers[partPath] = func(w http.ResponseWriter, r *http.Request) { s.muxer.mutex.Lock() - for !s.muxer.closed && s.muxer.nextPartID <= partID { + for { + if s.muxer.closed { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if s.muxer.nextPartID > partID { + break + } + s.muxer.cond.Wait() } diff --git a/muxer_test.go b/muxer_test.go index 41d9663..ceb2506 100644 --- a/muxer_test.go +++ b/muxer_test.go @@ -42,6 +42,7 @@ var testVideoTrack = &Track{ SPS: testSPS, PPS: []byte{0x08}, }, + ClockRate: 90000, } var testAudioTrack = &Track{ @@ -52,6 +53,7 @@ var testAudioTrack = &Track{ ChannelCount: 2, }, }, + ClockRate: 44100, } var testAudioTrack2 = &Track{ @@ -62,6 +64,7 @@ var testAudioTrack2 = &Track{ ChannelCount: 2, }, }, + ClockRate: 44100, } type dummyResponseWriter struct { @@ -153,166 +156,216 @@ func TestMuxer(t *testing.T) { switch stream { case "video+audio": d := 1 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {1}, // non-IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {1}, // non-IDR + }) require.NoError(t, err) d = 2 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 3500 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 4 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {1}, // non-IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {1}, // non-IDR + }) require.NoError(t, err) d = 4500 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 6 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) d = 7 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) case "video": d := 2 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) require.NoError(t, err) d = 6 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) d = 7 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-2*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) case "audio": for i := 0; i < 100; i++ { d := time.Duration(i) * 4 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) } d := 2 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) case "video+multiaudio": d := 2 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 3500 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 2 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 2500 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 6 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) d = 7 * time.Second - err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), d, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testVideoTrack.ClockRate)/int64(time.Second), + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) case "multiaudio": for i := 0; i < 100; i++ { d := time.Duration(i) * 4 * time.Millisecond - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) - err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack2, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) } d := 2 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) d = 3 * time.Second - err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), d, [][]byte{{ - 0x01, 0x02, 0x03, 0x04, - }}) + err = m.WriteMPEG4Audio(testAudioTrack, testTime.Add(d-1*time.Second), + int64(d)*int64(testAudioTrack.ClockRate)/int64(time.Second), + [][]byte{{ + 0x01, 0x02, 0x03, 0x04, + }}) require.NoError(t, err) } @@ -718,9 +771,7 @@ func TestMuxerCloseBeforeData(t *testing.T) { Variant: MuxerVariantFMP4, SegmentCount: 3, SegmentMinDuration: 1 * time.Second, - Tracks: []*Track{{ - Codec: &codecs.AV1{}, - }}, + Tracks: []*Track{testVideoTrack}, } err := m.Start() @@ -731,7 +782,7 @@ func TestMuxerCloseBeforeData(t *testing.T) { b, _, _ := doRequest(m, "index.m3u8") require.Equal(t, []byte(nil), b) - b, _, _ = doRequest(m, "main_stream.m3u8") + b, _, _ = doRequest(m, "video1_stream.m3u8") require.Equal(t, []byte(nil), b) b, _, _ = doRequest(m, m.prefix+"_init.mp4") @@ -751,7 +802,7 @@ func TestMuxerMaxSegmentSize(t *testing.T) { require.NoError(t, err) defer m.Close() - err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, int64(2*time.Second)*int64(testVideoTrack.ClockRate)/int64(time.Second), [][]byte{ testSPS, {5}, // IDR }) @@ -777,7 +828,7 @@ func TestMuxerDoubleRead(t *testing.T) { }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, int64(2*time.Second)*int64(testVideoTrack.ClockRate)/int64(time.Second), [][]byte{ {5}, // IDR {2}, }) @@ -840,13 +891,13 @@ func TestMuxerSaveToDisk(t *testing.T) { }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*90000, [][]byte{ {5}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 3*90000, [][]byte{ {5}, // IDR {2}, }) @@ -931,13 +982,13 @@ func TestMuxerDynamicParams(t *testing.T) { }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 1*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 1*90000, [][]byte{ {5}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 2*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 2*90000, [][]byte{ {5}, // IDR {2}, }) @@ -987,14 +1038,14 @@ func TestMuxerDynamicParams(t *testing.T) { 0xcb, } - err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 3*90000, [][]byte{ testSPS2, {0x65, 0x88, 0x84, 0x00, 0x33, 0xff}, // IDR {2}, }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, 5*time.Second, [][]byte{ + err = m.WriteH264(testVideoTrack, testTime, 5*90000, [][]byte{ {0x65, 0x88, 0x84, 0x00, 0x33, 0xff}, // IDR }) require.NoError(t, err) @@ -1055,7 +1106,7 @@ func TestMuxerFMP4ZeroDuration(t *testing.T) { }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, time.Now(), 1*time.Nanosecond, [][]byte{ + err = m.WriteH264(testVideoTrack, time.Now(), 1, [][]byte{ testSPS, // SPS {8}, // PPS {5}, // IDR @@ -1075,60 +1126,78 @@ func TestMuxerFMP4NegativeTimestamp(t *testing.T) { require.NoError(t, err) defer m.Close() - err = m.WriteMPEG4Audio(testAudioTrack, testTime, -9*time.Second, [][]byte{ - {1, 2, 3, 4}, - }) + err = m.WriteMPEG4Audio(testAudioTrack, testTime, + -9*44100, + [][]byte{ + {1, 2, 3, 4}, + }) require.NoError(t, err) // this is skipped - err = m.WriteH264(testVideoTrack, testTime, -11*time.Second, [][]byte{ - testSPS, - {5}, // IDR - {1}, - }) + err = m.WriteH264(testVideoTrack, testTime, + -11*90000, + [][]byte{ + testSPS, + {5}, // IDR + {1}, + }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, -9*time.Second, [][]byte{ - testSPS, - {5}, // IDR - {1}, - }) + err = m.WriteH264(testVideoTrack, testTime, + -9*90000, + [][]byte{ + testSPS, + {5}, // IDR + {1}, + }) require.NoError(t, err) - err = m.WriteH264(testVideoTrack, testTime, -8*time.Second, [][]byte{ - {5}, // IDR - {2}, - }) + err = m.WriteH264(testVideoTrack, testTime, + -8*90000, + [][]byte{ + {5}, // IDR + {2}, + }) require.NoError(t, err) // this is skipped - err = m.WriteMPEG4Audio(testAudioTrack, testTime, -11*time.Second, [][]byte{ - {1, 2, 3, 4}, - }) + err = m.WriteMPEG4Audio(testAudioTrack, testTime, + -11*44100, + [][]byte{ + {1, 2, 3, 4}, + }) require.NoError(t, err) - err = m.WriteMPEG4Audio(testAudioTrack, testTime, -8*time.Second, [][]byte{ - {5, 6, 7, 8}, - }) + err = m.WriteMPEG4Audio(testAudioTrack, testTime, + -8*44100, + [][]byte{ + {5, 6, 7, 8}, + }) require.NoError(t, err) - err = m.WriteMPEG4Audio(testAudioTrack, testTime, -7*time.Second, [][]byte{ - {9, 10, 11, 12}, - }) + err = m.WriteMPEG4Audio(testAudioTrack, testTime, + -7*44100, + [][]byte{ + {9, 10, 11, 12}, + }) require.NoError(t, err) // switch segment - err = m.WriteH264(testVideoTrack, testTime, -7*time.Second, [][]byte{ - {5}, // IDR - {3}, - }) + err = m.WriteH264(testVideoTrack, testTime, + -7*90000, + [][]byte{ + {5}, // IDR + {3}, + }) require.NoError(t, err) // switch segment - err = m.WriteH264(testVideoTrack, testTime, -5*time.Second, [][]byte{ - {5}, // IDR - {3}, - }) + err = m.WriteH264(testVideoTrack, testTime, + -5*90000, + [][]byte{ + {5}, // IDR + {3}, + }) require.NoError(t, err) bu, _, err := doRequest(m, "index.m3u8") @@ -1260,15 +1329,18 @@ func TestMuxerFMP4SequenceNumber(t *testing.T) { require.NoError(t, err) for i := 0; i < 3; i++ { - err = m.WriteH264(testVideoTrack, testTime, (1+time.Duration(i))*time.Second, [][]byte{ - {1}, // non IDR - }) + err = m.WriteH264(testVideoTrack, testTime, + (1+int64(i))*90000, [][]byte{ + {1}, // non IDR + }) require.NoError(t, err) } - err = m.WriteH264(testVideoTrack, testTime, 4*time.Second, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime, + 4*90000, + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) byts, _, err := doRequest(m, "index.m3u8") @@ -1356,11 +1428,13 @@ func TestMuxerInvalidFolder(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err := m.WriteH264(testVideoTrack, testTime, + int64(i)*90000, + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) if ca == "mpegts" || i == 1 { require.Error(t, err) @@ -1385,11 +1459,13 @@ func TestMuxerExpiredSegment(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err := m.WriteH264(testVideoTrack, testTime, + int64(i)*90000, + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) require.NoError(t, err) } @@ -1435,11 +1511,13 @@ func TestMuxerPreloadHint(t *testing.T) { defer m.Close() for i := 0; i < 2; i++ { - err := m.WriteH264(testVideoTrack, testTime, time.Duration(i)*time.Second, [][]byte{ - testSPS, // SPS - {8}, // PPS - {5}, // IDR - }) + err := m.WriteH264(testVideoTrack, testTime, + int64(i)*90000, + [][]byte{ + testSPS, // SPS + {8}, // PPS + {5}, // IDR + }) require.NoError(t, err) } @@ -1503,9 +1581,11 @@ func TestMuxerPreloadHint(t *testing.T) { case <-time.After(500 * time.Millisecond): } - err = m.WriteH264(testVideoTrack, testTime, 3*time.Second, [][]byte{ - {5}, // IDR - }) + err = m.WriteH264(testVideoTrack, testTime, + 3*90000, + [][]byte{ + {5}, // IDR + }) require.NoError(t, err) byts = <-preloadDone diff --git a/muxer_track.go b/muxer_track.go index 55f4e75..9b56ca5 100644 --- a/muxer_track.go +++ b/muxer_track.go @@ -1,8 +1,6 @@ package gohlslib import ( - "time" - "github.com/bluenviron/gohlslib/v2/pkg/codecs" "github.com/bluenviron/mediacommon/pkg/codecs/h264" "github.com/bluenviron/mediacommon/pkg/codecs/h265" @@ -17,13 +15,12 @@ type muxerTrack struct { isLeading bool firstRandomAccessReceived bool - h264DTSExtractor *h264.DTSExtractor - h265DTSExtractor *h265.DTSExtractor + h264DTSExtractor *h264.DTSExtractor2 + h265DTSExtractor *h265.DTSExtractor2 mpegtsTrack *mpegts.Track // mpegts only - fmp4TimeScale uint32 // fmp4 only fmp4NextSample *fmp4AugmentedSample // fmp4 only fmp4Samples []*fmp4.PartSample // fmp4 only - fmp4StartDTS time.Duration // fmp4 only + fmp4StartDTS int64 // fmp4 only } func (t *muxerTrack) initialize() { @@ -31,7 +28,5 @@ func (t *muxerTrack) initialize() { t.mpegtsTrack = &mpegts.Track{ Codec: codecs.ToMPEGTS(t.Codec), } - } else { - t.fmp4TimeScale = fmp4TimeScale(t.Codec) } } diff --git a/track.go b/track.go index 96b825e..77ffe27 100644 --- a/track.go +++ b/track.go @@ -6,5 +6,6 @@ import ( // Track is a HLS track. type Track struct { - codecs.Codec + Codec codecs.Codec + ClockRate int }