]> git.sesse.net Git - nageru/blob - nageru/input_mapping.cpp
Make number of cards flexible at runtime.
[nageru] / 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 #include "shared/text_proto.h"
14
15 using namespace std;
16 using namespace google::protobuf;
17
18 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
19 {
20         InputMappingProto mapping_proto;
21         {
22                 map<DeviceSpec, unsigned> used_devices;
23                 for (const InputMapping::Bus &bus : input_mapping.buses) {
24                         if (!used_devices.count(bus.device)) {
25                                 used_devices.emplace(bus.device, used_devices.size());
26                                 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
27                         }
28
29                         BusProto *bus_proto = mapping_proto.add_bus();
30                         bus_proto->set_name(bus.name);
31                         bus_proto->set_device_index(used_devices[bus.device]);
32                         bus_proto->set_source_channel_left(bus.source_channel[0]);
33                         bus_proto->set_source_channel_right(bus.source_channel[1]);
34                 }
35         }
36
37         return save_proto_to_file(mapping_proto, filename);
38 }
39
40 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
41 {
42         InputMappingProto mapping_proto;
43         if (!load_proto_from_file(filename, &mapping_proto)) {
44                 return false;
45         }
46
47         // Map devices in the proto to our current ones:
48
49         // Get a list of all active devices.
50         set<DeviceSpec> remaining_devices;
51         for (const auto &device_spec_and_info : devices) {
52                 remaining_devices.insert(device_spec_and_info.first);
53         }
54
55         // Now look at every device in the serialized protobuf and try to map
56         // it to one device we haven't taken yet. This isn't a full maximal matching,
57         // but it's good enough for our uses.
58         vector<DeviceSpec> device_mapping;
59         for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
60                 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
61                 switch (device_proto.type()) {
62                 case DeviceSpecProto::SILENCE:
63                         device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
64                         break;
65                 case DeviceSpecProto::CAPTURE_CARD: {
66                         // First see if there's a card that matches on both index and name.
67                         DeviceSpec spec;
68                         spec.type = InputSourceType::CAPTURE_CARD;
69                         spec.index = unsigned(device_proto.index());
70                         assert(devices.count(spec));
71
72                         const DeviceInfo &dev = devices.find(spec)->second;
73                         if (remaining_devices.count(spec) &&
74                             dev.display_name == device_proto.display_name()) {
75                                 device_mapping.push_back(spec);
76                                 remaining_devices.erase(spec);
77                                 goto found_capture_card;
78                         }
79
80                         // Scan and see if there's a match on name alone.
81                         for (const DeviceSpec &spec : remaining_devices) {
82                                 if (spec.type == InputSourceType::CAPTURE_CARD &&
83                                     dev.display_name == device_proto.display_name()) {
84                                         device_mapping.push_back(spec);
85                                         remaining_devices.erase(spec);
86                                         goto found_capture_card;
87                                 }
88                         }
89
90                         // OK, see if at least the index is free.
91                         if (remaining_devices.count(spec)) {
92                                 device_mapping.push_back(spec);
93                                 remaining_devices.erase(spec);
94                                 goto found_capture_card;
95                         }
96
97                         // Give up.
98                         device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
99 found_capture_card:
100                         break;
101                 }
102                 case DeviceSpecProto::ALSA_INPUT: {
103                         // For ALSA, we don't really care about index, but we can use address
104                         // in its place.
105
106                         // First see if there's a card that matches on name, num_channels and address.
107                         for (const DeviceSpec &spec : remaining_devices) {
108                                 assert(devices.count(spec));
109                                 const DeviceInfo &dev = devices.find(spec)->second;
110                                 if (spec.type == InputSourceType::ALSA_INPUT &&
111                                     dev.alsa_name == device_proto.alsa_name() &&
112                                     dev.alsa_info == device_proto.alsa_info() &&
113                                     int(dev.num_channels) == device_proto.num_channels() &&
114                                     dev.alsa_address == device_proto.address()) {
115                                         device_mapping.push_back(spec);
116                                         remaining_devices.erase(spec);
117                                         goto found_alsa_input;
118                                 }
119                         }
120
121                         // Looser check: Ignore the address.
122                         for (const DeviceSpec &spec : remaining_devices) {
123                                 assert(devices.count(spec));
124                                 const DeviceInfo &dev = devices.find(spec)->second;
125                                 if (spec.type == InputSourceType::ALSA_INPUT &&
126                                     dev.alsa_name == device_proto.alsa_name() &&
127                                     dev.alsa_info == device_proto.alsa_info() &&
128                                     int(dev.num_channels) == device_proto.num_channels()) {
129                                         device_mapping.push_back(spec);
130                                         remaining_devices.erase(spec);
131                                         goto found_alsa_input;
132                                 }
133                         }
134
135                         // OK, so we couldn't map this to a device, but perhaps one is added
136                         // at some point in the future through hotplug. Create a dead card
137                         // matching this one; right now, it will give only silence,
138                         // but it could be replaced with something later.
139                         //
140                         // NOTE: There's a potential race condition here, if the card
141                         // gets inserted while we're doing the device remapping
142                         // (or perhaps more realistically, while we're reading the
143                         // input mapping from disk).
144                         DeviceSpec dead_card_spec;
145                         dead_card_spec = global_audio_mixer->create_dead_card(
146                                 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
147                         device_mapping.push_back(dead_card_spec);
148
149 found_alsa_input:
150                         break;
151                 }
152                 default:
153                         assert(false);
154                 }
155         }
156
157         for (const BusProto &bus_proto : mapping_proto.bus()) {
158                 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
159                         return false;
160                 }
161                 InputMapping::Bus bus;
162                 bus.name = bus_proto.name();
163                 bus.device = device_mapping[bus_proto.device_index()];
164                 bus.source_channel[0] = bus_proto.source_channel_left();
165                 bus.source_channel[1] = bus_proto.source_channel_right();
166                 new_mapping->buses.push_back(bus);
167         }
168
169         return true;
170 }