blob: 6f389cafdda9a5e8e538ac2dc515fe3e49f6c4b0 [file] [log] [blame]
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00001// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +00005#include "media/midi/midi_manager_alsa.h"
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +00006
7#include <alsa/asoundlib.h>
8
9#include "base/bind.h"
10#include "base/debug/trace_event.h"
11#include "base/logging.h"
12#include "base/memory/ref_counted.h"
13#include "base/message_loop/message_loop.h"
14#include "base/strings/stringprintf.h"
15#include "base/threading/thread.h"
16#include "media/midi/midi_port_info.h"
17
18namespace media {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000019
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000020class MidiManagerAlsa::MidiDeviceInfo
21 : public base::RefCounted<MidiDeviceInfo> {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000022 public:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000023 MidiDeviceInfo(MidiManagerAlsa* manager,
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000024 const std::string& bus_id,
25 snd_ctl_card_info_t* card,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000026 const snd_rawmidi_info_t* midi,
27 int device) {
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000028 opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, bus_id.c_str(), 0);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000029 if (!opened_)
30 return;
31
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000032 const std::string id = base::StringPrintf("%s:%d", bus_id.c_str(), device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000033 const std::string name = snd_rawmidi_info_get_name(midi);
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +000034 // We assume that card longname is in the format of
35 // "<manufacturer> <name> at <bus>". Otherwise, we give up to detect
36 // a manufacturer name here.
37 std::string manufacturer;
38 const std::string card_name = snd_ctl_card_info_get_longname(card);
39 size_t name_index = card_name.find(name);
40 if (std::string::npos != name_index)
41 manufacturer = card_name.substr(0, name_index - 1);
42 const std::string version =
43 base::StringPrintf("%s / ALSA library version %d.%d.%d",
44 snd_ctl_card_info_get_driver(card),
45 SND_LIB_MAJOR, SND_LIB_MINOR, SND_LIB_SUBMINOR);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000046 port_info_ = MidiPortInfo(id, manufacturer, name, version);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000047 }
48
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000049 void Send(MidiManagerClient* client, const std::vector<uint8>& data) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000050 ssize_t result = snd_rawmidi_write(
51 midi_out_, reinterpret_cast<const void*>(&data[0]), data.size());
52 if (static_cast<size_t>(result) != data.size()) {
53 // TODO(toyoshim): Disconnect and reopen the device.
54 LOG(ERROR) << "snd_rawmidi_write fails: " << strerror(-result);
55 }
56 base::MessageLoop::current()->PostTask(
57 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000058 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000059 base::Unretained(client), data.size()));
60 }
61
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000062 const MidiPortInfo& GetMidiPortInfo() const { return port_info_; }
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000063 bool IsOpened() const { return opened_; }
64
65 private:
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000066 friend class base::RefCounted<MidiDeviceInfo>;
67 virtual ~MidiDeviceInfo() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000068 if (opened_) {
69 snd_rawmidi_close(midi_in_);
70 snd_rawmidi_close(midi_out_);
71 }
72 }
73
74 bool opened_;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000075 MidiPortInfo port_info_;
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000076 snd_rawmidi_t* midi_in_;
77 snd_rawmidi_t* midi_out_;
78
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000079 DISALLOW_COPY_AND_ASSIGN(MidiDeviceInfo);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000080};
81
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000082MidiManagerAlsa::MidiManagerAlsa()
83 : send_thread_("MidiSendThread") {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000084}
85
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000086bool MidiManagerAlsa::Initialize() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000087 // TODO(toyoshim): Make Initialize() asynchronous.
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +000088 // See http://crbug.com/339746.
89 TRACE_EVENT0("midi", "MidiManagerMac::Initialize");
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +000090
91 // Enumerate only hardware MIDI devices because software MIDIs running in
92 // the browser process is not secure.
93 snd_ctl_card_info_t* card;
94 snd_rawmidi_info_t* midi_out;
95 snd_rawmidi_info_t* midi_in;
96 snd_ctl_card_info_alloca(&card);
97 snd_rawmidi_info_alloca(&midi_out);
98 snd_rawmidi_info_alloca(&midi_in);
99 for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
100 const std::string id = base::StringPrintf("hw:CARD=%i", index);
101 snd_ctl_t* handle;
102 int err = snd_ctl_open(&handle, id.c_str(), 0);
103 if (err != 0) {
104 DLOG(ERROR) << "snd_ctl_open fails: " << snd_strerror(err);
105 continue;
106 }
107 err = snd_ctl_card_info(handle, card);
108 if (err != 0) {
109 DLOG(ERROR) << "snd_ctl_card_info fails: " << snd_strerror(err);
110 snd_ctl_close(handle);
111 continue;
112 }
113 for (int device = -1;
114 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
115 bool output;
116 bool input;
117 snd_rawmidi_info_set_device(midi_out, device);
118 snd_rawmidi_info_set_subdevice(midi_out, 0);
119 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
120 output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
121 snd_rawmidi_info_set_device(midi_in, device);
122 snd_rawmidi_info_set_subdevice(midi_in, 0);
123 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
124 input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
125 if (!output && !input)
126 continue;
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000127 scoped_refptr<MidiDeviceInfo> port = new MidiDeviceInfo(
toyoshim@chromium.org9ebe19a2014-02-02 06:10:36 +0000128 this, id, card, output ? midi_out : midi_in, device);
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000129 if (!port->IsOpened()) {
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000130 DLOG(ERROR) << "MidiDeviceInfo open fails";
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000131 continue;
132 }
133 if (input) {
134 in_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000135 AddInputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000136 }
137 if (output) {
138 out_devices_.push_back(port);
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000139 AddOutputPort(port->GetMidiPortInfo());
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000140 }
141 }
142 snd_ctl_close(handle);
143 }
144 return true;
145}
146
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000147MidiManagerAlsa::~MidiManagerAlsa() {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000148 send_thread_.Stop();
149}
150
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000151void MidiManagerAlsa::DispatchSendMidiData(MidiManagerClient* client,
dnicoara@chromium.org9f2a6f02014-01-03 21:25:00 +0000152 uint32 port_index,
153 const std::vector<uint8>& data,
154 double timestamp) {
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000155 if (out_devices_.size() <= port_index)
156 return;
157
158 base::TimeDelta delay;
159 if (timestamp != 0.0) {
160 base::TimeTicks time_to_send =
161 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
162 timestamp * base::Time::kMicrosecondsPerSecond);
163 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
164 }
165
166 if (!send_thread_.IsRunning())
167 send_thread_.Start();
168
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000169 scoped_refptr<MidiDeviceInfo> device = out_devices_[port_index];
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000170 send_thread_.message_loop()->PostDelayedTask(
171 FROM_HERE,
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000172 base::Bind(&MidiDeviceInfo::Send, device, client, data),
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000173 delay);
174}
175
toyoshim@chromium.orgc82e66e2014-02-04 07:05:47 +0000176MidiManager* MidiManager::Create() {
177 return new MidiManagerAlsa();
toyoshim@chromium.orga97eebf2014-01-03 07:52:39 +0000178}
179
180} // namespace media