1 /* PipeWire
2 *
3 * Copyright © 2020 Wim Taymans
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <stdint.h>
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <spa/node/io.h>
31 #include <spa/param/audio/raw.h>
32 #include <spa/pod/builder.h>
33 #include <spa/utils/hook.h>
34 #include <pipewire/context.h>
35 #include <pipewire/core.h>
36 #include <pipewire/log.h>
37 #include <pipewire/properties.h>
38 #include <pipewire/stream.h>
39
40 #include "format.h"
41 #include "log.h"
42 #include "sample.h"
43 #include "sample-play.h"
44
sample_play_stream_state_changed(void * data,enum pw_stream_state old,enum pw_stream_state state,const char * error)45 static void sample_play_stream_state_changed(void *data, enum pw_stream_state old,
46 enum pw_stream_state state, const char *error)
47 {
48 struct sample_play *p = data;
49
50 switch (state) {
51 case PW_STREAM_STATE_UNCONNECTED:
52 case PW_STREAM_STATE_ERROR:
53 sample_play_emit_done(p, -EIO);
54 break;
55 case PW_STREAM_STATE_PAUSED:
56 p->index = pw_stream_get_node_id(p->stream);
57 sample_play_emit_ready(p, p->index);
58 break;
59 default:
60 break;
61 }
62 }
63
sample_play_stream_io_changed(void * data,uint32_t id,void * area,uint32_t size)64 static void sample_play_stream_io_changed(void *data, uint32_t id, void *area, uint32_t size)
65 {
66 struct sample_play *p = data;
67
68 switch (id) {
69 case SPA_IO_RateMatch:
70 p->rate_match = area;
71 break;
72 }
73 }
74
sample_play_stream_destroy(void * data)75 static void sample_play_stream_destroy(void *data)
76 {
77 struct sample_play *p = data;
78
79 pw_log_info("destroy %s", p->sample->name);
80
81 spa_hook_remove(&p->listener);
82 p->stream = NULL;
83
84 sample_unref(p->sample);
85 p->sample = NULL;
86 }
87
sample_play_stream_process(void * data)88 static void sample_play_stream_process(void *data)
89 {
90 struct sample_play *p = data;
91 struct sample *s = p->sample;
92 struct pw_buffer *b;
93 struct spa_buffer *buf;
94 uint32_t size;
95 uint8_t *d;
96
97 if (p->offset >= s->length) {
98 pw_stream_flush(p->stream, true);
99 return;
100 }
101
102 size = s->length - p->offset;
103
104 if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) {
105 pw_log_warn("out of buffers: %m");
106 return;
107 }
108
109 buf = b->buffer;
110 if ((d = buf->datas[0].data) == NULL)
111 return;
112
113 size = SPA_MIN(size, buf->datas[0].maxsize);
114 if (p->rate_match)
115 size = SPA_MIN(size, p->rate_match->size * p->stride);
116
117 memcpy(d, s->buffer + p->offset, size);
118
119 p->offset += size;
120
121 buf->datas[0].chunk->offset = 0;
122 buf->datas[0].chunk->stride = p->stride;
123 buf->datas[0].chunk->size = size;
124
125 pw_stream_queue_buffer(p->stream, b);
126 }
127
sample_play_stream_drained(void * data)128 static void sample_play_stream_drained(void *data)
129 {
130 struct sample_play *p = data;
131
132 sample_play_emit_done(p, 0);
133 }
134
135 static const struct pw_stream_events sample_play_stream_events = {
136 PW_VERSION_STREAM_EVENTS,
137 .state_changed = sample_play_stream_state_changed,
138 .io_changed = sample_play_stream_io_changed,
139 .destroy = sample_play_stream_destroy,
140 .process = sample_play_stream_process,
141 .drained = sample_play_stream_drained,
142 };
143
sample_play_new(struct pw_core * core,struct sample * sample,struct pw_properties * props,size_t user_data_size)144 struct sample_play *sample_play_new(struct pw_core *core,
145 struct sample *sample, struct pw_properties *props,
146 size_t user_data_size)
147 {
148 struct sample_play *p;
149 uint8_t buffer[1024];
150 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
151 const struct spa_pod *params[1];
152 uint32_t n_params = 0;
153 int res;
154
155 p = calloc(1, sizeof(*p) + user_data_size);
156 if (p == NULL) {
157 res = -errno;
158 goto error_free;
159 }
160
161 p->context = pw_core_get_context(core);
162 p->main_loop = pw_context_get_main_loop(p->context);
163 spa_hook_list_init(&p->hooks);
164 p->user_data = SPA_PTROFF(p, sizeof(struct sample_play), void);
165
166 pw_properties_update(props, &sample->props->dict);
167
168 p->stream = pw_stream_new(core, sample->name, props);
169 props = NULL;
170 if (p->stream == NULL) {
171 res = -errno;
172 goto error_free;
173 }
174
175 /* safe to increment the reference count here because it will be decreased
176 by the stream's 'destroy' event handler, which will be called
177 (even if `pw_stream_connect()` fails) */
178 p->sample = sample_ref(sample);
179 p->stride = sample_spec_frame_size(&sample->ss);
180
181 pw_stream_add_listener(p->stream,
182 &p->listener,
183 &sample_play_stream_events, p);
184
185 params[n_params++] = format_build_param(&b, SPA_PARAM_EnumFormat,
186 &sample->ss, &sample->map);
187
188 res = pw_stream_connect(p->stream,
189 PW_DIRECTION_OUTPUT,
190 PW_ID_ANY,
191 PW_STREAM_FLAG_AUTOCONNECT |
192 PW_STREAM_FLAG_MAP_BUFFERS |
193 PW_STREAM_FLAG_RT_PROCESS,
194 params, n_params);
195 if (res < 0)
196 goto error_cleanup;
197
198 return p;
199
200 error_cleanup:
201 pw_stream_destroy(p->stream);
202 error_free:
203 pw_properties_free(props);
204 free(p);
205 errno = -res;
206 return NULL;
207 }
208
sample_play_destroy(struct sample_play * p)209 void sample_play_destroy(struct sample_play *p)
210 {
211 if (p->stream)
212 pw_stream_destroy(p->stream);
213
214 free(p);
215 }
216
sample_play_add_listener(struct sample_play * p,struct spa_hook * listener,const struct sample_play_events * events,void * data)217 void sample_play_add_listener(struct sample_play *p, struct spa_hook *listener,
218 const struct sample_play_events *events, void *data)
219 {
220 spa_hook_list_append(&p->hooks, listener, events, data);
221 }
222