static guint resume_a2dp(struct media_transport *transport, struct media_owner *owner) { struct a2dp_transport *a2dp = transport->data; struct media_endpoint *endpoint = transport->endpoint; struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); guint id; if (a2dp->session == NULL) { a2dp->session = avdtp_get(transport->device); if (a2dp->session == NULL) return 0; } if (state_in_use(transport->state)) return a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); if (a2dp_sep_lock(sep, a2dp->session) == FALSE) return 0; id = a2dp_resume(a2dp->session, sep, a2dp_resume_complete, owner); if (id == 0) { a2dp_sep_unlock(sep, a2dp->session); return 0; } if (transport->state == TRANSPORT_STATE_IDLE) transport_set_state(transport, TRANSPORT_STATE_REQUESTING); return id; }
static void transport_update_playing(struct media_transport *transport, gboolean playing) { DBG("%s State=%s Playing=%d", transport->path, str_state[transport->state], playing); if (playing == FALSE) { if (transport->state == TRANSPORT_STATE_PENDING) transport_set_state(transport, TRANSPORT_STATE_IDLE); else if (transport->state == TRANSPORT_STATE_ACTIVE) { /* Remove owner */ if (transport->owner != NULL) media_transport_remove_owner(transport); } } else if (transport->state == TRANSPORT_STATE_IDLE) transport_set_state(transport, TRANSPORT_STATE_PENDING); }
static guint suspend_a2dp(struct media_transport *transport, struct media_owner *owner) { struct a2dp_transport *a2dp = transport->data; struct media_endpoint *endpoint = transport->endpoint; struct a2dp_sep *sep = media_endpoint_get_sep(endpoint); if (owner != NULL) return a2dp_suspend(a2dp->session, sep, a2dp_suspend_complete, owner); transport_set_state(transport, TRANSPORT_STATE_IDLE); a2dp_sep_unlock(sep, a2dp->session); return 0; }
static void a2dp_resume_complete(struct avdtp *session, struct avdtp_error *err, void *user_data) { struct media_owner *owner = user_data; struct media_request *req = owner->pending; struct media_transport *transport = owner->transport; struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); struct avdtp_stream *stream; int fd; uint16_t imtu, omtu; gboolean ret; req->id = 0; if (err) goto fail; stream = a2dp_sep_get_stream(sep); if (stream == NULL) goto fail; ret = avdtp_stream_get_transport(stream, &fd, &imtu, &omtu, NULL); if (ret == FALSE) goto fail; media_transport_set_fd(transport, fd, imtu, omtu); ret = g_dbus_send_reply(btd_get_dbus_connection(), req->msg, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_UINT16, &imtu, DBUS_TYPE_UINT16, &omtu, DBUS_TYPE_INVALID); if (ret == FALSE) goto fail; media_owner_remove(owner); transport_set_state(transport, TRANSPORT_STATE_ACTIVE); return; fail: media_transport_remove_owner(transport); }
static void a2dp_suspend_complete(struct avdtp *session, struct avdtp_error *err, void *user_data) { struct media_owner *owner = user_data; struct media_transport *transport = owner->transport; struct a2dp_transport *a2dp = transport->data; struct a2dp_sep *sep = media_endpoint_get_sep(transport->endpoint); /* Release always succeeds */ if (owner->pending) { owner->pending->id = 0; media_request_reply(owner->pending, 0); media_owner_remove(owner); } a2dp_sep_unlock(sep, a2dp->session); transport_set_state(transport, TRANSPORT_STATE_IDLE); media_transport_remove_owner(transport); }
static DBusMessage *release(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_transport *transport = data; struct media_owner *owner = transport->owner; const char *sender; struct media_request *req; guint id; sender = dbus_message_get_sender(msg); if (owner == NULL || g_strcmp0(owner->name, sender) != 0) return btd_error_not_authorized(msg); if (owner->pending) { const char *member; member = dbus_message_get_member(owner->pending->msg); /* Cancel Acquire request if that exist */ if (g_str_equal(member, "Acquire")) media_owner_remove(owner); else return btd_error_in_progress(msg); } transport_set_state(transport, TRANSPORT_STATE_SUSPENDING); id = transport->suspend(transport, owner); if (id == 0) { media_transport_remove_owner(transport); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } req = media_request_create(msg, id); media_owner_add(owner, req); return NULL; }
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; }