# purego-pipewire Go bindings for [PipeWire](https://pipewire.org/) via [purego](https://github.com/bnema/purego) instead of cgo. Linux only. `CGO_ENABLED=0`. > [!WARNING] > This is early stage. The generated binding layer is in place, but the handwritten API is still focused on runtime lifecycle and float32 playback. API may change. ## Install ```sh go get github.com/bnema/purego-pipewire ``` ## Architecture The binding layer is **generated** from the checked-in model in `gen/pipewire.json`. The generator produces: - **purego function bindings** for PipeWire init, loop, and stream entry points - **Outbound port interfaces** grouped by API surface (`InitAPI`, `LoopAPI`, `StreamOps`) - **A composite CAPI interface** that embeds the generated port groups - **Inbound port interfaces** for the handwritten core layer - **Public API wrappers** in `pipewire/` and SPA constants/helpers in `spa/` A small hand-written core implements runtime init/deinit, player lifecycle, stream setup, and PCM buffer handoff for playback. Mocks for public interfaces and outbound ports are generated by [Mockery v3](https://vektra.github.io/mockery/) and shipped in `pipewire/mocks/` and `internal/ports/out/mocks/`. ## Usage ### Runtime lifecycle ```go import "github.com/bnema/purego-pipewire/pipewire" rt, err := pipewire.Init() if err != nil { return err } defer rt.Deinit() loop, err := rt.NewMainLoop() if err != nil { return err } defer loop.Destroy() go loop.Run() ``` ### Player playback ```go import "github.com/bnema/purego-pipewire/pipewire" rt, err := pipewire.Init() if err != nil { return err } defer rt.Deinit() player, err := pipewire.NewPlayer(pipewire.PlayerConfig{ SampleRate: 48000, Channels: 2, FramesPerBuffer: 256, SampleFormat: pipewire.SampleFormatF32, UnderrunPolicy: pipewire.UnderrunFillSilence, }, pipewire.PlayerCallbacks{ Fill: func(buf *pipewire.PCMBuffer) (int, error) { for ch := range buf.Samples { for i := range buf.Samples[ch] { buf.Samples[ch][i] = 0 } } return buf.Frames, nil }, }) if err != nil { return err } defer player.Close() if err := player.Start(); err != nil { return err } ``` ### Testing with mocks The package ships pre-generated [Mockery v3](https://vektra.github.io/mockery/) mocks for the public interfaces in `github.com/bnema/purego-pipewire/pipewire/mocks`. **Step 1: Accept the interface in your code** ```go package output import "github.com/bnema/purego-pipewire/pipewire" type Service struct { player pipewire.Player } func New(player pipewire.Player) *Service { return &Service{player: player} } func (s *Service) Start() error { return s.player.Start() } ``` **Step 2: Mock it in tests** ```go package output_test import ( "testing" "github.com/bnema/purego-pipewire/pipewire/mocks" "github.com/stretchr/testify/require" ) func TestServiceStart(t *testing.T) { mockPlayer := mocks.NewMockPlayer(t) mockPlayer.EXPECT().Start().Return(nil) svc := output.New(mockPlayer) require.NoError(t, svc.Start()) } ``` **Available mocks** in `github.com/bnema/purego-pipewire/pipewire/mocks`: | Mock | Interface | Key methods | |---|---|---| | `MockRuntime` | `pipewire.Runtime` | `Init`, `Deinit`, `NewMainLoop` | | `MockMainLoop` | `pipewire.MainLoop` | `Run`, `Destroy` | | `MockPlayer` | `pipewire.Player` | `Start`, `Pause`, `Stop`, `Close`, `State` | All mocks support `.EXPECT()` for type-safe expectations and `.On()`/`.Return()` for classic testify style. ## Requirements - Go 1.26.1+ - Linux with `libpipewire-0.3.so.0` - A running PipeWire daemon for integration tests and real playback - Override path: `PIPEWIRE_LIB_PATH=/custom/path/libpipewire-0.3.so.0` ## Building ```sh CGO_ENABLED=0 go build ./... CGO_ENABLED=0 go test ./... ``` Integration tests need a real PipeWire daemon: ```sh go test -tags integration ./integration ./pipewire -v ``` The player integration suite checks public lifecycle transitions and proves that a live PipeWire process callback invokes the user-supplied `Fill` callback. ## Regenerating bindings ```sh go generate ./... ``` Or manually: ```sh go run ./cmd/pipewiregen mockery ``` Maintainers refreshing the checked-in model can run: ```sh ./scripts/sync-headers.sh go generate ./... ``` ## Project layout ```text pipewire/ public API + mocks spa/ public PipeWire and SPA constants/helpers internal/ ports/in/ inbound port interfaces ports/out/ outbound port interfaces + mocks core/ runtime and playback logic capi/ purego bindings + bridge loader/ dlopen libpipewire-0.3.so.0 cmd/pipewiregen/ binding generator (parser, model, emitter, templates) gen/ checked-in model data integration/ integration tests (require a real PipeWire daemon) ``` ## License MIT