Example #1
0
static int aac_check_config(void *caps, uint8_t caps_len, void *conf,
							uint8_t conf_len)
{
	a2dp_aac_t *cap, *config;

	if (conf_len != caps_len || conf_len != sizeof(a2dp_aac_t)) {
		error("AAC: Invalid configuration size (%u)", conf_len);
		return -EINVAL;
	}

	cap = caps;
	config = conf;

	if (!(cap->object_type & config->object_type)) {
		error("AAC: Unsupported object type (%u) by endpoint",
							config->object_type);
		return -EINVAL;
	}

	if (!(AAC_GET_FREQUENCY(*cap) & AAC_GET_FREQUENCY(*config))) {
		error("AAC: Unsupported frequency (%u) by endpoint",
						AAC_GET_FREQUENCY(*config));
		return -EINVAL;
	}

	if (!(cap->channels & config->channels)) {
		error("AAC: Unsupported channels (%u) by endpoint",
							config->channels);
		return -EINVAL;
	}

	/* VBR support in SNK is mandatory but let's make sure we don't try to
	 * have VBR on remote which for some reason does not support it
	 */
	if (!cap->vbr && config->vbr) {
		error("AAC: Unsupported VBR (%u) by endpoint",
							config->vbr);
		return -EINVAL;
	}

	if (AAC_GET_BITRATE(*cap) < AAC_GET_BITRATE(*config))
		return -ERANGE;

	return 0;
}
Example #2
0
static struct a2dp_preset *aac_select_range(void *caps, uint8_t caps_len,
						void *conf, uint8_t conf_len)
{
	struct a2dp_preset *p;
	a2dp_aac_t *cap, *config;
	uint32_t bitrate;

	cap = caps;
	config = conf;

	bitrate = MIN(AAC_GET_BITRATE(*cap), AAC_GET_BITRATE(*config));
	AAC_SET_BITRATE(*config, bitrate);

	p = g_new0(struct a2dp_preset, 1);
	p->len = conf_len;
	p->data = g_memdup(conf, p->len);

	return p;
}
Example #3
0
static void print_mpeg24(a2dp_aac_t *aac)
{
	unsigned freq = AAC_GET_FREQUENCY(*aac);
	unsigned bitrate = AAC_GET_BITRATE(*aac);

	printf("\tMedia Codec: MPEG24\n\t\tObject Types: ");

	if (aac->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC)
		printf("MPEG-2 AAC LC ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC)
		printf("MPEG-4 AAC LC ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP)
		printf("MPEG-4 AAC LTP ");
	if (aac->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA)
		printf("MPEG-4 AAC scalable ");

	printf("\n\t\tFrequencies: ");
	if (freq & AAC_SAMPLING_FREQ_8000)
		printf("8kHz ");
	if (freq & AAC_SAMPLING_FREQ_11025)
		printf("11.025kHz ");
	if (freq & AAC_SAMPLING_FREQ_12000)
		printf("12kHz ");
	if (freq & AAC_SAMPLING_FREQ_16000)
		printf("16kHz ");
	if (freq & AAC_SAMPLING_FREQ_22050)
		printf("22.05kHz ");
	if (freq & AAC_SAMPLING_FREQ_24000)
		printf("24kHz ");
	if (freq & AAC_SAMPLING_FREQ_32000)
		printf("32kHz ");
	if (freq & AAC_SAMPLING_FREQ_44100)
		printf("44.1kHz ");
	if (freq & AAC_SAMPLING_FREQ_48000)
		printf("48kHz ");
	if (freq & AAC_SAMPLING_FREQ_64000)
		printf("64kHz ");
	if (freq & AAC_SAMPLING_FREQ_88200)
		printf("88.2kHz ");
	if (freq & AAC_SAMPLING_FREQ_96000)
		printf("96kHz ");

	printf("\n\t\tChannels: ");
	if (aac->channels & AAC_CHANNELS_1)
		printf("1 ");
	if (aac->channels & AAC_CHANNELS_2)
		printf("2 ");

	printf("\n\t\tBitrate: %u", bitrate);

	printf("\n\t\tVBR: %s", aac->vbr ? "Yes\n" : "No\n");
}
Example #4
0
void *io_thread_a2dp_source_aac(void *arg) {
	struct ba_transport *t = (struct ba_transport *)arg;
	const a2dp_aac_t *cconfig = (a2dp_aac_t *)t->a2dp.cconfig;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	pthread_cleanup_push(CANCEL_ROUTINE(io_thread_release), t);

	HANDLE_AACENCODER handle;
	AACENC_InfoStruct aacinf;
	AACENC_ERROR err;

	/* create AAC encoder without the Meta Data module */
	const unsigned int channels = transport_get_channels(t);
	if ((err = aacEncOpen(&handle, 0x07, channels)) != AACENC_OK) {
		error("Couldn't open AAC encoder: %s", aacenc_strerror(err));
		goto fail_open;
	}

	pthread_cleanup_push(CANCEL_ROUTINE(aacEncClose), &handle);

	unsigned int aot = AOT_NONE;
	unsigned int bitrate = AAC_GET_BITRATE(*cconfig);
	unsigned int samplerate = transport_get_sampling(t);
	unsigned int channelmode = channels == 1 ? MODE_1 : MODE_2;

	switch (cconfig->object_type) {
	case AAC_OBJECT_TYPE_MPEG2_AAC_LC:
#if AACENCODER_LIB_VERSION <= 0x03040C00 /* 3.4.12 */
		aot = AOT_MP2_AAC_LC;
		break;
#endif
	case AAC_OBJECT_TYPE_MPEG4_AAC_LC:
		aot = AOT_AAC_LC;
		break;
	case AAC_OBJECT_TYPE_MPEG4_AAC_LTP:
		aot = AOT_AAC_LTP;
		break;
	case AAC_OBJECT_TYPE_MPEG4_AAC_SCA:
		aot = AOT_AAC_SCAL;
		break;
	}

	if ((err = aacEncoder_SetParam(handle, AACENC_AOT, aot)) != AACENC_OK) {
		error("Couldn't set audio object type: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_BITRATE, bitrate)) != AACENC_OK) {
		error("Couldn't set bitrate: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_SAMPLERATE, samplerate)) != AACENC_OK) {
		error("Couldn't set sampling rate: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_CHANNELMODE, channelmode)) != AACENC_OK) {
		error("Couldn't set channel mode: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if (cconfig->vbr) {
		if ((err = aacEncoder_SetParam(handle, AACENC_BITRATEMODE, config.aac_vbr_mode)) != AACENC_OK) {
			error("Couldn't set VBR bitrate mode %u: %s", config.aac_vbr_mode, aacenc_strerror(err));
			goto fail_init;
		}
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_AFTERBURNER, config.aac_afterburner)) != AACENC_OK) {
		error("Couldn't enable afterburner: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_TRANSMUX, TT_MP4_LATM_MCP1)) != AACENC_OK) {
		error("Couldn't enable LATM transport type: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncoder_SetParam(handle, AACENC_HEADER_PERIOD, 1)) != AACENC_OK) {
		error("Couldn't set LATM header period: %s", aacenc_strerror(err));
		goto fail_init;
	}

	if ((err = aacEncEncode(handle, NULL, NULL, NULL, NULL)) != AACENC_OK) {
		error("Couldn't initialize AAC encoder: %s", aacenc_strerror(err));
		goto fail_init;
	}
	if ((err = aacEncInfo(handle, &aacinf)) != AACENC_OK) {
		error("Couldn't get encoder info: %s", aacenc_strerror(err));
		goto fail_init;
	}

	int in_buffer_identifier = IN_AUDIO_DATA;
	int out_buffer_identifier = OUT_BITSTREAM_DATA;
	int in_buffer_element_size = sizeof(int16_t);
	int out_buffer_element_size = 1;
	int16_t *in_buffer, *in_buffer_head;
	uint8_t *out_buffer, *out_payload;
	int in_buffer_size;
	int out_payload_size;

	AACENC_BufDesc in_buf = {
		.numBufs = 1,
		.bufs = (void **)&in_buffer_head,
		.bufferIdentifiers = &in_buffer_identifier,
		.bufSizes = &in_buffer_size,
		.bufElSizes = &in_buffer_element_size,
	};
	AACENC_BufDesc out_buf = {
		.numBufs = 1,
		.bufs = (void **)&out_payload,
		.bufferIdentifiers = &out_buffer_identifier,
		.bufSizes = &out_payload_size,
		.bufElSizes = &out_buffer_element_size,
	};
	AACENC_InArgs in_args = { 0 };
	AACENC_OutArgs out_args = { 0 };

	in_buffer_size = in_buffer_element_size * aacinf.inputChannels * aacinf.frameLength;
	out_payload_size = aacinf.maxOutBufBytes;
	in_buffer = malloc(in_buffer_size);
	out_buffer = malloc(sizeof(rtp_header_t) + out_payload_size);

	pthread_cleanup_push(CANCEL_ROUTINE(free), in_buffer);
	pthread_cleanup_push(CANCEL_ROUTINE(free), out_buffer);

	if (in_buffer == NULL || out_buffer == NULL) {
		error("Couldn't create data buffers: %s", strerror(ENOMEM));
		goto fail;
	}

	uint16_t seq_number = random();
	uint32_t timestamp = random();

	/* initialize RTP header (the constant part) */
	rtp_header_t *rtp_header = (rtp_header_t *)out_buffer;
	memset(rtp_header, 0, sizeof(*rtp_header));
	rtp_header->version = 2;
	rtp_header->paytype = 96;

	/* anchor for RTP payload - audioMuxElement (RFC 3016) */
	out_payload = (uint8_t *)&rtp_header->csrc[rtp_header->cc];
	/* helper variable used during payload fragmentation */
	const size_t rtp_header_len = out_payload - out_buffer;

	/* initial input buffer head position and the available size */
	size_t in_samples = in_buffer_size / in_buffer_element_size;
	in_buffer_head = in_buffer;

	struct pollfd pfds[] = {
		{ t->event_fd, POLLIN, 0 },
		{ -1, POLLIN, 0 },
	};

	struct io_sync io_sync = {
		.sampling = samplerate,
	};

	debug("Starting IO loop: %s",
			bluetooth_profile_to_string(t->profile, t->codec));
	for (;;) {
		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

		ssize_t samples;

		/* add PCM socket to the poll if transport is active */
		pfds[1].fd = t->state == TRANSPORT_ACTIVE ? t->a2dp.pcm.fd : -1;

		if (poll(pfds, sizeof(pfds) / sizeof(*pfds), -1) == -1) {
			error("Transport poll error: %s", strerror(errno));
			goto fail;
		}

		if (pfds[0].revents & POLLIN) {
			/* dispatch incoming event */
			eventfd_t event;
			eventfd_read(pfds[0].fd, &event);
			io_sync.frames = 0;
			continue;
		}

		/* read data from the FIFO - this function will block */
		if ((samples = io_thread_read_pcm(&t->a2dp.pcm, in_buffer_head, in_samples)) <= 0) {
			if (samples == -1)
				error("FIFO read error: %s", strerror(errno));
			goto fail;
		}

		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

		if (io_sync.frames == 0) {
			gettimestamp(&io_sync.ts);
			io_sync.ts0 = io_sync.ts;
		}

		if (!config.a2dp_volume || !t->a2dp.supports_dbus_volume)
			/* scale volume or mute audio signal */
			io_thread_scale_pcm(t, in_buffer_head, samples, channels);

		/* overall input buffer size */
		samples += in_buffer_head - in_buffer;
		/* in the encoding loop head is used for reading */
		in_buffer_head = in_buffer;

		while ((in_args.numInSamples = samples) != 0) {

			if ((err = aacEncEncode(handle, &in_buf, &out_buf, &in_args, &out_args)) != AACENC_OK)
				error("AAC encoding error: %s", aacenc_strerror(err));

			if (out_args.numOutBytes > 0) {

				size_t payload_len_max = t->mtu_write - rtp_header_len;
				size_t payload_len = out_args.numOutBytes;
				rtp_header->timestamp = htonl(timestamp);

				/* If the size of the RTP packet exceeds writing MTU, the RTP payload
				 * should be fragmented. According to the RFC 3016, fragmentation of
				 * the audioMuxElement requires no extra header - the payload should
				 * be fragmented and spread across multiple RTP packets.
				 *
				 * TODO: Confirm that the fragmentation logic is correct.
				 *
				 * This code has been tested with Jabra Move headset, however the
				 * outcome of this test is not positive. Fragmented packets are not
				 * recognized by the device. */
				for (;;) {

					ssize_t ret;
					size_t len;

					len = payload_len > payload_len_max ? payload_len_max : payload_len;
					rtp_header->markbit = len < payload_len_max;
					rtp_header->seq_number = htons(++seq_number);

					if ((ret = write(t->bt_fd, out_buffer, rtp_header_len + len)) == -1) {
						if (errno == ECONNRESET || errno == ENOTCONN) {
							/* exit the thread upon BT socket disconnection */
							debug("BT socket disconnected");
							goto fail;
						}
						error("BT socket write error: %s", strerror(errno));
						break;
					}

					/* break if the last part of the payload has been written */
					if ((payload_len -= ret - rtp_header_len) == 0)
						break;

					/* move rest of data to the beginning of the payload */
					debug("Payload fragmentation: extra %zd bytes", payload_len);
					memmove(out_payload, out_payload + ret, payload_len);

				}

			}

			/* progress the head position by the number of samples consumed by the
			 * encoder, also adjust the number of samples in the input buffer */
			in_buffer_head += out_args.numInSamples;
			samples -= out_args.numInSamples;

			/* keep data transfer at a constant bit rate, also
			 * get a timestamp for the next RTP frame */
			timestamp += io_thread_time_sync(&io_sync, out_args.numInSamples / channels);
			t->delay = io_sync.delay;

		}

		/* move leftovers to the beginning */
		if (samples > 0 && in_buffer != in_buffer_head)
			memmove(in_buffer, in_buffer_head, samples * in_buffer_element_size);
		/* reposition input buffer head */
		in_buffer_head = in_buffer + samples;
		in_samples = in_buffer_size / in_buffer_element_size - samples;

	}

fail:
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
fail_init:
	pthread_cleanup_pop(1);
fail_open:
	pthread_cleanup_pop(1);
	return NULL;
}
#endif

void *io_thread_rfcomm(void *arg) {
	struct ba_transport *t = (struct ba_transport *)arg;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	pthread_cleanup_push(CANCEL_ROUTINE(io_thread_release), t);

	uint8_t mic_gain = t->rfcomm.sco->sco.mic_gain;
	uint8_t spk_gain = t->rfcomm.sco->sco.spk_gain;
	char buffer[64];
	struct at_command at;
	int i;

	struct pollfd pfds[] = {
		{ t->event_fd, POLLIN, 0 },
		{ t->bt_fd, POLLIN, 0 },
	};

	/* HSP only supports CVSD */
	if (t->profile == BLUETOOTH_PROFILE_HSP_HS || t->profile == BLUETOOTH_PROFILE_HSP_AG)
		t->rfcomm.sco->sco.codec = TRANSPORT_SCO_CODEC_CVSD;

	debug("Starting RFCOMM loop: %s",
			bluetooth_profile_to_string(t->profile, t->codec));
	for (;;) {

		const char *response = "OK";
		ssize_t ret;

		pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

		if (poll(pfds, sizeof(pfds) / sizeof(*pfds), -1) == -1) {
			error("Transport poll error: %s", strerror(errno));
			goto fail;
		}

		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

		if (pfds[0].revents & POLLIN) {
			/* dispatch incoming event */

			eventfd_t event;
			eventfd_read(pfds[0].fd, &event);

			if (mic_gain != t->rfcomm.sco->sco.mic_gain) {
				mic_gain = t->rfcomm.sco->sco.mic_gain;
				debug("Setting microphone gain: %d", mic_gain);
				sprintf(buffer, "+VGM=%d", mic_gain);
				io_thread_write_at_response(pfds[1].fd, buffer);
			}
			if (spk_gain != t->rfcomm.sco->sco.spk_gain) {
				spk_gain = t->rfcomm.sco->sco.spk_gain;
				debug("Setting speaker gain: %d", spk_gain);
				sprintf(buffer, "+VGS=%d", mic_gain);
				io_thread_write_at_response(pfds[1].fd, buffer);
			}

			continue;
		}

		if ((ret = read(pfds[1].fd, buffer, sizeof(buffer))) == -1) {
			switch (errno) {
			case ECONNABORTED:
			case ECONNRESET:
			case ENOTCONN:
			case ETIMEDOUT:
				/* exit the thread upon socket disconnection */
				debug("RFCOMM disconnected: %s", strerror(errno));
				transport_set_state(t, TRANSPORT_ABORTED);
				goto fail;
			default:
				error("RFCOMM read error: %s", strerror(errno));
				continue;
			}
		}

		/* Parse AT command received from the headset. */
		if (at_parse(buffer, &at)) {
			warn("Invalid AT command: %s", buffer);
			continue;
		}

		debug("AT command: %s=%s", at.command, at.value);

		if (strcmp(at.command, "RING") == 0) {
		}
		else if (strcmp(at.command, "+CKPD") == 0 && atoi(at.value) == 200) {
		}
		else if (strcmp(at.command, "+VGM") == 0)
			t->rfcomm.sco->sco.mic_gain = mic_gain = atoi(at.value);
		else if (strcmp(at.command, "+VGS") == 0)
			t->rfcomm.sco->sco.spk_gain = spk_gain = atoi(at.value);
		else if (strcmp(at.command, "+IPHONEACCEV") == 0) {

			char *ptr = at.value;
			size_t count = atoi(strsep(&ptr, ","));
			char tmp;

			while (count-- && ptr != NULL)
				switch (tmp = *strsep(&ptr, ",")) {
				case '1':
					if (ptr != NULL)
						t->device->xapl.accev_battery = atoi(strsep(&ptr, ","));
					break;
				case '2':
					if (ptr != NULL)
						t->device->xapl.accev_docked = atoi(strsep(&ptr, ","));
					break;
				default:
					warn("Unsupported IPHONEACCEV key: %c", tmp);
					strsep(&ptr, ",");
				}

		}
		else if (strcmp(at.command, "+XAPL") == 0) {

			unsigned int vendor, product;
			unsigned int version, features;

			if (sscanf(at.value, "%x-%x-%u,%u", &vendor, &product, &version, &features) == 4) {
				t->device->xapl.vendor_id = vendor;
				t->device->xapl.product_id = product;
				t->device->xapl.version = version;
				t->device->xapl.features = features;
				response = "+XAPL=BlueALSA,0";
			}
			else {
				warn("Invalid XAPL value: %s", at.value);
				response = "ERROR";
			}

		}
		else if (strcmp(at.command, "+BRSF") == 0) {

			uint32_t hf_features = strtoul(at.value, NULL, 10);
			debug("Got HFP HF features: 0x%x", hf_features);

			uint32_t ag_features = HFP_AG_FEATURES;
#if defined(ENABLE_MSBC)
			if (config.enable_msbc) {
				if (hf_features & HFP_HF_FEAT_CODEC) {
					ag_features |= HFP_AG_FEAT_CODEC;
				}
			}
#endif
			if ((ag_features & HFP_AG_FEAT_CODEC) == 0) {
				/* Codec negotiation is not supported,
				   hence no wideband audio support.
				   AT+BAC is not sent
				   */
				t->rfcomm.sco->sco.codec = TRANSPORT_SCO_CODEC_CVSD;
			}

			t->rfcomm.sco->sco.hf_features = hf_features;

			snprintf(buffer, sizeof(buffer), "+BRSF: %u", ag_features);
			io_thread_write_at_response(pfds[1].fd, buffer);

		}
		else if (strcmp(at.command, "+BAC") == 0 && at.type == AT_CMD_TYPE_SET) {

			debug("Supported codecs: %s", at.value);
			/* In case some headsets send BAC even if we don't
			 * advertise support for it, just OK and ignore
			 */
#if defined(ENABLE_MSBC)
			/* Split codecs string */
			gchar **codecs = g_strsplit(at.value, ",", 0);
			for (i = 0; codecs[i]; i++) {
				gchar *codec = codecs[i];
				uint32_t codec_value = strtoul(codec, NULL, 10);
				if (codec_value == HFP_CODEC_MSBC) {
					t->rfcomm.sco->sco.codec = TRANSPORT_SCO_CODEC_MSBC;
				}
			}
			g_strfreev(codecs);
#endif
			/* Default to CVSD if no other was found */
			if (t->rfcomm.sco->sco.codec == TRANSPORT_SCO_CODEC_UNKNOWN)
				t->rfcomm.sco->sco.codec = TRANSPORT_SCO_CODEC_CVSD;

		}
		else if (strcmp(at.command, "+CIND") == 0) {

			if ( at.type == AT_CMD_TYPE_GET) {
				io_thread_write_at_response(pfds[1].fd,
					"+CIND: 0,0,1,4,0,4,0");
			}
			else if(at.type == AT_CMD_TYPE_TEST) {
				io_thread_write_at_response(pfds[1].fd,
					"+CIND: "
					"(\"call\",(0,1))"
					",(\"callsetup\",(0-3))"
					",(\"service\",(0-1))"
					",(\"signal\",(0-5))"
					",(\"roam\",(0,1))"
					",(\"battchg\",(0-5))"
					",(\"callheld\",(0-2))"
					);
			}

		}
		else if (strcmp(at.command, "+CMER") == 0 && at.type == AT_CMD_TYPE_SET) {

			/* +CMER is the last step of the "Service Level
			   Connection establishment" procedure */

			/* Send OK */
			io_thread_write_at_response(pfds[1].fd, response);

			/* Send codec select if anything besides CVSD was found */
			if (t->rfcomm.sco->sco.codec > TRANSPORT_SCO_CODEC_CVSD) {
				snprintf(buffer, sizeof(buffer), "+BCS: %u", t->rfcomm.sco->sco.codec);
				io_thread_write_at_response(pfds[1].fd, buffer);
			}
			continue;

		}
		else if (strcmp(at.command, "+BCS") == 0 && at.type == AT_CMD_TYPE_SET) {
			debug("Got codec selected: %d", atoi(at.value));
		}
		else if (strcmp(at.command, "+BTRH") == 0 && at.type == AT_CMD_TYPE_GET) {
		}
		else if (strcmp(at.command, "+NREC") == 0 && at.type == AT_CMD_TYPE_SET) {
		}
		else if (strcmp(at.command, "+CCWA") == 0 && at.type == AT_CMD_TYPE_SET) {
		}
		else if (strcmp(at.command, "+BIA") == 0 && at.type == AT_CMD_TYPE_SET) {
		}
		else if (strcmp(at.command, "+CHLD") == 0 && at.type == AT_CMD_TYPE_TEST) {
			io_thread_write_at_response(pfds[1].fd, "+CHLD: (0,1,2,3)");
		}
		else {
			warn("Unsupported AT command: %s=%s", at.command, at.value);
			response = "ERROR";
		}

		io_thread_write_at_response(pfds[1].fd, response);
	}

fail:
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	pthread_cleanup_pop(1);
	return NULL;
}

void *io_thread_sco(void *arg) {
	struct ba_transport *t = (struct ba_transport *)arg;

	/* this buffer has to be bigger than SCO MTU */
	const size_t buffer_size = 512;
	struct sbc_state *sbc = NULL;
	int16_t *buffer = malloc(buffer_size);

	pthread_cleanup_push(CANCEL_ROUTINE(free), buffer);
	pthread_cleanup_push(CANCEL_ROUTINE(free), sbc);

	if (buffer == NULL) {
		error("Couldn't create data buffers: %s", strerror(ENOMEM));
		goto fail;
	}

	struct pollfd pfds[] = {
		{ t->event_fd, POLLIN, 0 },
		{ -1, POLLIN, 0 }, //bt
		{ -1, 0, 0 }, // shm pcm mic follower by shm pcm spk
		{ -1, 0, 0 },
		{ -1, 0, 0 },
		{ -1, 0, 0 },
	};

	struct io_sync io_sync = {
		.frames = 0,
	};

	debug("Starting IO loop: %s",
			bluetooth_profile_to_string(t->profile, t->codec));
	for (;;) {

		if (t->sco.codec == TRANSPORT_SCO_CODEC_MSBC) {
			pfds[1].fd = t->bt_fd;
		} else {
			pfds[1].fd = t->sco.mic_pcm.shm != NULL ? t->bt_fd : -1;
		}

		int shm_mic_nr_pfds = libshm_nr_pollfd(t->sco.mic_pcm.shm);
		int shm_spk_nr_pfds = libshm_nr_pollfd(t->sco.spk_pcm.shm);
		libshm_populate_pollfd(t->sco.mic_pcm.shm, &pfds[2]);
		libshm_populate_pollfd(t->sco.spk_pcm.shm, &pfds[2 + shm_mic_nr_pfds]);

		if (poll(pfds, 2 + shm_mic_nr_pfds + shm_spk_nr_pfds, -1) == -1) {
			error("Transport poll error: %s", strerror(errno));
			goto fail;
		}

		if (pfds[0].revents & POLLIN) {
			/* dispatch incoming event */

			eventfd_t event;
			eventfd_read(pfds[0].fd, &event);

			/* It is required to release SCO if we are not transferring audio,
			 * because it will free Bluetooth bandwidth - microphone signal is
			 * transfered even though we are not reading from it! */
			if (t->sco.spk_pcm.shm == NULL && t->sco.mic_pcm.shm == NULL) {
				transport_release_bt_sco(t);
				io_sync.frames = 0;
			}
			else {
				debug("Trying to acquire");
				transport_acquire_bt_sco(t);
#if defined(ENABLE_MSBC)
				/* This can be called again, make sure it is "reentrant" */
				if (t->sco.codec == TRANSPORT_SCO_CODEC_MSBC) {
					sbc = iothread_initialize_msbc(sbc);
					if (!sbc)
						goto fail;
				}
#endif
			}

			io_sync.sampling = transport_get_sampling(t);
			continue;
		}

		if (io_sync.frames == 0) {
			gettimestamp(&io_sync.ts);
			io_sync.ts0 = io_sync.ts;
		}

		int poll_mic = libshm_poll(t->sco.mic_pcm.shm, &pfds[2], shm_mic_nr_pfds);
		int poll_spk = libshm_poll(t->sco.spk_pcm.shm, &pfds[2 + shm_mic_nr_pfds], shm_spk_nr_pfds);

		if (pfds[1].revents & POLLIN) { // bluetooth socket incoming

#if defined(ENABLE_MSBC)
			if (t->sco.codec == TRANSPORT_SCO_CODEC_MSBC) {
				iothread_handle_incoming_msbc(t, sbc);
			}
			else
#endif
			{
				ssize_t len;

				if ((len = read(pfds[1].fd, buffer, buffer_size)) == -1) {
					debug("SCO read error: %s", strerror(errno));
					continue;
				}

				libshm_write_all(t->sco.mic_pcm.shm, buffer, len);
			}
		}

		if (poll_spk & POLLIN) {

#if defined(ENABLE_MSBC)
			if (t->sco.codec == TRANSPORT_SCO_CODEC_MSBC) {
				iothread_handle_outgoing_msbc(t, sbc);
			}
			else
#endif
			{
				ssize_t samples = t->mtu_write / sizeof(int16_t);

				/* read data from the FIFO - this function will block */
				if ((samples = io_thread_read_pcm(&t->sco.spk_pcm, buffer, samples)) <= 0) {
					if (samples == -1)
						error("FIFO read error: %s", strerror(errno));
					continue;
				}

				write(t->bt_fd, buffer, samples * sizeof(int16_t));
			}
		}

		/* mSBC output is synchronized to input, no need for this */
		if (t->sco.codec != TRANSPORT_SCO_CODEC_MSBC) {
			/* keep data transfer at a constant bit rate */
			io_thread_time_sync(&io_sync, 48 / 2);
			t->delay = io_sync.delay;
		}

	}

fail:
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	return NULL;
}