video_device.h 7.69 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2014-2019 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
 *
4
 *  Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
5
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

22
#pragma once
23

24
25
#include "media/media_device.h"
#include "video_base.h"
26
#include "rational.h"
27

Guillaume Roguez's avatar
Guillaume Roguez committed
28
#include <cmath>
29
30
31
32
#include <map>
#include <memory>
#include <string>
#include <vector>
33
#include <algorithm>
34
#include <sstream>
35

36
#include "videomanager_interface.h"
37
38
#include "string_utils.h"
#include "logger.h"
39

Adrien Béraud's avatar
Adrien Béraud committed
40
namespace jami { namespace video {
41

42
43
44
using VideoSize = std::pair<unsigned, unsigned>;
using FrameRate = rational<double>;

45
46
47
48
49
class VideoDeviceImpl;

class VideoDevice {
public:

50
    VideoDevice(const std::string& path, const std::vector<std::map<std::string, std::string>>& devInfo);
51
52
53
54
55
56
    ~VideoDevice();

    /*
     * The device name, e.g. "Integrated Camera",
     * actually used as the identifier.
     */
57
    std::string name {};
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

    const std::string& getNode() const { return node_; }

    /*
     * Get the 3 level deep tree of possible settings for the device.
     * The levels are channels, sizes, and rates.
     *
     * The result map for the "Integrated Camera" looks like this:
     *
     *   {'Camera 1': {'1280x720': ['10'],
     *                 '320x240': ['30', '15'],
     *                 '352x288': ['30', '15'],
     *                 '424x240': ['30', '15'],
     *                 '640x360': ['30', '15'],
     *                 '640x480': ['30', '15'],
     *                 '800x448': ['15'],
     *                 '960x540': ['10']}}
     */
76
77
78
79
80
81
82
83
84
85
    DRing::VideoCapabilities getCapabilities() const {
        DRing::VideoCapabilities cap;

        for (const auto& chan : getChannelList())
            for (const auto& size : getSizeList(chan)) {
                std::stringstream sz;
                sz << size.first << "x" << size.second;
                auto rates = getRateList(chan, size);
                std::vector<std::string> rates_str {rates.size()};
                std::transform(rates.begin(), rates.end(), rates_str.begin(),
86
                               [](FrameRate r) { return std::to_string(r.real()); });
87
88
89
90
91
92
93
                cap[chan][sz.str()] = std::move(rates_str);
            }

        return cap;
    }

    /* Default setting is found by using following rules:
94
     * - frame height <= 640 pixels
95
96
97
98
     * - frame rate >= 10 fps
     */
    VideoSettings getDefaultSettings() const {
        auto settings = getSettings();
99
100
101
        auto channels = getChannelList();
        if (channels.empty())
            return {};
102
103
104
105
106
107
108
        settings.channel = getChannelList().front();

        VideoSize max_size {0, 0};
        FrameRate max_size_rate {0};

        auto sizes = getSizeList(settings.channel);
        for (auto& s : sizes) {
109
            if (s.second > 640)
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
                continue;
            auto rates = getRateList(settings.channel, s);
            if (rates.empty())
                continue;
            auto max_rate = *std::max_element(rates.begin(), rates.end());
            if (max_rate < 10)
                continue;
            if (s.second > max_size.second ||
                (s.second == max_size.second && s.first > max_size.first)) {
                max_size = s;
                max_size_rate = max_rate;
            }
        }
        if (max_size.second > 0) {
            std::stringstream video_size;
            video_size << max_size.first << "x" << max_size.second;
            settings.video_size = video_size.str();
127
            settings.framerate = std::to_string(max_size_rate.real());
Adrien Béraud's avatar
Adrien Béraud committed
128
            JAMI_WARN("Default video settings: %s, %s FPS", settings.video_size.c_str(),
129
130
131
132
133
                      settings.framerate.c_str());
        }

        return settings;
    }
134
135

    /*
136
     * Get the settings for the device.
137
     */
138
139
140
141
142
143
    VideoSettings getSettings() const {
        auto params = getDeviceParams();
        VideoSettings settings;
        settings.name = params.name;
        settings.channel = params.channel_name;
        settings.video_size = sizeToString(params.width, params.height);
144
        settings.framerate = std::to_string(params.framerate.real());
145
146
        return settings;
    }
147
148
149
150
151
152
153
154

    /*
     * Setup the device with the preferences listed in the "settings" map.
     * The expected map should be similar to the result of getSettings().
     *
     * If a key is missing, a valid default value is choosen. Thus, calling
     * this function with an empty map will reset the device to default.
     */
155
156
157
158
159
160
161
162
163
164
    void applySettings(VideoSettings settings) {
        DeviceParams params {};
        params.name = settings.name;
        params.channel_name = settings.channel;
        auto size = sizeFromString(settings.channel, settings.video_size);
        params.width = size.first;
        params.height = size.second;
        params.framerate = rateFromString(settings.channel, size, settings.framerate);
        setDeviceParams(params);
    }
165

Adrien Béraud's avatar
Adrien Béraud committed
166
167
168
169
    void setOrientation(int orientation) {
      orientation_ = orientation;
    }

170
171
172
173
    /**
     * Returns the parameters needed for actual use of the device
     */
    DeviceParams getDeviceParams() const;
174
    std::vector<std::string> getChannelList() const;
175

176
177
private:

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    std::vector<VideoSize> getSizeList(const std::string& channel) const;
    std::vector<FrameRate> getRateList(const std::string& channel, VideoSize size) const;

    VideoSize sizeFromString(const std::string& channel, const std::string& size) const {
        auto size_list = getSizeList(channel);
        for (const auto& s : size_list) {
            if (sizeToString(s.first, s.second) == size)
                return s;
        }
        return {0, 0};
    }

    std::string sizeToString(unsigned w, unsigned h) const {
        std::stringstream video_size;
        video_size << w << "x" << h;
        return video_size.str();
    }

    FrameRate rateFromString(const std::string& channel, VideoSize size,
                             const std::string& rate) const {
        FrameRate closest {0};
        double rate_val = 0;
        try {
Adrien Béraud's avatar
Adrien Béraud committed
201
            rate_val = rate.empty() ? 0 : jami::stod(rate);
202
        } catch (...) {
Adrien Béraud's avatar
Adrien Béraud committed
203
            JAMI_WARN("Can't read framerate \"%s\"", rate.c_str());
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        }
        // fallback to framerate closest to 30 FPS
        if (rate_val == 0)
            rate_val = 30;
        double closest_dist = std::numeric_limits<double>::max();
        auto rate_list = getRateList(channel, size);
        for (const auto& r : rate_list) {
            double dist = std::fabs(r.real() - rate_val);
            if (dist < closest_dist) {
                closest = r;
                closest_dist = dist;
            }
        }
        return closest;
    }

    void setDeviceParams(const DeviceParams&);

222
223
224
    /*
     * The device node, e.g. "/dev/video0".
     */
225
    std::string node_ {};
226

Adrien Béraud's avatar
Adrien Béraud committed
227
228
    int orientation_ {0};

229
230
231
232
233
234
235
236
237
238
    /*
     * Device specific implementation.
     * On Linux, V4L2 stuffs go there.
     *
     * Note: since a VideoDevice is copyable,
     * deviceImpl_ cannot be an unique_ptr.
     */
    std::shared_ptr<VideoDeviceImpl> deviceImpl_;
};

Adrien Béraud's avatar
Adrien Béraud committed
239
}} // namespace jami::video