Skip to content

Commit

Permalink
Nicer and more complete SEI output (#14)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tobbee authored Jan 23, 2024
1 parent f023955 commit bb57803
Show file tree
Hide file tree
Showing 23 changed files with 138 additions and 114 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Calculate frame-rate based on DTS/PTS and print out in JSON format
- Enable NALU/SEI printing by option
- Print SDT in JSON format
- Support for HEVC PicTiming SEI message
- SEI message data is now also printed as JSON

## Changed

- mp2ts-info and mp2ts-pslister now always print indented output
- mp2ts-nallister -sei option now turns on details.

## [0.1.0] - 2024-01-15

Expand Down
6 changes: 2 additions & 4 deletions cmd/mp2ts-info/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

"github.com/Eyevinn/mp2ts-tools/internal"
"github.com/Eyevinn/mp2ts-tools/internal/avc"
)

var usg = `Usage of %s:
Expand All @@ -19,9 +18,8 @@ var usg = `Usage of %s:
`

func parseOptions() internal.Options {
opts := internal.Options{ShowStreamInfo: true}
opts := internal.Options{ShowStreamInfo: true, Indent: true}
flag.BoolVar(&opts.ShowService, "service", false, "show service information")
flag.BoolVar(&opts.Indent, "indent", true, "indent JSON output")
flag.BoolVar(&opts.Version, "version", false, "print version")

flag.Usage = func() {
Expand All @@ -37,7 +35,7 @@ func parseOptions() internal.Options {
}

func parseInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
return avc.ParseInfo(ctx, w, f, o)
return internal.ParseInfo(ctx, w, f, o)
}

func main() {
Expand Down
7 changes: 3 additions & 4 deletions cmd/mp2ts-nallister/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

"github.com/Eyevinn/mp2ts-tools/internal"
"github.com/Eyevinn/mp2ts-tools/internal/avc"
)

var usg = `Usage of %s:
Expand All @@ -19,9 +18,9 @@ var usg = `Usage of %s:
`

func parseOptions() internal.Options {
opts := internal.Options{ShowStreamInfo: true, ShowService: false, ShowPS: false, ShowNALU: true, ShowSEI: false, ShowStatistics: true}
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.ShowSEI, "sei", false, "print sei messages")
flag.BoolVar(&opts.ShowSEIDetails, "sei", false, "print detailed sei message information")
flag.BoolVar(&opts.Indent, "indent", false, "indent JSON output")
flag.BoolVar(&opts.Version, "version", false, "print version")

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

func parseNALUInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
return avc.ParseAll(ctx, w, f, o)
return internal.ParseAll(ctx, w, f, o)
}

func main() {
Expand Down
6 changes: 2 additions & 4 deletions cmd/mp2ts-pslister/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"strings"

"github.com/Eyevinn/mp2ts-tools/internal"
"github.com/Eyevinn/mp2ts-tools/internal/avc"
)

var usg = `Usage of %s:
Expand All @@ -19,10 +18,9 @@ var usg = `Usage of %s:
`

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

flag.Usage = func() {
Expand All @@ -38,7 +36,7 @@ func parseOptions() internal.Options {
}

func parsePSInfo(ctx context.Context, w io.Writer, f io.Reader, o internal.Options) error {
return avc.ParseAll(ctx, w, f, o)
return internal.ParseAll(ctx, w, f, o)
}

func main() {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/Eyevinn/mp2ts-tools
go 1.19

require (
github.com/Eyevinn/mp4ff v0.41.0
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0
github.com/asticode/go-astits v1.13.0
github.com/stretchr/testify v1.8.4
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/Eyevinn/mp4ff v0.41.0 h1:EdXMeorCcuzMrnHShC7xX5dyNVYAkgp2lwK/ed64JKk=
github.com/Eyevinn/mp4ff v0.41.0/go.mod h1:w/6GSa5ghZ1VavzJK6McQ2/flx8mKtcrKDr11SsEweA=
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0 h1:TVO5vjF4CEAB6OKR0RWMQFvHXtZ+UdKCsp+awECiIVE=
github.com/Eyevinn/mp4ff v0.41.1-0.20240123164056-bbd4656aecc0/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
55 changes: 34 additions & 21 deletions internal/avc/avc.go → internal/avc.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package avc
package internal

import (
"fmt"
"strings"

"github.com/Eyevinn/mp2ts-tools/internal"
"github.com/Eyevinn/mp4ff/avc"
"github.com/Eyevinn/mp4ff/sei"
"github.com/asticode/go-astits"
Expand All @@ -15,11 +13,18 @@ type AvcPS struct {
ppss map[uint32]*avc.PPS
spsnalu []byte
ppsnalus [][]byte
Statistics internal.StreamStatistics
Statistics StreamStatistics
}

func (a *AvcPS) getSPS() *avc.SPS {
return a.spss[0]
if len(a.spss) == 0 {
return nil
}
for _, sps := range a.spss {
return sps
}
// Not reachable
return nil
}

func (a *AvcPS) setSPS(nalu []byte) error {
Expand Down Expand Up @@ -50,14 +55,14 @@ func (a *AvcPS) setPPS(nalu []byte) error {
return nil
}

func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o internal.Options) (*AvcPS, error) {
func ParseAVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o Options) (*AvcPS, error) {
pid := d.PID
pes := d.PES
fp := d.FirstPacket
if pes.Header.OptionalHeader.PTS == nil {
return nil, fmt.Errorf("no PTS in PES")
}
nfd := internal.NaluFrameData{
nfd := NaluFrameData{
PID: pid,
}
if ps == nil {
Expand Down Expand Up @@ -89,7 +94,7 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
nalus := avc.ExtractNalusFromByteStream(data)
firstPS := false
for _, nalu := range nalus {
seiMsg := ""
var data any
naluType := avc.GetNaluType(nalu[0])
switch naluType {
case avc.NALU_SPS:
Expand All @@ -108,25 +113,33 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
}
}
case avc.NALU_SEI:
if !o.ShowSEI {
continue
}
var sps *avc.SPS
if firstPS {
sps = ps.getSPS()
}
sps := ps.getSPS()
msgs, err := avc.ParseSEINalu(nalu, sps)
if err != nil {
return nil, err
}
seiTexts := make([]string, 0, len(msgs))
parts := make([]SeiOut, 0, len(msgs))
for _, msg := range msgs {
if msg.Type() == sei.SEIPicTimingType {
t := sei.SEIType(msg.Type())
if t == sei.SEIPicTimingType {
pt := msg.(*sei.PicTimingAvcSEI)
seiTexts = append(seiTexts, fmt.Sprintf("Type 1: %s", pt.Clocks[0]))
if o.ShowSEIDetails && sps != nil {
parts = append(parts, SeiOut{
Msg: t.String(),
Payload: pt,
})
} else {
parts = append(parts, SeiOut{Msg: t.String()})
}
} else {
if o.ShowSEIDetails {
parts = append(parts, SeiOut{Msg: t.String(), Payload: msg})
} else {
parts = append(parts, SeiOut{Msg: t.String()})
}
}
}
seiMsg = strings.Join(seiTexts, ", ")
data = parts
case avc.NALU_IDR, avc.NALU_NON_IDR:
if naluType == avc.NALU_IDR {
ps.Statistics.IDRPTS = append(ps.Statistics.IDRPTS, pts.Base)
Expand All @@ -136,10 +149,10 @@ func ParseAVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *AvcPS, o i
nfd.ImgType = fmt.Sprintf("[%s]", sliceType)
}
}
nfd.NALUS = append(nfd.NALUS, internal.NaluData{
nfd.NALUS = append(nfd.NALUS, NaluData{
Type: naluType.String(),
Len: len(nalu),
Data: seiMsg,
Data: data,
})
}

Expand Down
77 changes: 36 additions & 41 deletions internal/hevc/hevc.go → internal/hevc.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package hevc
package internal

import (
"bytes"
"fmt"

"github.com/Eyevinn/mp2ts-tools/internal"
"github.com/Eyevinn/mp4ff/avc"
"github.com/Eyevinn/mp4ff/hevc"
"github.com/Eyevinn/mp4ff/sei"
Expand All @@ -17,7 +15,7 @@ type HevcPS struct {
vpsnalu []byte
spsnalu []byte
ppsnalus [][]byte
Statistics internal.StreamStatistics
Statistics StreamStatistics
}

func (a *HevcPS) setSPS(nalu []byte) error {
Expand Down Expand Up @@ -48,14 +46,14 @@ func (a *HevcPS) setPPS(nalu []byte) error {
return nil
}

func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o internal.Options) (*HevcPS, error) {
func ParseHEVCPES(jp *JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o Options) (*HevcPS, error) {
pid := d.PID
pes := d.PES
fp := d.FirstPacket
if pes.Header.OptionalHeader.PTS == nil {
return nil, fmt.Errorf("no PTS in PES")
}
nfd := internal.NaluFrameData{
nfd := NaluFrameData{
PID: pid,
}
if ps == nil {
Expand Down Expand Up @@ -87,43 +85,12 @@ func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o
firstPS := false
for _, nalu := range avc.ExtractNalusFromByteStream(data) {
naluType := hevc.GetNaluType(nalu[0])
// Handle SEI messages separately
if naluType == hevc.NALU_SEI_PREFIX || naluType == hevc.NALU_SEI_SUFFIX {
if !o.ShowSEI {
continue
}
var hdrLen = 2
seiBytes := nalu[hdrLen:]
buf := bytes.NewReader(seiBytes)
seiDatas, err := sei.ExtractSEIData(buf)
if err != nil {
return nil, err
}

for _, seiData := range seiDatas {
var seiMsg sei.SEIMessage
seiMsg, err = sei.DecodeSEIMessage(&seiData, sei.HEVC)
if err != nil {
fmt.Printf("SEI: Got error %q\n", err)
continue
}

nfd.NALUS = append(nfd.NALUS, internal.NaluData{
Type: naluType.String(),
Len: len(nalu),
Data: seiMsg.String(),
})
}

continue
}

// Handle other NALUs
switch naluType {
case hevc.NALU_VPS:
ps.vpsnalu = nalu
firstPS = true
case hevc.NALU_SPS:
if !firstPS {
if firstPS {
err := ps.setSPS(nalu)
if err != nil {
return nil, fmt.Errorf("cannot set SPS")
Expand All @@ -137,13 +104,41 @@ func ParseHEVCPES(jp *internal.JsonPrinter, d *astits.DemuxerData, ps *HevcPS, o
return nil, fmt.Errorf("cannot set PPS")
}
}
case hevc.NALU_SEI_PREFIX, hevc.NALU_SEI_SUFFIX:

var seiData any
if o.ShowSEIDetails && len(ps.spss) > 0 {
sps := ps.spss[0]
seiMessages, err := hevc.ParseSEINalu(nalu, sps)
if err != nil {
return nil, fmt.Errorf("cannot parse SEI NALU")
}
parts := make([]SeiOut, 0, len(seiMessages))
for _, seiMsg := range seiMessages {
var payload any
switch seiMsg.Type() {
case sei.SEIPicTimingType:
payload = seiMsg.(*sei.PicTimingHevcSEI)
}
parts = append(parts, SeiOut{Msg: sei.SEIType(seiMsg.Type()).String(), Payload: payload})
}
seiData = parts
} else {
seiData = nil // hex.EncodeToString(nalu)
}
nfd.NALUS = append(nfd.NALUS, NaluData{
Type: naluType.String(),
Len: len(nalu),
Data: seiData,
})
continue
case hevc.NALU_IDR_W_RADL, hevc.NALU_IDR_N_LP:
ps.Statistics.IDRPTS = append(ps.Statistics.IDRPTS, pts.Base)
}
nfd.NALUS = append(nfd.NALUS, internal.NaluData{
nfd.NALUS = append(nfd.NALUS, NaluData{
Type: naluType.String(),
Len: len(nalu),
Data: "",
Data: nil,
})
}

Expand Down
7 changes: 6 additions & 1 deletion internal/nalu.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ type NaluFrameData struct {
type NaluData struct {
Type string `json:"type"`
Len int `json:"len"`
Data string `json:"data,omitempty"`
Data any `json:"data,omitempty"`
}

type SeiOut struct {
Msg string `json:"msg"`
Payload any `json:"payload,omitempty"`
}
Loading

0 comments on commit bb57803

Please sign in to comment.