blob: 781e842cda71e9bc9ce0e351964f5c2d1374951d [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
5#include "media/midi/midi_manager_linux.h"
6
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 {
19namespace {
20const char kUnknown[] = "[unknown]";
21} // namespace
22
23class MIDIManagerLinux::MIDIDeviceInfo
24 : public base::RefCounted<MIDIDeviceInfo> {
25 public:
26 MIDIDeviceInfo(MIDIManagerLinux* manager,
27 const std::string& card,
28 const snd_rawmidi_info_t* midi,
29 int device) {
30 opened_ = !snd_rawmidi_open(&midi_in_, &midi_out_, card.c_str(), 0);
31 if (!opened_)
32 return;
33
34 const std::string name = snd_rawmidi_info_get_name(midi);
35 const std::string id = base::StringPrintf("%s:%d", card.c_str(), device);
36 port_info_ = MIDIPortInfo(id, kUnknown, name, kUnknown);
37 }
38
39 void Send(MIDIManagerClient* client, const std::vector<uint8>& data) {
40 ssize_t result = snd_rawmidi_write(
41 midi_out_, reinterpret_cast<const void*>(&data[0]), data.size());
42 if (static_cast<size_t>(result) != data.size()) {
43 // TODO(toyoshim): Disconnect and reopen the device.
44 LOG(ERROR) << "snd_rawmidi_write fails: " << strerror(-result);
45 }
46 base::MessageLoop::current()->PostTask(
47 FROM_HERE,
48 base::Bind(&MIDIManagerClient::AccumulateMIDIBytesSent,
49 base::Unretained(client), data.size()));
50 }
51
52 const MIDIPortInfo& GetMIDIPortInfo() const { return port_info_; }
53 bool IsOpened() const { return opened_; }
54
55 private:
56 friend class base::RefCounted<MIDIDeviceInfo>;
57 virtual ~MIDIDeviceInfo() {
58 if (opened_) {
59 snd_rawmidi_close(midi_in_);
60 snd_rawmidi_close(midi_out_);
61 }
62 }
63
64 bool opened_;
65 MIDIPortInfo port_info_;
66 snd_rawmidi_t* midi_in_;
67 snd_rawmidi_t* midi_out_;
68
69 DISALLOW_COPY_AND_ASSIGN(MIDIDeviceInfo);
70};
71
72MIDIManagerLinux::MIDIManagerLinux()
73 : send_thread_("MIDISendThread") {
74}
75
76bool MIDIManagerLinux::Initialize() {
77 // TODO(toyoshim): Make Initialize() asynchronous.
78 TRACE_EVENT0("midi", "MIDIManagerMac::Initialize");
79
80 // Enumerate only hardware MIDI devices because software MIDIs running in
81 // the browser process is not secure.
82 snd_ctl_card_info_t* card;
83 snd_rawmidi_info_t* midi_out;
84 snd_rawmidi_info_t* midi_in;
85 snd_ctl_card_info_alloca(&card);
86 snd_rawmidi_info_alloca(&midi_out);
87 snd_rawmidi_info_alloca(&midi_in);
88 for (int index = -1; !snd_card_next(&index) && index >= 0; ) {
89 const std::string id = base::StringPrintf("hw:CARD=%i", index);
90 snd_ctl_t* handle;
91 int err = snd_ctl_open(&handle, id.c_str(), 0);
92 if (err != 0) {
93 DLOG(ERROR) << "snd_ctl_open fails: " << snd_strerror(err);
94 continue;
95 }
96 err = snd_ctl_card_info(handle, card);
97 if (err != 0) {
98 DLOG(ERROR) << "snd_ctl_card_info fails: " << snd_strerror(err);
99 snd_ctl_close(handle);
100 continue;
101 }
102 for (int device = -1;
103 !snd_ctl_rawmidi_next_device(handle, &device) && device >= 0; ) {
104 bool output;
105 bool input;
106 snd_rawmidi_info_set_device(midi_out, device);
107 snd_rawmidi_info_set_subdevice(midi_out, 0);
108 snd_rawmidi_info_set_stream(midi_out, SND_RAWMIDI_STREAM_OUTPUT);
109 output = snd_ctl_rawmidi_info(handle, midi_out) == 0;
110 snd_rawmidi_info_set_device(midi_in, device);
111 snd_rawmidi_info_set_subdevice(midi_in, 0);
112 snd_rawmidi_info_set_stream(midi_in, SND_RAWMIDI_STREAM_INPUT);
113 input = snd_ctl_rawmidi_info(handle, midi_in) == 0;
114 if (!output && !input)
115 continue;
116 scoped_refptr<MIDIDeviceInfo> port =
117 new MIDIDeviceInfo(this, id, output ? midi_out : midi_in, device);
118 if (!port->IsOpened()) {
119 DLOG(ERROR) << "MIDIDeviceInfo open fails";
120 continue;
121 }
122 if (input) {
123 in_devices_.push_back(port);
124 AddInputPort(port->GetMIDIPortInfo());
125 }
126 if (output) {
127 out_devices_.push_back(port);
128 AddOutputPort(port->GetMIDIPortInfo());
129 }
130 }
131 snd_ctl_close(handle);
132 }
133 return true;
134}
135
136MIDIManagerLinux::~MIDIManagerLinux() {
137 send_thread_.Stop();
138}
139
140void MIDIManagerLinux::DispatchSendMIDIData(MIDIManagerClient* client,
141 uint32 port_index,
142 const std::vector<uint8>& data,
143 double timestamp) {
144 if (out_devices_.size() <= port_index)
145 return;
146
147 base::TimeDelta delay;
148 if (timestamp != 0.0) {
149 base::TimeTicks time_to_send =
150 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
151 timestamp * base::Time::kMicrosecondsPerSecond);
152 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
153 }
154
155 if (!send_thread_.IsRunning())
156 send_thread_.Start();
157
158 scoped_refptr<MIDIDeviceInfo> device = out_devices_[port_index];
159 send_thread_.message_loop()->PostDelayedTask(
160 FROM_HERE,
161 base::Bind(&MIDIDeviceInfo::Send, device, client, data),
162 delay);
163}
164
165MIDIManager* MIDIManager::Create() {
166 return new MIDIManagerLinux();
167}
168
169} // namespace media