Incorrect PTS calculation on 32 bit platforms
jidckii opened this issue · comments
Evgeniy Medvedev commented
Hello.
I use your library for native prober
There was a need for distribution under raspberry pi (arm 32)
And I found that I get incorrect PTS values with 32 bit architecture.
my code:
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
astits "github.com/asticode/go-astits"
)
var (
filename = flag.String("f", "", "path to ts file")
verbose = flag.Bool("verbose", false, "verbose 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, startTime, err := GetChunkFileDuration(ctx, *filename, *verbose)
if err != nil {
fmt.Printf("Failed to open file: %v\n", err)
return
}
fmt.Printf("start_time: [%v]\n", startTime)
fmt.Printf("duration: [%v]\n", durationBasedPTS)
}
// GetChunkFileDuration find PTS and calculate duration
func GetChunkFileDuration(ctx context.Context, filename string, verbose bool) (time.Duration, 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, 0, err
}
defer f.Close()
var esCount int
// Create the demuxer
dmx := astits.New(ctx, f)
for {
d, err := dmx.NextData()
if err != nil {
if err == astits.ErrNoMorePackets {
durationPTS := lastPTSDuration - firstPTSDuration
if durationPTS != 0 {
duration := CalculateDuration(esCount, durationPTS, verbose)
return duration, firstPTSDuration, nil
}
}
// Если есть ошибка и это не индикатор окончания данных, то
// возвращаем саму ошибку
return 0, 0, err
}
// Если тип элементарного стрима video, то считываем
// значение PTS
if d.PES != nil {
typeES := ESType(d.PES.Header.StreamID)
if typeES == "video" {
ptsDuration := d.PES.Header.OptionalHeader.PTS.Duration()
if verbose {
fmt.Printf("PTS: %v\n", ptsDuration)
}
esCount++
if firstPTSDuration == 0 {
firstPTSDuration = ptsDuration
} else {
lastPTSDuration = ptsDuration
}
}
}
}
}
// ESType get content type in ES
func ESType(currentType uint8) string {
// ref:
// https://github.com/asticode/go-astits/blob/master/data_pes.go#L51
// https://en.wikipedia.org/wiki/Packetized_elementary_stream
var (
StreamIDVideo = []byte{0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF}
StreamIDAudio = []byte{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF}
)
streamIDMap := map[string][]byte{
"video": StreamIDVideo,
"audio": StreamIDAudio,
}
for name, bytes := range streamIDMap {
for _, b := range bytes {
if b == currentType {
return name
}
}
}
return "unknown"
}
// CalculateDuration calculate fps and duration 1 frame.
// And adds up the duration of the PTS and the duration of 1 frame.
func CalculateDuration(esCount int, durationPTS time.Duration, verbose bool) time.Duration {
durationPTSInt := int(durationPTS)
fps := (esCount * 1000) / (durationPTSInt / 1000000)
if verbose {
fmt.Printf("FPS: [%v]\n", fps)
}
var frameDuration = (((1 / float32(fps)) * 1000) * 1000) * 1000
frameDurationInt := int(frameDuration)
durationInt := durationPTSInt + frameDurationInt
return time.Duration(durationInt)
}
test fie : https://yadi.sk/d/Emx9YKJvHNv-1Q
run on amd64:
$ go run main.go -f testdata/ref.ts -verbose
PTS: 11.4s
PTS: 11.44s
PTS: 11.48s
PTS: 11.52s
PTS: 11.56s
PTS: 11.6s
PTS: 11.64s
PTS: 11.68s
PTS: 11.72s
PTS: 11.76s
PTS: 11.8s
PTS: 11.84s
PTS: 11.88s
PTS: 11.92s
PTS: 11.96s
PTS: 12s
PTS: 12.04s
PTS: 12.08s
PTS: 12.12s
PTS: 12.16s
PTS: 12.2s
PTS: 12.24s
PTS: 12.28s
PTS: 12.32s
PTS: 12.36s
PTS: 12.4s
PTS: 12.44s
PTS: 12.48s
PTS: 12.52s
PTS: 12.56s
PTS: 12.6s
PTS: 12.64s
PTS: 12.68s
PTS: 12.72s
PTS: 12.76s
PTS: 12.8s
PTS: 12.84s
PTS: 12.88s
PTS: 12.92s
PTS: 12.96s
PTS: 13s
PTS: 13.04s
PTS: 13.08s
PTS: 13.12s
PTS: 13.16s
PTS: 13.2s
PTS: 13.24s
PTS: 13.28s
PTS: 13.32s
PTS: 13.36s
FPS: [25]
start_time: [11.4s]
duration: [2s]
Build for 386 and run:
(on arm 32, actually)
$ GOOS=linux GOARCH=386 go build -o go-probe main.go
$ ./go-probe -f testdata/ref.ts -verbose
PTS: 11.471µs
PTS: 20.554µs
PTS: -18.085µs
PTS: -9.003µs
PTS: 79ns
PTS: 9.161µs
PTS: 18.243µs
PTS: -20.395µs
PTS: -11.313µs
PTS: -2.231µs
PTS: 6.85µs
PTS: 15.933µs
PTS: -22.706µs
PTS: -13.624µs
PTS: -4.541µs
PTS: 4.54µs
PTS: 13.622µs
PTS: 22.705µs
PTS: -15.934µs
PTS: -6.852µs
PTS: 2.23µs
PTS: 11.312µs
PTS: 20.394µs
PTS: -18.244µs
PTS: -9.162µs
PTS: -80ns
PTS: 9.002µs
PTS: 18.084µs
PTS: -20.555µs
PTS: -11.472µs
PTS: -2.39µs
PTS: 6.691µs
PTS: 15.773µs
PTS: -22.865µs
PTS: -13.783µs
PTS: -4.701µs
PTS: 4.381µs
PTS: 13.463µs
PTS: 22.545µs
PTS: -16.093µs
PTS: -7.011µs
PTS: 2.07µs
PTS: 11.153µs
PTS: 20.235µs
PTS: -18.404µs
PTS: -9.321µs
PTS: -239ns
PTS: 8.842µs
PTS: 17.924µs
PTS: -20.714µs
esCount: 50
durationPTSInt: -32185
panic: runtime error: integer divide by zero
goroutine 1 [running]:
main.CalculateDuration(0x32, 0xffff8247, 0xffffffff, 0xa496601, 0xa4d0f4c, 0x1)
/home/jidckii/go/src/gitlab.com/yuccastream/go-probe/main.go:123 +0x26c
main.GetChunkFileDuration(0x839c9c0, 0xa49aaa0, 0xfff2bf56, 0xf, 0xa496601, 0x0, 0x0, 0x0, 0x0, 0x0, ...)
/home/jidckii/go/src/gitlab.com/yuccastream/go-probe/main.go:65 +0x3fd
main.main()
/home/jidckii/go/src/gitlab.com/yuccastream/go-probe/main.go:34 +0x14c
Quentin Renard commented
Very nice find.
I've pushed a fix on master and also created a v1.4.0
tag.
Can you confirm this is working properly?
Evgeniy Medvedev commented
Now everything is correct.
Thank you so much.