OpenShot Library | libopenshot 1.1.1
ScreenCaptureReader.cpp
Go to the documentation of this file.
1
9// Copyright (c) 2008-2026 OpenShot Studios, LLC
10//
11// SPDX-License-Identifier: LGPL-3.0-or-later
12
13#include "ScreenCaptureReader.h"
14#include "FFmpegColorRange.h"
15#include "CaptureAudioBuffer.h"
16
17#include <algorithm>
18#include <cctype>
19#include <chrono>
20#include <condition_variable>
21#include <cstdlib>
22#include <deque>
23#include <limits>
24#include <mutex>
25#include <sstream>
26#include <thread>
27#include <vector>
28
29#if defined(__linux__)
30 #include <dlfcn.h>
31#endif
32
33extern "C" {
34 #include <libavdevice/avdevice.h>
35 #include <libavutil/imgutils.h>
36 #include <libavutil/time.h>
37}
38
39#if defined(_WIN32)
40 // Instantiate the Core Audio COM GUIDs in this translation unit. Older
41 // MinGW-w64 uuid import libraries do not provide all WASAPI identifiers.
42 #include <initguid.h>
43 #include <audioclient.h>
44 #include <mmdeviceapi.h>
45#endif
46
47#include "Exceptions.h"
48#include "Frame.h"
49#include "QtUtilities.h"
50
51using namespace openshot;
52
53#if defined(HAVE_WAYLAND_CAPTURE_PLUGIN) && defined(__linux__)
54namespace
55{
56 using WaylandBackendFactory = std::unique_ptr<ScreenCaptureReader::CaptureBackendReader> (*) (
58
59 void* load_wayland_capture_backend(std::string& error_message)
60 {
61 Dl_info library_info {};
62 if (dladdr(reinterpret_cast<void*>(load_wayland_capture_backend), &library_info)
63 && library_info.dli_fname) {
64 std::string library_path(library_info.dli_fname);
65 const auto separator = library_path.find_last_of('/');
66 if (separator != std::string::npos) {
67 const std::string module_path = library_path.substr(0, separator + 1)
68 + "libopenshot-wayland-capture.so";
69 if (void* module = dlopen(module_path.c_str(), RTLD_NOW | RTLD_LOCAL)) {
70 return module;
71 }
72 const char* error = dlerror();
73 error_message = error ? error : "unable to load PipeWire support.";
74 return nullptr;
75 }
76 }
77 void* module = dlopen("libopenshot-wayland-capture.so", RTLD_NOW | RTLD_LOCAL);
78 if (!module) {
79 const char* error = dlerror();
80 error_message = error ? error : "unable to load PipeWire support.";
81 }
82 return module;
83 }
84}
85#endif
86
87#if defined(__linux__)
89{
90public:
91 explicit SystemAudioCapture(const ScreenCaptureSettings& new_settings)
92 : settings(new_settings) {}
93
94 ~SystemAudioCapture() { Close(); }
95
96 void Open()
97 {
98 if (format_context) return;
99 avdevice_register_all();
100 AVInputFormat* input_format = const_cast<AVInputFormat*>(av_find_input_format("pulse"));
101 if (!input_format) {
102 throw InvalidOptions("FFmpeg PulseAudio input is unavailable for system audio capture.");
103 }
104
105 format_context = avformat_alloc_context();
106 if (!format_context) {
107 throw OutOfMemory("Unable to allocate system audio capture context.");
108 }
109 format_context->interrupt_callback.callback = InterruptCallback;
110 format_context->interrupt_callback.opaque = &close_requested;
111
112 AVDictionary* options = nullptr;
113 av_dict_set(&options, "wallclock", "1", 0);
114 // Pulse defaults to server-selected buffering, which can deliver large
115 // batches too late for a live video frame. Request 20 ms of default
116 // 16-bit PCM; timestamps still determine placement, not this buffer size.
117 av_dict_set_int(&options, "fragment_size",
118 static_cast<int64_t>(settings.audio_sample_rate) * settings.audio_channels * 2 / 50, 0);
119 av_dict_set(&options, "sample_rate", std::to_string(settings.audio_sample_rate).c_str(), 0);
120 av_dict_set(&options, "channels", std::to_string(settings.audio_channels).c_str(), 0);
121 const std::string device = settings.audio_device.empty() ? "@DEFAULT_MONITOR@" : settings.audio_device;
122 const int open_result = avformat_open_input(&format_context, device.c_str(), input_format, &options);
123 av_dict_free(&options);
124 if (open_result < 0) {
125 throw InvalidFile("Unable to open system audio capture input: " + std::string(av_err2str(open_result)), device);
126 }
127 if (avformat_find_stream_info(format_context, nullptr) < 0) {
128 throw InvalidFile("Unable to read system audio capture stream information.", device);
129 }
130 for (unsigned int index = 0; index < format_context->nb_streams; ++index) {
131 if (format_context->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
132 audio_stream = static_cast<int>(index);
133 break;
134 }
135 }
136 if (audio_stream < 0) {
137 throw InvalidFile("No audio stream found in system audio capture input.", device);
138 }
139 AVStream* stream = format_context->streams[audio_stream];
140 const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
141 codec_context = codec ? ffmpeg_get_codec_context(stream, codec) : nullptr;
142 if (codec_context) codec_context->pkt_timebase = stream->time_base;
143 if (!codec_context || avcodec_open2(codec_context, codec, nullptr) < 0) {
144 throw InvalidCodec("Unable to open system audio capture decoder.", device);
145 }
146 packet = av_packet_alloc();
147 decoded_frame = av_frame_alloc();
148 if (!packet || !decoded_frame) {
149 throw OutOfMemory("Unable to allocate system audio capture buffers.");
150 }
151 settings.audio_sample_rate = codec_context->sample_rate > 0
152 ? codec_context->sample_rate : settings.audio_sample_rate;
153 Reset();
154 close_requested = false;
155 worker = std::thread(&SystemAudioCapture::CaptureLoop, this);
156 }
157
158 void Close()
159 {
160 close_requested = true;
161 ready.notify_all();
162 if (worker.joinable()) {
163 worker.join();
164 }
165 if (decoded_frame) av_frame_free(&decoded_frame);
166 if (packet) av_packet_free(&packet);
167 if (codec_context) avcodec_free_context(&codec_context);
168 if (format_context) avformat_close_input(&format_context);
169 }
170
171 void AddFrameAudio(const std::shared_ptr<Frame>& frame, int64_t number, const Fraction& fps)
172 {
173 int sample_count = 0;
174 for (int64_t frame_number = last_output_frame + 1; frame_number <= number; ++frame_number) {
175 sample_count += Frame::GetSamplesPerFrame(
176 frame_number, fps, settings.audio_sample_rate, settings.audio_channels);
177 }
178 last_output_frame = std::max(last_output_frame, number);
179 if (!frame || sample_count <= 0) return;
180
181 std::unique_lock<std::mutex> lock(queue_mutex);
182 // Wait for capture delivery, but keep its original sample positions. A
183 // timed-out interval becomes silence; late packets cannot move it forward.
184 const auto wait_time = timeline_started
185 ? std::chrono::milliseconds(100)
186 : std::chrono::milliseconds(3000);
187 ready.wait_for(lock, wait_time, [this, sample_count]() {
188 return close_requested || audio_buffer.Covers(sample_count);
189 });
190 timeline_started = true;
191 auto samples = audio_buffer.Read(sample_count);
192 frame->SampleRate(settings.audio_sample_rate);
193 frame->ChannelsLayout(settings.audio_channels == 1 ? LAYOUT_MONO : LAYOUT_STEREO);
194 for (int channel = 0; channel < settings.audio_channels; ++channel) {
195 frame->AddAudio(true, channel, 0, samples[channel].data(), sample_count, 1.0f);
196 }
197 }
198
199 void Reset()
200 {
201 std::lock_guard<std::mutex> lock(queue_mutex);
202 // PulseAudio wallclock PTS uses av_gettime(), corrected for input latency.
203 // Reset the epoch too, so packets buffered before recording are discarded.
204 epoch_us = av_gettime();
205 audio_buffer.Reset(settings.audio_channels, static_cast<int64_t>(settings.audio_sample_rate) * 10);
206 last_output_frame = 0;
207 timeline_started = false;
208 }
209
210 int SampleRate() const { return settings.audio_sample_rate; }
211 int Channels() const { return settings.audio_channels; }
212
213private:
214 static int InterruptCallback(void* opaque)
215 {
216 const auto* requested = static_cast<std::atomic<bool>*>(opaque);
217 return requested && requested->load() ? 1 : 0;
218 }
219
220 float SampleAt(const AVFrame* frame, int channel, int sample) const
221 {
222 const AVSampleFormat format = static_cast<AVSampleFormat>(frame->format);
223 const bool planar = av_sample_fmt_is_planar(format) != 0;
224 const int source_channels = std::max(1,
226 codec_context->ch_layout.nb_channels
227#else
228 codec_context->channels
229#endif
230 );
231 const int source_channel = std::min(channel, source_channels - 1);
232 const uint8_t* data = planar ? frame->extended_data[source_channel] : frame->extended_data[0];
233 const int offset = planar ? sample : sample * source_channels + source_channel;
234 switch (format) {
235 case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_U8P:
236 return (static_cast<const uint8_t*>(static_cast<const void*>(data))[offset] - 128) / 128.0f;
237 case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S16P:
238 return static_cast<const int16_t*>(static_cast<const void*>(data))[offset] / 32768.0f;
239 case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_S32P:
240 return static_cast<float>(static_cast<const int32_t*>(static_cast<const void*>(data))[offset] / 2147483648.0);
241 case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_FLTP:
242 return static_cast<const float*>(static_cast<const void*>(data))[offset];
243 case AV_SAMPLE_FMT_DBL: case AV_SAMPLE_FMT_DBLP:
244 return static_cast<float>(static_cast<const double*>(static_cast<const void*>(data))[offset]);
245 default:
246 return 0.0f;
247 }
248 }
249
250 void CaptureLoop()
251 {
252 while (!close_requested) {
253 const int read_result = av_read_frame(format_context, packet);
254 if (read_result == AVERROR(EAGAIN)) continue;
255 if (read_result < 0) break;
256 if (packet->stream_index != audio_stream) {
257 av_packet_unref(packet);
258 continue;
259 }
260 const int send_result = avcodec_send_packet(codec_context, packet);
261 av_packet_unref(packet);
262 if (send_result < 0) continue;
263 while (avcodec_receive_frame(codec_context, decoded_frame) == 0) {
264 const int64_t timestamp = decoded_frame->best_effort_timestamp != AV_NOPTS_VALUE
265 ? decoded_frame->best_effort_timestamp : decoded_frame->pts;
266 if (timestamp == AV_NOPTS_VALUE) {
267 // A packet with unknown capture time cannot be aligned safely.
268 av_frame_unref(decoded_frame);
269 continue;
270 }
271 const int64_t timestamp_us = av_rescale_q(timestamp,
272 format_context->streams[audio_stream]->time_base, AVRational{1, AV_TIME_BASE});
273 std::vector<std::vector<float>> samples(settings.audio_channels,
274 std::vector<float>(decoded_frame->nb_samples));
275 for (int channel = 0; channel < settings.audio_channels; ++channel) {
276 for (int sample = 0; sample < decoded_frame->nb_samples; ++sample) {
277 samples[channel][sample] = SampleAt(decoded_frame, channel, sample);
278 }
279 }
280 std::lock_guard<std::mutex> lock(queue_mutex);
281 audio_buffer.PushTimestamp(timestamp_us, epoch_us, AVRational{1, AV_TIME_BASE},
282 settings.audio_sample_rate, std::move(samples));
283 av_frame_unref(decoded_frame);
284 ready.notify_all();
285 }
286 }
287 }
288
289 ScreenCaptureSettings settings;
290 AVFormatContext* format_context = nullptr;
291 AVCodecContext* codec_context = nullptr;
292 AVPacket* packet = nullptr;
293 AVFrame* decoded_frame = nullptr;
294 int audio_stream = -1;
295 std::atomic<bool> close_requested { false };
296 std::thread worker;
297 std::mutex queue_mutex;
298 std::condition_variable ready;
299 CaptureAudioBuffer audio_buffer;
300 int64_t epoch_us = 0;
301 int64_t last_output_frame = 0;
302 bool timeline_started = false;
303};
304#elif defined(_WIN32)
306{
307public:
308 explicit SystemAudioCapture(const ScreenCaptureSettings& new_settings)
309 : settings(new_settings) {}
310
311 ~SystemAudioCapture() { Close(); }
312
313 void Open()
314 {
315 if (worker.joinable()) return;
316 close_requested = false;
317 worker = std::thread(&SystemAudioCapture::CaptureLoop, this);
318 std::unique_lock<std::mutex> lock(state_mutex);
319 state_ready.wait_for(lock, std::chrono::seconds(5), [this]() { return opened || failed; });
320 if (!opened) {
321 close_requested = true;
322 lock.unlock();
323 if (worker.joinable()) worker.join();
324 throw InvalidOptions(error.empty() ? "Unable to open WASAPI system audio loopback capture." : error);
325 }
326 }
327
328 void Close()
329 {
330 close_requested = true;
331 ready.notify_all();
332 if (worker.joinable()) worker.join();
333 opened = false;
334 }
335
336 void AddFrameAudio(const std::shared_ptr<Frame>& frame, int64_t number, const Fraction& fps)
337 {
338 int sample_count = 0;
339 for (int64_t frame_number = last_output_frame + 1; frame_number <= number; ++frame_number) {
340 sample_count += Frame::GetSamplesPerFrame(frame_number, fps, sample_rate, channel_count);
341 }
342 last_output_frame = std::max(last_output_frame, number);
343 if (!frame || sample_count <= 0) return;
344 std::unique_lock<std::mutex> lock(queue_mutex);
345 const auto wait_time = timeline_started
346 ? std::chrono::milliseconds(100)
347 : std::chrono::milliseconds(3000);
348 ready.wait_for(lock, wait_time, [this, sample_count]() {
349 return close_requested || audio_buffer.Covers(sample_count);
350 });
351 timeline_started = true;
352 auto samples = audio_buffer.Read(sample_count);
353 frame->SampleRate(sample_rate);
354 frame->ChannelsLayout(channel_count == 1 ? LAYOUT_MONO : LAYOUT_STEREO);
355 for (int channel = 0; channel < channel_count; ++channel) {
356 frame->AddAudio(true, channel, 0, samples[channel].data(), sample_count, 1.0f);
357 }
358 }
359
360 void Reset()
361 {
362 std::lock_guard<std::mutex> lock(queue_mutex);
363 // WASAPI returns QPC timestamps already converted to 100 ns units.
364 LARGE_INTEGER counter, frequency;
365 QueryPerformanceCounter(&counter);
366 QueryPerformanceFrequency(&frequency);
367 epoch_100ns = av_rescale(counter.QuadPart, 10000000, frequency.QuadPart);
368 audio_buffer.Reset(channel_count, static_cast<int64_t>(sample_rate) * 10);
369 last_output_frame = 0;
370 timeline_started = false;
371 }
372
373 int SampleRate() const { return sample_rate; }
374 int Channels() const { return channel_count; }
375
376private:
377 template <typename T> static void Release(T*& value)
378 {
379 if (value) { value->Release(); value = nullptr; }
380 }
381
382 void Fail(const std::string& message)
383 {
384 std::lock_guard<std::mutex> lock(state_mutex);
385 error = message;
386 failed = true;
387 state_ready.notify_all();
388 }
389
390 void CaptureLoop()
391 {
392 IMMDeviceEnumerator* enumerator = nullptr;
393 IMMDevice* device = nullptr;
394 IAudioClient* client = nullptr;
395 IAudioCaptureClient* capture = nullptr;
396 WAVEFORMATEX* format = nullptr;
397 const HRESULT com_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
398 const bool uninitialize_com = SUCCEEDED(com_result);
399 HRESULT result = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
400 IID_IMMDeviceEnumerator, reinterpret_cast<void**>(&enumerator));
401 if (SUCCEEDED(result)) {
402 result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
403 }
404 if (SUCCEEDED(result)) {
405 result = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&client));
406 }
407 if (SUCCEEDED(result)) result = client->GetMixFormat(&format);
408 if (SUCCEEDED(result)) {
409 result = client->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK,
410 0, 0, format, nullptr);
411 }
412 if (SUCCEEDED(result)) {
413 result = client->GetService(IID_IAudioCaptureClient, reinterpret_cast<void**>(&capture));
414 }
415 if (SUCCEEDED(result)) result = client->Start();
416 if (FAILED(result) || !format || !capture) {
417 Fail("Unable to initialize WASAPI system audio loopback capture (HRESULT " + std::to_string(result) + ").");
418 if (format) CoTaskMemFree(format);
419 Release(capture); Release(client); Release(device); Release(enumerator);
420 if (uninitialize_com) CoUninitialize();
421 return;
422 }
423
424 sample_rate = static_cast<int>(format->nSamplesPerSec);
425 channel_count = std::max(1, std::min(2, static_cast<int>(format->nChannels)));
426 Reset();
427 bool floating_point = format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT;
428 if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE && format->cbSize >= 22) {
429 const auto* extensible = reinterpret_cast<const WAVEFORMATEXTENSIBLE*>(format);
430 floating_point = extensible->SubFormat.Data1 == WAVE_FORMAT_IEEE_FLOAT;
431 }
432 {
433 std::lock_guard<std::mutex> lock(state_mutex);
434 opened = true;
435 state_ready.notify_all();
436 }
437
438 while (!close_requested) {
439 UINT32 packet_frames = 0;
440 if (FAILED(capture->GetNextPacketSize(&packet_frames))) break;
441 if (packet_frames == 0) {
442 std::this_thread::sleep_for(std::chrono::milliseconds(2));
443 continue;
444 }
445 BYTE* data = nullptr;
446 UINT32 frames = 0;
447 DWORD flags = 0;
448 UINT64 timestamp_100ns = 0;
449 if (FAILED(capture->GetBuffer(&data, &frames, &flags, nullptr, &timestamp_100ns))) break;
450 {
451 std::vector<std::vector<float>> samples(channel_count, std::vector<float>(frames));
452 for (UINT32 frame_index = 0; frame_index < frames; ++frame_index) {
453 for (int channel = 0; channel < channel_count; ++channel) {
454 float sample = 0.0f;
455 if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT) && data) {
456 const int offset = static_cast<int>(frame_index) * format->nChannels + channel;
457 if (floating_point && format->wBitsPerSample == 32) {
458 sample = reinterpret_cast<const float*>(data)[offset];
459 } else if (format->wBitsPerSample == 16) {
460 sample = reinterpret_cast<const int16_t*>(data)[offset] / 32768.0f;
461 } else if (format->wBitsPerSample == 32) {
462 sample = static_cast<float>(reinterpret_cast<const int32_t*>(data)[offset] / 2147483648.0);
463 }
464 }
465 samples[channel][frame_index] = sample;
466 }
467 }
468 // Preserve timestamp gaps (including silence/discontinuities). Never
469 // invent a new epoch from packet arrival time after a delayed read.
470 if (!(flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR)) {
471 std::lock_guard<std::mutex> lock(queue_mutex);
472 audio_buffer.PushTimestamp(static_cast<int64_t>(timestamp_100ns), epoch_100ns,
473 AVRational{1, 10000000}, sample_rate, std::move(samples));
474 }
475 }
476 capture->ReleaseBuffer(frames);
477 ready.notify_all();
478 }
479
480 client->Stop();
481 CoTaskMemFree(format);
482 Release(capture); Release(client); Release(device); Release(enumerator);
483 if (uninitialize_com) CoUninitialize();
484 }
485
486 ScreenCaptureSettings settings;
487 std::atomic<bool> close_requested { false };
488 std::thread worker;
489 std::mutex queue_mutex;
490 std::condition_variable ready;
491 CaptureAudioBuffer audio_buffer;
492 int64_t epoch_100ns = 0;
493 int64_t last_output_frame = 0;
494 bool timeline_started = false;
495 int sample_rate = 48000;
496 int channel_count = 2;
497 std::mutex state_mutex;
498 std::condition_variable state_ready;
499 bool opened = false;
500 bool failed = false;
501 std::string error;
502};
503#else
505{
506public:
508 void Open() {}
509 void Close() {}
510 void AddFrameAudio(const std::shared_ptr<Frame>&, int64_t, const Fraction&) {}
511 void Reset() {}
512 int SampleRate() const { return 0; }
513 int Channels() const { return 0; }
514};
515#endif
516
517namespace
518{
519 std::string fraction_to_string(const Fraction& value)
520 {
521 std::stringstream stream;
522 stream << value.num << "/" << value.den;
523 return stream.str();
524 }
525
526 void set_option(AVDictionary** options, const std::string& key, const std::string& value)
527 {
528 av_dict_set(options, key.c_str(), value.c_str(), 0);
529 }
530
531 bool option_enabled(const std::map<std::string, std::string>& options, const std::string& key)
532 {
533 const auto option = options.find(key);
534 if (option == options.end()) {
535 return false;
536 }
537 return option->second == "1" || option->second == "true" || option->second == "yes";
538 }
539
540 int option_int(const std::map<std::string, std::string>& options, const std::string& key, int fallback)
541 {
542 const auto option = options.find(key);
543 if (option == options.end()) {
544 return fallback;
545 }
546 try {
547 return std::stoi(option->second);
548 } catch (...) {
549 return fallback;
550 }
551 }
552
553 int capture_interrupt_callback(void* opaque)
554 {
555 const auto* reader = static_cast<std::atomic<bool>*>(opaque);
556 return reader && reader->load() ? 1 : 0;
557 }
558
559 std::string avfoundation_video_name(const std::string& input_name)
560 {
561 const size_t separator = input_name.find(':');
562 return separator == std::string::npos ? input_name : input_name.substr(0, separator);
563 }
564
565 std::string avfoundation_audio_name(const std::string& input_name)
566 {
567 const size_t separator = input_name.find(':');
568 return separator == std::string::npos ? "none" : input_name.substr(separator + 1);
569 }
570
571 bool resolve_avfoundation_video_index(
572 AVInputFormat* input_format,
573 const std::string& video_name,
574 std::string& video_index)
575 {
576 const auto is_digit = [](unsigned char value) {
577 return std::isdigit(value) != 0;
578 };
579 if (video_name.empty()
580 || video_name == "default"
581 || video_name == "none"
582 || std::all_of(video_name.begin(), video_name.end(), is_digit)) {
583 return false;
584 }
585
586 AVDeviceInfoList* device_list = nullptr;
587 const int result = avdevice_list_input_sources(input_format, nullptr, nullptr, &device_list);
588 if (result < 0 || !device_list) {
589 avdevice_free_list_devices(&device_list);
590 return false;
591 }
592
593 bool found = false;
594 for (int index = 0; index < device_list->nb_devices; ++index) {
595 const AVDeviceInfo* device = device_list->devices[index];
596 if (!device || !device->device_name) {
597 continue;
598 }
599 const std::string name = device->device_name;
600 const std::string description = device->device_description
601 ? device->device_description
602 : device->device_name;
603 if (video_name == name || video_name == description) {
604 video_index = std::all_of(name.begin(), name.end(), is_digit)
605 ? name
606 : std::to_string(index);
607 found = true;
608 break;
609 }
610 }
611 avdevice_free_list_devices(&device_list);
612 return found;
613 }
614}
615
616ScreenCaptureReader::ScreenCaptureReader(const ScreenCaptureSettings& new_settings)
617 : settings(new_settings)
618 , backend_reader(nullptr)
619 , backend_module(nullptr)
620 , is_open(false)
621 , video_stream(-1)
622 , frames_read(0)
623 , dropped_packets(0)
624 , format_context(nullptr)
625 , codec_context(nullptr)
626 , source_frame(nullptr)
627 , rgba_frame(nullptr)
628 , packet(nullptr)
629 , sws_context(nullptr)
630 , close_requested(false)
631 , system_audio(nullptr)
632{
633 if (settings.backend == SCREEN_CAPTURE_AUTO) {
634 settings.backend = DefaultBackend();
635 }
636 ValidateSettings();
637 PopulateInfo();
638#if defined(__linux__) || defined(_WIN32)
639 if (settings.capture_audio) {
640 system_audio = std::make_unique<SystemAudioCapture>(settings);
641 }
642#endif
643 if (UsesWaylandPortal()) {
644 #if defined(HAVE_WAYLAND_CAPTURE_PLUGIN) && defined(__linux__)
645 std::string module_error;
646 backend_module = load_wayland_capture_backend(module_error);
647 if (!backend_module) {
648 throw InvalidOptions("Wayland screen capture backend is unavailable: "
649 + module_error);
650 }
651 auto factory = reinterpret_cast<WaylandBackendFactory>(
652 dlsym(backend_module, "OpenShotCreateWaylandScreenCaptureReader"));
653 if (!factory) {
654 const char* error = dlerror();
655 dlclose(backend_module);
656 backend_module = nullptr;
657 throw InvalidOptions("Wayland screen capture backend is invalid: "
658 + std::string(error ? error : "factory function is missing."));
659 }
660 try {
661 backend_reader = factory(settings, info);
662 } catch (...) {
663 dlclose(backend_module);
664 backend_module = nullptr;
665 throw;
666 }
667 if (!backend_reader) {
668 dlclose(backend_module);
669 backend_module = nullptr;
670 throw InvalidOptions("Wayland screen capture backend is unavailable.");
671 }
672 #else
673 throw InvalidOptions("Wayland screen capture backend is unavailable in this build.");
674 #endif
675 }
676}
677
679{
680 Close();
681#if defined(__linux__)
682 backend_reader.reset();
683 if (backend_module) {
684 dlclose(backend_module);
685 backend_module = nullptr;
686 }
687#endif
688}
689
691{
692 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
693 return backend_reader ? backend_reader->IsOpen() : is_open;
694}
695
697{
698#if defined(__linux__)
699 if (backend == SCREEN_CAPTURE_X11 || backend == SCREEN_CAPTURE_AUTO) {
700 return true;
701 }
702#if defined(HAVE_WAYLAND_CAPTURE_PLUGIN)
703 if (backend == SCREEN_CAPTURE_WAYLAND) {
704 return true;
705 }
706#endif
707 return false;
708#elif defined(_WIN32)
709 return backend == SCREEN_CAPTURE_WINDOWS_GDI || backend == SCREEN_CAPTURE_AUTO;
710#elif defined(__APPLE__)
711 return backend == SCREEN_CAPTURE_MAC_AVFOUNDATION || backend == SCREEN_CAPTURE_AUTO;
712#else
713 (void) backend;
714 return false;
715#endif
716}
717
719{
720#if defined(__linux__)
721 if (backend == SCREEN_CAPTURE_AUTO) {
722 backend = DefaultBackend();
723 }
724 avdevice_register_all();
725 return (backend == SCREEN_CAPTURE_X11 || backend == SCREEN_CAPTURE_WAYLAND)
726 && av_find_input_format("pulse") != nullptr;
727#elif defined(_WIN32)
728 return backend == SCREEN_CAPTURE_AUTO || backend == SCREEN_CAPTURE_WINDOWS_GDI;
729#else
730 (void) backend;
731 return false;
732#endif
733}
734
736{
737#if defined(__linux__)
738 const char* session = std::getenv("XDG_SESSION_TYPE");
739 if (session && std::string(session) == "wayland" && IsBackendSupported(SCREEN_CAPTURE_WAYLAND)) {
741 }
742 if (!session || std::string(session) == "x11") {
743 return SCREEN_CAPTURE_X11;
744 }
745#elif defined(_WIN32)
747#elif defined(__APPLE__)
749#endif
750 return SCREEN_CAPTURE_AUTO;
751}
752
753void ScreenCaptureReader::ValidateSettings() const
754{
755 if (!IsBackendSupported(settings.backend)) {
756 throw InvalidOptions("Screen capture backend is not supported on this OS or session.");
757 }
758 if (!UsesFFmpegDevice() && !UsesWaylandPortal()) {
759 throw InvalidOptions("Screen capture backend is not implemented in this build.");
760 }
761 if (settings.width <= 0 || settings.height <= 0) {
762 throw InvalidOptions("Screen capture requires a positive width and height.");
763 }
764 if (settings.fps.num <= 0 || settings.fps.den <= 0) {
765 throw InvalidOptions("Screen capture requires a positive frame rate.");
766 }
767 if (settings.capture_audio && !IsSystemAudioSupported(settings.backend)) {
768 throw InvalidOptions("System audio capture is not supported by this screen capture backend.");
769 }
770 if (settings.audio_sample_rate < 8000) {
771 throw InvalidSampleRate("System audio capture requires a sample rate of at least 8000 Hz.");
772 }
773 if (settings.audio_channels < 1 || settings.audio_channels > 2) {
774 throw InvalidChannels("System audio capture requires one or two channels.");
775 }
776}
777
778bool ScreenCaptureReader::UsesFFmpegDevice() const
779{
780 return settings.backend == SCREEN_CAPTURE_X11
783 || settings.options.count("input_format_name");
784}
785
786bool ScreenCaptureReader::UsesWaylandPortal() const
787{
788 return settings.backend == SCREEN_CAPTURE_WAYLAND;
789}
790
791std::string ScreenCaptureReader::InputFormatName() const
792{
793 const auto override_format = settings.options.find("input_format_name");
794 if (override_format != settings.options.end()) {
795 return override_format->second;
796 }
797 if (settings.backend == SCREEN_CAPTURE_X11) {
798 return "x11grab";
799 }
800 if (settings.backend == SCREEN_CAPTURE_WINDOWS_GDI) {
801 return "gdigrab";
802 }
804 return "avfoundation";
805 }
806 return "";
807}
808
809std::string ScreenCaptureReader::InputName() const
810{
811 if (InputFormatName() == "v4l2") {
812 return settings.display.empty() ? "/dev/video0" : settings.display;
813 }
814 if (InputFormatName() == "dshow") {
815 return settings.display;
816 }
817 if (InputFormatName() == "gdigrab") {
818 return settings.display.empty() ? "desktop" : settings.display;
819 }
820 if (InputFormatName() == "avfoundation") {
821 return settings.display.empty() ? "Capture screen 0:none" : settings.display;
822 }
823
824 std::string display = settings.display;
825 if (display.empty()) {
826 const char* env_display = std::getenv("DISPLAY");
827 display = env_display ? env_display : ":0.0";
828 }
829 if (InputFormatName() == "x11grab" && settings.options.count("window_id")) {
830 return display;
831 }
832
833 std::stringstream input;
834 input << display << "+" << settings.x << "," << settings.y;
835 return input.str();
836}
837
838void ScreenCaptureReader::PopulateInfo()
839{
840 info.has_video = true;
841 info.has_audio = settings.capture_audio;
842 info.has_single_image = false;
843 info.duration = 60.0f * 60.0f;
844 info.file_size = 0;
845 info.width = settings.width;
846 info.height = settings.height;
847 info.pixel_format = AV_PIX_FMT_RGBA;
848 info.fps = settings.fps;
850 info.pixel_ratio = Fraction(1, 1);
851 info.display_ratio = Fraction(settings.width, settings.height);
853 info.vcodec = "rawvideo";
854 info.video_length = static_cast<int64_t>(info.duration * settings.fps.ToFloat());
856 info.video_timebase = settings.fps.Reciprocal();
857 info.interlaced_frame = false;
858 info.top_field_first = false;
859 info.acodec = settings.capture_audio ? "pcm_f32le" : "";
861 info.sample_rate = settings.capture_audio ? settings.audio_sample_rate : 0;
862 info.channels = settings.capture_audio ? settings.audio_channels : 0;
866}
867
869{
870 if (backend_reader) {
871 if (backend_reader->IsOpen()) return;
872 manual_system_audio = false;
873 if (system_audio) {
874 system_audio->Open();
875 info.sample_rate = system_audio->SampleRate();
876 info.channels = system_audio->Channels();
878 }
879 try {
880 backend_reader->Open();
881 } catch (...) {
882 if (system_audio) system_audio->Close();
883 throw;
884 }
885 return;
886 }
887 // Close() may run on the UI thread while a worker is opening the device.
888 // Serialize initialization with frame reads and cleanup to keep FFmpeg's
889 // context alive until avformat_open_input/OpenDecoder have finished.
890 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
891 if (is_open) {
892 return;
893 }
894 manual_system_audio = false;
895 close_requested = false;
896 try {
897 if (system_audio) {
898 system_audio->Open();
899 info.sample_rate = system_audio->SampleRate();
900 info.channels = system_audio->Channels();
902 }
903 OpenDevice();
904 OpenDecoder();
905 if (close_requested) {
906 throw ReaderClosed("Capture initialization was cancelled.");
907 }
908 } catch (...) {
909 // Also release partially initialized device/decoder resources. The
910 // recursive lifecycle lock permits using the normal cleanup path here.
911 Close();
912 throw;
913 }
914 is_open = true;
915}
916
917void ScreenCaptureReader::OpenDevice()
918{
919 avdevice_register_all();
920
921 AVInputFormat* input_format = const_cast<AVInputFormat*>(av_find_input_format(InputFormatName().c_str()));
922 std::string input_name = InputName();
923 if (!input_format) {
924 throw InvalidOptions("FFmpeg input device is not available: " + InputFormatName(), input_name);
925 }
926
927 format_context = avformat_alloc_context();
928 if (!format_context) {
929 throw OutOfMemory("Unable to allocate capture format context.", input_name);
930 }
931 format_context->interrupt_callback.callback = capture_interrupt_callback;
932 format_context->interrupt_callback.opaque = &close_requested;
933 if (InputFormatName() == "dshow") {
934 // DirectShow's blocking packet wait does not check the interrupt callback.
935 // Poll through DecodeNextFrame instead so the owning thread can clean up COM.
936 format_context->flags |= AVFMT_FLAG_NONBLOCK;
937 }
938
939 AVDictionary* options = nullptr;
940 const bool use_device_defaults = settings.options.count("use_device_defaults") > 0;
941 const bool post_capture_crop = option_enabled(settings.options, "crop_after_capture");
942 if (!use_device_defaults && !post_capture_crop) {
943 set_option(&options, "framerate", fraction_to_string(settings.fps));
944 set_option(&options, "video_size", std::to_string(settings.width) + "x" + std::to_string(settings.height));
945 } else if (post_capture_crop) {
946 const int source_width = option_int(settings.options, "crop_source_width", settings.width);
947 const int source_height = option_int(settings.options, "crop_source_height", settings.height);
948 set_option(&options, "framerate", fraction_to_string(settings.fps));
949 set_option(&options, "video_size", std::to_string(source_width) + "x" + std::to_string(source_height));
950 }
951 if (InputFormatName() == "x11grab") {
952 set_option(&options, "draw_mouse", settings.include_cursor ? "1" : "0");
953 set_option(&options, "show_region", settings.show_region ? "1" : "0");
954 } else if (InputFormatName() == "gdigrab") {
955 set_option(&options, "draw_mouse", settings.include_cursor ? "1" : "0");
956 set_option(&options, "show_region", settings.show_region ? "1" : "0");
957 set_option(&options, "offset_x", std::to_string(settings.x));
958 set_option(&options, "offset_y", std::to_string(settings.y));
959 } else if (InputFormatName() == "avfoundation") {
960 set_option(&options, "capture_cursor", settings.include_cursor ? "1" : "0");
961 std::string video_index;
962 if (resolve_avfoundation_video_index(input_format, avfoundation_video_name(input_name), video_index)) {
963 set_option(&options, "video_device_index", video_index);
964 input_name = ":" + avfoundation_audio_name(input_name);
965 }
966 }
967 for (const auto& option : settings.options) {
968 if (option.first == "input_format_name"
969 || option.first == "use_device_defaults"
970 || option.first == "crop_after_capture"
971 || option.first == "crop_source_width"
972 || option.first == "crop_source_height") {
973 continue;
974 }
975 set_option(&options, option.first, option.second);
976 }
977
978 const int result = avformat_open_input(&format_context, input_name.c_str(), input_format, &options);
979 av_dict_free(&options);
980 if (result < 0) {
981 throw InvalidFile("Unable to open screen capture input: " + std::string(av_err2str(result)), input_name);
982 }
983}
984
985void ScreenCaptureReader::OpenDecoder()
986{
987 if (avformat_find_stream_info(format_context, nullptr) < 0) {
988 throw InvalidFile("Unable to read capture stream information.", InputName());
989 }
990
991 for (unsigned int index = 0; index < format_context->nb_streams; ++index) {
992 if (format_context->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
993 video_stream = static_cast<int>(index);
994 break;
995 }
996 }
997 if (video_stream < 0) {
998 throw InvalidFile("No video stream found in capture input.", InputName());
999 }
1000
1001 AVStream* stream = format_context->streams[video_stream];
1002 const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
1003 if (!codec) {
1004 throw InvalidCodec("No decoder available for capture stream.", InputName());
1005 }
1006
1007 codec_context = ffmpeg_get_codec_context(stream, codec);
1008 if (!codec_context || avcodec_open2(codec_context, codec, nullptr) < 0) {
1009 throw InvalidCodec("Unable to open capture stream decoder.", InputName());
1010 }
1011
1012 info.width = codec_context->width > 0 ? codec_context->width : settings.width;
1013 info.height = codec_context->height > 0 ? codec_context->height : settings.height;
1014 if (option_enabled(settings.options, "crop_after_capture")) {
1015 info.width = settings.width;
1016 info.height = settings.height;
1017 }
1018 info.video_stream_index = video_stream;
1019 info.pixel_format = codec_context->pix_fmt;
1022 if (codec_context->framerate.num > 0 && codec_context->framerate.den > 0) {
1023 info.fps = Fraction(codec_context->framerate.num, codec_context->framerate.den);
1025 } else if (stream->avg_frame_rate.num > 0 && stream->avg_frame_rate.den > 0) {
1026 info.fps = Fraction(stream->avg_frame_rate.num, stream->avg_frame_rate.den);
1028 } else if (stream->r_frame_rate.num > 0 && stream->r_frame_rate.den > 0) {
1029 info.fps = Fraction(stream->r_frame_rate.num, stream->r_frame_rate.den);
1031 }
1032
1033 source_frame = av_frame_alloc();
1034 rgba_frame = av_frame_alloc();
1035 packet = av_packet_alloc();
1036 if (!source_frame || !rgba_frame || !packet) {
1037 throw OutOfMemory("Unable to allocate capture decoder frames.", InputName());
1038 }
1039}
1040
1041std::shared_ptr<Frame> ScreenCaptureReader::GetFrame(int64_t number)
1042{
1043 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1044 if (backend_reader) {
1045 if (!backend_reader->IsOpen()) {
1046 throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
1047 }
1048 auto frame = backend_reader->GetFrame(number);
1049 if (system_audio && !manual_system_audio) system_audio->AddFrameAudio(frame, number, info.fps);
1050 return frame;
1051 }
1052 if (!is_open) {
1053 throw ReaderClosed("The ScreenCaptureReader is closed. Call Open() before GetFrame().");
1054 }
1055
1056 auto frame = DecodeNextFrame(number);
1057 if (system_audio && !manual_system_audio) system_audio->AddFrameAudio(frame, number, info.fps);
1058 return frame;
1059}
1060
1061void ScreenCaptureReader::AddSystemAudio(std::shared_ptr<Frame> frame, int64_t output_frame_number)
1062{
1063 if (system_audio) {
1064 system_audio->AddFrameAudio(frame, output_frame_number, info.fps);
1065 }
1066}
1067
1069{
1070 manual_system_audio = true;
1071 if (system_audio) {
1072 system_audio->Reset();
1073 }
1074}
1075
1076std::shared_ptr<Frame> ScreenCaptureReader::DecodeNextFrame(int64_t number)
1077{
1078 while (!close_requested) {
1079 const int read_result = av_read_frame(format_context, packet);
1080 if (read_result == AVERROR(EAGAIN)) {
1081 std::this_thread::sleep_for(std::chrono::milliseconds(5));
1082 continue;
1083 }
1084 if (read_result < 0) {
1085 const std::string message = frames_read > 0
1086 ? "Capture input ended after " + std::to_string(frames_read) + " frame(s): "
1087 : "Capture input ended before a frame could be read: ";
1088 throw InvalidFile(message + std::string(av_err2str(read_result)), InputName());
1089 }
1090
1091 if (packet->stream_index != video_stream) {
1092 av_packet_unref(packet);
1093 dropped_packets++;
1094 continue;
1095 }
1096
1097 const int64_t packet_timestamp = packet->pts != AV_NOPTS_VALUE ? packet->pts : packet->dts;
1098 const int send_result = avcodec_send_packet(codec_context, packet);
1099 av_packet_unref(packet);
1100 if (send_result < 0) {
1101 continue;
1102 }
1103
1104 const int receive_result = avcodec_receive_frame(codec_context, source_frame);
1105 if (receive_result == AVERROR(EAGAIN)) {
1106 continue;
1107 }
1108 if (receive_result < 0) {
1109 throw InvalidFile("Unable to decode capture frame: " + std::string(av_err2str(receive_result)), InputName());
1110 }
1111
1112 const AVStream* stream = format_context->streams[video_stream];
1113 int64_t frame_timestamp = source_frame->best_effort_timestamp;
1114 if (frame_timestamp == AV_NOPTS_VALUE) {
1115 frame_timestamp = source_frame->pts;
1116 }
1117 if (frame_timestamp == AV_NOPTS_VALUE) {
1118 frame_timestamp = packet_timestamp;
1119 }
1120 double capture_timestamp = std::numeric_limits<double>::quiet_NaN();
1121 if (frame_timestamp != AV_NOPTS_VALUE && stream) {
1122 capture_timestamp = static_cast<double>(frame_timestamp) * av_q2d(stream->time_base);
1123 }
1124
1125 const int width = source_frame->width > 0 ? source_frame->width : info.width;
1126 const int height = source_frame->height > 0 ? source_frame->height : info.height;
1127 bool src_full_range = source_frame->color_range == AVCOL_RANGE_JPEG;
1128 const PixelFormat src_fmt = NormalizeDeprecatedPixFmt(
1129 static_cast<PixelFormat>(source_frame->format), src_full_range);
1130
1131 sws_context = sws_getCachedContext(
1132 sws_context,
1133 width,
1134 height,
1135 src_fmt,
1136 width,
1137 height,
1138 AV_PIX_FMT_RGBA,
1139 SWS_BILINEAR,
1140 nullptr,
1141 nullptr,
1142 nullptr);
1143 if (!sws_context) {
1144 throw InvalidFile("Unable to create capture pixel conversion context.", InputName());
1145 }
1146
1147 const int* coefficients = sws_getCoefficients(SWS_CS_DEFAULT);
1148 sws_setColorspaceDetails(sws_context, coefficients, src_full_range ? 1 : 0,
1149 coefficients, 1, 0, 1 << 16, 1 << 16);
1150
1151 const int bytes_per_pixel = 4;
1152 const size_t buffer_size = static_cast<size_t>(width) * height * bytes_per_pixel;
1153 unsigned char* buffer = static_cast<unsigned char*>(aligned_malloc(buffer_size));
1154 if (!buffer) {
1155 throw OutOfMemory("Unable to allocate capture frame buffer.", InputName());
1156 }
1157
1158 av_image_fill_arrays(rgba_frame->data, rgba_frame->linesize, buffer, AV_PIX_FMT_RGBA, width, height, 1);
1159 const int scaled_lines = sws_scale(sws_context, source_frame->data, source_frame->linesize, 0, height, rgba_frame->data, rgba_frame->linesize);
1160 av_frame_unref(source_frame);
1161
1162 if (scaled_lines <= 0) {
1163 openshot::aligned_free(buffer);
1164 continue;
1165 }
1166
1167 int output_width = width;
1168 int output_height = height;
1169 unsigned char* output_buffer = buffer;
1170 if (option_enabled(settings.options, "crop_after_capture")) {
1171 const int crop_x = std::max(0, std::min(settings.x, width - 1));
1172 const int crop_y = std::max(0, std::min(settings.y, height - 1));
1173 output_width = std::max(1, std::min(settings.width, width - crop_x));
1174 output_height = std::max(1, std::min(settings.height, height - crop_y));
1175 const size_t output_buffer_size = static_cast<size_t>(output_width) * output_height * bytes_per_pixel;
1176 output_buffer = static_cast<unsigned char*>(aligned_malloc(output_buffer_size));
1177 if (!output_buffer) {
1178 openshot::aligned_free(buffer);
1179 throw OutOfMemory("Unable to allocate cropped capture frame buffer.", InputName());
1180 }
1181 for (int row = 0; row < output_height; ++row) {
1182 const unsigned char* source_row = buffer
1183 + (static_cast<size_t>(crop_y + row) * width + crop_x) * bytes_per_pixel;
1184 unsigned char* dest_row = output_buffer + static_cast<size_t>(row) * output_width * bytes_per_pixel;
1185 std::copy(source_row, source_row + static_cast<size_t>(output_width) * bytes_per_pixel, dest_row);
1186 }
1187 openshot::aligned_free(buffer);
1188 }
1189
1190 auto frame = std::make_shared<Frame>(number, output_width, output_height, "#000000");
1191 frame->capture_timestamp = capture_timestamp;
1192 frame->AddImage(output_width, output_height, bytes_per_pixel, QImage::Format_RGBA8888, output_buffer);
1193 frames_read++;
1194 return frame;
1195 }
1196
1197 throw ReaderClosed("The ScreenCaptureReader was closed.");
1198}
1199
1201{
1202 close_requested = true;
1203}
1204
1206{
1207 // Signal blocking native reads before waiting for GetFrame(). The FFmpeg
1208 // interrupt callback observes close_requested, while backend readers use
1209 // Close() to wake their own blocking waits.
1210 close_requested = true;
1211 if (backend_reader) {
1212 backend_reader->Close();
1213 }
1214
1215 // Open() and GetFrame() own FFmpeg and system-audio use under this mutex.
1216 // Wait for initialization or an interrupted read before releasing resources.
1217 const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
1218 if (system_audio) {
1219 system_audio->Close();
1220 }
1221 if (packet) {
1222 av_packet_free(&packet);
1223 }
1224 if (source_frame) {
1225 av_frame_free(&source_frame);
1226 }
1227 if (rgba_frame) {
1228 av_frame_free(&rgba_frame);
1229 }
1230 if (sws_context) {
1231 sws_freeContext(sws_context);
1232 sws_context = nullptr;
1233 }
1234 if (codec_context) {
1235 avcodec_free_context(&codec_context);
1236 }
1237 if (format_context) {
1238 avformat_close_input(&format_context);
1239 }
1240 is_open = false;
1241 video_stream = -1;
1242}
1243
1245{
1246 if (backend_reader) {
1247 return backend_reader->GetStats();
1248 }
1249 CaptureReaderStats stats;
1250 stats.is_open = is_open;
1251 stats.frames_read = frames_read;
1252 stats.dropped_packets = dropped_packets;
1253 const double fps = settings.fps.den != 0 ? static_cast<double>(settings.fps.num) / static_cast<double>(settings.fps.den) : 0.0;
1254 stats.duration = fps > 0.0 ? static_cast<double>(frames_read) / fps : 0.0;
1255 return stats;
1256}
1257
1258std::string ScreenCaptureReader::Json() const
1259{
1260 return JsonValue().toStyledString();
1261}
1262
1264{
1265 Json::Value root = ReaderBase::JsonValue();
1266 root["type"] = "ScreenCaptureReader";
1267 root["backend"] = settings.backend;
1268 root["display"] = settings.display;
1269 root["x"] = settings.x;
1270 root["y"] = settings.y;
1271 root["width"] = settings.width;
1272 root["height"] = settings.height;
1273 root["fps"]["num"] = settings.fps.num;
1274 root["fps"]["den"] = settings.fps.den;
1275 root["include_cursor"] = settings.include_cursor;
1276 root["show_region"] = settings.show_region;
1277 root["capture_audio"] = settings.capture_audio;
1278 root["audio_device"] = settings.audio_device;
1279 root["audio_sample_rate"] = settings.audio_sample_rate;
1280 root["audio_channels"] = settings.audio_channels;
1281 root["options"] = Json::Value(Json::objectValue);
1282 for (const auto& option : settings.options) {
1283 root["options"][option.first] = option.second;
1284 }
1285 return root;
1286}
1287
1288void ScreenCaptureReader::SetJson(const std::string value)
1289{
1290 try {
1292 } catch (const std::exception&) {
1293 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
1294 }
1295}
1296
1297void ScreenCaptureReader::SetJsonValue(const Json::Value root)
1298{
1299 if (!root["backend"].isNull())
1300 settings.backend = static_cast<ScreenCaptureBackend>(root["backend"].asInt());
1301 if (!root["display"].isNull())
1302 settings.display = root["display"].asString();
1303 if (!root["x"].isNull())
1304 settings.x = root["x"].asInt();
1305 if (!root["y"].isNull())
1306 settings.y = root["y"].asInt();
1307 if (!root["width"].isNull())
1308 settings.width = root["width"].asInt();
1309 if (!root["height"].isNull())
1310 settings.height = root["height"].asInt();
1311 if (!root["fps"].isNull() && root["fps"].isObject()) {
1312 if (!root["fps"]["num"].isNull())
1313 settings.fps.num = root["fps"]["num"].asInt();
1314 if (!root["fps"]["den"].isNull())
1315 settings.fps.den = root["fps"]["den"].asInt();
1316 }
1317 if (!root["include_cursor"].isNull())
1318 settings.include_cursor = root["include_cursor"].asBool();
1319 if (!root["show_region"].isNull())
1320 settings.show_region = root["show_region"].asBool();
1321 if (!root["capture_audio"].isNull())
1322 settings.capture_audio = root["capture_audio"].asBool();
1323 if (!root["audio_device"].isNull())
1324 settings.audio_device = root["audio_device"].asString();
1325 if (!root["audio_sample_rate"].isNull())
1326 settings.audio_sample_rate = root["audio_sample_rate"].asInt();
1327 if (!root["audio_channels"].isNull())
1328 settings.audio_channels = root["audio_channels"].asInt();
1329 if (!root["options"].isNull() && root["options"].isObject()) {
1330 settings.options.clear();
1331 for (const auto& key : root["options"].getMemberNames()) {
1332 settings.options[key] = root["options"][key].asString();
1333 }
1334 }
1335 if (settings.backend == SCREEN_CAPTURE_AUTO) {
1336 settings.backend = DefaultBackend();
1337 }
1338 ValidateSettings();
1339 PopulateInfo();
1340#if defined(__linux__) || defined(_WIN32)
1341 system_audio.reset();
1342 if (settings.capture_audio) {
1343 system_audio = std::make_unique<SystemAudioCapture>(settings);
1344 }
1345#endif
1346}
Header file for all Exception classes.
AVPixelFormat NormalizeDeprecatedPixFmt(AVPixelFormat pix_fmt, bool &is_full_range)
#define PixelFormat
#define HAVE_CH_LAYOUT
#define av_err2str(errnum)
if(!codec) codec
Header file for Frame class.
Header file for QtUtilities (compatibiity overlay)
Header file for live screen capture readers.
void AddFrameAudio(const std::shared_ptr< Frame > &, int64_t, const Fraction &)
This class represents a fraction.
Definition: Fraction.h:30
int num
Numerator for the fraction.
Definition: Fraction.h:32
float ToFloat()
Return this fraction as a float (i.e. 1/2 = 0.5)
Definition: Fraction.cpp:35
void Reduce()
Reduce this fraction (i.e. 640/480 = 4/3)
Definition: Fraction.cpp:65
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition: Fraction.cpp:78
int den
Denominator for the fraction.
Definition: Fraction.h:33
Exception when an invalid # of audio channels are detected.
Definition: Exceptions.h:164
Exception when no valid codec is found for a file.
Definition: Exceptions.h:179
Exception for files that can not be found or opened.
Definition: Exceptions.h:194
Exception for invalid JSON.
Definition: Exceptions.h:224
Exception when invalid encoding options are used.
Definition: Exceptions.h:239
Exception when invalid sample rate is detected during encoding.
Definition: Exceptions.h:254
Exception when memory could not be allocated.
Definition: Exceptions.h:355
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:91
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:110
std::recursive_mutex getFrameMutex
Mutex for multiple threads.
Definition: ReaderBase.h:79
Exception when a reader is closed, and a frame is requested.
Definition: Exceptions.h:370
void SetJson(const std::string value) override
Load JSON string into this object.
bool IsOpen() override
Determine if reader is open or closed.
void RequestClose()
Signal FFmpeg capture reads to stop; the opening thread must call Close().
openshot::CaptureReaderStats GetStats() const
void Close() override
Close the reader (and any resources it was consuming)
static ScreenCaptureBackend DefaultBackend()
static bool IsBackendSupported(ScreenCaptureBackend backend)
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
void AddSystemAudio(std::shared_ptr< openshot::Frame > frame, int64_t output_frame_number)
Json::Value JsonValue() const override
Generate Json::Value for this object.
std::shared_ptr< openshot::Frame > GetFrame(int64_t number) override
std::string Json() const override
Generate JSON string of this object.
void Open() override
Open the reader (and start consuming resources, such as images or video files)
static bool IsSystemAudioSupported(ScreenCaptureBackend backend)
This namespace is the default namespace for all code in the openshot library.
Definition: AnimatedCurve.h:24
void aligned_free(void *ptr)
Definition: QtUtilities.h:32
@ SCREEN_CAPTURE_MAC_AVFOUNDATION
@ SCREEN_CAPTURE_WINDOWS_GDI
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
This struct contains info about a media file, such as height, width, frames per second,...
Definition: ReaderBase.h:39
int audio_bit_rate
The bit rate of the audio stream (in bytes)
Definition: ReaderBase.h:59
int video_bit_rate
The bit rate of the video stream (in bytes)
Definition: ReaderBase.h:49
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42
float duration
Length of time (in seconds)
Definition: ReaderBase.h:43
openshot::Fraction audio_timebase
The audio timebase determines how long each audio packet should be played.
Definition: ReaderBase.h:64
int width
The width of the video (in pixesl)
Definition: ReaderBase.h:46
int channels
The number of audio channels used in the audio stream.
Definition: ReaderBase.h:61
openshot::Fraction fps
Frames per second, as a fraction (i.e. 24/1 = 24 fps)
Definition: ReaderBase.h:48
openshot::Fraction display_ratio
The ratio of width to height of the video stream (i.e. 640x480 has a ratio of 4/3)
Definition: ReaderBase.h:51
int height
The height of the video (in pixels)
Definition: ReaderBase.h:45
int pixel_format
The pixel format (i.e. YUV420P, RGB24, etc...)
Definition: ReaderBase.h:47
int64_t video_length
The number of frames in the video stream.
Definition: ReaderBase.h:53
std::string acodec
The name of the audio codec used to encode / decode the video stream.
Definition: ReaderBase.h:58
std::string vcodec
The name of the video codec used to encode / decode the video stream.
Definition: ReaderBase.h:52
openshot::Fraction pixel_ratio
The pixel ratio of the video stream as a fraction (i.e. some pixels are not square)
Definition: ReaderBase.h:50
openshot::ChannelLayout channel_layout
The channel layout (mono, stereo, 5 point surround, etc...)
Definition: ReaderBase.h:62
bool has_video
Determines if this file has a video stream.
Definition: ReaderBase.h:40
bool has_audio
Determines if this file has an audio stream.
Definition: ReaderBase.h:41
openshot::Fraction video_timebase
The video timebase determines how long each frame stays on the screen.
Definition: ReaderBase.h:55
int video_stream_index
The index of the video stream.
Definition: ReaderBase.h:54
int sample_rate
The number of audio samples per second (44100 is a common sample rate)
Definition: ReaderBase.h:60
int audio_stream_index
The index of the audio stream.
Definition: ReaderBase.h:63
int64_t file_size
Size of file (in bytes)
Definition: ReaderBase.h:44
std::map< std::string, std::string > options