v0.1.2 dc8c53b

pwbuffer.go RawHistoryFolder

1package core
2
3import (
4	"errors"
5	"fmt"
6	"unsafe"
7)
8
9// PipeWire buffer layout structs matching the C ABI.
10// These are low-level mappings that allow interpreting a dequeue'd buffer
11// pointer as writable planar float32 data. They are intentionally minimal
12// and local — not a general SPA buffer parser.
13
14// spaChunk mirrors struct spa_chunk from <spa/buffer/buffer.h>.
15type spaChunk struct {
16	Offset uint32
17	Size   uint32
18	Stride int32
19	Flags  int32
20}
21
22// spaData mirrors struct spa_data from <spa/buffer/buffer.h>.
23type spaData struct {
24	Type      uint32
25	Flags     uint32
26	Fd        int64
27	MapOffset uint32
28	Maxsize   uint32
29	Data      unsafe.Pointer
30	Chunk     *spaChunk
31}
32
33// spaBuffer mirrors struct spa_buffer from <spa/buffer/buffer.h>.
34type spaBuffer struct {
35	NMetas uint32
36	NDatas uint32
37	Metas  unsafe.Pointer // struct spa_meta * — pointer to array of spa_meta
38	Datas  unsafe.Pointer // struct spa_data * — pointer to array of spa_data
39}
40
41// pwBuffer mirrors struct pw_buffer from <pipewire/stream.h>.
42type pwBuffer struct {
43	Buffer    *spaBuffer
44	UserData  unsafe.Pointer
45	Size      uint64
46	Requested uint64
47	Time      uint64
48}
49
50// pwBufferView provides a safe Go view over a dequeue'd PipeWire buffer,
51// exposing planar float32 channel data. Call Commit after writing frames
52// to update chunk size/stride and pw buffer size.
53type pwBufferView struct {
54	buf      *pwBuffer
55	channels int
56	frames   int
57	data     []spaData
58	pcm      PCMBuffer
59	samples  [][]float32
60}
61
62var (
63	ErrNilBufferPointer = errors.New("nil buffer pointer")
64	ErrChannelMismatch  = errors.New("buffer data count does not match channels")
65	ErrUnmappedData     = errors.New("buffer data pointer is nil")
66	ErrFrameOverflow    = errors.New("requested frames exceed buffer capacity")
67)
68
69// newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that
70// exposes planar float32 samples for each channel. The caller must ensure
71// bufPtr is a valid pw_buffer pointer returned by pw_stream_dequeue_buffer.
72// The view holds a pointer directly into the original pwBuffer so that Commit
73// writes back into the shared memory structure, not a Go copy.
74func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView, error) {
75	if bufPtr == nil {
76		return nil, ErrNilBufferPointer
77	}
78
79	pw := (*pwBuffer)(bufPtr)
80	if pw.Buffer == nil {
81		return nil, ErrNilBufferPointer
82	}
83
84	if pw.Buffer.NDatas != uint32(channels) {
85		return nil, ErrChannelMismatch
86	}
87
88	// Datas is a pointer to a C array of spaData structs.
89	dataSlice := unsafe.Slice((*spaData)(pw.Buffer.Datas), int(pw.Buffer.NDatas))
90
91	samples := make([][]float32, channels)
92	for i := 0; i < channels; i++ {
93		data := dataSlice[i]
94		if data.Data == nil {
95			return nil, ErrUnmappedData
96		}
97		// Reject if the buffer cannot hold the requested number of frames.
98		maxFrames := int(data.Maxsize) / 4
99		if frames > maxFrames {
100			return nil, fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, frames, maxFrames)
101		}
102		samples[i] = unsafe.Slice((*float32)(data.Data), frames)
103	}
104
105	return &pwBufferView{
106		buf:      pw,
107		channels: channels,
108		frames:   frames,
109		data:     dataSlice,
110		pcm: PCMBuffer{
111			Frames:   frames,
112			Channels: channels,
113			Stride:   4,
114			Samples:  samples,
115		},
116		samples: samples,
117	}, nil
118}
119
120// PCM returns a PCMBuffer pointing at the buffer's planar float32 data.
121// The caller can write into Samples and then call Commit.
122// Stride is set to 4 (bytes per float32 sample) for planar float32 layout.
123func (v *pwBufferView) PCM() *PCMBuffer {
124	return &v.pcm
125}
126
127// Commit updates each data chunk's size and stride to reflect the number of
128// frames written, and sets the pw buffer size field. Call this after writing
129// audio data into the PCM buffer.
130func (v *pwBufferView) Commit(frames int) {
131	v.buf.Size = uint64(frames)
132	for i := 0; i < v.channels; i++ {
133		data := &v.data[i]
134		if data.Chunk != nil {
135			data.Chunk.Size = uint32(frames * 4) // float32 = 4 bytes
136			data.Chunk.Stride = 4                // stride for planar float32
137		}
138	}
139}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close