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);
30 case InputSourceType::FFMPEG_VIDEO_INPUT:
31 snprintf(buf, sizeof(buf), "FFmpeg input %u", device_spec.index);
38 bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
40 InputMappingProto mapping_proto;
42 map<DeviceSpec, unsigned> used_devices;
43 for (const InputMapping::Bus &bus : input_mapping.buses) {
44 if (!used_devices.count(bus.device)) {
45 used_devices.emplace(bus.device, used_devices.size());
46 global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
49 BusProto *bus_proto = mapping_proto.add_bus();
50 bus_proto->set_name(bus.name);
51 bus_proto->set_device_index(used_devices[bus.device]);
52 bus_proto->set_source_channel_left(bus.source_channel[0]);
53 bus_proto->set_source_channel_right(bus.source_channel[1]);
57 // Save to disk. We use the text format because it's friendlier
58 // for a user to look at and edit.
59 int fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
61 perror(filename.c_str());
64 io::FileOutputStream output(fd); // Takes ownership of fd.
65 if (!TextFormat::Print(mapping_proto, &output)) {
66 // TODO: Don't overwrite the old file (if any) on error.
75 bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
77 // Read and parse the protobuf from disk.
78 int fd = open(filename.c_str(), O_RDONLY);
80 perror(filename.c_str());
83 io::FileInputStream input(fd); // Takes ownership of fd.
84 InputMappingProto mapping_proto;
85 if (!TextFormat::Parse(&input, &mapping_proto)) {
91 // Map devices in the proto to our current ones:
93 // Get a list of all active devices.
94 set<DeviceSpec> remaining_devices;
95 for (const auto &device_spec_and_info : devices) {
96 remaining_devices.insert(device_spec_and_info.first);
99 // Now look at every device in the serialized protobuf and try to map
100 // it to one device we haven't taken yet. This isn't a full maximal matching,
101 // but it's good enough for our uses.
102 vector<DeviceSpec> device_mapping;
103 for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
104 const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
105 switch (device_proto.type()) {
106 case DeviceSpecProto::SILENCE:
107 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
109 case DeviceSpecProto::FFMPEG_VIDEO_INPUT:
110 case DeviceSpecProto::CAPTURE_CARD: {
111 // First see if there's a card that matches on both index and name.
113 spec.type = (device_proto.type() == DeviceSpecProto::CAPTURE_CARD) ?
114 InputSourceType::CAPTURE_CARD : InputSourceType::FFMPEG_VIDEO_INPUT;
115 spec.index = unsigned(device_proto.index());
116 assert(devices.count(spec));
118 const DeviceInfo &dev = devices.find(spec)->second;
119 if (remaining_devices.count(spec) &&
120 dev.display_name == device_proto.display_name()) {
121 device_mapping.push_back(spec);
122 remaining_devices.erase(spec);
123 goto found_capture_card;
126 // Scan and see if there's a match on name alone.
127 for (const DeviceSpec &spec : remaining_devices) {
128 if (spec.type == InputSourceType::CAPTURE_CARD &&
129 dev.display_name == device_proto.display_name()) {
130 device_mapping.push_back(spec);
131 remaining_devices.erase(spec);
132 goto found_capture_card;
136 // OK, see if at least the index is free.
137 if (remaining_devices.count(spec)) {
138 device_mapping.push_back(spec);
139 remaining_devices.erase(spec);
140 goto found_capture_card;
144 device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
148 case DeviceSpecProto::ALSA_INPUT: {
149 // For ALSA, we don't really care about index, but we can use address
152 // First see if there's a card that matches on name, num_channels and address.
153 for (const DeviceSpec &spec : remaining_devices) {
154 assert(devices.count(spec));
155 const DeviceInfo &dev = devices.find(spec)->second;
156 if (spec.type == InputSourceType::ALSA_INPUT &&
157 dev.alsa_name == device_proto.alsa_name() &&
158 dev.alsa_info == device_proto.alsa_info() &&
159 int(dev.num_channels) == device_proto.num_channels() &&
160 dev.alsa_address == device_proto.address()) {
161 device_mapping.push_back(spec);
162 remaining_devices.erase(spec);
163 goto found_alsa_input;
167 // Looser check: Ignore the address.
168 for (const DeviceSpec &spec : remaining_devices) {
169 assert(devices.count(spec));
170 const DeviceInfo &dev = devices.find(spec)->second;
171 if (spec.type == InputSourceType::ALSA_INPUT &&
172 dev.alsa_name == device_proto.alsa_name() &&
173 dev.alsa_info == device_proto.alsa_info() &&
174 int(dev.num_channels) == device_proto.num_channels()) {
175 device_mapping.push_back(spec);
176 remaining_devices.erase(spec);
177 goto found_alsa_input;
181 // OK, so we couldn't map this to a device, but perhaps one is added
182 // at some point in the future through hotplug. Create a dead card
183 // matching this one; right now, it will give only silence,
184 // but it could be replaced with something later.
186 // NOTE: There's a potential race condition here, if the card
187 // gets inserted while we're doing the device remapping
188 // (or perhaps more realistically, while we're reading the
189 // input mapping from disk).
190 DeviceSpec dead_card_spec;
191 dead_card_spec = global_audio_mixer->create_dead_card(
192 device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
193 device_mapping.push_back(dead_card_spec);
203 for (const BusProto &bus_proto : mapping_proto.bus()) {
204 if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
207 InputMapping::Bus bus;
208 bus.name = bus_proto.name();
209 bus.device = device_mapping[bus_proto.device_index()];
210 bus.source_channel[0] = bus_proto.source_channel_left();
211 bus.source_channel[1] = bus_proto.source_channel_right();
212 new_mapping->buses.push_back(bus);