Cccombiner "Need more caption data" log

Hi there!

I’m building a pipeline to add dynamically generated closed captions to my mpegts stream.

The pipeline runs without errors, however the output file has no closed captions stream…

Here is my pipeline:

cccombiner name=ccc schedule=false ! mux. mpegtsmux name=mux ! filesink location=tmp/output.ts shmsrc socket-path=tmp/ipc/Router_output_1 do-timestamp=true is-live=true ! queue ! tsdemux name=demux demux. ! queue name=video_queue ! h264parse ! ccc.sink appsrc name=CcMuxer_c1236aa586e94fc8 is-live=true do-timestamp=false format=time caps=application/x-subtitle ! subparse ! tttocea708 ! ccconverter ! queue ! ccc.caption

I’m pushing subtitles asynchronously from an appsrc:


    @final
    def subprocess_func(self, appsrc_name: str, queue: Queue) -> None:
        """Method run in a subprocess."""
        Gst.init(None)
        cmd = self.build_cmd()

        print(" ".join(cmd))
        pipeline = Gst.parse_launch(" ".join(cmd))
        # pipeline = self.build_pipeline()
        appsrc = pipeline.get_by_name(appsrc_name)

        demux = pipeline.get_by_name("demux")

        def on_pad_added(_: Gst.Element, pad: Gst.Pad) -> None:
            """Handle pad-added signal."""
            print("Pad added...", pad.get_current_caps().to_string())
            if "video/" in pad.get_current_caps().to_string():
                video_queue = pipeline.get_by_name("video_queue")
                pad.link(video_queue.get_static_pad("sink"))

            # if "audio/" in pad.get_current_caps().to_string():
            # audio_queue = pipeline.get_by_name("audio_queue")
            # pad.link(audio_queue.get_static_pad("sink"))

        demux.connect("pad-added", on_pad_added)

        pipeline.set_state(Gst.State.PLAYING)
        glib_loop = GLib.MainLoop()

        t = threading.Thread(
            target=self.push_subtitles, args=(pipeline, appsrc, queue), daemon=True
        )
        t.start()

        try:
            glib_loop.run()
        except KeyboardInterrupt:
            pass
        finally:
            pipeline.set_state(Gst.State.NULL)

    @final
    def push_subtitles(
        self, pipeline: Gst.Pipeline, appsrc: GstApp.AppSrc, queue: Queue
    ) -> None:
        """Push subtitles to the appsrc."""
        while self.state == State.RUNNING:
            if not queue.empty():
                text: Text = queue.get()

                if text is not None:
                    entry = text_to_chunk(self.subtitle_index, text, "vtt")

                    running_time = (
                        pipeline.get_clock().get_time() - pipeline.get_base_time()
                    )

                    buf = Gst.Buffer.new_wrapped(entry.data)
                    buf.pts = running_time
                    buf.dts = Gst.CLOCK_TIME_NONE  # let downstream infer it
                    buf.duration = (text.end - text.start) * 1_000_000

                    print("Pushing subtitle buffer...", text.text)
                    appsrc.emit("push-buffer", buf)

                    self.subtitle_index += 1

            time.sleep(self.timeout)

        appsrc.emit("end-of-stream")```

> Blockquote
0:00:11.608769250  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.628834458  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.648538458  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.669627333  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.689323916  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.706845458  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:11.725798125  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
Pushing subtitle buffer... Hello, world! 120
0:00:12.884144000  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:555:gst_cc_combiner_collect_captions:<ccc> Need more caption data
0:00:13.725520791  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:13.748449958  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:13.769693375  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:13.785583708  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
Pushing subtitle buffer... Hello, world! 140
0:00:14.882142333  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:555:gst_cc_combiner_collect_captions:<ccc> Need more caption data
0:00:15.730484666  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.749191958  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.769543375  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.786437250  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.806862958  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.830126541  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.848474208  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
0:00:15.869709958  8399    0x168590e70 DEBUG             cccombiner gstcccombiner.c:559:gst_cc_combiner_collect_captions:<ccc> No caption data on timeout
Pushing subtitle buffer... Hello, world! 160

I can see in the logs that every appsrc push reaches the cccombiner based on this log:

cccombiner gstcccombiner.c:555:gst_cc_combiner_collect_captions:<ccc> Need more caption data

But I don’t understand why it needs more caption data ? It is a full subtitle block…

Also putting this in case it helps…

Properties:
 Duration: 0:00:33.053199629
 Seekable: yes
 Live: no
 container #0: MPEG-2 Transport Stream
   video #1: H.264 (High 4:4:4 Profile)
     Stream ID: caf617a271ba0f3e050ce676e192b0cfd7b34846d231ba83ce958160db7b1b33:1/00000041
     Width: 1920
     Height: 1080
     Depth: 24
     Frame rate: 50/1
     Pixel aspect ratio: 1/1
     Interlaced: false
     Bitrate: 0
     Max bitrate: 0

You may want to provide something anyone can reproduce. cc @mathieu

p.s it’s not a good idea to call GST.init() more then once.

Yes good point!

Steps to reproduce:

First launch this python script to start the muxer.


import threading

import gi  # type: ignore
import time

gi.require_version("Gst", "1.0")
gi.require_version("GstApp", "1.0")

from gi.repository import GLib, Gst, GstApp


def build_cmd() -> list[str]:
    cmd = [
        "cccombiner",
        "name=ccc",
        "!",
        "mux.",
        "mpegtsmux",
        "name=mux",
        "!",
        "filesink",
        "location=tmp/output.ts",
        "udpsrc",
        "port=20000",
        "!",
        "queue",
        "!",
        "tsdemux",
        "name=demux",
        "demux.",
        "!",
        "queue",
        "name=video_queue",
        "!",
        "h264parse",
        "!",
        "ccc.sink",
        "demux.",
        "!",
        "queue",
        "name=audio_queue",
        "!",
        "aacparse",
        "!",
        "mux.",
        "appsrc",
        "name=subtitle_appsrc",
        "do-timestamp=false",
        "format=time",
        "caps=text/x-raw,format=utf8",
        "!",
        "tttocea708",
        "!",
        "ccconverter",
        "!",
        "queue",
        "!",
        "ccc.caption",
    ]

    return cmd


def push_subtitles(_: Gst.Pipeline, appsrc: GstApp.AppSrc) -> None:
    """Push subtitles to the appsrc."""
    index = 1

    while index < 100:
        print(f"Feeding subtitles {index}")
        buf = Gst.Buffer.new_wrapped(f"Subtitle {index}".encode())
        buf.pts = buf.dts = 1000 * index * Gst.MSECOND
        buf.duration = 1000 * Gst.MSECOND
        appsrc.push_buffer(buf)
        index += 1

        time.sleep(1)

    appsrc.emit("end-of-stream")


def mux_subtitles() -> None:
    """Method run in a subprocess."""
    Gst.init(None)
    cmd = build_cmd()

    print(" ".join(cmd))
    pipeline = Gst.parse_launch(" ".join(cmd))
    appsrc = pipeline.get_by_name("subtitle_appsrc")

    demux = pipeline.get_by_name("demux")

    def on_pad_added(_: Gst.Element, pad: Gst.Pad) -> None:
        """Handle pad-added signal."""
        print("Pad added...", pad.get_current_caps().to_string())
        if "video/" in pad.get_current_caps().to_string():
            video_queue = pipeline.get_by_name("video_queue")
            pad.link(video_queue.get_static_pad("sink"))

        if "audio/" in pad.get_current_caps().to_string():
            audio_queue = pipeline.get_by_name("audio_queue")
            pad.link(audio_queue.get_static_pad("sink"))

    demux.connect("pad-added", on_pad_added)

    pipeline.set_state(Gst.State.PLAYING)
    glib_loop = GLib.MainLoop()

    t = threading.Thread(target=push_subtitles, args=(pipeline, appsrc), daemon=True)
    t.start()

    try:
        glib_loop.run()
    except KeyboardInterrupt:
        pass
    finally:
        print("setting to null")
        pipeline.set_state(Gst.State.NULL)


if __name__ == "__main__":
    mux_subtitles()

Then run this command to feed video:

gst-launch-1.0 -v \
    videotestsrc is-live=true \
    ! video/x-raw, width=1920, height=1080, framerate=50/1 \
    ! videoconvert \
    ! x264enc tune=zerolatency bitrate=1000 speed-preset=ultrafast \
    ! h264parse \
    ! queue \
    ! mux. \
    audiotestsrc wave=sine freq=440 is-live=true \
    ! audio/x-raw, rate=44100, channels=2 \
    ! audioconvert \
    ! avenc_aac bitrate=128000 \
    ! queue \
    ! mux. \
    mpegtsmux name=mux \
    ! udpsink host=127.0.0.1 port=20000

I added h264ccinserter to the pipeline after the cccombiner. I still see the logs, but I’m able to extract subtitles from the output files after that.

Note: ffmpeg still doesn’t detect the subtitles after that (ffprobe -show_streams -i output.ts)