refactor: carry playback format through player core
- commit
- 28a2e52057744b1c597594171e819c136971f395
- author
- brice · · 2026-04-10 12:34 UTC
- parent
- 53759e6
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +168 -22
M internal/capi/stream_ops.go +12 -3
| @@ -85,7 +85,12 @@ func (s *streamOpsImpl) CreatePlaybackStream(loopPtr unsafe.Pointer, name string | |||
| 85 | 85 | return ptr, nil | |
| 86 | 86 | } | |
| 87 | 87 | ||
| 88 | - | func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |
| 88 | + | func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer, format portout.PlaybackFormat) error { | |
| 89 | + | // Validate format before making the C call. | |
| 90 | + | if format.SampleRate <= 0 || format.Channels <= 0 || format.FramesPerBuffer <= 0 { | |
| 91 | + | return fmt.Errorf("invalid PlaybackFormat: SampleRate=%d, Channels=%d, FramesPerBuffer=%d", | |
| 92 | + | format.SampleRate, format.Channels, format.FramesPerBuffer) | |
| 93 | + | } | |
| 89 | 94 | // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff | |
| 90 | 95 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | |
| 91 | 96 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | |
| @@ -148,8 +153,12 @@ func (s *streamOpsImpl) CreateMainLoop() (unsafe.Pointer, error) { | |||
| 148 | 153 | return ptr, nil | |
| 149 | 154 | } | |
| 150 | 155 | ||
| 151 | - | func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) { | |
| 152 | - | pw_main_loop_run(loopPtr) | |
| 156 | + | func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) error { | |
| 157 | + | ret := pw_main_loop_run(loopPtr) | |
| 158 | + | if ret < 0 { | |
| 159 | + | return &PWError{Func: "pw_main_loop_run", Code: ret} | |
| 160 | + | } | |
| 161 | + | return nil | |
| 153 | 162 | } | |
| 154 | 163 | ||
| 155 | 164 | func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) { | |
| @@ -85,7 +85,12 @@ func (s *streamOpsImpl) CreatePlaybackStream(loopPtr unsafe.Pointer, name string | |||||
| 85 | return ptr, nil | 85 | return ptr, nil | ||
| 86 | } | 86 | } | ||
| 87 | 87 | ||||
| 88 | - | func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | 88 | + | func (s *streamOpsImpl) ConnectPlaybackStream(streamPtr unsafe.Pointer, format portout.PlaybackFormat) error { |
| 89 | + | // Validate format before making the C call. | |||
| 90 | + | if format.SampleRate <= 0 || format.Channels <= 0 || format.FramesPerBuffer <= 0 { | |||
| 91 | + | return fmt.Errorf("invalid PlaybackFormat: SampleRate=%d, Channels=%d, FramesPerBuffer=%d", | |||
| 92 | + | format.SampleRate, format.Channels, format.FramesPerBuffer) | |||
| 93 | + | } | |||
| 89 | // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff | 94 | // PW_DIRECTION_OUTPUT = 1, PW_ID_ANY = 0xffffffff | ||
| 90 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | 95 | // PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS = 0x01 | 0x04 | ||
| 91 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | 96 | ret := pw_stream_connect(streamPtr, pwDirectionOutput, pwIDAny, pwStreamFlagAutoConnectMapBuffers, nil, 0) | ||
| @@ -148,8 +153,12 @@ func (s *streamOpsImpl) CreateMainLoop() (unsafe.Pointer, error) { | |||||
| 148 | return ptr, nil | 153 | return ptr, nil | ||
| 149 | } | 154 | } | ||
| 150 | 155 | ||||
| 151 | - | func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) { | 156 | + | func (s *streamOpsImpl) RunMainLoop(loopPtr unsafe.Pointer) error { |
| 152 | - | pw_main_loop_run(loopPtr) | 157 | + | ret := pw_main_loop_run(loopPtr) |
| 158 | + | if ret < 0 { | |||
| 159 | + | return &PWError{Func: "pw_main_loop_run", Code: ret} | |||
| 160 | + | } | |||
| 161 | + | return nil | |||
| 153 | } | 162 | } | ||
| 154 | 163 | ||||
| 155 | func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) { | 164 | func (s *streamOpsImpl) QuitMainLoop(loopPtr unsafe.Pointer) { | ||
M internal/capi/stream_ops_test.go +109 -1
| @@ -4,6 +4,8 @@ import ( | |||
| 4 | 4 | "errors" | |
| 5 | 5 | "testing" | |
| 6 | 6 | "unsafe" | |
| 7 | + | ||
| 8 | + | portout "github.com/bnema/purego-pipewire/internal/ports/out" | |
| 7 | 9 | ) | |
| 8 | 10 | ||
| 9 | 11 | // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates | |
| @@ -133,7 +135,12 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||
| 133 | 135 | ops := &streamOpsImpl{} | |
| 134 | 136 | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 135 | 137 | ||
| 136 | - | err := ops.ConnectPlaybackStream(fakePtr) | |
| 138 | + | validFmt := portout.PlaybackFormat{ | |
| 139 | + | SampleRate: 48000, | |
| 140 | + | Channels: 2, | |
| 141 | + | FramesPerBuffer: 1024, | |
| 142 | + | } | |
| 143 | + | err := ops.ConnectPlaybackStream(fakePtr, validFmt) | |
| 137 | 144 | if err == nil { | |
| 138 | 145 | t.Fatal("expected error for negative return code, got nil") | |
| 139 | 146 | } | |
| @@ -259,3 +266,104 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) { | |||
| 259 | 266 | t.Fatalf("expected nil on success, got %v", err) | |
| 260 | 267 | } | |
| 261 | 268 | } | |
| 269 | + | ||
| 270 | + | // TestConnectPlaybackStreamRejectsMissingFormat verifies that ConnectPlaybackStream | |
| 271 | + | // now requires a PlaybackFormat and rejects a zero-valued format. | |
| 272 | + | func TestConnectPlaybackStreamRejectsMissingFormat(t *testing.T) { | |
| 273 | + | origConnect := pw_stream_connect | |
| 274 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |
| 275 | + | ||
| 276 | + | // Even if pw_stream_connect succeeds, a zero PlaybackFormat should be rejected | |
| 277 | + | // at the StreamOps layer before the C call is made. | |
| 278 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |
| 279 | + | return 0 | |
| 280 | + | } | |
| 281 | + | ||
| 282 | + | ops := &streamOpsImpl{} | |
| 283 | + | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |
| 284 | + | ||
| 285 | + | // Zero-valued PlaybackFormat must be rejected. | |
| 286 | + | zeroFmt := portout.PlaybackFormat{} | |
| 287 | + | err := ops.ConnectPlaybackStream(fakePtr, zeroFmt) | |
| 288 | + | if err == nil { | |
| 289 | + | t.Fatal("expected error for zero-valued PlaybackFormat, got nil") | |
| 290 | + | } | |
| 291 | + | } | |
| 292 | + | ||
| 293 | + | // TestConnectPlaybackStreamBuildsParamsFromFormat verifies that ConnectPlaybackStream | |
| 294 | + | // accepts a valid PlaybackFormat and calls pw_stream_connect successfully. | |
| 295 | + | func TestConnectPlaybackStreamBuildsParamsFromFormat(t *testing.T) { | |
| 296 | + | origConnect := pw_stream_connect | |
| 297 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |
| 298 | + | ||
| 299 | + | var called bool | |
| 300 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |
| 301 | + | called = true | |
| 302 | + | return 0 | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | ops := &streamOpsImpl{} | |
| 306 | + | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |
| 307 | + | ||
| 308 | + | validFmt := portout.PlaybackFormat{ | |
| 309 | + | SampleRate: 48000, | |
| 310 | + | Channels: 2, | |
| 311 | + | FramesPerBuffer: 1024, | |
| 312 | + | } | |
| 313 | + | err := ops.ConnectPlaybackStream(fakePtr, validFmt) | |
| 314 | + | if err != nil { | |
| 315 | + | t.Fatalf("expected nil error for valid format, got %v", err) | |
| 316 | + | } | |
| 317 | + | if !called { | |
| 318 | + | t.Fatal("expected pw_stream_connect to be called") | |
| 319 | + | } | |
| 320 | + | } | |
| 321 | + | ||
| 322 | + | // TestRunMainLoopReturnsErrorOnNegativeReturn verifies that RunMainLoop | |
| 323 | + | // returns a PWError when pw_main_loop_run returns a negative value. | |
| 324 | + | func TestRunMainLoopReturnsErrorOnNegativeReturn(t *testing.T) { | |
| 325 | + | origRun := pw_main_loop_run | |
| 326 | + | t.Cleanup(func() { pw_main_loop_run = origRun }) | |
| 327 | + | ||
| 328 | + | pw_main_loop_run = func(loop unsafe.Pointer) int32 { | |
| 329 | + | return -22 // EINVAL | |
| 330 | + | } | |
| 331 | + | ||
| 332 | + | ops := &streamOpsImpl{} | |
| 333 | + | fakeLoop := unsafe.Pointer(uintptr(0xAAAA)) | |
| 334 | + | ||
| 335 | + | err := ops.RunMainLoop(fakeLoop) | |
| 336 | + | if err == nil { | |
| 337 | + | t.Fatal("expected error for negative return code, got nil") | |
| 338 | + | } | |
| 339 | + | ||
| 340 | + | var pwErr *PWError | |
| 341 | + | if !errors.As(err, &pwErr) { | |
| 342 | + | t.Fatalf("expected *PWError, got %T", err) | |
| 343 | + | } | |
| 344 | + | if pwErr.Func != "pw_main_loop_run" { | |
| 345 | + | t.Errorf("expected Func='pw_main_loop_run', got '%s'", pwErr.Func) | |
| 346 | + | } | |
| 347 | + | if pwErr.Code != -22 { | |
| 348 | + | t.Errorf("expected Code=-22, got %d", pwErr.Code) | |
| 349 | + | } | |
| 350 | + | } | |
| 351 | + | ||
| 352 | + | // TestRunMainLoopReturnsNilOnSuccess verifies that RunMainLoop | |
| 353 | + | // returns nil when pw_main_loop_run returns zero. | |
| 354 | + | func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) { | |
| 355 | + | origRun := pw_main_loop_run | |
| 356 | + | t.Cleanup(func() { pw_main_loop_run = origRun }) | |
| 357 | + | ||
| 358 | + | pw_main_loop_run = func(loop unsafe.Pointer) int32 { | |
| 359 | + | return 0 | |
| 360 | + | } | |
| 361 | + | ||
| 362 | + | ops := &streamOpsImpl{} | |
| 363 | + | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |
| 364 | + | ||
| 365 | + | err := ops.RunMainLoop(fakeLoop) | |
| 366 | + | if err != nil { | |
| 367 | + | t.Fatalf("expected nil on success, got %v", err) | |
| 368 | + | } | |
| 369 | + | } | |
| @@ -4,6 +4,8 @@ import ( | |||||
| 4 | "errors" | 4 | "errors" | ||
| 5 | "testing" | 5 | "testing" | ||
| 6 | "unsafe" | 6 | "unsafe" | ||
| 7 | + | ||||
| 8 | + | portout "github.com/bnema/purego-pipewire/internal/ports/out" | |||
| 7 | ) | 9 | ) | ||
| 8 | 10 | ||||
| 9 | // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates | 11 | // TestDestroyStreamCallsPwStreamDestroy verifies that DestroyStream delegates | ||
| @@ -133,7 +135,12 @@ func TestConnectPlaybackStreamReturnsTypedErrorOnNegativeReturn(t *testing.T) { | |||||
| 133 | ops := &streamOpsImpl{} | 135 | ops := &streamOpsImpl{} | ||
| 134 | fakePtr := unsafe.Pointer(uintptr(0x1234)) | 136 | fakePtr := unsafe.Pointer(uintptr(0x1234)) | ||
| 135 | 137 | ||||
| 136 | - | err := ops.ConnectPlaybackStream(fakePtr) | 138 | + | validFmt := portout.PlaybackFormat{ |
| 139 | + | SampleRate: 48000, | |||
| 140 | + | Channels: 2, | |||
| 141 | + | FramesPerBuffer: 1024, | |||
| 142 | + | } | |||
| 143 | + | err := ops.ConnectPlaybackStream(fakePtr, validFmt) | |||
| 137 | if err == nil { | 144 | if err == nil { | ||
| 138 | t.Fatal("expected error for negative return code, got nil") | 145 | t.Fatal("expected error for negative return code, got nil") | ||
| 139 | } | 146 | } | ||
| @@ -259,3 +266,104 @@ func TestDisconnectStreamReturnsNilOnSuccess(t *testing.T) { | |||||
| 259 | t.Fatalf("expected nil on success, got %v", err) | 266 | t.Fatalf("expected nil on success, got %v", err) | ||
| 260 | } | 267 | } | ||
| 261 | } | 268 | } | ||
| 269 | + | ||||
| 270 | + | // TestConnectPlaybackStreamRejectsMissingFormat verifies that ConnectPlaybackStream | |||
| 271 | + | // now requires a PlaybackFormat and rejects a zero-valued format. | |||
| 272 | + | func TestConnectPlaybackStreamRejectsMissingFormat(t *testing.T) { | |||
| 273 | + | origConnect := pw_stream_connect | |||
| 274 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |||
| 275 | + | ||||
| 276 | + | // Even if pw_stream_connect succeeds, a zero PlaybackFormat should be rejected | |||
| 277 | + | // at the StreamOps layer before the C call is made. | |||
| 278 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |||
| 279 | + | return 0 | |||
| 280 | + | } | |||
| 281 | + | ||||
| 282 | + | ops := &streamOpsImpl{} | |||
| 283 | + | fakePtr := unsafe.Pointer(uintptr(0x1234)) | |||
| 284 | + | ||||
| 285 | + | // Zero-valued PlaybackFormat must be rejected. | |||
| 286 | + | zeroFmt := portout.PlaybackFormat{} | |||
| 287 | + | err := ops.ConnectPlaybackStream(fakePtr, zeroFmt) | |||
| 288 | + | if err == nil { | |||
| 289 | + | t.Fatal("expected error for zero-valued PlaybackFormat, got nil") | |||
| 290 | + | } | |||
| 291 | + | } | |||
| 292 | + | ||||
| 293 | + | // TestConnectPlaybackStreamBuildsParamsFromFormat verifies that ConnectPlaybackStream | |||
| 294 | + | // accepts a valid PlaybackFormat and calls pw_stream_connect successfully. | |||
| 295 | + | func TestConnectPlaybackStreamBuildsParamsFromFormat(t *testing.T) { | |||
| 296 | + | origConnect := pw_stream_connect | |||
| 297 | + | t.Cleanup(func() { pw_stream_connect = origConnect }) | |||
| 298 | + | ||||
| 299 | + | var called bool | |||
| 300 | + | pw_stream_connect = func(stream unsafe.Pointer, direction int32, id uint32, flags uint32, ports unsafe.Pointer, n_ports uint32) int32 { | |||
| 301 | + | called = true | |||
| 302 | + | return 0 | |||
| 303 | + | } | |||
| 304 | + | ||||
| 305 | + | ops := &streamOpsImpl{} | |||
| 306 | + | fakePtr := unsafe.Pointer(uintptr(0x5678)) | |||
| 307 | + | ||||
| 308 | + | validFmt := portout.PlaybackFormat{ | |||
| 309 | + | SampleRate: 48000, | |||
| 310 | + | Channels: 2, | |||
| 311 | + | FramesPerBuffer: 1024, | |||
| 312 | + | } | |||
| 313 | + | err := ops.ConnectPlaybackStream(fakePtr, validFmt) | |||
| 314 | + | if err != nil { | |||
| 315 | + | t.Fatalf("expected nil error for valid format, got %v", err) | |||
| 316 | + | } | |||
| 317 | + | if !called { | |||
| 318 | + | t.Fatal("expected pw_stream_connect to be called") | |||
| 319 | + | } | |||
| 320 | + | } | |||
| 321 | + | ||||
| 322 | + | // TestRunMainLoopReturnsErrorOnNegativeReturn verifies that RunMainLoop | |||
| 323 | + | // returns a PWError when pw_main_loop_run returns a negative value. | |||
| 324 | + | func TestRunMainLoopReturnsErrorOnNegativeReturn(t *testing.T) { | |||
| 325 | + | origRun := pw_main_loop_run | |||
| 326 | + | t.Cleanup(func() { pw_main_loop_run = origRun }) | |||
| 327 | + | ||||
| 328 | + | pw_main_loop_run = func(loop unsafe.Pointer) int32 { | |||
| 329 | + | return -22 // EINVAL | |||
| 330 | + | } | |||
| 331 | + | ||||
| 332 | + | ops := &streamOpsImpl{} | |||
| 333 | + | fakeLoop := unsafe.Pointer(uintptr(0xAAAA)) | |||
| 334 | + | ||||
| 335 | + | err := ops.RunMainLoop(fakeLoop) | |||
| 336 | + | if err == nil { | |||
| 337 | + | t.Fatal("expected error for negative return code, got nil") | |||
| 338 | + | } | |||
| 339 | + | ||||
| 340 | + | var pwErr *PWError | |||
| 341 | + | if !errors.As(err, &pwErr) { | |||
| 342 | + | t.Fatalf("expected *PWError, got %T", err) | |||
| 343 | + | } | |||
| 344 | + | if pwErr.Func != "pw_main_loop_run" { | |||
| 345 | + | t.Errorf("expected Func='pw_main_loop_run', got '%s'", pwErr.Func) | |||
| 346 | + | } | |||
| 347 | + | if pwErr.Code != -22 { | |||
| 348 | + | t.Errorf("expected Code=-22, got %d", pwErr.Code) | |||
| 349 | + | } | |||
| 350 | + | } | |||
| 351 | + | ||||
| 352 | + | // TestRunMainLoopReturnsNilOnSuccess verifies that RunMainLoop | |||
| 353 | + | // returns nil when pw_main_loop_run returns zero. | |||
| 354 | + | func TestRunMainLoopReturnsNilOnSuccess(t *testing.T) { | |||
| 355 | + | origRun := pw_main_loop_run | |||
| 356 | + | t.Cleanup(func() { pw_main_loop_run = origRun }) | |||
| 357 | + | ||||
| 358 | + | pw_main_loop_run = func(loop unsafe.Pointer) int32 { | |||
| 359 | + | return 0 | |||
| 360 | + | } | |||
| 361 | + | ||||
| 362 | + | ops := &streamOpsImpl{} | |||
| 363 | + | fakeLoop := unsafe.Pointer(uintptr(0xBBBB)) | |||
| 364 | + | ||||
| 365 | + | err := ops.RunMainLoop(fakeLoop) | |||
| 366 | + | if err != nil { | |||
| 367 | + | t.Fatalf("expected nil on success, got %v", err) | |||
| 368 | + | } | |||
| 369 | + | } | |||
M internal/core/player_rt.go +1 -0
| @@ -36,6 +36,7 @@ func (b *PCMBuffer) allocate() { | |||
| 36 | 36 | ||
| 37 | 37 | // PlayerConfig holds configuration for the player | |
| 38 | 38 | type PlayerConfig struct { | |
| 39 | + | SampleRate int | |
| 39 | 40 | FramesPerBuffer int | |
| 40 | 41 | Channels int | |
| 41 | 42 | UnderrunPolicy UnderrunPolicy | |
| @@ -36,6 +36,7 @@ func (b *PCMBuffer) allocate() { | |||||
| 36 | 36 | ||||
| 37 | // PlayerConfig holds configuration for the player | 37 | // PlayerConfig holds configuration for the player | ||
| 38 | type PlayerConfig struct { | 38 | type PlayerConfig struct { | ||
| 39 | + | SampleRate int | |||
| 39 | FramesPerBuffer int | 40 | FramesPerBuffer int | ||
| 40 | Channels int | 41 | Channels int | ||
| 41 | UnderrunPolicy UnderrunPolicy | 42 | UnderrunPolicy UnderrunPolicy | ||
M internal/ports/out/mocks/mock_StreamOps.go +33 -15
| @@ -7,6 +7,7 @@ package mocks | |||
| 7 | 7 | import ( | |
| 8 | 8 | "unsafe" | |
| 9 | 9 | ||
| 10 | + | "github.com/bnema/purego-pipewire/internal/ports/out" | |
| 10 | 11 | mock "github.com/stretchr/testify/mock" | |
| 11 | 12 | ) | |
| 12 | 13 | ||
| @@ -38,16 +39,16 @@ func (_m *MockStreamOps) EXPECT() *MockStreamOps_Expecter { | |||
| 38 | 39 | } | |
| 39 | 40 | ||
| 40 | 41 | // ConnectPlaybackStream provides a mock function for the type MockStreamOps | |
| 41 | - | func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | |
| 42 | - | ret := _mock.Called(streamPtr) | |
| 42 | + | func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer, format out.PlaybackFormat) error { | |
| 43 | + | ret := _mock.Called(streamPtr, format) | |
| 43 | 44 | ||
| 44 | 45 | if len(ret) == 0 { | |
| 45 | 46 | panic("no return value specified for ConnectPlaybackStream") | |
| 46 | 47 | } | |
| 47 | 48 | ||
| 48 | 49 | var r0 error | |
| 49 | - | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | |
| 50 | - | r0 = returnFunc(streamPtr) | |
| 50 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer, out.PlaybackFormat) error); ok { | |
| 51 | + | r0 = returnFunc(streamPtr, format) | |
| 51 | 52 | } else { | |
| 52 | 53 | r0 = ret.Error(0) | |
| 53 | 54 | } | |
| @@ -61,18 +62,24 @@ type MockStreamOps_ConnectPlaybackStream_Call struct { | |||
| 61 | 62 | ||
| 62 | 63 | // ConnectPlaybackStream is a helper method to define mock.On call | |
| 63 | 64 | // - streamPtr unsafe.Pointer | |
| 64 | - | func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 65 | - | return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr)} | |
| 65 | + | // - format out.PlaybackFormat | |
| 66 | + | func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}, format interface{}) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 67 | + | return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr, format)} | |
| 66 | 68 | } | |
| 67 | 69 | ||
| 68 | - | func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer)) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 70 | + | func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat)) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 69 | 71 | _c.Call.Run(func(args mock.Arguments) { | |
| 70 | 72 | var arg0 unsafe.Pointer | |
| 71 | 73 | if args[0] != nil { | |
| 72 | 74 | arg0 = args[0].(unsafe.Pointer) | |
| 73 | 75 | } | |
| 76 | + | var arg1 out.PlaybackFormat | |
| 77 | + | if args[1] != nil { | |
| 78 | + | arg1 = args[1].(out.PlaybackFormat) | |
| 79 | + | } | |
| 74 | 80 | run( | |
| 75 | 81 | arg0, | |
| 82 | + | arg1, | |
| 76 | 83 | ) | |
| 77 | 84 | }) | |
| 78 | 85 | return _c | |
| @@ -83,7 +90,7 @@ func (_c *MockStreamOps_ConnectPlaybackStream_Call) Return(err error) *MockStrea | |||
| 83 | 90 | return _c | |
| 84 | 91 | } | |
| 85 | 92 | ||
| 86 | - | func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 93 | + | func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat) error) *MockStreamOps_ConnectPlaybackStream_Call { | |
| 87 | 94 | _c.Call.Return(run) | |
| 88 | 95 | return _c | |
| 89 | 96 | } | |
| @@ -493,9 +500,20 @@ func (_c *MockStreamOps_QuitMainLoop_Call) RunAndReturn(run func(loopPtr unsafe. | |||
| 493 | 500 | } | |
| 494 | 501 | ||
| 495 | 502 | // RunMainLoop provides a mock function for the type MockStreamOps | |
| 496 | - | func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) { | |
| 497 | - | _mock.Called(loopPtr) | |
| 498 | - | return | |
| 503 | + | func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) error { | |
| 504 | + | ret := _mock.Called(loopPtr) | |
| 505 | + | ||
| 506 | + | if len(ret) == 0 { | |
| 507 | + | panic("no return value specified for RunMainLoop") | |
| 508 | + | } | |
| 509 | + | ||
| 510 | + | var r0 error | |
| 511 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | |
| 512 | + | r0 = returnFunc(loopPtr) | |
| 513 | + | } else { | |
| 514 | + | r0 = ret.Error(0) | |
| 515 | + | } | |
| 516 | + | return r0 | |
| 499 | 517 | } | |
| 500 | 518 | ||
| 501 | 519 | // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop' | |
| @@ -522,13 +540,13 @@ func (_c *MockStreamOps_RunMainLoop_Call) Run(run func(loopPtr unsafe.Pointer)) | |||
| 522 | 540 | return _c | |
| 523 | 541 | } | |
| 524 | 542 | ||
| 525 | - | func (_c *MockStreamOps_RunMainLoop_Call) Return() *MockStreamOps_RunMainLoop_Call { | |
| 526 | - | _c.Call.Return() | |
| 543 | + | func (_c *MockStreamOps_RunMainLoop_Call) Return(err error) *MockStreamOps_RunMainLoop_Call { | |
| 544 | + | _c.Call.Return(err) | |
| 527 | 545 | return _c | |
| 528 | 546 | } | |
| 529 | 547 | ||
| 530 | - | func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer)) *MockStreamOps_RunMainLoop_Call { | |
| 531 | - | _c.Run(run) | |
| 548 | + | func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer) error) *MockStreamOps_RunMainLoop_Call { | |
| 549 | + | _c.Call.Return(run) | |
| 532 | 550 | return _c | |
| 533 | 551 | } | |
| 534 | 552 | ||
| @@ -7,6 +7,7 @@ package mocks | |||||
| 7 | import ( | 7 | import ( | ||
| 8 | "unsafe" | 8 | "unsafe" | ||
| 9 | 9 | ||||
| 10 | + | "github.com/bnema/purego-pipewire/internal/ports/out" | |||
| 10 | mock "github.com/stretchr/testify/mock" | 11 | mock "github.com/stretchr/testify/mock" | ||
| 11 | ) | 12 | ) | ||
| 12 | 13 | ||||
| @@ -38,16 +39,16 @@ func (_m *MockStreamOps) EXPECT() *MockStreamOps_Expecter { | |||||
| 38 | } | 39 | } | ||
| 39 | 40 | ||||
| 40 | // ConnectPlaybackStream provides a mock function for the type MockStreamOps | 41 | // ConnectPlaybackStream provides a mock function for the type MockStreamOps | ||
| 41 | - | func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer) error { | 42 | + | func (_mock *MockStreamOps) ConnectPlaybackStream(streamPtr unsafe.Pointer, format out.PlaybackFormat) error { |
| 42 | - | ret := _mock.Called(streamPtr) | 43 | + | ret := _mock.Called(streamPtr, format) |
| 43 | 44 | ||||
| 44 | if len(ret) == 0 { | 45 | if len(ret) == 0 { | ||
| 45 | panic("no return value specified for ConnectPlaybackStream") | 46 | panic("no return value specified for ConnectPlaybackStream") | ||
| 46 | } | 47 | } | ||
| 47 | 48 | ||||
| 48 | var r0 error | 49 | var r0 error | ||
| 49 | - | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | 50 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer, out.PlaybackFormat) error); ok { |
| 50 | - | r0 = returnFunc(streamPtr) | 51 | + | r0 = returnFunc(streamPtr, format) |
| 51 | } else { | 52 | } else { | ||
| 52 | r0 = ret.Error(0) | 53 | r0 = ret.Error(0) | ||
| 53 | } | 54 | } | ||
| @@ -61,18 +62,24 @@ type MockStreamOps_ConnectPlaybackStream_Call struct { | |||||
| 61 | 62 | ||||
| 62 | // ConnectPlaybackStream is a helper method to define mock.On call | 63 | // ConnectPlaybackStream is a helper method to define mock.On call | ||
| 63 | // - streamPtr unsafe.Pointer | 64 | // - streamPtr unsafe.Pointer | ||
| 64 | - | func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}) *MockStreamOps_ConnectPlaybackStream_Call { | 65 | + | // - format out.PlaybackFormat |
| 65 | - | return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr)} | 66 | + | func (_e *MockStreamOps_Expecter) ConnectPlaybackStream(streamPtr interface{}, format interface{}) *MockStreamOps_ConnectPlaybackStream_Call { |
| 67 | + | return &MockStreamOps_ConnectPlaybackStream_Call{Call: _e.mock.On("ConnectPlaybackStream", streamPtr, format)} | |||
| 66 | } | 68 | } | ||
| 67 | 69 | ||||
| 68 | - | func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer)) *MockStreamOps_ConnectPlaybackStream_Call { | 70 | + | func (_c *MockStreamOps_ConnectPlaybackStream_Call) Run(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat)) *MockStreamOps_ConnectPlaybackStream_Call { |
| 69 | _c.Call.Run(func(args mock.Arguments) { | 71 | _c.Call.Run(func(args mock.Arguments) { | ||
| 70 | var arg0 unsafe.Pointer | 72 | var arg0 unsafe.Pointer | ||
| 71 | if args[0] != nil { | 73 | if args[0] != nil { | ||
| 72 | arg0 = args[0].(unsafe.Pointer) | 74 | arg0 = args[0].(unsafe.Pointer) | ||
| 73 | } | 75 | } | ||
| 76 | + | var arg1 out.PlaybackFormat | |||
| 77 | + | if args[1] != nil { | |||
| 78 | + | arg1 = args[1].(out.PlaybackFormat) | |||
| 79 | + | } | |||
| 74 | run( | 80 | run( | ||
| 75 | arg0, | 81 | arg0, | ||
| 82 | + | arg1, | |||
| 76 | ) | 83 | ) | ||
| 77 | }) | 84 | }) | ||
| 78 | return _c | 85 | return _c | ||
| @@ -83,7 +90,7 @@ func (_c *MockStreamOps_ConnectPlaybackStream_Call) Return(err error) *MockStrea | |||||
| 83 | return _c | 90 | return _c | ||
| 84 | } | 91 | } | ||
| 85 | 92 | ||||
| 86 | - | func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer) error) *MockStreamOps_ConnectPlaybackStream_Call { | 93 | + | func (_c *MockStreamOps_ConnectPlaybackStream_Call) RunAndReturn(run func(streamPtr unsafe.Pointer, format out.PlaybackFormat) error) *MockStreamOps_ConnectPlaybackStream_Call { |
| 87 | _c.Call.Return(run) | 94 | _c.Call.Return(run) | ||
| 88 | return _c | 95 | return _c | ||
| 89 | } | 96 | } | ||
| @@ -493,9 +500,20 @@ func (_c *MockStreamOps_QuitMainLoop_Call) RunAndReturn(run func(loopPtr unsafe. | |||||
| 493 | } | 500 | } | ||
| 494 | 501 | ||||
| 495 | // RunMainLoop provides a mock function for the type MockStreamOps | 502 | // RunMainLoop provides a mock function for the type MockStreamOps | ||
| 496 | - | func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) { | 503 | + | func (_mock *MockStreamOps) RunMainLoop(loopPtr unsafe.Pointer) error { |
| 497 | - | _mock.Called(loopPtr) | 504 | + | ret := _mock.Called(loopPtr) |
| 498 | - | return | 505 | + | |
| 506 | + | if len(ret) == 0 { | |||
| 507 | + | panic("no return value specified for RunMainLoop") | |||
| 508 | + | } | |||
| 509 | + | ||||
| 510 | + | var r0 error | |||
| 511 | + | if returnFunc, ok := ret.Get(0).(func(unsafe.Pointer) error); ok { | |||
| 512 | + | r0 = returnFunc(loopPtr) | |||
| 513 | + | } else { | |||
| 514 | + | r0 = ret.Error(0) | |||
| 515 | + | } | |||
| 516 | + | return r0 | |||
| 499 | } | 517 | } | ||
| 500 | 518 | ||||
| 501 | // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop' | 519 | // MockStreamOps_RunMainLoop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RunMainLoop' | ||
| @@ -522,13 +540,13 @@ func (_c *MockStreamOps_RunMainLoop_Call) Run(run func(loopPtr unsafe.Pointer)) | |||||
| 522 | return _c | 540 | return _c | ||
| 523 | } | 541 | } | ||
| 524 | 542 | ||||
| 525 | - | func (_c *MockStreamOps_RunMainLoop_Call) Return() *MockStreamOps_RunMainLoop_Call { | 543 | + | func (_c *MockStreamOps_RunMainLoop_Call) Return(err error) *MockStreamOps_RunMainLoop_Call { |
| 526 | - | _c.Call.Return() | 544 | + | _c.Call.Return(err) |
| 527 | return _c | 545 | return _c | ||
| 528 | } | 546 | } | ||
| 529 | 547 | ||||
| 530 | - | func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer)) *MockStreamOps_RunMainLoop_Call { | 548 | + | func (_c *MockStreamOps_RunMainLoop_Call) RunAndReturn(run func(loopPtr unsafe.Pointer) error) *MockStreamOps_RunMainLoop_Call { |
| 531 | - | _c.Run(run) | 549 | + | _c.Call.Return(run) |
| 532 | return _c | 550 | return _c | ||
| 533 | } | 551 | } | ||
| 534 | 552 | ||||
M internal/ports/out/stream_ops.go +12 -3
| @@ -2,6 +2,13 @@ package out | |||
| 2 | 2 | ||
| 3 | 3 | import "unsafe" | |
| 4 | 4 | ||
| 5 | + | // PlaybackFormat describes the audio format for a playback stream. | |
| 6 | + | type PlaybackFormat struct { | |
| 7 | + | SampleRate int | |
| 8 | + | Channels int | |
| 9 | + | FramesPerBuffer int | |
| 10 | + | } | |
| 11 | + | ||
| 5 | 12 | // StreamOps defines the outbound interface for PipeWire stream lifecycle | |
| 6 | 13 | // operations that the player needs. This abstracts the raw C API so the | |
| 7 | 14 | // player can be tested without a real PipeWire daemon. | |
| @@ -11,8 +18,9 @@ type StreamOps interface { | |||
| 11 | 18 | // stream needs more audio data. Returns the stream pointer or an error. | |
| 12 | 19 | CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error) | |
| 13 | 20 | ||
| 14 | - | // ConnectPlaybackStream connects the stream for playback output. | |
| 15 | - | ConnectPlaybackStream(streamPtr unsafe.Pointer) error | |
| 21 | + | // ConnectPlaybackStream connects the stream for playback output using the | |
| 22 | + | // given format. Returns an error if the format is invalid or the C call fails. | |
| 23 | + | ConnectPlaybackStream(streamPtr unsafe.Pointer, format PlaybackFormat) error | |
| 16 | 24 | ||
| 17 | 25 | // SetStreamActive activates or deactivates the stream. | |
| 18 | 26 | SetStreamActive(streamPtr unsafe.Pointer, active bool) error | |
| @@ -36,7 +44,8 @@ type StreamOps interface { | |||
| 36 | 44 | CreateMainLoop() (loopPtr unsafe.Pointer, err error) | |
| 37 | 45 | ||
| 38 | 46 | // RunMainLoop runs the main loop (blocks until quit). | |
| 39 | - | RunMainLoop(loopPtr unsafe.Pointer) | |
| 47 | + | // Returns an error if the loop fails to run. | |
| 48 | + | RunMainLoop(loopPtr unsafe.Pointer) error | |
| 40 | 49 | ||
| 41 | 50 | // QuitMainLoop signals the main loop to stop. | |
| 42 | 51 | QuitMainLoop(loopPtr unsafe.Pointer) | |
| @@ -2,6 +2,13 @@ package out | |||||
| 2 | 2 | ||||
| 3 | import "unsafe" | 3 | import "unsafe" | ||
| 4 | 4 | ||||
| 5 | + | // PlaybackFormat describes the audio format for a playback stream. | |||
| 6 | + | type PlaybackFormat struct { | |||
| 7 | + | SampleRate int | |||
| 8 | + | Channels int | |||
| 9 | + | FramesPerBuffer int | |||
| 10 | + | } | |||
| 11 | + | ||||
| 5 | // StreamOps defines the outbound interface for PipeWire stream lifecycle | 12 | // StreamOps defines the outbound interface for PipeWire stream lifecycle | ||
| 6 | // operations that the player needs. This abstracts the raw C API so the | 13 | // operations that the player needs. This abstracts the raw C API so the | ||
| 7 | // player can be tested without a real PipeWire daemon. | 14 | // player can be tested without a real PipeWire daemon. | ||
| @@ -11,8 +18,9 @@ type StreamOps interface { | |||||
| 11 | // stream needs more audio data. Returns the stream pointer or an error. | 18 | // stream needs more audio data. Returns the stream pointer or an error. | ||
| 12 | CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error) | 19 | CreatePlaybackStream(loopPtr unsafe.Pointer, name string, onProcess func()) (streamPtr unsafe.Pointer, err error) | ||
| 13 | 20 | ||||
| 14 | - | // ConnectPlaybackStream connects the stream for playback output. | 21 | + | // ConnectPlaybackStream connects the stream for playback output using the |
| 15 | - | ConnectPlaybackStream(streamPtr unsafe.Pointer) error | 22 | + | // given format. Returns an error if the format is invalid or the C call fails. |
| 23 | + | ConnectPlaybackStream(streamPtr unsafe.Pointer, format PlaybackFormat) error | |||
| 16 | 24 | ||||
| 17 | // SetStreamActive activates or deactivates the stream. | 25 | // SetStreamActive activates or deactivates the stream. | ||
| 18 | SetStreamActive(streamPtr unsafe.Pointer, active bool) error | 26 | SetStreamActive(streamPtr unsafe.Pointer, active bool) error | ||
| @@ -36,7 +44,8 @@ type StreamOps interface { | |||||
| 36 | CreateMainLoop() (loopPtr unsafe.Pointer, err error) | 44 | CreateMainLoop() (loopPtr unsafe.Pointer, err error) | ||
| 37 | 45 | ||||
| 38 | // RunMainLoop runs the main loop (blocks until quit). | 46 | // RunMainLoop runs the main loop (blocks until quit). | ||
| 39 | - | RunMainLoop(loopPtr unsafe.Pointer) | 47 | + | // Returns an error if the loop fails to run. |
| 48 | + | RunMainLoop(loopPtr unsafe.Pointer) error | |||
| 40 | 49 | ||||
| 41 | // QuitMainLoop signals the main loop to stop. | 50 | // QuitMainLoop signals the main loop to stop. | ||
| 42 | QuitMainLoop(loopPtr unsafe.Pointer) | 51 | QuitMainLoop(loopPtr unsafe.Pointer) | ||
M pipewire/player.go +1 -0
| @@ -207,6 +207,7 @@ func NewPlayer(config PlayerConfig, callbacks PlayerCallbacks) (Player, error) { | |||
| 207 | 207 | ||
| 208 | 208 | // Create internal player with mapped config | |
| 209 | 209 | coreConfig := core.PlayerConfig{ | |
| 210 | + | SampleRate: config.SampleRate, | |
| 210 | 211 | FramesPerBuffer: config.FramesPerBuffer, | |
| 211 | 212 | Channels: config.Channels, | |
| 212 | 213 | UnderrunPolicy: coreUnderrunPolicy, | |
| @@ -207,6 +207,7 @@ func NewPlayer(config PlayerConfig, callbacks PlayerCallbacks) (Player, error) { | |||||
| 207 | 207 | ||||
| 208 | // Create internal player with mapped config | 208 | // Create internal player with mapped config | ||
| 209 | coreConfig := core.PlayerConfig{ | 209 | coreConfig := core.PlayerConfig{ | ||
| 210 | + | SampleRate: config.SampleRate, | |||
| 210 | FramesPerBuffer: config.FramesPerBuffer, | 211 | FramesPerBuffer: config.FramesPerBuffer, | ||
| 211 | Channels: config.Channels, | 212 | Channels: config.Channels, | ||
| 212 | UnderrunPolicy: coreUnderrunPolicy, | 213 | UnderrunPolicy: coreUnderrunPolicy, | ||