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"
15 using namespace google::protobuf;
17 string spec_to_string(DeviceSpec device_spec)
21 switch (device_spec.type) {
22 case InputSourceType::SILENCE:
24 case InputSourceType::CAPTURE_CARD:
25 snprintf(buf, sizeof(buf), "Capture card %u", device_spec.index);
27 case InputSourceType::ALSA_INPUT:
28 snprintf(buf, sizeof(buf), "ALSA input %u", device_spec.index);
35 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
37 InputMappingProto mapping_proto;
39 map<DeviceSpec, unsigned> used_devices;
40 for (const InputMapping::Bus &bus : input_mapping.buses) {
41 if (!used_devices.count(bus.device)) {
42 used_devices.emplace(bus.device, used_devices.size());
43 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
46 BusProto *bus_proto = mapping_proto.add_bus();
47 bus_proto->set_name(bus.name);
48 bus_proto->set_device_index(used_devices[bus.device]);
49 bus_proto->set_source_channel_left(bus.source_channel[0]);
50 bus_proto->set_source_channel_right(bus.source_channel[1]);
54 // Save to disk. We use the text format because it's friendlier
55 // for a user to look at and edit.
56 int fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
58 perror(filename.c_str());
61 io::FileOutputStream output(fd); // Takes ownership of fd.
62 if (!TextFormat::Print(mapping_proto, &output)) {
63 // TODO: Don't overwrite the old file (if any) on error.
72 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
74 // Read and parse the protobuf from disk.
75 int fd = open(filename.c_str(), O_RDONLY);
77 perror(filename.c_str());
80 io::FileInputStream input(fd); // Takes ownership of fd.
81 InputMappingProto mapping_proto;
82 if (!TextFormat::Parse(&input, &mapping_proto)) {
88 // Map devices in the proto to our current ones:
90 // Get a list of all active devices.
91 set<DeviceSpec> remaining_devices;
92 for (const auto &device_spec_and_info : devices) {
93 remaining_devices.insert(device_spec_and_info.first);
96 // Now look at every device in the serialized protobuf and try to map
97 // it to one device we haven't taken yet. This isn't a full maximal matching,
98 // but it's good enough for our uses.
99 vector<DeviceSpec> device_mapping;
100 for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
101 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
102 switch (device_proto.type()) {
103 case DeviceSpecProto::SILENCE:
104 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
106 case DeviceSpecProto::CAPTURE_CARD: {
107 // First see if there's a card that matches on both index and name.
108 DeviceSpec spec{InputSourceType::CAPTURE_CARD, unsigned(device_proto.index())};
109 assert(devices.count(spec));
110 const DeviceInfo &dev = devices.find(spec)->second;
111 if (remaining_devices.count(spec) &&
112 dev.display_name == device_proto.display_name()) {
113 device_mapping.push_back(spec);
114 remaining_devices.erase(spec);
115 goto found_capture_card;
118 // Scan and see if there's a match on name alone.
119 for (const DeviceSpec &spec : remaining_devices) {
120 if (spec.type == InputSourceType::CAPTURE_CARD &&
121 dev.display_name == device_proto.display_name()) {
122 device_mapping.push_back(spec);
123 remaining_devices.erase(spec);
124 goto found_capture_card;
128 // OK, see if at least the index is free.
129 if (remaining_devices.count(spec)) {
130 device_mapping.push_back(spec);
131 remaining_devices.erase(spec);
132 goto found_capture_card;
136 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
140 case DeviceSpecProto::ALSA_INPUT: {
141 // For ALSA, we don't really care about index, but we can use address
144 // First see if there's a card that matches on name, num_channels and address.
145 for (const DeviceSpec &spec : remaining_devices) {
146 assert(devices.count(spec));
147 const DeviceInfo &dev = devices.find(spec)->second;
148 if (spec.type == InputSourceType::ALSA_INPUT &&
149 dev.alsa_name == device_proto.alsa_name() &&
150 dev.alsa_info == device_proto.alsa_info() &&
151 int(dev.num_channels) == device_proto.num_channels() &&
152 dev.alsa_address == device_proto.address()) {
153 device_mapping.push_back(spec);
154 remaining_devices.erase(spec);
155 goto found_alsa_input;
159 // Looser check: Ignore the address.
160 for (const DeviceSpec &spec : remaining_devices) {
161 assert(devices.count(spec));
162 const DeviceInfo &dev = devices.find(spec)->second;
163 if (spec.type == InputSourceType::ALSA_INPUT &&
164 dev.alsa_name == device_proto.alsa_name() &&
165 dev.alsa_info == device_proto.alsa_info() &&
166 int(dev.num_channels) == device_proto.num_channels()) {
167 device_mapping.push_back(spec);
168 remaining_devices.erase(spec);
169 goto found_alsa_input;
173 // OK, so we couldn't map this to a device, but perhaps one is added
174 // at some point in the future through hotplug. Create a dead card
175 // matching this one; right now, it will give only silence,
176 // but it could be replaced with something later.
178 // NOTE: There's a potential race condition here, if the card
179 // gets inserted while we're doing the device remapping
180 // (or perhaps more realistically, while we're reading the
181 // input mapping from disk).
182 DeviceSpec dead_card_spec;
183 dead_card_spec = global_audio_mixer->create_dead_card(
184 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
185 device_mapping.push_back(dead_card_spec);
195 for (const BusProto &bus_proto : mapping_proto.bus()) {
196 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
199 InputMapping::Bus bus;
200 bus.name = bus_proto.name();
201 bus.device = device_mapping[bus_proto.device_index()];
202 bus.source_channel[0] = bus_proto.source_channel_left();
203 bus.source_channel[1] = bus_proto.source_channel_right();
204 new_mapping->buses.push_back(bus);