Skip to content

Commit

Permalink
feat: smpte-2038 support
Browse files Browse the repository at this point in the history
  • Loading branch information
tobbee committed Feb 23, 2024
1 parent 1b2c5c3 commit 27a6d60
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 19 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- Nothing yet
### Added

- SMPTE-2038 data option to mp2ts-tools

## [0.2.1] - 2024-01-25

Expand Down
4 changes: 3 additions & 1 deletion cmd/mp2ts-nallister/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import (

var usg = `Usage of %s:
%s generates a list of nalus with information about timestamps, rai, SEI etc.
%s generates a list of AVC/HEVC nalus with information about timestamps, rai, SEI etc.
It can further be used to generate a list of SMPTE-2038 data.
`

func parseOptions() internal.Options {
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: false, ShowNALU: true, ShowSEIDetails: false, ShowStatistics: true}
flag.IntVar(&opts.MaxNrPictures, "max", 0, "max nr pictures to parse")
flag.BoolVar(&opts.ShowSEIDetails, "sei", false, "print detailed sei message information")
flag.BoolVar(&opts.ShowSMPTE2038, "smpte2038", false, "print details about SMPTE-2038 data")
flag.BoolVar(&opts.Indent, "indent", false, "indent JSON output")
flag.BoolVar(&opts.Version, "version", false, "print version")

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.19

require (
github.com/Comcast/gots/v2 v2.2.1
github.com/Eyevinn/mp4ff v0.42.0
github.com/Eyevinn/mp4ff v0.42.1-0.20240221161741-c9bb3c122204
github.com/asticode/go-astits v1.13.0
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github.com/Comcast/gots/v2 v2.2.1 h1:LU/SRg7p2KQqVkNqInV7I4MOQKAqvWQP/PSSLtygP2s=
github.com/Comcast/gots/v2 v2.2.1/go.mod h1:firJ11on3eUiGHAhbY5cZNqG0OqhQ1+nSZHfsEEzVVU=
github.com/Eyevinn/mp4ff v0.42.0 h1:I85b/EDTkP77GsoBL8nRV6sfFKZhAXoP6oJHU8fv6kM=
github.com/Eyevinn/mp4ff v0.42.0/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
github.com/Eyevinn/mp4ff v0.42.1-0.20240221161741-c9bb3c122204 h1:hxaiwOKm+ooV4lH/UIfgBzi8/CGj3pRIYXnVU3uQQpc=
github.com/Eyevinn/mp4ff v0.42.1-0.20240221161741-c9bb3c122204/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
github.com/asticode/go-astikit v0.42.0 h1:pnir/2KLUSr0527Tv908iAH6EGYYrYta132vvjXsH5w=
github.com/asticode/go-astikit v0.42.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
Expand Down
3 changes: 3 additions & 0 deletions internal/avc.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ func ParseAVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o Options) (
})
}

if jp == nil {
return ps, nil
}
if firstPS {
for nr := range ps.spss {
jp.PrintPS(pid, "SPS", nr, ps.spsnalu, ps.spss[nr], o.VerbosePSInfo, o.ShowPS)
Expand Down
3 changes: 3 additions & 0 deletions internal/hevc.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ func ParseHEVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o Options)
Data: nil,
})
}
if jp == nil {
return ps, nil
}

if firstPS {
jp.PrintPS(pid, "VPS", 0, ps.vpsnalu, nil, o.VerbosePSInfo, o.ShowPS)
Expand Down
19 changes: 6 additions & 13 deletions internal/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,9 @@ dataLoop:
if pmtPID < 0 && d.PMT != nil {
// Loop through elementary streams
for _, es := range d.PMT.ElementaryStreams {
var streamInfo *ElementaryStreamInfo
switch es.StreamType {
case astits.StreamTypeH264Video:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "AVC", Type: "video"}
esKinds[es.ElementaryPID] = "AVC"
case astits.StreamTypeAACAudio:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "AAC", Type: "audio"}
esKinds[es.ElementaryPID] = "AAC"
case astits.StreamTypeH265Video:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "HEVC", Type: "video"}
esKinds[es.ElementaryPID] = "HEVC"
}

streamInfo := ParseAstitsElementaryStreamInfo(es)
if streamInfo != nil {
esKinds[es.ElementaryPID] = streamInfo.Codec
jp.Print(streamInfo, o.ShowStreamInfo)
}
}
Expand Down Expand Up @@ -107,6 +96,10 @@ dataLoop:
}
nrPics++
statistics[d.PID] = &hevcPS.Statistics
case "SMPTE-2038":
if o.ShowSMPTE2038 {
ParseSMPTE2038(jp, d, o)
}
default:
// Skip unknown elementary streams
continue
Expand Down
104 changes: 104 additions & 0 deletions internal/smpte2038.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package internal

import (
"bytes"
"fmt"
"io"
"log"

"github.com/Eyevinn/mp4ff/bits"
"github.com/asticode/go-astits"
)

// didMap maps SMPTE-20348 [did] values registered by SMPTE
// [dids]: https://smpte-ra.org/smpte-ancillary-data-smpte-st-291
type SMPTE291Identifier struct {
did, sdid byte
}

var SMPTE291Map = map[SMPTE291Identifier]string{
{0x41, 0x7}: "ANSI/SCTE 104 messages",
{0x41, 0x5}: "AFD and Bar Data",
{0x41, 0x8}: "DVB/SCTE VBI data",
{0x61, 0x1}: "EIA 708B Data mapping into VANC space",
{0x61, 0x2}: "EIA 608 Data mapping into VANC space",
}

type smpte2038Data struct {
PID uint16 `json:"pid"`
PTS int64 `json:"pts"`
Entries []smpte2038Entry
}

type smpte2038Entry struct {
LineNr byte `json:"lineNr"`
HorOffset byte `json:"horOffset"`
DID byte `json:"did"`
SDID byte `json:"sdid"`
DataCount byte `json:"dataCount"`
Type string `json:"type"`
}

func ParseSMPTE2038(jp *JsonPrinter, d *astits.DemuxerData, o Options) {
pl := d.PES.Data
pdtDtsIndicator := d.PES.Header.OptionalHeader.PTSDTSIndicator
if pdtDtsIndicator != 2 {
fmt.Printf("SMPTE-2038: invalid PDT_DTS_Indicator=%d\n", pdtDtsIndicator)
}
pts := d.PES.Header.OptionalHeader.PTS
rd := bytes.NewBuffer(pl)
r := bits.NewReader(rd)
smpteData := smpte2038Data{PID: d.PID, PTS: pts.Base}
for {
z := r.Read(6)
if r.AccError() == io.EOF {
break
}
if z == 0xffffffffffff {
z2 := r.Read(2)
if z2 != 0x3 {
log.Printf("SMPTE-2038: invalid stuffing\n")
return
}
_ = r.ReadRemainingBytes()
}
if z != 0 {
log.Printf("SMPTE-2038: reserved bits not zero %x\n", z)
return
}
_ = r.Read(1) // cNotYChFlag
lineNr := r.Read(11)
horOffset := r.Read(12)
did := r.Read(10)
did = did & 0xff // 8 bits
sdid := r.Read(10)
sdid = sdid & 0xff // 8 bits
didStr := SMPTE291Map[SMPTE291Identifier{byte(did), byte(sdid)}]
if didStr == "" {
didStr = "unknown SID/DID"
}
dataCount := int(r.Read(10)) & 0xff // 8 bits
for j := 0; j < dataCount; j++ {
_ = r.Read(10)
}
_ = r.Read(10) // checkSumWord
if r.NrBitsReadInCurrentByte() != 8 {
_ = r.Read(8 - r.NrBitsReadInCurrentByte())
}
if r.AccError() != nil {
fmt.Printf("SMPTE-2038: read error\n")
return
}
smpteData.Entries = append(smpteData.Entries, smpte2038Entry{
LineNr: byte(lineNr),
HorOffset: byte(horOffset),
DID: byte(did),
SDID: byte(sdid),
DataCount: byte(dataCount),
Type: didStr,
})
}
if jp != nil {
jp.Print(smpteData, true)
}
}
39 changes: 38 additions & 1 deletion internal/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internal

import (
"context"
"encoding/hex"
"errors"
"flag"
"fmt"
Expand Down Expand Up @@ -30,6 +31,7 @@ type Options struct {
VerbosePSInfo bool
ShowNALU bool
ShowSEIDetails bool
ShowSMPTE2038 bool
ShowSCTE35 bool
ShowStatistics bool
FilterPids bool
Expand All @@ -38,9 +40,14 @@ type Options struct {
}

func CreateFullOptions(max int) Options {
return Options{MaxNrPictures: max, ShowStreamInfo: true, ShowService: true, ShowPS: true, ShowNALU: true, ShowSEIDetails: true, ShowStatistics: true}
return Options{MaxNrPictures: max, ShowStreamInfo: true, ShowService: true, ShowPS: true, ShowNALU: true, ShowSEIDetails: true, ShowSMPTE2038: true, ShowStatistics: true}
}

const (
ANC_REGISTERED_IDENTIFIER = 0x56414E43
ANC_DESCRIPTOR_TAG = 0xC4
)

type OptionParseFunc func() Options
type RunableFunc func(ctx context.Context, w io.Writer, f io.Reader, o Options) error

Expand Down Expand Up @@ -127,6 +134,36 @@ func ParseAstitsElementaryStreamInfo(es *astits.PMTElementaryStream) *Elementary
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "HEVC", Type: "video"}
case astits.StreamTypeSCTE35:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "SCTE35", Type: "cue"}
case astits.StreamTypePrivateData:
streamInfo = &ElementaryStreamInfo{PID: es.ElementaryPID, Codec: "PrivateData", Type: "data"}
default:
return nil
}
for _, d := range es.ElementaryStreamDescriptors {
switch d.Tag {
case astits.DescriptorTagISO639LanguageAndAudioType:
l := d.ISO639LanguageAndAudioType
fmt.Printf("Language: %s\n", l.Language)
case astits.DescriptorTagDataStreamAlignment:
a := d.DataStreamAlignment
log.Printf("PID %d: Descriptor Data stream alignment: %d\n", es.ElementaryPID, a.Type)
case astits.DescriptorTagRegistration:
r := d.Registration
switch r.FormatIdentifier {
case ANC_REGISTERED_IDENTIFIER:
streamInfo.Codec = "SMPTE-2038"
streamInfo.Type = "ANC"
}
case ANC_DESCRIPTOR_TAG:
if streamInfo.Type != "ANC" {
log.Printf("PID %d: bad combination of descriptor 0xc4 and no preceding ANC", es.ElementaryPID)
continue
}
u := d.UserDefined
log.Printf("PID %d: Got ancillary descriptor with data: %q\n", es.ElementaryPID, hex.EncodeToString(u))
default:
// Nothing
}
}

return streamInfo
Expand Down

0 comments on commit 27a6d60

Please sign in to comment.