Line data Source code
1 : /*
2 : * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 : *
4 : * This program is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program 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
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 : */
17 : #pragma once
18 :
19 : #include "media/media_device.h"
20 : #include "video_base.h"
21 : #include "rational.h"
22 :
23 : #include "videomanager_interface.h"
24 : #include "string_utils.h"
25 : #include "logger.h"
26 :
27 : #include <fmt/core.h>
28 :
29 : #include <cmath>
30 : #include <map>
31 : #include <memory>
32 : #include <string>
33 : #include <vector>
34 : #include <algorithm>
35 : #include <sstream>
36 :
37 : namespace jami {
38 : namespace video {
39 :
40 : using VideoSize = std::pair<unsigned, unsigned>;
41 : using FrameRate = rational<double>;
42 : static constexpr const char DEVICE_DESKTOP[] = "desktop";
43 :
44 : class VideoDeviceImpl;
45 :
46 : class VideoDevice
47 : {
48 : public:
49 : VideoDevice(const std::string& path, const std::vector<std::map<std::string, std::string>>& devInfo);
50 : ~VideoDevice();
51 :
52 : /*
53 : * The device name, e.g. "Integrated Camera",
54 : * actually used as the identifier.
55 : */
56 : std::string name {};
57 :
58 1623 : const std::string& getDeviceId() const { return id_; }
59 :
60 : /*
61 : * Get the 3 level deep tree of possible settings for the device.
62 : * The levels are channels, sizes, and rates.
63 : *
64 : * The result map for the "Integrated Camera" looks like this:
65 : *
66 : * {'Camera 1': {'1280x720': ['10'],
67 : * '320x240': ['30', '15'],
68 : * '352x288': ['30', '15'],
69 : * '424x240': ['30', '15'],
70 : * '640x360': ['30', '15'],
71 : * '640x480': ['30', '15'],
72 : * '800x448': ['15'],
73 : * '960x540': ['10']}}
74 : */
75 0 : libjami::VideoCapabilities getCapabilities() const
76 : {
77 0 : libjami::VideoCapabilities cap;
78 :
79 0 : for (const auto& chan : getChannelList())
80 0 : for (const auto& size : getSizeList(chan)) {
81 0 : std::string sz = fmt::format("{}x{}", size.first, size.second);
82 0 : auto rates = getRateList(chan, size);
83 0 : std::vector<std::string> rates_str {rates.size()};
84 0 : std::transform(rates.begin(), rates.end(), rates_str.begin(), [](const FrameRate& r) {
85 0 : return jami::to_string(r.real());
86 : });
87 0 : cap[chan][sz] = std::move(rates_str);
88 0 : }
89 :
90 0 : return cap;
91 0 : }
92 :
93 : /* Default setting is found by using following rules:
94 : * - frame height <= 640 pixels
95 : * - frame rate >= 10 fps
96 : */
97 32 : VideoSettings getDefaultSettings() const
98 : {
99 32 : auto settings = getSettings();
100 32 : auto channels = getChannelList();
101 32 : if (channels.empty())
102 0 : return {};
103 32 : settings.channel = getChannelList().front();
104 :
105 32 : VideoSize max_size {0, 0};
106 32 : FrameRate max_size_rate {0};
107 :
108 32 : auto sizes = getSizeList(settings.channel);
109 64 : for (auto& s : sizes) {
110 32 : if (s.second > 640)
111 0 : continue;
112 32 : auto rates = getRateList(settings.channel, s);
113 32 : if (rates.empty())
114 0 : continue;
115 32 : auto max_rate = *std::max_element(rates.begin(), rates.end());
116 32 : if (max_rate < 10)
117 0 : continue;
118 32 : if (s.second > max_size.second || (s.second == max_size.second && s.first > max_size.first)) {
119 0 : max_size = s;
120 0 : max_size_rate = max_rate;
121 : }
122 32 : }
123 32 : if (max_size.second > 0) {
124 0 : settings.video_size = fmt::format("{}x{}", max_size.first, max_size.second);
125 0 : settings.framerate = jami::to_string(max_size_rate.real());
126 0 : JAMI_WARNING("[{}] Default video settings: {}, {} FPS", name, settings.video_size, settings.framerate);
127 : }
128 :
129 32 : return settings;
130 32 : }
131 :
132 : /*
133 : * Get the settings for the device.
134 : */
135 64 : VideoSettings getSettings() const
136 : {
137 64 : auto params = getDeviceParams();
138 64 : VideoSettings settings;
139 64 : settings.name = name.empty() ? params.name : name;
140 64 : settings.unique_id = params.unique_id;
141 64 : settings.input = params.input;
142 64 : settings.channel = params.channel_name;
143 64 : settings.video_size = sizeToString(params.width, params.height);
144 64 : settings.framerate = jami::to_string(params.framerate.real());
145 128 : return settings;
146 64 : }
147 :
148 : /*
149 : * Setup the device with the preferences listed in the "settings" map.
150 : * The expected map should be similar to the result of getSettings().
151 : *
152 : * If a key is missing, a valid default value is choosen. Thus, calling
153 : * this function with an empty map will reset the device to default.
154 : */
155 32 : void applySettings(VideoSettings settings)
156 : {
157 32 : DeviceParams params {};
158 32 : params.name = settings.name;
159 32 : params.input = settings.input;
160 32 : params.unique_id = settings.unique_id;
161 32 : params.channel_name = settings.channel;
162 32 : auto size = sizeFromString(settings.channel, settings.video_size);
163 32 : params.width = size.first;
164 32 : params.height = size.second;
165 32 : params.framerate = rateFromString(settings.channel, size, settings.framerate);
166 32 : setDeviceParams(params);
167 32 : }
168 :
169 0 : void setOrientation(int orientation) { orientation_ = orientation; }
170 :
171 : /**
172 : * Returns the parameters needed for actual use of the device
173 : */
174 : DeviceParams getDeviceParams() const;
175 : std::vector<std::string> getChannelList() const;
176 :
177 : private:
178 : std::vector<VideoSize> getSizeList(const std::string& channel) const;
179 : std::vector<FrameRate> getRateList(const std::string& channel, VideoSize size) const;
180 :
181 32 : VideoSize sizeFromString(const std::string& channel, const std::string& size) const
182 : {
183 32 : auto size_list = getSizeList(channel);
184 32 : for (const auto& s : size_list) {
185 32 : if (sizeToString(s.first, s.second) == size)
186 32 : return s;
187 : }
188 0 : return {0, 0};
189 32 : }
190 :
191 192 : std::string sizeToString(unsigned w, unsigned h) const { return fmt::format("{}x{}", w, h); }
192 :
193 32 : FrameRate rateFromString(const std::string& channel, VideoSize size, const std::string& rate) const
194 : {
195 32 : FrameRate closest {0};
196 32 : double rate_val = 0;
197 : try {
198 32 : rate_val = rate.empty() ? 0 : std::stod(rate);
199 0 : } catch (...) {
200 0 : JAMI_WARN("Unable to read framerate \"%s\"", rate.c_str());
201 0 : }
202 : // fallback to framerate closest to 30 FPS
203 32 : if (rate_val == 0)
204 0 : rate_val = 30;
205 32 : double closest_dist = std::numeric_limits<double>::max();
206 32 : auto rate_list = getRateList(channel, size);
207 352 : for (const auto& r : rate_list) {
208 320 : double dist = std::fabs(r.real() - rate_val);
209 320 : if (dist < closest_dist) {
210 224 : closest = r;
211 224 : closest_dist = dist;
212 : }
213 : }
214 32 : return closest;
215 32 : }
216 :
217 : void setDeviceParams(const DeviceParams&);
218 :
219 : /*
220 : * The device node, e.g. "046d082dF41A2B3F".
221 : */
222 : std::string id_ {};
223 :
224 : int orientation_ {0};
225 :
226 : /*
227 : * Device specific implementation.
228 : * On Linux, V4L2 stuffs go there.
229 : *
230 : * Note: since a VideoDevice is copyable,
231 : * deviceImpl_ cannot be an unique_ptr.
232 : */
233 : std::shared_ptr<VideoDeviceImpl> deviceImpl_;
234 : };
235 :
236 : } // namespace video
237 : } // namespace jami
|