asticode / go-astits

Demux and mux MPEG Transport Streams (.ts) natively in GO

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Incorrect calculation of duration compared to ffprobe.

jidckii opened this issue · comments

Hey.
Thanks for the project)

I need to get the duration of the file, but I’m not getting the correct value compared to ffprobe.

Here is an example of my code and file
videoAndAudio.zip

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	astits "github.com/asticode/go-astits"
)

var (
	tsfile  = flag.String("f", "", "path to ts file")
	vebrose = flag.Bool("vebrose", false, "vebrose duration")
)

func main() {
	flag.Parse()

	// Create a cancellable context in case you want to stop reading packets/data any time you want
	ctx, cancel := context.WithCancel(context.Background())

	// Handle SIGTERM signal
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGTERM)
	go func() {
		<-ch
		cancel()
	}()

	durationBasedPTS, err := GetChunkFileDurationPTS(ctx, *tsfile, *vebrose)
	if err != nil {
		fmt.Printf("Failed to open file: %v\n", err)
		return
	}
	fmt.Println("Chunk duration PTS based:", durationBasedPTS.Nanoseconds())
	durationBasedPCR, err := GetChunkFileDurationPCR(ctx, *tsfile, *vebrose)
	if err != nil {
		fmt.Printf("Failed to open file: %v\n", err)
		return
	}
	fmt.Println("Chunk duration PCR based:", durationBasedPCR.Nanoseconds())
}

func GetChunkFileDurationPTS(ctx context.Context, filename string, vebrose bool) (time.Duration, error) {
	var (
		firstPTSDuration time.Duration
		lastPTSDuration  time.Duration
	)

	// Open your file or initialize any kind of io.Reader
	f, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	// Create the demuxer
	dmx := astits.New(ctx, f)
	for {
		d, err := dmx.NextData()
		if err != nil {
			if err == astits.ErrNoMorePackets {
				duration := lastPTSDuration - firstPTSDuration
				return duration, nil
			}
			// Если есть ошибка и это не индикатор окончания данных, то
			// возвращаем саму ошибку
			return 0, err
		}

		if d.PES != nil {
			ptsDuration := d.PES.Header.OptionalHeader.PTS.Duration()
			if vebrose {
				fmt.Println(ptsDuration.Nanoseconds())
			}
			if firstPTSDuration == 0 {
				firstPTSDuration = ptsDuration
			} else {
				lastPTSDuration = ptsDuration
			}
		}
	}
}

func GetChunkFileDurationPCR(ctx context.Context, filename string, vebrose bool) (time.Duration, error) {
	var (
		firstPCRDuration time.Duration
		lastPCRDuration  time.Duration
	)

	// Open your file or initialize any kind of io.Reader
	f, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	defer f.Close()
	// Create the demuxer
	dmx := astits.New(ctx, f)
	for {
		d, err := dmx.NextPacket()
		if err != nil {
			if err == astits.ErrNoMorePackets {
				duration := lastPCRDuration - firstPCRDuration
				return duration, nil
			}
			// Если есть ошибка и это не индикатор окончания данных, то
			// возвращаем саму ошибку
			return 0, err
		}
		// fmt.Println(d.AdaptationField.)
		if d.Header.HasAdaptationField {
			if d.AdaptationField.HasPCR {
				pcrDuration := d.AdaptationField.PCR.Duration()
				if vebrose {
					fmt.Println(pcrDuration.Nanoseconds())
				}
				if firstPCRDuration == 0 {
					firstPCRDuration = pcrDuration
				} else {
					lastPCRDuration = pcrDuration
				}
			}
		}
	}
}

example response:

$ ffprobe -v 8 -hide_banner -of json -show_format  testdata/videoAndAudio.ts | jq .format.duration
"5.052000"

$ go run main.go -f testdata/videoAndAudio.ts
Chunk duration PTS based: 4766666666
Chunk duration PCR based: 4520000000

Am I doing something wrong?
Why do PCR and PTS values differ from each other, and ffprobe?

The count of elementary streams from ffmpeg and astits is always different.
photo_2019-07-02_20-39-29

demuxer astits does not find the last ES:
photo_2019-07-02_20-35-23

Thanks for providing example files and such a detailed question! I'll check it out!

@jidckii regarding your first comment:

  1. Why do my values differ from ffprobe?

PTS is a timestamp, not a duration. It tells you when the frame should be shown, not when it should disappear.

When you use the Duration() method in d.PES.Header.OptionalHeader.PTS.Duration(), it doesn't return the duration of the frame, it returns the PTS value has a GO time.Duration.

You will need to know the duration of the frame for your computation but its value won't be in the frame metadata. As far as I know, you have 2 ways of knowing that duration:

  • guessing it by doing PTS[next] - PTS[previous]. This is not 100% accurate as it's only true when frame duration is the same for all frames. In your case, it is.
  • knowing it beforehand (for instance you're manipulating a 25fps video therefore you know that one frame lasts 1/25 => 40ms)

Once you know the duration of a frame, your computation becomes : PTS[last] - PTS[first] + FrameDuration since the last PTS indicates when the last frame should be shown, not when it will disappear.

Another thing, your file has 2 streams in it: a video stream and an audio stream. You'll need to compute durations for both of them (it will most likely return different results since the frame duration of both streams are different and not in sync).

  1. Why do PCR and PTS values differ from each other?

I may be wrong but as far as I know PCR is a reference not a timestamp. You should only use PTS to compute durations.

@jidckii regarding your second comment, is there any way you can upload both test files in this issue?

@jidckii regarding your second comment, is there any way you can upload both test files in this issue?

Upload is not a screenshot, but the output text?

No I need the testdata/arch_vag_000.ts and testdata/arch_shef_000.ts files in a .zip file if possible. Like you did for videoAndAudio.zip in your first comment.

@asticode

  • guessing it by doing PTS[next] - PTS[previous]. This is not 100% accurate as it's only true when frame duration is the same for all frames. In your case, it is.
  • knowing it beforehand (for instance you're manipulating a 25fps video therefore you know that one frame lasts 1/25 => 40ms)

Once you know the duration of a frame, your computation becomes : PTS[last] - PTS[first] + FrameDuration since the last PTS indicates when the last frame should be shown, not when it will disappear.

Another thing, your file has 2 streams in it: a video stream and an audio stream. You'll need to compute durations for both of them (it will most likely return different results since the frame duration of both streams are different and not in sync).

Thanks for the answer )
But what if the video has non-persistent FPS?
And how can I calculate the correct fps?

  • But what if the video has non-persistent FPS?

In that case you need to know each frame duration for sure. As far as I know it can only be achieved by interpreting the content of the PES data which is the video/audio frame itself. For instance, if you're video has been compressed using H264, the PES data will be the raw H264 frame. You can then use an H264 decoder to interpret its metadata and get the frame duration.

Unfortunately the H264 decoding part is not in the scope of this project.

  • And how can I calculate the correct fps?

As far as I know, you can only guess fps of your video stream. The first way that comes to mind would be to average the PTS[next] - PTS[previous] for all frames.

@jidckii I've pushed changes that make sure that the last packets are parsed. You'll need to pull changes of github.com/asticode/go-astitools as well. I've tried it against your files and it seems to fix the issue. Let me know if it does, if so I'll close the issue.

@jidckii I've pushed changes that make sure that the last packets are parsed. You'll need to pull changes of github.com/asticode/go-astitools as well. I've tried it against your files and it seems to fix the issue. Let me know if it does, if so I'll close the issue.

Thank you very much ! I will check this weekend.

@asticode
This is worked, great! Bug is closed. Also please add any tag, semver based. Thank you.

@jidckii I've tagged astitools with v1.1.0 and astits with v1.0.0