Index: media/midi/midi_manager_alsa.cc |
diff --git a/media/midi/midi_manager_alsa.cc b/media/midi/midi_manager_alsa.cc |
index c4be32ffae29428b918cdae2707d4e92c920675d..95c7e8ba70c5bbf3a041fdcb32fd799983507ee0 100644 |
--- a/media/midi/midi_manager_alsa.cc |
+++ b/media/midi/midi_manager_alsa.cc |
@@ -23,276 +23,321 @@ namespace media { |
namespace { |
-const size_t kReceiveBufferSize = 4096; |
-const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL; |
+const size_t kSendBufferSize = 256; |
Takashi Toyoshima
2014/05/07 13:06:13
If larger buffer size improves sysex performance,
Adam Goode
2014/05/08 03:00:54
I put a note here. I don't think a large buffer is
|
+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; |
+} |
} // namespace |
-class MidiManagerAlsa::MidiDeviceInfo |
- : public base::RefCounted<MidiDeviceInfo> { |
- public: |
- MidiDeviceInfo(MidiManagerAlsa* manager, |
- const std::string& bus_id, |
- snd_ctl_card_info_t* card, |
- const snd_rawmidi_info_t* midi, |
- int device) { |
- opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0); |
- if (!opened_) |
- return; |
- |
- const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device); |
- const std::string name = snd_rawmidi_info_get_name(midi); |
- // We assume that card longname is in the format of |
- // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect |
- // a manufacturer name here. |
- std::string manufacturer; |
- const std::string card_name = snd_ctl_card_info_get_longname(card); |
- size_t name_index = card_name.find(name); |
- if (std::string::npos != name_index) |
- manufacturer = card_name.substr(0, name_index - 1); |
- const std::string version = |
- base::StringPrintf("%s / ALSA library version %d.%d.%d", |
- snd_ctl_card_info_get_driver(card), |
- SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR); |
- port_info_ = MidiPortInfo(id, manufacturer, name, version); |
- } |
+MidiManagerAlsa::MidiManagerAlsa() |
+ : in_client_(NULL), |
+ out_client_(NULL), |
+ out_client_id_(-1), |
+ in_port_(-1), |
+ decoder_(NULL), |
+ 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); |
+} |
- void Send(MidiManagerClient* client, const std::vector<uint8>& data) { |
- ssize_t result = snd_rawmidi_write( |
- midi_out_, reinterpret_cast<const void*>(&data[0]), data.size()); |
- if (static_cast<size_t>(result) != data.size()) { |
- // TODO(toyoshim): Handle device disconnection. |
- VLOG(1) << "snd_rawmidi_write fails: " << strerror(-result); |
- } |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiManagerClient::AccumulateMidiBytesSent, |
- base::Unretained(client), data.size())); |
+void MidiManagerAlsa::StartInitialization() { |
+ // Create client handles. |
+ int err = snd_seq_open(&in_client_, "hw", SND_SEQ_OPEN_INPUT, 0); |
+ if (err != 0) { |
+ VLOG(1) << "snd_seq_open fails: " << snd_strerror(err); |
+ // Nothing to close, just return failure. |
+ return CompleteInitialization(MIDI_INITIALIZATION_ERROR); |
} |
- |
- // Read input data from a MIDI input device which is ready to read through |
- // the ALSA library. Called from EventLoop() and read data will be sent to |
- // blink through MIDIManager base class. |
- size_t Receive(uint8* data, size_t length) { |
- return snd_rawmidi_read(midi_in_, reinterpret_cast<void*>(data), length); |
+ int in_client_id = snd_seq_client_id(in_client_); |
+ err = snd_seq_open(&out_client_, "hw", 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_); |
- const MidiPortInfo& GetMidiPortInfo() const { return port_info_; } |
- |
- // Get the number of descriptors which is required to call poll() on the |
- // device. The ALSA library always returns 1 here now, but it may be changed |
- // in the future. |
- int GetPollDescriptorsCount() { |
- return snd_rawmidi_poll_descriptors_count(midi_in_); |
+ // Name the clients. |
+ err = snd_seq_set_client_name(in_client_, "Google Chrome (input)"); |
+ if (err != 0) { |
+ VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err); |
+ return CompleteInitialization(MIDI_INITIALIZATION_ERROR); |
} |
- |
- // Following API initializes pollfds for polling the device, and returns the |
- // number of descriptors they are initialized. It must be the same value with |
- // snd_rawmidi_poll_descriptors_count(). |
- int SetupPollDescriptors(struct pollfd* pfds, unsigned int count) { |
- return snd_rawmidi_poll_descriptors(midi_in_, pfds, count); |
+ err = snd_seq_set_client_name(out_client_, "Google Chrome (output)"); |
+ if (err != 0) { |
+ VLOG(1) << "snd_seq_set_client_name fails: " << snd_strerror(err); |
+ return CompleteInitialization(MIDI_INITIALIZATION_ERROR); |
} |
- unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) { |
- unsigned short revents; |
- snd_rawmidi_poll_descriptors_revents(midi_in_, |
- pfds, |
- GetPollDescriptorsCount(), |
- &revents); |
- return revents; |
+ // 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); |
} |
- bool IsOpened() const { return opened_; } |
- |
- private: |
- friend class base::RefCounted<MidiDeviceInfo>; |
- virtual ~MidiDeviceInfo() { |
- if (opened_) { |
- snd_rawmidi_close(midi_in_); |
- snd_rawmidi_close(midi_out_); |
- } |
+ // 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); |
} |
- bool opened_; |
- MidiPortInfo port_info_; |
- snd_rawmidi_t* midi_in_; |
- snd_rawmidi_t* midi_out_; |
- |
- DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo); |
-}; |
- |
-MidiManagerAlsa::MidiManagerAlsa() |
- : send_thread_("MidiSendThread"), |
- event_thread_("MidiEventThread") { |
- for (size_t i = 0; i < arraysize(pipe_fd_); ++i) |
- pipe_fd_[i] = -1; |
-} |
- |
-void MidiManagerAlsa::StartInitialization() { |
- // Enumerate only hardware MIDI devices because software MIDIs running in |
- // the browser process is not secure. |
- snd_ctl_card_info_t* card; |
- snd_rawmidi_info_t* midi_out; |
- snd_rawmidi_info_t* midi_in; |
- snd_ctl_card_info_alloca(&card); |
- snd_rawmidi_info_alloca(&midi_out); |
- snd_rawmidi_info_alloca(&midi_in); |
- for (int index = -1; !snd_card_next(&index) && index >= 0; ) { |
- const std::string id = base::StringPrintf("hw:CARD=%i", 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); |
+ // Enumerate all ports in 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); |
+ |
+ snd_seq_client_info_set_client(client_info, -1); |
+ // Enumerate clients. |
+ uint32 current_input = 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; |
} |
- for (int device = -1; |
- !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) { |
- bool output; |
- bool input; |
- snd_rawmidi_info_set_device(midi_out, device); |
- snd_rawmidi_info_set_subdevice(midi_out, 0); |
- snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT); |
- output = snd_ctl_rawmidi_info(handle, midi_out) == 0; |
- snd_rawmidi_info_set_device(midi_in, device); |
- snd_rawmidi_info_set_subdevice(midi_in, 0); |
- snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT); |
- input = snd_ctl_rawmidi_info(handle, midi_in) == 0; |
- if (!output && !input) |
- continue; |
- scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo( |
- this, id, card, output ? midi_out : midi_in, device); |
- if (!port->IsOpened()) { |
- VLOG(1) << "MidiDeviceInfo open fails"; |
- continue; |
- } |
- if (input) { |
- in_devices_.push_back(port); |
- AddInputPort(port->GetMidiPortInfo()); |
- } |
- if (output) { |
- out_devices_.push_back(port); |
- AddOutputPort(port->GetMidiPortInfo()); |
+ snd_seq_port_info_set_client(port_info, client_id); |
+ snd_seq_port_info_set_port(port_info, -1); |
+ // 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); |
+ const std::string id = base::StringPrintf("%d:%d %s", |
+ addr->client, |
+ addr->port, |
+ name.c_str()); |
+ |
+ 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); |
+ 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++; |
+ AddInputPort(MidiPortInfo(id, "", name, "")); |
+ } |
+ } |
+ 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); |
+ 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 { |
+ snd_midi_event_t* encoder; |
+ snd_midi_event_new(kSendBufferSize, &encoder); |
+ encoders_.push_back(encoder); |
+ out_ports_.push_back(out_port); |
+ AddOutputPort(MidiPortInfo(id, "", name, "")); |
+ } |
+ } |
} |
} |
- snd_ctl_close(handle); |
} |
- if (pipe(pipe_fd_) < 0) { |
- VPLOG(1) << "pipe() failed"; |
- CompleteInitialization(MIDI_INITIALIZATION_ERROR); |
- } else { |
- event_thread_.Start(); |
- event_thread_.message_loop()->PostTask( |
- FROM_HERE, |
- base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this))); |
- CompleteInitialization(MIDI_OK); |
- } |
+ event_thread_.Start(); |
+ event_thread_.message_loop()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this))); |
+ |
+ CompleteInitialization(MIDI_OK); |
} |
MidiManagerAlsa::~MidiManagerAlsa() { |
- // Send a shutdown message to awake |event_thread_| from poll(). |
- if (pipe_fd_[1] >= 0) |
- HANDLE_EINTR(write(pipe_fd_[1], "Q", 1)); |
+ // Tell the event thread it will soon be time to shut down. |
+ shutdown_mu_.Acquire(); |
+ event_thread_shutdown_ = true; |
+ shutdown_mu_.Release(); |
+ |
+ // 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_); |
+ } |
- // Stop receiving messages. |
+ // Wait for the event thread to stop. |
event_thread_.Stop(); |
- for (int i = 0; i < 2; ++i) { |
- if (pipe_fd_[i] >= 0) |
- close(pipe_fd_[i]); |
+ // Close the in client. |
+ if (in_client_) { |
+ snd_seq_close(in_client_); |
+ } |
+ |
+ // Free the decoder. |
+ snd_midi_event_free(decoder_); |
+ |
+ // Free the encoders. |
+ for (EncoderList::iterator i = encoders_.begin(); i != encoders_.end(); ++i) { |
+ snd_midi_event_free(*i); |
+ } |
+} |
+ |
+void MidiManagerAlsa::SendMidiData(uint32 port_index, |
+ const std::vector<uint8>& data) { |
+ DCHECK(send_thread_.message_loop_proxy()->BelongsToCurrentThread()); |
+ |
+ snd_midi_event_t* encoder = encoders_[port_index]; |
+ 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); |
+ } |
} |
- send_thread_.Stop(); |
} |
void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client, |
uint32 port_index, |
const std::vector<uint8>& data, |
double timestamp) { |
- if (out_devices_.size() <= port_index) |
+ if (out_ports_.size() <= port_index) |
return; |
+ if (!send_thread_.IsRunning()) |
+ send_thread_.Start(); |
+ |
+ // Compute delay. |
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()); |
+ delay = |
+ base::TimeDelta::FromSecondsD(timestamp) - |
+ (base::TimeTicks::HighResNow() - base::TimeTicks()); |
} |
- if (!send_thread_.IsRunning()) |
- send_thread_.Start(); |
+ // Avoid negative delay. This can happen: because of timer jitter? |
+ if (delay < base::TimeDelta()) { |
+ delay = base::TimeDelta(); |
+ } |
Takashi Toyoshima
2014/05/07 13:06:13
Just a question, but why do you want to change thi
Adam Goode
2014/05/08 03:00:54
Thanks for pointing this out. I actually think my
|
- scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index]; |
send_thread_.message_loop()->PostDelayedTask( |
FROM_HERE, |
- base::Bind(&MidiDeviceInfo::Send, device, client, data), |
- delay); |
+ 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())); |
} |
void MidiManagerAlsa::EventReset() { |
- CHECK_GE(pipe_fd_[0], 0); |
- |
- // Sum up descriptors which are needed to poll input devices and a shutdown |
- // message. |
- // Keep the first one descriptor for a shutdown message. |
- size_t poll_fds_size = 1; |
- for (size_t i = 0; i < in_devices_.size(); ++i) |
- poll_fds_size += in_devices_[i]->GetPollDescriptorsCount(); |
- poll_fds_.resize(poll_fds_size); |
- |
- // Setup struct pollfd to poll input MIDI devices and a shutdown message. |
- // The first pollfd is for a shutdown message. |
- poll_fds_[0].fd = pipe_fd_[0]; |
- poll_fds_[0].events = kPollEventMask; |
- int fds_index = 1; |
- for (size_t i = 0; i < in_devices_.size(); ++i) { |
- fds_index += in_devices_[i]->SetupPollDescriptors( |
- &poll_fds_[fds_index], poll_fds_.size() - fds_index); |
- } |
- |
event_thread_.message_loop()->PostTask( |
FROM_HERE, |
base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this))); |
} |
void MidiManagerAlsa::EventLoop() { |
- if (HANDLE_EINTR(poll(&poll_fds_[0], poll_fds_.size(), -1)) < 0) { |
- VPLOG(1) << "Couldn't poll(). Stop to poll input MIDI devices."; |
- // TODO(toyoshim): Handle device disconnection, and try to reconnect? |
- return; |
- } |
- |
- // Check timestamp as soon as possible because the API requires accurate |
- // timestamp as possible. It will be useful for recording MIDI events. |
- base::TimeTicks now = base::TimeTicks::HighResNow(); |
- |
- // Is this thread going to be shutdown? |
- if (poll_fds_[0].revents & kPollEventMask) |
- return; |
- |
// Read available incoming MIDI data. |
- int fds_index = 1; |
- uint8 buffer[kReceiveBufferSize]; |
- |
- for (size_t i = 0; i < in_devices_.size(); ++i) { |
- unsigned short revents = |
- in_devices_[i]->GetPollDescriptorsRevents(&poll_fds_[fds_index]); |
- if (revents & (POLLERR | POLLNVAL)) { |
- // TODO(toyoshim): Handle device disconnection. |
- VLOG(1) << "snd_rawmidi_descriptors_revents fails"; |
- poll_fds_[fds_index].events = 0; |
+ snd_seq_event_t* event; |
+ int err = snd_seq_event_input(in_client_, &event); |
+ double timestamp = |
+ (base::TimeTicks::HighResNow() - base::TimeTicks()).InSecondsF(); |
+ 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_mu_); |
+ if (event_thread_shutdown_) { |
+ return; |
+ } |
+ } else if (err < 0) { |
+ VLOG(1) << "snd_seq_event_input fails: " << snd_strerror(err); |
+ return; |
+ } else { |
+ // Check for disconnection of out client. This means "shut down". |
+ if (event->source.client == SND_SEQ_CLIENT_SYSTEM && |
+ event->source.port == SND_SEQ_PORT_SYSTEM_ANNOUNCE && |
+ event->type == SND_SEQ_EVENT_CLIENT_EXIT && |
+ event->data.addr.client == out_client_id_) { |
+ return; |
} |
- if (revents & POLLIN) { |
- size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize); |
- ReceiveMidiData(i, buffer, read_size, now); |
+ |
+ std::map<int, uint32>::iterator 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); |
+ } |
+ } else { |
+ ReceiveMidiData(source, buf, count, timestamp); |
+ } |
+ } |
} |
- fds_index += in_devices_[i]->GetPollDescriptorsCount(); |
} |
// Do again. |