]> git.sesse.net Git - nageru/blobdiff - nageru/input_mapping.cpp
Move everything into a separate futatabi/ subdir, for the upcoming merge with Futatabi.
[nageru] / nageru / input_mapping.cpp
diff --git a/nageru/input_mapping.cpp b/nageru/input_mapping.cpp
new file mode 100644 (file)
index 0000000..45b6009
--- /dev/null
@@ -0,0 +1,216 @@
+#include "input_mapping.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+#include <google/protobuf/text_format.h>
+#include <stdio.h>
+#include <set>
+#include <utility>
+
+#include "audio_mixer.h" 
+#include "state.pb.h"
+
+using namespace std;
+using namespace google::protobuf;
+
+string spec_to_string(DeviceSpec device_spec)
+{
+       char buf[256];
+
+       switch (device_spec.type) {
+       case InputSourceType::SILENCE:
+               return "<silence>";
+       case InputSourceType::CAPTURE_CARD:
+               snprintf(buf, sizeof(buf), "Capture card %u", device_spec.index);
+               return buf;
+       case InputSourceType::ALSA_INPUT:
+               snprintf(buf, sizeof(buf), "ALSA input %u", device_spec.index);
+               return buf;
+       case InputSourceType::FFMPEG_VIDEO_INPUT:
+               snprintf(buf, sizeof(buf), "FFmpeg input %u", device_spec.index);
+               return buf;
+       default:
+               assert(false);
+       }
+}
+
+bool save_input_mapping_to_file(const map<DeviceSpec, DeviceInfo> &devices, const InputMapping &input_mapping, const string &filename)
+{
+       InputMappingProto mapping_proto;
+       {
+               map<DeviceSpec, unsigned> used_devices;
+               for (const InputMapping::Bus &bus : input_mapping.buses) {
+                       if (!used_devices.count(bus.device)) {
+                               used_devices.emplace(bus.device, used_devices.size());
+                               global_audio_mixer->serialize_device(bus.device, mapping_proto.add_device());
+                       }
+
+                       BusProto *bus_proto = mapping_proto.add_bus();
+                       bus_proto->set_name(bus.name);
+                       bus_proto->set_device_index(used_devices[bus.device]);
+                       bus_proto->set_source_channel_left(bus.source_channel[0]);
+                       bus_proto->set_source_channel_right(bus.source_channel[1]);
+               }
+       }
+
+       // Save to disk. We use the text format because it's friendlier
+       // for a user to look at and edit.
+       int fd = open(filename.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
+       if (fd == -1) {
+               perror(filename.c_str());
+               return false;
+       }
+       io::FileOutputStream output(fd);  // Takes ownership of fd.
+       if (!TextFormat::Print(mapping_proto, &output)) {
+               // TODO: Don't overwrite the old file (if any) on error.
+               output.Close();
+               return false;
+       }
+
+       output.Close();
+       return true;
+}
+
+bool load_input_mapping_from_file(const map<DeviceSpec, DeviceInfo> &devices, const string &filename, InputMapping *new_mapping)
+{
+       // Read and parse the protobuf from disk.
+       int fd = open(filename.c_str(), O_RDONLY);
+       if (fd == -1) {
+               perror(filename.c_str());
+               return false;
+       }
+       io::FileInputStream input(fd);  // Takes ownership of fd.
+       InputMappingProto mapping_proto;
+       if (!TextFormat::Parse(&input, &mapping_proto)) {
+               input.Close();
+               return false;
+       }
+       input.Close();
+
+       // Map devices in the proto to our current ones:
+
+       // Get a list of all active devices.
+       set<DeviceSpec> remaining_devices;
+       for (const auto &device_spec_and_info : devices) {
+               remaining_devices.insert(device_spec_and_info.first);
+       }
+
+       // Now look at every device in the serialized protobuf and try to map
+       // it to one device we haven't taken yet. This isn't a full maximal matching,
+       // but it's good enough for our uses.
+       vector<DeviceSpec> device_mapping;
+       for (unsigned device_index = 0; device_index < unsigned(mapping_proto.device_size()); ++device_index) {
+               const DeviceSpecProto &device_proto = mapping_proto.device(device_index);
+               switch (device_proto.type()) {
+               case DeviceSpecProto::SILENCE:
+                       device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
+                       break;
+               case DeviceSpecProto::FFMPEG_VIDEO_INPUT:
+               case DeviceSpecProto::CAPTURE_CARD: {
+                       // First see if there's a card that matches on both index and name.
+                       DeviceSpec spec;
+                       spec.type = (device_proto.type() == DeviceSpecProto::CAPTURE_CARD) ?
+                               InputSourceType::CAPTURE_CARD : InputSourceType::FFMPEG_VIDEO_INPUT;
+                       spec.index = unsigned(device_proto.index());
+                       assert(devices.count(spec));
+
+                       const DeviceInfo &dev = devices.find(spec)->second;
+                       if (remaining_devices.count(spec) &&
+                           dev.display_name == device_proto.display_name()) {
+                               device_mapping.push_back(spec);
+                               remaining_devices.erase(spec);
+                               goto found_capture_card;
+                       }
+
+                       // Scan and see if there's a match on name alone.
+                       for (const DeviceSpec &spec : remaining_devices) {
+                               if (spec.type == InputSourceType::CAPTURE_CARD &&
+                                   dev.display_name == device_proto.display_name()) {
+                                       device_mapping.push_back(spec);
+                                       remaining_devices.erase(spec);
+                                       goto found_capture_card;
+                               }
+                       }
+
+                       // OK, see if at least the index is free.
+                       if (remaining_devices.count(spec)) {
+                               device_mapping.push_back(spec);
+                               remaining_devices.erase(spec);
+                               goto found_capture_card;
+                       }
+
+                       // Give up.
+                       device_mapping.push_back(DeviceSpec{InputSourceType::SILENCE, 0});
+found_capture_card:
+                       break;
+               }
+               case DeviceSpecProto::ALSA_INPUT: {
+                       // For ALSA, we don't really care about index, but we can use address
+                       // in its place.
+
+                       // First see if there's a card that matches on name, num_channels and address.
+                       for (const DeviceSpec &spec : remaining_devices) {
+                               assert(devices.count(spec));
+                               const DeviceInfo &dev = devices.find(spec)->second;
+                               if (spec.type == InputSourceType::ALSA_INPUT &&
+                                   dev.alsa_name == device_proto.alsa_name() &&
+                                   dev.alsa_info == device_proto.alsa_info() &&
+                                   int(dev.num_channels) == device_proto.num_channels() &&
+                                   dev.alsa_address == device_proto.address()) {
+                                       device_mapping.push_back(spec);
+                                       remaining_devices.erase(spec);
+                                       goto found_alsa_input;
+                               }
+                       }
+
+                       // Looser check: Ignore the address.
+                       for (const DeviceSpec &spec : remaining_devices) {
+                               assert(devices.count(spec));
+                               const DeviceInfo &dev = devices.find(spec)->second;
+                               if (spec.type == InputSourceType::ALSA_INPUT &&
+                                   dev.alsa_name == device_proto.alsa_name() &&
+                                   dev.alsa_info == device_proto.alsa_info() &&
+                                   int(dev.num_channels) == device_proto.num_channels()) {
+                                       device_mapping.push_back(spec);
+                                       remaining_devices.erase(spec);
+                                       goto found_alsa_input;
+                               }
+                       }
+
+                       // OK, so we couldn't map this to a device, but perhaps one is added
+                       // at some point in the future through hotplug. Create a dead card
+                       // matching this one; right now, it will give only silence,
+                       // but it could be replaced with something later.
+                       //
+                       // NOTE: There's a potential race condition here, if the card
+                       // gets inserted while we're doing the device remapping
+                       // (or perhaps more realistically, while we're reading the
+                       // input mapping from disk).
+                       DeviceSpec dead_card_spec;
+                       dead_card_spec = global_audio_mixer->create_dead_card(
+                               device_proto.alsa_name(), device_proto.alsa_info(), device_proto.num_channels());
+                       device_mapping.push_back(dead_card_spec);
+
+found_alsa_input:
+                       break;
+               }
+               default:
+                       assert(false);
+               }
+       }
+
+       for (const BusProto &bus_proto : mapping_proto.bus()) {
+               if (bus_proto.device_index() < 0 || unsigned(bus_proto.device_index()) >= device_mapping.size()) {
+                       return false;
+               }
+               InputMapping::Bus bus;
+               bus.name = bus_proto.name();
+               bus.device = device_mapping[bus_proto.device_index()];
+               bus.source_channel[0] = bus_proto.source_channel_left();
+               bus.source_channel[1] = bus_proto.source_channel_right();
+               new_mapping->buses.push_back(bus);
+       }
+
+       return true;
+}