Commit 3e47c6db authored by toyoshim's avatar toyoshim Committed by Commit bot

Web MIDI: Improve device change monitornig on Windows

Current implementation monitors DBT_DEVICEARRIVAL mesage, but that
is not a right message to monitor device changes since actual device
list that OS holds will be updated a little later. Instead, monitor
DBT_DEVNODES_CHANGES that is delivered after the list being updated.

In Chrome, SystemMessageWindowWin invokes base::SystemMonitor's observer
with DEVTYPE_UNKNOWN for DBT_DEVNODES_CHANGED, and the SystemMonitor
provides a way to observe the event on the registered thread.
Using the monitor must be the right implementation.

BUG=472980

Review URL: https://codereview.chromium.org/1056333002

Cr-Commit-Position: refs/heads/master@{#324031}
parent 61b74f33
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "base/memory/scoped_vector.h" #include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/system_monitor/system_monitor.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace media { namespace media {
...@@ -245,6 +246,9 @@ TEST_F(MidiManagerTest, AbortSession) { ...@@ -245,6 +246,9 @@ TEST_F(MidiManagerTest, AbortSession) {
} }
TEST_F(MidiManagerTest, CreateMidiManager) { TEST_F(MidiManagerTest, CreateMidiManager) {
// SystemMonitor is needed on Windows.
base::SystemMonitor system_monitor;
scoped_ptr<FakeMidiManagerClient> client; scoped_ptr<FakeMidiManagerClient> client;
client.reset(new FakeMidiManagerClient); client.reset(new FakeMidiManagerClient);
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include "media/midi/midi_manager_win.h" #include "media/midi/midi_manager_win.h"
#include <windows.h> #include <windows.h>
#include <dbt.h>
#include <ks.h> #include <ks.h>
#include <ksmedia.h> #include <ksmedia.h>
#include <mmreg.h> #include <mmreg.h>
...@@ -36,6 +35,7 @@ ...@@ -36,6 +35,7 @@
#include "base/strings/string_piece.h" #include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/system_monitor/system_monitor.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
...@@ -49,17 +49,12 @@ ...@@ -49,17 +49,12 @@
namespace media { namespace media {
namespace { namespace {
const wchar_t kWindowClassName[] = L"ChromeMidiDeviceMonitorWindow";
const int kOnDeviceArrivalDelayMilliSec = 500;
static const size_t kBufferLength = 32 * 1024; static const size_t kBufferLength = 32 * 1024;
// We assume that nullpter represents an invalid MIDI handle. // We assume that nullpter represents an invalid MIDI handle.
const HMIDIIN kInvalidMidiInHandle = nullptr; const HMIDIIN kInvalidMidiInHandle = nullptr;
const HMIDIOUT kInvalidMidiOutHandle = nullptr; const HMIDIOUT kInvalidMidiOutHandle = nullptr;
// Note that |RegisterDeviceNotificationW| returns nullptr as an invalid handle.
const HDEVNOTIFY kInvalidDeviceNotifyHandle = nullptr;
std::string GetInErrorMessage(MMRESULT result) { std::string GetInErrorMessage(MMRESULT result) {
wchar_t text[MAXERRORLENGTH]; wchar_t text[MAXERRORLENGTH];
MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text)); MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
...@@ -310,67 +305,6 @@ using PortNumberCache = base::hash_map< ...@@ -310,67 +305,6 @@ using PortNumberCache = base::hash_map<
std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>, std::priority_queue<uint32, std::vector<uint32>, std::greater<uint32>>,
MidiDeviceInfo::Hasher>; MidiDeviceInfo::Hasher>;
class DeviceMonitorWindow final {
public:
explicit DeviceMonitorWindow(base::Closure on_device_arrival)
: on_device_arrival_(on_device_arrival),
dev_notify_handle_(kInvalidDeviceNotifyHandle) {
window_.reset(new base::win::MessageWindow);
if (!window_->CreateNamed(base::Bind(&DeviceMonitorWindow::HandleMessage,
base::Unretained(this)),
base::string16(kWindowClassName))) {
LOG(ERROR) << "Failed to create message window: " << kWindowClassName;
window_.reset();
}
DEV_BROADCAST_DEVICEINTERFACE kBloadcastRequest = {
sizeof(DEV_BROADCAST_DEVICEINTERFACE), DBT_DEVTYP_DEVICEINTERFACE,
};
dev_notify_handle_ = RegisterDeviceNotificationW(
window_->hwnd(), &kBloadcastRequest,
DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
}
~DeviceMonitorWindow() {
if (dev_notify_handle_ != kInvalidDeviceNotifyHandle) {
BOOL result = UnregisterDeviceNotification(dev_notify_handle_);
if (!result) {
const DWORD last_error = GetLastError();
DLOG(ERROR) << "UnregisterDeviceNotification failed. error: "
<< last_error;
}
dev_notify_handle_ = kInvalidDeviceNotifyHandle;
}
}
private:
bool HandleMessage(UINT message,
WPARAM wparam,
LPARAM lparam,
LRESULT* result) {
if (message == WM_DEVICECHANGE) {
switch (wparam) {
case DBT_DEVICEARRIVAL: {
device_arrival_timer_.Stop();
device_arrival_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kOnDeviceArrivalDelayMilliSec),
on_device_arrival_);
break;
}
}
}
return false;
}
base::Closure on_device_arrival_;
base::OneShotTimer<DeviceMonitorWindow> device_arrival_timer_;
base::ThreadChecker thread_checker_;
scoped_ptr<base::win::MessageWindow> window_;
HDEVNOTIFY dev_notify_handle_;
DISALLOW_COPY_AND_ASSIGN(DeviceMonitorWindow);
};
struct MidiInputDeviceState final : base::RefCounted<MidiInputDeviceState> { struct MidiInputDeviceState final : base::RefCounted<MidiInputDeviceState> {
explicit MidiInputDeviceState(const MidiDeviceInfo& device_info) explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
: device_info(device_info), : device_info(device_info),
...@@ -425,29 +359,27 @@ struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> { ...@@ -425,29 +359,27 @@ struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> {
// The core logic of MIDI device handling for Windows. Basically this class is // The core logic of MIDI device handling for Windows. Basically this class is
// shared among following 4 threads: // shared among following 4 threads:
// 1. Device Monitor Thread // 1. Chrome IO Thread
// 2. OS Multimedia Thread // 2. OS Multimedia Thread
// 3. Misc. Task Thread // 3. Task Thread
// 4. Sender Thread // 4. Sender Thread
// //
// Device Monitor Thread: // Chrome IO Thread:
// This is a standalone UI thread that is dedicated for a message-only window // MidiManager runs on Chrome IO thread. Device change notification is
// which will receive WM_DEVICECHANGE Win32 message whose // delivered to the thread through the SystemMonitor service.
// wParam == DBT_DEVICEARRIVAL. This event will be used as the trigger to open // OnDevicesChanged() callback is invoked to update the MIDI device list.
// all the new MIDI devices. Note that in the current implementation we will // Note that in the current implementation we will try to open all the
// try to open all the existing devices in practice. This is OK because trying // existing devices in practice. This is OK because trying to reopen a MIDI
// to reopen a MIDI device that is already opened would simply fail, and there // device that is already opened would simply fail, and there is no unwilling
// is no unwilling side effect. Note also that this thread isn't responsible // side effect.
// for updating the device database. It will be handled by MIM_OPEN/MOM_OPEN
// events dispatched to OS Multimedia Thread.
// //
// OS Multimedia Thread: // OS Multimedia Thread:
// This thread is maintained by the OS as a part of MIDI runtime, and // This thread is maintained by the OS as a part of MIDI runtime, and
// responsible for receiving all the system initiated events such as device // responsible for receiving all the system initiated events such as device
// open and close. For performance reasons, most of potentially blocking // close, and receiving data. For performance reasons, most of potentially
// operations will be dispatched into Misc. Task Thread. // blocking operations will be dispatched into Task Thread.
// //
// Misc. Task Thread: // Task Thread:
// This thread will be used to call back following methods of MidiManager. // This thread will be used to call back following methods of MidiManager.
// - MidiManager::CompleteInitialization // - MidiManager::CompleteInitialization
// - MidiManager::AddInputPort // - MidiManager::AddInputPort
...@@ -458,28 +390,27 @@ struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> { ...@@ -458,28 +390,27 @@ struct MidiOutputDeviceState final : base::RefCounted<MidiOutputDeviceState> {
// //
// Sender Thread: // Sender Thread:
// This thread will be used to call Win32 APIs to send MIDI message at the // This thread will be used to call Win32 APIs to send MIDI message at the
// specified time. We don't want to call MIDI send APIs on Misc. Task Thread // specified time. We don't want to call MIDI send APIs on Task Thread
// because those APIs could be performed synchronously, hence they could block // because those APIs could be performed synchronously, hence they could block
// the caller thread for a while. See the comment in // the caller thread for a while. See the comment in
// SendLongMidiMessageInternal for details. Currently we expect that the // SendLongMidiMessageInternal for details. Currently we expect that the
// blocking time would be less than 1 second. // blocking time would be less than 1 second.
class MidiServiceWinImpl : public MidiServiceWin { class MidiServiceWinImpl : public MidiServiceWin,
public base::SystemMonitor::DevicesChangedObserver {
public: public:
MidiServiceWinImpl() MidiServiceWinImpl()
: delegate_(nullptr), : delegate_(nullptr),
monitor_thread_("Windows MIDI device monitor thread"),
sender_thread_("Windows MIDI sender thread"), sender_thread_("Windows MIDI sender thread"),
task_thread_("Windows MIDI task thread"), task_thread_("Windows MIDI task thread"),
destructor_started(false) {} destructor_started(false) {}
~MidiServiceWinImpl() final { ~MidiServiceWinImpl() final {
// Start() and Stop() of the threads, and AddDevicesChangeObserver() and
// RemoveDevicesChangeObserver() should be called on the same thread.
CHECK(thread_checker_.CalledOnValidThread());
destructor_started = true; destructor_started = true;
if (monitor_thread_.IsRunning()) { base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
monitor_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&MidiServiceWinImpl::StopDeviceMonitorOnMonitorThreadSync,
base::Unretained(this)));
}
{ {
std::vector<HMIDIIN> input_devices; std::vector<HMIDIIN> input_devices;
{ {
...@@ -522,23 +453,32 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -522,23 +453,32 @@ class MidiServiceWinImpl : public MidiServiceWin {
} }
} }
} }
monitor_thread_.Stop();
sender_thread_.Stop(); sender_thread_.Stop();
task_thread_.Stop(); task_thread_.Stop();
} }
// MidiServiceWin overrides: // MidiServiceWin overrides:
void InitializeAsync(MidiServiceWinDelegate* delegate) final { void InitializeAsync(MidiServiceWinDelegate* delegate) final {
// Start() and Stop() of the threads, and AddDevicesChangeObserver() and
// RemoveDevicesChangeObserver() should be called on the same thread.
CHECK(thread_checker_.CalledOnValidThread());
delegate_ = delegate; delegate_ = delegate;
sender_thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); sender_thread_.Start();
task_thread_.StartWithOptions( task_thread_.Start();
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
monitor_thread_.StartWithOptions( // Start monitoring device changes. This should start before the
base::Thread::Options(base::MessageLoop::TYPE_UI, 0)); // following UpdateDeviceList() call not to miss the event happening
monitor_thread_.message_loop()->PostTask( // between the call and the observer registration.
FROM_HERE, base::Bind(&MidiServiceWinImpl::InitializeOnMonitorThread, base::SystemMonitor::Get()->AddDevicesChangedObserver(this);
base::Unretained(this)));
UpdateDeviceList();
task_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
base::Unretained(this), MIDI_OK));
} }
void SendMidiDataAsync(uint32 port_number, void SendMidiDataAsync(uint32 port_number,
...@@ -576,6 +516,25 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -576,6 +516,25 @@ class MidiServiceWinImpl : public MidiServiceWin {
} }
} }
// base::SystemMonitor::DevicesChangedObserver overrides:
void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final {
CHECK(thread_checker_.CalledOnValidThread());
if (destructor_started)
return;
switch (device_type) {
case base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE:
case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
// Add case of other unrelated device types here.
return;
case base::SystemMonitor::DEVTYPE_UNKNOWN:
// Interested in MIDI devices. Try updating the device list.
UpdateDeviceList();
break;
// No default here to capture new DeviceType by compile time.
}
}
private: private:
scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle( scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
HMIDIIN midi_handle) { HMIDIIN midi_handle) {
...@@ -599,11 +558,18 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -599,11 +558,18 @@ class MidiServiceWinImpl : public MidiServiceWin {
return output_ports_[port_number]; return output_ports_[port_number];
} }
void UpdateDeviceList() {
task_thread_.message_loop()->PostTask(
FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread,
base::Unretained(this)));
}
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Callbacks on the OS multimedia thread. // Callbacks on the OS multimedia thread.
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
static void CALLBACK OnMidiInEventOnMultimediaThread(HMIDIIN midi_in_handle, static void CALLBACK
OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle,
UINT message, UINT message,
DWORD_PTR instance, DWORD_PTR instance,
DWORD_PTR param1, DWORD_PTR param1,
...@@ -613,7 +579,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -613,7 +579,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
return; return;
switch (message) { switch (message) {
case MIM_OPEN: case MIM_OPEN:
self->OnMidiInOpenOnMultimediaThread(midi_in_handle); self->OnMidiInOpen(midi_in_handle);
break; break;
case MIM_DATA: case MIM_DATA:
self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2); self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
...@@ -628,7 +594,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -628,7 +594,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
} }
} }
void OnMidiInOpenOnMultimediaThread(HMIDIIN midi_in_handle) { void OnMidiInOpen(HMIDIIN midi_in_handle) {
UINT device_id = 0; UINT device_id = 0;
MMRESULT result = midiInGetID(midi_in_handle, &device_id); MMRESULT result = midiInGetID(midi_in_handle, &device_id);
if (result != MMSYSERR_NOERROR) { if (result != MMSYSERR_NOERROR) {
...@@ -787,7 +753,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -787,7 +753,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
} }
static void CALLBACK static void CALLBACK
OnMidiOutEventOnMultimediaThread(HMIDIOUT midi_out_handle, OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle,
UINT message, UINT message,
DWORD_PTR instance, DWORD_PTR instance,
DWORD_PTR param1, DWORD_PTR param1,
...@@ -797,7 +763,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -797,7 +763,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
return; return;
switch (message) { switch (message) {
case MOM_OPEN: case MOM_OPEN:
self->OnMidiOutOpenOnMultimediaThread(midi_out_handle, param1, param2); self->OnMidiOutOpen(midi_out_handle, param1, param2);
break; break;
case MOM_DONE: case MOM_DONE:
self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1); self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
...@@ -808,7 +774,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -808,7 +774,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
} }
} }
void OnMidiOutOpenOnMultimediaThread(HMIDIOUT midi_out_handle, void OnMidiOutOpen(HMIDIOUT midi_out_handle,
DWORD_PTR param1, DWORD_PTR param1,
DWORD_PTR param2) { DWORD_PTR param2) {
UINT device_id = 0; UINT device_id = 0;
...@@ -907,95 +873,6 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -907,95 +873,6 @@ class MidiServiceWinImpl : public MidiServiceWin {
MIDI_PORT_DISCONNECTED)); MIDI_PORT_DISCONNECTED));
} }
/////////////////////////////////////////////////////////////////////////////
// Callbacks on the monitor thread.
/////////////////////////////////////////////////////////////////////////////
void AssertOnMonitorThread() {
DCHECK_EQ(monitor_thread_.thread_id(), base::PlatformThread::CurrentId());
}
void InitializeOnMonitorThread() {
AssertOnMonitorThread();
// Synchronously open all the existing MIDI devices.
TryOpenAllDevicesOnMonitorThreadSync();
// Since |TryOpenAllDevicesOnMonitorThreadSync()| is a blocking call, it is
// guaranteed that all the initial MIDI ports are already opened here, and
// corresponding tasks to call |AddInputPortOnTaskThread()| and
// |AddOutputPortOnTaskThread()| are already queued in the Misc. Task
// Thread. This means that the following task to call
// |CompleteInitializationOnTaskThread()| are always called after all the
// pending tasks to call |AddInputPortOnTaskThread()| and
// |AddOutputPortOnTaskThread()| are completed.
task_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
base::Unretained(this), MIDI_OK));
// Start monitoring based on notifications generated by
// RegisterDeviceNotificationW API.
device_monitor_window_.reset(new DeviceMonitorWindow(
base::Bind(&MidiServiceWinImpl::TryOpenAllDevicesOnMonitorThreadSync,
base::Unretained(this))));
}
void StopDeviceMonitorOnMonitorThreadSync() {
device_monitor_window_.reset();
}
// Note that this is a blocking method.
void TryOpenAllDevicesOnMonitorThreadSync() {
AssertOnMonitorThread();
const UINT num_in_devices = midiInGetNumDevs();
for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
// Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
// MIM_OPEN, and MIM_CLOSE events.
// - MIM_DATA: This is the only way to get a short MIDI message with
// timestamp information.
// - MIM_LONGDATA: This is the only way to get a long MIDI message with
// timestamp information.
// - MIM_OPEN: This event is sent the input device is opened.
// - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
// the MIDI device becomes unavailable for some reasons, e.g., the
// cable is disconnected. As for the former case, HMIDIOUT will be
// invalidated soon after the callback is finished. As for the later
// case, however, HMIDIOUT continues to be valid until midiInClose()
// is called.
HMIDIIN midi_handle = kInvalidMidiInHandle;
const MMRESULT result = midiInOpen(
&midi_handle, device_id,
reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMultimediaThread),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
<< "Failed to open output device. "
<< " id: " << device_id << " message: " << GetInErrorMessage(result);
}
const UINT num_out_devices = midiOutGetNumDevs();
for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
// Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
// MOM_CLOSE events.
// - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
// up the backing store where a long MIDI message is stored.
// - MOM_OPEN: This event is sent the output device is opened.
// - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
// the MIDI device becomes unavailable for some reasons, e.g., the
// cable is disconnected. As for the former case, HMIDIOUT will be
// invalidated soon after the callback is finished. As for the later
// case, however, HMIDIOUT continues to be valid until midiOutClose()
// is called.
HMIDIOUT midi_handle = kInvalidMidiOutHandle;
const MMRESULT result = midiOutOpen(
&midi_handle, device_id,
reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMultimediaThread),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
if (result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED) {
DLOG(ERROR) << "Failed to open output device. "
<< " id: " << device_id
<< " message: " << GetOutErrorMessage(result);
}
}
}
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// Callbacks on the sender thread. // Callbacks on the sender thread.
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
...@@ -1060,6 +937,59 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -1060,6 +937,59 @@ class MidiServiceWinImpl : public MidiServiceWin {
DCHECK_EQ(task_thread_.thread_id(), base::PlatformThread::CurrentId()); DCHECK_EQ(task_thread_.thread_id(), base::PlatformThread::CurrentId());
} }
void UpdateDeviceListOnTaskThread() {
AssertOnTaskThread();
const UINT num_in_devices = midiInGetNumDevs();
for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
// Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
// MIM_OPEN, and MIM_CLOSE events.
// - MIM_DATA: This is the only way to get a short MIDI message with
// timestamp information.
// - MIM_LONGDATA: This is the only way to get a long MIDI message with
// timestamp information.
// - MIM_OPEN: This event is sent the input device is opened. Note that
// this message is called on the caller thread.
// - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
// the MIDI device becomes unavailable for some reasons, e.g., the
// cable is disconnected. As for the former case, HMIDIOUT will be
// invalidated soon after the callback is finished. As for the later
// case, however, HMIDIOUT continues to be valid until midiInClose()
// is called.
HMIDIIN midi_handle = kInvalidMidiInHandle;
const MMRESULT result = midiInOpen(
&midi_handle, device_id,
reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
<< "Failed to open output device. "
<< " id: " << device_id << " message: " << GetInErrorMessage(result);
}
const UINT num_out_devices = midiOutGetNumDevs();
for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
// Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
// MOM_CLOSE events.
// - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
// up the backing store where a long MIDI message is stored.
// - MOM_OPEN: This event is sent the output device is opened. Note that
// this message is called on the caller thread.
// - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
// the MIDI device becomes unavailable for some reasons, e.g., the
// cable is disconnected. As for the former case, HMIDIOUT will be
// invalidated soon after the callback is finished. As for the later
// case, however, HMIDIOUT continues to be valid until midiOutClose()
// is called.
HMIDIOUT midi_handle = kInvalidMidiOutHandle;
const MMRESULT result = midiOutOpen(
&midi_handle, device_id,
reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread),
reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
<< "Failed to open output device. "
<< " id: " << device_id << " message: " << GetOutErrorMessage(result);
}
}
void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) { void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle) {
AssertOnTaskThread(); AssertOnTaskThread();
auto state = GetInputDeviceFromHandle(midi_in_handle); auto state = GetInputDeviceFromHandle(midi_in_handle);
...@@ -1129,12 +1059,11 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -1129,12 +1059,11 @@ class MidiServiceWinImpl : public MidiServiceWin {
// Does not take ownership. // Does not take ownership.
MidiServiceWinDelegate* delegate_; MidiServiceWinDelegate* delegate_;
base::Thread monitor_thread_; base::ThreadChecker thread_checker_;
base::Thread sender_thread_; base::Thread sender_thread_;
base::Thread task_thread_; base::Thread task_thread_;
scoped_ptr<DeviceMonitorWindow> device_monitor_window_;
base::Lock input_ports_lock_; base::Lock input_ports_lock_;
base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>> base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
input_device_map_; // GUARDED_BY(input_ports_lock_) input_device_map_; // GUARDED_BY(input_ports_lock_)
...@@ -1153,7 +1082,7 @@ class MidiServiceWinImpl : public MidiServiceWin { ...@@ -1153,7 +1082,7 @@ class MidiServiceWinImpl : public MidiServiceWin {
// True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
// that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
// |monitor_thread_|, |sender_thread_|, and |task_thread_| are stopped. // |sender_thread_|, and |task_thread_| are stopped.
// This flag can be used as the signal that when background tasks must be // This flag can be used as the signal that when background tasks must be
// interrupted. // interrupted.
// TODO(toyoshim): Use std::atomic<bool> when it is allowed. // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment