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