Repository navigation
fastframe 0.12.0
fastframe 0.12.0 lets apps record as well as play with fastframe-audio. An input works as an output does: it costs nothing while paused, follows the default microphone and reopens after a failure. Devices now have ids that stay when a device is renamed or the list changes, an output can pause itself between sounds, and an app that mixes with rodio can use the crate's mixer renderer instead of its own.
Upgrade by moving every fastframe crate to the tag:
fastframe-audio = { git = "https://github.com/crmne/fastframe", tag = "v0.12.0" }No code change is required for what apps do today, with two exceptions that only hit code written against every variant or field:
Devicehas a new variant,Device::Id. AmatchonDevicethat namesDefaultandNamedand no wildcard adds an arm for it.OutputOptionshas a new field,pause_when_idle. A struct literal that names every field addspause_when_idle: None; one that ends in..OutputOptions::default(), as Solco's and Spotifast's do, needs nothing.
Maintained::Reopened keeps its fields, so Solco and Spotifast build unchanged.
New
- Capture audio (
Input,Capture,InputOptions). An input is an output the other way round: it opens the device by id, name or the default, falls back through the same configurations, gets no callbacks while paused, reopens after a failure or a stall, follows the default input, and classifies errors as an output does. Its callback runs with the same priority as an output's: real-time on Linux on the devices that wait for the hardware, in MMCSS on Windows, on CoreAudio's I/O thread on macOS. Every sample format cpal captures arrives asf32. ItsClockcounts what was captured, andunderruns()the blocks the device lost because the callback came late. On PipeWire on Linux, the default input and the Cam Link and FLOW 8 inputs captured at 48 kHz, with no callbacks while paused; so did the PulseAudio host. Thecaptureexample measures what it captures and drops it. By @crmne. - An output can pause itself when it has nothing to play (
OutputOptions::pause_when_idle,Render::idle). A renderer that says it is idle (nothing queued, nothing sounding) for that long is paused bymaintain(), so a sound effect player that is quiet most of the time costs nothing between sounds, and afterrelease_afterlets the device go. The app callsresume()whenever it queues a sound, which costs nothing while the output plays. Both are off by default:Render::idleis a default method that saysfalse, andpause_when_idleisNone, so existing renderers need no change. By @crmne. - Play rodio's mixer (
MixerRender, therodiofeature). A renderer around rodio'sMixer, modelled on Spotifast's: the output configures it when it opens a stream, the same format keeps the mixer and what it plays, and another rate or channel count makes a new one, which the app takes from aMixerSlot. It is idle while the mixer has nothing to play, so it pauses withpause_when_idle. The feature is off by default. By @crmne. - Pick a device by an id that stays (
output_devices,input_devices,Device::Id). The two listings give each device a picker can offer as aDeviceInfo: aDeviceIdto save, its name, and whether it is the default. The id is the system's own, without the system's name: the PipeWire node name (which is also the sink or source name underpipewire-pulse), the ALSA PCM name, the WASAPI endpoint id or the CoreAudio UID.Device::Idopens the device by it, and falls back to the default with a warning when it is missing, asDevice::Nameddoes. An input is opened by id or name only among the devicesinput_devices()offers, so a source that shares its name with a sink opens the source, never the sink's monitor. Unlike a name, an id tells apart two devices with the same name. The listings leave out cpal's PipeWire default devices and other apps' playback streams, the monitors of outputs among the inputs, and ALSA'snulldevice.Output::device_id()andInput::device_id()give the id of the device the stream opened on; aftermaintain()returnsReopened(whose fields have not changed), they give the new device's. On PipeWire's default device, the first call after each open asks PipeWire's PulseAudio server which node that is, about 10 ms; opening itself does not wait for it.output_device_names()andDevice::Namedwork as before. By @crmne. - Name the audio system in the log (
fastframe_audio::host()). It returnsPipeWire,PulseAudio,ALSA,CoreAudioorWASAPI. The process chooses its audio system the first time it opens or lists a device and keeps it, so every stream and listing goes to the same one, and each still gets a fresh connection, so devices plugged in later show up. By @crmne.
Changed
- Open errors no longer say "output" (
OpenError). Its messages are now "There is no audio device." and "Cannot open the audio device: ...", for an input as for an output. An app that shows them reads the new wording. By @crmne.
Dependencies
- The new
rodiofeature offastframe-audiobringsrodio0.21, the version Spotifast uses, without its default features: noplayback(and so no second cpal), no decoders. It addsnum-rationaland itsnum-bigintandnum-integer;dasp_sampleis already in the tree through cpal.
Known limitations
- On PulseAudio an input can count overruns it did not have. cpal estimates PulseAudio's capture times from the wall clock, so a callback that came late without losing anything can count in
Clock::underruns(). In three runs of thecaptureexample, each capturing for two seconds, it counted 0, 1 and 2; PipeWire counted none. - On PipeWire, another app's recording stream can be listed among the inputs. cpal lists it as it lists a source.
- On PipeWire,
Audio/Duplexnodes are missing frominput_devices(). cpal lists them as it lists sinks, duplex because a sink's monitor can be captured, so they are left out with the monitors, and an input is not opened on them by id or name. They are still listed as outputs. MixerRendercan drop a sound queued just before a reopen at another rate. A sound added to the mixer right beforeresume()reopens a device let go, or beforemaintain()reopens on another device, at a different rate or channel count plays from the old mixer, which the new stream no longer reads. Add sounds to the mixer fromMixerSlot::take()after eachReopened; a same-format reopen keeps the mixer.- The audio system is chosen once per process. The first open or listing picks PipeWire, PulseAudio or ALSA and the process keeps it, so an app that opens audio before PipeWire's socket exists, early in a session, stays on ALSA until it restarts.
Thanks
- The Solco session, for reviewing this release before it went out: it found that an input opened by a name a sink shares recorded the sink's monitor, that every open of the default on PipeWire waited for its PulseAudio server, and the race between an idle render and
resume(). - Spotifast's rodio adapter, which
MixerRenderis modelled on.
Full changelog: v0.11.0...v0.12.0