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