OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 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 |
OLD | NEW |