• v0.10.0 e10f0cb282

    v0.10.0 Stable

    builduser released this 2026-09-08 02:00:07 +02:00 | 0 commits to main since this release

    The virtual camera path was finally run rather than reasoned about, which
    found two faults. The configuration is also considerably smaller: 950 lines of
    it removed, most being questions with only one sensible answer.

    Existing configuration is not migrated. Camera and audio assignments are
    recorded differently now, so switch each source's Camera and Audio back on
    once after upgrading.

    Two faults, found by testing against a real device

    A source carrying no audio got no camera at all. ffmpeg refuses an output
    that ends up with no stream in it, and that refusal took the whole process
    down, video included, restarting every second. Making the audio mapping
    optional does not help, since the output is still empty. Audio is now added
    only when the source is actually sending some, asked afresh for each process.

    -check was broken and would have failed on a working setup. A generated
    test pattern written without -re goes as fast as the machine can manage, so
    the writer finished in a fraction of a second and had let go long before the
    read. The device then reverts to output only and reports "not a video capture
    device", which reads as broken hardware rather than a test racing itself.

    The hub's log now also goes to standard error. It only ever reached the
    interface, which is why the first fault was invisible to anyone running the
    program from a terminal to find out why it was failing.

    What did check out, against v4l2-ctl: the capability and format reads agree
    exactly, and a device does flip from output to capture as soon as a writer
    attaches. There are now end to end tests that publish over RTMP, carry it
    through the agent and the sink, and read a frame back off the loopback at the
    source's own resolution.

    Simpler

    Virtual devices are no longer picked from menus. A camera is taken from
    whichever loopback nodes are free, an audio device is created for the source
    and named after it, and each source has two switches. Which device carries
    which source is not a decision worth putting to anyone, and every question
    asked is another way to end up pointing at the wrong thing. What was chosen is
    remembered, so a scene built against a device keeps working.

    Removed as redundant:

    Gone Because
    manual delay, safety margin the delay tracks the slowest source; a typed number cannot improve on it
    fast first frame joining at a keyframe is simply correct; the alternative leaves sources permanently offset
    pinned size the picture size is read from the stream
    timestamp rebase it discards the alignment this program exists to produce
    preview size taken from what the source actually sends
    program view grid one window per source mirrors how cameras work

    A source is now a device on Linux and a window on Windows, one for one, so the
    same rig is set up the same way on either platform.

    What remains is what cannot be worked out: which ports to listen on, the
    shared secret, where the outputs go, the per-source offset, and where to look
    for updates.

    Downloads

    File For
    streamsink-0.10.0-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.10.0-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.10.0-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.10.0-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.9.0 f2d213aeb5

    v0.9.0 Stable

    builduser released this 2026-09-07 07:33:29 +02:00 | 2 commits to main since this release

    Fixes virtual camera resolutions not being detected or set correctly, and adds
    an MIT licence.

    Sources now report what size they send

    The resolution a source sends was never established. Nothing here decodes, so
    the only size available was whatever the device happened to be set to, which
    is whatever a consumer settled on when it attached: usually the module's
    default, rarely what the source sends.

    The size is now read from the codec configuration that begins every stream.
    That means walking the H.264 sequence parameter set, which is a bitstream of
    variable length fields that is not byte aligned, with a crop counted in chroma
    samples. Anything unrecognised is refused rather than guessed at, including
    the enhanced framing used by HEVC and AV1, because a wrong size is worse than
    no size.

    Tested against streams a real encoder produced at 1920x1080, 1280x720,
    854x480, 640x360, 320x180 and 1080x1920 portrait, across baseline, main and
    high profiles. Two of those sizes are not whole macroblocks and depend on the
    cropping arithmetic being right.

    What changes as a result

    • Device free — the camera sends the source's own size and the device
      follows it. This is the case that was broken.

    • Device held by OBS — its format cannot change, so it is still matched,
      but the message names both sizes and what is holding it:

      This source sends 1920x1080 but /dev/video10 is set to 640x480 by obs
      (pid 4821), which cannot change while it holds the device. The picture is
      being scaled to fit. To use the full size, remove the source in OBS, leave
      this publishing, and add it again.

    The size a source sends is also shown on the Sources page, where nothing
    reported it before.

    The reliable order is: assign the camera in StreamSink, confirm it is
    publishing, then add the Video Capture Device in OBS. Sources already in OBS
    need removing and adding once to pick up the correct format.

    Licence

    MIT, which the repository previously had none of.

    Downloads

    File For
    streamsink-0.9.0-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.9.0-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.9.0-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.9.0-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.8.0 46449328ad

    v0.8.0 Stable

    builduser released this 2026-09-07 07:17:47 +02:00 | 3 commits to main since this release

    Fixes virtual audio devices all arriving in OBS with the same name, so there
    was no way to tell which carried which source.

    Descriptions were being truncated

    A device description travels in a property list that is split on whitespace,
    and nothing survives it: quoting, backslashes and single quotes were all tried
    and the value is still cut at the first space. "StreamSink 1" and
    "StreamSink 2" therefore both arrived as "StreamSink".

    Descriptions no longer contain spaces, so devices appear as:

    Monitor of StreamSink-Studio-A
    Monitor of StreamSink-Roof-Cam
    

    Devices are also named after the sources they will carry rather than numbered,
    and the Sources page says exactly what to select in OBS for the device each
    source is assigned to.

    Existing devices keep their truncated names. Set each source's audio to
    Off, then use Create audio devices again.

    On the test that missed it

    The check that should have caught this only required a description to be
    non-empty, which the fault passes. It now reads the description back from the
    audio server, compares it against what was asked for, and requires devices to
    differ from one another. Create returns what is actually stored rather than
    what it requested, since reporting the request instead of the result is how a
    value silently truncated by another process goes unnoticed.

    Downloads

    File For
    streamsink-0.8.0-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.8.0-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.8.0-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.8.0-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.9 6c34fbdb9f

    v0.7.9 Stable

    builduser released this 2026-09-07 06:46:37 +02:00 | 4 commits to main since this release

    Fixes a picture of coloured bands that survived the packet-discarding fix in
    0.7.8. This one is a disagreement about how big a frame is.

    What was wrong

    A loopback keeps the format it was given for as long as anything holds it
    open. A consumer that attaches before anything has written settles the format
    itself, on the module's default, and it cannot then be changed. Frames of the
    source's own size written into that are read as though they were the default
    size, which is a picture of coloured bands.

    Relying on being started in the right order is not a fix: nothing enforces it,
    and a consumer reconnecting is enough to undo it.

    The device is now asked what it is set to, and that is what gets sent. The
    picture is correct whoever attached first, at whatever size the consumer
    settled on. A size set explicitly on the source still wins, and with nothing
    attached the source passes through at its own size as before.

    When the size came from a consumer rather than the source, the interface says
    so, names what is holding the device, and gives both ways to change it.

    Seeing it

    streamsink -devices
    

    now reports the format each device is set to and what is holding it open:

    /dev/video10  driver v4l2 loopback  format 640x480 YU12  output true  capture false  "StreamSink 1"
                     held open by obs (pid 4821), so its format cannot change
    

    Neither was visible before, and both decide whether a picture will be right.

    If you want full resolution rather than whatever OBS settled on

    Either remove the source in OBS, confirm StreamSink is publishing, and re-add
    it; or set the per-source Size, which wins over everything.

    Caveat

    v4l2loopback is not installed on the machine this is built on, so this
    behaviour is reasoned from the ioctl semantics rather than observed. -check
    and -devices exist to test it where the module actually is.

    Downloads

    File For
    streamsink-0.7.9-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.9-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.9-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.9-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.8 42cf8e2390

    v0.7.8 Stable

    builduser released this 2026-09-07 06:33:22 +02:00 | 5 commits to main since this release

    Fixes a source publishing a wrongly sized picture in coloured bands.

    The stream was being discarded while it was identified

    The cause was -fflags nobuffer, which both the camera output and the program
    view passed to ffmpeg.

    It sounds like the right thing for a live feed and is widely recommended. What
    it actually does is discard the packets consumed while identifying the streams
    rather than passing them on, and those include the codec configuration. The
    decoder is then working from a stream it cannot properly interpret, which is a
    picture of coloured bands when it is not simply nothing at all.

    Measured on ffmpeg n9.0.1 with a four second sample:

    frames decoded
    without the flag 60 of 60
    with -fflags nobuffer 30 of 60
    from a file rather than a pipe 0

    How much is lost depends on how the stream arrives, which is why the fault
    came and went and looked like a problem with one particular machine. The same
    flag also contributed to the "stream map matches no streams" failure fixed in
    0.7.7.

    -flags low_delay stays: it tells the decoder not to hold frames back and
    discards nothing.

    Seeing and avoiding format mismatches

    Devices are now asked what geometry they are set to, shown in the device list
    and by streamsink -devices. A loopback keeps the format it was given for as
    long as anything holds it open, so a source of a different size cannot change
    it while OBS is attached. When a camera starts against a device something else
    already holds, that is now said plainly rather than left to be inferred from a
    strange looking picture.

    A size can also be pinned per source, so the format never has to change at
    all. Useful when one device carries sources of differing sizes over time.
    Leave it on Match source otherwise.

    Downloads

    File For
    streamsink-0.7.8-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.8-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.8-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.8-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.7 d1a2b7ed10

    v0.7.7 Stable

    builduser released this 2026-09-07 06:22:40 +02:00 | 6 commits to main since this release

    Fixes a source failing to publish with:

    stream map '' matches no streams
    failed to set value '0:v' for option map
    

    What was wrong

    ffmpeg's stream probing was limited to 32 bytes and no analysis time, in the
    belief that it kept latency down. It does not: probing stops as soon as the
    streams are known, so those limits only matter when something goes wrong. What
    they did instead was make ffmpeg give up before it had seen a single video
    tag, leaving no video stream for -map 0:v to match.

    Whether it failed depended on how the stream happened to arrive. Fed a whole
    file at once, 32 bytes can be enough by luck; fed at the rate a live source
    actually arrives, it is not. One source worked and the next did not, which
    made it look like a problem with the second machine rather than with the
    setting.

    The limits are now a megabyte and half a second, still upper bounds rather
    than waits, so there is no cost in the normal case. The decoder behind the
    program view had the same setting.

    The test feeds a stream at roughly live pace rather than all at once, since
    delivering it in one go is exactly the case where too small a probe happens to
    work anyway.

    Downloads

    File For
    streamsink-0.7.7-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.7-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.7-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.7-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.6 d6841cd03d

    v0.7.6 Stable

    builduser released this 2026-09-07 06:08:32 +02:00 | 7 commits to main since this release

    Makes a failing virtual camera say what actually went wrong, and fixes one
    that could stop for good without ever reporting it.

    The reason was being discarded

    A camera failing said:

    vcam agent: Nate-Windows: flv: write tag body: write | 1: broken pipe
    

    That is the symptom and never the reason. Writing to ffmpeg's input only
    breaks because ffmpeg has already exited, and its own message, which says what
    actually went wrong, was thrown away in exactly that case: the feed error
    returned first and the captured output was never looked at.

    What ffmpeg said now comes first, along with the device it was writing to,
    since a rig has several and knowing which one failed matters:

    vcam agent: Nate-Windows: ffmpeg failed writing to /dev/video11: <reason>
    

    Reading that message also raced with waiting for the process, which closes the
    pipe, so it could be truncated or lost depending on timing. It is now read to
    the end first.

    A dead decoder could hang instead of restarting

    Feeding blocks waiting for media, so a session that only ended when the feed
    failed would hang for as long as a source stayed quiet after ffmpeg died: no
    write, no error, no restart, and a camera that never came back. The feed now
    ends when the process does.

    The decoder behind the program view had both faults and both fixes.

    Downloads

    File For
    streamsink-0.7.6-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.6-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.6-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.6-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.5 c67c8f1307

    v0.7.5 Stable

    builduser released this 2026-09-07 05:51:59 +02:00 | 8 commits to main since this release

    Fixes a crash when installing an update, and answers "what do I have to do"
    for the audio side, which the program previously could not.

    The update panel crashed while reporting a failure

    Installing an update that failed died with a nil pointer dereference, which
    hid whatever had actually gone wrong.

    The panel was handed its logging function before the log page it belongs to
    had been built. A method value on a nil pointer binds the nil receiver quite
    happily and only fails when it is finally called: here in a worker goroutine,
    reporting a failed install. The log page is now built first, and logging
    tolerates being called before one exists, because a failure being reported is
    not a good moment to crash. The same log was also written from worker
    goroutines and read on the main loop with nothing guarding it, and is now
    behind a mutex.

    Two install failures are also explained rather than reported bare, since one
    of them is what triggered the crash. An archive whose programs are not the
    ones installed here says what it contained and where it looked, and a
    directory that cannot be written to is detected before any binary is swapped
    rather than partway through.

    Audio devices are now diagnosed

    Creating an audio device failed with a message naming Debian packages on an
    Arch machine, and -check did not cover audio at all, so the program could
    not say what was missing.

    pactl comes from libpulse on Arch and pulseaudio-utils on Debian and
    Fedora. Suggestions now follow whichever package manager is present.

    -check reports both halves. A rig on virtual cameras needs both: the video
    loopback carries no sound, so a source with a camera and no audio device is
    silent.

    Video
      [ok  ]  platform                   Linux
      [ok  ]  ffmpeg                     /usr/bin/ffmpeg (n9.0.1)
      [FAIL]  v4l2loopback module        not loaded
    
    Audio
      [ok  ]  pactl                      /usr/bin/pactl
      [ok  ]  sound server               PulseAudio (on PipeWire 1.6.8) 15.0.0
      [FAIL]  audio devices              none yet
    

    Every failure carries the command to fix it.

    The likeliest audio failure is a machine with no desktop session, where no
    sound server is running at all. That is called out specifically, including the
    case where StreamSink was started outside a login session and so cannot see a
    per-user server:

    sudo pacman -S libpulse pipewire pipewire-pulse wireplumber
    systemctl --user enable --now pipewire pipewire-pulse wireplumber
    

    Unlike the video side this needs no root and no kernel module. Once a sound
    server is answering, Create audio devices in Settings works directly.

    Downloads

    File For
    streamsink-0.7.5-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.5-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.5-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.5-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater

    There was no 0.7.4 release; it is folded into this one.

    Downloads
  • v0.7.3 ad7b41d12b

    v0.7.3 Stable

    builduser released this 2026-09-07 05:39:24 +02:00 | 10 commits to main since this release

    Fixes virtual camera devices being rejected even once they appeared. Both
    faults were found by streamsink -check on a real machine, which reported
    them plainly.

    The driver has a space in its name

    The module names itself v4l2 loopback, while the package and the modprobe
    argument are v4l2loopback. Devices were matched against the obvious spelling
    exactly, so every device the module created was rejected as being driven by
    something else and none were offered:

    [FAIL]  devices    2 found, none usable
      StreamSink 1  (/dev/video10)  — driven by "v4l2 loopback", not v4l2loopback
    

    The name is now compared with spaces and case ignored.

    exclusive_caps is per device

    It takes one value per device, not one for the module. The command this program
    printed passed a single 1, which sets it on the first device and leaves the
    rest without it, so the parameter read Y,N,N,N... and OBS would list one
    camera and ignore the others.

    The generated command now passes one value per device, and the check reads the
    array per device rather than looking for a 1 anywhere in it, so it names the
    devices actually missing the setting.

    This one needs the module reloaded, since the setting is fixed when it
    loads:

    sudo sh -c 'modprobe -r v4l2loopback; modprobe v4l2loopback video_nr=10,11 exclusive_caps=1,1 card_label="StreamSink 1,StreamSink 2"'
    

    Downloads

    File For
    streamsink-0.7.3-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.3-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.3-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.3-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads
  • v0.7.2 4ab2838fae

    v0.7.2 Stable

    builduser released this 2026-09-07 05:33:44 +02:00 | 11 commits to main since this release

    Fixes virtual camera devices not appearing in the interface at all, on a
    machine where they plainly existed as /dev/video10 and /dev/video11.

    What was wrong

    Enumeration walked /sys/devices/virtual/video4linux, which is a detail of how
    the kernel happens to arrange devices and is not present on every machine.
    Where that directory is absent the listing came back empty, and the interface
    reported no devices with nothing to suggest it had been looking in the wrong
    place.

    The nodes in /dev now decide what exists. What each one actually is still
    comes from asking its driver, which was already how loopbacks were told apart
    from real capture hardware.

    Two things made this harder to see than it should have been, and both are also
    fixed:

    • The device menu listed only devices it considered usable, so one that existed
      but could not be used looked identical to nothing existing. Every loopback
      device is now listed, with the reason when it cannot be used.
    • Enumeration opened each device for writing to test it, every couple of
      seconds. That is intrusive on a device whose advertised capabilities change
      depending on whether a writer holds it. Capabilities are now read with a
      non-blocking open, the way v4l2 tools probe, and whether a device can be
      claimed is decided when one is assigned.

    A new diagnostic

    ./streamsink -devices
    

    Lists every video node and what its driver says about it, with no filtering,
    then says which ones StreamSink is offering. For the case where the interface
    and /dev disagree.

    ./streamsink -check still exercises the whole path, ending with a test
    pattern written to a device and read back.

    Downloads

    File For
    streamsink-0.7.2-linux-amd64.tar.gz Linux server, client and agent
    streamsink-0.7.2-windows-agent-amd64.zip Windows agent, headless
    streamsink-0.7.2-windows-server-amd64.zip Windows server and client, with the GTK4 runtime
    streamsink-0.7.2-windows-decoder-amd64.zip Windows decoder, for the program view
    SHA256SUMS checksums, used by the updater
    Downloads