Eyevinn / mp2ts-tools

Tools for MPEG-2 TS
MIT License
14 stars 0 forks source link

More video statistics and optional nalu printout #7

Closed tobbee closed 7 months ago

tobbee commented 8 months ago

The tool should present the frame rate, video bitrate, and video GoP duration (with variation) in JSON format at the end of processing. It should also happen when pressing Ctrl-C (useful for live case when piping data to stdin).

Frame-rate is most easily obtained from video timestamps. If DTS is present, use that, otherwise use PTS to get a continuously growing timeline. There is also wrap-around since PTS/DTS is 33bits.

GoP-duration can be obtained from RAI-marker distance, but also from checking the NAL type (IDR distance). Collect and present both.

It may now happen that the timing information is more interesting than the nalu and SEI information, so make the latter optional by introducing two command line options:

-nalu -sei

Wkkkkk commented 7 months ago

Some outliners were found while testing.

For example, file bbb_1s.ts is parsed into

{"pid":256,"codec":"AVC","type":"video"}
{"pid":257,"codec":"AAC","type":"audio"}
{"pid":256,"parameterSet":"SPS","nr":0,"hex":"6764001facd9405005bb011000000300100000030300f1831960","length":26}
{"pid":256,"parameterSet":"PPS","nr":0,"hex":"68ebecb22c","length":5}
{"pid":256,"rai":true,"pts":133500,"dts":126000,"nalus":[{"type":"AUD_9","len":2},{"type":"SEI_6","len":701},{"type":"SPS_7","len":26},{"type":"PPS_8","len":5},{"type":"IDR_5","len":209}]}
{"pid":256,"rai":false,"pts":144750,"dts":129750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":34}]}
{"pid":256,"rai":false,"pts":137250,"dts":133500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":32}]}
{"pid":256,"rai":false,"pts":141000,"dts":137250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":32}]}
{"pid":256,"rai":false,"pts":148500,"dts":141000,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":48}]}
{"pid":256,"rai":false,"pts":152250,"dts":144750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":145}]}
{"pid":256,"rai":false,"pts":156000,"dts":148500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":204}]}
{"pid":256,"rai":false,"pts":163500,"dts":152250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":143}]}
{"pid":256,"rai":false,"pts":159750,"dts":156000,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":150}]}
{"pid":256,"rai":false,"pts":167250,"dts":159750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":315}]}
{"pid":256,"rai":false,"pts":171000,"dts":163500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":679}]}
{"SDT":[{"serviceId":1,"descriptors":[{"serviceName":"ts-info","providerName":"Eyevinn Technology"}]}]}
{"pid":256,"rai":false,"pts":174750,"dts":167250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":1718}]}
{"pid":256,"rai":false,"pts":182250,"dts":171000,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":918}]}
{"pid":256,"rai":false,"pts":178500,"dts":174750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":506}]}
{"pid":256,"rai":false,"pts":189750,"dts":178500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":1342}]}
{"pid":256,"rai":false,"pts":186000,"dts":182250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":704}]}
{"pid":256,"rai":false,"pts":201000,"dts":186000,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":6516}]}
{"pid":256,"rai":false,"pts":193500,"dts":189750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":1073}]}
{"pid":256,"rai":false,"pts":197250,"dts":193500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":1739}]}
{"pid":256,"rai":false,"pts":204750,"dts":197250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":12196}]}
{"pid":256,"rai":false,"pts":219750,"dts":201000,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":18657}]}
{"pid":256,"rai":false,"pts":212250,"dts":204750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":7371}]}
{"pid":256,"rai":false,"pts":208500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":3435}]}
{"pid":256,"rai":false,"pts":216000,"dts":212250,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":4061}]}
{"pid":256,"parameterSet":"SPS","nr":0,"hex":"6764001facd9405005bb011000000300100000030300f1831960","length":26}
{"pid":256,"parameterSet":"PPS","nr":0,"hex":"68ebecb22c","length":5}
{"pid":256,"rai":true,"pts":223500,"dts":216000,"nalus":[{"type":"AUD_9","len":2},{"type":"SPS_7","len":26},{"type":"PPS_8","len":5},{"type":"IDR_5","len":12975}]}
{"pid":256,"rai":false,"pts":234750,"dts":219750,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":24332}]}
{"streamType":"AVC","pid":256,"frameRate":23.04147465437788,"maxStep":7500,"minStep":3750,"avgStep":3906,"RAIGopDuration":1,"IDRGopDuration":1}

Here we noticed that dts is missing in

{"pid":256,"rai":false,"pts":208500,"nalus":[{"type":"AUD_9","len":2},{"type":"NonIDR_1","len":3435}]}

Since we are using dts to calculate frame rate, this outliner resulted in output "frameRate":23.04147465437788,"maxStep":7500,"minStep":3750

Similar outliners can be found in other ts files too.

Wkkkkk commented 7 months ago

It is important to handle PTS/DTS wrap-around by using right modulo operations. This must be included in tests (TODO).

Wkkkkk commented 7 months ago

An error field is added to statistics to indicate possible problems. Example messages are "PTS/DTS steps are not constant", or "Not enough PTS steps to calculate GOP duration".

To handle PTS/DTS wrap-arounds, we check for each timestamp step and pad it up if necessary. This is done by checking if a step is lower than -MAXSTEP/2, if so, we add it by MAXSTEP. Here MAXSTEP = 2^33 - 1.

Wkkkkk commented 7 months ago

maxPic should be 0 by default (infinite loops).

tobbee commented 7 months ago

Some useful functions for TS difference calculations could be

const (
    ptsWrap = 1 << 33
    pcrWrap = ptsWrap * 300
)

func SignedPTSDiff(p2, p1 int64) int64 {
    return (p2-p1+3*ptsWrap/2)%ptsWrap - ptsWrap/2
}

func UnsignedPTSDiff(p2, p1 int64) int64 {
    return (p2 - p1 + 2*ptsWrap) % ptsWrap
}

func AddPTS(p1, p2 int64) int64 {
    return (p1 + p2) % ptsWrap
}

func RewritePCR(p *packet.Packet, dtsOffset uint64) {
    deltaPCR := dtsOffset * 300
    pcrBytes, _ := adaptationfield.PCR(p)
    pcr := gots.ExtractPCR(pcrBytes)
    newPcr := (pcr + deltaPCR) % pcrWrap
    gots.InsertPCR(pcrBytes, newPcr)
}

// GetPCRValue - return PCR value or -1 if absent
func GetPCRValue(p *packet.Packet) int64 {
    if adaptationfield.HasPCR(p) {
        pcrBytes, _ := adaptationfield.PCR(p)
        pcr := gots.ExtractPCR(pcrBytes)
        return int64(pcr)
    }
    return -1
}

// SignedPCRDiff - calculate signed diff pcr1 - pcr2 considering wrap-around
func SignedPCRDiff(pcr1 int64, pcr2 int64) int64 {
    if pcr1-pcr2 < -pcrWrap/2 {
        return pcr1 - pcr2 + pcrWrap
    }
    if pcr1-pcr2 > pcrWrap/2 {
        return pcr1 - pcr2 - pcrWrap
    }
    return pcr1 - pcr2
}

func UnsignedPCRDiff(pcr1 int64, pcr2 int64) int64 {
    if pcr1-pcr2 < 0 {
        return pcr1 - pcr2 + pcrWrap
    }
    return pcr1 - pcr2
}