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}