Chromium Code Reviews| 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 use option 2) in favor of asynchronous design. | |
| 104 // | |
| 105 // Open question: | |
| 106 // In the era of USB-MIDI, OS built-in driver seems to always perform | |
| 107 // synchronously. Thus option 1) still might be a good option. | |
| 108 | |
| 109 ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size())); | |
| 110 for (size_t i = 0; i < message.size(); ++i) | |
| 111 midi_header->lpData[i] = static_cast<char>(message[i]); | |
| 112 | |
| 113 MMRESULT result = midiOutPrepareHeader( | |
| 114 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
| 115 if (result != MMSYSERR_NOERROR) { | |
| 116 DLOG(ERROR) << "Failed to prepare output buffer: " | |
| 117 << GetOutErrorMessage(result); | |
| 118 return; | |
| 119 } | |
| 120 | |
| 121 result = midiOutLongMsg( | |
| 122 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
| 123 if (result != MMSYSERR_NOERROR) { | |
| 124 DLOG(ERROR) << "Failed to output long message: " | |
| 125 << GetOutErrorMessage(result); | |
| 126 result = midiOutUnprepareHeader( | |
| 127 midi_out_handle, midi_header.get(), sizeof(*midi_header)); | |
| 128 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 129 << "Failed to uninitialize output buffer: " | |
| 130 << GetOutErrorMessage(result); | |
| 131 return; | |
| 132 } | |
| 133 | |
| 134 // The ownership of |midi_header| is moved to MOM_DONE event handler. | |
| 135 midi_header.release(); | |
| 136 } | |
| 137 | |
| 138 void SendMIDIDataInternal(HMIDIOUT midi_out_handle, | |
| 139 const std::vector<uint8>& data) { | |
| 140 // MIDI Running status must not be enabled for |data|. | |
| 141 MIDIMessageQueue message_queue(false); | |
| 142 message_queue.Add(data); | |
| 143 std::vector<uint8> message; | |
| 144 while (true) { | |
| 145 message_queue.Get(&message); | |
| 146 if (message.empty()) | |
| 147 break; | |
| 148 // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes. | |
| 149 if (message.size() <= 3) | |
| 150 SendShortMIDIMessageInternal(midi_out_handle, message); | |
| 151 else | |
| 152 SendLongMIDIMessageInternal(midi_out_handle, message); | |
| 153 } | |
| 154 } | |
| 155 | |
| 156 } // namespace | |
| 157 | |
| 158 class MIDIManagerWin::InPortInfo { | |
| 159 public: | |
| 160 ~InPortInfo() { | |
| 161 Uninitialize(); | |
| 162 } | |
| 163 void set_port_index(int index) { | |
| 164 port_index_ = index; | |
| 165 } | |
| 166 int port_index() const { | |
| 167 return port_index_; | |
| 168 } | |
| 169 bool device_to_be_closed() const { | |
| 170 return device_to_be_closed_; | |
| 171 } | |
| 172 HMIDIIN midi_handle() const { | |
| 173 return midi_handle_; | |
| 174 } | |
| 175 const base::TimeDelta& start_time_offset() const { | |
| 176 return start_time_offset_; | |
| 177 } | |
| 178 | |
| 179 static scoped_ptr<InPortInfo> Create(MIDIManagerWin* manager, | |
| 180 UINT device_id) { | |
| 181 scoped_ptr<InPortInfo> obj(new InPortInfo(manager)); | |
| 182 if (!obj->Initialize(device_id)) | |
| 183 obj.reset(NULL); | |
| 184 return obj.Pass(); | |
| 185 } | |
| 186 | |
| 187 private: | |
| 188 static const int kInvalidPortIndex = -1; | |
| 189 static const size_t kBufferLength = 32 * 1024; | |
| 190 | |
| 191 explicit InPortInfo(MIDIManagerWin* manager) | |
| 192 : manager_(manager), | |
| 193 port_index_(kInvalidPortIndex), | |
| 194 midi_handle_(NULL), | |
| 195 started_(false), | |
| 196 device_to_be_closed_(false) { | |
| 197 } | |
| 198 | |
| 199 bool Initialize(DWORD device_id) { | |
| 200 Uninitialize(); | |
| 201 midi_header_ = CreateMIDIHDR(kBufferLength); | |
| 202 | |
| 203 // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, and | |
| 204 // MIM_CLOSE events. | |
| 205 // - MIM_DATA: This is the only way to get a short MIDI message with | |
| 206 // timestamp information. | |
| 207 // - MIM_LONGDATA: This is the only way to get a long MIDI message with | |
| 208 // timestamp information. | |
| 209 // - MIN_CLOSE: This event is notified when the device is removed logically | |
| 210 // or physically. | |
| 211 MMRESULT result = midiInOpen(&midi_handle_, | |
| 212 device_id, | |
| 213 reinterpret_cast<DWORD_PTR>(&HandleMessage), | |
| 214 reinterpret_cast<DWORD_PTR>(this), | |
| 215 CALLBACK_FUNCTION); | |
| 216 if (result != MMSYSERR_NOERROR) { | |
| 217 DLOG(ERROR) << "Failed to open output device. " | |
| 218 << " id: " << device_id | |
| 219 << " message: " << GetInErrorMessage(result); | |
| 220 return false; | |
| 221 } | |
| 222 result = midiInPrepareHeader( | |
| 223 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
| 224 if (result != MMSYSERR_NOERROR) { | |
| 225 DLOG(ERROR) << "Failed to initialize input buffer: " | |
| 226 << GetInErrorMessage(result); | |
| 227 return false; | |
| 228 } | |
| 229 result = midiInAddBuffer( | |
| 230 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
| 231 if (result != MMSYSERR_NOERROR) { | |
| 232 DLOG(ERROR) << "Failed to attach input buffer: " | |
| 233 << GetInErrorMessage(result); | |
| 234 return false; | |
| 235 } | |
| 236 result = midiInStart(midi_handle_); | |
| 237 if (result != MMSYSERR_NOERROR) { | |
| 238 DLOG(ERROR) << "Failed to start input port: " | |
| 239 << GetInErrorMessage(result); | |
| 240 return false; | |
| 241 } | |
| 242 started_ = true; | |
| 243 start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks(); | |
| 244 return true; | |
| 245 } | |
| 246 | |
| 247 void Uninitialize() { | |
| 248 MMRESULT result = MMSYSERR_NOERROR; | |
| 249 if (midi_handle_ && started_) { | |
| 250 result = midiInStop(midi_handle_); | |
| 251 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 252 << "Failed to stop input port: " << GetInErrorMessage(result); | |
| 253 started_ = false; | |
| 254 start_time_offset_ = base::TimeDelta(); | |
| 255 } | |
| 256 if (midi_handle_) { | |
| 257 // midiInReset flushes pending messages. We ignore these messages. | |
| 258 device_to_be_closed_ = true; | |
| 259 result = midiInReset(midi_handle_); | |
| 260 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 261 << "Failed to reset input port: " << GetInErrorMessage(result); | |
| 262 result = midiInClose(midi_handle_); | |
| 263 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 264 << "Failed to close input port: " << GetInErrorMessage(result); | |
| 265 } | |
| 266 device_to_be_closed_ = false; | |
| 267 } | |
| 268 | |
| 269 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, | |
| 270 UINT message, | |
| 271 DWORD_PTR instance, | |
| 272 DWORD_PTR param1, | |
| 273 DWORD_PTR param2) { | |
| 274 // This method is called back by Windows multimedia subsystem and not always | |
| 275 // in the UI thread. | |
| 276 InPortInfo* self = reinterpret_cast<InPortInfo*>(instance); | |
| 277 if (!self) | |
| 278 return; | |
| 279 if (self->midi_handle() != midi_in_handle) | |
| 280 return; | |
| 281 self->HandleMessageInternal(message, param1, param2); | |
| 282 } | |
| 283 | |
| 284 void HandleMessageInternal(UINT message, DWORD_PTR param1, DWORD_PTR param2) { | |
| 285 // This method is called from HandleMessage method and not always in the UI | |
| 286 // thread. | |
| 287 switch (message) { | |
| 288 case MIM_DATA: | |
| 289 HandleShortMessageInternal(static_cast<uint8>(param1 & 0xff), | |
| 290 static_cast<uint8>((param1 >> 8) & 0xff), | |
| 291 static_cast<uint8>((param1 >> 16) & 0xff), | |
| 292 TickToTimeDelta(param2)); | |
| 293 return; | |
| 294 case MIM_LONGDATA: | |
| 295 HandleLongMessageInternal(reinterpret_cast<MIDIHDR*>(param1), | |
| 296 TickToTimeDelta(param2)); | |
| 297 return; | |
| 298 case MIM_CLOSE: | |
| 299 HandleCloseMessageInternal(); | |
| 300 } | |
| 301 } | |
| 302 | |
| 303 void HandleShortMessageInternal(uint8 status_byte, | |
| 304 uint8 first_data_byte, | |
| 305 uint8 second_data_byte, | |
| 306 base::TimeDelta timestamp) { | |
| 307 if (device_to_be_closed()) | |
| 308 return; | |
| 309 const size_t len = GetMIDIMessageLength(status_byte); | |
| 310 if (len == 0 || port_index() == kInvalidPortIndex) | |
| 311 return; | |
| 312 const uint8 kData[] = { status_byte, first_data_byte, second_data_byte }; | |
| 313 DCHECK_LE(len, arraysize(kData)); | |
| 314 manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF()); | |
| 315 } | |
| 316 | |
| 317 void HandleLongMessageInternal(MIDIHDR* header, base::TimeDelta timestamp) { | |
| 318 if (header != midi_header_.get()) | |
| 319 return; | |
| 320 MMRESULT result = MMSYSERR_NOERROR; | |
| 321 if (device_to_be_closed()) { | |
| 322 if (midi_header_ && | |
| 323 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { | |
| 324 result = midiInUnprepareHeader( | |
| 325 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
| 326 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 327 << "Failed to uninitialize input buffer: " | |
| 328 << GetInErrorMessage(result); | |
| 329 } | |
| 330 return; | |
| 331 } | |
| 332 if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) { | |
| 333 manager_->ReceiveMIDIData(port_index_, | |
| 334 reinterpret_cast<const uint8*>(header->lpData), | |
| 335 header->dwBytesRecorded, | |
| 336 timestamp.InSecondsF()); | |
| 337 } | |
| 338 result = midiInAddBuffer(midi_handle(), header, sizeof(*header)); | |
| 339 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 340 << "Failed to attach input port: " << GetInErrorMessage(result); | |
| 341 } | |
| 342 | |
| 343 void HandleCloseMessageInternal() { | |
| 344 if (midi_handle_ && midi_header_ && | |
| 345 (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) { | |
| 346 MMRESULT result = midiInUnprepareHeader( | |
| 347 midi_handle_, midi_header_.get(), sizeof(*midi_header_)); | |
| 348 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 349 << "Failed to uninitialize input buffer: " | |
| 350 << GetInErrorMessage(result); | |
| 351 } | |
| 352 midi_header_.reset(); | |
| 353 midi_handle_ = NULL; | |
| 354 port_index_ = kInvalidPortIndex; | |
| 355 return; | |
| 356 } | |
| 357 | |
| 358 base::TimeDelta TickToTimeDelta(DWORD tick) const { | |
| 359 const base::TimeDelta delta = | |
| 360 base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick)); | |
| 361 return start_time_offset_ + delta; | |
| 362 } | |
| 363 | |
| 364 MIDIManagerWin* manager_; | |
| 365 int port_index_; | |
| 366 HMIDIIN midi_handle_; | |
| 367 ScopedMIDIHDR midi_header_; | |
| 368 base::TimeDelta start_time_offset_; | |
| 369 bool started_; | |
| 370 bool device_to_be_closed_; | |
| 371 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InPortInfo); | |
| 372 }; | |
| 373 | |
| 374 class MIDIManagerWin::OutPortInfo { | |
| 375 public: | |
| 376 ~OutPortInfo() { | |
| 377 Uninitialize(); | |
| 378 } | |
| 379 | |
| 380 HMIDIOUT midi_handle() const { | |
| 381 return midi_handle_; | |
| 382 } | |
| 383 | |
| 384 static scoped_ptr<OutPortInfo> Create(UINT device_id) { | |
| 385 scoped_ptr<OutPortInfo> obj(new OutPortInfo); | |
| 386 if (!obj->Initialize(device_id)) | |
| 387 obj.reset(NULL); | |
| 388 return obj.Pass(); | |
| 389 } | |
| 390 | |
| 391 private: | |
| 392 OutPortInfo() | |
| 393 : midi_handle_(NULL) {} | |
| 394 | |
| 395 bool Initialize(DWORD device_id) { | |
| 396 Uninitialize(); | |
| 397 // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE and MOM_CLOSE | |
| 398 // events. | |
| 399 // - MOM_DONE: SendLongMIDIMessageInternal relies on this event to clean up | |
| 400 // the backing store where a long MIDI message is stored. | |
| 401 // - MOM_CLOSE: This event is notified when the device is removed logically | |
| 402 // or physically. | |
| 403 MMRESULT result = midiOutOpen(&midi_handle_, | |
| 404 device_id, | |
| 405 reinterpret_cast<DWORD_PTR>(&HandleMessage), | |
| 406 reinterpret_cast<DWORD_PTR>(this), | |
| 407 CALLBACK_FUNCTION); | |
| 408 if (result != MMSYSERR_NOERROR) { | |
| 409 DLOG(ERROR) << "Failed to open output device. " | |
| 410 << " id: " << device_id | |
| 411 << " message: "<< GetOutErrorMessage(result); | |
| 412 midi_handle_ = NULL; | |
| 413 return false; | |
| 414 } | |
| 415 return true; | |
| 416 } | |
| 417 | |
| 418 void Uninitialize() { | |
| 419 if (midi_handle_ == NULL) | |
| 420 return; | |
| 421 MMRESULT result = midiOutReset(midi_handle_); | |
| 422 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 423 << "Failed to reset output port: " << GetOutErrorMessage(result); | |
| 424 result = midiOutClose(midi_handle_); | |
| 425 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 426 << "Failed to close output port: " << GetOutErrorMessage(result); | |
| 427 midi_handle_ = NULL; | |
| 428 } | |
| 429 | |
| 430 static void CALLBACK HandleMessage(HMIDIIN midi_in_handle, | |
| 431 UINT message, | |
| 432 DWORD_PTR instance, | |
| 433 DWORD_PTR param1, | |
| 434 DWORD_PTR param2) { | |
| 435 // This method is called back by Windows multimedia subsystem and not always | |
| 436 // in the UI thread. | |
| 437 | |
| 438 OutPortInfo* self = reinterpret_cast<OutPortInfo*>(instance); | |
| 439 if (!self) | |
| 440 return; | |
| 441 switch (message) { | |
| 442 case MOM_DONE: { | |
| 443 // Take the ownership of the MIDIHDR object. | |
| 444 ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1)); | |
| 445 if (!header) | |
| 446 return; | |
| 447 MMRESULT result = midiOutUnprepareHeader( | |
| 448 self->midi_handle(), header.get(), sizeof(*header)); | |
| 449 DLOG_IF(ERROR, result != MMSYSERR_NOERROR) | |
| 450 << "Failed to uninitialize output buffer: " | |
| 451 << GetOutErrorMessage(result); | |
| 452 return; | |
| 453 } | |
| 454 case MOM_CLOSE: | |
| 455 self->midi_handle_ = NULL; | |
| 456 return; | |
| 457 } | |
| 458 } | |
| 459 | |
| 460 HMIDIOUT midi_handle_; | |
| 461 DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutPortInfo); | |
| 462 }; | |
| 463 | |
| 464 MIDIManagerWin::MIDIManagerWin() { | |
|
Takashi Toyoshima
2013/11/27 14:18:52
I don't think I understand this comment correctly,
yukawa
2013/11/27 15:23:37
Oops, this is just a garbage. Removed.
| |
| 465 // Wait for |send_thread_| is finished before all the members and | |
| 466 // overriden methods of MIDIManagerWin are invalidated. | |
| 467 send_thread_.reset(); | |
| 468 } | |
| 469 | |
| 470 bool MIDIManagerWin::Initialize() { | |
| 471 { | |
| 472 const UINT num_in_devices = midiInGetNumDevs(); | |
| 473 in_ports_.reserve(num_in_devices); | |
| 474 for (UINT device_id = 0; device_id < num_in_devices; ++device_id) { | |
| 475 MIDIINCAPS caps = {}; | |
| 476 MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps)); | |
| 477 if (result != MMSYSERR_NOERROR) { | |
| 478 DLOG(ERROR) << "Failed to obtain input device info: " | |
| 479 << GetInErrorMessage(result); | |
| 480 continue; | |
| 481 } | |
| 482 scoped_ptr<InPortInfo> in_port(InPortInfo::Create(this, device_id)); | |
| 483 if (!in_port) | |
| 484 continue; | |
| 485 MIDIPortInfo info( | |
| 486 base::IntToString(static_cast<int>(device_id)), | |
| 487 "", | |
| 488 base::WideToUTF8(caps.szPname), | |
| 489 base::IntToString(static_cast<int>(caps.vDriverVersion))); | |
| 490 AddInputPort(info); | |
| 491 in_port->set_port_index(input_ports_.size() - 1); | |
| 492 in_ports_.push_back(in_port.Pass()); | |
| 493 } | |
| 494 } | |
| 495 { | |
| 496 const UINT num_out_devices = midiOutGetNumDevs(); | |
| 497 out_ports_.reserve(num_out_devices); | |
| 498 for (UINT device_id = 0; device_id < num_out_devices; ++device_id) { | |
| 499 MIDIOUTCAPS caps = {}; | |
| 500 MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps)); | |
| 501 if (result != MMSYSERR_NOERROR) { | |
| 502 DLOG(ERROR) << "Failed to obtain output device info: " | |
| 503 << GetOutErrorMessage(result); | |
| 504 continue; | |
| 505 } | |
| 506 scoped_ptr<OutPortInfo> out_port(OutPortInfo::Create(device_id)); | |
| 507 if (!out_port) | |
| 508 continue; | |
| 509 MIDIPortInfo info( | |
| 510 base::IntToString(static_cast<int>(device_id)), | |
| 511 "", | |
| 512 base::WideToUTF8(caps.szPname), | |
| 513 base::IntToString(static_cast<int>(caps.vDriverVersion))); | |
| 514 AddOutputPort(info); | |
| 515 out_ports_.push_back(out_port.Pass()); | |
| 516 } | |
| 517 } | |
| 518 return true; | |
| 519 } | |
| 520 | |
| 521 MIDIManagerWin::~MIDIManagerWin() { | |
| 522 // |send_thread_| must be terminated here to ensure that | |
| 523 // MIDIManagerWin::SendMIDIData will not be called any longer. A drawback | |
| 524 // of this approach is if |send_thread_| is somehow blocked forever, | |
| 525 // the UI thread is also blocked forever. | |
| 526 send_thread_.reset(NULL); | |
|
Takashi Toyoshima
2013/11/27 14:18:52
Hum... The same issue seems to happen in mac poten
yukawa
2013/11/27 15:23:37
Probably yes. I'm making a different CL to do it.
| |
| 527 } | |
| 528 | |
| 529 void MIDIManagerWin::SendMIDIData(MIDIManagerClient* client, | |
| 530 uint32 port_index, | |
| 531 const std::vector<uint8>& data, | |
| 532 double timestamp) { | |
| 533 DCHECK(CurrentlyOnMIDISendThread()); | |
| 534 // Caveat: Whether |this| is still valid or not is non-trivial question | |
| 535 // because this method is called back in a background thread that is owned by | |
| 536 // MIDIManager rather than MIDIManagerWin. Currently |this| is considered to | |
| 537 // be always valid because ~MIDIManagerWin() internally waits for the | |
| 538 // termination of this background thread. | |
| 539 | |
| 540 if (out_ports_.size() <= port_index) | |
| 541 return; | |
| 542 | |
| 543 // Check if the target device is still available. Note that |handle| can be | |
| 544 // NULL once the device is logically or physically removed. | |
| 545 const HMIDIOUT handle = out_ports_[port_index]->midi_handle(); | |
| 546 if (!handle) | |
| 547 return; | |
| 548 | |
| 549 base::TimeDelta delay; | |
| 550 if (timestamp != 0.0) { | |
| 551 base::TimeTicks time_to_send = | |
| 552 base::TimeTicks() + base::TimeDelta::FromMicroseconds( | |
| 553 timestamp * base::Time::kMicrosecondsPerSecond); | |
| 554 delay = time_to_send - base::TimeTicks::Now(); | |
| 555 } | |
| 556 | |
| 557 if (delay.InMilliseconds() <= 0) { | |
| 558 SendMIDIDataInternal(handle, data); | |
| 559 } else { | |
| 560 base::MessageLoop::current()->PostDelayedTask( | |
| 561 FROM_HERE, | |
| 562 base::Bind(&SendMIDIDataInternal, handle, data), | |
| 563 delay); | |
| 564 } | |
| 565 | |
| 566 client->AccumulateMIDIBytesSent(data.size()); | |
| 567 } | |
| 568 | |
| 569 MIDIManager* MIDIManager::Create() { | |
| 570 return new MIDIManagerWin(); | |
| 571 } | |
| 572 | |
| 573 } // namespace media | |
| OLD | NEW |