-
v0.10.0 Stable
released this
2026-09-08 02:00:07 +02:00 | 0 commits to main since this releaseThe 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.-checkwas broken and would have failed on a working setup. A generated
test pattern written without-regoes 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.gzLinux server, client and agent streamsink-0.10.0-windows-agent-amd64.zipWindows agent, headless streamsink-0.10.0-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.10.0-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.9.0 Stable
released this
2026-09-07 07:33:29 +02:00 | 2 commits to main since this releaseFixes 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.gzLinux server, client and agent streamsink-0.9.0-windows-agent-amd64.zipWindows agent, headless streamsink-0.9.0-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.9.0-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
-
v0.8.0 Stable
released this
2026-09-07 07:17:47 +02:00 | 3 commits to main since this releaseFixes 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-CamDevices 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.Createreturns 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.gzLinux server, client and agent streamsink-0.8.0-windows-agent-amd64.zipWindows agent, headless streamsink-0.8.0-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.8.0-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.9 Stable
released this
2026-09-07 06:46:37 +02:00 | 4 commits to main since this releaseFixes 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 -devicesnow 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 changeNeither 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
v4l2loopbackis not installed on the machine this is built on, so this
behaviour is reasoned from the ioctl semantics rather than observed.-check
and-devicesexist to test it where the module actually is.Downloads
File For streamsink-0.7.9-linux-amd64.tar.gzLinux server, client and agent streamsink-0.7.9-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.9-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.9-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.8 Stable
released this
2026-09-07 06:33:22 +02:00 | 5 commits to main since this releaseFixes 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 nobuffer30 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_delaystays: 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 bystreamsink -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.gzLinux server, client and agent streamsink-0.7.8-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.8-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.8-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.7 Stable
released this
2026-09-07 06:22:40 +02:00 | 6 commits to main since this releaseFixes a source failing to publish with:
stream map '' matches no streams failed to set value '0:v' for option mapWhat 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:vto 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.gzLinux server, client and agent streamsink-0.7.7-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.7-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.7-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.6 Stable
released this
2026-09-07 06:08:32 +02:00 | 7 commits to main since this releaseMakes 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 pipeThat 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.gzLinux server, client and agent streamsink-0.7.6-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.6-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.6-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.5 Stable
released this
2026-09-07 05:51:59 +02:00 | 8 commits to main since this releaseFixes 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-checkdid not cover audio at all, so the program could
not say what was missing.pactlcomes fromlibpulseon Arch andpulseaudio-utilson Debian and
Fedora. Suggestions now follow whichever package manager is present.-checkreports 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 yetEvery 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 wireplumberUnlike 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.gzLinux server, client and agent streamsink-0.7.5-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.5-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.5-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater There was no 0.7.4 release; it is folded into this one.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.3 Stable
released this
2026-09-07 05:39:24 +02:00 | 10 commits to main since this releaseFixes virtual camera devices being rejected even once they appeared. Both
faults were found bystreamsink -checkon 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 arev4l2loopback. 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 v4l2loopbackThe 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 single1, which sets it on the first device and leaves the
rest without it, so the parameter readY,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 a1anywhere 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.gzLinux server, client and agent streamsink-0.7.3-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.3-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.3-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
Source code (ZIP)
-
v0.7.2 Stable
released this
2026-09-07 05:33:44 +02:00 | 11 commits to main since this releaseFixes virtual camera devices not appearing in the interface at all, on a
machine where they plainly existed as/dev/video10and/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
/devnow 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 -devicesLists 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/devdisagree../streamsink -checkstill 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.gzLinux server, client and agent streamsink-0.7.2-windows-agent-amd64.zipWindows agent, headless streamsink-0.7.2-windows-server-amd64.zipWindows server and client, with the GTK4 runtime streamsink-0.7.2-windows-decoder-amd64.zipWindows decoder, for the program view SHA256SUMSchecksums, used by the updater Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The device menu listed only devices it considered usable, so one that existed