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