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 "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" 29 30 namespace media { 31 namespace { 32 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(); 41 } 42 return WideToUTF8(text); 43 } 44 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(); 53 } 54 return WideToUTF8(text); 55 } 56 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; 66 } 67 }; 68 69 typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR; 70 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(); 77 } 78 79 void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle, 80 const std::vector<uint8>& message) { 81 if (message.size() >= 4) 82 return; 83 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); 90 } 91 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. 104 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; 117 } 118 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]); 122 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; 129 } 130 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; 142 } 143 144 // The ownership of |midi_header| is moved to MOM_DONE event handler. 145 midi_header.release(); 146 } 147 148 } // namespace 149 150 class MIDIManagerWin::InDeviceInfo { 151 public: 152 ~InDeviceInfo() { 153 Uninitialize(); 154 } 155 void set_port_index(int index) { 156 port_index_ = index; 157 } 158 int port_index() const { 159 return port_index_; 160 } 161 bool device_to_be_closed() const { 162 return device_to_be_closed_; 163 } 164 HMIDIIN midi_handle() const { 165 return midi_handle_; 166 } 167 const base::TimeDelta& start_time_offset() const { 168 return start_time_offset_; 169 } 170 171 static scoped_ptr<InDeviceInfo> Create(MIDIManagerWin* manager, 172 UINT device_id) { 173 scoped_ptr<InDeviceInfo> obj(new InDeviceInfo(manager)); 174 if (!obj->Initialize(device_id)) 175 obj.reset(); 176 return obj.Pass(); 177 } 178 179 private: 180 static const int kInvalidPortIndex = -1; 181 static const size_t kBufferLength = 32 * 1024; 182 183 explicit InDeviceInfo(MIDIManagerWin* manager) 184 : manager_(manager), 185 port_index_(kInvalidPortIndex), 186 midi_handle_(NULL), 187 started_(false), 188 device_to_be_closed_(false) { 189 } 190 191 bool Initialize(DWORD device_id) { 192 Uninitialize(); 193 midi_header_ = CreateMIDIHDR(kBufferLength); 194 195 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and 196 // MIM_CLOSE events. 197 // - MIM_DATA: This is the only way to get a short MIDI message with 198 // timestamp information. 199 // - MIM_LONGDATA: This is the only way to get a long MIDI message with 200 // timestamp information. 201 // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2) 202 // the MIDI device becomes unavailable for some reasons, e.g., the cable 203 // is disconnected. As for the former case, HMIDIOUT will be invalidated 204 // soon after the callback is finished. As for the later case, however, 205 // HMIDIOUT continues to be valid until midiInClose() is called. 206 MMRESULT result = midiInOpen(&midi_handle_, 207 device_id, 208 reinterpret_cast<DWORD_PTR>(&HandleMessage), 209 reinterpret_cast<DWORD_PTR>(this), 210 CALLBACK_FUNCTION); 211 if (result != MMSYSERR_NOERROR) { 212 DLOG(ERROR) << "Failed to open output device. " 213 << " id: " << device_id 214 << " message: " << GetInErrorMessage(result); 215 return false; 216 } 217 result = midiInPrepareHeader( 218 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); 219 if (result != MMSYSERR_NOERROR) { 220 DLOG(ERROR) << "Failed to initialize input buffer: " 221 << GetInErrorMessage(result); 222 return false; 223 } 224 result = midiInAddBuffer( 225 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); 226 if (result != MMSYSERR_NOERROR) { 227 DLOG(ERROR) << "Failed to attach input buffer: " 228 << GetInErrorMessage(result); 229 return false; 230 } 231 result = midiInStart(midi_handle_); 232 if (result != MMSYSERR_NOERROR) { 233 DLOG(ERROR) << "Failed to start input port: " 234 << GetInErrorMessage(result); 235 return false; 236 } 237 started_ = true; 238 start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks(); 239 return true; 240 } 241 242 void Uninitialize() { 243 MMRESULT result = MMSYSERR_NOERROR; 244 if (midi_handle_ && started_) { 245 result = midiInStop(midi_handle_); 246 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 247 << "Failed to stop input port: " << GetInErrorMessage(result); 248 started_ = false; 249 start_time_offset_ = base::TimeDelta(); 250 } 251 if (midi_handle_) { 252 // midiInReset flushes pending messages. We ignore these messages. 253 device_to_be_closed_ = true; 254 result = midiInReset(midi_handle_); 255 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 256 << "Failed to reset input port: " << GetInErrorMessage(result); 257 result = midiInClose(midi_handle_); 258 device_to_be_closed_ = false; 259 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 260 << "Failed to close input port: " << GetInErrorMessage(result); 261 midi_header_.reset(); 262 midi_handle_ = NULL; 263 port_index_ = kInvalidPortIndex; 264 } 265 } 266 267 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, 268 UINT message, 269 DWORD_PTR instance, 270 DWORD_PTR param1, 271 DWORD_PTR param2) { 272 // This method can be called back on any thread depending on Windows 273 // multimedia subsystem and underlying MIDI drivers. 274 InDeviceInfo* self = reinterpret_cast<InDeviceInfo*>(instance); 275 if (!self) 276 return; 277 if (self->midi_handle() != midi_in_handle) 278 return; 279 280 switch (message) { 281 case MIM_DATA: 282 self->OnShortMessageReceived(static_cast<uint8>(param1 & 0xff), 283 static_cast<uint8>((param1 >> 8) & 0xff), 284 static_cast<uint8>((param1 >> 16) & 0xff), 285 self->TickToTimeDelta(param2)); 286 return; 287 case MIM_LONGDATA: 288 self->OnLongMessageReceived(reinterpret_cast<MIDIHDR*>(param1), 289 self->TickToTimeDelta(param2)); 290 return; 291 case MIM_CLOSE: 292 // TODO(yukawa): Implement crbug.com/279097. 293 return; 294 } 295 } 296 297 void OnShortMessageReceived(uint8 status_byte, 298 uint8 first_data_byte, 299 uint8 second_data_byte, 300 base::TimeDelta timestamp) { 301 if (device_to_be_closed()) 302 return; 303 const size_t len = GetMIDIMessageLength(status_byte); 304 if (len == 0 || port_index() == kInvalidPortIndex) 305 return; 306 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte }; 307 DCHECK_LE(len, arraysize(kData)); 308 manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF()); 309 } 310 311 void OnLongMessageReceived(MIDIHDR* header, base::TimeDelta timestamp) { 312 if (header != midi_header_.get()) 313 return; 314 MMRESULT result = MMSYSERR_NOERROR; 315 if (device_to_be_closed()) { 316 if (midi_header_ && 317 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { 318 result = midiInUnprepareHeader( 319 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); 320 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 321 << "Failed to uninitialize input buffer: " 322 << GetInErrorMessage(result); 323 } 324 return; 325 } 326 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) { 327 manager_->ReceiveMIDIData(port_index_, 328 reinterpret_cast<const uint8*>(header->lpData), 329 header->dwBytesRecorded, 330 timestamp.InSecondsF()); 331 } 332 result = midiInAddBuffer(midi_handle(), header, sizeof(*header)); 333 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 334 << "Failed to attach input port: " << GetInErrorMessage(result); 335 } 336 337 base::TimeDelta TickToTimeDelta(DWORD tick) const { 338 const base::TimeDelta delta = 339 base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick)); 340 return start_time_offset_ + delta; 341 } 342 343 MIDIManagerWin* manager_; 344 int port_index_; 345 HMIDIIN midi_handle_; 346 ScopedMIDIHDR midi_header_; 347 base::TimeDelta start_time_offset_; 348 bool started_; 349 bool device_to_be_closed_; 350 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InDeviceInfo); 351 }; 352 353 class MIDIManagerWin::OutDeviceInfo { 354 public: 355 ~OutDeviceInfo() { 356 Uninitialize(); 357 } 358 359 static scoped_ptr<OutDeviceInfo> Create(UINT device_id) { 360 scoped_ptr<OutDeviceInfo> obj(new OutDeviceInfo); 361 if (!obj->Initialize(device_id)) 362 obj.reset(); 363 return obj.Pass(); 364 } 365 366 HMIDIOUT midi_handle() const { 367 return midi_handle_; 368 } 369 370 void Quit() { 371 quitting_ = true; 372 } 373 374 void Send(const std::vector<uint8>& data) { 375 // Check if the attached device is still available or not. 376 if (!midi_handle_) 377 return; 378 379 // Give up sending MIDI messages here if the device is already closed. 380 // Note that this check is optional. Regardless of that we check |closed_| 381 // or not, nothing harmful happens as long as |midi_handle_| is still valid. 382 if (closed_) 383 return; 384 385 // MIDI Running status must be filtered out. 386 MIDIMessageQueue message_queue(false); 387 message_queue.Add(data); 388 std::vector<uint8> message; 389 while (!quitting_) { 390 message_queue.Get(&message); 391 if (message.empty()) 392 break; 393 // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes. 394 if (message.size() <= 3) 395 SendShortMIDIMessageInternal(midi_handle_, message); 396 else 397 SendLongMIDIMessageInternal(midi_handle_, message); 398 } 399 } 400 401 private: 402 OutDeviceInfo() 403 : midi_handle_(NULL), 404 closed_(false), 405 quitting_(false) {} 406 407 bool Initialize(DWORD device_id) { 408 Uninitialize(); 409 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE 410 // events. 411 // - MOM_DONE: SendLongMIDIMessageInternal() relies on this event to clean 412 // up the backing store where a long MIDI message is stored. 413 // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2) 414 // the MIDI device becomes unavailable for some reasons, e.g., the cable 415 // is disconnected. As for the former case, HMIDIOUT will be invalidated 416 // soon after the callback is finished. As for the later case, however, 417 // HMIDIOUT continues to be valid until midiOutClose() is called. 418 MMRESULT result = midiOutOpen(&midi_handle_, 419 device_id, 420 reinterpret_cast<DWORD_PTR>(&HandleMessage), 421 reinterpret_cast<DWORD_PTR>(this), 422 CALLBACK_FUNCTION); 423 if (result != MMSYSERR_NOERROR) { 424 DLOG(ERROR) << "Failed to open output device. " 425 << " id: " << device_id 426 << " message: "<< GetOutErrorMessage(result); 427 midi_handle_ = NULL; 428 return false; 429 } 430 return true; 431 } 432 433 void Uninitialize() { 434 if (!midi_handle_) 435 return; 436 437 MMRESULT result = midiOutReset(midi_handle_); 438 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 439 << "Failed to reset output port: " << GetOutErrorMessage(result); 440 result = midiOutClose(midi_handle_); 441 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 442 << "Failed to close output port: " << GetOutErrorMessage(result); 443 midi_handle_ = NULL; 444 closed_ = true; 445 } 446 447 static void CALLBACK HandleMessage(HMIDIOUT midi_out_handle, 448 UINT message, 449 DWORD_PTR instance, 450 DWORD_PTR param1, 451 DWORD_PTR param2) { 452 // This method can be called back on any thread depending on Windows 453 // multimedia subsystem and underlying MIDI drivers. 454 455 OutDeviceInfo* self = reinterpret_cast<OutDeviceInfo*>(instance); 456 if (!self) 457 return; 458 if (self->midi_handle() != midi_out_handle) 459 return; 460 switch (message) { 461 case MOM_DONE: { 462 // Take ownership of the MIDIHDR object. 463 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); 464 if (!header) 465 return; 466 MMRESULT result = midiOutUnprepareHeader( 467 self->midi_handle(), header.get(), sizeof(*header)); 468 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) 469 << "Failed to uninitialize output buffer: " 470 << GetOutErrorMessage(result); 471 return; 472 } 473 case MOM_CLOSE: 474 // No lock is required since this flag is just a hint to avoid 475 // unnecessary API calls that will result in failure anyway. 476 self->closed_ = true; 477 // TODO(yukawa): Implement crbug.com/279097. 478 return; 479 } 480 } 481 482 HMIDIOUT midi_handle_; 483 484 // True if the device is already closed. 485 volatile bool closed_; 486 487 // True if the MIDIManagerWin is trying to stop the sender thread. 488 volatile bool quitting_; 489 490 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutDeviceInfo); 491 }; 492 493 MIDIManagerWin::MIDIManagerWin() 494 : send_thread_("MIDISendThread") { 495 } 496 497 bool MIDIManagerWin::Initialize() { 498 const UINT num_in_devices = midiInGetNumDevs(); 499 in_devices_.reserve(num_in_devices); 500 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { 501 MIDIINCAPS caps = {}; 502 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps)); 503 if (result != MMSYSERR_NOERROR) { 504 DLOG(ERROR) << "Failed to obtain input device info: " 505 << GetInErrorMessage(result); 506 continue; 507 } 508 scoped_ptr<InDeviceInfo> in_device(InDeviceInfo::Create(this, device_id)); 509 if (!in_device) 510 continue; 511 MIDIPortInfo info( 512 base::IntToString(static_cast<int>(device_id)), 513 "", 514 base::WideToUTF8(caps.szPname), 515 base::IntToString(static_cast<int>(caps.vDriverVersion))); 516 AddInputPort(info); 517 in_device->set_port_index(input_ports_.size() - 1); 518 in_devices_.push_back(in_device.Pass()); 519 } 520 521 const UINT num_out_devices = midiOutGetNumDevs(); 522 out_devices_.reserve(num_out_devices); 523 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { 524 MIDIOUTCAPS caps = {}; 525 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps)); 526 if (result != MMSYSERR_NOERROR) { 527 DLOG(ERROR) << "Failed to obtain output device info: " 528 << GetOutErrorMessage(result); 529 continue; 530 } 531 scoped_ptr<OutDeviceInfo> out_port(OutDeviceInfo::Create(device_id)); 532 if (!out_port) 533 continue; 534 MIDIPortInfo info( 535 base::IntToString(static_cast<int>(device_id)), 536 "", 537 base::WideToUTF8(caps.szPname), 538 base::IntToString(static_cast<int>(caps.vDriverVersion))); 539 AddOutputPort(info); 540 out_devices_.push_back(out_port.Pass()); 541 } 542 543 return true; 544 } 545 546 MIDIManagerWin::~MIDIManagerWin() { 547 // Cleanup order is important. |send_thread_| must be stopped before 548 // |out_devices_| is cleared. 549 for (size_t i = 0; i < output_ports_.size(); ++i) 550 out_devices_[i]->Quit(); 551 send_thread_.Stop(); 552 553 out_devices_.clear(); 554 output_ports_.clear(); 555 in_devices_.clear(); 556 input_ports_.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