Index: media/midi/midi_manager_alsa.cc |
diff --git a/media/midi/midi_manager_alsa.cc b/media/midi/midi_manager_alsa.cc |
index ea111e30c504f27505436da966f5b557b98e7277..de633e68bdd25bc0a576a4b211788fa7f32a9b36 100644 |
--- a/media/midi/midi_manager_alsa.cc |
+++ b/media/midi/midi_manager_alsa.cc |
@@ -37,6 +37,14 @@ namespace { |
// realtime messages with respect to sysex. |
const size_t kSendBufferSize = 256; |
+// Minimum client id for which we will have ALSA card devices for. When we |
+// are searching for card devices (used to get the path, id, and manufacturer), |
+// we don't want to get confused by kernel clients that do not have a card. |
+// See seq_clientmgr.c in the ALSA code for this. |
+// TODO(agoode): Add proper client -> card export from the kernel to avoid |
+// hardcoding. |
+const int kMinimumClientIdForCards = 16; |
+ |
// ALSA constants. |
const char kAlsaHw[] = "hw"; |
@@ -47,6 +55,23 @@ const char kUdevPropertySoundInitialized[] = "SOUND_INITIALIZED"; |
const char kUdevActionChange[] = "change"; |
const char kUdevActionRemove[] = "remove"; |
+const char kUdevIdVendor[] = "ID_VENDOR"; |
+const char kUdevIdVendorEnc[] = "ID_VENDOR_ENC"; |
+const char kUdevIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE"; |
+const char kUdevIdVendorId[] = "ID_VENDOR_ID"; |
+const char kUdevIdModelId[] = "ID_MODEL_ID"; |
+const char kUdevIdBus[] = "ID_BUS"; |
+const char kUdevIdPath[] = "ID_PATH"; |
+const char kUdevIdUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM"; |
+const char kUdevIdSerialShort[] = "ID_SERIAL_SHORT"; |
+ |
+const char kSysattrVendorName[] = "vendor_name"; |
+const char kSysattrVendor[] = "vendor"; |
+const char kSysattrModel[] = "model"; |
+const char kSysattrGuid[] = "guid"; |
+ |
+const char kCardSyspath[] = "/card"; |
+ |
// Constants for the capabilities we search for in inputs and outputs. |
// See http://www.alsa-project.org/alsa-doc/alsa-lib/seq.html. |
const unsigned int kRequiredInputPortCaps = |
@@ -65,6 +90,45 @@ int AddrToInt(int client, int port) { |
return (client << 8) | port; |
} |
+// Returns true if this client has an ALSA card associated with it. |
+bool IsCardClient(snd_seq_client_type_t type, int client_id) { |
+ return (type == SND_SEQ_KERNEL_CLIENT) && |
+ (client_id >= kMinimumClientIdForCards); |
+} |
+ |
+// TODO(agoode): Move this to device/udev_linux. |
+const std::string UdevDeviceGetPropertyOrSysattr( |
+ struct udev_device* udev_device, |
+ const char* property_key, |
+ const char* sysattr_key) { |
+ // First try the property. |
+ std::string value = |
+ device::UdevDeviceGetPropertyValue(udev_device, property_key); |
+ |
+ // If no property, look for sysattrs and walk up the parent devices too. |
+ while (value.empty() && udev_device) { |
+ value = device::UdevDeviceGetSysattrValue(udev_device, sysattr_key); |
+ udev_device = device::udev_device_get_parent(udev_device); |
+ } |
+ return value; |
+} |
+ |
+int GetCardNumber(udev_device* dev) { |
+ const char* syspath = device::udev_device_get_syspath(dev); |
+ if (!syspath) |
+ return -1; |
+ |
+ std::string syspath_str(syspath); |
+ size_t i = syspath_str.rfind(kCardSyspath); |
+ if (i == std::string::npos) |
+ return -1; |
+ |
+ int number; |
+ if (!base::StringToInt(syspath_str.substr(i + strlen(kCardSyspath)), &number)) |
+ return -1; |
+ return number; |
+} |
+ |
void SetStringIfNonEmpty(base::DictionaryValue* value, |
const std::string& path, |
const std::string& in_value) { |
@@ -79,6 +143,8 @@ MidiManagerAlsa::MidiManagerAlsa() |
out_client_(NULL), |
out_client_id_(-1), |
in_port_id_(-1), |
+ alsa_cards_deleter_(&alsa_cards_), |
+ alsa_card_midi_count_(0), |
decoder_(NULL), |
udev_(device::udev_new()), |
send_thread_("MidiSendThread"), |
@@ -495,7 +561,8 @@ uint32 MidiManagerAlsa::MidiPortState::Insert(scoped_ptr<MidiPort> port) { |
return web_port_index; |
} |
-MidiManagerAlsa::AlsaSeqState::AlsaSeqState() : clients_deleter_(&clients_) { |
+MidiManagerAlsa::AlsaSeqState::AlsaSeqState() |
+ : clients_deleter_(&clients_), card_client_count_(0) { |
} |
MidiManagerAlsa::AlsaSeqState::~AlsaSeqState() { |
@@ -506,6 +573,8 @@ void MidiManagerAlsa::AlsaSeqState::ClientStart(int client_id, |
snd_seq_client_type_t type) { |
ClientExit(client_id); |
clients_[client_id] = new Client(client_name, type); |
+ if (IsCardClient(type, client_id)) |
+ ++card_client_count_; |
} |
bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) { |
@@ -515,6 +584,8 @@ bool MidiManagerAlsa::AlsaSeqState::ClientStarted(int client_id) { |
void MidiManagerAlsa::AlsaSeqState::ClientExit(int client_id) { |
auto it = clients_.find(client_id); |
if (it != clients_.end()) { |
+ if (IsCardClient(it->second->type(), client_id)) |
+ --card_client_count_; |
delete it->second; |
clients_.erase(it); |
} |
@@ -547,11 +618,14 @@ snd_seq_client_type_t MidiManagerAlsa::AlsaSeqState::ClientType( |
} |
scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> |
-MidiManagerAlsa::AlsaSeqState::ToMidiPortState() { |
+MidiManagerAlsa::AlsaSeqState::ToMidiPortState(const AlsaCardMap& alsa_cards) { |
scoped_ptr<MidiManagerAlsa::TemporaryMidiPortState> midi_ports( |
new TemporaryMidiPortState); |
- // TODO(agoode): Use information from udev as well. |
+ // TODO(agoode): Use more information from udev, to allow hardware matching. |
+ // See http://crbug.com/486471. |
+ auto card_it = alsa_cards.begin(); |
+ int card_midi_device = -1; |
for (const auto& client_pair : clients_) { |
int client_id = client_pair.first; |
const auto& client = client_pair.second; |
@@ -567,6 +641,21 @@ MidiManagerAlsa::AlsaSeqState::ToMidiPortState() { |
std::string card_longname; |
int midi_device = -1; |
+ if (IsCardClient(client->type(), client_id)) { |
+ auto& card = card_it->second; |
+ if (card_midi_device == -1) |
+ card_midi_device = 0; |
+ |
+ manufacturer = card->manufacturer(); |
+ midi_device = card_midi_device; |
+ |
+ ++card_midi_device; |
+ if (card_midi_device >= card->midi_device_count()) { |
+ card_midi_device = -1; |
+ ++card_it; |
+ } |
+ } |
+ |
for (const auto& port_pair : *client) { |
int port_id = port_pair.first; |
const auto& port = port_pair.second; |
@@ -662,8 +751,53 @@ MidiManagerAlsa::AlsaSeqState::Client::end() const { |
return ports_.end(); |
} |
+MidiManagerAlsa::AlsaCard::AlsaCard(udev_device* dev, |
+ const std::string& alsa_name, |
+ const std::string& alsa_longname, |
+ const std::string& alsa_driver, |
+ int midi_device_count) |
+ : alsa_name_(alsa_name), |
+ alsa_longname_(alsa_longname), |
+ alsa_driver_(alsa_driver), |
+ midi_device_count_(midi_device_count) { |
+ // Try to get the vendor string. Sometimes it is encoded. |
+ std::string vendor = device::UdevDecodeString( |
+ device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorEnc)); |
+ // Sometimes it is not encoded. |
+ if (vendor.empty()) |
+ vendor = |
+ UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendor, kSysattrVendorName); |
+ // Also get the vendor string from the hardware database. |
+ std::string vendor_from_database = |
+ device::UdevDeviceGetPropertyValue(dev, kUdevIdVendorFromDatabase); |
+ |
+ // Get the device path. |
+ path_ = device::UdevDeviceGetPropertyValue(dev, kUdevIdPath); |
+ // Get the bus. |
+ bus_ = device::UdevDeviceGetPropertyValue(dev, kUdevIdBus); |
+ |
+ // Get the "serial" number. (Often untrustable or missing.) |
+ serial_ = |
+ UdevDeviceGetPropertyOrSysattr(dev, kUdevIdSerialShort, kSysattrGuid); |
+ |
+ // Get the vendor id, by either property or sysattr. |
+ vendor_id_ = |
+ UdevDeviceGetPropertyOrSysattr(dev, kUdevIdVendorId, kSysattrVendor); |
+ // Get the model id, by either property or sysattr. |
+ model_id_ = |
+ UdevDeviceGetPropertyOrSysattr(dev, kUdevIdModelId, kSysattrModel); |
+ // Get the usb interface number. |
+ usb_interface_num_ = |
+ device::UdevDeviceGetPropertyValue(dev, kUdevIdUsbInterfaceNum); |
+ manufacturer_ = ExtractManufacturerString( |
+ vendor, vendor_id_, vendor_from_database, alsa_name, alsa_longname); |
+} |
+ |
+MidiManagerAlsa::AlsaCard::~AlsaCard() { |
+} |
+ |
// static |
-std::string MidiManagerAlsa::ExtractManufacturerString( |
+std::string MidiManagerAlsa::AlsaCard::ExtractManufacturerString( |
const std::string& udev_id_vendor, |
const std::string& udev_id_vendor_id, |
const std::string& udev_id_vendor_from_database, |
@@ -923,15 +1057,108 @@ void MidiManagerAlsa::ProcessUdevEvent(udev_device* dev) { |
action = kUdevActionChange; |
if (strcmp(action, kUdevActionChange) == 0) { |
- // TODO(agoode): add |
+ AddCard(dev); |
+ // Generate Web MIDI events. |
+ UpdatePortStateAndGenerateEvents(); |
} else if (strcmp(action, kUdevActionRemove) == 0) { |
- // TODO(agoode): remove |
+ RemoveCard(GetCardNumber(dev)); |
+ // Generate Web MIDI events. |
+ UpdatePortStateAndGenerateEvents(); |
} |
} |
+void MidiManagerAlsa::AddCard(udev_device* dev) { |
+ int number = GetCardNumber(dev); |
+ if (number == -1) |
+ return; |
+ |
+ RemoveCard(number); |
+ |
+ snd_ctl_card_info_t* card; |
+ snd_hwdep_info_t* hwdep; |
+ snd_ctl_card_info_alloca(&card); |
+ snd_hwdep_info_alloca(&hwdep); |
+ const std::string id = base::StringPrintf("hw:CARD=%i", number); |
+ snd_ctl_t* handle; |
+ int err = snd_ctl_open(&handle, id.c_str(), 0); |
+ if (err != 0) { |
+ VLOG(1) << "snd_ctl_open fails: " << snd_strerror(err); |
+ return; |
+ } |
+ err = snd_ctl_card_info(handle, card); |
+ if (err != 0) { |
+ VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err); |
+ snd_ctl_close(handle); |
+ return; |
+ } |
+ std::string name = snd_ctl_card_info_get_name(card); |
+ std::string longname = snd_ctl_card_info_get_longname(card); |
+ std::string driver = snd_ctl_card_info_get_driver(card); |
+ |
+ // Count rawmidi devices (not subdevices). |
+ int midi_count = 0; |
+ for (int device = -1; |
+ !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0;) |
+ ++midi_count; |
+ |
+ // Count any hwdep synths that become MIDI devices outside of rawmidi. |
+ // |
+ // Explanation: |
+ // Any kernel driver can create an ALSA client (visible to us). |
+ // With modern hardware, only rawmidi devices do this. Kernel |
+ // drivers create rawmidi devices and the rawmidi subsystem makes |
+ // the seq clients. But the OPL3 driver is special, it does not |
+ // make a rawmidi device but a seq client directly. (This is the |
+ // only one to worry about in the kernel code, as of 2015-03-23.) |
+ // |
+ // OPL3 is very old (but still possible to get in new |
+ // hardware). It is unlikely that new drivers would not use |
+ // rawmidi and defeat our heuristic. |
+ // |
+ // Longer term, support should be added in the kernel to expose a |
+ // direct link from card->client (or client->card) so that all |
+ // these heuristics will be obsolete. Once that is there, we can |
+ // assume our old heuristics will work on old kernels and the new |
+ // robust code will be used on new. Then we will not need to worry |
+ // about changes to kernel internals breaking our code. |
+ // See the TODO above at kMinimumClientIdForCards. |
+ for (int device = -1; |
+ !snd_ctl_hwdep_next_device(handle, &device) && device >= 0;) { |
+ err = snd_ctl_hwdep_info(handle, hwdep); |
+ if (err != 0) { |
+ VLOG(1) << "snd_ctl_hwdep_info fails: " << snd_strerror(err); |
+ continue; |
+ } |
+ snd_hwdep_iface_t iface = snd_hwdep_info_get_iface(hwdep); |
+ if (iface == SND_HWDEP_IFACE_OPL2 || iface == SND_HWDEP_IFACE_OPL3 || |
+ iface == SND_HWDEP_IFACE_OPL4) |
+ ++midi_count; |
+ } |
+ snd_ctl_close(handle); |
+ |
+ if (midi_count > 0) |
+ alsa_cards_[number] = new AlsaCard(dev, name, longname, driver, midi_count); |
+ alsa_card_midi_count_ += midi_count; |
+} |
+ |
+void MidiManagerAlsa::RemoveCard(int number) { |
+ auto it = alsa_cards_.find(number); |
+ if (it == alsa_cards_.end()) |
+ return; |
+ |
+ alsa_card_midi_count_ -= it->second->midi_device_count(); |
+ delete it->second; |
+ alsa_cards_.erase(it); |
+} |
+ |
void MidiManagerAlsa::UpdatePortStateAndGenerateEvents() { |
+ // Verify that our information from ALSA and udev are in sync. If |
+ // not, we cannot generate events right now. |
+ if (alsa_card_midi_count_ != alsa_seq_state_.card_client_count()) |
+ return; |
+ |
// Generate new port state. |
- auto new_port_state = alsa_seq_state_.ToMidiPortState(); |
+ auto new_port_state = alsa_seq_state_.ToMidiPortState(alsa_cards_); |
// Disconnect any connected old ports that are now missing. |
for (auto* old_port : port_state_) { |