Files
midi_framework/frameworks/native/midi/src/midi_client.cpp
T
2026-01-16 15:27:13 +08:00

426 lines
15 KiB
C++

/*
* Copyright (c) 2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LOG_TAG
#define LOG_TAG "MidiClient"
#endif
#include <cstring>
#include <chrono>
#include "midi_log.h"
#include "midi_client_private.h"
#include "midi_callback_stub.h"
#include "midi_service_client.h"
#include "securec.h"
namespace OHOS {
namespace MIDI {
namespace {
std::vector<std::unique_ptr<MidiClient>> clients;
std::mutex clientsMutex;
} // namespace
class MidiClientCallback : public MidiCallbackStub {
public:
MidiClientCallback(OH_MIDICallbacks callbacks, void *userData,
std::function<void(OH_MIDIDeviceChangeAction change, OH_MIDIDeviceInformation info)> deviceChange);
~MidiClientCallback() = default;
int32_t NotifyDeviceChange(int32_t change, const std::map<int32_t, std::string> &deviceInfo) override;
int32_t NotifyError(int32_t code) override;
OH_MIDICallbacks callbacks_;
void *userData_;
std::function<void(OH_MIDIDeviceChangeAction change, OH_MIDIDeviceInformation info)> deviceChange_;
};
static bool ConvertToDeviceInformation(
const std::map<int32_t, std::string> &deviceInfo, OH_MIDIDeviceInformation &outInfo)
{
// 初始化outInfo
memset_s(&outInfo, sizeof(outInfo), 0, sizeof(outInfo));
auto it = deviceInfo.find(DEVICE_ID);
CHECK_AND_RETURN_RET_LOG(it != deviceInfo.end(), false, "deviceId error");
outInfo.midiDeviceId = std::stoll(it->second);
it = deviceInfo.find(DEVICE_TYPE);
CHECK_AND_RETURN_RET_LOG(it != deviceInfo.end(), false, "deviceType error");
outInfo.deviceType = static_cast<OH_MIDIDeviceType>(std::stoi(it->second));
it = deviceInfo.find(MIDI_PROTOCOL);
CHECK_AND_RETURN_RET_LOG(it != deviceInfo.end(), false, "protocol error");
outInfo.nativeProtocol = static_cast<OH_MIDIProtocol>(std::stoi(it->second));
it = deviceInfo.find(PRODUCT_NAME);
CHECK_AND_RETURN_RET_LOG(it != deviceInfo.end(), false, "productName error");
CHECK_AND_RETURN_RET_LOG(
strncpy_s(outInfo.productName, sizeof(outInfo.productName), it->second.c_str(), it->second.length()) ==
MIDI_STATUS_OK,
false,
"copy productName failed");
it = deviceInfo.find(VENDOR_NAME);
CHECK_AND_RETURN_RET_LOG(it != deviceInfo.end(), false, "vendorName error");
CHECK_AND_RETURN_RET_LOG(
strncpy_s(outInfo.vendorName, sizeof(outInfo.vendorName), it->second.c_str(), it->second.length()) ==
MIDI_STATUS_OK,
false,
"copy vendorName failed");
return true;
}
static bool ConvertToPortInformation(
const std::map<int32_t, std::string> &portInfo, int64_t deviceId, OH_MIDIPortInformation &outInfo)
{
memset_s(&outInfo, sizeof(outInfo), 0, sizeof(outInfo));
outInfo.deviceId = deviceId;
auto it = portInfo.find(PORT_INDEX);
CHECK_AND_RETURN_RET_LOG(it != portInfo.end(), false, "port index error");
outInfo.portIndex = static_cast<uint32_t>(std::stoll(it->second));
it = portInfo.find(DIRECTION);
CHECK_AND_RETURN_RET_LOG(it != portInfo.end(), false, "direction error");
outInfo.direction = static_cast<OH_MIDIPortDirection>(std::stoi(it->second));
it = portInfo.find(PORT_NAME);
CHECK_AND_RETURN_RET_LOG(it != portInfo.end() && !it->second.empty(), false, "port name error");
CHECK_AND_RETURN_RET_LOG(
strncpy_s(outInfo.name, sizeof(outInfo.name), it->second.c_str(), it->second.length()) == MIDI_STATUS_OK,
false,
"copy port name failed");
return true;
}
MidiClientCallback::MidiClientCallback(OH_MIDICallbacks callbacks, void *userData,
std::function<void(OH_MIDIDeviceChangeAction change, OH_MIDIDeviceInformation info)> deviceChange)
: callbacks_(callbacks), userData_(userData), deviceChange_(deviceChange)
{}
int32_t MidiClientCallback::NotifyDeviceChange(int32_t change, const std::map<int32_t, std::string> &deviceInfo)
{
CHECK_AND_RETURN_RET_LOG(
callbacks_.onDeviceChange != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "callbacks_.onDeviceChange is nullptr");
OH_MIDIDeviceInformation info;
bool ret = ConvertToDeviceInformation(deviceInfo, info);
// todo 改变midiClient中的设备信息
CHECK_AND_RETURN_RET_LOG(ret, MIDI_STATUS_UNKNOWN_ERROR, "ConvertToDeviceInformation failed");
deviceChange_(static_cast<OH_MIDIDeviceChangeAction>(change), info);
callbacks_.onDeviceChange(userData_, static_cast<OH_MIDIDeviceChangeAction>(change), info);
return 0;
}
int32_t MidiClientCallback::NotifyError(int32_t code)
{
CHECK_AND_RETURN_RET_LOG(callbacks_.onError != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "callbacks_.onError is nullptr");
callbacks_.onError(userData_, (OH_MIDIStatusCode)code);
return 0;
}
MidiDevicePrivate::MidiDevicePrivate(std::shared_ptr<MidiServiceInterface> midiServiceInterface, int64_t deviceId)
: ipc_(midiServiceInterface), deviceId_(deviceId)
{
MIDI_INFO_LOG("MidiDevicePrivate created");
}
MidiDevicePrivate::~MidiDevicePrivate()
{
MIDI_INFO_LOG("MidiDevicePrivate destroyed");
}
OH_MIDIStatusCode MidiDevicePrivate::CloseDevice()
{
auto ipc = ipc_.lock();
CHECK_AND_RETURN_RET_LOG(ipc != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
return ipc->CloseDevice(deviceId_);
}
OH_MIDIStatusCode MidiDevicePrivate::OpenInputPort(uint32_t portIndex, OH_OnMIDIReceived callback, void *userData)
{
auto ipc = ipc_.lock();
CHECK_AND_RETURN_RET_LOG(ipc != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
{
std::lock_guard<std::mutex> lock(inputPortsMutex_);
auto iter = inputPortsMap_.find(portIndex);
CHECK_AND_RETURN_RET(iter == inputPortsMap_.end(), MIDI_STATUS_OK);
}
auto inputPort = std::make_shared<MidiInputPort>(callback, userData);
std::shared_ptr<MidiSharedRing> &buffer = inputPort->GetRingBuffer();
auto ret = ipc->OpenInputPort(buffer, deviceId_, portIndex);
CHECK_AND_RETURN_RET_LOG(ret == MIDI_STATUS_OK, ret, "open inputport fail");
CHECK_AND_RETURN_RET_LOG(
inputPort->StartReceiverThread() == true, MIDI_STATUS_UNKNOWN_ERROR, "start receiver thread fail");
{
std::lock_guard<std::mutex> lock(inputPortsMutex_);
inputPortsMap_.emplace(portIndex, std::move(inputPort));
}
MIDI_INFO_LOG("port[%{public}u] success", portIndex);
return MIDI_STATUS_OK;
}
OH_MIDIStatusCode MidiDevicePrivate::ClosePort(uint32_t portIndex)
{
auto ipc = ipc_.lock();
CHECK_AND_RETURN_RET_LOG(ipc != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
OH_MIDIStatusCode ret = MIDI_STATUS_OK;
{
std::lock_guard<std::mutex> lock(inputPortsMutex_);
auto it = inputPortsMap_.find(portIndex);
CHECK_AND_RETURN_RET(it != inputPortsMap_.end(), MIDI_STATUS_GENERIC_INVALID_ARGUMENT);
ret = ipc->CloseInputPort(deviceId_, portIndex);
inputPortsMap_.erase(it);
}
CHECK_AND_RETURN_RET_LOG(ret == MIDI_STATUS_OK, ret, "close inputport fail");
return MIDI_STATUS_OK;
}
MidiInputPort::MidiInputPort(OH_OnMIDIReceived callback, void *userData) : callback_(callback), userData_(userData)
{
MIDI_INFO_LOG("InputPort created");
}
bool MidiInputPort::StartReceiverThread()
{
CHECK_AND_RETURN_RET_LOG(running_.load() != true, false, "already start");
CHECK_AND_RETURN_RET_LOG(ringBuffer_ != nullptr && callback_ != nullptr, false, "buffer or callback is nullptr");
running_.store(true);
receiverThread_ = std::thread(&MidiInputPort::ReceiverThreadLoop, this);
return true;
}
bool MidiInputPort::StopReceiverThread()
{
bool expected = true;
CHECK_AND_RETURN_RET(running_.compare_exchange_strong(expected, false), true);
if (ringBuffer_) {
std::atomic<uint32_t> *futexPtr = ringBuffer_->GetFutex();
if (futexPtr != nullptr) {
(void)FutexTool::FutexWake(futexPtr, IS_PRE_EXIT);
}
}
if (receiverThread_.joinable()) {
receiverThread_.join();
}
return true;
}
void MidiInputPort::ReceiverThreadLoop()
{
if (!ringBuffer_) {
running_.store(false);
return;
}
std::atomic<uint32_t> *futexPtr = ringBuffer_->GetFutex();
if (futexPtr == nullptr) {
running_.store(false);
return;
}
constexpr int64_t kWaitForever = -1;
while (running_.load()) {
(void)FutexTool::FutexWait(futexPtr, kWaitForever, [this]() { return ShouldWakeForReadOrExit(); });
if (!running_.load()) {
break;
}
DrainRingAndDispatch();
}
}
bool MidiInputPort::ShouldWakeForReadOrExit() const
{
if (!running_.load()) {
return true;
}
if (!ringBuffer_) {
return true;
}
MidiSharedRing::PeekedEvent peekedEvent{};
MidiStatusCode status = ringBuffer_->PeekNext(peekedEvent);
return (status == MidiStatusCode::OK);
}
void MidiInputPort::DrainRingAndDispatch()
{
if (!ringBuffer_ || callback_ == nullptr) {
return;
}
std::vector<MidiEvent> midiEvents;
std::vector<std::vector<uint32_t>> payloadBuffers;
ringBuffer_->DrainToBatch(midiEvents, payloadBuffers, 0);
if (midiEvents.empty()) {
return;
}
std::vector<OH_MIDIEvent> callbackEvents;
callbackEvents.reserve(midiEvents.size());
for (const auto &event : midiEvents) {
OH_MIDIEvent callbackEvent{};
callbackEvent.timestamp = event.timestamp;
callbackEvent.length = event.length;
callbackEvent.data = event.data;
callbackEvents.push_back(callbackEvent);
}
callback_(userData_, callbackEvents.data(), callbackEvents.size());
}
MidiInputPort::~MidiInputPort()
{
(void)StopReceiverThread();
}
std::shared_ptr<MidiSharedRing> &MidiInputPort::GetRingBuffer()
{
return ringBuffer_;
}
MidiClientPrivate::MidiClientPrivate() : ipc_(std::make_shared<MidiServiceClient>())
{
MIDI_INFO_LOG("MidiClientPrivate created");
}
MidiClientPrivate::~MidiClientPrivate()
{
MIDI_INFO_LOG("MidiClientPrivate destroyed");
}
OH_MIDIStatusCode MidiClientPrivate::Init(OH_MIDICallbacks callbacks, void *userData)
{
CHECK_AND_RETURN_RET_LOG(ipc_ != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
callback_ = sptr<MidiClientCallback>::MakeSptr(callbacks,
userData,
[this](OH_MIDIDeviceChangeAction change, OH_MIDIDeviceInformation info) { this->DeviceChange(change, info); });
auto ret = ipc_->Init(callback_, clientId_);
CHECK_AND_RETURN_RET(ret == MIDI_STATUS_OK, ret);
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::map<int32_t, std::string>> deviceInfos;
ret = ipc_->GetDevices(deviceInfos);
CHECK_AND_RETURN_RET(ret == MIDI_STATUS_OK, ret);
for (auto deviceInfo : deviceInfos) {
OH_MIDIDeviceInformation info;
bool ret = ConvertToDeviceInformation(deviceInfo, info);
CHECK_AND_CONTINUE_LOG(ret, "ConvertToDeviceInformation failed");
deviceInfos_.push_back(info);
}
return MIDI_STATUS_OK;
}
void MidiClientPrivate::DeviceChange(OH_MIDIDeviceChangeAction change, OH_MIDIDeviceInformation info)
{
std::lock_guard<std::mutex> lock(mutex_);
MIDI_INFO_LOG("DeviceChange: %{public}d", change);
if (change == MIDI_DEVICE_CHANGE_ACTION_CONNECTED) {
deviceInfos_.push_back(info);
return;
}
for (auto it = deviceInfos_.begin(); it != deviceInfos_.end();) {
if (it->midiDeviceId == info.midiDeviceId) {
it = deviceInfos_.erase(it);
break;
} else {
it++;
}
}
}
OH_MIDIStatusCode MidiClientPrivate::GetDevices(OH_MIDIDeviceInformation *infos, size_t *numDevices)
{
std::lock_guard<std::mutex> lock(mutex_);
if (*numDevices < deviceInfos_.size()) {
*numDevices = deviceInfos_.size();
return MIDI_STATUS_INSUFFICIENT_RESULT_SPACE;
}
*numDevices = deviceInfos_.size();
CHECK_AND_RETURN_RET(*numDevices != 0, MIDI_STATUS_OK);
CHECK_AND_RETURN_RET(infos != nullptr, MIDI_STATUS_GENERIC_INVALID_ARGUMENT);
for (size_t i = 0; i < *numDevices; i++) {
infos[i] = deviceInfos_[i];
}
return MIDI_STATUS_OK;
}
OH_MIDIStatusCode MidiClientPrivate::OpenDevice(int64_t deviceId, MidiDevice **midiDevice)
{
CHECK_AND_RETURN_RET_LOG(midiDevice != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "midiDevice is nullptr");
CHECK_AND_RETURN_RET_LOG(ipc_ != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
auto ret = ipc_->OpenDevice(deviceId);
CHECK_AND_RETURN_RET(ret == MIDI_STATUS_OK, ret);
auto newDevice = new MidiDevicePrivate(ipc_, deviceId);
*midiDevice = newDevice;
MIDI_INFO_LOG("Device opened: %{public}" PRId64, deviceId);
return MIDI_STATUS_OK;
}
OH_MIDIStatusCode MidiClientPrivate::GetDevicePorts(int64_t deviceId, OH_MIDIPortInformation *infos, size_t *numPorts)
{
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::map<int32_t, std::string>> portInfos;
CHECK_AND_RETURN_RET_LOG(ipc_ != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
auto ret = ipc_->GetDevicePorts(deviceId, portInfos);
CHECK_AND_RETURN_RET(ret == MIDI_STATUS_OK, ret);
if (*numPorts < portInfos.size()) {
*numPorts = portInfos.size();
return MIDI_STATUS_INSUFFICIENT_RESULT_SPACE;
}
*numPorts = portInfos.size();
CHECK_AND_RETURN_RET(*numPorts != 0, MIDI_STATUS_OK);
CHECK_AND_RETURN_RET(infos != nullptr, MIDI_STATUS_GENERIC_INVALID_ARGUMENT);
for (size_t i = 0; i < portInfos.size(); i++) {
OH_MIDIPortInformation info;
bool ret = ConvertToPortInformation(portInfos[i], deviceId, info);
CHECK_AND_CONTINUE_LOG(ret, "ConvertToPortInformation failed");
infos[i] = info;
}
return MIDI_STATUS_OK;
}
OH_MIDIStatusCode MidiClientPrivate::DestroyMidiClient()
{
CHECK_AND_RETURN_RET_LOG(ipc_ != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "ipc_ is nullptr");
return ipc_->DestroyMidiClient();
}
OH_MIDIStatusCode MidiClient::CreateMidiClient(MidiClient **client, OH_MIDICallbacks callbacks, void *userData)
{
CHECK_AND_RETURN_RET_LOG(client != nullptr, MIDI_STATUS_UNKNOWN_ERROR, "client is nullptr");
*client = new MidiClientPrivate();
OH_MIDIStatusCode ret = (*client)->Init(callbacks, userData);
CHECK_AND_RETURN_RET(ret != MIDI_STATUS_OK, ret);
delete *client;
return ret;
}
} // namespace MIDI
} // namespace OHOS