Chromium Code Reviews| Index: media/midi/dynamically_initialized_midi_manager_win.cc |
| diff --git a/media/midi/dynamically_initialized_midi_manager_win.cc b/media/midi/dynamically_initialized_midi_manager_win.cc |
| index 2a83b006fd0ae8e2d8597b6cfd4df6b09aba4f7c..cef899a3ac2a0332073ea3ecd22e9f1d9f924fa0 100644 |
| --- a/media/midi/dynamically_initialized_midi_manager_win.cc |
| +++ b/media/midi/dynamically_initialized_midi_manager_win.cc |
| @@ -19,17 +19,53 @@ |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| +#include "media/midi/message_util.h" |
| #include "media/midi/midi_port_info.h" |
| #include "media/midi/midi_service.h" |
| namespace midi { |
| +// Forward declaration of PortManager for anonymous functions and internal |
| +// classes to use it. |
| +class DynamicallyInitializedMidiManagerWin::PortManager { |
| + public: |
| + // Calculates event time from elapsed time that system provides. |
| + base::TimeTicks CalculateInEventTime(size_t index, uint32_t elapsed_ms) const; |
| + |
| + // Registers HMIDIIN handle to resolve port index. |
| + void RegisterInHandle(HMIDIIN handle, size_t index); |
| + |
| + // Unregisters HMIDIIN handle. |
| + void UnregisterInHandle(HMIDIIN handle); |
| + |
| + // Finds HMIDIIN handle and fullfil |out_index| with the port index. |
| + bool FindHandle(HMIDIIN hmi, size_t* out_index); |
| + |
| + // Restores used input buffer for the next data receive. |
| + void RestoreInBuffer(size_t index); |
| + |
| + // Ports accessors. |
| + std::vector<std::unique_ptr<InPort>>* inputs() { return &input_ports_; } |
| + std::vector<std::unique_ptr<OutPort>>* outputs() { return &output_ports_; } |
| + |
| + private: |
| + // Holds all MIDI input or output ports connected once. |
| + std::vector<std::unique_ptr<InPort>> input_ports_; |
| + std::vector<std::unique_ptr<OutPort>> output_ports_; |
| + |
| + // Map to resolve MIDI input port index from HMIDIIN. |
| + std::map<HMIDIIN, size_t> hmidiin_to_index_map_; |
| +}; |
| + |
| namespace { |
| // Assumes that nullptr represents an invalid MIDI handle. |
| constexpr HMIDIIN kInvalidInHandle = nullptr; |
| constexpr HMIDIOUT kInvalidOutHandle = nullptr; |
| +// Defines input buffer size. |
| +constexpr size_t kBufferLength = 32 * 1024; |
| + |
| // Global variables to identify MidiManager instance. |
| constexpr int kInvalidInstanceId = -1; |
| int g_active_instance_id = kInvalidInstanceId; |
| @@ -76,8 +112,44 @@ void RunTask(int instance_id, const base::Closure& task) { |
| // TODO(toyoshim): Factor out TaskRunner related functionaliries above, and |
| // deprecate MidiScheduler. It should be available via MidiManager::scheduler(). |
| +// Utility class to handle MIDIHDR struct safely. |
| +class MIDIHDRDeleter { |
| + public: |
| + void operator()(LPMIDIHDR header) { |
| + if (!header) |
| + return; |
| + delete[] static_cast<char*>(header->lpData); |
| + delete header; |
| + } |
| +}; |
| + |
| +using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>; |
| + |
| +ScopedMIDIHDR CreateMIDIHDR(size_t size) { |
| + ScopedMIDIHDR hdr(new MIDIHDR); |
| + ZeroMemory(hdr.get(), sizeof(*hdr)); |
| + hdr->lpData = new char[size]; |
| + hdr->dwBufferLength = static_cast<DWORD>(size); |
| + return hdr; |
| +} |
| + |
| +ScopedMIDIHDR CreateMIDIHDR(const std::vector<uint8_t>& data) { |
| + ScopedMIDIHDR hdr(CreateMIDIHDR(data.size())); |
| + std::copy(data.begin(), data.end(), hdr->lpData); |
| + return hdr; |
| +} |
| + |
| // Helper functions to close MIDI device handles on TaskRunner asynchronously. |
| -void FinalizeInPort(HMIDIIN handle) { |
| +void FinalizeInPort(HMIDIIN handle, LPMIDIHDR lphdr) { |
| + // Resets the device. This stops receiving messages, and allows to release |
|
Takashi Toyoshima
2017/02/24 08:53:34
Actually, our legacy code of midi_manager_win.cc d
|
| + // registered buffer headers. Otherwise, midiInUnprepareHeader() and |
| + // midiInClose() will fail with MIDIERR_STILLPLAYING. |
| + midiInReset(handle); |
| + |
| + // Obtains MIDIHDR ownership to release allocated buffers. |
| + ScopedMIDIHDR hdr(lphdr); |
| + if (hdr) |
| + midiInUnprepareHeader(handle, hdr.get(), sizeof(*hdr)); |
| midiInClose(handle); |
| } |
| @@ -91,7 +163,53 @@ void CALLBACK HandleMidiInCallback(HMIDIIN hmi, |
| DWORD_PTR instance, |
| DWORD_PTR param1, |
| DWORD_PTR param2) { |
| - // TODO(toyoshim): Following patches will implement actual functions. |
| + if (msg != MIM_DATA && msg != MIM_LONGDATA) |
| + return; |
| + int instance_id = static_cast<int>(instance); |
| + DynamicallyInitializedMidiManagerWin* manager = nullptr; |
| + |
| + // Use |g_task_lock| so to ensure the instance can keep alive while running, |
| + // and to access member variables that are used on TaskRunner. |
| + base::AutoLock task_lock(*GetTaskLock()); |
| + { |
| + base::AutoLock lock(*GetInstanceIdLock()); |
| + if (instance_id != g_active_instance_id) |
| + return; |
| + manager = g_manager_instance; |
| + } |
| + |
| + size_t index; |
| + if (!manager->port_manager()->FindHandle(hmi, &index)) |
| + return; |
| + |
| + if (msg == MIM_DATA) { |
| + const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff); |
|
Takashi Toyoshima
2017/02/24 08:53:34
Following code through PostReplyTask is same with
|
| + const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff); |
| + const uint8_t second_data_byte = |
| + static_cast<uint8_t>((param1 >> 16) & 0xff); |
| + const size_t len = GetMessageLength(status_byte); |
| + const uint8_t kData[] = {status_byte, first_data_byte, second_data_byte}; |
| + std::vector<uint8_t> data; |
| + data.assign(kData, kData + len); |
| + manager->PostReplyTask(base::Bind( |
| + &DynamicallyInitializedMidiManagerWin::ReceiveMidiData, |
| + base::Unretained(manager), index, data, |
| + manager->port_manager()->CalculateInEventTime(index, param2))); |
| + } else { // msg == MIM_LONGDATA |
|
yhirano
2017/02/24 12:22:58
Is this true? In the old code, the corresponding s
Takashi Toyoshima
2017/02/27 06:44:49
See line 166.
But, it would be nicer to have DCHEC
|
| + LPMIDIHDR hdr = reinterpret_cast<LPMIDIHDR>(param1); |
| + if (hdr->dwBytesRecorded > 0) { |
| + const uint8_t* src = reinterpret_cast<const uint8_t*>(hdr->lpData); |
| + std::vector<uint8_t> data; |
| + data.assign(src, src + hdr->dwBytesRecorded); |
| + manager->PostReplyTask(base::Bind( |
| + &DynamicallyInitializedMidiManagerWin::ReceiveMidiData, |
| + base::Unretained(manager), index, data, |
| + manager->port_manager()->CalculateInEventTime(index, param2))); |
| + } |
| + manager->PostTask(base::Bind( |
| + &DynamicallyInitializedMidiManagerWin::PortManager::RestoreInBuffer, |
| + base::Unretained(manager->port_manager()), index)); |
| + } |
| } |
| // Handles MIDI output port callbacks that runs on a system provided thread. |
| @@ -192,7 +310,10 @@ class Port { |
| // TODO(toyoshim): Following patches will implement actual functions. |
| class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
| public: |
| - InPort(UINT device_id, const MIDIINCAPS2W& caps) |
| + InPort(DynamicallyInitializedMidiManagerWin* manager, |
| + int instance_id, |
| + UINT device_id, |
| + const MIDIINCAPS2W& caps) |
| : Port("input", |
| device_id, |
| caps.wMid, |
| @@ -200,9 +321,13 @@ class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
| caps.vDriverVersion, |
| base::WideToUTF8( |
| base::string16(caps.szPname, wcslen(caps.szPname)))), |
| - in_handle_(kInvalidInHandle) {} |
| + manager_(manager), |
| + in_handle_(kInvalidInHandle), |
| + instance_id_(instance_id) {} |
| - static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts() { |
| + static std::vector<std::unique_ptr<InPort>> EnumerateActivePorts( |
| + DynamicallyInitializedMidiManagerWin* manager, |
| + int instance_id) { |
| std::vector<std::unique_ptr<InPort>> ports; |
| const UINT num_devices = midiInGetNumDevs(); |
| for (UINT device_id = 0; device_id < num_devices; ++device_id) { |
| @@ -213,18 +338,31 @@ class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
| LOG(ERROR) << "midiInGetDevCaps fails on device " << device_id; |
| continue; |
| } |
| - ports.push_back(base::MakeUnique<InPort>(device_id, caps)); |
| + ports.push_back( |
| + base::MakeUnique<InPort>(manager, instance_id, device_id, caps)); |
| } |
| return ports; |
| } |
| void Finalize(scoped_refptr<base::SingleThreadTaskRunner> runner) { |
| if (in_handle_ != kInvalidInHandle) { |
| - runner->PostTask(FROM_HERE, base::Bind(&FinalizeInPort, in_handle_)); |
| + runner->PostTask(FROM_HERE, |
| + base::Bind(&FinalizeInPort, in_handle_, hdr_.release())); |
| + manager_->port_manager()->UnregisterInHandle(in_handle_); |
| in_handle_ = kInvalidInHandle; |
| } |
| } |
| + base::TimeTicks CalculateInEventTime(uint32_t elapsed_ms) const { |
| + return start_time_ + base::TimeDelta::FromMilliseconds(elapsed_ms); |
| + } |
| + |
| + void RestoreBuffer() { |
| + if (in_handle_ == kInvalidInHandle || !hdr_) |
| + return; |
| + midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); |
| + } |
| + |
| void NotifyPortStateSet(DynamicallyInitializedMidiManagerWin* manager) { |
| manager->PostReplyTask( |
| base::Bind(&DynamicallyInitializedMidiManagerWin::SetInputPortState, |
| @@ -243,27 +381,48 @@ class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
| // Following API call may fail because device was already disconnected. |
| // But just in case. |
| midiInClose(in_handle_); |
| + manager_->port_manager()->UnregisterInHandle(in_handle_); |
| in_handle_ = kInvalidInHandle; |
| } |
| return Port::Disconnect(); |
| } |
| void Open() override { |
| - // TODO(toyoshim): Pass instance_id to implement HandleMidiInCallback. |
| MMRESULT result = |
| midiInOpen(&in_handle_, device_id_, |
| - reinterpret_cast<DWORD_PTR>(&HandleMidiInCallback), 0, |
| - CALLBACK_FUNCTION); |
| + reinterpret_cast<DWORD_PTR>(&HandleMidiInCallback), |
| + instance_id_, CALLBACK_FUNCTION); |
| if (result == MMSYSERR_NOERROR) { |
| + hdr_ = CreateMIDIHDR(kBufferLength); |
| + result = midiInPrepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); |
| + } |
| + if (result != MMSYSERR_NOERROR) |
| + in_handle_ = kInvalidInHandle; |
| + if (result == MMSYSERR_NOERROR) |
| + result = midiInAddBuffer(in_handle_, hdr_.get(), sizeof(*hdr_)); |
| + if (result == MMSYSERR_NOERROR) |
| + result = midiInStart(in_handle_); |
| + if (result == MMSYSERR_NOERROR) { |
| + start_time_ = base::TimeTicks::Now(); |
| + manager_->port_manager()->RegisterInHandle(in_handle_, index_); |
| Port::Open(); |
| } else { |
| - in_handle_ = kInvalidInHandle; |
| + if (in_handle_ != kInvalidInHandle) { |
| + midiInUnprepareHeader(in_handle_, hdr_.get(), sizeof(*hdr_)); |
| + hdr_.reset(); |
| + midiInClose(in_handle_); |
| + in_handle_ = kInvalidInHandle; |
| + } |
| Disconnect(); |
| } |
| } |
| private: |
| + DynamicallyInitializedMidiManagerWin* manager_; |
| HMIDIIN in_handle_; |
| + ScopedMIDIHDR hdr_; |
| + base::TimeTicks start_time_; |
| + int instance_id_; |
| }; |
| // TODO(toyoshim): Following patches will implement actual functions. |
| @@ -354,9 +513,51 @@ class DynamicallyInitializedMidiManagerWin::OutPort final : public Port { |
| HMIDIOUT out_handle_; |
| }; |
| +base::TimeTicks |
| +DynamicallyInitializedMidiManagerWin::PortManager::CalculateInEventTime( |
| + size_t index, |
| + uint32_t elapsed_ms) const { |
| + GetTaskLock()->AssertAcquired(); |
|
Takashi Toyoshima
2017/02/24 08:53:34
task lock should be obtained outside this method,
|
| + CHECK_GT(input_ports_.size(), index); |
| + return input_ports_[index]->CalculateInEventTime(elapsed_ms); |
| +} |
| + |
| +void DynamicallyInitializedMidiManagerWin::PortManager::RegisterInHandle( |
| + HMIDIIN handle, |
| + size_t index) { |
| + GetTaskLock()->AssertAcquired(); |
| + hmidiin_to_index_map_[handle] = index; |
| +} |
| + |
| +void DynamicallyInitializedMidiManagerWin::PortManager::UnregisterInHandle( |
| + HMIDIIN handle) { |
| + GetTaskLock()->AssertAcquired(); |
| + hmidiin_to_index_map_.erase(handle); |
| +} |
| + |
| +bool DynamicallyInitializedMidiManagerWin::PortManager::FindHandle( |
| + HMIDIIN hmi, |
| + size_t* out_index) { |
| + GetTaskLock()->AssertAcquired(); |
| + auto found = hmidiin_to_index_map_.find(hmi); |
| + if (found == hmidiin_to_index_map_.end()) |
| + return false; |
| + *out_index = found->second; |
| + return true; |
| +} |
| + |
| +void DynamicallyInitializedMidiManagerWin::PortManager::RestoreInBuffer( |
| + size_t index) { |
| + GetTaskLock()->AssertAcquired(); |
| + CHECK_GT(input_ports_.size(), index); |
| + input_ports_[index]->RestoreBuffer(); |
| +} |
| + |
| DynamicallyInitializedMidiManagerWin::DynamicallyInitializedMidiManagerWin( |
| MidiService* service) |
| - : MidiManager(service), instance_id_(IssueNextInstanceId()) { |
| + : MidiManager(service), |
| + instance_id_(IssueNextInstanceId()), |
| + port_manager_(base::MakeUnique<PortManager>()) { |
| base::AutoLock lock(*GetInstanceIdLock()); |
| CHECK_EQ(kInvalidInstanceId, g_active_instance_id); |
| @@ -370,6 +571,19 @@ DynamicallyInitializedMidiManagerWin::~DynamicallyInitializedMidiManagerWin() { |
| CHECK(thread_runner_->BelongsToCurrentThread()); |
| } |
| +void DynamicallyInitializedMidiManagerWin::ReceiveMidiData( |
| + uint32_t index, |
| + const std::vector<uint8_t>& data, |
| + base::TimeTicks time) { |
| + MidiManager::ReceiveMidiData(index, data.data(), data.size(), time); |
| +} |
| + |
| +void DynamicallyInitializedMidiManagerWin::PostTask(const base::Closure& task) { |
| + service() |
| + ->GetTaskRunner(kTaskRunner) |
| + ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
| +} |
| + |
| void DynamicallyInitializedMidiManagerWin::PostReplyTask( |
| const base::Closure& task) { |
| thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
| @@ -416,9 +630,9 @@ void DynamicallyInitializedMidiManagerWin::Finalize() { |
| // on TaskRunner. If another MidiManager instance is created, its |
| // initialization runs on the same task runner after all tasks posted here |
| // finish. |
| - for (const auto& port : input_ports_) |
| + for (const auto& port : *port_manager_->inputs()) |
| port->Finalize(service()->GetTaskRunner(kTaskRunner)); |
| - for (const auto& port : output_ports_) |
| + for (const auto& port : *port_manager_->outputs()) |
| port->Finalize(service()->GetTaskRunner(kTaskRunner)); |
| } |
| @@ -449,12 +663,6 @@ void DynamicallyInitializedMidiManagerWin::OnDevicesChanged( |
| } |
| } |
| -void DynamicallyInitializedMidiManagerWin::PostTask(const base::Closure& task) { |
| - service() |
| - ->GetTaskRunner(kTaskRunner) |
| - ->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
| -} |
| - |
| void DynamicallyInitializedMidiManagerWin::InitializeOnTaskRunner() { |
| UpdateDeviceListOnTaskRunner(); |
| PostReplyTask( |
| @@ -464,12 +672,12 @@ void DynamicallyInitializedMidiManagerWin::InitializeOnTaskRunner() { |
| void DynamicallyInitializedMidiManagerWin::UpdateDeviceListOnTaskRunner() { |
| std::vector<std::unique_ptr<InPort>> active_input_ports = |
| - InPort::EnumerateActivePorts(); |
| - ReflectActiveDeviceList(this, &input_ports_, &active_input_ports); |
| + InPort::EnumerateActivePorts(this, instance_id_); |
| + ReflectActiveDeviceList(this, port_manager_->inputs(), &active_input_ports); |
| std::vector<std::unique_ptr<OutPort>> active_output_ports = |
| OutPort::EnumerateActivePorts(); |
| - ReflectActiveDeviceList(this, &output_ports_, &active_output_ports); |
| + ReflectActiveDeviceList(this, port_manager_->outputs(), &active_output_ports); |
| // TODO(toyoshim): This method may run before internal MIDI device lists that |
| // Windows manages were updated. This may be because MIDI driver may be loaded |