midi_manager_mac.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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. #include "media/midi/midi_manager_mac.h"
  5. #include <stddef.h>
  6. #include <algorithm>
  7. #include <iterator>
  8. #include <string>
  9. #include <CoreAudio/HostTime.h>
  10. #include "base/bind.h"
  11. #include "base/strings/string_number_conversions.h"
  12. #include "base/strings/sys_string_conversions.h"
  13. #include "base/task/single_thread_task_runner.h"
  14. #include "media/midi/midi_service.h"
  15. #include "media/midi/task_service.h"
  16. using base::NumberToString;
  17. using base::SysCFStringRefToUTF8;
  18. using midi::mojom::PortState;
  19. using midi::mojom::Result;
  20. // NB: System MIDI types are pointer types in 32-bit and integer types in
  21. // 64-bit. Therefore, the initialization is the simplest one that satisfies both
  22. // (if possible).
  23. namespace midi {
  24. namespace {
  25. // Maximum buffer size that CoreMIDI can handle for MIDIPacketList.
  26. const size_t kCoreMIDIMaxPacketListSize = 65536;
  27. // Pessimistic estimation on available data size of MIDIPacketList.
  28. const size_t kEstimatedMaxPacketDataSize = kCoreMIDIMaxPacketListSize / 2;
  29. enum {
  30. kSessionTaskRunner = TaskService::kDefaultRunnerId,
  31. kClientTaskRunner,
  32. };
  33. mojom::PortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint) {
  34. std::string manufacturer;
  35. CFStringRef manufacturer_ref = NULL;
  36. OSStatus result = MIDIObjectGetStringProperty(
  37. endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
  38. if (result == noErr) {
  39. manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
  40. } else {
  41. // kMIDIPropertyManufacturer is not supported in IAC driver providing
  42. // endpoints, and the result will be kMIDIUnknownProperty (-10835).
  43. DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status "
  44. << result;
  45. }
  46. std::string name;
  47. CFStringRef name_ref = NULL;
  48. result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName,
  49. &name_ref);
  50. if (result == noErr) {
  51. name = SysCFStringRefToUTF8(name_ref);
  52. } else {
  53. DLOG(WARNING) << "Failed to get kMIDIPropertyDisplayName with status "
  54. << result;
  55. }
  56. std::string version;
  57. SInt32 version_number = 0;
  58. result = MIDIObjectGetIntegerProperty(
  59. endpoint, kMIDIPropertyDriverVersion, &version_number);
  60. if (result == noErr) {
  61. version = NumberToString(version_number);
  62. } else {
  63. // kMIDIPropertyDriverVersion is not supported in IAC driver providing
  64. // endpoints, and the result will be kMIDIUnknownProperty (-10835).
  65. DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status "
  66. << result;
  67. }
  68. std::string id;
  69. SInt32 id_number = 0;
  70. result = MIDIObjectGetIntegerProperty(
  71. endpoint, kMIDIPropertyUniqueID, &id_number);
  72. if (result == noErr) {
  73. id = NumberToString(id_number);
  74. } else {
  75. // On connecting some devices, e.g., nano KONTROL2, unknown endpoints
  76. // appear and disappear quickly and they fail on queries.
  77. // Let's ignore such ghost devices.
  78. // Same problems will happen if the device is disconnected before finishing
  79. // all queries.
  80. DLOG(WARNING) << "Failed to get kMIDIPropertyUniqueID with status "
  81. << result;
  82. }
  83. const PortState state = PortState::OPENED;
  84. return mojom::PortInfo(id, manufacturer, name, version, state);
  85. }
  86. base::TimeTicks MIDITimeStampToTimeTicks(MIDITimeStamp timestamp) {
  87. UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
  88. return base::TimeTicks() + base::Nanoseconds(nanoseconds);
  89. }
  90. MIDITimeStamp TimeTicksToMIDITimeStamp(base::TimeTicks ticks) {
  91. return AudioConvertNanosToHostTime(ticks.since_origin().InNanoseconds());
  92. }
  93. } // namespace
  94. MidiManager* MidiManager::Create(MidiService* service) {
  95. return new MidiManagerMac(service);
  96. }
  97. MidiManagerMac::MidiManagerMac(MidiService* service) : MidiManager(service) {}
  98. MidiManagerMac::~MidiManagerMac() {
  99. if (!service()->task_service()->UnbindInstance())
  100. return;
  101. // Finalization steps should be implemented after the UnbindInstance() call.
  102. // Do not need to dispose |coremidi_input_| and |coremidi_output_| explicitly.
  103. // CoreMIDI automatically disposes them on the client disposal.
  104. base::AutoLock lock(midi_client_lock_);
  105. if (midi_client_)
  106. MIDIClientDispose(midi_client_);
  107. }
  108. void MidiManagerMac::StartInitialization() {
  109. if (!service()->task_service()->BindInstance())
  110. return CompleteInitialization(Result::INITIALIZATION_ERROR);
  111. service()->task_service()->PostBoundTask(
  112. kClientTaskRunner, base::BindOnce(&MidiManagerMac::InitializeCoreMIDI,
  113. base::Unretained(this)));
  114. }
  115. void MidiManagerMac::DispatchSendMidiData(MidiManagerClient* client,
  116. uint32_t port_index,
  117. const std::vector<uint8_t>& data,
  118. base::TimeTicks timestamp) {
  119. service()->task_service()->PostBoundTask(
  120. kClientTaskRunner,
  121. base::BindOnce(&MidiManagerMac::SendMidiData, base::Unretained(this),
  122. client, port_index, data, timestamp));
  123. }
  124. void MidiManagerMac::InitializeCoreMIDI() {
  125. DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
  126. // CoreMIDI registration.
  127. MIDIClientRef client = 0u;
  128. OSStatus result = MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch,
  129. this, &client);
  130. if (result != noErr || client == 0u)
  131. return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
  132. {
  133. base::AutoLock lock(midi_client_lock_);
  134. midi_client_ = client;
  135. }
  136. // Create input and output port. These MIDIPortRef references are not needed
  137. // to be disposed explicitly. CoreMIDI automatically disposes them on the
  138. // client disposal.
  139. result = MIDIInputPortCreate(client, CFSTR("MIDI Input"), ReadMidiDispatch,
  140. this, &midi_input_);
  141. if (result != noErr || midi_input_ == 0u)
  142. return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
  143. result = MIDIOutputPortCreate(client, CFSTR("MIDI Output"), &midi_output_);
  144. if (result != noErr || midi_output_ == 0u)
  145. return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
  146. // Following loop may miss some newly attached devices, but such device will
  147. // be captured by ReceiveMidiNotifyDispatch callback.
  148. destinations_.resize(MIDIGetNumberOfDestinations());
  149. for (size_t i = 0u; i < destinations_.size(); ++i) {
  150. MIDIEndpointRef destination = MIDIGetDestination(i);
  151. DCHECK_NE(0u, destination);
  152. // Keep track of all destinations (known as outputs by the Web MIDI API).
  153. destinations_[i] = destination;
  154. AddOutputPort(GetPortInfoFromEndpoint(destination));
  155. }
  156. // Allocate maximum size of buffer that CoreMIDI can handle.
  157. midi_buffer_.resize(kCoreMIDIMaxPacketListSize);
  158. // Open connections from all sources. This loop also may miss new devices.
  159. sources_.resize(MIDIGetNumberOfSources());
  160. for (size_t i = 0u; i < sources_.size(); ++i) {
  161. MIDIEndpointRef source = MIDIGetSource(i);
  162. DCHECK_NE(0u, source);
  163. // Keep track of all sources (known as inputs by the Web MIDI API).
  164. sources_[i] = source;
  165. AddInputPort(GetPortInfoFromEndpoint(source));
  166. }
  167. // Start listening.
  168. for (size_t i = 0u; i < sources_.size(); ++i)
  169. MIDIPortConnectSource(midi_input_, sources_[i], reinterpret_cast<void*>(i));
  170. CompleteCoreMIDIInitialization(Result::OK);
  171. }
  172. void MidiManagerMac::CompleteCoreMIDIInitialization(mojom::Result result) {
  173. service()->task_service()->PostBoundTask(
  174. kSessionTaskRunner,
  175. base::BindOnce(&MidiManagerMac::CompleteInitialization,
  176. base::Unretained(this), result));
  177. }
  178. // static
  179. void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification* message,
  180. void* refcon) {
  181. // This callback function is invoked on |kClientTaskRunner|.
  182. // |manager| should be valid because we can ensure |midi_client_| is still
  183. // alive here.
  184. MidiManagerMac* manager = static_cast<MidiManagerMac*>(refcon);
  185. manager->ReceiveMidiNotify(message);
  186. }
  187. void MidiManagerMac::ReceiveMidiNotify(const MIDINotification* message) {
  188. DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
  189. if (kMIDIMsgObjectAdded == message->messageID) {
  190. // New device is going to be attached.
  191. const MIDIObjectAddRemoveNotification* notification =
  192. reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
  193. MIDIEndpointRef endpoint =
  194. static_cast<MIDIEndpointRef>(notification->child);
  195. if (notification->childType == kMIDIObjectType_Source) {
  196. // Attaching device is an input device.
  197. auto it = std::find(sources_.begin(), sources_.end(), endpoint);
  198. if (it == sources_.end()) {
  199. mojom::PortInfo info = GetPortInfoFromEndpoint(endpoint);
  200. // If the device disappears before finishing queries, mojom::PortInfo
  201. // becomes incomplete. Skip and do not cache such information here.
  202. // On kMIDIMsgObjectRemoved, the entry will be ignored because it
  203. // will not be found in the pool.
  204. if (!info.id.empty()) {
  205. sources_.push_back(endpoint);
  206. AddInputPort(info);
  207. MIDIPortConnectSource(midi_input_, endpoint,
  208. reinterpret_cast<void*>(sources_.size() - 1));
  209. }
  210. } else {
  211. SetInputPortState(it - sources_.begin(), PortState::OPENED);
  212. }
  213. } else if (notification->childType == kMIDIObjectType_Destination) {
  214. // Attaching device is an output device.
  215. auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
  216. if (it == destinations_.end()) {
  217. mojom::PortInfo info = GetPortInfoFromEndpoint(endpoint);
  218. // Skip cases that queries are not finished correctly.
  219. if (!info.id.empty()) {
  220. destinations_.push_back(endpoint);
  221. AddOutputPort(info);
  222. }
  223. } else {
  224. SetOutputPortState(it - destinations_.begin(), PortState::OPENED);
  225. }
  226. }
  227. } else if (kMIDIMsgObjectRemoved == message->messageID) {
  228. // Existing device is going to be detached.
  229. const MIDIObjectAddRemoveNotification* notification =
  230. reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
  231. MIDIEndpointRef endpoint =
  232. static_cast<MIDIEndpointRef>(notification->child);
  233. if (notification->childType == kMIDIObjectType_Source) {
  234. // Detaching device is an input device.
  235. auto it = std::find(sources_.begin(), sources_.end(), endpoint);
  236. if (it != sources_.end())
  237. SetInputPortState(it - sources_.begin(), PortState::DISCONNECTED);
  238. } else if (notification->childType == kMIDIObjectType_Destination) {
  239. // Detaching device is an output device.
  240. auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
  241. if (it != destinations_.end())
  242. SetOutputPortState(it - destinations_.begin(), PortState::DISCONNECTED);
  243. }
  244. }
  245. }
  246. // static
  247. void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list,
  248. void* read_proc_refcon,
  249. void* src_conn_refcon) {
  250. // This method is called on a separate high-priority thread owned by CoreMIDI.
  251. // |manager| should be valid because we can ensure |midi_client_| is still
  252. // alive here.
  253. MidiManagerMac* manager = static_cast<MidiManagerMac*>(read_proc_refcon);
  254. DCHECK(manager);
  255. uint32_t port_index = reinterpret_cast<uintptr_t>(src_conn_refcon);
  256. // Go through each packet and process separately.
  257. const MIDIPacket* packet = &packet_list->packet[0];
  258. for (size_t i = 0u; i < packet_list->numPackets; i++) {
  259. // Each packet contains MIDI data for one or more messages (like note-on).
  260. base::TimeTicks timestamp = MIDITimeStampToTimeTicks(packet->timeStamp);
  261. manager->ReceiveMidiData(port_index, packet->data, packet->length,
  262. timestamp);
  263. packet = MIDIPacketNext(packet);
  264. }
  265. }
  266. void MidiManagerMac::SendMidiData(MidiManagerClient* client,
  267. uint32_t port_index,
  268. const std::vector<uint8_t>& data,
  269. base::TimeTicks timestamp) {
  270. DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
  271. // Lookup the destination based on the port index.
  272. if (static_cast<size_t>(port_index) >= destinations_.size())
  273. return;
  274. MIDITimeStamp coremidi_timestamp = TimeTicksToMIDITimeStamp(timestamp);
  275. MIDIEndpointRef destination = destinations_[port_index];
  276. size_t send_size;
  277. for (size_t sent_size = 0u; sent_size < data.size(); sent_size += send_size) {
  278. MIDIPacketList* packet_list =
  279. reinterpret_cast<MIDIPacketList*>(midi_buffer_.data());
  280. MIDIPacket* midi_packet = MIDIPacketListInit(packet_list);
  281. // Limit the maximum payload size to kEstimatedMaxPacketDataSize that is
  282. // half of midi_buffer data size. MIDIPacketList and MIDIPacket consume
  283. // extra buffer areas for meta information, and available size is smaller
  284. // than buffer size. Here, we simply assume that at least half size is
  285. // available for data payload.
  286. send_size = std::min(data.size() - sent_size, kEstimatedMaxPacketDataSize);
  287. midi_packet = MIDIPacketListAdd(
  288. packet_list,
  289. kCoreMIDIMaxPacketListSize,
  290. midi_packet,
  291. coremidi_timestamp,
  292. send_size,
  293. &data[sent_size]);
  294. DCHECK(midi_packet);
  295. MIDISend(midi_output_, destination, packet_list);
  296. }
  297. AccumulateMidiBytesSent(client, data.size());
  298. }
  299. } // namespace midi