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 cd89e8f035aec82d7dc250ecd39b9533d357d181..60539a76b2c4d7c313bf7a30fa3b6bcbe8ecb1dd 100644 |
--- a/media/midi/dynamically_initialized_midi_manager_win.cc |
+++ b/media/midi/dynamically_initialized_midi_manager_win.cc |
@@ -40,7 +40,7 @@ class DynamicallyInitializedMidiManagerWin::PortManager { |
void UnregisterInHandle(HMIDIIN handle); |
// Finds HMIDIIN handle and fullfil |out_index| with the port index. |
- bool FindHandle(HMIDIIN hmi, size_t* out_index); |
+ bool FindInHandle(HMIDIIN hmi, size_t* out_index); |
// Restores used input buffer for the next data receive. |
void RestoreInBuffer(size_t index); |
@@ -56,6 +56,13 @@ class DynamicallyInitializedMidiManagerWin::PortManager { |
DWORD_PTR param1, |
DWORD_PTR param2); |
+ // Handles MIDI output port callbacks that runs on a system provided thread. |
+ static void CALLBACK HandleMidiOutCallback(HMIDIOUT hmo, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2); |
+ |
private: |
// Holds all MIDI input or output ports connected once. |
std::vector<std::unique_ptr<InPort>> input_ports_; |
@@ -71,6 +78,20 @@ namespace { |
constexpr HMIDIIN kInvalidInHandle = nullptr; |
constexpr HMIDIOUT kInvalidOutHandle = nullptr; |
+// Defines SysEx message size limit. |
+// TODO(crbug.com/383578): This restriction should be removed once Web MIDI |
+// defines a standardized way to handle large sysex messages. |
+// Note for built-in USB-MIDI driver: |
+// From an observation on Windows 7/8.1 with a USB-MIDI keyboard, |
+// midiOutLongMsg() will be always blocked. Sending 64 bytes or less data takes |
+// roughly 300 usecs. Sending 2048 bytes or more data takes roughly |
+// |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size |
+// limit on SysEx message, with hoping that midiOutLongMsg will be blocked at |
+// most 4 sec or so with a typical USB-MIDI device. |
+// TODO(toyoshim): Consider to use linked small buffers so that midiOutReset() |
+// can abort sending unhandled following buffers. |
+constexpr size_t kSysExSizeLimit = 256 * 1024; |
+ |
// Defines input buffer size. |
constexpr size_t kBufferLength = 32 * 1024; |
@@ -141,6 +162,12 @@ ScopedMIDIHDR CreateMIDIHDR(size_t 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, ScopedMIDIHDR hdr) { |
// Resets the device. This stops receiving messages, and allows to release |
@@ -154,18 +181,12 @@ void FinalizeInPort(HMIDIIN handle, ScopedMIDIHDR hdr) { |
} |
void FinalizeOutPort(HMIDIOUT handle) { |
+ // Resets inflight buffers. This will cancel sending data that system |
+ // holds and were not sent yet. |
+ midiOutReset(handle); |
midiOutClose(handle); |
} |
-// Handles MIDI output port callbacks that runs on a system provided thread. |
-void CALLBACK HandleMidiOutCallback(HMIDIOUT hmo, |
- UINT msg, |
- DWORD_PTR instance, |
- DWORD_PTR param1, |
- DWORD_PTR param2) { |
- // TODO(toyoshim): Following patches will implement actual functions. |
-} |
- |
// All instances of Port subclasses are always accessed behind a lock of |
// *GetTaskLock(). Port and subclasses implementation do not need to |
// consider thread safety. |
@@ -252,7 +273,6 @@ class Port { |
} // namespace |
-// TODO(toyoshim): Following patches will implement actual functions. |
class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
public: |
InPort(DynamicallyInitializedMidiManagerWin* manager, |
@@ -371,7 +391,6 @@ class DynamicallyInitializedMidiManagerWin::InPort final : public Port { |
const int instance_id_; |
}; |
-// TODO(toyoshim): Following patches will implement actual functions. |
class DynamicallyInitializedMidiManagerWin::OutPort final : public Port { |
public: |
OutPort(UINT device_id, const MIDIOUTCAPS2W& caps) |
@@ -420,6 +439,37 @@ class DynamicallyInitializedMidiManagerWin::OutPort final : public Port { |
base::Unretained(manager), info_)); |
} |
+ void Send(const std::vector<uint8_t>& data) { |
+ if (out_handle_ == kInvalidOutHandle) |
+ return; |
+ |
+ if (data.size() <= 3) { |
+ uint32_t message = 0; |
+ for (size_t i = 0; i < data.size(); ++i) |
+ message |= (static_cast<uint32_t>(data[i]) << (i * 8)); |
+ midiOutShortMsg(out_handle_, message); |
+ } else { |
+ if (data.size() > kSysExSizeLimit) { |
+ LOG(ERROR) << "Ignoring SysEx message due to the size limit" |
+ << ", size = " << data.size(); |
+ // TODO(toyoshim): Consider to report metrics here. |
+ return; |
+ } |
+ ScopedMIDIHDR hdr(CreateMIDIHDR(data)); |
+ MMRESULT result = |
+ midiOutPrepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); |
+ if (result != MMSYSERR_NOERROR) |
+ return; |
+ result = midiOutLongMsg(out_handle_, hdr.get(), sizeof(*hdr)); |
+ if (result != MMSYSERR_NOERROR) { |
+ midiOutUnprepareHeader(out_handle_, hdr.get(), sizeof(*hdr)); |
+ } else { |
+ // MIDIHDR will be released on MOM_DONE. |
+ ignore_result(hdr.release()); |
+ } |
+ } |
+ } |
+ |
// Port overrides: |
bool Connect() override { |
// Until |software| option is supported, disable Microsoft GS Wavetable |
@@ -443,10 +493,10 @@ class DynamicallyInitializedMidiManagerWin::OutPort final : public Port { |
} |
void Open() override { |
- MMRESULT result = |
- midiOutOpen(&out_handle_, device_id_, |
- reinterpret_cast<DWORD_PTR>(&HandleMidiOutCallback), 0, |
- CALLBACK_FUNCTION); |
+ MMRESULT result = midiOutOpen( |
+ &out_handle_, device_id_, |
+ reinterpret_cast<DWORD_PTR>(&PortManager::HandleMidiOutCallback), 0, |
+ CALLBACK_FUNCTION); |
if (result == MMSYSERR_NOERROR) { |
Port::Open(); |
} else { |
@@ -481,7 +531,7 @@ void DynamicallyInitializedMidiManagerWin::PortManager::UnregisterInHandle( |
hmidiin_to_index_map_.erase(handle); |
} |
-bool DynamicallyInitializedMidiManagerWin::PortManager::FindHandle( |
+bool DynamicallyInitializedMidiManagerWin::PortManager::FindInHandle( |
HMIDIIN hmi, |
size_t* out_index) { |
GetTaskLock()->AssertAcquired(); |
@@ -522,7 +572,7 @@ DynamicallyInitializedMidiManagerWin::PortManager::HandleMidiInCallback( |
} |
size_t index; |
- if (!manager->port_manager()->FindHandle(hmi, &index)) |
+ if (!manager->port_manager()->FindInHandle(hmi, &index)) |
return; |
DCHECK(msg == MIM_DATA || msg == MIM_LONGDATA); |
@@ -558,6 +608,26 @@ DynamicallyInitializedMidiManagerWin::PortManager::HandleMidiInCallback( |
} |
} |
+void CALLBACK |
+DynamicallyInitializedMidiManagerWin::PortManager::HandleMidiOutCallback( |
+ HMIDIOUT hmo, |
+ UINT msg, |
+ DWORD_PTR instance, |
+ DWORD_PTR param1, |
+ DWORD_PTR param2) { |
+ if (msg == MOM_DONE) { |
+ ScopedMIDIHDR hdr(reinterpret_cast<LPMIDIHDR>(param1)); |
+ if (!hdr) |
+ return; |
+ // TODO(toyoshim): Call midiOutUnprepareHeader outside the callback. |
+ // Since this callback may be invoked after the manager is destructed, |
+ // and can not send a task to the TaskRunner in such case, we need to |
+ // consider to track MIDIHDR per port, and clean it in port finalization |
+ // steps, too. |
+ midiOutUnprepareHeader(hmo, hdr.get(), sizeof(*hdr)); |
+ } |
+} |
+ |
DynamicallyInitializedMidiManagerWin::DynamicallyInitializedMidiManagerWin( |
MidiService* service) |
: MidiManager(service), |
@@ -628,7 +698,21 @@ void DynamicallyInitializedMidiManagerWin::DispatchSendMidiData( |
uint32_t port_index, |
const std::vector<uint8_t>& data, |
double timestamp) { |
- // TODO(toyoshim): Following patches will implement. |
+ if (timestamp != 0.0) { |
+ base::TimeTicks time = base::TimeTicks() + |
+ base::TimeDelta::FromMicroseconds( |
+ timestamp * base::Time::kMicrosecondsPerSecond); |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ if (now < time) { |
+ PostDelayedTask( |
+ base::Bind(&DynamicallyInitializedMidiManagerWin::SendOnTaskRunner, |
+ base::Unretained(this), client, port_index, data), |
+ time - now); |
+ return; |
+ } |
+ } |
+ PostTask(base::Bind(&DynamicallyInitializedMidiManagerWin::SendOnTaskRunner, |
+ base::Unretained(this), client, port_index, data)); |
} |
void DynamicallyInitializedMidiManagerWin::OnDevicesChanged( |
@@ -663,6 +747,15 @@ void DynamicallyInitializedMidiManagerWin::PostTask(const base::Closure& task) { |
->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
} |
+void DynamicallyInitializedMidiManagerWin::PostDelayedTask( |
+ const base::Closure& task, |
+ base::TimeDelta delay) { |
+ service() |
+ ->GetTaskRunner(kTaskRunner) |
+ ->PostDelayedTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task), |
+ delay); |
+} |
+ |
void DynamicallyInitializedMidiManagerWin::PostReplyTask( |
const base::Closure& task) { |
thread_runner_->PostTask(FROM_HERE, base::Bind(&RunTask, instance_id_, task)); |
@@ -725,4 +818,16 @@ void DynamicallyInitializedMidiManagerWin::ReflectActiveDeviceList( |
} |
} |
+void DynamicallyInitializedMidiManagerWin::SendOnTaskRunner( |
+ MidiManagerClient* client, |
+ uint32_t port_index, |
+ const std::vector<uint8_t>& data) { |
+ CHECK_GT(port_manager_->outputs()->size(), port_index); |
+ (*port_manager_->outputs())[port_index]->Send(data); |
+ // |client| will be checked inside MidiManager::AccumulateMidiBytesSent. |
+ PostReplyTask( |
+ base::Bind(&DynamicallyInitializedMidiManagerWin::AccumulateMidiBytesSent, |
+ base::Unretained(this), client, data.size())); |
+} |
+ |
} // namespace midi |