-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinput_mapping.cpp
190 lines (170 loc) · 6.29 KB
/
input_mapping.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#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;
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::CAPTURE_CARD: {
// First see if there's a card that matches on both index and name.
DeviceSpec spec{InputSourceType::CAPTURE_CARD, 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;
}