From 515d6eb455407e937b23b1d4fc8333c5a0ae517b Mon Sep 17 00:00:00 2001 From: bog Date: Tue, 26 Mar 2024 09:08:57 -0700 Subject: [PATCH 1/2] use incoming dts/pts for record and playback - modifies the record server to not offset start of the part for dts/pts - modifies the playback server to handle recorded files which do not start at 0 dts/pts - modifies the beginning of recordings to not set PTSOffset/Duration to 0 of frames before the requested time, but after the last key frame. i'd like to set start_time/start_pts to the requested time on the fmp4, but not sure how to do that as of now. Ultimately, our process uses pts/dts so we can seek to the requested values once we have the download without worrying about the extra data at the start. --- internal/playback/fmp4.go | 60 ++++++++++++++++++----------- internal/record/format_fmp4_part.go | 2 +- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/internal/playback/fmp4.go b/internal/playback/fmp4.go index 55630844..3c288e43 100644 --- a/internal/playback/fmp4.go +++ b/internal/playback/fmp4.go @@ -90,8 +90,9 @@ func fmp4SeekAndMuxParts( maxTime time.Duration, w io.Writer, ) (time.Duration, error) { - minTimeMP4 := durationGoToMp4(minTime, 90000) - maxTimeMP4 := durationGoToMp4(maxTime, 90000) + var minTimeMP4 uint64 + var maxTimeMP4 uint64 + var baseTime uint64 moofOffset := uint64(0) var tfhd *mp4.Tfhd var tfdt *mp4.Tfdt @@ -99,6 +100,8 @@ func fmp4SeekAndMuxParts( var outTrack *fmp4.PartTrack var outBuf seekablebuffer.Buffer elapsed := uint64(0) + firstSampleRead := false + firstSamplePTS := uint64(0) initWritten := false firstSampleWritten := make(map[uint32]struct{}) gop := make(map[uint32][]*fmp4.PartSample) @@ -126,12 +129,21 @@ func fmp4SeekAndMuxParts( return nil, err } tfdt = box.(*mp4.Tfdt) + if !firstSampleRead { + firstSamplePTS = tfdt.BaseMediaDecodeTimeV1 + minTimeMP4 = durationGoToMp4(minTime, 90000) + firstSamplePTS + maxTimeMP4 = durationGoToMp4(maxTime, 90000) + firstSamplePTS + firstSampleRead = true + } if tfdt.BaseMediaDecodeTimeV1 >= maxTimeMP4 { return nil, errTerminated } - outTrack = &fmp4.PartTrack{ID: int(tfhd.TrackID)} + outTrack = &fmp4.PartTrack{ + ID: int(tfhd.TrackID), + BaseTime: tfdt.BaseMediaDecodeTimeV1, + } case "trun": box, _, err := h.ReadPayload() @@ -148,7 +160,6 @@ func fmp4SeekAndMuxParts( } elapsed = tfdt.BaseMediaDecodeTimeV1 - baseTimeSet := false for _, e := range trun.Entries { payload := make([]byte, e.SampleSize) @@ -174,30 +185,25 @@ func fmp4SeekAndMuxParts( if !fsw { if isRandom { gop[tfhd.TrackID] = []*fmp4.PartSample{sa} + baseTime = elapsed } else { gop[tfhd.TrackID] = append(gop[tfhd.TrackID], sa) } } if elapsed >= minTimeMP4 { - if !baseTimeSet { - outTrack.BaseTime = elapsed - minTimeMP4 - - if !fsw { - if !isRandom { - for _, sa2 := range gop[tfhd.TrackID][:len(gop[tfhd.TrackID])-1] { - sa2.Duration = 0 - sa2.PTSOffset = 0 - outTrack.Samples = append(outTrack.Samples, sa2) - } - } - - delete(gop, tfhd.TrackID) - firstSampleWritten[tfhd.TrackID] = struct{}{} + if !fsw { + outTrack.BaseTime = baseTime + for _, sa2 := range gop[tfhd.TrackID][:len(gop[tfhd.TrackID])] { + outTrack.Samples = append(outTrack.Samples, sa2) } - } - outTrack.Samples = append(outTrack.Samples, sa) + delete(gop, tfhd.TrackID) + firstSampleWritten[tfhd.TrackID] = struct{}{} + fsw = true + } else { + outTrack.Samples = append(outTrack.Samples, sa) + } } elapsed += uint64(e.SampleDuration) @@ -255,7 +261,7 @@ func fmp4MuxParts( maxTime time.Duration, w io.Writer, ) (time.Duration, error) { - maxTimeMP4 := durationGoToMp4(maxTime, 90000) + var maxTimeMP4 uint64 moofOffset := uint64(0) var tfhd *mp4.Tfhd var tfdt *mp4.Tfdt @@ -263,6 +269,8 @@ func fmp4MuxParts( var outTrack *fmp4.PartTrack var outBuf seekablebuffer.Buffer elapsed := uint64(0) + firstSampleRead := false + firstSamplePTS := uint64(0) _, err := mp4.ReadBoxStructure(r, func(h *mp4.ReadHandle) (interface{}, error) { switch h.BoxInfo.Type.String() { @@ -288,13 +296,19 @@ func fmp4MuxParts( } tfdt = box.(*mp4.Tfdt) + if !firstSampleRead { + firstSamplePTS = tfdt.BaseMediaDecodeTimeV1 + maxTimeMP4 = durationGoToMp4(maxTime, 90000) + firstSamplePTS + firstSampleRead = true + } + if tfdt.BaseMediaDecodeTimeV1 >= maxTimeMP4 { return nil, errTerminated } outTrack = &fmp4.PartTrack{ ID: int(tfhd.TrackID), - BaseTime: tfdt.BaseMediaDecodeTimeV1 + durationGoToMp4(startTime, 90000), + BaseTime: tfdt.BaseMediaDecodeTimeV1, } case "trun": @@ -367,7 +381,7 @@ func fmp4MuxParts( return 0, err } - return durationMp4ToGo(elapsed, 90000), nil + return durationMp4ToGo(elapsed - firstSamplePTS, 90000), nil } func fmp4SeekAndMux( diff --git a/internal/record/format_fmp4_part.go b/internal/record/format_fmp4_part.go index 1f1968fe..aedea4ea 100644 --- a/internal/record/format_fmp4_part.go +++ b/internal/record/format_fmp4_part.go @@ -86,7 +86,7 @@ func (p *formatFMP4Part) record(track *formatFMP4Track, sample *sample) error { if !ok { partTrack = &fmp4.PartTrack{ ID: track.initTrack.ID, - BaseTime: durationGoToMp4(sample.dts-p.s.startDTS, track.initTrack.TimeScale), + BaseTime: durationGoToMp4(sample.dts, track.initTrack.TimeScale), } p.partTracks[track] = partTrack } From 7932a2ff338de6f24f5f5a2350c0c460f6cac8eb Mon Sep 17 00:00:00 2001 From: bog Date: Wed, 27 Mar 2024 08:14:24 -0700 Subject: [PATCH 2/2] add simplfified ecr build/push script --- scripts/ecr.mk | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 scripts/ecr.mk diff --git a/scripts/ecr.mk b/scripts/ecr.mk new file mode 100644 index 00000000..2afcd1fe --- /dev/null +++ b/scripts/ecr.mk @@ -0,0 +1,67 @@ +# This file is based on binaries.mk and dockerhub.mk. It's simplified to +# build just the platform we need, and push to ECR. +# +# This file assumes you already have a valid ECR login, see Amazon's +# instructions: https://docs.aws.amazon.com/AmazonECR/latest/userguide/getting-started-cli.html#cli-authenticate-registry +# +BINARY_NAME = mediamtx + +define DOCKERFILE_BINARIES +FROM $(BASE_IMAGE) AS build-base +RUN apk add --no-cache zip make git tar +WORKDIR /s +COPY go.mod go.sum ./ +RUN go mod download +COPY . ./ +ARG VERSION +ENV CGO_ENABLED 0 +RUN rm -rf tmp binaries +RUN mkdir tmp binaries +RUN cp mediamtx.yml LICENSE tmp/ +RUN go generate ./... + +FROM build-base AS build-linux-amd64 +RUN GOOS=linux GOARCH=amd64 go build -ldflags "-X github.com/bluenviron/mediamtx/internal/core.version=$$VERSION" -o tmp/$(BINARY_NAME) +RUN tar -C tmp -czf binaries/$(BINARY_NAME)_$${VERSION}_linux_amd64.tar.gz --owner=0 --group=0 $(BINARY_NAME) mediamtx.yml LICENSE + +FROM $(BASE_IMAGE) +COPY --from=build-linux-amd64 /s/binaries /s/binaries +endef +export DOCKERFILE_BINARIES + +ecr-binaries: + echo "$$DOCKERFILE_BINARIES" | DOCKER_BUILDKIT=1 docker build . -f - \ + --build-arg VERSION=$$(git describe --tags) \ + -t temp + docker run --rm -v $(PWD):/out \ + temp sh -c "rm -rf /out/binaries && cp -r /s/binaries /out/" + +define DOCKERFILE_ECR +FROM scratch +ARG TARGETPLATFORM +ADD tmp/binaries/$$TARGETPLATFORM.tar.gz / +ENTRYPOINT [ "/mediamtx" ] +endef +export DOCKERFILE_ECR + +ecr-push: + $(eval VERSION := $(shell git describe --tags | tr -d v)) + + if [ -z "$(ECR_TAG)" ]; then \ + echo "ECR_TAG is required"; \ + exit 1; \ + fi + + rm -rf tmp + mkdir -p tmp tmp/binaries/linux/arm tmp/rpi_base/linux/arm + + cp binaries/*linux_amd64.tar.gz tmp/binaries/linux/amd64.tar.gz + + echo "$$DOCKERFILE_ECR" | docker buildx build . -f - \ + --provenance=false \ + --platform=linux/amd64 \ + -t $(ECR_TAG) \ + --push + + docker buildx rm builder || true + rm -rf $$HOME/.docker/manifests/*