]> git.sesse.net Git - nageru/blob - input_mapping.cpp
Release Nageru 1.7.2.
[nageru] / input_mapping.cpp
1 #include "input_mapping.h"
2
3 #include <assert.h>
4 #include <fcntl.h>
5 #include <google/protobuf/io/zero_copy_stream_impl.h>
6 #include <google/protobuf/text_format.h>
7 #include <stdio.h>
8 #include <set>
9 #include <utility>
10
11 #include "audio_mixer.h" 
12 #include "state.pb.h"
13
14 using namespace std;
15 using namespace google::protobuf;
16
17 string spec_to_string(DeviceSpec device_spec)
18 {
19         char buf[256];
20
21         switch (device_spec.type) {
22         case InputSourceType::SILENCE:
23                 return "<silence>";
24         case InputSourceType::CAPTURE_CARD:
25                 snprintf(buf, sizeof(buf), "Capture card %u", device_spec.index);
26                 return buf;
27         case InputSourceType::ALSA_INPUT:
28                 snprintf(buf, sizeof(buf), "ALSA input %u", device_spec.index);
29                 return buf;
30         case InputSourceType::FFMPEG_VIDEO_INPUT:
31                 snprintf(buf, sizeof(buf), "FFmpeg input %u", device_spec.index);
32                 return buf;
33         default:
34                 assert(false);
35         }
36 }
37
38 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
39 {
40         InputMappingProto mapping_proto;
41         {
42                 map<DeviceSpec, unsigned> used_devices;
43                 for (const InputMapping::Bus &bus : input_mapping.buses) {
44                         if (!used_devices.count(bus.device)) {
45                                 used_devices.emplace(bus.device, used_devices.size());
46                                 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
47                         }
48
49                         BusProto *bus_proto = mapping_proto.add_bus();
50                         bus_proto->set_name(bus.name);
51                         bus_proto->set_device_index(used_devices[bus.device]);
52                         bus_proto->set_source_channel_left(bus.source_channel[0]);
53                         bus_proto->set_source_channel_right(bus.source_channel[1]);
54                 }
55         }
56
57         // Save to disk. We use the text format because it's friendlier
58         // for a user to look at and edit.
59         int fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
60         if (fd == -1) {
61                 perror(filename.c_str());
62                 return false;
63         }
64         io::FileOutputStream output(fd);  // Takes ownership of fd.
65         if (!TextFormat::Print(mapping_proto, &output)) {
66                 // TODO: Don't overwrite the old file (if any) on error.
67                 output.Close();
68                 return false;
69         }
70
71         output.Close();
72         return true;
73 }
74
75 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
76 {
77         // Read and parse the protobuf from disk.
78         int fd = open(filename.c_str(), O_RDONLY);
79         if (fd == -1) {
80                 perror(filename.c_str());
81                 return false;
82         }
83         io::FileInputStream input(fd);  // Takes ownership of fd.
84         InputMappingProto mapping_proto;
85         if (!TextFormat::Parse(&input, &mapping_proto)) {
86                 input.Close();
87                 return false;
88         }
89         input.Close();
90
91         // Map devices in the proto to our current ones:
92
93         // Get a list of all active devices.
94         set<DeviceSpec> remaining_devices;
95         for (const auto &device_spec_and_info : devices) {
96                 remaining_devices.insert(device_spec_and_info.first);
97         }
98
99         // Now look at every device in the serialized protobuf and try to map
100         // it to one device we haven't taken yet. This isn't a full maximal matching,
101         // but it's good enough for our uses.
102         vector<DeviceSpec> device_mapping;
103         for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
104                 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
105                 switch (device_proto.type()) {
106                 case DeviceSpecProto::SILENCE:
107                         device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
108                         break;
109                 case DeviceSpecProto::FFMPEG_VIDEO_INPUT:
110                 case DeviceSpecProto::CAPTURE_CARD: {
111                         // First see if there's a card that matches on both index and name.
112                         DeviceSpec spec;
113                         spec.type = (device_proto.type() == DeviceSpecProto::CAPTURE_CARD) ?
114                                 InputSourceType::CAPTURE_CARD : InputSourceType::FFMPEG_VIDEO_INPUT;
115                         spec.index = unsigned(device_proto.index());
116                         assert(devices.count(spec));
117
118                         const DeviceInfo &dev = devices.find(spec)->second;
119                         if (remaining_devices.count(spec) &&
120                             dev.display_name == device_proto.display_name()) {
121                                 device_mapping.push_back(spec);
122                                 remaining_devices.erase(spec);
123                                 goto found_capture_card;
124                         }
125
126                         // Scan and see if there's a match on name alone.
127                         for (const DeviceSpec &spec : remaining_devices) {
128                                 if (spec.type == InputSourceType::CAPTURE_CARD &&
129                                     dev.display_name == device_proto.display_name()) {
130                                         device_mapping.push_back(spec);
131                                         remaining_devices.erase(spec);
132                                         goto found_capture_card;
133                                 }
134                         }
135
136                         // OK, see if at least the index is free.
137                         if (remaining_devices.count(spec)) {
138                                 device_mapping.push_back(spec);
139                                 remaining_devices.erase(spec);
140                                 goto found_capture_card;
141                         }
142
143                         // Give up.
144                         device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
145 found_capture_card:
146                         break;
147                 }
148                 case DeviceSpecProto::ALSA_INPUT: {
149                         // For ALSA, we don't really care about index, but we can use address
150                         // in its place.
151
152                         // First see if there's a card that matches on name, num_channels and address.
153                         for (const DeviceSpec &spec : remaining_devices) {
154                                 assert(devices.count(spec));
155                                 const DeviceInfo &dev = devices.find(spec)->second;
156                                 if (spec.type == InputSourceType::ALSA_INPUT &&
157                                     dev.alsa_name == device_proto.alsa_name() &&
158                                     dev.alsa_info == device_proto.alsa_info() &&
159                                     int(dev.num_channels) == device_proto.num_channels() &&
160                                     dev.alsa_address == device_proto.address()) {
161                                         device_mapping.push_back(spec);
162                                         remaining_devices.erase(spec);
163                                         goto found_alsa_input;
164                                 }
165                         }
166
167                         // Looser check: Ignore the address.
168                         for (const DeviceSpec &spec : remaining_devices) {
169                                 assert(devices.count(spec));
170                                 const DeviceInfo &dev = devices.find(spec)->second;
171                                 if (spec.type == InputSourceType::ALSA_INPUT &&
172                                     dev.alsa_name == device_proto.alsa_name() &&
173                                     dev.alsa_info == device_proto.alsa_info() &&
174                                     int(dev.num_channels) == device_proto.num_channels()) {
175                                         device_mapping.push_back(spec);
176                                         remaining_devices.erase(spec);
177                                         goto found_alsa_input;
178                                 }
179                         }
180
181                         // OK, so we couldn't map this to a device, but perhaps one is added
182                         // at some point in the future through hotplug. Create a dead card
183                         // matching this one; right now, it will give only silence,
184                         // but it could be replaced with something later.
185                         //
186                         // NOTE: There's a potential race condition here, if the card
187                         // gets inserted while we're doing the device remapping
188                         // (or perhaps more realistically, while we're reading the
189                         // input mapping from disk).
190                         DeviceSpec dead_card_spec;
191                         dead_card_spec = global_audio_mixer->create_dead_card(
192                                 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
193                         device_mapping.push_back(dead_card_spec);
194
195 found_alsa_input:
196                         break;
197                 }
198                 default:
199                         assert(false);
200                 }
201         }
202
203         for (const BusProto &bus_proto : mapping_proto.bus()) {
204                 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
205                         return false;
206                 }
207                 InputMapping::Bus bus;
208                 bus.name = bus_proto.name();
209                 bus.device = device_mapping[bus_proto.device_index()];
210                 bus.source_channel[0] = bus_proto.source_channel_left();
211                 bus.source_channel[1] = bus_proto.source_channel_right();
212                 new_mapping->buses.push_back(bus);
213         }
214
215         return true;
216 }