Looping playback of a list of Gst.Sample with GstApp.AppSrc

1.2k views Asked by At

I'm trying to write a simple music player using GStreamer. I want to play any arbitrary music file ABS_FILE_PATH, store the samples for other purposes and later loop over these indefinitely, once the original end of stream is reached.

Now playing the music works fine until short after the last sample of the track was played. Most of the time there's just silence, but sometimes there are one or two audible samples indicating, that the track just started playing again. The same holds for the terminal output. It shows, that a few samples after looping was started, the need-data signal is sent more frequently than before.

I've used fakesink for dumping the data, which seemed to work perfectly fine. The data was just looped, like it was intended.

So what happens here? Why don't the samples play a second (third, fourth, ...) time? I've run out of ideas.

Following I added a minimal example of what I'm doing without any UI, but with the same problem:

import itertools, signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
from gi.repository import Gst, GstApp, Gtk
Gst.init(None)

# read samples with Gst.AppSink from a playbin
playbin = Gst.ElementFactory.make("playbin")
playbin.props.uri = "file://" + ABS_FILE_PATH # only works with absolute paths
playbin.props.audio_sink = GstApp.AppSink(sync=False, emit_signals=True)
playbin.set_state(Gst.State.PLAYING)

# loop over all samples
def samples(app_sink):
    samples = []
    sample = app_sink.pull_sample()
    while sample:
        yield sample
        samples.append(sample)
        sample = app_sink.pull_sample()
    print('looping')
    for sample in itertools.cycle(samples):
        yield sample

# write samples with Gst.AppSrc
def need_data(appsrc, length, samples):
    print('sample')
    sample = next(samples)
    appsrc.set_caps(sample.get_caps())
    appsrc.push_buffer(sample.get_buffer())
src = GstApp.AppSrc(format=Gst.Format.TIME, emit_signals=True)
src.connect('need-data', need_data, samples(playbin.props.audio_sink))

# to the autoaudiosink or just a fakesink
sink = Gst.ElementFactory.make("autoaudiosink")
#sink = Gst.ElementFactory.make("fakesink")
#sink.props.dump = True # dump contents of fakesink

# playback
play = Gst.Pipeline()
play.add(src)
play.add(sink)
src.link(sink)
play.set_state(Gst.State.PLAYING)

Gtk.main()

gst-plugins-base: 1.4.4

1

There are 1 answers

0
tynn On BEST ANSWER

I've found a working solution with help of the comment of thiagoss.

The GstBuffer struct documentation lists all the fields relevant for the timestamp

struct GstBuffer {
  [...]
  /* timestamp */
  GstClockTime           pts;
  GstClockTime           dts;
  GstClockTime           duration;
  [...]
};

where the presentation timestamp pts is the relevant value. Setting it to Gst.CLOCK_TIME_NONE before using the sample for a second time will solve the issue:

# loop over all samples
def samples(app_sink):
    samples = []
    sample = app_sink.pull_sample()
    while sample:
        yield sample
        sample.get_buffer().pts = Gst.CLOCK_TIME_NONE #unset the pts
        samples.append(sample)
        sample = app_sink.pull_sample()
    print('looping')
    for sample in itertools.cycle(samples):
        yield sample