v0.2.0 4c97002

stream_params.go RawHistoryFolder

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}

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