简体   繁体   English

如何在 iOS 中查找蓝牙音频设备

[英]how to find Bluetooth audio devices in iOS

Okay, I'm working on a fun project that has a hurdle where I need to enable Bluetooth audio support for my iOS app.好的,我正在开发一个有趣的项目,该项目有一个障碍,我需要为我的 iOS 应用程序启用蓝牙音频支持。

The hurdle I'm at is that I simply can't even begin to get a list of connected Bluetooth audio devices.我遇到的障碍是我什至无法开始获取已连接蓝牙音频设备的列表。 Even though my iPhone 5S recognizes my earpiece (a ~3 - 4 year old LG HBM-230 , to be precise) and plays audio through it for phone calls, BOTH External Accessory and CoreBluetooth are giving me nothing useful when I query both.虽然我的iPhone 5S认识我的耳机(A〜3 - 4岁的LG HBM-230 ,要准确),并通过它来进行电话呼叫播放音频,既有外部附件和CoreBluetooth是给我什么有用的,当我查询两者。

I'm basing my own code off questions & answers I found for both the CoreBluetooth and External Accessory frameworks.我将自己的代码基于为CoreBluetooth外部附件框架找到的问题和答案。

When my code simply tries to " scanForPeripheralsWithServices:nil " for any Bluetooth devices which Settings->Bluetooth say are visible and connected, the below code simply is NOT coming up with a single hit beyond the " CBCentralManagerStatePoweredOn " message in the console.当我的代码只是尝试“ scanForPeripheralsWithServices:nil ”设置->蓝牙说可见并连接的任何蓝牙设备时,下面的代码只是在控制台中的“ CBCentralManagerStatePoweredOn ”消息之外没有出现任何点击。

And this line in my code (with a valid EAAccessoryManager instance)我的代码中的这一行(带有有效的 EAAccessoryManager 实例)

NSArray * connectedDevices = [self.eAAccessoryManager connectedAccessories];

also comes back with a nil array.也返回一个 nil 数组。

What could I be doing wrong?我可能做错了什么?

BTW, I've made this code available as a GitHub project .顺便说一句,我已将此代码作为 GitHub 项目提供

@implementation BluetoothManager

+ (BluetoothManager *)sharedInstance
{
    static dispatch_once_t pred = 0;
    __strong static id _bluetoothMGR = nil;

    dispatch_once(&pred, ^{
        _bluetoothMGR = [[BluetoothManager alloc] init];
    });

    return _bluetoothMGR;
}

- (id)init
{
    self = [super init];
    if(self)
    {
        dispatch_queue_t centralQueue = dispatch_queue_create("com.yo.mycentral", DISPATCH_QUEUE_SERIAL);

        // whether we try this on a queue of "nil" (the main queue) or this separate thread, still not getting results
        self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:nil];
    }
    return self;
}

// this would hit.... if I instantiated this in a storyboard of XIB file
- (void)awakeFromNib
{
    if(!self.cbManager)
        self.cbManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:nil];
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI {

    NSLog(@"hey I found %@",[advertisementData description]);
}

- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
    NSLog( @"I retrieved CONNECTED peripherals");
}

-(void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals{
    NSLog(@"This is it!");
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    NSString *messtoshow;

    switch (central.state) {
        case CBCentralManagerStateUnknown:
        {
            messtoshow=@"State unknown, update imminent.";
            break;
        }
        case CBCentralManagerStateResetting:
        {
            messtoshow=@"The connection with the system service was momentarily lost, update imminent.";
            break;
        }
        case CBCentralManagerStateUnsupported:
        {
            messtoshow=@"The platform doesn't support Bluetooth Low Energy";
            break;
        }
        case CBCentralManagerStateUnauthorized:
        {
            messtoshow=@"The app is not authorized to use Bluetooth Low Energy";
            break;
        }
        case CBCentralManagerStatePoweredOff:
        {
            messtoshow=@"Bluetooth is currently powered off.";
            break;
        }
        case CBCentralManagerStatePoweredOn:
        {
            messtoshow=@"Bluetooth is currently powered on and available to use.";
            NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil];

            [_cbManager scanForPeripheralsWithServices:nil options:options];

            break;
        }   

    }
    NSLog(@"%@", messtoshow);
}

@end

First you will need to configure your applications audio session to allow bluetooth connections that support audio.首先,您需要配置应用程序音频会话以允许支持音频的蓝牙连接。 You can do this in, for example, your application delegates - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method.例如,您可以在您的应用程序委托中执行此操作 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法。 Make sure you link the AVFoundation Framework and import in headers that will use it.确保链接 AVFoundation 框架并导入将使用它的标头。

#import <AVFoundation/AVFoundation.h>// place in .h

[self prepareAudioSession];// called from application didFinishLaunchingWithOptions

- (BOOL)prepareAudioSession {

     // deactivate session
     BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
     if (!success) {
         NSLog(@"deactivationError");
     }

     // set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
     success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
     if (!success) {
         NSLog(@"setCategoryError");
     }

     // activate audio session
     success = [[AVAudioSession sharedInstance] setActive:YES error: nil];
     if (!success) {
         NSLog(@"activationError");
     }

return success;

}

Every application has an audio session singleton that you can configure.每个应用程序都有一个可以配置的音频会话单例。 The sessions category and mode (in this example I did not set the mode so it reverts to the default mode) declare your applications intentions as to how you would like audio routing to be handled.会话类别和模式(在本例中我没有设置模式,因此它会恢复到默认模式)声明您的应用程序意图如何处理音频路由。 It follows an important rule of last in wins .它遵循胜利中最后一个重要规则。 This means that if the user plugs in a headset or in this case a bluetooth device that is a hands free peripheral (HFP) the system will automatically route the audio to the headset or bluetooth device.这意味着如果用户插入耳机或在这种情况下是免提外围设备 (HFP) 的蓝牙设备,系统将自动将音频路由到耳机或蓝牙设备。 The users physical actions are used to determine audio routing.用户的身体动作用于确定音频路由。 However if you wish to give the user a list of available routes Apple recommend using MPVolumeView class.但是,如果您希望为用户提供可用路线列表,Apple 建议使用 MPVolumeView 类。

An example for adding MPVolumeView could be put in a UIViewController subclasses viewDidLoad method.添加MPVolumeView的示例可以放在UIViewController子类 viewDidLoad 方法中。

#import <MediaPlayer/MediaPlayer.h> // place in .h
// prefered way using MPVolumeView for user selecting audio routes
self.view.backgroundColor = [UIColor clearColor];
CGRect frameForMPVV = CGRectMake(50.0, 50.0, 100.0, 100.0);
MPVolumeView *routeView = [[MPVolumeView alloc] initWithFrame:frameForMPVV];
[routeView setShowsVolumeSlider:NO];
[routeView setShowsRouteButton:YES];
[self.view addSubview: routeView];

As of iOS 7 you can get all inputs like this从 iOS 7 开始,您可以获得这样的所有输入

// portDesc.portType could be for example - BluetoothHFP, MicrophoneBuiltIn, MicrophoneWired
NSArray *availInputs = [[AVAudioSession sharedInstance] availableInputs];
int count = [availInputs count];
for (int k = 0; k < count; k++) {
    AVAudioSessionPortDescription *portDesc = [availInputs objectAtIndex:k];
    NSLog(@"input%i port type %@", k+1, portDesc.portType);
    NSLog(@"input%i port name %@", k+1, portDesc.portName);
}

The portType you would be interested in is "BluetoothHFP".您感兴趣的端口类型是“BluetoothHFP”。 The portName property typically is the manufacturer/model which is what you would show to the user. portName 属性通常是制造商/型号,这是您将向用户显示的内容。 (I've checked this with a non-LE bluetooth Motorola dinosaur and it works) (我已经用非 LE 蓝牙摩托罗拉恐龙检查过这个,它可以工作)

Because of the last in wins rule you will need to observe these two notifications (iOS 7 included).由于获胜规则的最后一个,您需要观察这两个通知(包括 iOS 7)。 One to handle interruptions (such as phone calls or an alarm) and the second to be notified of route changes.一个用于处理中断(例如电话或警报),第二个用于通知路线更改。 Route change notifications is the one related to this question.路由更改通知是与此问题相关的通知。

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(myInterruptionSelector:)
                                             name:AVAudioSessionInterruptionNotification
                                           object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(myRouteChangeSelector:)
                                             name:AVAudioSessionRouteChangeNotification
                                           object:nil];

For iOS 6.x you could read the currentRoute property of AVAudioSession inside the myRouteChange: selector to get the new route, as this will get called when a headset or bluetooth device is connected.对于 iOS 6.x,您可以在 myRouteChange: 选择器中读取 AVAudioSession 的 currentRoute 属性以获取新路由,因为这将在连接耳机或蓝牙设备时调用。

- (void)myRouteChangeSelector:(NSNotification*)notification {
 AVAudioSessionRouteDescription *currentRoute = [[AVAudioSession sharedInstance] currentRoute];
      NSArray *inputsForRoute = currentRoute.inputs;
      NSArray *outputsForRoute = currentRoute.outputs;
      AVAudioSessionPortDescription *outPortDesc = [outputsForRoute objectAtIndex:0];
      NSLog(@"current outport type %@", outPortDesc.portType);
      AVAudioSessionPortDescription *inPortDesc = [inputsForRoute objectAtIndex:0];
      NSLog(@"current inPort type %@", inPortDesc.portType);
}

Any iOS version < 6.0 you'll need the ' now deprecated ' AudioSessionServices class.任何低于 6.0 的 iOS 版本都需要“现已弃用”的 AudioSessionServices 类。 This class is a C api that instead of notifications it allows you to add property listeners.此类是一个 C api,它允许您添加属性侦听器而不是通知。

I'll finish on this note - YOU DONT ALWAYS GET WHAT YOU WANT from the system.我会在这个笔记上结束 - 你并不总是从系统中得到你想要的。 There are interruption handling notifications to observe and lots of error checking needed.有中断处理通知需要观察并需要进行大量错误检查。 I think this is a really good question and I hope this sheds some light on what it is you are trying to achieve.我认为这是一个非常好的问题,我希望这对您正在努力实现的目标有所了解。

Swift version迅捷版

do {
        try AVAudioSession.sharedInstance().setCategory(.playAndRecord, options: .allowBluetooth)
        try AVAudioSession.sharedInstance().setActive(true)
    } catch {}
let availableInputs = AVAudioSession.sharedInstance().availableInputs

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM