Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Unified Diff: media/midi/midi_manager_win.cc

Issue 29793006: Enable WebMIDI for Windows behind the flag (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « media/midi/midi_manager_win.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/midi/midi_manager_win.cc
diff --git a/media/midi/midi_manager_win.cc b/media/midi/midi_manager_win.cc
new file mode 100644
index 0000000000000000000000000000000000000000..565e52645bdf4a6ec82d5efb6e1dc55b0e22a830
--- /dev/null
+++ b/media/midi/midi_manager_win.cc
@@ -0,0 +1,573 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/midi/midi_manager_win.h"
+
+#include <Windows.h>
+
+// Prevent unnecessary functions from being included from <mmsystem.h>
+#define MMNODRV
+#define MMNOSOUND
+#define MMNOWAVE
+#define MMNOAUX
+#define MMNOMIXER
+#define MMNOTIMER
+#define MMNOJOY
+#define MMNOMCI
+#define MMNOMMIO
+#include <mmsystem.h>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/thread.h"
+#include "media/midi/midi_message_queue.h"
+#include "media/midi/midi_message_util.h"
+#include "media/midi/midi_port_info.h"
+
+namespace media {
+namespace {
+
+std::string GetInErrorMessage(MMRESULT result) {
+ wchar_t text[MAXERRORLENGTH];
+ MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
+ if (get_result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to get error message."
+ << " original error: " << result
+ << " midiInGetErrorText error: " << get_result;
+ return std::string();
+ }
+ return WideToUTF8(text);
+}
+
+std::string GetOutErrorMessage(MMRESULT result) {
+ wchar_t text[MAXERRORLENGTH];
+ MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
+ if (get_result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to get error message."
+ << " original error: " << result
+ << " midiOutGetErrorText error: " << get_result;
+ return std::string();
+ }
+ return WideToUTF8(text);
+}
+
+class MIDIHDRDeleter {
+ public:
+ void operator()(MIDIHDR* header) {
+ if (!header)
+ return;
+ delete[] static_cast<char*>(header->lpData);
+ header->lpData = NULL;
+ header->dwBufferLength = 0;
+ delete header;
+ }
+};
+
+typedef scoped_ptr<MIDIHDR, MIDIHDRDeleter> ScopedMIDIHDR;
+
+ScopedMIDIHDR CreateMIDIHDR(size_t size) {
+ ScopedMIDIHDR header(new MIDIHDR);
+ ZeroMemory(header.get(), sizeof(*header));
+ header->lpData = new char[size];
+ header->dwBufferLength = size;
+ return header.Pass();
+}
+
+void SendShortMIDIMessageInternal(HMIDIOUT midi_out_handle,
+ const std::vector<uint8>& message) {
+ if (message.size() >= 4)
+ return;
+
+ DWORD packed_message = 0;
+ for (size_t i = 0; i < message.size(); ++i)
+ packed_message |= (static_cast<uint32>(message[i]) << (i * 8));
+ MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to output short message: " << GetOutErrorMessage(result);
+}
+
+void SendLongMIDIMessageInternal(HMIDIOUT midi_out_handle,
+ const std::vector<uint8>& message) {
+ // Implementation note:
+ // Sending long MIDI message can be performed synchronously or asynchronously
+ // depending on the driver. There are 2 options to support both cases:
+ // 1) Call midiOutLongMsg API and wait for its completion within this
+ // function. In this approach, we can avoid memory copy by directly pointing
+ // |message| as the data buffer to be sent.
+ // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg
+ // API. The buffer will be freed in the MOM_DONE event hander, which tells
+ // us that the task of midiOutLongMsg API is completed.
+ // Here we use option 2) in favor of asynchronous design.
+ //
+ // Open question:
+ // In the era of USB-MIDI, OS built-in driver seems to always perform
+ // synchronously. Thus option 1) still might be a good option.
+
+ ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
+ for (size_t i = 0; i < message.size(); ++i)
+ midi_header->lpData[i] = static_cast<char>(message[i]);
+
+ MMRESULT result = midiOutPrepareHeader(
+ midi_out_handle, midi_header.get(), sizeof(*midi_header));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to prepare output buffer: "
+ << GetOutErrorMessage(result);
+ return;
+ }
+
+ result = midiOutLongMsg(
+ midi_out_handle, midi_header.get(), sizeof(*midi_header));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to output long message: "
+ << GetOutErrorMessage(result);
+ result = midiOutUnprepareHeader(
+ midi_out_handle, midi_header.get(), sizeof(*midi_header));
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to uninitialize output buffer: "
+ << GetOutErrorMessage(result);
+ return;
+ }
+
+ // The ownership of |midi_header| is moved to MOM_DONE event handler.
+ midi_header.release();
+}
+
+void SendMIDIDataInternal(HMIDIOUT midi_out_handle,
+ const std::vector<uint8>& data) {
+ // MIDI Running status must not be enabled for |data|.
+ MIDIMessageQueue message_queue(false);
+ message_queue.Add(data);
+ std::vector<uint8> message;
+ while (true) {
+ message_queue.Get(&message);
+ if (message.empty())
+ break;
+ // SendShortMIDIMessageInternal can send a MIDI message up to 3 bytes.
+ if (message.size() <= 3)
+ SendShortMIDIMessageInternal(midi_out_handle, message);
+ else
+ SendLongMIDIMessageInternal(midi_out_handle, message);
+ }
+}
+
+} // namespace
+
+class MIDIManagerWin::InPortInfo {
+ public:
+ ~InPortInfo() {
+ Uninitialize();
+ }
+ void set_port_index(int index) {
+ port_index_ = index;
+ }
+ int port_index() const {
+ return port_index_;
+ }
+ bool device_to_be_closed() const {
+ return device_to_be_closed_;
+ }
+ HMIDIIN midi_handle() const {
+ return midi_handle_;
+ }
+ const base::TimeDelta& start_time_offset() const {
+ return start_time_offset_;
+ }
+
+ static scoped_ptr<InPortInfo> Create(MIDIManagerWin* manager,
+ UINT device_id) {
+ scoped_ptr<InPortInfo> obj(new InPortInfo(manager));
+ if (!obj->Initialize(device_id))
+ obj.reset(NULL);
+ return obj.Pass();
+ }
+
+ private:
+ static const int kInvalidPortIndex = -1;
+ static const size_t kBufferLength = 32 * 1024;
+
+ explicit InPortInfo(MIDIManagerWin* manager)
+ : manager_(manager),
+ port_index_(kInvalidPortIndex),
+ midi_handle_(NULL),
+ started_(false),
+ device_to_be_closed_(false) {
+ }
+
+ bool Initialize(DWORD device_id) {
+ Uninitialize();
+ midi_header_ = CreateMIDIHDR(kBufferLength);
+
+ // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA, 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.
+ // - MIN_CLOSE: This event is notified when the device is removed logically
+ // or physically.
+ MMRESULT result = midiInOpen(&midi_handle_,
+ device_id,
+ reinterpret_cast<DWORD_PTR>(&HandleMessage),
+ reinterpret_cast<DWORD_PTR>(this),
+ CALLBACK_FUNCTION);
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to open output device. "
+ << " id: " << device_id
+ << " message: " << GetInErrorMessage(result);
+ return false;
+ }
+ result = midiInPrepareHeader(
+ midi_handle_, midi_header_.get(), sizeof(*midi_header_));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to initialize input buffer: "
+ << GetInErrorMessage(result);
+ return false;
+ }
+ result = midiInAddBuffer(
+ midi_handle_, midi_header_.get(), sizeof(*midi_header_));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to attach input buffer: "
+ << GetInErrorMessage(result);
+ return false;
+ }
+ result = midiInStart(midi_handle_);
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to start input port: "
+ << GetInErrorMessage(result);
+ return false;
+ }
+ started_ = true;
+ start_time_offset_ = base::TimeTicks::Now() - base::TimeTicks();
+ return true;
+ }
+
+ void Uninitialize() {
+ MMRESULT result = MMSYSERR_NOERROR;
+ if (midi_handle_ && started_) {
+ result = midiInStop(midi_handle_);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to stop input port: " << GetInErrorMessage(result);
+ started_ = false;
+ start_time_offset_ = base::TimeDelta();
+ }
+ if (midi_handle_) {
+ // midiInReset flushes pending messages. We ignore these messages.
+ device_to_be_closed_ = true;
+ result = midiInReset(midi_handle_);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to reset input port: " << GetInErrorMessage(result);
+ result = midiInClose(midi_handle_);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to close input port: " << GetInErrorMessage(result);
+ }
+ device_to_be_closed_ = false;
+ }
+
+ static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
+ UINT message,
+ DWORD_PTR instance,
+ DWORD_PTR param1,
+ DWORD_PTR param2) {
+ // This method is called back by Windows multimedia subsystem and not always
+ // in the UI thread.
+ InPortInfo* self = reinterpret_cast<InPortInfo*>(instance);
+ if (!self)
+ return;
+ if (self->midi_handle() != midi_in_handle)
+ return;
+ self->HandleMessageInternal(message, param1, param2);
+ }
+
+ void HandleMessageInternal(UINT message, DWORD_PTR param1, DWORD_PTR param2) {
+ // This method is called from HandleMessage method and not always in the UI
+ // thread.
+ switch (message) {
+ case MIM_DATA:
+ HandleShortMessageInternal(static_cast<uint8>(param1 & 0xff),
+ static_cast<uint8>((param1 >> 8) & 0xff),
+ static_cast<uint8>((param1 >> 16) & 0xff),
+ TickToTimeDelta(param2));
+ return;
+ case MIM_LONGDATA:
+ HandleLongMessageInternal(reinterpret_cast<MIDIHDR*>(param1),
+ TickToTimeDelta(param2));
+ return;
+ case MIM_CLOSE:
+ HandleCloseMessageInternal();
+ }
+ }
+
+ void HandleShortMessageInternal(uint8 status_byte,
+ uint8 first_data_byte,
+ uint8 second_data_byte,
+ base::TimeDelta timestamp) {
+ if (device_to_be_closed())
+ return;
+ const size_t len = GetMIDIMessageLength(status_byte);
+ if (len == 0 || port_index() == kInvalidPortIndex)
+ return;
+ const uint8 kData[] = { status_byte, first_data_byte, second_data_byte };
+ DCHECK_LE(len, arraysize(kData));
+ manager_->ReceiveMIDIData(port_index(), kData, len, timestamp.InSecondsF());
+ }
+
+ void HandleLongMessageInternal(MIDIHDR* header, base::TimeDelta timestamp) {
+ if (header != midi_header_.get())
+ return;
+ MMRESULT result = MMSYSERR_NOERROR;
+ if (device_to_be_closed()) {
+ if (midi_header_ &&
+ (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
+ result = midiInUnprepareHeader(
+ midi_handle_, midi_header_.get(), sizeof(*midi_header_));
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to uninitialize input buffer: "
+ << GetInErrorMessage(result);
+ }
+ return;
+ }
+ if (header->dwBytesRecorded > 0 && port_index() != kInvalidPortIndex) {
+ manager_->ReceiveMIDIData(port_index_,
+ reinterpret_cast<const uint8*>(header->lpData),
+ header->dwBytesRecorded,
+ timestamp.InSecondsF());
+ }
+ result = midiInAddBuffer(midi_handle(), header, sizeof(*header));
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to attach input port: " << GetInErrorMessage(result);
+ }
+
+ void HandleCloseMessageInternal() {
+ if (midi_handle_ && midi_header_ &&
+ (midi_header_->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
+ MMRESULT result = midiInUnprepareHeader(
+ midi_handle_, midi_header_.get(), sizeof(*midi_header_));
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to uninitialize input buffer: "
+ << GetInErrorMessage(result);
+ }
+ midi_header_.reset();
+ midi_handle_ = NULL;
+ port_index_ = kInvalidPortIndex;
+ return;
+ }
+
+ base::TimeDelta TickToTimeDelta(DWORD tick) const {
+ const base::TimeDelta delta =
+ base::TimeDelta::FromMicroseconds(static_cast<uint32>(tick));
+ return start_time_offset_ + delta;
+ }
+
+ MIDIManagerWin* manager_;
+ int port_index_;
+ HMIDIIN midi_handle_;
+ ScopedMIDIHDR midi_header_;
+ base::TimeDelta start_time_offset_;
+ bool started_;
+ bool device_to_be_closed_;
+ DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::InPortInfo);
+};
+
+class MIDIManagerWin::OutPortInfo {
+ public:
+ ~OutPortInfo() {
+ Uninitialize();
+ }
+
+ HMIDIOUT midi_handle() const {
+ return midi_handle_;
+ }
+
+ static scoped_ptr<OutPortInfo> Create(UINT device_id) {
+ scoped_ptr<OutPortInfo> obj(new OutPortInfo);
+ if (!obj->Initialize(device_id))
+ obj.reset(NULL);
+ return obj.Pass();
+ }
+
+ private:
+ OutPortInfo()
+ : midi_handle_(NULL) {}
+
+ bool Initialize(DWORD device_id) {
+ Uninitialize();
+ // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE 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_CLOSE: This event is notified when the device is removed logically
+ // or physically.
+ MMRESULT result = midiOutOpen(&midi_handle_,
+ device_id,
+ reinterpret_cast<DWORD_PTR>(&HandleMessage),
+ reinterpret_cast<DWORD_PTR>(this),
+ CALLBACK_FUNCTION);
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to open output device. "
+ << " id: " << device_id
+ << " message: "<< GetOutErrorMessage(result);
+ midi_handle_ = NULL;
+ return false;
+ }
+ return true;
+ }
+
+ void Uninitialize() {
+ if (midi_handle_ == NULL)
+ return;
+ MMRESULT result = midiOutReset(midi_handle_);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to reset output port: " << GetOutErrorMessage(result);
+ result = midiOutClose(midi_handle_);
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to close output port: " << GetOutErrorMessage(result);
+ midi_handle_ = NULL;
+ }
+
+ static void CALLBACK HandleMessage(HMIDIIN midi_in_handle,
+ UINT message,
+ DWORD_PTR instance,
+ DWORD_PTR param1,
+ DWORD_PTR param2) {
+ // This method is called back by Windows multimedia subsystem and not always
+ // in the UI thread.
+
+ OutPortInfo* self = reinterpret_cast<OutPortInfo*>(instance);
+ if (!self)
+ return;
+ switch (message) {
+ case MOM_DONE: {
+ // Take the ownership of the MIDIHDR object.
+ ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
+ if (!header)
+ return;
+ MMRESULT result = midiOutUnprepareHeader(
+ self->midi_handle(), header.get(), sizeof(*header));
+ DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
+ << "Failed to uninitialize output buffer: "
+ << GetOutErrorMessage(result);
+ return;
+ }
+ case MOM_CLOSE:
+ self->midi_handle_ = NULL;
+ return;
+ }
+ }
+
+ HMIDIOUT midi_handle_;
+ DISALLOW_COPY_AND_ASSIGN(MIDIManagerWin::OutPortInfo);
+};
+
+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.
+ // Wait for |send_thread_| is finished before all the members and
+ // overriden methods of MIDIManagerWin are invalidated.
+ send_thread_.reset();
+}
+
+bool MIDIManagerWin::Initialize() {
+ {
+ const UINT num_in_devices = midiInGetNumDevs();
+ in_ports_.reserve(num_in_devices);
+ for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
+ MIDIINCAPS caps = {};
+ MMRESULT result = midiInGetDevCaps(device_id, &caps, sizeof(caps));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to obtain input device info: "
+ << GetInErrorMessage(result);
+ continue;
+ }
+ scoped_ptr<InPortInfo> in_port(InPortInfo::Create(this, device_id));
+ if (!in_port)
+ continue;
+ MIDIPortInfo info(
+ base::IntToString(static_cast<int>(device_id)),
+ "",
+ base::WideToUTF8(caps.szPname),
+ base::IntToString(static_cast<int>(caps.vDriverVersion)));
+ AddInputPort(info);
+ in_port->set_port_index(input_ports_.size() - 1);
+ in_ports_.push_back(in_port.Pass());
+ }
+ }
+ {
+ const UINT num_out_devices = midiOutGetNumDevs();
+ out_ports_.reserve(num_out_devices);
+ for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
+ MIDIOUTCAPS caps = {};
+ MMRESULT result = midiOutGetDevCaps(device_id, &caps, sizeof(caps));
+ if (result != MMSYSERR_NOERROR) {
+ DLOG(ERROR) << "Failed to obtain output device info: "
+ << GetOutErrorMessage(result);
+ continue;
+ }
+ scoped_ptr<OutPortInfo> out_port(OutPortInfo::Create(device_id));
+ if (!out_port)
+ continue;
+ MIDIPortInfo info(
+ base::IntToString(static_cast<int>(device_id)),
+ "",
+ base::WideToUTF8(caps.szPname),
+ base::IntToString(static_cast<int>(caps.vDriverVersion)));
+ AddOutputPort(info);
+ out_ports_.push_back(out_port.Pass());
+ }
+ }
+ return true;
+}
+
+MIDIManagerWin::~MIDIManagerWin() {
+ // |send_thread_| must be terminated here to ensure that
+ // MIDIManagerWin::SendMIDIData will not be called any longer. A drawback
+ // of this approach is if |send_thread_| is somehow blocked forever,
+ // the UI thread is also blocked forever.
+ 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.
+}
+
+void MIDIManagerWin::SendMIDIData(MIDIManagerClient* client,
+ uint32 port_index,
+ const std::vector<uint8>& data,
+ double timestamp) {
+ DCHECK(CurrentlyOnMIDISendThread());
+ // Caveat: Whether |this| is still valid or not is non-trivial question
+ // because this method is called back in a background thread that is owned by
+ // MIDIManager rather than MIDIManagerWin. Currently |this| is considered to
+ // be always valid because ~MIDIManagerWin() internally waits for the
+ // termination of this background thread.
+
+ if (out_ports_.size() <= port_index)
+ return;
+
+ // Check if the target device is still available. Note that |handle| can be
+ // NULL once the device is logically or physically removed.
+ const HMIDIOUT handle = out_ports_[port_index]->midi_handle();
+ if (!handle)
+ return;
+
+ base::TimeDelta delay;
+ if (timestamp != 0.0) {
+ base::TimeTicks time_to_send =
+ base::TimeTicks() + base::TimeDelta::FromMicroseconds(
+ timestamp * base::Time::kMicrosecondsPerSecond);
+ delay = time_to_send - base::TimeTicks::Now();
+ }
+
+ if (delay.InMilliseconds() <= 0) {
+ SendMIDIDataInternal(handle, data);
+ } else {
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SendMIDIDataInternal, handle, data),
+ delay);
+ }
+
+ client->AccumulateMIDIBytesSent(data.size());
+}
+
+MIDIManager* MIDIManager::Create() {
+ return new MIDIManagerWin();
+}
+
+} // namespace media
« no previous file with comments | « media/midi/midi_manager_win.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698