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