手动在 iOS 上宣传蓝牙 MIDI,无需 CABTMIDILocalPeripheralViewController

Advertise Bluetooth MIDI on iOS manually, without CABTMIDILocalPeripheralViewController

我刚刚发现 CABTMIDILocalPeripheralViewController iOS 处理用于启用蓝牙 MIDI 可发现性的用户设置。这是 很好,但为了将蓝牙集成到我应用程序的其余部分 网络 MIDI 连接最好能够处理 直接从我的应用程序代码启用,而不是依赖这个不透明的 VC。 有谁知道这是否可能?

我想你可能正在寻找这个:CABTMIDICentralViewController 此页面上的更多信息:https://developer.apple.com/library/ios/qa/qa1831/_index.html 基本上这可以帮助您通过您的应用程序扫描并连接到设备。我不确定您是只想被发现还是想成为扫描的人。 希望对您有所帮助

没有 public API 来管理此功能。在使用 Instruments 进行调查后,该开关似乎导致了 CBPeripheralManager 的实例化。我假设它将设备设置为蓝牙外围设备,并手动将数据传入和传出也已创建的 MIDIEndpointRef

换句话说,没有单行解决方案。如果我沿着这条路走得更远,我将 post 代码,除非其他人想要尝试...

更新

魔术代码...

- (instancetype)init
{
    self = [super init];
    if (self) {
        _peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
    }
    return self;
}

//---------------------------------------------------------------------

- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
    if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
        return;
    }

    info(@"_peripheralManager powered on.");

//    CBMutableCharacteristic *tx = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:RBL_TX_UUID] properties:CBCharacteristicPropertyWriteWithoutResponse value:nil permissions:CBAttributePermissionsWriteable];
//    
    rx = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"7772E5DB-3868-4112-A1A9-F2669D106BF3"] properties:CBCharacteristicPropertyRead|CBCharacteristicPropertyWriteWithoutResponse|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable|CBAttributePermissionsWriteable];

    CBMutableService *s = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"03B80E5A-EDE8-4B33-A751-6CE34EC4C700"] primary:YES];
    s.characteristics = @[rx];

    [_peripheralManager addService:s];

    NSDictionary *advertisingData = @{CBAdvertisementDataLocalNameKey : BLE_NAME, CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"03B80E5A-EDE8-4B33-A751-6CE34EC4C700"]]};
    [_peripheralManager startAdvertising:advertisingData];
}

正是这些 ID 定义了 MIDI 外围设备。更多信息:

Apple 曾经在

有一个文档

...不再是。我很想找到一份旧副本,因为事实证明接收方代码(模拟 CABTMIDICentralViewController)更难破解...

所以我拼凑了一个非常 hacky 的解决方案,用于发现用户在 CABTMIDICentralViewController 中单击了哪个 MIDI 设备。我不确定这是否是个好主意——如果 Apple 更改了控制器的内部结构,它就无法再工作了。另外我不确定它是否 "legal" 关于 App Store 指南。有人知道这方面的更多信息吗?

DPBleMidiDeviceManager.h:

#import <CoreAudioKit/CoreAudioKit.h>

@protocol MidiDeviceConnectedDelegate <NSObject>

-(void) onMidiDeviceConnected: (NSString*) deviceName;

@end


@interface DPBleMidiDeviceManager : CABTMIDICentralViewController

@property (weak, nonatomic) id<MidiDeviceConnectedDelegate> midiDeviceDelegate;

@end

DPBleMidiDeviceManager.m:

#import "DPBleMidiDeviceManager.h"

@implementation DPBleMidiDeviceManager


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"midi device selected %@", indexPath);

    [super tableView:tableView didSelectRowAtIndexPath:indexPath];

    // TODO: this is very bad. apple may change their internal API and this will break.
    UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
    if ([cell respondsToSelector:@selector(deviceNameLabel)]) {
        UILabel* deviceLabel = [cell performSelector:@selector(deviceNameLabel)];

        NSLog(@"midi device named %@", deviceLabel.text);

        // must wait a couple seconds for it to actually connect.
        [self performSelector:@selector(sendMidiDeviceConnected:) withObject:deviceLabel.text afterDelay: 3];
    }
}


- (void) sendMidiDeviceConnected: (NSString*) deviceName
{
    [self.midiDeviceDelegate onMidiDeviceConnected:deviceName];
}
@end

然后,在您的父视图控制器中,您可以从委托中获取结果并查找与该名称匹配的新 MIDI 设备:

...
    DPBleMidiDeviceManager *controller = [DPBleMidiDeviceManager new];
    controller.midiDeviceDelegate = self;
    // now present the VC as usual
...


-(void) onMidiDeviceConnected: (NSString*) deviceName
{
    [self connectMidiDevice: deviceName];
}


/**
 Connects to a MIDI source with the given name,
 and interprets all notes from that source as notes;

 */
- (void) connectMidiDevice: (NSString*) deviceName
{
    NSLog(@"Connecting to MIDI device: %@", deviceName);

    PGMidi* midi = [[PGMidi alloc] init];

    if (midi != NULL) {
        NSArray* sources = midi.sources;
        for (PGMidiSource* src in sources) {
            NSLog(@"Found midi source: %@", src.name);

            if ([src.name containsString: deviceName]) {

                NSLog(@"Connecting to midi source: %@", src.name);
                [src addDelegate:self];
            }
        }
    }

}

我能想到的唯一其他选择是在显示控制器之前扫描 MIDI 设备,保存设备列表,然后打开控制器。当它关闭时,再次扫描 MIDI 设备,并将新列表与旧列表进行比较。出现的任何新 MIDI 设备都是用户选择的。不知道为什么 Apple 没有让我们更容易...

这是swift5版本。这很好用。

import SpriteKit
import CoreBluetooth

class GameScene: SKScene {
    var manager: CBPeripheralManager!
    var service: CBMutableService!
    // MIDI Service UUID
    let serviceID = CBUUID(string: "03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
    // MIDI I/O Characteristic UUID
    let characteristicID = CBUUID(string: "7772E5DB-3868-4112-A1A9-F2669D106BF3")

    override func didMove(to view: SKView) {
        super.didMove(to: view)
        self.manager = CBPeripheralManager(delegate : self, queue : nil, options: nil)
    }
}

extension GameScene: CBPeripheralManagerDelegate {
    // Notify the peripheral state
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        guard peripheral.state == .poweredOn else {
            print("error: \(peripheral.state)")
            return
        }
        
        print("bluetooth pwoer on")
        // Add service and characteristic
        self.service = CBMutableService(type: self.serviceID,
                                       primary: true)

        let properties: CBCharacteristicProperties = [.notify, .read, .writeWithoutResponse]
        let permissions: CBAttributePermissions = [.readable, .writeable]
        let characteristic = CBMutableCharacteristic(type: self.characteristicID,
                                                     properties: properties,
                                                     value: nil, permissions: permissions)
        
        self.service.characteristics = [characteristic]
        self.manager.add(self.service)
    }
    // Notify add the service
    func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
        guard (error == nil) else {
            print("Add service failed")
            return
        }
        print("Add service succeeded")
        
        // Start advertising
        let advertisementData = [CBAdvertisementDataLocalNameKey: "Penguin Drums",
                                 CBAdvertisementDataServiceUUIDsKey: [self.serviceID]] as [String : Any]
        manager.startAdvertising(advertisementData)
        
        print("Service starts advertising!")
    }
}