detached 66c3355

fix: tighten PipeWire buffer helper invariants

commit
66c3355792f6d0576c462837103a6f42f41da9f0
author
brice · · 2026-04-10 13:27 UTC
parent
177250e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +119 -68
M internal/core/pwbuffer.go +9 -6
@@ -2,6 +2,7 @@ package core
22
33 import (
44 "errors"
5+ "fmt"
56 "unsafe"
67 )
78
@@ -58,6 +59,7 @@ var (
5859 ErrNilBufferPointer = errors.New("nil buffer pointer")
5960 ErrChannelMismatch = errors.New("buffer data count does not match channels")
6061 ErrUnmappedData = errors.New("buffer data pointer is nil")
62+ ErrFrameOverflow = errors.New("requested frames exceed buffer capacity")
6163 )
6264
6365 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that
@@ -88,13 +90,12 @@ func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView
8890 if data.Data == nil {
8991 return nil, ErrUnmappedData
9092 }
91- // Slice the raw memory as []float32 for this channel.
92- // Data.Maxsize gives the total buffer size in bytes for this channel.
93- nFloats := frames
94- if maxSize := int(data.Maxsize) / 4; nFloats > maxSize {
95- nFloats = maxSize
93+ // Reject if the buffer cannot hold the requested number of frames.
94+ maxFrames := int(data.Maxsize) / 4
95+ if frames > maxFrames {
96+ return nil, fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, frames, maxFrames)
9697 }
97- samples[i] = unsafe.Slice((*float32)(data.Data), nFloats)
98+ samples[i] = unsafe.Slice((*float32)(data.Data), frames)
9899 }
99100
100101 return &pwBufferView{
@@ -107,10 +108,12 @@ func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView
107108
108109 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.
109110 // The caller can write into Samples and then call Commit.
111+// Stride is set to 4 (bytes per float32 sample) for planar float32 layout.
110112 func (v *pwBufferView) PCM() *PCMBuffer {
111113 return &PCMBuffer{
112114 Frames: v.frames,
113115 Channels: v.channels,
116+ Stride: 4, // sizeof(float32) — planar float32 byte stride per channel
114117 Samples: v.samples,
115118 }
116119 }
@@ -2,6 +2,7 @@ package core
2 2
3 import (3 import (
4 "errors"4 "errors"
5+ "fmt"
5 "unsafe"6 "unsafe"
6 )7 )
7 8
@@ -58,6 +59,7 @@ var (
58 ErrNilBufferPointer = errors.New("nil buffer pointer")59 ErrNilBufferPointer = errors.New("nil buffer pointer")
59 ErrChannelMismatch = errors.New("buffer data count does not match channels")60 ErrChannelMismatch = errors.New("buffer data count does not match channels")
60 ErrUnmappedData = errors.New("buffer data pointer is nil")61 ErrUnmappedData = errors.New("buffer data pointer is nil")
62+ ErrFrameOverflow = errors.New("requested frames exceed buffer capacity")
61 )63 )
62 64
63 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that65 // newPWBufferView interprets bufPtr as a *pwBuffer and returns a view that
@@ -88,13 +90,12 @@ func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView
88 if data.Data == nil {90 if data.Data == nil {
89 return nil, ErrUnmappedData91 return nil, ErrUnmappedData
90 }92 }
91- // Slice the raw memory as []float32 for this channel.93+ // Reject if the buffer cannot hold the requested number of frames.
92- // Data.Maxsize gives the total buffer size in bytes for this channel.94+ maxFrames := int(data.Maxsize) / 4
93- nFloats := frames95+ if frames > maxFrames {
94- if maxSize := int(data.Maxsize) / 4; nFloats > maxSize {96+ return nil, fmt.Errorf("%w: requested %d frames but buffer holds %d", ErrFrameOverflow, frames, maxFrames)
95- nFloats = maxSize
96 }97 }
97- samples[i] = unsafe.Slice((*float32)(data.Data), nFloats)98+ samples[i] = unsafe.Slice((*float32)(data.Data), frames)
98 }99 }
99 100
100 return &pwBufferView{101 return &pwBufferView{
@@ -107,10 +108,12 @@ func newPWBufferView(bufPtr unsafe.Pointer, channels, frames int) (*pwBufferView
107 108
108 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.109 // PCM returns a PCMBuffer pointing at the buffer's planar float32 data.
109 // The caller can write into Samples and then call Commit.110 // The caller can write into Samples and then call Commit.
111+// Stride is set to 4 (bytes per float32 sample) for planar float32 layout.
110 func (v *pwBufferView) PCM() *PCMBuffer {112 func (v *pwBufferView) PCM() *PCMBuffer {
111 return &PCMBuffer{113 return &PCMBuffer{
112 Frames: v.frames,114 Frames: v.frames,
113 Channels: v.channels,115 Channels: v.channels,
116+ Stride: 4, // sizeof(float32) — planar float32 byte stride per channel
114 Samples: v.samples,117 Samples: v.samples,
115 }118 }
116 }119 }
M internal/core/pwbuffer_test.go +110 -62
@@ -6,62 +6,58 @@ import (
66 "unsafe"
77 )
88
9-// TestNewPWBufferViewWrapsWritablePlanarFloatData verifies that newPWBufferView
10-// correctly interprets a C-layout pwBuffer as planar float32 channel data.
11-func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
12- const channels = 2
13- const frames = 64
9+// Named constants for SPA data type and flag values (from <spa/buffer/buffer.h>).
10+const (
11+ spaDataTypeMemPtr uint32 = 2 // SPA_DATA_MemPtr
12+ spaDataFlagRW uint32 = 3 // SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_WRITABLE
13+)
1414
15- // Allocate planar float32 backing memory for each channel.
15+// helper to build a channels×frames buffer view for tests.
16+// Returns the view, the chunks slice, and the pwBuffer pointer.
17+func newTestBufferView(t *testing.T, channels, frames, maxsizeFrames int) (*pwBufferView, []spaChunk, *pwBuffer) {
18+ t.Helper()
1619 channelData := make([][]float32, channels)
1720 channelPtrs := make([]unsafe.Pointer, channels)
1821 for ch := 0; ch < channels; ch++ {
19- channelData[ch] = make([]float32, frames)
22+ channelData[ch] = make([]float32, maxsizeFrames)
2023 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
2124 }
2225
23- // Build the C-layout chunk structs.
2426 chunks := make([]spaChunk, channels)
25- for ch := 0; ch < channels; ch++ {
26- chunks[ch] = spaChunk{
27- Offset: 0,
28- Size: 0, // will be set on Commit
29- Stride: 0,
30- Flags: 0,
31- }
32- }
33-
34- // Build the C-layout spaData structs.
3527 datas := make([]spaData, channels)
3628 for ch := 0; ch < channels; ch++ {
29+ chunks[ch] = spaChunk{}
3730 datas[ch] = spaData{
38- Type: 2, // SPA_DATA_MemPtr
39- Flags: 3, // SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_WRITABLE
40- Maxsize: uint32(frames * 4), // bytes
31+ Type: spaDataTypeMemPtr,
32+ Flags: spaDataFlagRW,
33+ Maxsize: uint32(maxsizeFrames * 4),
4134 Data: channelPtrs[ch],
4235 Chunk: &chunks[ch],
4336 }
4437 }
4538
46- // Build the C-layout spaBuffer.
4739 buf := spaBuffer{
48- NMetas: 0,
4940 NDatas: uint32(channels),
50- Metas: nil,
5141 Datas: unsafe.Pointer(&datas[0]),
5242 }
53-
54- // Build the C-layout pwBuffer.
5543 pwBuf := pwBuffer{
56- Buffer: &buf,
57- UserData: nil,
58- Size: 0,
44+ Buffer: &buf,
5945 }
6046
6147 view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
6248 if err != nil {
63- t.Fatalf("unexpected error: %v", err)
49+ t.Fatalf("unexpected error building test buffer: %v", err)
6450 }
51+ return view, chunks, &pwBuf
52+}
53+
54+// TestNewPWBufferViewWrapsWritablePlanarFloatData verifies that newPWBufferView
55+// correctly interprets a C-layout pwBuffer as planar float32 channel data.
56+func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
57+ const channels = 2
58+ const frames = 64
59+
60+ view, _, _ := newTestBufferView(t, channels, frames, frames)
6561
6662 pcm := view.PCM()
6763 if pcm.Channels != channels {
@@ -70,6 +66,9 @@ func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
7066 if pcm.Frames != frames {
7167 t.Errorf("expected %d frames, got %d", frames, pcm.Frames)
7268 }
69+ if pcm.Stride != 4 {
70+ t.Errorf("expected Stride=4 for planar float32, got %d", pcm.Stride)
71+ }
7372 if len(pcm.Samples) != channels {
7473 t.Fatalf("expected %d sample slices, got %d", channels, len(pcm.Samples))
7574 }
@@ -78,8 +77,60 @@ func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
7877 t.Errorf("channel %d: expected %d frames, got %d", ch, frames, len(pcm.Samples[ch]))
7978 }
8079 }
80+}
81+
82+// TestNewPWBufferViewPCMStrideIsSet verifies that PCM() returns a buffer
83+// with Stride set to 4 (bytes per float32 for planar layout).
84+func TestNewPWBufferViewPCMStrideIsSet(t *testing.T) {
85+ const channels = 1
86+ const frames = 32
87+
88+ view, _, _ := newTestBufferView(t, channels, frames, frames)
89+
90+ pcm := view.PCM()
91+ if pcm.Stride != 4 {
92+ t.Errorf("PCMBuffer.Stride = %d, want 4 (sizeof float32)", pcm.Stride)
93+ }
94+}
95+
96+// TestNewPWBufferViewSharedMemory verifies that writes to the PCMBuffer
97+// propagate to the underlying channel data (not a copy).
98+func TestNewPWBufferViewSharedMemory(t *testing.T) {
99+ const channels = 2
100+ const frames = 64
101+
102+ channelData := make([][]float32, channels)
103+ channelPtrs := make([]unsafe.Pointer, channels)
104+ for ch := 0; ch < channels; ch++ {
105+ channelData[ch] = make([]float32, frames)
106+ channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
107+ }
81108
82- // Verify the PCM buffer shares memory with channelData (not a copy).
109+ chunks := make([]spaChunk, channels)
110+ datas := make([]spaData, channels)
111+ for ch := 0; ch < channels; ch++ {
112+ chunks[ch] = spaChunk{}
113+ datas[ch] = spaData{
114+ Type: spaDataTypeMemPtr,
115+ Flags: spaDataFlagRW,
116+ Maxsize: uint32(frames * 4),
117+ Data: channelPtrs[ch],
118+ Chunk: &chunks[ch],
119+ }
120+ }
121+
122+ buf := spaBuffer{
123+ NDatas: uint32(channels),
124+ Datas: unsafe.Pointer(&datas[0]),
125+ }
126+ pwBuf := pwBuffer{Buffer: &buf}
127+
128+ view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
129+ if err != nil {
130+ t.Fatalf("unexpected error: %v", err)
131+ }
132+
133+ pcm := view.PCM()
83134 pcm.Samples[0][0] = 1.0
84135 if channelData[0][0] != 1.0 {
85136 t.Error("PCM buffer does not share memory with underlying channel data — write did not propagate")
@@ -114,21 +165,17 @@ func TestNewPWBufferViewRejectsNilPointer(t *testing.T) {
114165 // TestNewPWBufferViewRejectsChannelMismatch verifies that a buffer whose
115166 // NDatas does not match the requested channel count returns ErrChannelMismatch.
116167 func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {
117- // Build a buffer claiming 1 data slot.
118168 singleData := spaData{
119- Type: 2,
169+ Type: spaDataTypeMemPtr,
120170 Maxsize: 256,
121171 Data: unsafe.Pointer(new(float32)),
122172 Chunk: &spaChunk{},
123173 }
124174 buf := spaBuffer{
125- NMetas: 0,
126175 NDatas: 1, // only 1 channel
127176 Datas: unsafe.Pointer(&singleData),
128177 }
129- pwBuf := pwBuffer{
130- Buffer: &buf,
131- }
178+ pwBuf := pwBuffer{Buffer: &buf}
132179
133180 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64) // requesting 2 channels
134181 if !errors.Is(err, ErrChannelMismatch) {
@@ -141,26 +188,23 @@ func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {
141188 func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {
142189 datas := make([]spaData, 2)
143190 datas[0] = spaData{
144- Type: 2,
191+ Type: spaDataTypeMemPtr,
145192 Maxsize: 256,
146193 Data: nil, // nil — should trigger ErrUnmappedData
147194 Chunk: &spaChunk{},
148195 }
149196 datas[1] = spaData{
150- Type: 2,
197+ Type: spaDataTypeMemPtr,
151198 Maxsize: 256,
152199 Data: unsafe.Pointer(new(float32)),
153200 Chunk: &spaChunk{},
154201 }
155202
156203 buf := spaBuffer{
157- NMetas: 0,
158204 NDatas: 2,
159205 Datas: unsafe.Pointer(&datas[0]),
160206 }
161- pwBuf := pwBuffer{
162- Buffer: &buf,
163- }
207+ pwBuf := pwBuffer{Buffer: &buf}
164208
165209 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
166210 if !errors.Is(err, ErrUnmappedData) {
@@ -172,9 +216,7 @@ func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {
172216 // pointer inside the pwBuffer returns ErrNilBufferPointer.
173217 func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {
174218 pwBuf := pwBuffer{
175- Buffer: nil, // nil — should trigger ErrNilBufferPointer
176- UserData: nil,
177- Size: 0,
219+ Buffer: nil,
178220 }
179221
180222 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
@@ -183,43 +225,49 @@ func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {
183225 }
184226 }
185227
186-// TestCommitUpdatesSizeAndStride verifies that Commit writes the correct
187-// byte sizes and strides for planar float32 data.
188-func TestCommitUpdatesSizeAndStride(t *testing.T) {
228+// TestNewPWBufferViewRejectsOversizedFrames verifies that requesting more
229+// frames than the buffer can hold (based on Maxsize) returns ErrFrameOverflow.
230+func TestNewPWBufferViewRejectsOversizedFrames(t *testing.T) {
189231 const channels = 2
190- const frames = 128
232+ const maxFrames = 32
233+ const requestedFrames = 64 // exceeds maxFrames
191234
192235 channelData := make([][]float32, channels)
193236 channelPtrs := make([]unsafe.Pointer, channels)
194237 for ch := 0; ch < channels; ch++ {
195- channelData[ch] = make([]float32, frames)
238+ channelData[ch] = make([]float32, maxFrames)
196239 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
197240 }
198241
199- chunks := make([]spaChunk, channels)
200242 datas := make([]spaData, channels)
201243 for ch := 0; ch < channels; ch++ {
202244 datas[ch] = spaData{
203- Type: 2,
204- Maxsize: uint32(frames * 4),
245+ Type: spaDataTypeMemPtr,
246+ Maxsize: uint32(maxFrames * 4), // only room for 32 frames
205247 Data: channelPtrs[ch],
206- Chunk: &chunks[ch],
248+ Chunk: &spaChunk{},
207249 }
208250 }
209251
210252 buf := spaBuffer{
211- NMetas: 0,
212253 NDatas: uint32(channels),
213254 Datas: unsafe.Pointer(&datas[0]),
214255 }
215- pwBuf := pwBuffer{
216- Buffer: &buf,
217- }
256+ pwBuf := pwBuffer{Buffer: &buf}
218257
219- view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
220- if err != nil {
221- t.Fatalf("unexpected error: %v", err)
258+ _, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, requestedFrames)
259+ if !errors.Is(err, ErrFrameOverflow) {
260+ t.Fatalf("expected ErrFrameOverflow, got %v", err)
222261 }
262+}
263+
264+// TestCommitUpdatesSizeAndStride verifies that Commit writes the correct
265+// byte sizes and strides for planar float32 data.
266+func TestCommitUpdatesSizeAndStride(t *testing.T) {
267+ const channels = 2
268+ const frames = 128
269+
270+ view, chunks, pwBuf := newTestBufferView(t, channels, frames, frames)
223271
224272 // Write some audio data to the PCM buffer.
225273 const writtenFrames = 96
@@ -6,62 +6,58 @@ import (
6 "unsafe"6 "unsafe"
7 )7 )
8 8
9-// TestNewPWBufferViewWrapsWritablePlanarFloatData verifies that newPWBufferView9+// Named constants for SPA data type and flag values (from <spa/buffer/buffer.h>).
10-// correctly interprets a C-layout pwBuffer as planar float32 channel data.10+const (
11-func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {11+ spaDataTypeMemPtr uint32 = 2 // SPA_DATA_MemPtr
12- const channels = 212+ spaDataFlagRW uint32 = 3 // SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_WRITABLE
13- const frames = 6413+)
14 14
15- // Allocate planar float32 backing memory for each channel.15+// helper to build a channels×frames buffer view for tests.
16+// Returns the view, the chunks slice, and the pwBuffer pointer.
17+func newTestBufferView(t *testing.T, channels, frames, maxsizeFrames int) (*pwBufferView, []spaChunk, *pwBuffer) {
18+ t.Helper()
16 channelData := make([][]float32, channels)19 channelData := make([][]float32, channels)
17 channelPtrs := make([]unsafe.Pointer, channels)20 channelPtrs := make([]unsafe.Pointer, channels)
18 for ch := 0; ch < channels; ch++ {21 for ch := 0; ch < channels; ch++ {
19- channelData[ch] = make([]float32, frames)22+ channelData[ch] = make([]float32, maxsizeFrames)
20 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])23 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
21 }24 }
22 25
23- // Build the C-layout chunk structs.
24 chunks := make([]spaChunk, channels)26 chunks := make([]spaChunk, channels)
25- for ch := 0; ch < channels; ch++ {
26- chunks[ch] = spaChunk{
27- Offset: 0,
28- Size: 0, // will be set on Commit
29- Stride: 0,
30- Flags: 0,
31- }
32- }
33-
34- // Build the C-layout spaData structs.
35 datas := make([]spaData, channels)27 datas := make([]spaData, channels)
36 for ch := 0; ch < channels; ch++ {28 for ch := 0; ch < channels; ch++ {
29+ chunks[ch] = spaChunk{}
37 datas[ch] = spaData{30 datas[ch] = spaData{
38- Type: 2, // SPA_DATA_MemPtr31+ Type: spaDataTypeMemPtr,
39- Flags: 3, // SPA_DATA_FLAG_READABLE | SPA_DATA_FLAG_WRITABLE32+ Flags: spaDataFlagRW,
40- Maxsize: uint32(frames * 4), // bytes33+ Maxsize: uint32(maxsizeFrames * 4),
41 Data: channelPtrs[ch],34 Data: channelPtrs[ch],
42 Chunk: &chunks[ch],35 Chunk: &chunks[ch],
43 }36 }
44 }37 }
45 38
46- // Build the C-layout spaBuffer.
47 buf := spaBuffer{39 buf := spaBuffer{
48- NMetas: 0,
49 NDatas: uint32(channels),40 NDatas: uint32(channels),
50- Metas: nil,
51 Datas: unsafe.Pointer(&datas[0]),41 Datas: unsafe.Pointer(&datas[0]),
52 }42 }
53-
54- // Build the C-layout pwBuffer.
55 pwBuf := pwBuffer{43 pwBuf := pwBuffer{
56- Buffer: &buf,44+ Buffer: &buf,
57- UserData: nil,
58- Size: 0,
59 }45 }
60 46
61 view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)47 view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
62 if err != nil {48 if err != nil {
63- t.Fatalf("unexpected error: %v", err)49+ t.Fatalf("unexpected error building test buffer: %v", err)
64 }50 }
51+ return view, chunks, &pwBuf
52+}
53+
54+// TestNewPWBufferViewWrapsWritablePlanarFloatData verifies that newPWBufferView
55+// correctly interprets a C-layout pwBuffer as planar float32 channel data.
56+func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
57+ const channels = 2
58+ const frames = 64
59+
60+ view, _, _ := newTestBufferView(t, channels, frames, frames)
65 61
66 pcm := view.PCM()62 pcm := view.PCM()
67 if pcm.Channels != channels {63 if pcm.Channels != channels {
@@ -70,6 +66,9 @@ func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
70 if pcm.Frames != frames {66 if pcm.Frames != frames {
71 t.Errorf("expected %d frames, got %d", frames, pcm.Frames)67 t.Errorf("expected %d frames, got %d", frames, pcm.Frames)
72 }68 }
69+ if pcm.Stride != 4 {
70+ t.Errorf("expected Stride=4 for planar float32, got %d", pcm.Stride)
71+ }
73 if len(pcm.Samples) != channels {72 if len(pcm.Samples) != channels {
74 t.Fatalf("expected %d sample slices, got %d", channels, len(pcm.Samples))73 t.Fatalf("expected %d sample slices, got %d", channels, len(pcm.Samples))
75 }74 }
@@ -78,8 +77,60 @@ func TestNewPWBufferViewWrapsWritablePlanarFloatData(t *testing.T) {
78 t.Errorf("channel %d: expected %d frames, got %d", ch, frames, len(pcm.Samples[ch]))77 t.Errorf("channel %d: expected %d frames, got %d", ch, frames, len(pcm.Samples[ch]))
79 }78 }
80 }79 }
80+}
81+
82+// TestNewPWBufferViewPCMStrideIsSet verifies that PCM() returns a buffer
83+// with Stride set to 4 (bytes per float32 for planar layout).
84+func TestNewPWBufferViewPCMStrideIsSet(t *testing.T) {
85+ const channels = 1
86+ const frames = 32
87+
88+ view, _, _ := newTestBufferView(t, channels, frames, frames)
89+
90+ pcm := view.PCM()
91+ if pcm.Stride != 4 {
92+ t.Errorf("PCMBuffer.Stride = %d, want 4 (sizeof float32)", pcm.Stride)
93+ }
94+}
95+
96+// TestNewPWBufferViewSharedMemory verifies that writes to the PCMBuffer
97+// propagate to the underlying channel data (not a copy).
98+func TestNewPWBufferViewSharedMemory(t *testing.T) {
99+ const channels = 2
100+ const frames = 64
101+
102+ channelData := make([][]float32, channels)
103+ channelPtrs := make([]unsafe.Pointer, channels)
104+ for ch := 0; ch < channels; ch++ {
105+ channelData[ch] = make([]float32, frames)
106+ channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
107+ }
81 108
82- // Verify the PCM buffer shares memory with channelData (not a copy).109+ chunks := make([]spaChunk, channels)
110+ datas := make([]spaData, channels)
111+ for ch := 0; ch < channels; ch++ {
112+ chunks[ch] = spaChunk{}
113+ datas[ch] = spaData{
114+ Type: spaDataTypeMemPtr,
115+ Flags: spaDataFlagRW,
116+ Maxsize: uint32(frames * 4),
117+ Data: channelPtrs[ch],
118+ Chunk: &chunks[ch],
119+ }
120+ }
121+
122+ buf := spaBuffer{
123+ NDatas: uint32(channels),
124+ Datas: unsafe.Pointer(&datas[0]),
125+ }
126+ pwBuf := pwBuffer{Buffer: &buf}
127+
128+ view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)
129+ if err != nil {
130+ t.Fatalf("unexpected error: %v", err)
131+ }
132+
133+ pcm := view.PCM()
83 pcm.Samples[0][0] = 1.0134 pcm.Samples[0][0] = 1.0
84 if channelData[0][0] != 1.0 {135 if channelData[0][0] != 1.0 {
85 t.Error("PCM buffer does not share memory with underlying channel data — write did not propagate")136 t.Error("PCM buffer does not share memory with underlying channel data — write did not propagate")
@@ -114,21 +165,17 @@ func TestNewPWBufferViewRejectsNilPointer(t *testing.T) {
114 // TestNewPWBufferViewRejectsChannelMismatch verifies that a buffer whose165 // TestNewPWBufferViewRejectsChannelMismatch verifies that a buffer whose
115 // NDatas does not match the requested channel count returns ErrChannelMismatch.166 // NDatas does not match the requested channel count returns ErrChannelMismatch.
116 func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {167 func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {
117- // Build a buffer claiming 1 data slot.
118 singleData := spaData{168 singleData := spaData{
119- Type: 2,169+ Type: spaDataTypeMemPtr,
120 Maxsize: 256,170 Maxsize: 256,
121 Data: unsafe.Pointer(new(float32)),171 Data: unsafe.Pointer(new(float32)),
122 Chunk: &spaChunk{},172 Chunk: &spaChunk{},
123 }173 }
124 buf := spaBuffer{174 buf := spaBuffer{
125- NMetas: 0,
126 NDatas: 1, // only 1 channel175 NDatas: 1, // only 1 channel
127 Datas: unsafe.Pointer(&singleData),176 Datas: unsafe.Pointer(&singleData),
128 }177 }
129- pwBuf := pwBuffer{178+ pwBuf := pwBuffer{Buffer: &buf}
130- Buffer: &buf,
131- }
132 179
133 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64) // requesting 2 channels180 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64) // requesting 2 channels
134 if !errors.Is(err, ErrChannelMismatch) {181 if !errors.Is(err, ErrChannelMismatch) {
@@ -141,26 +188,23 @@ func TestNewPWBufferViewRejectsChannelMismatch(t *testing.T) {
141 func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {188 func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {
142 datas := make([]spaData, 2)189 datas := make([]spaData, 2)
143 datas[0] = spaData{190 datas[0] = spaData{
144- Type: 2,191+ Type: spaDataTypeMemPtr,
145 Maxsize: 256,192 Maxsize: 256,
146 Data: nil, // nil — should trigger ErrUnmappedData193 Data: nil, // nil — should trigger ErrUnmappedData
147 Chunk: &spaChunk{},194 Chunk: &spaChunk{},
148 }195 }
149 datas[1] = spaData{196 datas[1] = spaData{
150- Type: 2,197+ Type: spaDataTypeMemPtr,
151 Maxsize: 256,198 Maxsize: 256,
152 Data: unsafe.Pointer(new(float32)),199 Data: unsafe.Pointer(new(float32)),
153 Chunk: &spaChunk{},200 Chunk: &spaChunk{},
154 }201 }
155 202
156 buf := spaBuffer{203 buf := spaBuffer{
157- NMetas: 0,
158 NDatas: 2,204 NDatas: 2,
159 Datas: unsafe.Pointer(&datas[0]),205 Datas: unsafe.Pointer(&datas[0]),
160 }206 }
161- pwBuf := pwBuffer{207+ pwBuf := pwBuffer{Buffer: &buf}
162- Buffer: &buf,
163- }
164 208
165 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)209 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
166 if !errors.Is(err, ErrUnmappedData) {210 if !errors.Is(err, ErrUnmappedData) {
@@ -172,9 +216,7 @@ func TestNewPWBufferViewRejectsUnmappedData(t *testing.T) {
172 // pointer inside the pwBuffer returns ErrNilBufferPointer.216 // pointer inside the pwBuffer returns ErrNilBufferPointer.
173 func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {217 func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {
174 pwBuf := pwBuffer{218 pwBuf := pwBuffer{
175- Buffer: nil, // nil — should trigger ErrNilBufferPointer219+ Buffer: nil,
176- UserData: nil,
177- Size: 0,
178 }220 }
179 221
180 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)222 _, err := newPWBufferView(unsafe.Pointer(&pwBuf), 2, 64)
@@ -183,43 +225,49 @@ func TestNewPWBufferViewRejectsNilSpaBuffer(t *testing.T) {
183 }225 }
184 }226 }
185 227
186-// TestCommitUpdatesSizeAndStride verifies that Commit writes the correct228+// TestNewPWBufferViewRejectsOversizedFrames verifies that requesting more
187-// byte sizes and strides for planar float32 data.229+// frames than the buffer can hold (based on Maxsize) returns ErrFrameOverflow.
188-func TestCommitUpdatesSizeAndStride(t *testing.T) {230+func TestNewPWBufferViewRejectsOversizedFrames(t *testing.T) {
189 const channels = 2231 const channels = 2
190- const frames = 128232+ const maxFrames = 32
233+ const requestedFrames = 64 // exceeds maxFrames
191 234
192 channelData := make([][]float32, channels)235 channelData := make([][]float32, channels)
193 channelPtrs := make([]unsafe.Pointer, channels)236 channelPtrs := make([]unsafe.Pointer, channels)
194 for ch := 0; ch < channels; ch++ {237 for ch := 0; ch < channels; ch++ {
195- channelData[ch] = make([]float32, frames)238+ channelData[ch] = make([]float32, maxFrames)
196 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])239 channelPtrs[ch] = unsafe.Pointer(&channelData[ch][0])
197 }240 }
198 241
199- chunks := make([]spaChunk, channels)
200 datas := make([]spaData, channels)242 datas := make([]spaData, channels)
201 for ch := 0; ch < channels; ch++ {243 for ch := 0; ch < channels; ch++ {
202 datas[ch] = spaData{244 datas[ch] = spaData{
203- Type: 2,245+ Type: spaDataTypeMemPtr,
204- Maxsize: uint32(frames * 4),246+ Maxsize: uint32(maxFrames * 4), // only room for 32 frames
205 Data: channelPtrs[ch],247 Data: channelPtrs[ch],
206- Chunk: &chunks[ch],248+ Chunk: &spaChunk{},
207 }249 }
208 }250 }
209 251
210 buf := spaBuffer{252 buf := spaBuffer{
211- NMetas: 0,
212 NDatas: uint32(channels),253 NDatas: uint32(channels),
213 Datas: unsafe.Pointer(&datas[0]),254 Datas: unsafe.Pointer(&datas[0]),
214 }255 }
215- pwBuf := pwBuffer{256+ pwBuf := pwBuffer{Buffer: &buf}
216- Buffer: &buf,
217- }
218 257
219- view, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, frames)258+ _, err := newPWBufferView(unsafe.Pointer(&pwBuf), channels, requestedFrames)
220- if err != nil {259+ if !errors.Is(err, ErrFrameOverflow) {
221- t.Fatalf("unexpected error: %v", err)260+ t.Fatalf("expected ErrFrameOverflow, got %v", err)
222 }261 }
262+}
263+
264+// TestCommitUpdatesSizeAndStride verifies that Commit writes the correct
265+// byte sizes and strides for planar float32 data.
266+func TestCommitUpdatesSizeAndStride(t *testing.T) {
267+ const channels = 2
268+ const frames = 128
269+
270+ view, chunks, pwBuf := newTestBufferView(t, channels, frames, frames)
223 271
224 // Write some audio data to the PCM buffer.272 // Write some audio data to the PCM buffer.
225 const writtenFrames = 96273 const writtenFrames = 96

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