1 #include "input_mapping.h"
5 #include <google/protobuf/io/zero_copy_stream_impl.h>
6 #include <google/protobuf/text_format.h>
11 #include "audio_mixer.h"
13 #include "shared/text_proto.h"
16 using namespace google::protobuf;
18 string spec_to_string(DeviceSpec device_spec)
22 switch (device_spec.type) {
23 case InputSourceType::SILENCE:
25 case InputSourceType::CAPTURE_CARD:
26 snprintf(buf, sizeof(buf), "Capture card %u", device_spec.index);
28 case InputSourceType::ALSA_INPUT:
29 snprintf(buf, sizeof(buf), "ALSA input %u", device_spec.index);
31 case InputSourceType::FFMPEG_VIDEO_INPUT:
32 snprintf(buf, sizeof(buf), "FFmpeg input %u", device_spec.index);
39 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
41 InputMappingProto mapping_proto;
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);
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);
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]);
64 return save_proto_to_file(mapping_proto, filename);
67 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
69 InputMappingProto mapping_proto;
70 if (!load_proto_from_file(filename, &mapping_proto)) {
74 // Map devices in the proto to our current ones:
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);
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});
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.
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));
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;
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;
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;
127 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
131 case DeviceSpecProto::ALSA_INPUT: {
132 // For ALSA, we don't really care about index, but we can use address
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;
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;
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.
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);
184 new_mapping->extra_delay_ms.emplace(device_mapping.back(), device_proto.extra_delay_ms());
187 for (const BusProto &bus_proto : mapping_proto.bus()) {
188 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
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);