Roll src/third_party/skia 1107e90:06f3647
[chromium-blink-merge.git] / media / midi / midi_manager_mac.cc
blob1a5f426a09d08aa50d7125e20dc76ee8af692dbf
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.
5 #include "media/midi/midi_manager_mac.h"
7 #include <algorithm>
8 #include <string>
10 #include "base/bind.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/sys_string_conversions.h"
15 #include <CoreAudio/HostTime.h>
17 using base::IntToString;
18 using base::SysCFStringRefToUTF8;
19 using std::string;
21 // NB: System MIDI types are pointer types in 32-bit and integer types in
22 // 64-bit. Therefore, the initialization is the simplest one that satisfies both
23 // (if possible).
25 namespace media {
26 namespace midi {
28 namespace {
30 // Maximum buffer size that CoreMIDI can handle for MIDIPacketList.
31 const size_t kCoreMIDIMaxPacketListSize = 65536;
32 // Pessimistic estimation on available data size of MIDIPacketList.
33 const size_t kEstimatedMaxPacketDataSize = kCoreMIDIMaxPacketListSize / 2;
35 MidiPortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint) {
36 string manufacturer;
37 CFStringRef manufacturer_ref = NULL;
38 OSStatus result = MIDIObjectGetStringProperty(
39 endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
40 if (result == noErr) {
41 manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
42 } else {
43 // kMIDIPropertyManufacturer is not supported in IAC driver providing
44 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
45 DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status "
46 << result;
49 string name;
50 CFStringRef name_ref = NULL;
51 result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName,
52 &name_ref);
53 if (result == noErr) {
54 name = SysCFStringRefToUTF8(name_ref);
55 } else {
56 DLOG(WARNING) << "Failed to get kMIDIPropertyDisplayName with status "
57 << result;
60 string version;
61 SInt32 version_number = 0;
62 result = MIDIObjectGetIntegerProperty(
63 endpoint, kMIDIPropertyDriverVersion, &version_number);
64 if (result == noErr) {
65 version = IntToString(version_number);
66 } else {
67 // kMIDIPropertyDriverVersion is not supported in IAC driver providing
68 // endpoints, and the result will be kMIDIUnknownProperty (-10835).
69 DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status "
70 << result;
73 string id;
74 SInt32 id_number = 0;
75 result = MIDIObjectGetIntegerProperty(
76 endpoint, kMIDIPropertyUniqueID, &id_number);
77 if (result == noErr) {
78 id = IntToString(id_number);
79 } else {
80 // On connecting some devices, e.g., nano KONTROL2, unknown endpoints
81 // appear and disappear quickly and they fail on queries.
82 // Let's ignore such ghost devices.
83 // Same problems will happen if the device is disconnected before finishing
84 // all queries.
85 DLOG(WARNING) << "Failed to get kMIDIPropertyUniqueID with status "
86 << result;
89 const MidiPortState state = MIDI_PORT_OPENED;
90 return MidiPortInfo(id, manufacturer, name, version, state);
93 double MIDITimeStampToSeconds(MIDITimeStamp timestamp) {
94 UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
95 return static_cast<double>(nanoseconds) / 1.0e9;
98 MIDITimeStamp SecondsToMIDITimeStamp(double seconds) {
99 UInt64 nanos = UInt64(seconds * 1.0e9);
100 return AudioConvertNanosToHostTime(nanos);
103 } // namespace
105 MidiManager* MidiManager::Create() {
106 return new MidiManagerMac();
109 MidiManagerMac::MidiManagerMac()
110 : midi_client_(0),
111 coremidi_input_(0),
112 coremidi_output_(0),
113 client_thread_("MidiClientThread"),
114 shutdown_(false) {
117 MidiManagerMac::~MidiManagerMac() {
118 // Wait for the termination of |client_thread_| before disposing MIDI ports.
119 shutdown_ = true;
120 client_thread_.Stop();
122 if (coremidi_input_)
123 MIDIPortDispose(coremidi_input_);
124 if (coremidi_output_)
125 MIDIPortDispose(coremidi_output_);
126 if (midi_client_)
127 MIDIClientDispose(midi_client_);
130 void MidiManagerMac::StartInitialization() {
131 // MIDIClient should be created on |client_thread_| to receive CoreMIDI event
132 // notifications.
133 RunOnClientThread(
134 base::Bind(&MidiManagerMac::InitializeCoreMIDI, base::Unretained(this)));
137 void MidiManagerMac::DispatchSendMidiData(MidiManagerClient* client,
138 uint32 port_index,
139 const std::vector<uint8>& data,
140 double timestamp) {
141 RunOnClientThread(
142 base::Bind(&MidiManagerMac::SendMidiData,
143 base::Unretained(this), client, port_index, data, timestamp));
146 void MidiManagerMac::RunOnClientThread(const base::Closure& closure) {
147 if (shutdown_)
148 return;
150 if (!client_thread_.IsRunning())
151 client_thread_.Start();
153 client_thread_.message_loop()->PostTask(FROM_HERE, closure);
156 void MidiManagerMac::InitializeCoreMIDI() {
157 DCHECK(client_thread_.message_loop_proxy()->BelongsToCurrentThread());
159 // CoreMIDI registration.
160 DCHECK_EQ(0u, midi_client_);
161 OSStatus result =
162 MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch, this,
163 &midi_client_);
164 if (result != noErr || midi_client_ == 0)
165 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
167 // Create input and output port.
168 DCHECK_EQ(0u, coremidi_input_);
169 result = MIDIInputPortCreate(
170 midi_client_,
171 CFSTR("MIDI Input"),
172 ReadMidiDispatch,
173 this,
174 &coremidi_input_);
175 if (result != noErr || coremidi_input_ == 0)
176 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
178 DCHECK_EQ(0u, coremidi_output_);
179 result = MIDIOutputPortCreate(
180 midi_client_,
181 CFSTR("MIDI Output"),
182 &coremidi_output_);
183 if (result != noErr || coremidi_output_ == 0)
184 return CompleteInitialization(MIDI_INITIALIZATION_ERROR);
186 // Following loop may miss some newly attached devices, but such device will
187 // be captured by ReceiveMidiNotifyDispatch callback.
188 uint32 destination_count = MIDIGetNumberOfDestinations();
189 destinations_.resize(destination_count);
190 for (uint32 i = 0; i < destination_count ; i++) {
191 MIDIEndpointRef destination = MIDIGetDestination(i);
192 if (destination == 0) {
193 // One ore more devices may be detached.
194 destinations_.resize(i);
195 break;
198 // Keep track of all destinations (known as outputs by the Web MIDI API).
199 // Cache to avoid any possible overhead in calling MIDIGetDestination().
200 destinations_[i] = destination;
202 MidiPortInfo info = GetPortInfoFromEndpoint(destination);
203 AddOutputPort(info);
206 // Open connections from all sources. This loop also may miss new devices.
207 uint32 source_count = MIDIGetNumberOfSources();
208 for (uint32 i = 0; i < source_count; ++i) {
209 // Receive from all sources.
210 MIDIEndpointRef source = MIDIGetSource(i);
211 if (source == 0)
212 break;
214 // Start listening.
215 MIDIPortConnectSource(
216 coremidi_input_, source, reinterpret_cast<void*>(source));
218 // Keep track of all sources (known as inputs in Web MIDI API terminology).
219 source_map_[source] = i;
221 MidiPortInfo info = GetPortInfoFromEndpoint(source);
222 AddInputPort(info);
225 // Allocate maximum size of buffer that CoreMIDI can handle.
226 midi_buffer_.resize(kCoreMIDIMaxPacketListSize);
228 CompleteInitialization(MIDI_OK);
231 // static
232 void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification* message,
233 void* refcon) {
234 // This callback function is invoked on |client_thread_|.
235 MidiManagerMac* manager = static_cast<MidiManagerMac*>(refcon);
236 manager->ReceiveMidiNotify(message);
239 void MidiManagerMac::ReceiveMidiNotify(const MIDINotification* message) {
240 DCHECK(client_thread_.message_loop_proxy()->BelongsToCurrentThread());
242 if (kMIDIMsgObjectAdded == message->messageID) {
243 // New device is going to be attached.
244 const MIDIObjectAddRemoveNotification* notification =
245 reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
246 MIDIEndpointRef endpoint =
247 static_cast<MIDIEndpointRef>(notification->child);
248 if (notification->childType == kMIDIObjectType_Source) {
249 // Attaching device is an input device.
250 auto it = source_map_.find(endpoint);
251 if (it == source_map_.end()) {
252 MidiPortInfo info = GetPortInfoFromEndpoint(endpoint);
253 // If the device disappears before finishing queries, MidiPortInfo
254 // becomes incomplete. Skip and do not cache such information here.
255 // On kMIDIMsgObjectRemoved, the entry will be ignored because it
256 // will not be found in the pool.
257 if (!info.id.empty()) {
258 uint32 index = source_map_.size();
259 source_map_[endpoint] = index;
260 AddInputPort(info);
261 MIDIPortConnectSource(
262 coremidi_input_, endpoint, reinterpret_cast<void*>(endpoint));
264 } else {
265 SetInputPortState(it->second, MIDI_PORT_OPENED);
267 } else if (notification->childType == kMIDIObjectType_Destination) {
268 // Attaching device is an output device.
269 auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
270 if (it == destinations_.end()) {
271 MidiPortInfo info = GetPortInfoFromEndpoint(endpoint);
272 // Skip cases that queries are not finished correctly.
273 if (!info.id.empty()) {
274 destinations_.push_back(endpoint);
275 AddOutputPort(info);
277 } else {
278 SetOutputPortState(it - destinations_.begin(), MIDI_PORT_OPENED);
281 } else if (kMIDIMsgObjectRemoved == message->messageID) {
282 // Existing device is going to be detached.
283 const MIDIObjectAddRemoveNotification* notification =
284 reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
285 MIDIEndpointRef endpoint =
286 static_cast<MIDIEndpointRef>(notification->child);
287 if (notification->childType == kMIDIObjectType_Source) {
288 // Detaching device is an input device.
289 auto it = source_map_.find(endpoint);
290 if (it != source_map_.end())
291 SetInputPortState(it->second, MIDI_PORT_DISCONNECTED);
292 } else if (notification->childType == kMIDIObjectType_Destination) {
293 // Detaching device is an output device.
294 auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
295 if (it != destinations_.end())
296 SetOutputPortState(it - destinations_.begin(), MIDI_PORT_DISCONNECTED);
301 // static
302 void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list,
303 void* read_proc_refcon,
304 void* src_conn_refcon) {
305 // This method is called on a separate high-priority thread owned by CoreMIDI.
307 MidiManagerMac* manager = static_cast<MidiManagerMac*>(read_proc_refcon);
308 #if __LP64__
309 MIDIEndpointRef source = reinterpret_cast<uintptr_t>(src_conn_refcon);
310 #else
311 MIDIEndpointRef source = static_cast<MIDIEndpointRef>(src_conn_refcon);
312 #endif
314 // Dispatch to class method.
315 manager->ReadMidi(source, packet_list);
318 void MidiManagerMac::ReadMidi(MIDIEndpointRef source,
319 const MIDIPacketList* packet_list) {
320 // This method is called from ReadMidiDispatch() and runs on a separate
321 // high-priority thread owned by CoreMIDI.
323 // Lookup the port index based on the source.
324 auto it = source_map_.find(source);
325 if (it == source_map_.end())
326 return;
327 // This is safe since MidiManagerMac does not remove any existing
328 // MIDIEndpointRef, and the order is saved.
329 uint32 port_index = it->second;
331 // Go through each packet and process separately.
332 const MIDIPacket* packet = &packet_list->packet[0];
333 for (size_t i = 0; i < packet_list->numPackets; i++) {
334 // Each packet contains MIDI data for one or more messages (like note-on).
335 double timestamp_seconds = MIDITimeStampToSeconds(packet->timeStamp);
337 ReceiveMidiData(
338 port_index,
339 packet->data,
340 packet->length,
341 timestamp_seconds);
343 packet = MIDIPacketNext(packet);
347 void MidiManagerMac::SendMidiData(MidiManagerClient* client,
348 uint32 port_index,
349 const std::vector<uint8>& data,
350 double timestamp) {
351 DCHECK(client_thread_.message_loop_proxy()->BelongsToCurrentThread());
353 // Lookup the destination based on the port index.
354 if (static_cast<size_t>(port_index) >= destinations_.size())
355 return;
357 MIDITimeStamp coremidi_timestamp = SecondsToMIDITimeStamp(timestamp);
358 MIDIEndpointRef destination = destinations_[port_index];
360 size_t send_size;
361 for (size_t sent_size = 0; sent_size < data.size(); sent_size += send_size) {
362 MIDIPacketList* packet_list =
363 reinterpret_cast<MIDIPacketList*>(midi_buffer_.data());
364 MIDIPacket* midi_packet = MIDIPacketListInit(packet_list);
365 // Limit the maximum payload size to kEstimatedMaxPacketDataSize that is
366 // half of midi_buffer data size. MIDIPacketList and MIDIPacket consume
367 // extra buffer areas for meta information, and available size is smaller
368 // than buffer size. Here, we simply assume that at least half size is
369 // available for data payload.
370 send_size = std::min(data.size() - sent_size, kEstimatedMaxPacketDataSize);
371 midi_packet = MIDIPacketListAdd(
372 packet_list,
373 kCoreMIDIMaxPacketListSize,
374 midi_packet,
375 coremidi_timestamp,
376 send_size,
377 &data[sent_size]);
378 DCHECK(midi_packet);
380 MIDISend(coremidi_output_, destination, packet_list);
383 client->AccumulateMidiBytesSent(data.size());
386 } // namespace midi
387 } // namespace media