[Android] Add ps_ext tool for tools-friendly cpu/mem/proc stats.
[chromium-blink-merge.git] / media / midi / midi_manager_win.cc
blob6643ffd24f385a0587923eff52c46efdc86aa67b
1 // Copyright 2013 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.
5 #include "media/midi/midi_manager_win.h"
7 #include <windows.h>
9 // Prevent unnecessary functions from being included from <mmsystem.h>
10 #define MMNODRV
11 #define MMNOSOUND
12 #define MMNOWAVE
13 #define MMNOAUX
14 #define MMNOMIXER
15 #define MMNOTIMER
16 #define MMNOJOY
17 #define MMNOMCI
18 #define MMNOMMIO
19 #include <mmsystem.h>
21 #include "base/bind.h"
22 #include "base/message_loop/message_loop.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/utf_string_conversions.h"
25 #include "base/threading/thread.h"
26 #include "media/midi/midi_message_queue.h"
27 #include "media/midi/midi_message_util.h"
28 #include "media/midi/midi_port_info.h"
30 namespace media {
31 namespace {
33 std::string GetInErrorMessage(MMRESULT result) {
34 wchar_t text[MAXERRORLENGTH];
35 MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
36 if (get_result != MMSYSERR_NOERROR) {
37 DLOG(ERROR) << "Failed to get error message."
38 << " original error: " << result
39 << " midiInGetErrorText error: " << get_result;
40 return std::string();
42 return base::WideToUTF8(text);
45 std::string GetOutErrorMessage(MMRESULT result) {
46 wchar_t text[MAXERRORLENGTH];
47 MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
48 if (get_result != MMSYSERR_NOERROR) {
49 DLOG(ERROR) << "Failed to get error message."
50 << " original error: " << result
51 << " midiOutGetErrorText error: " << get_result;
52 return std::string();
54 return base::WideToUTF8(text);
57 class MIDIHDRDeleter {
58 public:
59 void operator()(MIDIHDR* header) {
60 if (!header)
61 return;
62 delete[] static_cast<char*>(header->lpData);
63 header->lpData = NULL;
64 header->dwBufferLength = 0;
65 delete header;
69 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
71 ScopedMIDIHDR CreateMIDIHDR(size_t size) {
72 ScopedMIDIHDR header(new MIDIHDR);
73 ZeroMemory(header.get(), sizeof(*header));
74 header->lpData = new char[size];
75 header->dwBufferLength = size;
76 return header.Pass();
79 void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle,
80 const std::vector<uint8>& message) {
81 if (message.size() >= 4)
82 return;
84 DWORD packed_message = 0;
85 for (size_t i = 0; i < message.size(); ++i)
86 packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
87 MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
88 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
89 << "Failed to output short message: " << GetOutErrorMessage(result);
92 void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle,
93 const std::vector<uint8>& message) {
94 // Implementation note:
95 // Sending long MIDI message can be performed synchronously or asynchronously
96 // depending on the driver. There are 2 options to support both cases:
97 // 1) Call midiOutLongMsg() API and wait for its completion within this
98 // function. In this approach, we can avoid memory copy by directly pointing
99 // |message| as the data buffer to be sent.
100 // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
101 // API. The buffer will be freed in the MOM_DONE event hander, which tells
102 // us that the task of midiOutLongMsg() API is completed.
103 // Here we choose option 2) in favor of asynchronous design.
105 // Note for built-in USB-MIDI driver:
106 // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
107 // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
108 // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
109 // |message.size() / (75 * 1024)| secs in practice. Here we put 60 KB size
110 // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
111 // most 1 sec or so with a typical USB-MIDI device.
112 const size_t kSysExSizeLimit = 60 * 1024;
113 if (message.size() >= kSysExSizeLimit) {
114 DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
115 << ", size = " << message.size();
116 return;
119 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
120 for (size_t i = 0; i < message.size(); ++i)
121 midi_header->lpData[i] = static_cast<char>(message[i]);
123 MMRESULT result = midiOutPrepareHeader(
124 midi_out_handle, midi_header.get(), sizeof(*midi_header));
125 if (result != MMSYSERR_NOERROR) {
126 DLOG(ERROR) << "Failed to prepare output buffer: "
127 << GetOutErrorMessage(result);
128 return;
131 result = midiOutLongMsg(
132 midi_out_handle, midi_header.get(), sizeof(*midi_header));
133 if (result != MMSYSERR_NOERROR) {
134 DLOG(ERROR) << "Failed to output long message: "
135 << GetOutErrorMessage(result);
136 result = midiOutUnprepareHeader(
137 midi_out_handle, midi_header.get(), sizeof(*midi_header));
138 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
139 << "Failed to uninitialize output buffer: "
140 << GetOutErrorMessage(result);
141 return;
144 // The ownership of |midi_header| is moved to MOM_DONE event handler.
145 midi_header.release();
148 } // namespace
150 class MidiManagerWin::InDeviceInfo {
151 public:
152 ~InDeviceInfo() {
153 Uninitialize();
155 void set_port_index(int index) {
156 port_index_ = index;
158 int port_index() const {
159 return port_index_;
161 bool device_to_be_closed() const {
162 return device_to_be_closed_;
164 HMIDIIN midi_handle() const {
165 return midi_handle_;
168 static scoped_ptr<InDeviceInfo> Create(MidiManagerWin* manager,
169 UINT device_id) {
170 scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager));
171 if (!obj->Initialize(device_id))
172 obj.reset();
173 return obj.Pass();
176 private:
177 static const int kInvalidPortIndex = -1;
178 static const size_t kBufferLength = 32 * 1024;
180 explicit InDeviceInfo(MidiManagerWin* manager)
181 : manager_(manager),
182 port_index_(kInvalidPortIndex),
183 midi_handle_(NULL),
184 started_(false),
185 device_to_be_closed_(false) {
188 bool Initialize(DWORD device_id) {
189 Uninitialize();
190 midi_header_ = CreateMIDIHDR(kBufferLength);
192 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and
193 // MIM_CLOSE events.
194 // - MIM_DATA: This is the only way to get a short MIDI message with
195 // timestamp information.
196 // - MIM_LONGDATA: This is the only way to get a long MIDI message with
197 // timestamp information.
198 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
199 // the MIDI device becomes unavailable for some reasons, e.g., the cable
200 // is disconnected. As for the former case, HMIDIOUT will be invalidated
201 // soon after the callback is finished. As for the later case, however,
202 // HMIDIOUT continues to be valid until midiInClose() is called.
203 MMRESULT result = midiInOpen(&midi_handle_,
204 device_id,
205 reinterpret_cast<DWORD_PTR>(&HandleMessage),
206 reinterpret_cast<DWORD_PTR>(this),
207 CALLBACK_FUNCTION);
208 if (result != MMSYSERR_NOERROR) {
209 DLOG(ERROR) << "Failed to open output device. "
210 << " id: " << device_id
211 << " message: " << GetInErrorMessage(result);
212 return false;
214 result = midiInPrepareHeader(
215 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
216 if (result != MMSYSERR_NOERROR) {
217 DLOG(ERROR) << "Failed to initialize input buffer: "
218 << GetInErrorMessage(result);
219 return false;
221 result = midiInAddBuffer(
222 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
223 if (result != MMSYSERR_NOERROR) {
224 DLOG(ERROR) << "Failed to attach input buffer: "
225 << GetInErrorMessage(result);
226 return false;
228 result = midiInStart(midi_handle_);
229 if (result != MMSYSERR_NOERROR) {
230 DLOG(ERROR) << "Failed to start input port: "
231 << GetInErrorMessage(result);
232 return false;
234 started_ = true;
235 start_time_ = base::TimeTicks::Now();
236 return true;
239 void Uninitialize() {
240 MMRESULT result = MMSYSERR_NOERROR;
241 if (midi_handle_ && started_) {
242 result = midiInStop(midi_handle_);
243 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
244 << "Failed to stop input port: " << GetInErrorMessage(result);
245 started_ = false;
246 start_time_ = base::TimeTicks();
248 if (midi_handle_) {
249 // midiInReset flushes pending messages. We ignore these messages.
250 device_to_be_closed_ = true;
251 result = midiInReset(midi_handle_);
252 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
253 << "Failed to reset input port: " << GetInErrorMessage(result);
254 result = midiInClose(midi_handle_);
255 device_to_be_closed_ = false;
256 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
257 << "Failed to close input port: " << GetInErrorMessage(result);
258 midi_header_.reset();
259 midi_handle_ = NULL;
260 port_index_ = kInvalidPortIndex;
264 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
265 UINT message,
266 DWORD_PTR instance,
267 DWORD_PTR param1,
268 DWORD_PTR param2) {
269 // This method can be called back on any thread depending on Windows
270 // multimedia subsystem and underlying MIDI drivers.
271 InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance);
272 if (!self)
273 return;
274 if (self->midi_handle() != midi_in_handle)
275 return;
277 switch (message) {
278 case MIM_DATA:
279 self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff),
280 static_cast<uint8>((param1 >> 8) & 0xff),
281 static_cast<uint8>((param1 >> 16) & 0xff),
282 param2);
283 return;
284 case MIM_LONGDATA:
285 self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1),
286 param2);
287 return;
288 case MIM_CLOSE:
289 // TODO(yukawa): Implement crbug.com/279097.
290 return;
294 void OnShortMessageReceived(uint8 status_byte,
295 uint8 first_data_byte,
296 uint8 second_data_byte,
297 DWORD elapsed_ms) {
298 if (device_to_be_closed())
299 return;
300 const size_t len = GetMidiMessageLength(status_byte);
301 if (len == 0 || port_index() == kInvalidPortIndex)
302 return;
303 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte };
304 DCHECK_LE(len, arraysize(kData));
305 OnMessageReceived(kData, len, elapsed_ms);
308 void OnLongMessageReceived(MIDIHDR* header, DWORD elapsed_ms) {
309 if (header != midi_header_.get())
310 return;
311 MMRESULT result = MMSYSERR_NOERROR;
312 if (device_to_be_closed()) {
313 if (midi_header_ &&
314 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
315 result = midiInUnprepareHeader(
316 midi_handle_, midi_header_.get(), sizeof(*midi_header_));
317 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
318 << "Failed to uninitialize input buffer: "
319 << GetInErrorMessage(result);
321 return;
323 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) {
324 OnMessageReceived(reinterpret_cast<const uint8*>(header->lpData),
325 header->dwBytesRecorded,
326 elapsed_ms);
328 result = midiInAddBuffer(midi_handle_, header, sizeof(*header));
329 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
330 << "Failed to attach input port: " << GetInErrorMessage(result);
333 void OnMessageReceived(const uint8* data, size_t length, DWORD elapsed_ms) {
334 // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
335 // called as the origin of |elapsed_ms|.
336 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
337 // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
338 const base::TimeTicks event_time =
339 start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms);
340 // MidiManager::ReceiveMidiData() expects |timestamp| as the elapsed seconds
341 // from base::TimeTicks::Now().
342 // TODO(yukawa): Update MidiManager::ReceiveMidiData() so that it can
343 // receive |event_time| directly if the precision of base::TimeTicks is
344 // sufficient.
345 const double timestamp = (event_time - base::TimeTicks()).InSecondsF();
346 manager_->ReceiveMidiData(port_index_, data, length, timestamp);
349 MidiManagerWin* manager_;
350 int port_index_;
351 HMIDIIN midi_handle_;
352 ScopedMIDIHDR midi_header_;
353 base::TimeTicks start_time_;
354 bool started_;
355 bool device_to_be_closed_;
356 DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::InDeviceInfo);
359 class MidiManagerWin::OutDeviceInfo {
360 public:
361 ~OutDeviceInfo() {
362 Uninitialize();
365 static scoped_ptr<OutDeviceInfo> Create(UINT device_id) {
366 scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo);
367 if (!obj->Initialize(device_id))
368 obj.reset();
369 return obj.Pass();
372 HMIDIOUT midi_handle() const {
373 return midi_handle_;
376 void Quit() {
377 quitting_ = true;
380 void Send(const std::vector<uint8>& data) {
381 // Check if the attached device is still available or not.
382 if (!midi_handle_)
383 return;
385 // Give up sending MIDI messages here if the device is already closed.
386 // Note that this check is optional. Regardless of that we check |closed_|
387 // or not, nothing harmful happens as long as |midi_handle_| is still valid.
388 if (closed_)
389 return;
391 // MIDI Running status must be filtered out.
392 MidiMessageQueue message_queue(false);
393 message_queue.Add(data);
394 std::vector<uint8> message;
395 while (!quitting_) {
396 message_queue.Get(&message);
397 if (message.empty())
398 break;
399 // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
400 if (message.size() <= 3)
401 SendShortMidiMessageInternal(midi_handle_, message);
402 else
403 SendLongMidiMessageInternal(midi_handle_, message);
407 private:
408 OutDeviceInfo()
409 : midi_handle_(NULL),
410 closed_(false),
411 quitting_(false) {}
413 bool Initialize(DWORD device_id) {
414 Uninitialize();
415 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE
416 // events.
417 // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
418 // up the backing store where a long MIDI message is stored.
419 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
420 // the MIDI device becomes unavailable for some reasons, e.g., the cable
421 // is disconnected. As for the former case, HMIDIOUT will be invalidated
422 // soon after the callback is finished. As for the later case, however,
423 // HMIDIOUT continues to be valid until midiOutClose() is called.
424 MMRESULT result = midiOutOpen(&midi_handle_,
425 device_id,
426 reinterpret_cast<DWORD_PTR>(&HandleMessage),
427 reinterpret_cast<DWORD_PTR>(this),
428 CALLBACK_FUNCTION);
429 if (result != MMSYSERR_NOERROR) {
430 DLOG(ERROR) << "Failed to open output device. "
431 << " id: " << device_id
432 << " message: "<< GetOutErrorMessage(result);
433 midi_handle_ = NULL;
434 return false;
436 return true;
439 void Uninitialize() {
440 if (!midi_handle_)
441 return;
443 MMRESULT result = midiOutReset(midi_handle_);
444 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
445 << "Failed to reset output port: " << GetOutErrorMessage(result);
446 result = midiOutClose(midi_handle_);
447 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
448 << "Failed to close output port: " << GetOutErrorMessage(result);
449 midi_handle_ = NULL;
450 closed_ = true;
453 static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle,
454 UINT message,
455 DWORD_PTR instance,
456 DWORD_PTR param1,
457 DWORD_PTR param2) {
458 // This method can be called back on any thread depending on Windows
459 // multimedia subsystem and underlying MIDI drivers.
461 OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance);
462 if (!self)
463 return;
464 if (self->midi_handle() != midi_out_handle)
465 return;
466 switch (message) {
467 case MOM_DONE: {
468 // Take ownership of the MIDIHDR object.
469 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
470 if (!header)
471 return;
472 MMRESULT result = midiOutUnprepareHeader(
473 self->midi_handle(), header.get(), sizeof(*header));
474 DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
475 << "Failed to uninitialize output buffer: "
476 << GetOutErrorMessage(result);
477 return;
479 case MOM_CLOSE:
480 // No lock is required since this flag is just a hint to avoid
481 // unnecessary API calls that will result in failure anyway.
482 self->closed_ = true;
483 // TODO(yukawa): Implement crbug.com/279097.
484 return;
488 HMIDIOUT midi_handle_;
490 // True if the device is already closed.
491 volatile bool closed_;
493 // True if the MidiManagerWin is trying to stop the sender thread.
494 volatile bool quitting_;
496 DISALLOW_COPY_AND_ASSIGN(MidiManagerWin::OutDeviceInfo);
499 MidiManagerWin::MidiManagerWin()
500 : send_thread_("MidiSendThread") {
503 bool MidiManagerWin::Initialize() {
504 const UINT num_in_devices = midiInGetNumDevs();
505 in_devices_.reserve(num_in_devices);
506 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
507 MIDIINCAPS caps = {};
508 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps));
509 if (result != MMSYSERR_NOERROR) {
510 DLOG(ERROR) << "Failed to obtain input device info: "
511 << GetInErrorMessage(result);
512 continue;
514 scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id));
515 if (!in_device)
516 continue;
517 MidiPortInfo info(
518 base::IntToString(static_cast<int>(device_id)),
520 base::WideToUTF8(caps.szPname),
521 base::IntToString(static_cast<int>(caps.vDriverVersion)));
522 AddInputPort(info);
523 in_device->set_port_index(input_ports_.size() - 1);
524 in_devices_.push_back(in_device.Pass());
527 const UINT num_out_devices = midiOutGetNumDevs();
528 out_devices_.reserve(num_out_devices);
529 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
530 MIDIOUTCAPS caps = {};
531 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps));
532 if (result != MMSYSERR_NOERROR) {
533 DLOG(ERROR) << "Failed to obtain output device info: "
534 << GetOutErrorMessage(result);
535 continue;
537 scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id));
538 if (!out_port)
539 continue;
540 MidiPortInfo info(
541 base::IntToString(static_cast<int>(device_id)),
543 base::WideToUTF8(caps.szPname),
544 base::IntToString(static_cast<int>(caps.vDriverVersion)));
545 AddOutputPort(info);
546 out_devices_.push_back(out_port.Pass());
549 return true;
552 MidiManagerWin::~MidiManagerWin() {
553 // Cleanup order is important. |send_thread_| must be stopped before
554 // |out_devices_| is cleared.
555 for (size_t i = 0; i < output_ports_.size(); ++i)
556 out_devices_[i]->Quit();
557 send_thread_.Stop();
559 out_devices_.clear();
560 output_ports_.clear();
561 in_devices_.clear();
562 input_ports_.clear();
565 void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
566 uint32 port_index,
567 const std::vector<uint8>& data,
568 double timestamp) {
569 if (out_devices_.size() <= port_index)
570 return;
572 base::TimeDelta delay;
573 if (timestamp != 0.0) {
574 base::TimeTicks time_to_send =
575 base::TimeTicks() + base::TimeDelta::FromMicroseconds(
576 timestamp * base::Time::kMicrosecondsPerSecond);
577 delay = std::max(time_to_send - base::TimeTicks::Now(), base::TimeDelta());
580 if (!send_thread_.IsRunning())
581 send_thread_.Start();
583 OutDeviceInfo* out_port = out_devices_[port_index].get();
584 send_thread_.message_loop()->PostDelayedTask(
585 FROM_HERE,
586 base::Bind(&OutDeviceInfo::Send, base::Unretained(out_port), data),
587 delay);
589 // Call back AccumulateMidiBytesSent() on |send_thread_| to emulate the
590 // behavior of MidiManagerMac::SendMidiData.
591 // TODO(yukawa): Do this task in a platform-independent way if possible.
592 // See crbug.com/325810.
593 send_thread_.message_loop()->PostTask(
594 FROM_HERE,
595 base::Bind(&MidiManagerClient::AccumulateMidiBytesSent,
596 base::Unretained(client), data.size()));
599 MidiManager* MidiManager::Create() {
600 return new MidiManagerWin();
603 } // namespace media