1package capi
2
3import (
4 "encoding/binary"
5 "fmt"
6 "unsafe"
7
8 portout "github.com/bnema/purego-pipewire/internal/ports/out"
9)
10
11// SPA POD type identifiers (from spa/utils/type.h).
12const (
13 spaTypeNone uint32 = iota + 1 // 1; reserved so iota matches SPA type numbering
14 spaTypeBool // 2
15 spaTypeId // 3
16 spaTypeInt // 4
17 spaTypeLong // 5
18 spaTypeFloat // 6
19 spaTypeDouble // 7
20 spaTypeString // 8
21 spaTypeBytes // 9
22 spaTypeRectangle // 10
23 spaTypeFraction // 11
24 spaTypeBitmap // 12
25 spaTypeArray // 13
26 spaTypeStruct // 14
27 spaTypeObject // 15
28)
29
30// SPA object type identifiers (from spa/utils/type.h).
31const (
32 spaTypeObjectFormat uint32 = 0x40003 // SPA_TYPE_OBJECT_Format
33)
34
35// SPA param type identifiers (from spa/param/param.h).
36const (
37 spaParamEnumFormat uint32 = 3 // SPA_PARAM_EnumFormat
38)
39
40// SPA format property keys (from spa/param/format.h).
41const (
42 spaFormatMediaSubtype uint32 = 2 // SPA_FORMAT_mediaSubtype
43 spaFormatMediaType uint32 = 1 // SPA_FORMAT_mediaType
44
45 // Audio format keys (SPA_FORMAT_START_Audio = 0x10000).
46 spaFormatAudioFormat uint32 = 0x10001 // SPA_FORMAT_AUDIO_format
47 spaFormatAudioRate uint32 = 0x10003 // SPA_FORMAT_AUDIO_rate
48 spaFormatAudioChannels uint32 = 0x10004 // SPA_FORMAT_AUDIO_channels
49)
50
51// SPA media type and subtype identifiers (from spa/param/format.h).
52const (
53 spaMediaTypeAudio uint32 = 1 // SPA_MEDIA_TYPE_audio
54 spaMediaSubtypeRaw uint32 = 1 // SPA_MEDIA_SUBTYPE_raw
55)
56
57// SPA audio format identifiers (from spa/param/audio/raw.h).
58// Planar formats start at SPA_AUDIO_FORMAT_START_Planar = 0x200.
59const (
60 spaAudioFormatF32P uint32 = 0x206 // SPA_AUDIO_FORMAT_F32P
61)
62
63// buildRawAudioParams constructs one minimal SPA POD for SPA_PARAM_EnumFormat
64// describing a raw audio stream with planar float32 sample format. The returned
65// connectParams holds the storage buffer and the params slice suitable for
66// passing to pw_stream_connect.
67//
68// This is a focused, handwritten builder for the first playable target format.
69// It is intentionally minimal: no choice PODs, no format negotiation, no
70// general-purpose SPA param generation.
71func buildRawAudioParams(format portout.PlaybackFormat) (*connectParams, error) {
72 if format.SampleRate <= 0 {
73 return nil, fmt.Errorf("%w: sample rate must be positive, got %d",
74 ErrInvalidPlaybackFormat, format.SampleRate)
75 }
76 if format.Channels <= 0 {
77 return nil, fmt.Errorf("%w: channels must be positive, got %d",
78 ErrInvalidPlaybackFormat, format.Channels)
79 }
80 if format.FramesPerBuffer <= 0 {
81 return nil, fmt.Errorf("%w: frames per buffer must be positive, got %d",
82 ErrInvalidPlaybackFormat, format.FramesPerBuffer)
83 }
84
85 // Layout of an SPA POD Object for EnumFormat with 5 properties:
86 //
87 // spa_pod header (8 bytes): {size, type}
88 // object_body body (8 bytes): {objType, id}
89 // prop[0] mediaType (24 bytes): {key, flags, {4,Id}, value, pad}
90 // prop[1] mediaSubtype (24 bytes): {key, flags, {4,Id}, value, pad}
91 // prop[2] audioFormat (24 bytes): {key, flags, {4,Id}, value, pad}
92 // prop[3] audioRate (24 bytes): {key, flags, {4,Int}, value, pad}
93 // prop[4] audioChannels (24 bytes): {key, flags, {4,Int}, value, pad}
94 //
95 // Total: 8 + 8 + 5×24 = 136 bytes.
96 // Pod body size (excl. header): 8 + 120 = 128 bytes.
97
98 const propSize = 24 // key(4) + flags(4) + pod{4,4}(8) + value(4) + pad(4)
99 const objBodySize = 8 + 5*propSize // 128
100 const totalSize = 8 + objBodySize // 136
101
102 buf := make([]byte, totalSize)
103
104 // POD header
105 binary.LittleEndian.PutUint32(buf[0:4], objBodySize) // size
106 binary.LittleEndian.PutUint32(buf[4:8], spaTypeObject) // type
107
108 // Object body
109 binary.LittleEndian.PutUint32(buf[8:12], spaTypeObjectFormat) // objType
110 binary.LittleEndian.PutUint32(buf[12:16], spaParamEnumFormat) // id
111
112 // Properties
113 off := 16
114 off += writeIdProp(buf[off:], spaFormatMediaType, spaMediaTypeAudio)
115 off += writeIdProp(buf[off:], spaFormatMediaSubtype, spaMediaSubtypeRaw)
116 off += writeIdProp(buf[off:], spaFormatAudioFormat, spaAudioFormatF32P)
117 off += writeIntProp(buf[off:], spaFormatAudioRate, int32(format.SampleRate))
118 _ = writeIntProp(buf[off:], spaFormatAudioChannels, int32(format.Channels))
119
120 cp := &connectParams{
121 storage: buf,
122 params: []unsafe.Pointer{unsafe.Pointer(&buf[0])},
123 }
124
125 return cp, nil
126}
127
128// connectParams holds the backing storage and parameter slice for a
129// pw_stream_connect call. The storage buffer contains the SPA POD data,
130// and params points into that storage.
131type connectParams struct {
132 storage []byte
133 params []unsafe.Pointer
134}
135
136// Pointer returns a pointer to the params array, suitable for passing as
137// the params argument to pw_stream_connect (which expects spa_pod **).
138func (cp *connectParams) Pointer() unsafe.Pointer {
139 if len(cp.params) == 0 {
140 return nil
141 }
142 return unsafe.Pointer(&cp.params[0])
143}
144
145// Count returns the number of params in the params slice.
146func (cp *connectParams) Count() uint32 {
147 return uint32(len(cp.params))
148}
149
150// writeIdProp writes an SPA POD property with an Id value into buf.
151// Layout: key(4) | flags(4) | pod{4,Id}(8) | value(4) | pad(4) = 24 bytes.
152func writeIdProp(buf []byte, key uint32, value uint32) int {
153 binary.LittleEndian.PutUint32(buf[0:4], key)
154 binary.LittleEndian.PutUint32(buf[4:8], 0) // flags
155 binary.LittleEndian.PutUint32(buf[8:12], 4) // pod body size
156 binary.LittleEndian.PutUint32(buf[12:16], spaTypeId)
157 binary.LittleEndian.PutUint32(buf[16:20], value)
158 binary.LittleEndian.PutUint32(buf[20:24], 0) // padding
159 return 24
160}
161
162// writeIntProp writes an SPA POD property with an Int value into buf.
163// Layout: key(4) | flags(4) | pod{4,Int}(8) | value(4) | pad(4) = 24 bytes.
164func writeIntProp(buf []byte, key uint32, value int32) int {
165 binary.LittleEndian.PutUint32(buf[0:4], key)
166 binary.LittleEndian.PutUint32(buf[4:8], 0) // flags
167 binary.LittleEndian.PutUint32(buf[8:12], 4) // pod body size
168 binary.LittleEndian.PutUint32(buf[12:16], spaTypeInt)
169 binary.LittleEndian.PutUint32(buf[16:20], uint32(value))
170 binary.LittleEndian.PutUint32(buf[20:24], 0) // padding
171 return 24
172}