Skip to content

Publish each program of a multi-program source as its own track - #17

Merged
mondain merged 5 commits into
openmoq:mainfrom
gwendalsimon:feat/mpts-pipeline
Oct 23, 2026
Merged

mondain merged 5 commits into
openmoq:mainfrom
gwendalsimon:feat/mpts-pipeline

Conversation

@gwendalsimon

@gwendalsimon gwendalsimon commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

MptsPipeline publishes every program of one live source as a per-program track, with one catalog for all tracks. Every program starts at the live edge on the first pull. The start-up windows of a program are PCR time, because each packetizer reads the whole multiplex. A program declares its configured mux rate, or 10 % above its measured rate. A program that ends closes its last Group at once. The other programs go on. The CLI reaches the pipeline in PR 7. Three known limits stay open: one copy of the multiplex per program, a start-window policy spread over four functions, and FIFO handling inside the pipeline.

Depends on #16; only the last four commits belong to this PR.


This change is Reviewable

gwendalsimon and others added 4 commits October 21, 2026 20:26
* Add MsftsMuxer::catalogJson(QList<MsftsCatalog>): each entry gives its
  media track, its timeline track, and its initDataList entry, in list
  order; the root fields come from the first entry
* Add workedOutMuxRate(): the mpeg2tsMuxRate of a program measured at a
  rate, 10 % above it to cover the jitter of a fixed-rate service over
  the one-second measure, rounded up to 10,000 bit/s
* The single-entry catalogJson() is the list of one, so its output is
  unchanged

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* setTimedStartWindows(): a per-program track of a live multiplex reads
  the whole multiplex, so open() bounds its windows by PCR time, not by
  a packet count: 1 s for the PAT and the PMT, 10 s for the first random
  access point, and 1 s for the rates, within 600,000 packets each. A
  PCR step back or over 1 s counts as no time, and the rate measure
  starts again from it, null-packet count included. A PCR that does not
  advance for about 2 s ends open() with an error, and a program without
  a PCR PID times its windows on the first PCR of the multiplex
* The timed scans keep no packet, so the track starts at the next
  random access point after them; prebufferedBytes() says what open()
  keeps
* PacketQueueDevice::flushToLiveEdge() drops what is queued but the rest
  of a begun packet and records a gap there with the new cause LiveEdge.
  The packetizer starts the first Group there without a warning and
  without a PCR discontinuity mark, as nothing before it is published,
  also when a queue overflow falls at the same offset
* M2tsObject::sourceOffset gives the source offset of an Object's first
  packet, which tells the Objects read before a flush from those after
* The single-program and file paths keep their packet windows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add PublishedObject::endsTrack, a marker that is never sent: it
  releases its track's held Object as final at once, with the timeline
  Object that waits for it, so a track that ends while others go on
  closes its last Group without waiting for the end of the session
* Name the catalog track with one shared constant, kCatalogTrackName
  (MSF -01 Section 5), instead of a literal in each place that builds
  catalog Objects
* Test the marker: the last Object is final without a further read, the
  marker is not sent, and a stalled media track's timeline Object goes
  out right after its media Object when the marker comes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Add MptsPipeline: MptsFanOut reads the source once, one M2tsPacketizer
  per program reads its own queue on a worker thread, and the workers
  push their Objects into one bounded merged queue that the publisher
  takes through IMoqOutput. One catalog lists every program's track and
  timeline track
* Every program starts at the live edge on the first pull: the queues
  are flushed, and nothing read before the first subscriber is published
  but the catalog. atLiveEdge() signals when every program started
* Each program declares its own mux rate, or the one worked out from
  its measured rate, as mpeg2tsMuxRate and as its bitrate; a configured
  rate below the measured one gets a warning
* A PSI change publishes a new independent catalog with the program's
  updated initData (MSFTS 6.12). A program that ends, or whose read
  fails, queues its track end and reports programEnded(); error() is
  fatal only. stop() joins the fan-out thread outside the lock
* LivePipeline and MptsPipeline share timelineObject() on a
  TimelineHistory, nowUnixUs(), and packetizerCatalog(), which builds a
  track's catalog entry from its packetizer once
* The MPTS build guard and posix-only-sources-guards.sh now cover
  MptsPipeline.cpp, which includes fcntl.h, sys/stat.h, and unistd.h
* Tests: two programs, a mux rate below the measured rate, a program
  missing from the PAT, a PMT change, a program that leaves the PAT, the
  live edge after a late first subscriber, a gap in every program,
  start-up windows on a 40 Mbit/s multiplex, ten programs, a seekable
  file refused, and a prompt stop()

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mondain
mondain merged commit 45e8bb3 into openmoq:main Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants