blob: 89b0da8cb815aa605df8863cfb60a709cb6a7baa [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "media/midi/midi_manager_alsa.h"
#include <alsa/asoundlib.h>
#include <stdlib.h>
#include <algorithm>
#include <string>
#include "base/bind.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/values.h"
#include "crypto/sha2.h"
#include "media/midi/midi_port_info.h"
namespace media {
namespace {
// Per-output buffer. This can be smaller, but then large sysex messages
// will be (harmlessly) split across multiple seq events. This should
// not have any real practical effect, except perhaps to slightly reorder
// 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;
// udev key constants.
#if defined(USE_UDEV)
const char kSoundClass[] = "sound";
const char kIdVendor[] = "ID_VENDOR";
const char kIdVendorEnc[] = "ID_VENDOR_ENC";
const char kIdVendorFromDatabase[] = "ID_VENDOR_FROM_DATABASE";
const char kSysattrVendorName[] = "vendor_name";
const char kIdVendorId[] = "ID_VENDOR_ID";
const char kSysattrVendor[] = "vendor";
const char kIdModelId[] = "ID_MODEL_ID";
const char kSysattrModel[] = "model";
const char kIdBus[] = "ID_BUS";
const char kIdPath[] = "ID_PATH";
const char kUsbInterfaceNum[] = "ID_USB_INTERFACE_NUM";
#endif // defined(USE_UDEV)
// ALSA constants.
const char kAlsaHw[] = "hw";
// 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 =
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
const unsigned int kRequiredOutputPortCaps =
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
int AddrToInt(const snd_seq_addr_t* addr) {
return (addr->client << 8) | addr->port;
}
// TODO(agoode): Move this to device/udev_linux.
#if defined(USE_UDEV)
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;
}
#endif // defined(USE_UDEV)
void SetStringIfNonEmpty(base::DictionaryValue* value,
const std::string& path,
const std::string& in_value) {
if (!in_value.empty())
value->SetString(path, in_value);
}
} // namespace
MidiManagerAlsa::MidiManagerAlsa()
: in_client_(NULL),
out_client_(NULL),
out_client_id_(-1),
in_port_(-1),
decoder_(NULL),
#if defined(USE_UDEV)
udev_(device::udev_new()),
#endif // defined(USE_UDEV)
send_thread_("MidiSendThread"),
event_thread_("MidiEventThread"),
event_thread_shutdown_(false) {
// Initialize decoder.
snd_midi_event_new(0, &decoder_);
snd_midi_event_no_status(decoder_, 1);
}
MidiManagerAlsa::~MidiManagerAlsa() {
// Tell the event thread it will soon be time to shut down. This gives
// us assurance the thread will stop in case the SND_SEQ_EVENT_CLIENT_EXIT
// message is lost.
{
base::AutoLock lock(shutdown_lock_);
event_thread_shutdown_ = true;
}
// Stop the send thread.
send_thread_.Stop();
// Close the out client. This will trigger the event thread to stop,
// because of SND_SEQ_EVENT_CLIENT_EXIT.
if (out_client_)
snd_seq_close(out_client_);
// Wait for the event thread to stop.
event_thread_.Stop();
// Close the in client.
if (in_client_)
snd_seq_close(in_client_);
// Free the decoder.
snd_midi_event_free(decoder_);
}
void MidiManagerAlsa::StartInitialization() {
// TODO(agoode): Move off I/O thread. See http://crbug.com/374341.
// Create client handles.
int err = snd_seq_open(&in_client_, kAlsaHw, SND_SEQ_OPEN_INPUT, 0);
if (err != 0) {
VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
int in_client_id = snd_seq_client_id(in_client_);
err = snd_seq_open(&out_client_, kAlsaHw, SND_SEQ_OPEN_OUTPUT, 0);
if (err != 0) {
VLOG(1) << "snd_seq_open fails: " << snd_strerror(err);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
out_client_id_ = snd_seq_client_id(out_client_);
// Name the clients.
err = snd_seq_set_client_name(in_client_, "Chrome (input)");
if (err != 0) {
VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
err = snd_seq_set_client_name(out_client_, "Chrome (output)");
if (err != 0) {
VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
// Create input port.
in_port_ = snd_seq_create_simple_port(in_client_, NULL,
SND_SEQ_PORT_CAP_WRITE |
SND_SEQ_PORT_CAP_NO_EXPORT,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
if (in_port_ < 0) {
VLOG(1) << "snd_seq_create_simple_port fails: " << snd_strerror(in_port_);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
// Subscribe to the announce port.
snd_seq_port_subscribe_t* subs;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_addr_t announce_sender;
snd_seq_addr_t announce_dest;
announce_sender.client = SND_SEQ_CLIENT_SYSTEM;
announce_sender.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
announce_dest.client = in_client_id;
announce_dest.port = in_port_;
snd_seq_port_subscribe_set_sender(subs, &announce_sender);
snd_seq_port_subscribe_set_dest(subs, &announce_dest);
err = snd_seq_subscribe_port(in_client_, subs);
if (err != 0) {
VLOG(1) << "snd_seq_subscribe_port on the announce port fails: "
<< snd_strerror(err);
return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
}
EnumeratePorts();
event_thread_.Start();
event_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&MidiManagerAlsa::ScheduleEventLoop, base::Unretained(this)));
CompleteInitialization(MIDI_OK);
}
void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
uint32 port_index,
const std::vector<uint8>& data,
double timestamp) {
if (out_ports_.size() <= port_index)
return;
// Not correct right now. http://crbug.com/374341.
if (!send_thread_.IsRunning())
send_thread_.Start();
base::TimeDelta delay;
if (timestamp != 0.0) {
base::TimeTicks time_to_send =
base::TimeTicks() + base::TimeDelta::FromMicroseconds(
timestamp * base::Time::kMicrosecondsPerSecond);
delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
}
send_thread_.message_loop()->PostDelayedTask(
FROM_HERE, base::Bind(&MidiManagerAlsa::SendMidiData,
base::Unretained(this), port_index, data),
delay);
// Acknowledge send.
send_thread_.message_loop()->PostTask(
FROM_HERE, base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
base::Unretained(client), data.size()));
}
MidiManagerAlsa::AlsaCard::AlsaCard(const MidiManagerAlsa* outer,
const std::string& alsa_name,
const std::string& alsa_longname,
const std::string& alsa_driver,
int card_index,
int midi_count)
: alsa_name_(alsa_name),
alsa_longname_(alsa_longname),
alsa_driver_(alsa_driver),
midi_count_(midi_count) {
// Get udev properties if available.
std::string vendor;
std::string vendor_from_database;
#if defined(USE_UDEV)
const std::string sysname = base::StringPrintf("card%i", card_index);
device::ScopedUdevDevicePtr udev_device(
device::udev_device_new_from_subsystem_sysname(
outer->udev_.get(), kSoundClass, sysname.c_str()));
// TODO(agoode): Move this to a new utility class in device/udev_linux?
// Try to get the vendor string. Sometimes it is encoded.
vendor = device::UdevDecodeString(
device::UdevDeviceGetPropertyValue(udev_device.get(), kIdVendorEnc));
// Sometimes it is not encoded.
if (vendor.empty())
vendor = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdVendor,
kSysattrVendorName);
// Also get the vendor string from the hardware database.
vendor_from_database = device::UdevDeviceGetPropertyValue(
udev_device.get(), kIdVendorFromDatabase);
// Get the device path.
path_ = device::UdevDeviceGetPropertyValue(udev_device.get(), kIdPath);
// Get the bus.
bus_ = device::UdevDeviceGetPropertyValue(udev_device.get(), kIdBus);
// Get the vendor id, by either property or sysattr.
vendor_id_ = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdVendorId,
kSysattrVendor);
// Get the model id, by either property or sysattr.
model_id_ = UdevDeviceGetPropertyOrSysattr(udev_device.get(), kIdModelId,
kSysattrModel);
// Get the usb interface number.
usb_interface_num_ =
device::UdevDeviceGetPropertyValue(udev_device.get(), kUsbInterfaceNum);
#endif // defined(USE_UDEV)
manufacturer_ = ExtractManufacturerString(
vendor, vendor_id_, vendor_from_database, alsa_name, alsa_longname);
}
MidiManagerAlsa::AlsaCard::~AlsaCard() {
}
const std::string MidiManagerAlsa::AlsaCard::alsa_name() const {
return alsa_name_;
}
const std::string MidiManagerAlsa::AlsaCard::alsa_longname() const {
return alsa_longname_;
}
const std::string MidiManagerAlsa::AlsaCard::manufacturer() const {
return manufacturer_;
}
const std::string MidiManagerAlsa::AlsaCard::alsa_driver() const {
return alsa_driver_;
}
const std::string MidiManagerAlsa::AlsaCard::path() const {
return path_;
}
const std::string MidiManagerAlsa::AlsaCard::bus() const {
return bus_;
}
const std::string MidiManagerAlsa::AlsaCard::vendor_id() const {
return vendor_id_;
}
const std::string MidiManagerAlsa::AlsaCard::id() const {
std::string id = vendor_id_;
if (!model_id_.empty())
id += ":" + model_id_;
if (!usb_interface_num_.empty())
id += ":" + usb_interface_num_;
return id;
}
const int MidiManagerAlsa::AlsaCard::midi_count() const {
return midi_count_;
}
// static
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,
const std::string& alsa_name,
const std::string& alsa_longname) {
// Let's try to determine the manufacturer. Here is the ordered preference
// in extraction:
// 1. Vendor name from the hardware device string, from udev properties
// or sysattrs.
// 2. Vendor name from the udev database (property ID_VENDOR_FROM_DATABASE).
// 3. Heuristic from ALSA.
// Is the vendor string present and not just the vendor hex id?
if (!udev_id_vendor.empty() && (udev_id_vendor != udev_id_vendor_id)) {
return udev_id_vendor;
}
// Is there a vendor string in the hardware database?
if (!udev_id_vendor_from_database.empty()) {
return udev_id_vendor_from_database;
}
// Ok, udev gave us nothing useful, or was unavailable. So try a heuristic.
// We assume that card longname is in the format of
// "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
// a manufacturer name here.
size_t at_index = alsa_longname.rfind(" at ");
if (at_index && at_index != std::string::npos) {
size_t name_index = alsa_longname.rfind(alsa_name, at_index - 1);
if (name_index && name_index != std::string::npos)
return alsa_longname.substr(0, name_index - 1);
}
// Failure.
return "";
}
MidiManagerAlsa::AlsaPortMetadata::AlsaPortMetadata(
const std::string& path,
const std::string& bus,
const std::string& id,
const snd_seq_addr_t* address,
const std::string& client_name,
const std::string& port_name,
const std::string& card_name,
const std::string& card_longname,
Type type)
: path_(path),
bus_(bus),
id_(id),
client_addr_(address->client),
port_addr_(address->port),
client_name_(client_name),
port_name_(port_name),
card_name_(card_name),
card_longname_(card_longname),
type_(type) {
}
MidiManagerAlsa::AlsaPortMetadata::~AlsaPortMetadata() {
}
scoped_ptr<base::Value> MidiManagerAlsa::AlsaPortMetadata::Value() const {
scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue);
SetStringIfNonEmpty(value.get(), "path", path_);
SetStringIfNonEmpty(value.get(), "bus", bus_);
SetStringIfNonEmpty(value.get(), "id", id_);
value->SetInteger("clientAddr", client_addr_);
value->SetInteger("portAddr", port_addr_);
SetStringIfNonEmpty(value.get(), "clientName", client_name_);
SetStringIfNonEmpty(value.get(), "portName", port_name_);
SetStringIfNonEmpty(value.get(), "cardName", card_name_);
SetStringIfNonEmpty(value.get(), "cardLongname", card_longname_);
std::string type;
switch (type_) {
case Type::kInput:
type = "input";
break;
case Type::kOutput:
type = "output";
break;
}
SetStringIfNonEmpty(value.get(), "type", type);
return value.Pass();
}
std::string MidiManagerAlsa::AlsaPortMetadata::JSONValue() const {
std::string json;
JSONStringValueSerializer serializer(&json);
serializer.Serialize(*Value().get());
return json;
}
// TODO(agoode): Do not use SHA256 here. Instead store a persistent
// mapping and just use a UUID or other random string.
// http://crbug.com/465320
std::string MidiManagerAlsa::AlsaPortMetadata::OpaqueKey() const {
uint8 hash[crypto::kSHA256Length];
crypto::SHA256HashString(JSONValue(), &hash, sizeof(hash));
return base::HexEncode(&hash, sizeof(hash));
}
// TODO(agoode): Add a client->card/rawmidi mapping to the kernel to avoid
// needing to probe in this way.
ScopedVector<MidiManagerAlsa::AlsaCard> MidiManagerAlsa::AllMidiCards() {
ScopedVector<AlsaCard> devices;
snd_ctl_card_info_t* card;
snd_hwdep_info_t* hwdep;
snd_ctl_card_info_alloca(&card);
snd_hwdep_info_alloca(&hwdep);
for (int card_index = -1; !snd_card_next(&card_index) && card_index >= 0;) {
int midi_count = 0;
const std::string id = base::StringPrintf("hw:CARD=%i", card_index);
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);
continue;
}
err = snd_ctl_card_info(handle, card);
if (err != 0) {
VLOG(1) << "snd_ctl_card_info fails: " << snd_strerror(err);
snd_ctl_close(handle);
continue;
}
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).
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;) {
snd_ctl_hwdep_info(handle, hwdep);
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;
}
if (midi_count > 0)
devices.push_back(
new AlsaCard(this, name, longname, driver, card_index, midi_count));
snd_ctl_close(handle);
}
return devices.Pass();
}
void MidiManagerAlsa::EnumeratePorts() {
ScopedVector<AlsaCard> cards = AllMidiCards();
std::vector<const AlsaCard*> devices;
for (const auto* card : cards) {
// Insert 1 AlsaCard per number of MIDI devices.
for (int n = 0; n < card->midi_count(); ++n)
devices.push_back(card);
}
snd_seq_port_subscribe_t* subs;
snd_seq_port_subscribe_alloca(&subs);
int in_client_id = snd_seq_client_id(in_client_);
// Enumerate all clients.
snd_seq_client_info_t* client_info;
snd_seq_client_info_alloca(&client_info);
snd_seq_port_info_t* port_info;
snd_seq_port_info_alloca(&port_info);
// Enumerate clients.
snd_seq_client_info_set_client(client_info, -1);
uint32 current_input = 0;
unsigned int current_device = 0;
while (!snd_seq_query_next_client(in_client_, client_info)) {
int client_id = snd_seq_client_info_get_client(client_info);
if ((client_id == in_client_id) || (client_id == out_client_id_)) {
// Skip our own clients.
continue;
}
// Get client metadata.
const std::string client_name = snd_seq_client_info_get_name(client_info);
snd_seq_port_info_set_client(port_info, client_id);
snd_seq_port_info_set_port(port_info, -1);
std::string manufacturer;
std::string driver;
std::string path;
std::string bus;
std::string vendor_id;
std::string id;
std::string card_name;
std::string card_longname;
// Join kernel clients against the list of AlsaCards.
// In the current ALSA kernel implementation, kernel clients match the
// kernel devices in the same order, for clients with client_id over
// kMinimumClientIdForCards.
if ((snd_seq_client_info_get_type(client_info) == SND_SEQ_KERNEL_CLIENT) &&
(current_device < devices.size()) &&
(client_id >= kMinimumClientIdForCards)) {
const AlsaCard* device = devices[current_device];
manufacturer = device->manufacturer();
driver = device->alsa_driver();
path = device->path();
bus = device->bus();
vendor_id = device->vendor_id();
id = device->id();
card_name = device->alsa_name();
card_longname = device->alsa_longname();
current_device++;
}
// Enumerate ports.
while (!snd_seq_query_next_port(in_client_, port_info)) {
unsigned int port_type = snd_seq_port_info_get_type(port_info);
if (port_type & SND_SEQ_PORT_TYPE_MIDI_GENERIC) {
const snd_seq_addr_t* addr = snd_seq_port_info_get_addr(port_info);
const std::string name = snd_seq_port_info_get_name(port_info);
std::string version;
if (!driver.empty()) {
version = driver + " / ";
}
version += base::StringPrintf("ALSA library version %d.%d.%d",
SND_LIB_MAJOR,
SND_LIB_MINOR,
SND_LIB_SUBMINOR);
unsigned int caps = snd_seq_port_info_get_capability(port_info);
if ((caps & kRequiredInputPortCaps) == kRequiredInputPortCaps) {
// Subscribe to this port.
const snd_seq_addr_t* sender = snd_seq_port_info_get_addr(port_info);
snd_seq_addr_t dest;
dest.client = snd_seq_client_id(in_client_);
dest.port = in_port_;
snd_seq_port_subscribe_set_sender(subs, sender);
snd_seq_port_subscribe_set_dest(subs, &dest);
int err = snd_seq_subscribe_port(in_client_, subs);
if (err != 0) {
VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
} else {
source_map_[AddrToInt(sender)] = current_input++;
const AlsaPortMetadata metadata(path, bus, id, addr, client_name,
name, card_name, card_longname,
AlsaPortMetadata::Type::kInput);
const std::string id = metadata.OpaqueKey();
AddInputPort(MidiPortInfo(id.c_str(), manufacturer, name, version,
MIDI_PORT_OPENED));
}
}
if ((caps & kRequiredOutputPortCaps) == kRequiredOutputPortCaps) {
// Create a port for us to send on.
int out_port =
snd_seq_create_simple_port(out_client_, NULL,
SND_SEQ_PORT_CAP_READ |
SND_SEQ_PORT_CAP_NO_EXPORT,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
if (out_port < 0) {
VLOG(1) << "snd_seq_create_simple_port fails: "
<< snd_strerror(out_port);
// Skip this output port for now.
continue;
}
// Activate port subscription.
snd_seq_addr_t sender;
const snd_seq_addr_t* dest = snd_seq_port_info_get_addr(port_info);
sender.client = snd_seq_client_id(out_client_);
sender.port = out_port;
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_port_subscribe_set_dest(subs, dest);
int err = snd_seq_subscribe_port(out_client_, subs);
if (err != 0) {
VLOG(1) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
snd_seq_delete_simple_port(out_client_, out_port);
} else {
out_ports_.push_back(out_port);
const AlsaPortMetadata metadata(path, bus, id, addr, client_name,
name, card_name, card_longname,
AlsaPortMetadata::Type::kOutput);
const std::string id = metadata.OpaqueKey();
AddOutputPort(MidiPortInfo(id.c_str(), manufacturer, name, version,
MIDI_PORT_OPENED));
}
}
}
}
}
}
void MidiManagerAlsa::SendMidiData(uint32 port_index,
const std::vector<uint8>& data) {
DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread());
snd_midi_event_t* encoder;
snd_midi_event_new(kSendBufferSize, &encoder);
for (unsigned int i = 0; i < data.size(); i++) {
snd_seq_event_t event;
int result = snd_midi_event_encode_byte(encoder, data[i], &event);
if (result == 1) {
// Full event, send it.
snd_seq_ev_set_source(&event, out_ports_[port_index]);
snd_seq_ev_set_subs(&event);
snd_seq_ev_set_direct(&event);
snd_seq_event_output_direct(out_client_, &event);
}
}
snd_midi_event_free(encoder);
}
void MidiManagerAlsa::ScheduleEventLoop() {
event_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
}
void MidiManagerAlsa::EventLoop() {
// Read available incoming MIDI data.
snd_seq_event_t* event;
int err = snd_seq_event_input(in_client_, &event);
double timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
// Handle errors.
if (err == -ENOSPC) {
VLOG(1) << "snd_seq_event_input detected buffer overrun";
// We've lost events: check another way to see if we need to shut down.
base::AutoLock lock(shutdown_lock_);
if (!event_thread_shutdown_)
ScheduleEventLoop();
return;
} else if (err < 0) {
VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err);
// TODO(agoode): Use RecordAction() or similar to log this.
return;
}
// Handle announce events.
if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
switch (event->type) {
case SND_SEQ_EVENT_CLIENT_START:
// TODO(agoode): rescan hardware devices.
break;
case SND_SEQ_EVENT_PORT_START:
// TODO(agoode): add port.
break;
case SND_SEQ_EVENT_CLIENT_EXIT:
// Check for disconnection of our "out" client. This means "shut down".
if (event->data.addr.client == out_client_id_)
return;
// TODO(agoode): remove all ports for a client.
break;
case SND_SEQ_EVENT_PORT_EXIT:
// TODO(agoode): remove port.
break;
}
} else {
ProcessSingleEvent(event, timestamp);
}
// Do again.
ScheduleEventLoop();
}
void MidiManagerAlsa::ProcessSingleEvent(snd_seq_event_t* event,
double timestamp) {
auto source_it = source_map_.find(AddrToInt(&event->source));
if (source_it != source_map_.end()) {
uint32 source = source_it->second;
if (event->type == SND_SEQ_EVENT_SYSEX) {
// Special! Variable-length sysex.
ReceiveMidiData(source, static_cast<const uint8*>(event->data.ext.ptr),
event->data.ext.len, timestamp);
} else {
// Otherwise, decode this and send that on.
unsigned char buf[12];
long count = snd_midi_event_decode(decoder_, buf, sizeof(buf), event);
if (count <= 0) {
if (count != -ENOENT) {
// ENOENT means that it's not a MIDI message, which is not an
// error, but other negative values are errors for us.
VLOG(1) << "snd_midi_event_decoder fails " << snd_strerror(count);
// TODO(agoode): Record this failure.
}
} else {
ReceiveMidiData(source, buf, count, timestamp);
}
}
}
}
MidiManager* MidiManager::Create() {
return new MidiManagerAlsa();
}
} // namespace media