Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / media / midi / midi_manager_alsa.cc
index c281877..4716db2 100644 (file)
 #include "media/midi/midi_manager_alsa.h"
 
 #include <alsa/asoundlib.h>
+#include <stdlib.h>
 
 #include "base/bind.h"
 #include "base/debug/trace_event.h"
 #include "base/logging.h"
 #include "base/memory/ref_counted.h"
 #include "base/message_loop/message_loop.h"
+#include "base/posix/eintr_wrapper.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread.h"
+#include "base/time/time.h"
 #include "media/midi/midi_port_info.h"
 
 namespace media {
+
 namespace {
-const char kUnknown[] = "[unknown]";
+
+const size_t kReceiveBufferSize = 4096;
+const unsigned short kPollEventMask = POLLIN | POLLERR | POLLNVAL;
+
 }  // namespace
 
-class MIDIManagerAlsa::MIDIDeviceInfo
-    : public base::RefCounted<MIDIDeviceInfo> {
+class MidiManagerAlsa::MidiDeviceInfo
+    : public base::RefCounted<MidiDeviceInfo> {
  public:
-  MIDIDeviceInfo(MIDIManagerAlsa* manager,
-                 const std::string& card,
+  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_, card.c_str(), 0);
+    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);
-    const std::string id = base::StringPrintf("%s:%d", card.c_str(), device);
-    port_info_ = MIDIPortInfo(id, kUnknown, name, kUnknown);
+    // 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);
   }
 
-  void Send(MIDIManagerClient* client, const std::vector<uint8>& data) {
+  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): Disconnect and reopen the device.
+      // TODO(toyoshim): Handle device disconnection.
       LOG(ERROR) << "snd_rawmidi_write fails: " << strerror(-result);
     }
     base::MessageLoop::current()->PostTask(
         FROM_HERE,
-        base::Bind(&MIDIManagerClient::AccumulateMIDIBytesSent,
+        base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
                    base::Unretained(client), data.size()));
   }
 
-  const MIDIPortInfo& GetMIDIPortInfo() const { return port_info_; }
+  // 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);
+  }
+
+  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_);
+  }
+
+  // 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);
+  }
+
+  unsigned short GetPollDescriptorsRevents(struct pollfd* pfds) {
+    unsigned short revents;
+    snd_rawmidi_poll_descriptors_revents(midi_in_,
+                                         pfds,
+                                         GetPollDescriptorsCount(),
+                                         &revents);
+    return revents;
+  }
+
   bool IsOpened() const { return opened_; }
 
  private:
-  friend class base::RefCounted<MIDIDeviceInfo>;
-  virtual ~MIDIDeviceInfo() {
+  friend class base::RefCounted<MidiDeviceInfo>;
+  virtual ~MidiDeviceInfo() {
     if (opened_) {
       snd_rawmidi_close(midi_in_);
       snd_rawmidi_close(midi_out_);
@@ -62,20 +113,24 @@ class MIDIManagerAlsa::MIDIDeviceInfo
   }
 
   bool opened_;
-  MIDIPortInfo port_info_;
+  MidiPortInfo port_info_;
   snd_rawmidi_t* midi_in_;
   snd_rawmidi_t* midi_out_;
 
-  DISALLOW_COPY_AND_ASSIGN(MIDIDeviceInfo);
+  DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo);
 };
 
-MIDIManagerAlsa::MIDIManagerAlsa()
-    : send_thread_("MIDISendThread") {
+MidiManagerAlsa::MidiManagerAlsa()
+    : send_thread_("MidiSendThread"),
+      event_thread_("MidiEventThread") {
+  for (size_t i = 0; i < arraysize(pipe_fd_); ++i)
+    pipe_fd_[i] = -1;
 }
 
-bool MIDIManagerAlsa::Initialize() {
+bool MidiManagerAlsa::Initialize() {
   // TODO(toyoshim): Make Initialize() asynchronous.
-  TRACE_EVENT0("midi", "MIDIManagerMac::Initialize");
+  // See http://crbug.com/339746.
+  TRACE_EVENT0("midi", "MidiManagerMac::Initialize");
 
   // Enumerate only hardware MIDI devices because software MIDIs running in
   // the browser process is not secure.
@@ -113,31 +168,52 @@ bool MIDIManagerAlsa::Initialize() {
       input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
       if (!output && !input)
         continue;
-      scoped_refptr<MIDIDeviceInfo> port =
-          new MIDIDeviceInfo(this, id, output ? midi_out : midi_in, device);
+      scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo(
+          this, id, card, output ? midi_out : midi_in, device);
       if (!port->IsOpened()) {
-        DLOG(ERROR) << "MIDIDeviceInfo open fails";
+        DLOG(ERROR) << "MidiDeviceInfo open fails";
         continue;
       }
       if (input) {
         in_devices_.push_back(port);
-        AddInputPort(port->GetMIDIPortInfo());
+        AddInputPort(port->GetMidiPortInfo());
       }
       if (output) {
         out_devices_.push_back(port);
-        AddOutputPort(port->GetMIDIPortInfo());
+        AddOutputPort(port->GetMidiPortInfo());
       }
     }
     snd_ctl_close(handle);
   }
+
+  if (pipe(pipe_fd_) < 0) {
+    DPLOG(ERROR) << "pipe() failed";
+    return false;
+  }
+  event_thread_.Start();
+  event_thread_.message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&MidiManagerAlsa::EventReset, base::Unretained(this)));
+
   return true;
 }
 
-MIDIManagerAlsa::~MIDIManagerAlsa() {
+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));
+
+  // Stop receiving messages.
+  event_thread_.Stop();
+
+  for (int i = 0; i < 2; ++i) {
+    if (pipe_fd_[i] >= 0)
+      close(pipe_fd_[i]);
+  }
   send_thread_.Stop();
 }
 
-void MIDIManagerAlsa::DispatchSendMIDIData(MIDIManagerClient* client,
+void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
                                            uint32 port_index,
                                            const std::vector<uint8>& data,
                                            double timestamp) {
@@ -155,15 +231,85 @@ void MIDIManagerAlsa::DispatchSendMIDIData(MIDIManagerClient* client,
   if (!send_thread_.IsRunning())
     send_thread_.Start();
 
-  scoped_refptr<MIDIDeviceInfo> device = out_devices_[port_index];
+  scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index];
   send_thread_.message_loop()->PostDelayedTask(
       FROM_HERE,
-      base::Bind(&MIDIDeviceInfo::Send, device, client, data),
+      base::Bind(&MidiDeviceInfo::Send, device, client, data),
       delay);
 }
 
-MIDIManager* MIDIManager::Create() {
-  return new MIDIManagerAlsa();
+void MidiManagerAlsa::EventReset() {
+  CHECK(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) {
+    DPLOG(ERROR) << "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];
+  // TODO(toyoshim): Revisit if the following conversion is the best way.
+  base::TimeDelta timestamp_delta =
+      base::TimeDelta::FromInternalValue(now.ToInternalValue());
+  double timestamp = timestamp_delta.InSecondsF();
+
+  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.
+      LOG(ERROR) << "snd_rawmidi_descriptors_revents fails";
+      poll_fds_[fds_index].events = 0;
+    }
+    if (revents & POLLIN) {
+      size_t read_size = in_devices_[i]->Receive(buffer, kReceiveBufferSize);
+      ReceiveMidiData(i, buffer, read_size, timestamp);
+    }
+    fds_index += in_devices_[i]->GetPollDescriptorsCount();
+  }
+
+  // Do again.
+  event_thread_.message_loop()->PostTask(
+      FROM_HERE,
+      base::Bind(&MidiManagerAlsa::EventLoop, base::Unretained(this)));
+}
+
+MidiManager* MidiManager::Create() {
+  return new MidiManagerAlsa();
 }
 
 }  // namespace media