C++程序  |  223行  |  6.82 KB

/* 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 <errno.h>
#include <stdint.h>
#include <syslog.h>

#include "a2dp-codecs.h"
#include "cras_a2dp_endpoint.h"
#include "cras_a2dp_iodev.h"
#include "cras_iodev.h"
#include "cras_bt_constants.h"
#include "cras_bt_endpoint.h"
#include "cras_system_state.h"
#include "cras_util.h"

#define A2DP_SOURCE_ENDPOINT_PATH "/org/chromium/Cras/Bluetooth/A2DPSource"
#define A2DP_SINK_ENDPOINT_PATH   "/org/chromium/Cras/Bluetooth/A2DPSink"


/* Pointers for the only connected a2dp device. */
static struct a2dp {
	struct cras_iodev *iodev;
	struct cras_bt_device *device;
} connected_a2dp;

static int cras_a2dp_get_capabilities(struct cras_bt_endpoint *endpoint,
				      void *capabilities, int *len)
{
	a2dp_sbc_t *sbc_caps = capabilities;

	if (*len < sizeof(*sbc_caps))
		return -ENOSPC;

	*len = sizeof(*sbc_caps);

	/* Return all capabilities. */
	sbc_caps->channel_mode = SBC_CHANNEL_MODE_MONO |
			SBC_CHANNEL_MODE_DUAL_CHANNEL |
			SBC_CHANNEL_MODE_STEREO |
			SBC_CHANNEL_MODE_JOINT_STEREO;
	sbc_caps->frequency = SBC_SAMPLING_FREQ_16000 |
			SBC_SAMPLING_FREQ_32000 |
			SBC_SAMPLING_FREQ_44100 |
			SBC_SAMPLING_FREQ_48000;
	sbc_caps->allocation_method = SBC_ALLOCATION_SNR |
			SBC_ALLOCATION_LOUDNESS;
	sbc_caps->subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8;
	sbc_caps->block_length = SBC_BLOCK_LENGTH_4 |
			SBC_BLOCK_LENGTH_8 |
			SBC_BLOCK_LENGTH_12 |
			SBC_BLOCK_LENGTH_16;
	sbc_caps->min_bitpool = MIN_BITPOOL;
	sbc_caps->max_bitpool = MAX_BITPOOL;

	return 0;
}

static int cras_a2dp_select_configuration(struct cras_bt_endpoint *endpoint,
					  void *capabilities, int len,
					  void *configuration)
{
	a2dp_sbc_t *sbc_caps = capabilities;
	a2dp_sbc_t *sbc_config = configuration;

	/* Pick the highest configuration. */
	if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO) {
		sbc_config->channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
	} else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_STEREO) {
		sbc_config->channel_mode = SBC_CHANNEL_MODE_STEREO;
	} else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL) {
		sbc_config->channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
	} else if (sbc_caps->channel_mode & SBC_CHANNEL_MODE_MONO) {
		sbc_config->channel_mode = SBC_CHANNEL_MODE_MONO;
	} else {
		syslog(LOG_WARNING, "No supported channel modes.");
		return -ENOSYS;
	}

	if (sbc_caps->frequency & SBC_SAMPLING_FREQ_48000) {
		sbc_config->frequency = SBC_SAMPLING_FREQ_48000;
	} else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_44100) {
		sbc_config->frequency = SBC_SAMPLING_FREQ_44100;
	} else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_32000) {
		sbc_config->frequency = SBC_SAMPLING_FREQ_32000;
	} else if (sbc_caps->frequency & SBC_SAMPLING_FREQ_16000) {
		sbc_config->frequency = SBC_SAMPLING_FREQ_16000;
	} else {
		syslog(LOG_WARNING, "No supported sampling frequencies.");
		return -ENOSYS;
	}

	if (sbc_caps->allocation_method & SBC_ALLOCATION_LOUDNESS) {
		sbc_config->allocation_method = SBC_ALLOCATION_LOUDNESS;
	} else if (sbc_caps->allocation_method & SBC_ALLOCATION_SNR) {
		sbc_config->allocation_method = SBC_ALLOCATION_SNR;
	} else {
		syslog(LOG_WARNING, "No supported allocation method.");
		return -ENOSYS;
	}

	if (sbc_caps->subbands & SBC_SUBBANDS_8) {
		sbc_config->subbands = SBC_SUBBANDS_8;
	} else if (sbc_caps->subbands & SBC_SUBBANDS_4) {
		sbc_config->subbands = SBC_SUBBANDS_4;
	} else {
		syslog(LOG_WARNING, "No supported subbands.");
		return -ENOSYS;
	}

	if (sbc_caps->block_length & SBC_BLOCK_LENGTH_16) {
		sbc_config->block_length = SBC_BLOCK_LENGTH_16;
	} else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_12) {
		sbc_config->block_length = SBC_BLOCK_LENGTH_12;
	} else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_8) {
		sbc_config->block_length = SBC_BLOCK_LENGTH_8;
	} else if (sbc_caps->block_length & SBC_BLOCK_LENGTH_4) {
		sbc_config->block_length = SBC_BLOCK_LENGTH_4;
	} else {
		syslog(LOG_WARNING, "No supported block length.");
		return -ENOSYS;
	}

	sbc_config->min_bitpool = (sbc_caps->min_bitpool > MIN_BITPOOL
				   ? sbc_caps->min_bitpool : MIN_BITPOOL);
	sbc_config->max_bitpool = (sbc_caps->max_bitpool < MAX_BITPOOL
				   ? sbc_caps->max_bitpool : MAX_BITPOOL);

	return 0;
}

static void cras_a2dp_set_configuration(struct cras_bt_endpoint *endpoint,
			    struct cras_bt_transport *transport)
{
	struct cras_bt_device *device;

	device = cras_bt_transport_device(transport);
	cras_bt_device_a2dp_configured(device);
}

static void cras_a2dp_suspend(struct cras_bt_endpoint *endpoint,
			      struct cras_bt_transport *transport)
{
	struct cras_bt_device *device = cras_bt_transport_device(transport);
	cras_a2dp_suspend_connected_device(device);
}

static void a2dp_transport_state_changed(struct cras_bt_endpoint *endpoint,
					 struct cras_bt_transport *transport)
{
	if (connected_a2dp.iodev && transport) {
		/* When pending message is received in bluez, try to aquire
		 * the transport. */
		if (cras_bt_transport_fd(transport) != -1 &&
		    cras_bt_transport_state(transport) ==
				CRAS_BT_TRANSPORT_STATE_PENDING)
			cras_bt_transport_try_acquire(transport);
	}
}

static struct cras_bt_endpoint cras_a2dp_endpoint = {
	/* BlueZ connects the device A2DP Sink to our A2DP Source endpoint,
	 * and the device A2DP Source to our A2DP Sink. It's best if you don't
	 * think about it too hard.
	 */
	.object_path = A2DP_SOURCE_ENDPOINT_PATH,
	.uuid = A2DP_SOURCE_UUID,
	.codec = A2DP_CODEC_SBC,

	.get_capabilities = cras_a2dp_get_capabilities,
	.select_configuration = cras_a2dp_select_configuration,
	.set_configuration = cras_a2dp_set_configuration,
	.suspend = cras_a2dp_suspend,
	.transport_state_changed = a2dp_transport_state_changed
};

int cras_a2dp_endpoint_create(DBusConnection *conn)
{
	return cras_bt_endpoint_add(conn, &cras_a2dp_endpoint);
}

void cras_a2dp_start(struct cras_bt_device *device)
{
	struct cras_bt_transport *transport = cras_a2dp_endpoint.transport;

	if (!transport || device != cras_bt_transport_device(transport)) {
		syslog(LOG_ERR, "Device and active transport not match.");
		return;
	}

	if (connected_a2dp.iodev) {
		syslog(LOG_WARNING,
		       "Replacing existing endpoint configuration");
		a2dp_iodev_destroy(connected_a2dp.iodev);
	}

	connected_a2dp.iodev = a2dp_iodev_create(transport);
	connected_a2dp.device = cras_bt_transport_device(transport);

	if (!connected_a2dp.iodev)
		syslog(LOG_WARNING, "Failed to create a2dp iodev");
}

struct cras_bt_device *cras_a2dp_connected_device()
{
	return connected_a2dp.device;
}

void cras_a2dp_suspend_connected_device(struct cras_bt_device *device)
{
	if (connected_a2dp.device != device)
		return;

	if (connected_a2dp.iodev) {
		syslog(LOG_INFO, "Destroying iodev for A2DP device");
		a2dp_iodev_destroy(connected_a2dp.iodev);
		connected_a2dp.iodev = NULL;
		connected_a2dp.device = NULL;
	}
}