Skip to content

Commit bb57803

Browse files
authored
Nicer and more complete SEI output (#14)
Support for HEVC PicTiming SEI message SEI message data is now also printed as JSON mp2ts-info and mp2ts-pslister now always print indented output mp2ts-nallister -sei option now turns on details Refactored structure to put all files in internal package for now.
1 parent f023955 commit bb57803

23 files changed

+138
-114
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Calculate frame-rate based on DTS/PTS and print out in JSON format
1515
- Enable NALU/SEI printing by option
1616
- Print SDT in JSON format
17+
- Support for HEVC PicTiming SEI message
18+
- SEI message data is now also printed as JSON
19+
20+
## Changed
21+
22+
- mp2ts-info and mp2ts-pslister now always print indented output
23+
- mp2ts-nallister -sei option now turns on details.
1724

1825
## [0.1.0] - 2024-01-15
1926

cmd/mp2ts-info/main.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111

1212
"github.com/Eyevinn/mp2ts-tools/internal"
13-
"github.com/Eyevinn/mp2ts-tools/internal/avc"
1413
)
1514

1615
var usg = `Usage of %s:
@@ -19,9 +18,8 @@ var usg = `Usage of %s:
1918
`
2019

2120
func parseOptions() internal.Options {
22-
opts := internal.Options{ShowStreamInfo: true}
21+
opts := internal.Options{ShowStreamInfo: true, Indent: true}
2322
flag.BoolVar(&opts.ShowService, "service", false, "show service information")
24-
flag.BoolVar(&opts.Indent, "indent", true, "indent JSON output")
2523
flag.BoolVar(&opts.Version, "version", false, "print version")
2624

2725
flag.Usage = func() {
@@ -37,7 +35,7 @@ func parseOptions() internal.Options {
3735
}
3836

3937
func parseInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
40-
return avc.ParseInfo(ctx, w, f, o)
38+
return internal.ParseInfo(ctx, w, f, o)
4139
}
4240

4341
func main() {

cmd/mp2ts-nallister/main.go

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111

1212
"github.com/Eyevinn/mp2ts-tools/internal"
13-
"github.com/Eyevinn/mp2ts-tools/internal/avc"
1413
)
1514

1615
var usg = `Usage of %s:
@@ -19,9 +18,9 @@ var usg = `Usage of %s:
1918
`
2019

2120
func parseOptions() internal.Options {
22-
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: false, ShowNALU: true, ShowSEI: false, ShowStatistics: true}
21+
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: false, ShowNALU: true, ShowSEIDetails: false, ShowStatistics: true}
2322
flag.IntVar(&opts.MaxNrPictures, "max", 0, "max nr pictures to parse")
24-
flag.BoolVar(&opts.ShowSEI, "sei", false, "print sei messages")
23+
flag.BoolVar(&opts.ShowSEIDetails, "sei", false, "print detailed sei message information")
2524
flag.BoolVar(&opts.Indent, "indent", false, "indent JSON output")
2625
flag.BoolVar(&opts.Version, "version", false, "print version")
2726

@@ -38,7 +37,7 @@ func parseOptions() internal.Options {
3837
}
3938

4039
func parseNALUInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
41-
return avc.ParseAll(ctx, w, f, o)
40+
return internal.ParseAll(ctx, w, f, o)
4241
}
4342

4443
func main() {

cmd/mp2ts-pslister/main.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111

1212
"github.com/Eyevinn/mp2ts-tools/internal"
13-
"github.com/Eyevinn/mp2ts-tools/internal/avc"
1413
)
1514

1615
var usg = `Usage of %s:
@@ -19,10 +18,9 @@ var usg = `Usage of %s:
1918
`
2019

2120
func parseOptions() internal.Options {
22-
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: true, ShowNALU: false, ShowSEI: false, ShowStatistics: false}
21+
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: true, Indent: true, ShowNALU: false, ShowSEIDetails: false, ShowStatistics: false}
2322
flag.IntVar(&opts.MaxNrPictures, "max", 0, "max nr pictures to parse")
2423
flag.BoolVar(&opts.VerbosePSInfo, "ps", false, "show verbose information")
25-
flag.BoolVar(&opts.Indent, "indent", true, "indent JSON output")
2624
flag.BoolVar(&opts.Version, "version", false, "print version")
2725

2826
flag.Usage = func() {
@@ -38,7 +36,7 @@ func parseOptions() internal.Options {
3836
}
3937

4038
func parsePSInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
41-
return avc.ParseAll(ctx, w, f, o)
39+
return internal.ParseAll(ctx, w, f, o)
4240
}
4341

4442
func main() {

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/Eyevinn/mp2ts-tools
33
go 1.19
44

55
require (
6-
github.com/Eyevinn/mp4ff v0.41.0
6+
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0
77
github.com/asticode/go-astits v1.13.0
88
github.com/stretchr/testify v1.8.4
99
)

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
github.com/Eyevinn/mp4ff v0.41.0 h1:EdXMeorCcuzMrnHShC7xX5dyNVYAkgp2lwK/ed64JKk=
2-
github.com/Eyevinn/mp4ff v0.41.0/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
1+
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0 h1:TVO5vjF4CEAB6OKR0RWMQFvHXtZ+UdKCsp+awECiIVE=
2+
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
33
github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
44
github.com/asticode/go-astikit v0.42.0 h1:pnir/2KLUSr0527Tv908iAH6EGYYrYta132vvjXsH5w=
55
github.com/asticode/go-astikit v0.42.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=

internal/avc/avc.go internal/avc.go

+34-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package avc
1+
package internal
22

33
import (
44
"fmt"
5-
"strings"
65

7-
"github.com/Eyevinn/mp2ts-tools/internal"
86
"github.com/Eyevinn/mp4ff/avc"
97
"github.com/Eyevinn/mp4ff/sei"
108
"github.com/asticode/go-astits"
@@ -15,11 +13,18 @@ type AvcPS struct {
1513
ppss map[uint32]*avc.PPS
1614
spsnalu []byte
1715
ppsnalus [][]byte
18-
Statistics internal.StreamStatistics
16+
Statistics StreamStatistics
1917
}
2018

2119
func (a *AvcPS) getSPS() *avc.SPS {
22-
return a.spss[0]
20+
if len(a.spss) == 0 {
21+
return nil
22+
}
23+
for _, sps := range a.spss {
24+
return sps
25+
}
26+
// Not reachable
27+
return nil
2328
}
2429

2530
func (a *AvcPS) setSPS(nalu []byte) error {
@@ -50,14 +55,14 @@ func (a *AvcPS) setPPS(nalu []byte) error {
5055
return nil
5156
}
5257

53-
func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o internal.Options) (*AvcPS, error) {
58+
func ParseAVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o Options) (*AvcPS, error) {
5459
pid := d.PID
5560
pes := d.PES
5661
fp := d.FirstPacket
5762
if pes.Header.OptionalHeader.PTS == nil {
5863
return nil, fmt.Errorf("no PTS in PES")
5964
}
60-
nfd := internal.NaluFrameData{
65+
nfd := NaluFrameData{
6166
PID: pid,
6267
}
6368
if ps == nil {
@@ -89,7 +94,7 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
8994
nalus := avc.ExtractNalusFromByteStream(data)
9095
firstPS := false
9196
for _, nalu := range nalus {
92-
seiMsg := ""
97+
var data any
9398
naluType := avc.GetNaluType(nalu[0])
9499
switch naluType {
95100
case avc.NALU_SPS:
@@ -108,25 +113,33 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
108113
}
109114
}
110115
case avc.NALU_SEI:
111-
if !o.ShowSEI {
112-
continue
113-
}
114-
var sps *avc.SPS
115-
if firstPS {
116-
sps = ps.getSPS()
117-
}
116+
sps := ps.getSPS()
118117
msgs, err := avc.ParseSEINalu(nalu, sps)
119118
if err != nil {
120119
return nil, err
121120
}
122-
seiTexts := make([]string, 0, len(msgs))
121+
parts := make([]SeiOut, 0, len(msgs))
123122
for _, msg := range msgs {
124-
if msg.Type() == sei.SEIPicTimingType {
123+
t := sei.SEIType(msg.Type())
124+
if t == sei.SEIPicTimingType {
125125
pt := msg.(*sei.PicTimingAvcSEI)
126-
seiTexts = append(seiTexts, fmt.Sprintf("Type 1: %s", pt.Clocks[0]))
126+
if o.ShowSEIDetails && sps != nil {
127+
parts = append(parts, SeiOut{
128+
Msg: t.String(),
129+
Payload: pt,
130+
})
131+
} else {
132+
parts = append(parts, SeiOut{Msg: t.String()})
133+
}
134+
} else {
135+
if o.ShowSEIDetails {
136+
parts = append(parts, SeiOut{Msg: t.String(), Payload: msg})
137+
} else {
138+
parts = append(parts, SeiOut{Msg: t.String()})
139+
}
127140
}
128141
}
129-
seiMsg = strings.Join(seiTexts, ", ")
142+
data = parts
130143
case avc.NALU_IDR, avc.NALU_NON_IDR:
131144
if naluType == avc.NALU_IDR {
132145
ps.Statistics.IDRPTS = append(ps.Statistics.IDRPTS, pts.Base)
@@ -136,10 +149,10 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
136149
nfd.ImgType = fmt.Sprintf("[%s]", sliceType)
137150
}
138151
}
139-
nfd.NALUS = append(nfd.NALUS, internal.NaluData{
152+
nfd.NALUS = append(nfd.NALUS, NaluData{
140153
Type: naluType.String(),
141154
Len: len(nalu),
142-
Data: seiMsg,
155+
Data: data,
143156
})
144157
}
145158

internal/hevc/hevc.go internal/hevc.go

+36-41
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package hevc
1+
package internal
22

33
import (
4-
"bytes"
54
"fmt"
65

7-
"github.com/Eyevinn/mp2ts-tools/internal"
86
"github.com/Eyevinn/mp4ff/avc"
97
"github.com/Eyevinn/mp4ff/hevc"
108
"github.com/Eyevinn/mp4ff/sei"
@@ -17,7 +15,7 @@ type HevcPS struct {
1715
vpsnalu []byte
1816
spsnalu []byte
1917
ppsnalus [][]byte
20-
Statistics internal.StreamStatistics
18+
Statistics StreamStatistics
2119
}
2220

2321
func (a *HevcPS) setSPS(nalu []byte) error {
@@ -48,14 +46,14 @@ func (a *HevcPS) setPPS(nalu []byte) error {
4846
return nil
4947
}
5048

51-
func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o internal.Options) (*HevcPS, error) {
49+
func ParseHEVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o Options) (*HevcPS, error) {
5250
pid := d.PID
5351
pes := d.PES
5452
fp := d.FirstPacket
5553
if pes.Header.OptionalHeader.PTS == nil {
5654
return nil, fmt.Errorf("no PTS in PES")
5755
}
58-
nfd := internal.NaluFrameData{
56+
nfd := NaluFrameData{
5957
PID: pid,
6058
}
6159
if ps == nil {
@@ -87,43 +85,12 @@ func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o
8785
firstPS := false
8886
for _, nalu := range avc.ExtractNalusFromByteStream(data) {
8987
naluType := hevc.GetNaluType(nalu[0])
90-
// Handle SEI messages separately
91-
if naluType == hevc.NALU_SEI_PREFIX || naluType == hevc.NALU_SEI_SUFFIX {
92-
if !o.ShowSEI {
93-
continue
94-
}
95-
var hdrLen = 2
96-
seiBytes := nalu[hdrLen:]
97-
buf := bytes.NewReader(seiBytes)
98-
seiDatas, err := sei.ExtractSEIData(buf)
99-
if err != nil {
100-
return nil, err
101-
}
102-
103-
for _, seiData := range seiDatas {
104-
var seiMsg sei.SEIMessage
105-
seiMsg, err = sei.DecodeSEIMessage(&seiData, sei.HEVC)
106-
if err != nil {
107-
fmt.Printf("SEI: Got error %q\n", err)
108-
continue
109-
}
110-
111-
nfd.NALUS = append(nfd.NALUS, internal.NaluData{
112-
Type: naluType.String(),
113-
Len: len(nalu),
114-
Data: seiMsg.String(),
115-
})
116-
}
117-
118-
continue
119-
}
120-
121-
// Handle other NALUs
12288
switch naluType {
12389
case hevc.NALU_VPS:
12490
ps.vpsnalu = nalu
91+
firstPS = true
12592
case hevc.NALU_SPS:
126-
if !firstPS {
93+
if firstPS {
12794
err := ps.setSPS(nalu)
12895
if err != nil {
12996
return nil, fmt.Errorf("cannot set SPS")
@@ -137,13 +104,41 @@ func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o
137104
return nil, fmt.Errorf("cannot set PPS")
138105
}
139106
}
107+
case hevc.NALU_SEI_PREFIX, hevc.NALU_SEI_SUFFIX:
108+
109+
var seiData any
110+
if o.ShowSEIDetails && len(ps.spss) > 0 {
111+
sps := ps.spss[0]
112+
seiMessages, err := hevc.ParseSEINalu(nalu, sps)
113+
if err != nil {
114+
return nil, fmt.Errorf("cannot parse SEI NALU")
115+
}
116+
parts := make([]SeiOut, 0, len(seiMessages))
117+
for _, seiMsg := range seiMessages {
118+
var payload any
119+
switch seiMsg.Type() {
120+
case sei.SEIPicTimingType:
121+
payload = seiMsg.(*sei.PicTimingHevcSEI)
122+
}
123+
parts = append(parts, SeiOut{Msg: sei.SEIType(seiMsg.Type()).String(), Payload: payload})
124+
}
125+
seiData = parts
126+
} else {
127+
seiData = nil // hex.EncodeToString(nalu)
128+
}
129+
nfd.NALUS = append(nfd.NALUS, NaluData{
130+
Type: naluType.String(),
131+
Len: len(nalu),
132+
Data: seiData,
133+
})
134+
continue
140135
case hevc.NALU_IDR_W_RADL, hevc.NALU_IDR_N_LP:
141136
ps.Statistics.IDRPTS = append(ps.Statistics.IDRPTS, pts.Base)
142137
}
143-
nfd.NALUS = append(nfd.NALUS, internal.NaluData{
138+
nfd.NALUS = append(nfd.NALUS, NaluData{
144139
Type: naluType.String(),
145140
Len: len(nalu),
146-
Data: "",
141+
Data: nil,
147142
})
148143
}
149144

internal/nalu.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,10 @@ type NaluFrameData struct {
1212
type NaluData struct {
1313
Type string `json:"type"`
1414
Len int `json:"len"`
15-
Data string `json:"data,omitempty"`
15+
Data any `json:"data,omitempty"`
16+
}
17+
18+
type SeiOut struct {
19+
Msg string `json:"msg"`
20+
Payload any `json:"payload,omitempty"`
1621
}

0 commit comments

Comments
 (0)