blob: 9a747e88877fa8b52b0e38f17aedb06c9f8f044c [file] [log] [blame]
// Copyright 2017 The Chromium OS 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 "midis/seq_handler.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <poll.h>
#include "media/midi/message_util.h"
#include "midis/constants.h"
namespace {
const unsigned int kCreateInputPortCaps =
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_NO_EXPORT;
const unsigned int kCreateOutputPortCaps =
SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_NO_EXPORT;
const unsigned int kCreatePortType =
SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION;
const char kSndSeqName[] = "hw";
} // namespace
namespace midis {
SeqHandler::SeqHandler() : weak_factory_(this) {}
SeqHandler::SeqHandler(AddDeviceCallback add_device_cb,
RemoveDeviceCallback remove_device_cb,
HandleReceiveDataCallback handle_rx_data_cb,
IsDevicePresentCallback is_device_present_cb,
IsPortPresentCallback is_port_present_cb)
: add_device_cb_(add_device_cb),
remove_device_cb_(remove_device_cb),
handle_rx_data_cb_(handle_rx_data_cb),
is_device_present_cb_(is_device_present_cb),
is_port_present_cb_(is_port_present_cb),
weak_factory_(this) {}
bool SeqHandler::InitSeq() {
// Create client handles.
snd_seq_t* tmp_seq = nullptr;
int err =
snd_seq_open(&tmp_seq, kSndSeqName, SND_SEQ_OPEN_INPUT, SND_SEQ_NONBLOCK);
if (err != 0) {
LOG(ERROR) << "snd_seq_open fails: " << snd_strerror(err);
return false;
}
ScopedSeqPtr in_client(tmp_seq);
tmp_seq = nullptr;
in_client_id_ = snd_seq_client_id(in_client.get());
err = snd_seq_open(&tmp_seq, kSndSeqName, SND_SEQ_OPEN_OUTPUT, 0);
if (err != 0) {
LOG(ERROR) << "snd_seq_open fails: " << snd_strerror(err);
return false;
}
ScopedSeqPtr out_client(tmp_seq);
tmp_seq = nullptr;
out_client_id_ = snd_seq_client_id(out_client.get());
// Name the clients.
err = snd_seq_set_client_name(in_client.get(), "midis (input)");
if (err != 0) {
LOG(ERROR) << "snd_seq_set_client_name fails: " << snd_strerror(err);
return false;
}
err = snd_seq_set_client_name(out_client.get(), "midis (output)");
if (err != 0) {
LOG(ERROR) << "snd_seq_set_client_name fails: " << snd_strerror(err);
return false;
}
// Create input port.
in_port_id_ = snd_seq_create_simple_port(
in_client.get(), NULL, kCreateInputPortCaps, kCreatePortType);
if (in_port_id_ < 0) {
LOG(ERROR) << "snd_seq_create_simple_port fails: "
<< snd_strerror(in_port_id_);
return false;
}
// 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_id_;
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.get(), subs);
if (err != 0) {
LOG(ERROR) << "snd_seq_subscribe_port on the announce port fails: "
<< snd_strerror(err);
return false;
}
in_client_ = std::move(in_client);
out_client_ = std::move(out_client);
// Initialize decoder.
decoder_ = CreateMidiEvent(0);
EnumerateExistingDevices();
// Obtain the poll file descriptor to watch.
pfd_ = std::make_unique<pollfd>();
snd_seq_poll_descriptors(in_client_.get(), pfd_.get(), 1, POLLIN);
watcher_ = base::FileDescriptorWatcher::WatchReadable(
pfd_->fd,
base::BindRepeating(&SeqHandler::ProcessAlsaClientFd,
weak_factory_.GetWeakPtr()));
if (!watcher_) {
in_client_.reset();
out_client_.reset();
decoder_.reset();
pfd_.reset();
return false;
}
return true;
}
void SeqHandler::ProcessAlsaClientFd() {
int remaining;
do {
snd_seq_event_t* event;
int err = SndSeqEventInput(in_client_.get(), &event);
remaining = SndSeqEventInputPending(in_client_.get(), 0);
if (err == -ENOSPC) {
// Handle out of space error.
LOG(ERROR) << "snd_seq_event_input detected buffer overrun";
// We've lost events: check another way to see if we need to shut
// down.
} else if (err == -EAGAIN) {
// We've read all the data.
} else if (err < 0) {
// Handle other errors.
LOG(ERROR) << "snd_seq_event_input fails: " << snd_strerror(err);
// TODO(pmalani): Stop the message loop here then.
} else if (event->source.client == SND_SEQ_CLIENT_SYSTEM &&
event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE) {
// Handle announce events.
switch (event->type) {
case SND_SEQ_EVENT_PORT_START:
// Don't use SND_SEQ_EVENT_CLIENT_START because the
// client name may not be set by the time we query
// it. It should be set by the time ports are made.
AddSeqDevice(event->data.addr.client);
AddSeqPort(event->data.addr.client, event->data.addr.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_) {
// TODO(pmalani): Stop the message loop here then.
remaining = 0;
} else {
RemoveSeqDevice(event->data.addr.client);
}
break;
case SND_SEQ_EVENT_PORT_EXIT:
RemoveSeqPort(event->data.addr.client, event->data.addr.port);
break;
}
} else {
// Normal operation.
ProcessMidiEvent(event);
}
} while (remaining > 0);
}
void SeqHandler::AddSeqDevice(uint32_t device_id) {
if (is_device_present_cb_.Run(0 /* TODO(pmalani): Remove card number */,
device_id)) {
LOG(INFO) << "Device: " << device_id << " already exists.";
return;
}
// Check that the device isn't our own in/our client.
if (device_id == in_client_id_ || device_id == out_client_id_) {
return;
}
snd_seq_client_info_t* client_info;
snd_seq_client_info_alloca(&client_info);
int err =
snd_seq_get_any_client_info(in_client_.get(), device_id, client_info);
if (err != 0) {
LOG(ERROR) << "Failed to get client info.";
return;
}
std::string name(snd_seq_client_info_get_name(client_info));
// Store the list of MIDI ports and corresponding capabilities in a map.
std::map<uint32_t, unsigned int> port_caps;
snd_seq_port_info_t* port_info;
snd_seq_port_info_alloca(&port_info);
snd_seq_port_info_set_client(port_info, device_id);
snd_seq_port_info_set_port(port_info, -1);
while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
if (!(snd_seq_port_info_get_type(port_info) &
SND_SEQ_PORT_TYPE_MIDI_GENERIC)) {
LOG(INFO) << "Skipping non-MIDI port.";
continue;
}
port_caps.emplace(snd_seq_port_info_get_port(port_info),
snd_seq_port_info_get_capability(port_info));
}
// If the number of MIDI ports is 0, there is no use in creating
// a device.
if (port_caps.size() == 0) {
LOG(INFO) << "Connected device: " << name << " has no MIDI ports.";
return;
}
auto dev = std::make_unique<Device>(
name, std::string(),
0 /* card number; TODO(pmalani) remove card number */, device_id,
port_caps.size(), 0 /* device flags TODO(pmalani): flags not needed. */,
base::Bind(&SeqHandler::SubscribeInPort, base::Unretained(this)),
base::Bind(&SeqHandler::SubscribeOutPort, base::Unretained(this)),
base::Bind(&SeqHandler::UnsubscribeInPort, weak_factory_.GetWeakPtr()),
base::Bind(&SeqHandler::UnsubscribeOutPort, weak_factory_.GetWeakPtr()),
base::Bind(&SeqHandler::SendMidiData, weak_factory_.GetWeakPtr()),
std::move(port_caps));
add_device_cb_.Run(std::move(dev));
}
void SeqHandler::AddSeqPort(uint32_t device_id, uint32_t port_id) {
if (!is_port_present_cb_.Run(0, device_id, port_id)) {
LOG(WARNING) << "Received port start event for new port: " << port_id
<< " on device: " << device_id << "; ignoring";
}
}
void SeqHandler::RemoveSeqDevice(uint32_t device_id) {
remove_device_cb_.Run(0 /* FIXME remove card number */, device_id);
}
void SeqHandler::RemoveSeqPort(uint32_t device_id, uint32_t port_id) {
if (!is_port_present_cb_.Run(0, device_id, port_id)) {
LOG(WARNING) << "Received port start event for new port: " << port_id
<< " on device: " << device_id << "; ignoring";
}
}
bool SeqHandler::SubscribeInPort(uint32_t device_id, uint32_t port_id) {
snd_seq_port_subscribe_t* subs;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_addr_t sender;
sender.client = device_id;
sender.port = port_id;
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_addr_t dest;
dest.client = in_client_id_;
dest.port = in_port_id_;
snd_seq_port_subscribe_set_dest(subs, &dest);
int err = snd_seq_subscribe_port(in_client_.get(), subs);
if (err != 0) {
LOG(ERROR) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
return false;
}
return true;
}
int SeqHandler::SubscribeOutPort(uint32_t device_id, uint32_t port_id) {
int out_port;
out_port = snd_seq_create_simple_port(out_client_.get(), NULL,
kCreateOutputPortCaps, kCreatePortType);
if (out_port < 0) {
LOG(INFO) << "snd_seq_creat_simple_port (output) failed: "
<< snd_strerror(out_port);
return -1;
}
snd_seq_port_subscribe_t* subs;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_addr_t sender;
sender.client = out_client_id_;
sender.port = out_port;
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_addr_t dest;
dest.client = device_id;
dest.port = port_id;
snd_seq_port_subscribe_set_dest(subs, &dest);
int err = snd_seq_subscribe_port(out_client_.get(), subs);
if (err != 0) {
snd_seq_delete_simple_port(out_client_.get(), out_port);
LOG(ERROR) << "snd_seq_subscribe_port fails: " << snd_strerror(err);
return -1;
}
return out_port;
}
void SeqHandler::UnsubscribeInPort(uint32_t device_id, uint32_t port_id) {
snd_seq_port_subscribe_t* subs;
snd_seq_port_subscribe_alloca(&subs);
snd_seq_addr_t sender;
sender.client = device_id;
sender.port = port_id;
snd_seq_port_subscribe_set_sender(subs, &sender);
snd_seq_addr_t dest;
dest.client = in_client_id_;
dest.port = in_port_id_;
snd_seq_port_subscribe_set_dest(subs, &dest);
int err = snd_seq_unsubscribe_port(in_client_.get(), subs);
if (err != 0) {
LOG(WARNING) << "snd_seq_unsubscribe_port fails: " << snd_strerror(err);
return;
}
}
void SeqHandler::UnsubscribeOutPort(int out_port_id) {
snd_seq_delete_simple_port(out_client_.get(), out_port_id);
}
bool SeqHandler::EncodeMidiBytes(int out_port_id,
snd_seq_t* out_client,
const uint8_t* buffer,
size_t buf_len,
snd_midi_event_t* encoder) {
if (buf_len == 0 || buf_len > kMaxBufSize) {
return false;
}
for (int i = 0; i < buf_len; i++) {
snd_seq_event_t event;
int result = snd_midi_event_encode_byte(encoder, buffer[i], &event);
if (result < 0) {
LOG(ERROR) << "Error snd_midi_event_encode_byte(): " << result;
return false;
}
if (result == 1) {
// Send the message.
snd_seq_ev_set_source(&event, out_port_id);
snd_seq_ev_set_subs(&event);
snd_seq_ev_set_direct(&event);
int expected_length = snd_seq_event_length(&event);
result = SndSeqEventOutputDirect(out_client, &event);
if (result != expected_length) {
LOG(WARNING) << "Error in snd_seq_event_output_direct(): " << result;
return false;
}
return true;
}
}
// If we reached here, something went wrong.
return false;
}
void SeqHandler::SendMidiData(int out_port_id,
const uint8_t* buffer,
size_t buf_len) {
std::vector<uint8_t> v(buffer, buffer + buf_len);
if (!midi::IsValidWebMIDIData(v)) {
LOG(WARNING) << "Received invalid MIDI Data.";
return;
}
snd_midi_event_t* encoder;
int ret = snd_midi_event_new(buf_len, &encoder);
if (ret != 0) {
LOG(ERROR) << "Error snd_midi_event_new(): " << ret;
return;
}
bool success =
EncodeMidiBytes(out_port_id, out_client_.get(), buffer, buf_len, encoder);
if (!success) {
LOG(WARNING) << "Failed to send MIDI data to output port: " << out_port_id;
}
snd_midi_event_free(encoder);
}
void SeqHandler::ProcessMidiEvent(snd_seq_event_t* event) {
uint32_t device_id = event->source.client;
uint32_t subdevice_num = event->source.port;
if (event->type == SND_SEQ_EVENT_SYSEX) {
// SysEX, so pass it through without decoding.
handle_rx_data_cb_.Run(0, device_id, subdevice_num,
static_cast<char*>(event->data.ext.ptr),
event->data.ext.len);
} else {
// Normal message, so decode and send.
unsigned char buf[12];
int64_t count =
snd_midi_event_decode(decoder_.get(), buf, sizeof(buf), event);
if (count <= 0) {
if (count != -ENOENT) {
LOG(ERROR) << "snd_midi_event_decoder failed: " << snd_strerror(count);
}
} else {
handle_rx_data_cb_.Run(0, device_id, subdevice_num,
reinterpret_cast<char*>(buf), count);
}
}
}
int SeqHandler::SndSeqEventOutputDirect(snd_seq_t* out_client,
snd_seq_event_t* event) {
return snd_seq_event_output_direct(out_client, event);
}
int SeqHandler::SndSeqEventInput(snd_seq_t* in_client, snd_seq_event_t** ev) {
return snd_seq_event_input(in_client, ev);
}
int SeqHandler::SndSeqEventInputPending(snd_seq_t* in_client,
int fetch_sequencer) {
return snd_seq_event_input_pending(in_client, fetch_sequencer);
}
void SeqHandler::EnumerateExistingDevices() {
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);
snd_seq_client_info_set_client(client_info, -1);
while (!snd_seq_query_next_client(in_client_.get(), client_info)) {
int device_id = snd_seq_client_info_get_client(client_info);
AddSeqDevice(device_id);
// Call AddSeqPort to make sure we "process" all the ports of a client.
// Note that currently we don't support the dynamic addition / deletion
// of ports.
snd_seq_port_info_set_client(port_info, device_id);
snd_seq_port_info_set_port(port_info, -1);
while (!snd_seq_query_next_port(in_client_.get(), port_info)) {
int port_id = snd_seq_port_info_get_port(port_info);
AddSeqPort(device_id, port_id);
}
}
}
SeqHandler::ScopedMidiEventPtr SeqHandler::CreateMidiEvent(size_t buf_size) {
snd_midi_event_t* tmp = nullptr;
snd_midi_event_new(buf_size, &tmp);
ScopedMidiEventPtr ev(tmp);
tmp = nullptr;
snd_midi_event_no_status(ev.get(), 1);
return ev;
}
} // namespace midis