mirror of
https://github.com/bluenviron/mediacommon.git
synced 2026-04-23 00:07:07 +08:00
mpeg-ts: speed up initialization
stop scanning for parameters that are not strictly necessary for track identification.
This commit is contained in:
@@ -36,7 +36,7 @@ Go ≥ 1.24 is required.
|
||||
|ISO 14496-1, Coding of audio-visual objects, Part 1, Systems|formats / fMP4|
|
||||
|ISO 14496-12, Coding of audio-visual objects, Part 12, ISO base media file format|formats / fMP4|
|
||||
|ISO 14496-14, Coding of audio-visual objects, Part 14, MP4 file format|formats / fMP4|
|
||||
|ISO 14496-15, Coding of audio-visual objects, Part 15, Advanced Video Coding (AVC) file format|formats / fMP4 + H264 / H265|
|
||||
|ISO 14496-15, Coding of audio-visual objects, Part 15, Advanced Video Coding (AVC) file format|formats / fMP4 + H264, H265|
|
||||
|[VP9 Codec ISO Media File Format Binding](https://www.webmproject.org/vp9/mp4/)|formats / fMP4 + VP9|
|
||||
|[AV1 Codec ISO Media File Format Binding](https://aomediacodec.github.io/av1-isobmff)|formats / fMP4 + AV1|
|
||||
|[Opus in MP4/ISOBMFF](https://opus-codec.org/docs/opus_in_isobmff.html)|formats / fMP4 + Opus|
|
||||
@@ -45,7 +45,7 @@ Go ≥ 1.24 is required.
|
||||
|[ETSI TS Opus 0.1.3-draft, Opus Interactive Audio Codec Transport Multiplexing Standard](https://opus-codec.org/docs/ETSI_TS_opus-v0.1.3-draft.pdf)|formats / MPEG-TS + Opus|
|
||||
|[MISB ST 1402, MPEG-2 Transport Stream for Class 1/Class 2 Motion Imagery, Audio and Metadata](https://nsgreg.nga.mil/doc/view?i=4273)|formats / MPEG-TS + KLV|
|
||||
|[ETSI EN 300 743, Digital Video Broadcasting (DVB), Subtitling systems](https://www.etsi.org/deliver/etsi_en/300700_300799/300743/01.06.01_20/en_300743v010601a.pdf)|formats / MPEG-TS + DVB subtitles|
|
||||
|[ETSI EN 300 468, Digital Video Broadcasting (DVB), Specification for Service Information (SI) in DVB systems](https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.17.01_20/en_300468v011701a.pdf)|formats / MPEG-TS + DVB subtitles|
|
||||
|[ETSI EN 300 468, Digital Video Broadcasting (DVB), Specification for Service Information (SI) in DVB systems](https://www.etsi.org/deliver/etsi_en/300400_300499/300468/01.17.01_20/en_300468v011701a.pdf)|formats / MPEG-TS + DVB subtitles, AC-3, E-AC-3|
|
||||
|
||||
## Related projects
|
||||
|
||||
|
||||
@@ -2,8 +2,25 @@ package codecs
|
||||
|
||||
// AC3 is an AC-3 codec.
|
||||
// Specification: ISO 13818-1
|
||||
// Specification: ETSI EN 300 468
|
||||
type AC3 struct {
|
||||
SampleRate int
|
||||
// Full service flag.
|
||||
FullService bool
|
||||
|
||||
// Channels coding.
|
||||
// 0=1-2ch
|
||||
// 1=mono
|
||||
// 2=2ch stereo
|
||||
// 3=2ch surround
|
||||
// 4=multichannel mono
|
||||
// 5=multichannel stereo
|
||||
// 6=multichannel surround
|
||||
ChannelsCoding uint8
|
||||
|
||||
// Deprecated: not filled and not used anymore.
|
||||
SampleRate int
|
||||
|
||||
// Deprecated: not filled and not used anymore.
|
||||
ChannelCount int
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,25 @@ package codecs
|
||||
|
||||
// EAC3 is an Enhanced AC-3 (Dolby Digital Plus) codec.
|
||||
// Specification: ETSI TS 102 366 V1.4.1, Annex E
|
||||
// Specification: ETSI EN 300 468
|
||||
type EAC3 struct {
|
||||
SampleRate int
|
||||
// Full service flag.
|
||||
FullService bool
|
||||
|
||||
// Channels coding.
|
||||
// 0=1-2ch
|
||||
// 1=mono
|
||||
// 2=2ch stereo
|
||||
// 3=2ch surround
|
||||
// 4=multichannel mono
|
||||
// 5=multichannel stereo
|
||||
// 6=multichannel surround
|
||||
ChannelsCoding uint8
|
||||
|
||||
// Deprecated: not filled anymore.
|
||||
SampleRate int
|
||||
|
||||
// Deprecated: not filled anymore.
|
||||
ChannelCount int
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
// MPEG4Audio is a MPEG-4 Audio codec.
|
||||
// Specification: ISO 13818-1
|
||||
type MPEG4Audio struct {
|
||||
// Deprecated: not filled anymore.
|
||||
mpeg4audio.Config
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,13 @@ type ReaderOnDataMPEGxVideoFunc func(pts int64, frame []byte) error
|
||||
type ReaderOnDataOpusFunc func(pts int64, packets [][]byte) error
|
||||
|
||||
// ReaderOnDataMPEG4AudioFunc is the prototype of the callback passed to OnDataMPEG4Audio.
|
||||
//
|
||||
// Deprecated: replaced by ReaderOnDataMPEG4Audio2Func.
|
||||
type ReaderOnDataMPEG4AudioFunc func(pts int64, aus [][]byte) error
|
||||
|
||||
// ReaderOnDataMPEG4Audio2Func is the prototype of the callback passed to OnDataMPEG4Audio2.
|
||||
type ReaderOnDataMPEG4Audio2Func func(pts int64, packets mpeg4audio.ADTSPackets) error
|
||||
|
||||
// ReaderOnDataMPEG4AudioLATMFunc is the prototype of the callback passed to OnDataMPEG4AudioLATM.
|
||||
type ReaderOnDataMPEG4AudioLATMFunc func(pts int64, els [][]byte) error
|
||||
|
||||
@@ -205,7 +210,7 @@ func (r *Reader) Initialize() error {
|
||||
|
||||
for i, es := range pmt.ElementaryStreams {
|
||||
var track Track
|
||||
err = track.unmarshal(dem, es)
|
||||
err = track.unmarshal(es)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -331,6 +336,8 @@ func (r *Reader) OnDataOpus(track *Track, cb ReaderOnDataOpusFunc) {
|
||||
}
|
||||
|
||||
// OnDataMPEG4Audio sets a callback that is called when data from an MPEG-4 Audio track is received.
|
||||
//
|
||||
// Deprecated: replaced by OnDataMPEG4Audio2.
|
||||
func (r *Reader) OnDataMPEG4Audio(track *Track, cb ReaderOnDataMPEG4AudioFunc) {
|
||||
r.onData[track.PID] = func(pts int64, dts int64, data []byte) error {
|
||||
if pts != dts {
|
||||
@@ -354,6 +361,25 @@ func (r *Reader) OnDataMPEG4Audio(track *Track, cb ReaderOnDataMPEG4AudioFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
// OnDataMPEG4Audio2 sets a callback that is called when data from an MPEG-4 Audio track is received.
|
||||
func (r *Reader) OnDataMPEG4Audio2(track *Track, cb ReaderOnDataMPEG4Audio2Func) {
|
||||
r.onData[track.PID] = func(pts int64, dts int64, data []byte) error {
|
||||
if pts != dts {
|
||||
r.onDecodeError(fmt.Errorf("PTS is not equal to DTS"))
|
||||
return nil
|
||||
}
|
||||
|
||||
var pkts mpeg4audio.ADTSPackets
|
||||
err := pkts.Unmarshal(data)
|
||||
if err != nil {
|
||||
r.onDecodeError(fmt.Errorf("invalid ADTS: %w", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return cb(pts, pkts)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDataMPEG4AudioLATM sets a callback that is called when data from an MPEG-4 Audio LATM track is received.
|
||||
func (r *Reader) OnDataMPEG4AudioLATM(track *Track, cb ReaderOnDataMPEG4AudioLATMFunc) {
|
||||
r.onData[track.PID] = func(pts int64, dts int64, data []byte) error {
|
||||
|
||||
@@ -41,7 +41,7 @@ var testH264SPS = []byte{
|
||||
type sample struct {
|
||||
pts int64
|
||||
dts int64
|
||||
data [][]byte
|
||||
data any
|
||||
}
|
||||
|
||||
var casesReadWriter = []struct {
|
||||
@@ -255,7 +255,7 @@ var casesReadWriter = []struct {
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{0, 0, 1, 0xb3}},
|
||||
[]byte{0, 0, 1, 0xb3},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -315,7 +315,7 @@ var casesReadWriter = []struct {
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{0, 0, 1, 0xb8, 1, 2, 3, 4}},
|
||||
[]byte{0, 0, 1, 0xb8, 1, 2, 3, 4},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -482,21 +482,29 @@ var casesReadWriter = []struct {
|
||||
{
|
||||
"mpeg-4 audio",
|
||||
&Track{
|
||||
PID: 257,
|
||||
Codec: &codecs.MPEG4Audio{
|
||||
Config: mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelConfig: 2,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
PID: 257,
|
||||
Codec: &codecs.MPEG4Audio{},
|
||||
},
|
||||
[]sample{
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{3}, {2}},
|
||||
mpeg4audio.ADTSPackets{
|
||||
{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelConfig: 2,
|
||||
ChannelCount: 2,
|
||||
AU: []byte{3},
|
||||
},
|
||||
{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelConfig: 2,
|
||||
ChannelCount: 2,
|
||||
AU: []byte{2},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -739,15 +747,15 @@ var casesReadWriter = []struct {
|
||||
&Track{
|
||||
PID: 257,
|
||||
Codec: &codecs.AC3{
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 1,
|
||||
FullService: true,
|
||||
ChannelsCoding: 2,
|
||||
},
|
||||
},
|
||||
[]sample{
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{
|
||||
[]byte{
|
||||
0x0b, 0x77, 0x47, 0x11, 0x0c, 0x40, 0x2f, 0x84,
|
||||
0x2b, 0xc1, 0x07, 0x7a, 0xb0, 0xfa, 0xbb, 0xea,
|
||||
0xef, 0x9f, 0x57, 0x7c, 0xf9, 0xf3, 0xf7, 0xcf,
|
||||
@@ -796,7 +804,7 @@ var casesReadWriter = []struct {
|
||||
0x9e, 0x8e, 0x04, 0x02, 0xae, 0x65, 0x87, 0x5c,
|
||||
0x4e, 0x72, 0xfd, 0x3c, 0x01, 0x86, 0xfe, 0x56,
|
||||
0x59, 0x74, 0x44, 0x3a, 0x40, 0x00, 0xec, 0xfc,
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -926,8 +934,8 @@ var casesReadWriter = []struct {
|
||||
&Track{
|
||||
PID: 257,
|
||||
Codec: &codecs.EAC3{
|
||||
SampleRate: 48000,
|
||||
ChannelCount: 2,
|
||||
FullService: true,
|
||||
ChannelsCoding: 2,
|
||||
},
|
||||
},
|
||||
[]sample{
|
||||
@@ -938,9 +946,9 @@ var casesReadWriter = []struct {
|
||||
// strmtyp=0, substreamid=0, frmsiz=63 (128 bytes)
|
||||
// fscod=0 (48kHz), numblkscod=3 (6 blocks)
|
||||
// acmod=2 (stereo), lfeon=0, bsid=16
|
||||
[][]byte{append([]byte{
|
||||
append([]byte{
|
||||
0x0b, 0x77, 0x00, 0x3f, 0x34, 0x80,
|
||||
}, bytes.Repeat([]byte{0x00}, 122)...)},
|
||||
}, bytes.Repeat([]byte{0x00}, 122)...),
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -1008,7 +1016,7 @@ var casesReadWriter = []struct {
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{1, 2, 3}},
|
||||
[]byte{1, 2, 3},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -1086,7 +1094,7 @@ var casesReadWriter = []struct {
|
||||
{
|
||||
30 * 90000,
|
||||
30 * 90000,
|
||||
[][]byte{{1, 2, 3}},
|
||||
[]byte{1, 2, 3},
|
||||
},
|
||||
},
|
||||
[]*astits.Packet{
|
||||
@@ -1179,7 +1187,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.MPEG4Video:
|
||||
r.OnDataMPEGxVideo(ca.track, func(pts int64, frame []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], frame)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), frame)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1187,7 +1195,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.MPEG1Video:
|
||||
r.OnDataMPEGxVideo(ca.track, func(pts int64, frame []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], frame)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), frame)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1201,9 +1209,9 @@ func TestReader(t *testing.T) {
|
||||
})
|
||||
|
||||
case *codecs.MPEG4Audio:
|
||||
r.OnDataMPEG4Audio(ca.track, func(pts int64, aus [][]byte) error {
|
||||
r.OnDataMPEG4Audio2(ca.track, func(pts int64, packets mpeg4audio.ADTSPackets) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data, aus)
|
||||
require.Equal(t, ca.samples[i].data.(mpeg4audio.ADTSPackets), packets)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1227,7 +1235,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.AC3:
|
||||
r.OnDataAC3(ca.track, func(pts int64, frame []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], frame)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), frame)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1235,7 +1243,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.EAC3:
|
||||
r.OnDataEAC3(ca.track, func(pts int64, frame []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], frame)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), frame)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1243,7 +1251,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.KLV:
|
||||
r.OnDataKLV(ca.track, func(pts int64, frame []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], frame)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), frame)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1251,7 +1259,7 @@ func TestReader(t *testing.T) {
|
||||
case *codecs.DVBSubtitle:
|
||||
r.OnDataDVBSubtitle(ca.track, func(pts int64, data []byte) error {
|
||||
require.Equal(t, ca.samples[i].pts, pts)
|
||||
require.Equal(t, ca.samples[i].data[0], data)
|
||||
require.Equal(t, ca.samples[i].data.([]byte), data)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
@@ -1663,7 +1671,7 @@ func TestReaderDecodeErrors(t *testing.T) {
|
||||
})
|
||||
|
||||
case "mpeg-4 audio pts != dts", "mpeg-4 audio invalid":
|
||||
r.OnDataMPEG4Audio(r.Tracks()[0], func(_ int64, _ [][]byte) error {
|
||||
r.OnDataMPEG4Audio2(r.Tracks()[0], func(_ int64, _ mpeg4audio.ADTSPackets) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
|
||||
+74
-147
@@ -4,9 +4,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/ac3"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/eac3"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
)
|
||||
|
||||
@@ -23,79 +20,29 @@ const (
|
||||
metadataApplicationFormatStillImageOnDemand = 0x0103
|
||||
)
|
||||
|
||||
func findMPEG4AudioConfig(dem *robustDemuxer, pid uint16) (*mpeg4audio.AudioSpecificConfig, error) {
|
||||
for {
|
||||
data, err := dem.nextData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if data.PES == nil || data.PID != pid {
|
||||
continue
|
||||
}
|
||||
|
||||
var adtsPkts mpeg4audio.ADTSPackets
|
||||
err = adtsPkts.Unmarshal(data.PES.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode ADTS: %w", err)
|
||||
}
|
||||
|
||||
pkt := adtsPkts[0]
|
||||
return &mpeg4audio.AudioSpecificConfig{
|
||||
Type: pkt.Type,
|
||||
SampleRate: pkt.SampleRate,
|
||||
ChannelConfig: pkt.ChannelConfig,
|
||||
ChannelCount: pkt.ChannelCount, //nolint:staticcheck
|
||||
}, nil
|
||||
func boolToUint8(v bool) uint8 {
|
||||
if v {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func findAC3Parameters(dem *robustDemuxer, pid uint16) (int, int, error) {
|
||||
for {
|
||||
data, err := dem.nextData()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
func findAC3Descriptor(descriptors []*astits.Descriptor) *astits.DescriptorAC3 {
|
||||
for _, sd := range descriptors {
|
||||
if sd.Tag == astits.DescriptorTagAC3 && sd.AC3 != nil {
|
||||
return sd.AC3
|
||||
}
|
||||
|
||||
if data.PES == nil || data.PID != pid {
|
||||
continue
|
||||
}
|
||||
|
||||
var syncInfo ac3.SyncInfo
|
||||
err = syncInfo.Unmarshal(data.PES.Data)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid AC-3 frame: %w", err)
|
||||
}
|
||||
|
||||
var bsi ac3.BSI
|
||||
err = bsi.Unmarshal(data.PES.Data[5:])
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid AC-3 frame: %w", err)
|
||||
}
|
||||
|
||||
return syncInfo.SampleRate(), bsi.ChannelCount(), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findEAC3Parameters(dem *robustDemuxer, pid uint16) (int, int, error) {
|
||||
for {
|
||||
data, err := dem.nextData()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
func findEAC3Descriptor(descriptors []*astits.Descriptor) *astits.DescriptorEnhancedAC3 {
|
||||
for _, sd := range descriptors {
|
||||
if sd.Tag == astits.DescriptorTagEnhancedAC3 && sd.EnhancedAC3 != nil {
|
||||
return sd.EnhancedAC3
|
||||
}
|
||||
|
||||
if data.PES == nil || data.PID != pid {
|
||||
continue
|
||||
}
|
||||
|
||||
var syncInfo eac3.SyncInfo
|
||||
err = syncInfo.Unmarshal(data.PES.Data)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid E-AC-3 frame: %w", err)
|
||||
}
|
||||
|
||||
return syncInfo.SampleRate(), syncInfo.ChannelCount(), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findRegistrationIdentifier(descriptors []*astits.Descriptor) (uint32, bool) {
|
||||
@@ -161,7 +108,7 @@ func findOpusChannelCount(descriptors []*astits.Descriptor) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func findCodec(dem *robustDemuxer, es *astits.PMTElementaryStream) (codecs.Codec, error) {
|
||||
func findCodec(es *astits.PMTElementaryStream) (codecs.Codec, error) {
|
||||
switch es.StreamType {
|
||||
// video
|
||||
|
||||
@@ -180,14 +127,7 @@ func findCodec(dem *robustDemuxer, es *astits.PMTElementaryStream) (codecs.Codec
|
||||
// audio
|
||||
|
||||
case astits.StreamTypeAACAudio:
|
||||
conf, err := findMPEG4AudioConfig(dem, es.ElementaryPID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &codecs.MPEG4Audio{
|
||||
Config: *conf,
|
||||
}, nil
|
||||
return &codecs.MPEG4Audio{}, nil
|
||||
|
||||
case astits.StreamTypeAACLATMAudio:
|
||||
return &codecs.MPEG4AudioLATM{}, nil
|
||||
@@ -196,25 +136,33 @@ func findCodec(dem *robustDemuxer, es *astits.PMTElementaryStream) (codecs.Codec
|
||||
return &codecs.MPEG1Audio{}, nil
|
||||
|
||||
case astits.StreamTypeAC3Audio:
|
||||
sampleRate, channelCount, err := findAC3Parameters(dem, es.ElementaryPID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var fullService bool
|
||||
var channelCoding uint8
|
||||
|
||||
desc := findAC3Descriptor(es.ElementaryStreamDescriptors)
|
||||
if desc != nil {
|
||||
fullService = (desc.ComponentType & 0b1) != 0
|
||||
channelCoding = (desc.ComponentType >> 1) & 0b111
|
||||
}
|
||||
|
||||
return &codecs.AC3{
|
||||
SampleRate: sampleRate,
|
||||
ChannelCount: channelCount,
|
||||
FullService: fullService,
|
||||
ChannelsCoding: channelCoding,
|
||||
}, nil
|
||||
|
||||
case astits.StreamTypeEAC3Audio:
|
||||
sampleRate, channelCount, err := findEAC3Parameters(dem, es.ElementaryPID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var fullService bool
|
||||
var channelCoding uint8
|
||||
|
||||
desc := findEAC3Descriptor(es.ElementaryStreamDescriptors)
|
||||
if desc != nil {
|
||||
fullService = (desc.ComponentType & 0b1) != 0
|
||||
channelCoding = (desc.ComponentType >> 1) & 0b111
|
||||
}
|
||||
|
||||
return &codecs.EAC3{
|
||||
SampleRate: sampleRate,
|
||||
ChannelCount: channelCount,
|
||||
FullService: fullService,
|
||||
ChannelsCoding: channelCoding,
|
||||
}, nil
|
||||
|
||||
// other
|
||||
@@ -255,63 +203,6 @@ func findCodec(dem *robustDemuxer, es *astits.PMTElementaryStream) (codecs.Codec
|
||||
return &codecs.Unsupported{}, nil
|
||||
}
|
||||
|
||||
// ac3ComponentType builds the DVB component_type byte for AC-3.
|
||||
// Per ETSI EN 300 468, the AC3 descriptor uses a similar format to E-AC-3.
|
||||
func ac3ComponentType(channels int, fullService bool) uint8 {
|
||||
var ct uint8
|
||||
|
||||
// Set full_service_flag (bit 0)
|
||||
if fullService {
|
||||
ct |= 0x01
|
||||
}
|
||||
|
||||
// Encode channel configuration in bits 3-1
|
||||
switch {
|
||||
case channels <= 2:
|
||||
ct |= (0x02 << 1) // 2ch stereo
|
||||
case channels <= 4:
|
||||
ct |= (0x05 << 1) // multichannel stereo
|
||||
default:
|
||||
ct |= (0x06 << 1) // multichannel surround (5.1, etc.)
|
||||
}
|
||||
|
||||
return ct
|
||||
}
|
||||
|
||||
// componentTypeFromConfig builds the DVB component_type byte.
|
||||
// Per ETSI EN 300 468, table D.1:
|
||||
// Bits 7-4: service_type_flag (0=complete main, 1=music/effects, etc.)
|
||||
// Bits 3-1: number_of_channels mapping
|
||||
// Bit 0: full_service_flag
|
||||
//
|
||||
// For E-AC-3, the component_type encodes channel configuration:
|
||||
//
|
||||
// 0x00-0x3F: Full service, complete main
|
||||
// Bits 2-0 encode channel config: 0=mono/stereo, 1=mono, 2=stereo, 3=2ch, etc.
|
||||
func eac3ComponentType(channels int, fullService bool) uint8 {
|
||||
// Start with full service, complete main audio (bits 7-4 = 0000)
|
||||
var ct uint8
|
||||
|
||||
// Set full_service_flag (bit 0)
|
||||
if fullService {
|
||||
ct |= 0x01
|
||||
}
|
||||
|
||||
// Encode channel configuration in bits 3-1 (number_of_channels)
|
||||
// Per EN 300 468: 0=1-2ch, 1=mono, 2=2ch stereo, 3=2ch surround,
|
||||
// 4=multichannel mono, 5=multichannel stereo, 6=multichannel surround
|
||||
switch {
|
||||
case channels <= 2:
|
||||
ct |= (0x02 << 1) // 2ch stereo
|
||||
case channels <= 4:
|
||||
ct |= (0x05 << 1) // multichannel stereo
|
||||
default:
|
||||
ct |= (0x06 << 1) // multichannel surround (5.1, 7.1, etc.)
|
||||
}
|
||||
|
||||
return ct
|
||||
}
|
||||
|
||||
// Track is a MPEG-TS track.
|
||||
type Track struct {
|
||||
PID uint16
|
||||
@@ -380,6 +271,24 @@ func (t Track) marshal() (*astits.PMTElementaryStream, error) {
|
||||
}, nil
|
||||
|
||||
case *codecs.AC3:
|
||||
var componentType uint8
|
||||
if c.ChannelCount != 0 { //nolint:staticcheck
|
||||
var channelCoding uint8
|
||||
switch c.ChannelCount { //nolint:staticcheck
|
||||
case 1:
|
||||
channelCoding = 1
|
||||
case 2:
|
||||
channelCoding = 2
|
||||
case 3, 4:
|
||||
channelCoding = 5
|
||||
default:
|
||||
channelCoding = 6
|
||||
}
|
||||
componentType = channelCoding<<1 | boolToUint8(c.FullService)
|
||||
} else {
|
||||
componentType = c.ChannelsCoding&0b111<<1 | boolToUint8(c.FullService)
|
||||
}
|
||||
|
||||
return &astits.PMTElementaryStream{
|
||||
ElementaryPID: t.PID,
|
||||
StreamType: astits.StreamTypeAC3Audio,
|
||||
@@ -391,7 +300,7 @@ func (t Track) marshal() (*astits.PMTElementaryStream, error) {
|
||||
Tag: astits.DescriptorTagAC3,
|
||||
AC3: &astits.DescriptorAC3{
|
||||
HasComponentType: true,
|
||||
ComponentType: ac3ComponentType(c.ChannelCount, true),
|
||||
ComponentType: componentType,
|
||||
// BSID for standard AC-3 (not E-AC-3)
|
||||
HasBSID: true,
|
||||
BSID: 8,
|
||||
@@ -401,6 +310,24 @@ func (t Track) marshal() (*astits.PMTElementaryStream, error) {
|
||||
}, nil
|
||||
|
||||
case *codecs.EAC3:
|
||||
var componentType uint8
|
||||
if c.ChannelCount != 0 { //nolint:staticcheck
|
||||
var channelCoding uint8
|
||||
switch c.ChannelCount { //nolint:staticcheck
|
||||
case 1:
|
||||
channelCoding = 1
|
||||
case 2:
|
||||
channelCoding = 2
|
||||
case 3, 4:
|
||||
channelCoding = 5
|
||||
default:
|
||||
channelCoding = 6
|
||||
}
|
||||
componentType = channelCoding<<1 | boolToUint8(c.FullService)
|
||||
} else {
|
||||
componentType = c.ChannelsCoding&0b111<<1 | boolToUint8(c.FullService)
|
||||
}
|
||||
|
||||
return &astits.PMTElementaryStream{
|
||||
ElementaryPID: t.PID,
|
||||
StreamType: astits.StreamTypeEAC3Audio,
|
||||
@@ -412,7 +339,7 @@ func (t Track) marshal() (*astits.PMTElementaryStream, error) {
|
||||
Tag: astits.DescriptorTagEnhancedAC3,
|
||||
EnhancedAC3: &astits.DescriptorEnhancedAC3{
|
||||
HasComponentType: true,
|
||||
ComponentType: eac3ComponentType(c.ChannelCount, true),
|
||||
ComponentType: componentType,
|
||||
// BSID=16 indicates E-AC-3
|
||||
HasBSID: true,
|
||||
BSID: 16,
|
||||
@@ -528,10 +455,10 @@ func (t Track) marshal() (*astits.PMTElementaryStream, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Track) unmarshal(dem *robustDemuxer, es *astits.PMTElementaryStream) error {
|
||||
func (t *Track) unmarshal(es *astits.PMTElementaryStream) error {
|
||||
t.PID = es.ElementaryPID
|
||||
|
||||
codec, err := findCodec(dem, es)
|
||||
codec, err := findCodec(es)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
)
|
||||
|
||||
@@ -623,15 +622,8 @@ func TestTrackUnmarshalExternal(t *testing.T) {
|
||||
0x00, 0x00, 0x00, 0x38,
|
||||
},
|
||||
&Track{
|
||||
PID: 256,
|
||||
Codec: &codecs.MPEG4Audio{
|
||||
Config: mpeg4audio.AudioSpecificConfig{
|
||||
Type: 2,
|
||||
SampleRate: 48000,
|
||||
ChannelConfig: 2,
|
||||
ChannelCount: 2,
|
||||
},
|
||||
},
|
||||
PID: 256,
|
||||
Codec: &codecs.MPEG4Audio{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -702,7 +694,7 @@ func TestTrackUnmarshalExternal(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
var track Track
|
||||
err = track.unmarshal(dem, pmt.ElementaryStreams[0])
|
||||
err = track.unmarshal(pmt.ElementaryStreams[0])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.track, &track)
|
||||
})
|
||||
|
||||
@@ -218,6 +218,8 @@ func (w *Writer) WriteOpus(
|
||||
}
|
||||
|
||||
// WriteMPEG4Audio writes MPEG-4 Audio access units.
|
||||
//
|
||||
// Deprecated: use WriteMPEG4Audio2.
|
||||
func (w *Writer) WriteMPEG4Audio(
|
||||
track *Track,
|
||||
pts int64,
|
||||
@@ -245,6 +247,20 @@ func (w *Writer) WriteMPEG4Audio(
|
||||
return w.writeAudio(track, pts, enc)
|
||||
}
|
||||
|
||||
// WriteMPEG4Audio2 writes MPEG-4 Audio ADTS packets.
|
||||
func (w *Writer) WriteMPEG4Audio2(
|
||||
track *Track,
|
||||
pts int64,
|
||||
packets mpeg4audio.ADTSPackets,
|
||||
) error {
|
||||
enc, err := packets.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writeAudio(track, pts, enc)
|
||||
}
|
||||
|
||||
// WriteMPEG4AudioLATM writes MPEG-4 Audio LATM audioMuxElements.
|
||||
func (w *Writer) WriteMPEG4AudioLATM(
|
||||
track *Track,
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/asticode/go-astits"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/codecs/mpeg4audio"
|
||||
"github.com/bluenviron/mediacommon/v2/pkg/formats/mpegts/codecs"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -24,40 +25,40 @@ func TestWriter(t *testing.T) {
|
||||
|
||||
switch ca.track.Codec.(type) {
|
||||
case *codecs.H265:
|
||||
err = w.WriteH265(ca.track, sample.pts, sample.dts, sample.data)
|
||||
err = w.WriteH265(ca.track, sample.pts, sample.dts, sample.data.([][]byte))
|
||||
|
||||
case *codecs.H264:
|
||||
err = w.WriteH264(ca.track, sample.pts, sample.dts, sample.data)
|
||||
err = w.WriteH264(ca.track, sample.pts, sample.dts, sample.data.([][]byte))
|
||||
|
||||
case *codecs.MPEG4Video:
|
||||
err = w.WriteMPEG4Video(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteMPEG4Video(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
case *codecs.MPEG1Video:
|
||||
err = w.WriteMPEG1Video(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteMPEG1Video(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
case *codecs.Opus:
|
||||
err = w.WriteOpus(ca.track, sample.pts, sample.data)
|
||||
err = w.WriteOpus(ca.track, sample.pts, sample.data.([][]byte))
|
||||
|
||||
case *codecs.MPEG4Audio:
|
||||
err = w.WriteMPEG4Audio(ca.track, sample.pts, sample.data)
|
||||
err = w.WriteMPEG4Audio2(ca.track, sample.pts, sample.data.(mpeg4audio.ADTSPackets))
|
||||
|
||||
case *codecs.MPEG4AudioLATM:
|
||||
err = w.WriteMPEG4AudioLATM(ca.track, sample.pts, sample.data)
|
||||
err = w.WriteMPEG4AudioLATM(ca.track, sample.pts, sample.data.([][]byte))
|
||||
|
||||
case *codecs.MPEG1Audio:
|
||||
err = w.WriteMPEG1Audio(ca.track, sample.pts, sample.data)
|
||||
err = w.WriteMPEG1Audio(ca.track, sample.pts, sample.data.([][]byte))
|
||||
|
||||
case *codecs.AC3:
|
||||
err = w.WriteAC3(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteAC3(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
case *codecs.EAC3:
|
||||
err = w.WriteEAC3(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteEAC3(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
case *codecs.KLV:
|
||||
err = w.WriteKLV(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteKLV(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
case *codecs.DVBSubtitle:
|
||||
err = w.WriteDVBSubtitle(ca.track, sample.pts, sample.data[0])
|
||||
err = w.WriteDVBSubtitle(ca.track, sample.pts, sample.data.([]byte))
|
||||
|
||||
default:
|
||||
panic("unexpected")
|
||||
|
||||
Reference in New Issue
Block a user