5 #include <google/protobuf/text_format.h>
6 #include <google/protobuf/io/zero_copy_stream.h>
7 #include <google/protobuf/io/zero_copy_stream_impl.h>
9 #include "audio_mixer.h"
10 #include "input_mapping.h"
14 using namespace google::protobuf;
16 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
18 InputMappingProto mapping_proto;
20 map<DeviceSpec, unsigned> used_devices;
21 for (const InputMapping::Bus &bus : input_mapping.buses) {
22 if (!used_devices.count(bus.device)) {
23 used_devices.emplace(bus.device, used_devices.size());
24 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
27 BusProto *bus_proto = mapping_proto.add_bus();
28 bus_proto->set_name(bus.name);
29 bus_proto->set_device_index(used_devices[bus.device]);
30 bus_proto->set_source_channel_left(bus.source_channel[0]);
31 bus_proto->set_source_channel_right(bus.source_channel[1]);
35 // Save to disk. We use the text format because it's friendlier
36 // for a user to look at and edit.
37 int fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
39 perror(filename.c_str());
42 io::FileOutputStream output(fd); // Takes ownership of fd.
43 if (!TextFormat::Print(mapping_proto, &output)) {
44 // TODO: Don't overwrite the old file (if any) on error.
53 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
55 // Read and parse the protobuf from disk.
56 int fd = open(filename.c_str(), O_RDONLY);
58 perror(filename.c_str());
61 io::FileInputStream input(fd); // Takes ownership of fd.
62 InputMappingProto mapping_proto;
63 if (!TextFormat::Parse(&input, &mapping_proto)) {
69 // Map devices in the proto to our current ones:
71 // Get a list of all active devices.
72 set<DeviceSpec> remaining_devices;
73 for (const auto &device_spec_and_info : devices) {
74 remaining_devices.insert(device_spec_and_info.first);
77 // Now look at every device in the serialized protobuf and try to map
78 // it to one device we haven't taken yet. This isn't a full maximal matching,
79 // but it's good enough for our uses.
80 vector<DeviceSpec> device_mapping;
81 for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
82 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
83 switch (device_proto.type()) {
84 case DeviceSpecProto::SILENCE:
85 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
87 case DeviceSpecProto::CAPTURE_CARD: {
88 // First see if there's a card that matches on both index and name.
89 DeviceSpec spec{InputSourceType::CAPTURE_CARD, unsigned(device_proto.index())};
90 assert(devices.count(spec));
91 const DeviceInfo &dev = devices.find(spec)->second;
92 if (remaining_devices.count(spec) &&
93 dev.display_name == device_proto.display_name()) {
94 device_mapping.push_back(spec);
95 remaining_devices.erase(spec);
96 goto found_capture_card;
99 // Scan and see if there's a match on name alone.
100 for (const DeviceSpec &spec : remaining_devices) {
101 if (spec.type == InputSourceType::CAPTURE_CARD &&
102 dev.display_name == device_proto.display_name()) {
103 device_mapping.push_back(spec);
104 remaining_devices.erase(spec);
105 goto found_capture_card;
109 // OK, see if at least the index is free.
110 if (remaining_devices.count(spec)) {
111 device_mapping.push_back(spec);
112 remaining_devices.erase(spec);
113 goto found_capture_card;
117 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
121 case DeviceSpecProto::ALSA_INPUT: {
122 // For ALSA, we don't really care about index, but we can use address
125 // First see if there's a card that matches on name, num_channels and address.
126 for (const DeviceSpec &spec : remaining_devices) {
127 assert(devices.count(spec));
128 const DeviceInfo &dev = devices.find(spec)->second;
129 if (spec.type == InputSourceType::ALSA_INPUT &&
130 dev.alsa_name == device_proto.alsa_name() &&
131 dev.alsa_info == device_proto.alsa_info() &&
132 int(dev.num_channels) == device_proto.num_channels() &&
133 dev.alsa_address == device_proto.address()) {
134 device_mapping.push_back(spec);
135 remaining_devices.erase(spec);
136 goto found_alsa_input;
140 // Looser check: Ignore the address.
141 for (const DeviceSpec &spec : remaining_devices) {
142 assert(devices.count(spec));
143 const DeviceInfo &dev = devices.find(spec)->second;
144 if (spec.type == InputSourceType::ALSA_INPUT &&
145 dev.alsa_name == device_proto.alsa_name() &&
146 dev.alsa_info == device_proto.alsa_info() &&
147 int(dev.num_channels) == device_proto.num_channels()) {
148 device_mapping.push_back(spec);
149 remaining_devices.erase(spec);
150 goto found_alsa_input;
154 // OK, so we couldn't map this to a device, but perhaps one is added
155 // at some point in the future through hotplug. Create a dead card
156 // matching this one; right now, it will give only silence,
157 // but it could be replaced with something later.
159 // NOTE: There's a potential race condition here, if the card
160 // gets inserted while we're doing the device remapping
161 // (or perhaps more realistically, while we're reading the
162 // input mapping from disk).
163 DeviceSpec dead_card_spec;
164 dead_card_spec = global_audio_mixer->create_dead_card(
165 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
166 device_mapping.push_back(dead_card_spec);
176 for (const BusProto &bus_proto : mapping_proto.bus()) {
177 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
180 InputMapping::Bus bus;
181 bus.name = bus_proto.name();
182 bus.device = device_mapping[bus_proto.device_index()];
183 bus.source_channel[0] = bus_proto.source_channel_left();
184 bus.source_channel[1] = bus_proto.source_channel_right();
185 new_mapping->buses.push_back(bus);