1 #include "input_mapping.h"
10 #include "audio_mixer.h"
12 #include "shared/text_proto.h"
15 using namespace google::protobuf;
17 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
19 InputMappingProto mapping_proto;
21 map<DeviceSpec, unsigned> used_devices;
22 for (const InputMapping::Bus &bus : input_mapping.buses) {
23 if (!used_devices.count(bus.device)) {
24 used_devices.emplace(bus.device, used_devices.size());
25 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
28 BusProto *bus_proto = mapping_proto.add_bus();
29 bus_proto->set_name(bus.name);
30 bus_proto->set_device_index(used_devices[bus.device]);
31 bus_proto->set_source_channel_left(bus.source_channel[0]);
32 bus_proto->set_source_channel_right(bus.source_channel[1]);
36 return save_proto_to_file(mapping_proto, filename);
39 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
41 InputMappingProto mapping_proto;
42 if (!load_proto_from_file(filename, &mapping_proto)) {
46 // Map devices in the proto to our current ones:
48 // Get a list of all active devices.
49 set<DeviceSpec> remaining_devices;
50 for (const auto &device_spec_and_info : devices) {
51 remaining_devices.insert(device_spec_and_info.first);
54 // Now look at every device in the serialized protobuf and try to map
55 // it to one device we haven't taken yet. This isn't a full maximal matching,
56 // but it's good enough for our uses.
57 vector<DeviceSpec> device_mapping;
58 for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
59 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
60 switch (device_proto.type()) {
61 case DeviceSpecProto::SILENCE:
62 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
64 case DeviceSpecProto::CAPTURE_CARD: {
65 // First see if there's a card that matches on both index and name.
67 spec.type = InputSourceType::CAPTURE_CARD;
68 spec.index = unsigned(device_proto.index());
69 assert(devices.count(spec));
71 const DeviceInfo &dev = devices.find(spec)->second;
72 if (remaining_devices.count(spec) &&
73 dev.display_name == device_proto.display_name()) {
74 device_mapping.push_back(spec);
75 remaining_devices.erase(spec);
76 goto found_capture_card;
79 // Scan and see if there's a match on name alone.
80 for (const DeviceSpec &spec : remaining_devices) {
81 if (spec.type == InputSourceType::CAPTURE_CARD &&
82 dev.display_name == device_proto.display_name()) {
83 device_mapping.push_back(spec);
84 remaining_devices.erase(spec);
85 goto found_capture_card;
89 // OK, see if at least the index is free.
90 if (remaining_devices.count(spec)) {
91 device_mapping.push_back(spec);
92 remaining_devices.erase(spec);
93 goto found_capture_card;
97 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
101 case DeviceSpecProto::ALSA_INPUT: {
102 // For ALSA, we don't really care about index, but we can use address
105 // First see if there's a card that matches on name, num_channels and address.
106 for (const DeviceSpec &spec : remaining_devices) {
107 assert(devices.count(spec));
108 const DeviceInfo &dev = devices.find(spec)->second;
109 if (spec.type == InputSourceType::ALSA_INPUT &&
110 dev.alsa_name == device_proto.alsa_name() &&
111 dev.alsa_info == device_proto.alsa_info() &&
112 int(dev.num_channels) == device_proto.num_channels() &&
113 dev.alsa_address == device_proto.address()) {
114 device_mapping.push_back(spec);
115 remaining_devices.erase(spec);
116 goto found_alsa_input;
120 // Looser check: Ignore the address.
121 for (const DeviceSpec &spec : remaining_devices) {
122 assert(devices.count(spec));
123 const DeviceInfo &dev = devices.find(spec)->second;
124 if (spec.type == InputSourceType::ALSA_INPUT &&
125 dev.alsa_name == device_proto.alsa_name() &&
126 dev.alsa_info == device_proto.alsa_info() &&
127 int(dev.num_channels) == device_proto.num_channels()) {
128 device_mapping.push_back(spec);
129 remaining_devices.erase(spec);
130 goto found_alsa_input;
134 // OK, so we couldn't map this to a device, but perhaps one is added
135 // at some point in the future through hotplug. Create a dead card
136 // matching this one; right now, it will give only silence,
137 // but it could be replaced with something later.
139 // NOTE: There's a potential race condition here, if the card
140 // gets inserted while we're doing the device remapping
141 // (or perhaps more realistically, while we're reading the
142 // input mapping from disk).
143 DeviceSpec dead_card_spec;
144 dead_card_spec = global_audio_mixer->create_dead_card(
145 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
146 device_mapping.push_back(dead_card_spec);
156 for (const BusProto &bus_proto : mapping_proto.bus()) {
157 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
160 InputMapping::Bus bus;
161 bus.name = bus_proto.name();
162 bus.device = device_mapping[bus_proto.device_index()];
163 bus.source_channel[0] = bus_proto.source_channel_left();
164 bus.source_channel[1] = bus_proto.source_channel_right();
165 new_mapping->buses.push_back(bus);