PR #23650 opened by shadowcaster3
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23650
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23650.patch
# Summary of changes
Add a new muxer option `mpegts_pcr_pid` that generates PCR on a dedicated
adaptation-field-only PID, separate from any elementary stream.
ISDB-T 1seg receivers require PCR on a standalone PID. When PCR shares the
video PID, these receivers acquire the service but refuse to decode video and
audio. This is standard practice in Japanese digital terrestrial broadcasting
per ARIB TR-B14.
When `mpegts_pcr_pid` is set to a non-zero value:
- PCR packets are generated as adaptation-field-only packets on the specified
PID at the interval set by `pcr_period`
- The video stream adaptation fields do not carry PCR flags
- The PMT PCR_PID field is set to the specified PID
When `mpegts_pcr_pid` is 0 (default): existing behavior is unchanged.
Example:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f mpegts \
-mpegts_pcr_pid 0x05FF -muxrate 416000 -pcr_period 40 output.ts
Tested with a PSP 1seg TV tuner receiving over-the-air via gr-isdbt SDR
transmitter. Video and audio decode correctly with separate PCR PID; without it
the receiver refuses to decode.
Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/23649
>From b106635be51c7303a64a2e42de04f7a76f26a687 Mon Sep 17 00:00:00 2001
From: shadowcaster3 <[email protected]>
Date: Tue, 30 Jun 2026 00:35:30 +0300
Subject: [PATCH] avformat/mpegtsenc: add mpegts_pcr_pid option for separate
PCR PID
Add a new muxer option mpegts_pcr_pid that generates PCR on a dedicated
adaptation-field-only PID, separate from any elementary stream.
ISDB-T 1seg receivers require PCR on a standalone PID. When PCR shares
the video PID, these receivers acquire the service but refuse to decode
video and audio. This is standard practice in Japanese digital
terrestrial broadcasting per ARIB TR-B14.
When mpegts_pcr_pid is set to a non-zero value:
- PCR packets are generated as adaptation-field-only packets on the
specified PID at the interval set by pcr_period
- The video stream adaptation fields do not carry PCR flags
- The PMT PCR_PID field is set to the specified PID
When mpegts_pcr_pid is 0 (default): existing behavior is unchanged.
Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/23649
Signed-off-by: shadowcaster3 <[email protected]>
---
doc/muxers.texi | 8 +++++
libavformat/mpegtsenc.c | 74 ++++++++++++++++++++++++++++++++++++++---
2 files changed, 77 insertions(+), 5 deletions(-)
diff --git a/doc/muxers.texi b/doc/muxers.texi
index 9a579ebebf..93001b64c1 100644
--- a/doc/muxers.texi
+++ b/doc/muxers.texi
@@ -3170,6 +3170,14 @@ Override the default PCR retransmission time in
milliseconds. Default is
20 ms is used for CBR streams, the highest multiple of the frame duration which
is less than 100 ms is used for VBR streams.
+@item mpegts_pcr_pid @var{integer}
+Set a separate PID for PCR packets. When set to a non-zero value, PCR is
+carried in adaptation-field-only packets on the specified PID instead of
+being embedded in an elementary stream. The PMT PCR_PID field is set
+accordingly. Default is @code{0} which means PCR is placed on the video
+stream PID (existing behavior). This is required for ISDB-T 1seg receivers
+which expect a dedicated PCR PID.
+
@item pat_period @var{duration}
Maximum time in seconds between PAT/PMT tables. Default is @code{0.1}.
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index 7f76883531..03d3012d60 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -132,6 +132,12 @@ typedef struct MpegTSWrite {
uint8_t provider_name[256];
int omit_video_pes_length;
+
+ int pcr_pid; ///< user-specified separate PCR PID (0 = use
video PID)
+ int pcr_stream_pid; ///< actual PID used for dedicated PCR packets
+ int pcr_stream_cc; ///< continuity counter for dedicated PCR PID
+ int64_t pcr_stream_pcr_period; ///< PCR period for the dedicated stream
+ int64_t pcr_stream_last_pcr; ///< last PCR sent on the dedicated PID
} MpegTSWrite;
/* a PES packet header is generated every DEFAULT_PES_HEADER_FREQ packets */
@@ -1088,7 +1094,16 @@ static void select_pcr_streams(AVFormatContext *s)
}
}
- if (pcr_st) {
+ if (ts->pcr_pid > 0) {
+ int pcr_period_ms = ts->pcr_period_ms == -1 ? PCR_RETRANS_TIME :
ts->pcr_period_ms;
+ service->pcr_pid = ts->pcr_pid;
+ ts->pcr_stream_pid = ts->pcr_pid;
+ ts->pcr_stream_cc = 15;
+ ts->pcr_stream_pcr_period = av_rescale(pcr_period_ms,
PCR_TIME_BASE, 1000);
+ ts->pcr_stream_last_pcr = ts->first_pcr -
ts->pcr_stream_pcr_period;
+ av_log(s, AV_LOG_VERBOSE, "service %i using separate PCR pid=%d,
pcr_period=%"PRId64"ms\n",
+ service->sid, ts->pcr_stream_pid, (int64_t)pcr_period_ms);
+ } else if (pcr_st) {
MpegTSWriteStream *ts_st = pcr_st->priv_data;
service->pcr_pid = ts_st->pid;
enable_pcr_generation_for_stream(s, pcr_st);
@@ -1406,6 +1421,30 @@ static void mpegts_insert_pcr_only(AVFormatContext *s,
AVStream *st)
write_packet(s, buf);
}
+/** Write a single transport stream packet with PCR on a dedicated PID. */
+static void mpegts_insert_pcr_only_pid(AVFormatContext *s)
+{
+ MpegTSWrite *ts = s->priv_data;
+ uint8_t *q;
+ uint8_t buf[TS_PACKET_SIZE];
+
+ q = buf;
+ *q++ = SYNC_BYTE;
+ *q++ = (ts->pcr_stream_pid >> 8) & 0x1f;
+ *q++ = ts->pcr_stream_pid & 0xff;
+ *q++ = 0x20 | (ts->pcr_stream_cc & 0xf); /* Adaptation only, no payload
*/
+ /* Continuity Count field does not increment for adaptation-only (13818-1
section 2.4.3.3) */
+ *q++ = TS_PACKET_SIZE - 5; /* Adaptation Field Length */
+ *q++ = 0x10; /* Adaptation flags: PCR present */
+
+ /* PCR coded into 6 bytes */
+ q += write_pcr_bits(q, get_pcr(ts));
+
+ /* stuffing bytes */
+ memset(q, STUFFING_BYTE, TS_PACKET_SIZE - (q - buf));
+ write_packet(s, buf);
+}
+
static void write_pts(uint8_t *q, int fourbits, int64_t pts)
{
int val;
@@ -1534,7 +1573,15 @@ static void mpegts_write_pes(AVFormatContext *s,
AVStream *st,
if (ts->mux_rate > 1) {
/* Send PCR packets for all PCR streams if needed */
pcr = get_pcr(ts);
- if (pcr >= ts->next_pcr) {
+ if (ts->pcr_pid > 0) {
+ /* Dedicated PCR PID: send adaptation-only packets on that PID
*/
+ if (pcr - ts->pcr_stream_last_pcr >=
ts->pcr_stream_pcr_period) {
+ ts->pcr_stream_last_pcr = FFMAX(pcr -
ts->pcr_stream_pcr_period, ts->pcr_stream_last_pcr + ts->pcr_stream_pcr_period);
+ mpegts_insert_pcr_only_pid(s);
+ pcr = get_pcr(ts);
+ }
+ ts->next_pcr = ts->pcr_stream_last_pcr +
ts->pcr_stream_pcr_period;
+ } else if (pcr >= ts->next_pcr) {
int64_t next_pcr = INT64_MAX;
for (int i = 0; i < s->nb_streams; i++) {
/* Make the current stream the last, because for that we
@@ -1559,13 +1606,28 @@ static void mpegts_write_pes(AVFormatContext *s,
AVStream *st,
}
if (dts != AV_NOPTS_VALUE && (dts - pcr /
SYSTEM_CLOCK_FREQUENCY_DIVISOR) > delay) {
/* pcr insert gets priority over null packet insert */
- if (write_pcr)
+ if (ts->pcr_pid > 0) {
+ int64_t cur_pcr = get_pcr(ts);
+ if (cur_pcr - ts->pcr_stream_last_pcr >=
ts->pcr_stream_pcr_period) {
+ ts->pcr_stream_last_pcr = FFMAX(cur_pcr -
ts->pcr_stream_pcr_period, ts->pcr_stream_last_pcr + ts->pcr_stream_pcr_period);
+ mpegts_insert_pcr_only_pid(s);
+ } else {
+ mpegts_insert_null_packet(s);
+ }
+ } else if (write_pcr) {
mpegts_insert_pcr_only(s, st);
- else
+ } else {
mpegts_insert_null_packet(s);
+ }
/* recalculate write_pcr and possibly retransmit si_info */
continue;
}
+ } else if (ts->pcr_pid > 0 && pcr != AV_NOPTS_VALUE) {
+ /* VBR mode with dedicated PCR PID */
+ if (pcr - ts->pcr_stream_last_pcr >= ts->pcr_stream_pcr_period &&
is_start) {
+ ts->pcr_stream_last_pcr = FFMAX(pcr -
ts->pcr_stream_pcr_period, ts->pcr_stream_last_pcr + ts->pcr_stream_pcr_period);
+ mpegts_insert_pcr_only_pid(s);
+ }
} else if (ts_st->pcr_period && pcr != AV_NOPTS_VALUE) {
if (pcr - ts_st->last_pcr >= ts_st->pcr_period && is_start) {
ts_st->last_pcr = FFMAX(pcr - ts_st->pcr_period,
ts_st->last_pcr + ts_st->pcr_period);
@@ -1594,7 +1656,7 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream
*st,
key && is_start && pts != AV_NOPTS_VALUE &&
!is_dvb_teletext /* adaptation+payload forbidden for teletext
(ETSI EN 300 472 V1.3.1 4.1) */) {
// set Random Access for key frames
- if (ts_st->pcr_period)
+ if (ts_st->pcr_period && !ts->pcr_pid)
write_pcr = 1;
set_af_flag(buf, 0x40);
q = get_ts_payload_start(buf);
@@ -2389,6 +2451,8 @@ static const AVOption options[] = {
OFFSET(omit_video_pes_length), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC
},
{ "pcr_period", "PCR retransmission time in milliseconds",
OFFSET(pcr_period_ms), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, ENC
},
+ { "mpegts_pcr_pid", "Set a separate PCR PID (0 means PCR on video PID)",
+ OFFSET(pcr_pid), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 0x1FFF, ENC },
{ "pat_period", "PAT/PMT retransmission time limit in seconds",
OFFSET(pat_period_us), AV_OPT_TYPE_DURATION, { .i64 = PAT_RETRANS_TIME *
1000LL }, 0, INT64_MAX, ENC },
{ "sdt_period", "SDT retransmission time limit in seconds",
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]