Changeset View
Changeset View
Standalone View
Standalone View
screencaststream.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright © 2018-2020 Red Hat, Inc | ||||
3 | * | ||||
4 | * This program is free software; you can redistribute it and/or | ||||
5 | * modify it under the terms of the GNU Lesser General Public | ||||
6 | * License as published by the Free Software Foundation; either | ||||
7 | * version 2 of the License, or (at your option) any later version. | ||||
8 | * | ||||
9 | * This library is distributed in the hope that it will be useful, | ||||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||
12 | * Lesser General Public License for more details. | ||||
13 | * | ||||
14 | * You should have received a copy of the GNU Lesser General Public | ||||
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||||
16 | * | ||||
17 | * Authors: | ||||
18 | * Jan Grulich <jgrulich@redhat.com> | ||||
19 | */ | ||||
20 | | ||||
21 | #include "screencaststream.h" | ||||
22 | | ||||
23 | #include <limits.h> | ||||
24 | #include <math.h> | ||||
25 | #include <sys/mman.h> | ||||
26 | #include <stdio.h> | ||||
27 | | ||||
28 | #include <QLoggingCategory> | ||||
29 | #include "utils.h" | ||||
30 | | ||||
31 | #include <KNotification> | ||||
32 | #include <KLocalizedString> | ||||
33 | | ||||
34 | class PwFraction { | ||||
35 | public: | ||||
36 | int num; | ||||
37 | int denom; | ||||
38 | }; | ||||
39 | | ||||
40 | // Stolen from mutter | ||||
41 | | ||||
42 | #define MAX_TERMS 30 | ||||
43 | #define MIN_DIVISOR 1.0e-10 | ||||
44 | #define MAX_ERROR 1.0e-20 | ||||
45 | | ||||
46 | #define PROP_RANGE(min, max) 2, (min), (max) | ||||
47 | | ||||
48 | static int greatestCommonDivisor(int a, int b) | ||||
49 | { | ||||
50 | while (b != 0) { | ||||
51 | int temp = a; | ||||
52 | | ||||
53 | a = b; | ||||
54 | b = temp % b; | ||||
55 | } | ||||
56 | | ||||
57 | return qAbs(a); | ||||
58 | } | ||||
59 | | ||||
60 | static PwFraction pipewireFractionFromDouble(double src) | ||||
61 | { | ||||
62 | double V, F; /* double being converted */ | ||||
63 | int N, D; /* will contain the result */ | ||||
64 | int A; /* current term in continued fraction */ | ||||
65 | int64_t N1, D1; /* numerator, denominator of last approx */ | ||||
66 | int64_t N2, D2; /* numerator, denominator of previous approx */ | ||||
67 | int i; | ||||
68 | int gcd; | ||||
69 | bool negative = false; | ||||
70 | | ||||
71 | /* initialize fraction being converted */ | ||||
72 | F = src; | ||||
73 | if (F < 0.0) { | ||||
74 | F = -F; | ||||
75 | negative = true; | ||||
76 | } | ||||
77 | | ||||
78 | V = F; | ||||
79 | /* initialize fractions with 1/0, 0/1 */ | ||||
80 | N1 = 1; | ||||
81 | D1 = 0; | ||||
82 | N2 = 0; | ||||
83 | D2 = 1; | ||||
84 | N = 1; | ||||
85 | D = 1; | ||||
86 | | ||||
87 | for (i = 0; i < MAX_TERMS; ++i) { | ||||
88 | /* get next term */ | ||||
89 | A = (int) F; /* no floor() needed, F is always >= 0 */ | ||||
90 | /* get new divisor */ | ||||
91 | F = F - A; | ||||
92 | | ||||
93 | /* calculate new fraction in temp */ | ||||
94 | N2 = N1 * A + N2; | ||||
95 | D2 = D1 * A + D2; | ||||
96 | | ||||
97 | /* guard against overflow */ | ||||
98 | if (N2 > INT_MAX || D2 > INT_MAX) | ||||
99 | break; | ||||
100 | | ||||
101 | N = N2; | ||||
102 | D = D2; | ||||
103 | | ||||
104 | /* save last two fractions */ | ||||
105 | N2 = N1; | ||||
106 | D2 = D1; | ||||
107 | N1 = N; | ||||
108 | D1 = D; | ||||
109 | | ||||
110 | /* quit if dividing by zero or close enough to target */ | ||||
111 | if (F < MIN_DIVISOR || fabs (V - ((double) N) / D) < MAX_ERROR) | ||||
112 | break; | ||||
113 | | ||||
114 | /* Take reciprocal */ | ||||
115 | F = 1 / F; | ||||
116 | } | ||||
117 | | ||||
118 | /* fix for overflow */ | ||||
119 | if (D == 0) { | ||||
120 | N = INT_MAX; | ||||
121 | D = 1; | ||||
122 | } | ||||
123 | | ||||
124 | /* fix for negative */ | ||||
125 | if (negative) | ||||
126 | N = -N; | ||||
127 | | ||||
128 | /* simplify */ | ||||
129 | gcd = greatestCommonDivisor(N, D); | ||||
130 | if (gcd) { | ||||
131 | N /= gcd; | ||||
132 | D /= gcd; | ||||
133 | } | ||||
134 | | ||||
135 | PwFraction fraction; | ||||
136 | fraction.num = N; | ||||
137 | fraction.denom = D; | ||||
138 | | ||||
139 | return fraction; | ||||
140 | } | ||||
141 | | ||||
142 | void ScreenCastStream::stop() | ||||
143 | { | ||||
144 | m_stopped = true; | ||||
145 | delete this; | ||||
146 | } | ||||
147 | | ||||
148 | void ScreenCastStream::onCoreError(void *data, uint32_t id, int seq, int res, const char *message) | ||||
149 | { | ||||
150 | Q_UNUSED(seq) | ||||
151 | ScreenCastStream *pw = static_cast<ScreenCastStream*>(data); | ||||
152 | | ||||
153 | qCWarning(KWIN_CORE) << "PipeWire remote error: " << message; | ||||
154 | | ||||
155 | if (id == PW_ID_CORE) { | ||||
156 | if (res == -EPIPE) { | ||||
157 | Q_EMIT pw->stopStreaming(pw->nodeId()); | ||||
158 | } | ||||
159 | } | ||||
160 | } | ||||
161 | | ||||
162 | void ScreenCastStream::onStreamStateChanged(void *data, pw_stream_state old, pw_stream_state state, const char *error_message) | ||||
163 | { | ||||
164 | Q_UNUSED(old) | ||||
165 | | ||||
166 | ScreenCastStream *pw = static_cast<ScreenCastStream*>(data); | ||||
167 | | ||||
168 | switch (state) { | ||||
169 | case PW_STREAM_STATE_ERROR: | ||||
170 | qCWarning(KWIN_CORE) << "Stream error: " << error_message; | ||||
171 | break; | ||||
172 | case PW_STREAM_STATE_PAUSED: | ||||
173 | qCDebug(KWIN_CORE) << "Stream state: " << pw_stream_state_as_string(state); | ||||
174 | if (pw->nodeId() == 0 && pw->pwStream) { | ||||
175 | pw->pwNodeId = pw_stream_get_node_id(pw->pwStream); | ||||
176 | Q_EMIT pw->streamReady(pw->nodeId()); | ||||
177 | } | ||||
178 | if (pw->m_stopped) { | ||||
179 | Q_EMIT pw->stopStreaming(pw->nodeId()); | ||||
180 | } | ||||
181 | break; | ||||
182 | case PW_STREAM_STATE_STREAMING: | ||||
183 | qCDebug(KWIN_CORE) << "Stream state: " << pw_stream_state_as_string(state); | ||||
184 | Q_EMIT pw->startStreaming(); | ||||
185 | break; | ||||
186 | case PW_STREAM_STATE_UNCONNECTED: | ||||
187 | case PW_STREAM_STATE_CONNECTING: | ||||
188 | qCDebug(KWIN_CORE) << "Stream state: " << pw_stream_state_as_string(state); | ||||
189 | break; | ||||
190 | } | ||||
191 | } | ||||
192 | | ||||
193 | void ScreenCastStream::onStreamParamChanged(void *data, uint32_t id, const struct spa_pod *format) | ||||
194 | { | ||||
195 | qCDebug(KWIN_CORE) << "Stream format changed"; | ||||
196 | | ||||
197 | ScreenCastStream *pw = static_cast<ScreenCastStream*>(data); | ||||
198 | | ||||
199 | uint8_t paramsBuffer[1024]; | ||||
200 | int32_t width, height, stride, size; | ||||
201 | struct spa_pod_builder pod_builder; | ||||
202 | const struct spa_pod *params[1]; | ||||
203 | | ||||
204 | if (!format || id != SPA_PARAM_Format) { | ||||
205 | return; | ||||
206 | } | ||||
207 | | ||||
208 | spa_format_video_raw_parse (format, &pw->videoFormat); | ||||
209 | | ||||
210 | width = pw->videoFormat.size.width; | ||||
211 | height =pw->videoFormat.size.height; | ||||
212 | stride = SPA_ROUND_UP_N (width * pw->m_bytesPerPixel, 4); | ||||
213 | size = height * stride; | ||||
214 | | ||||
215 | pod_builder = SPA_POD_BUILDER_INIT (paramsBuffer, sizeof (paramsBuffer)); | ||||
216 | | ||||
217 | params[0] = (spa_pod*) spa_pod_builder_add_object(&pod_builder, | ||||
218 | SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, | ||||
219 | SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(16, 2, 16), | ||||
220 | SPA_PARAM_BUFFERS_blocks, SPA_POD_Int (1), | ||||
221 | SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), | ||||
222 | SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, stride, INT32_MAX), | ||||
223 | SPA_PARAM_BUFFERS_align, SPA_POD_Int(16)); | ||||
224 | pw_stream_update_params(pw->pwStream, params, 1); | ||||
225 | } | ||||
226 | | ||||
227 | ScreenCastStream::ScreenCastStream(const QSize &resolution, QObject *parent) | ||||
228 | : QObject(parent) | ||||
229 | , m_resolution(resolution) | ||||
230 | { | ||||
231 | pwCoreEvents.version = PW_VERSION_CORE_EVENTS; | ||||
232 | pwCoreEvents.error = &onCoreError; | ||||
233 | | ||||
234 | pwStreamEvents.version = PW_VERSION_STREAM_EVENTS; | ||||
235 | pwStreamEvents.state_changed = &onStreamStateChanged; | ||||
236 | pwStreamEvents.param_changed = &onStreamParamChanged; | ||||
237 | } | ||||
238 | | ||||
239 | ScreenCastStream::~ScreenCastStream() | ||||
240 | { | ||||
241 | if (pwMainLoop) { | ||||
242 | pw_thread_loop_stop(pwMainLoop); | ||||
243 | } | ||||
244 | | ||||
245 | if (pwStream) { | ||||
246 | pw_stream_destroy(pwStream); | ||||
247 | } | ||||
248 | | ||||
249 | | ||||
250 | if (pwCore) { | ||||
251 | pw_core_disconnect(pwCore); | ||||
252 | } | ||||
253 | | ||||
254 | if (pwContext) { | ||||
255 | pw_context_destroy(pwContext); | ||||
256 | } | ||||
257 | | ||||
258 | if (pwMainLoop) { | ||||
259 | pw_thread_loop_destroy(pwMainLoop); | ||||
260 | } | ||||
261 | } | ||||
262 | | ||||
263 | bool ScreenCastStream::init(int bitsPerPixel) | ||||
264 | { | ||||
265 | pw_init(nullptr, nullptr); | ||||
266 | m_bytesPerPixel = bitsPerPixel; | ||||
267 | | ||||
268 | pwMainLoop = pw_thread_loop_new("pipewire-main-loop", nullptr); | ||||
269 | pwContext = pw_context_new(pw_thread_loop_get_loop(pwMainLoop), nullptr, 0); | ||||
270 | if (!pwContext) { | ||||
271 | qCWarning(KWIN_CORE) << "Failed to create PipeWire context"; | ||||
272 | m_error = i18n("Failed to create PipeWire context"); | ||||
273 | return false; | ||||
274 | } | ||||
275 | | ||||
276 | pwCore = pw_context_connect(pwContext, nullptr, 0); | ||||
277 | if (!pwCore) { | ||||
278 | qCWarning(KWIN_CORE) << "Failed to connect PipeWire context"; | ||||
279 | m_error = i18n("Failed to connect PipeWire context"); | ||||
280 | return false; | ||||
281 | } | ||||
282 | | ||||
283 | pw_core_add_listener(pwCore, &coreListener, &pwCoreEvents, this); | ||||
284 | | ||||
285 | pwStream = createStream(); | ||||
286 | if (!pwStream) { | ||||
287 | qCWarning(KWIN_CORE) << "Failed to create PipeWire stream"; | ||||
288 | m_error = i18n("Failed to create PipeWire stream"); | ||||
289 | return false; | ||||
290 | } | ||||
291 | | ||||
292 | | ||||
293 | if (pw_thread_loop_start(pwMainLoop) < 0) { | ||||
294 | qCWarning(KWIN_CORE) << "Failed to start main PipeWire loop"; | ||||
295 | m_error = i18n("Failed to start main PipeWire loop"); | ||||
296 | return false; | ||||
297 | } | ||||
298 | return true; | ||||
299 | } | ||||
300 | | ||||
301 | uint ScreenCastStream::framerate() | ||||
302 | { | ||||
303 | if (pwStream) { | ||||
304 | return videoFormat.max_framerate.num / videoFormat.max_framerate.denom; | ||||
305 | } | ||||
306 | | ||||
307 | return 0; | ||||
308 | } | ||||
309 | | ||||
310 | uint ScreenCastStream::nodeId() | ||||
311 | { | ||||
312 | return pwNodeId; | ||||
313 | } | ||||
314 | | ||||
315 | pw_stream *ScreenCastStream::createStream() | ||||
316 | { | ||||
317 | uint8_t buffer[1024]; | ||||
318 | spa_pod_builder podBuilder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); | ||||
319 | | ||||
320 | const float frameRate = 25; | ||||
321 | | ||||
322 | spa_fraction maxFramerate; | ||||
323 | spa_fraction minFramerate; | ||||
324 | const spa_pod *params[1]; | ||||
325 | | ||||
326 | auto stream = pw_stream_new(pwCore, "kwin-screen-cast", nullptr); | ||||
327 | | ||||
328 | PwFraction fraction = pipewireFractionFromDouble(frameRate); | ||||
329 | | ||||
330 | minFramerate = SPA_FRACTION(1, 1); | ||||
331 | maxFramerate = SPA_FRACTION(uint32_t(fraction.num), uint32_t(fraction.denom)); | ||||
332 | | ||||
333 | spa_rectangle minResolution = SPA_RECTANGLE(1, 1); | ||||
334 | spa_rectangle maxResolution = SPA_RECTANGLE(uint32_t(m_resolution.width()), uint32_t(m_resolution.height())); | ||||
335 | | ||||
336 | spa_fraction paramFraction = SPA_FRACTION(0, 1); | ||||
337 | | ||||
338 | params[0] = (spa_pod*)spa_pod_builder_add_object(&podBuilder, | ||||
339 | SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, | ||||
340 | SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), | ||||
341 | SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), | ||||
342 | SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBx), | ||||
343 | SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&maxResolution, &minResolution, &maxResolution), | ||||
344 | SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(¶mFraction), | ||||
345 | SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_CHOICE_RANGE_Fraction(&maxFramerate, &minFramerate, &maxFramerate)); | ||||
346 | | ||||
347 | pw_stream_add_listener(stream, &streamListener, &pwStreamEvents, this); | ||||
348 | | ||||
349 | auto flags = static_cast<pw_stream_flags>(PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS); | ||||
350 | | ||||
351 | if (pw_stream_connect(stream, PW_DIRECTION_OUTPUT, SPA_ID_INVALID, flags, params, 1) != 0) { | ||||
352 | qCWarning(KWIN_CORE) << "Could not connect to stream"; | ||||
353 | return nullptr; | ||||
354 | } | ||||
355 | | ||||
356 | return stream; | ||||
357 | } | ||||
358 | | ||||
359 | void ScreenCastStream::removeStream() | ||||
360 | { | ||||
361 | // FIXME destroying streams seems to be crashing, Mutter also doesn't remove them, maybe Pipewire does this automatically | ||||
362 | // pw_stream_destroy(pwStream); | ||||
363 | // pwStream = nullptr; | ||||
364 | pw_stream_disconnect(pwStream); | ||||
365 | } |