123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- // 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_mac.h"
- #include <stddef.h>
- #include <algorithm>
- #include <iterator>
- #include <string>
- #include <CoreAudio/HostTime.h>
- #include "base/bind.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/sys_string_conversions.h"
- #include "base/task/single_thread_task_runner.h"
- #include "media/midi/midi_service.h"
- #include "media/midi/task_service.h"
- using base::NumberToString;
- using base::SysCFStringRefToUTF8;
- using midi::mojom::PortState;
- using midi::mojom::Result;
- // NB: System MIDI types are pointer types in 32-bit and integer types in
- // 64-bit. Therefore, the initialization is the simplest one that satisfies both
- // (if possible).
- namespace midi {
- namespace {
- // Maximum buffer size that CoreMIDI can handle for MIDIPacketList.
- const size_t kCoreMIDIMaxPacketListSize = 65536;
- // Pessimistic estimation on available data size of MIDIPacketList.
- const size_t kEstimatedMaxPacketDataSize = kCoreMIDIMaxPacketListSize / 2;
- enum {
- kSessionTaskRunner = TaskService::kDefaultRunnerId,
- kClientTaskRunner,
- };
- mojom::PortInfo GetPortInfoFromEndpoint(MIDIEndpointRef endpoint) {
- std::string manufacturer;
- CFStringRef manufacturer_ref = NULL;
- OSStatus result = MIDIObjectGetStringProperty(
- endpoint, kMIDIPropertyManufacturer, &manufacturer_ref);
- if (result == noErr) {
- manufacturer = SysCFStringRefToUTF8(manufacturer_ref);
- } else {
- // kMIDIPropertyManufacturer is not supported in IAC driver providing
- // endpoints, and the result will be kMIDIUnknownProperty (-10835).
- DLOG(WARNING) << "Failed to get kMIDIPropertyManufacturer with status "
- << result;
- }
- std::string name;
- CFStringRef name_ref = NULL;
- result = MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName,
- &name_ref);
- if (result == noErr) {
- name = SysCFStringRefToUTF8(name_ref);
- } else {
- DLOG(WARNING) << "Failed to get kMIDIPropertyDisplayName with status "
- << result;
- }
- std::string version;
- SInt32 version_number = 0;
- result = MIDIObjectGetIntegerProperty(
- endpoint, kMIDIPropertyDriverVersion, &version_number);
- if (result == noErr) {
- version = NumberToString(version_number);
- } else {
- // kMIDIPropertyDriverVersion is not supported in IAC driver providing
- // endpoints, and the result will be kMIDIUnknownProperty (-10835).
- DLOG(WARNING) << "Failed to get kMIDIPropertyDriverVersion with status "
- << result;
- }
- std::string id;
- SInt32 id_number = 0;
- result = MIDIObjectGetIntegerProperty(
- endpoint, kMIDIPropertyUniqueID, &id_number);
- if (result == noErr) {
- id = NumberToString(id_number);
- } else {
- // On connecting some devices, e.g., nano KONTROL2, unknown endpoints
- // appear and disappear quickly and they fail on queries.
- // Let's ignore such ghost devices.
- // Same problems will happen if the device is disconnected before finishing
- // all queries.
- DLOG(WARNING) << "Failed to get kMIDIPropertyUniqueID with status "
- << result;
- }
- const PortState state = PortState::OPENED;
- return mojom::PortInfo(id, manufacturer, name, version, state);
- }
- base::TimeTicks MIDITimeStampToTimeTicks(MIDITimeStamp timestamp) {
- UInt64 nanoseconds = AudioConvertHostTimeToNanos(timestamp);
- return base::TimeTicks() + base::Nanoseconds(nanoseconds);
- }
- MIDITimeStamp TimeTicksToMIDITimeStamp(base::TimeTicks ticks) {
- return AudioConvertNanosToHostTime(ticks.since_origin().InNanoseconds());
- }
- } // namespace
- MidiManager* MidiManager::Create(MidiService* service) {
- return new MidiManagerMac(service);
- }
- MidiManagerMac::MidiManagerMac(MidiService* service) : MidiManager(service) {}
- MidiManagerMac::~MidiManagerMac() {
- if (!service()->task_service()->UnbindInstance())
- return;
- // Finalization steps should be implemented after the UnbindInstance() call.
- // Do not need to dispose |coremidi_input_| and |coremidi_output_| explicitly.
- // CoreMIDI automatically disposes them on the client disposal.
- base::AutoLock lock(midi_client_lock_);
- if (midi_client_)
- MIDIClientDispose(midi_client_);
- }
- void MidiManagerMac::StartInitialization() {
- if (!service()->task_service()->BindInstance())
- return CompleteInitialization(Result::INITIALIZATION_ERROR);
- service()->task_service()->PostBoundTask(
- kClientTaskRunner, base::BindOnce(&MidiManagerMac::InitializeCoreMIDI,
- base::Unretained(this)));
- }
- void MidiManagerMac::DispatchSendMidiData(MidiManagerClient* client,
- uint32_t port_index,
- const std::vector<uint8_t>& data,
- base::TimeTicks timestamp) {
- service()->task_service()->PostBoundTask(
- kClientTaskRunner,
- base::BindOnce(&MidiManagerMac::SendMidiData, base::Unretained(this),
- client, port_index, data, timestamp));
- }
- void MidiManagerMac::InitializeCoreMIDI() {
- DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
- // CoreMIDI registration.
- MIDIClientRef client = 0u;
- OSStatus result = MIDIClientCreate(CFSTR("Chrome"), ReceiveMidiNotifyDispatch,
- this, &client);
- if (result != noErr || client == 0u)
- return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
- {
- base::AutoLock lock(midi_client_lock_);
- midi_client_ = client;
- }
- // Create input and output port. These MIDIPortRef references are not needed
- // to be disposed explicitly. CoreMIDI automatically disposes them on the
- // client disposal.
- result = MIDIInputPortCreate(client, CFSTR("MIDI Input"), ReadMidiDispatch,
- this, &midi_input_);
- if (result != noErr || midi_input_ == 0u)
- return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
- result = MIDIOutputPortCreate(client, CFSTR("MIDI Output"), &midi_output_);
- if (result != noErr || midi_output_ == 0u)
- return CompleteCoreMIDIInitialization(Result::INITIALIZATION_ERROR);
- // Following loop may miss some newly attached devices, but such device will
- // be captured by ReceiveMidiNotifyDispatch callback.
- destinations_.resize(MIDIGetNumberOfDestinations());
- for (size_t i = 0u; i < destinations_.size(); ++i) {
- MIDIEndpointRef destination = MIDIGetDestination(i);
- DCHECK_NE(0u, destination);
- // Keep track of all destinations (known as outputs by the Web MIDI API).
- destinations_[i] = destination;
- AddOutputPort(GetPortInfoFromEndpoint(destination));
- }
- // Allocate maximum size of buffer that CoreMIDI can handle.
- midi_buffer_.resize(kCoreMIDIMaxPacketListSize);
- // Open connections from all sources. This loop also may miss new devices.
- sources_.resize(MIDIGetNumberOfSources());
- for (size_t i = 0u; i < sources_.size(); ++i) {
- MIDIEndpointRef source = MIDIGetSource(i);
- DCHECK_NE(0u, source);
- // Keep track of all sources (known as inputs by the Web MIDI API).
- sources_[i] = source;
- AddInputPort(GetPortInfoFromEndpoint(source));
- }
- // Start listening.
- for (size_t i = 0u; i < sources_.size(); ++i)
- MIDIPortConnectSource(midi_input_, sources_[i], reinterpret_cast<void*>(i));
- CompleteCoreMIDIInitialization(Result::OK);
- }
- void MidiManagerMac::CompleteCoreMIDIInitialization(mojom::Result result) {
- service()->task_service()->PostBoundTask(
- kSessionTaskRunner,
- base::BindOnce(&MidiManagerMac::CompleteInitialization,
- base::Unretained(this), result));
- }
- // static
- void MidiManagerMac::ReceiveMidiNotifyDispatch(const MIDINotification* message,
- void* refcon) {
- // This callback function is invoked on |kClientTaskRunner|.
- // |manager| should be valid because we can ensure |midi_client_| is still
- // alive here.
- MidiManagerMac* manager = static_cast<MidiManagerMac*>(refcon);
- manager->ReceiveMidiNotify(message);
- }
- void MidiManagerMac::ReceiveMidiNotify(const MIDINotification* message) {
- DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
- if (kMIDIMsgObjectAdded == message->messageID) {
- // New device is going to be attached.
- const MIDIObjectAddRemoveNotification* notification =
- reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
- MIDIEndpointRef endpoint =
- static_cast<MIDIEndpointRef>(notification->child);
- if (notification->childType == kMIDIObjectType_Source) {
- // Attaching device is an input device.
- auto it = std::find(sources_.begin(), sources_.end(), endpoint);
- if (it == sources_.end()) {
- mojom::PortInfo info = GetPortInfoFromEndpoint(endpoint);
- // If the device disappears before finishing queries, mojom::PortInfo
- // becomes incomplete. Skip and do not cache such information here.
- // On kMIDIMsgObjectRemoved, the entry will be ignored because it
- // will not be found in the pool.
- if (!info.id.empty()) {
- sources_.push_back(endpoint);
- AddInputPort(info);
- MIDIPortConnectSource(midi_input_, endpoint,
- reinterpret_cast<void*>(sources_.size() - 1));
- }
- } else {
- SetInputPortState(it - sources_.begin(), PortState::OPENED);
- }
- } else if (notification->childType == kMIDIObjectType_Destination) {
- // Attaching device is an output device.
- auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
- if (it == destinations_.end()) {
- mojom::PortInfo info = GetPortInfoFromEndpoint(endpoint);
- // Skip cases that queries are not finished correctly.
- if (!info.id.empty()) {
- destinations_.push_back(endpoint);
- AddOutputPort(info);
- }
- } else {
- SetOutputPortState(it - destinations_.begin(), PortState::OPENED);
- }
- }
- } else if (kMIDIMsgObjectRemoved == message->messageID) {
- // Existing device is going to be detached.
- const MIDIObjectAddRemoveNotification* notification =
- reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
- MIDIEndpointRef endpoint =
- static_cast<MIDIEndpointRef>(notification->child);
- if (notification->childType == kMIDIObjectType_Source) {
- // Detaching device is an input device.
- auto it = std::find(sources_.begin(), sources_.end(), endpoint);
- if (it != sources_.end())
- SetInputPortState(it - sources_.begin(), PortState::DISCONNECTED);
- } else if (notification->childType == kMIDIObjectType_Destination) {
- // Detaching device is an output device.
- auto it = std::find(destinations_.begin(), destinations_.end(), endpoint);
- if (it != destinations_.end())
- SetOutputPortState(it - destinations_.begin(), PortState::DISCONNECTED);
- }
- }
- }
- // static
- void MidiManagerMac::ReadMidiDispatch(const MIDIPacketList* packet_list,
- void* read_proc_refcon,
- void* src_conn_refcon) {
- // This method is called on a separate high-priority thread owned by CoreMIDI.
- // |manager| should be valid because we can ensure |midi_client_| is still
- // alive here.
- MidiManagerMac* manager = static_cast<MidiManagerMac*>(read_proc_refcon);
- DCHECK(manager);
- uint32_t port_index = reinterpret_cast<uintptr_t>(src_conn_refcon);
- // Go through each packet and process separately.
- const MIDIPacket* packet = &packet_list->packet[0];
- for (size_t i = 0u; i < packet_list->numPackets; i++) {
- // Each packet contains MIDI data for one or more messages (like note-on).
- base::TimeTicks timestamp = MIDITimeStampToTimeTicks(packet->timeStamp);
- manager->ReceiveMidiData(port_index, packet->data, packet->length,
- timestamp);
- packet = MIDIPacketNext(packet);
- }
- }
- void MidiManagerMac::SendMidiData(MidiManagerClient* client,
- uint32_t port_index,
- const std::vector<uint8_t>& data,
- base::TimeTicks timestamp) {
- DCHECK(service()->task_service()->IsOnTaskRunner(kClientTaskRunner));
- // Lookup the destination based on the port index.
- if (static_cast<size_t>(port_index) >= destinations_.size())
- return;
- MIDITimeStamp coremidi_timestamp = TimeTicksToMIDITimeStamp(timestamp);
- MIDIEndpointRef destination = destinations_[port_index];
- size_t send_size;
- for (size_t sent_size = 0u; sent_size < data.size(); sent_size += send_size) {
- MIDIPacketList* packet_list =
- reinterpret_cast<MIDIPacketList*>(midi_buffer_.data());
- MIDIPacket* midi_packet = MIDIPacketListInit(packet_list);
- // Limit the maximum payload size to kEstimatedMaxPacketDataSize that is
- // half of midi_buffer data size. MIDIPacketList and MIDIPacket consume
- // extra buffer areas for meta information, and available size is smaller
- // than buffer size. Here, we simply assume that at least half size is
- // available for data payload.
- send_size = std::min(data.size() - sent_size, kEstimatedMaxPacketDataSize);
- midi_packet = MIDIPacketListAdd(
- packet_list,
- kCoreMIDIMaxPacketListSize,
- midi_packet,
- coremidi_timestamp,
- send_size,
- &data[sent_size]);
- DCHECK(midi_packet);
- MIDISend(midi_output_, destination, packet_list);
- }
- AccumulateMidiBytesSent(client, data.size());
- }
- } // namespace midi
|