blob: fccc62244d8b6fb7913d55f8abc03f6cec9c10ad [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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_usb.h"
#include <memory>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/strings/stringprintf.h"
#include "media/midi/midi_service.h"
#include "media/midi/task_service.h"
#include "media/midi/usb_midi_descriptor_parser.h"
namespace midi {
using mojom::PortState;
using mojom::Result;
MidiManagerUsb::MidiManagerUsb(MidiService* service,
std::unique_ptr<UsbMidiDevice::Factory> factory)
: MidiManager(service), device_factory_(std::move(factory)) {}
MidiManagerUsb::~MidiManagerUsb() {
if (!service()->task_service()->UnbindInstance())
return;
// Finalization steps should be implemented after the UnbindInstance() call
// above, if we need.
}
void MidiManagerUsb::StartInitialization() {
if (!service()->task_service()->BindInstance())
return CompleteInitialization(Result::INITIALIZATION_ERROR);
Initialize();
}
void MidiManagerUsb::Initialize() {
// This is safe because EnumerateDevices cancels the operation on destruction.
device_factory_->EnumerateDevices(
this, base::BindOnce(&MidiManagerUsb::OnEnumerateDevicesDone,
base::Unretained(this)));
}
void MidiManagerUsb::DispatchSendMidiData(MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
base::TimeTicks timestamp) {
if (port_index >= output_streams_.size()) {
// |port_index| is provided by a renderer so we can't believe that it is
// in the valid range.
return;
}
base::TimeDelta delay = MidiService::TimestampToTimeDeltaDelay(timestamp);
// output_streams_[port_index] is alive unless MidiManagerUsb is deleted.
service()->task_service()->PostBoundDelayedTask(
TaskService::kDefaultRunnerId,
base::BindOnce(&UsbMidiOutputStream::Send,
base::Unretained(output_streams_[port_index].get()), data),
delay);
service()->task_service()->PostBoundDelayedTask(
TaskService::kDefaultRunnerId,
base::BindOnce(&MidiManager::AccumulateMidiBytesSent,
base::Unretained(this), client, data.size()),
delay);
}
void MidiManagerUsb::ReceiveUsbMidiData(UsbMidiDevice* device,
int endpoint_number,
const uint8_t* data,
size_t size,
base::TimeTicks time) {
if (!input_stream_)
return;
input_stream_->OnReceivedData(device,
endpoint_number,
data,
size,
time);
}
void MidiManagerUsb::OnDeviceAttached(std::unique_ptr<UsbMidiDevice> device) {
int device_id = static_cast<int>(devices_.size());
devices_.push_back(std::move(device));
AddPorts(devices_.back().get(), device_id);
}
void MidiManagerUsb::OnDeviceDetached(size_t index) {
if (index >= devices_.size()) {
return;
}
UsbMidiDevice* device = devices_[index].get();
for (size_t i = 0; i < output_streams_.size(); ++i) {
if (output_streams_[i]->jack().device == device) {
SetOutputPortState(static_cast<uint32_t>(i), PortState::DISCONNECTED);
}
}
const std::vector<UsbMidiJack>& input_jacks = input_stream_->jacks();
for (size_t i = 0; i < input_jacks.size(); ++i) {
if (input_jacks[i].device == device) {
SetInputPortState(static_cast<uint32_t>(i), PortState::DISCONNECTED);
}
}
}
void MidiManagerUsb::OnReceivedData(size_t jack_index,
const uint8_t* data,
size_t size,
base::TimeTicks time) {
ReceiveMidiData(static_cast<uint32_t>(jack_index), data, size, time);
}
void MidiManagerUsb::OnEnumerateDevicesDone(bool result,
UsbMidiDevice::Devices* devices) {
if (result) {
input_stream_ = std::make_unique<UsbMidiInputStream>(this);
devices->swap(devices_);
for (size_t i = 0; i < devices_.size(); ++i) {
if (!AddPorts(devices_[i].get(), static_cast<int>(i))) {
result = false;
break;
}
}
}
service()->task_service()->PostBoundTask(
TaskService::kDefaultRunnerId,
base::BindOnce(&MidiManager::CompleteInitialization,
base::Unretained(this),
result ? Result::OK : Result::INITIALIZATION_ERROR));
}
bool MidiManagerUsb::AddPorts(UsbMidiDevice* device, int device_id) {
UsbMidiDescriptorParser parser;
std::vector<uint8_t> descriptor = device->GetDescriptors();
const uint8_t* data = descriptor.size() > 0 ? &descriptor[0] : NULL;
std::vector<UsbMidiJack> jacks;
bool parse_result = parser.Parse(device,
data,
descriptor.size(),
&jacks);
if (!parse_result)
return false;
std::string manufacturer(device->GetManufacturer());
std::string product_name(device->GetProductName());
std::string version(device->GetDeviceVersion());
for (size_t j = 0; j < jacks.size(); ++j) {
// Port ID must be unique in a MIDI manager. This ID setting is
// sufficiently unique although there is no user-friendly meaning.
// TODO(yhirano): Use a hashed string as ID.
std::string id(
base::StringPrintf("usb:port-%d-%ld", device_id, static_cast<long>(j)));
if (jacks[j].direction() == UsbMidiJack::DIRECTION_OUT) {
output_streams_.push_back(
std::make_unique<UsbMidiOutputStream>(jacks[j]));
AddOutputPort(mojom::PortInfo(id, manufacturer, product_name, version,
PortState::OPENED));
} else {
DCHECK_EQ(jacks[j].direction(), UsbMidiJack::DIRECTION_IN);
input_stream_->Add(jacks[j]);
AddInputPort(mojom::PortInfo(id, manufacturer, product_name, version,
PortState::OPENED));
}
}
return true;
}
} // namespace midi