/* Copyright (c) 2013 The Chromium OS 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>
#include "audio_thread.h"
#include "byte_buffer.h"
#include "cras_iodev_list.h"
#include "cras_hfp_info.h"
#include "utlist.h"
/* The max buffer size. Note that the actual used size must set to multiple
* of SCO packet size, and the packet size does not necessarily be equal to
* MTU.
*/
#define MAX_HFP_BUF_SIZE_BYTES 16384
/* rate(8kHz) * sample_size(2 bytes) * channels(1) */
#define HFP_BYTE_RATE 16000
/* Structure to hold variables for a HFP connection. Since HFP supports
* bi-direction audio, two iodevs should share one hfp_info if they
* represent two directions of the same HFP headset
* Members:
* fd - The file descriptor for SCO socket.
* started - If the hfp_info has started to read/write SCO data.
* mtu - The max transmit unit reported from BT adapter.
* packet_size - The size of SCO packet to read/write preferred by
* adapter, could be different than mtu.
* capture_buf - The buffer to hold samples read from SCO socket.
* playback_buf - The buffer to hold samples about to write to SCO socket.
* idev - The input iodev using this hfp_info.
* odev - The output iodev using this hfp_info.
* packet_size_changed_cbs - The callbacks to trigger when SCO packet
* size changed.
*/
struct hfp_info {
int fd;
int started;
unsigned int mtu;
unsigned int packet_size;
struct byte_buffer *capture_buf;
struct byte_buffer *playback_buf;
struct cras_iodev *idev;
struct cras_iodev *odev;
struct hfp_packet_size_changed_callback *packet_size_changed_cbs;
};
int hfp_info_add_iodev(struct hfp_info *info, struct cras_iodev *dev)
{
if (dev->direction == CRAS_STREAM_OUTPUT) {
if (info->odev)
goto invalid;
info->odev = dev;
buf_reset(info->playback_buf);
} else if (dev->direction == CRAS_STREAM_INPUT) {
if (info->idev)
goto invalid;
info->idev = dev;
buf_reset(info->capture_buf);
}
return 0;
invalid:
return -EINVAL;
}
int hfp_info_rm_iodev(struct hfp_info *info, struct cras_iodev *dev)
{
if (dev->direction == CRAS_STREAM_OUTPUT && info->odev == dev) {
info->odev = NULL;
} else if (dev->direction == CRAS_STREAM_INPUT && info->idev == dev){
info->idev = NULL;
} else
return -EINVAL;
return 0;
}
int hfp_info_has_iodev(struct hfp_info *info)
{
return info->odev || info->idev;
}
void hfp_buf_acquire(struct hfp_info *info, struct cras_iodev *dev,
uint8_t **buf, unsigned *count)
{
size_t format_bytes;
unsigned int buf_avail;
format_bytes = cras_get_format_bytes(dev->format);
*count *= format_bytes;
if (dev->direction == CRAS_STREAM_OUTPUT)
*buf = buf_write_pointer_size(info->playback_buf, &buf_avail);
else
*buf = buf_read_pointer_size(info->capture_buf, &buf_avail);
if (*count > buf_avail)
*count = buf_avail;
*count /= format_bytes;
}
int hfp_buf_size(struct hfp_info *info, struct cras_iodev *dev)
{
return info->playback_buf->used_size / cras_get_format_bytes(dev->format);
}
void hfp_buf_release(struct hfp_info *info, struct cras_iodev *dev,
unsigned written_frames)
{
size_t format_bytes;
format_bytes = cras_get_format_bytes(dev->format);
written_frames *= format_bytes;
if (dev->direction == CRAS_STREAM_OUTPUT)
buf_increment_write(info->playback_buf, written_frames);
else
buf_increment_read(info->capture_buf, written_frames);
}
int hfp_buf_queued(struct hfp_info *info, const struct cras_iodev *dev)
{
size_t format_bytes;
format_bytes = cras_get_format_bytes(dev->format);
if (dev->direction == CRAS_STREAM_OUTPUT)
return buf_queued(info->playback_buf) / format_bytes;
else
return buf_queued(info->capture_buf) / format_bytes;
}
int hfp_write(struct hfp_info *info)
{
int err = 0;
unsigned to_send;
uint8_t *samples;
/* Write something */
samples = buf_read_pointer_size(info->playback_buf, &to_send);
if (to_send < info->packet_size)
return 0;
to_send = info->packet_size;
send_sample:
err = send(info->fd, samples, to_send, 0);
if (err < 0) {
if (errno == EINTR)
goto send_sample;
return err;
}
if (err != (int)info->packet_size) {
syslog(LOG_ERR,
"Partially write %d bytes for SCO packet size %u",
err, info->packet_size);
return -1;
}
buf_increment_read(info->playback_buf, to_send);
return err;
}
static void hfp_info_set_packet_size(struct hfp_info *info,
unsigned int packet_size)
{
struct hfp_packet_size_changed_callback *callback;
unsigned int used_size =
MAX_HFP_BUF_SIZE_BYTES / packet_size * packet_size;
info->packet_size = packet_size;
byte_buffer_set_used_size(info->playback_buf, used_size);
byte_buffer_set_used_size(info->capture_buf, used_size);
DL_FOREACH(info->packet_size_changed_cbs, callback)
callback->cb(callback->data);
}
void hfp_register_packet_size_changed_callback(struct hfp_info *info,
void (*cb)(void *data),
void *data)
{
struct hfp_packet_size_changed_callback *callback =
(struct hfp_packet_size_changed_callback *)calloc(1,
sizeof(struct hfp_packet_size_changed_callback));
callback->data = data;
callback->cb = cb;
DL_APPEND(info->packet_size_changed_cbs, callback);
}
void hfp_unregister_packet_size_changed_callback(struct hfp_info *info,
void *data)
{
struct hfp_packet_size_changed_callback *callback;
DL_FOREACH(info->packet_size_changed_cbs, callback) {
if (data == callback->data) {
DL_DELETE(info->packet_size_changed_cbs, callback);
free(callback);
}
}
}
int hfp_read(struct hfp_info *info)
{
int err = 0;
unsigned to_read;
uint8_t *capture_buf;
capture_buf = buf_write_pointer_size(info->capture_buf, &to_read);
if (to_read < info->packet_size)
return 0;
to_read = info->packet_size;
recv_sample:
err = recv(info->fd, capture_buf, to_read, 0);
if (err < 0) {
syslog(LOG_ERR, "Read error %s", strerror(errno));
if (errno == EINTR)
goto recv_sample;
return err;
}
if (err != (int)info->packet_size) {
/* Allow the SCO packet size be modified from the default MTU
* value to the size of SCO data we first read. This is for
* some adapters who prefers a different value than MTU for
* transmitting SCO packet.
*/
if (err && (info->packet_size == info->mtu)) {
hfp_info_set_packet_size(info, err);
} else {
syslog(LOG_ERR, "Partially read %d bytes for %u size SCO packet",
err, info->packet_size);
return -1;
}
}
buf_increment_write(info->capture_buf, err);
return err;
}
/* Callback function to handle sample read and write.
* Note that we poll the SCO socket for read sample, since it reflects
* there is actual some sample to read while the socket always reports
* writable even when device buffer is full.
* The strategy is to synchronize read & write operations:
* 1. Read one chunk of MTU bytes of data.
* 2. When input device not attached, ignore the data just read.
* 3. When output device attached, write one chunk of MTU bytes of data.
*/
static int hfp_info_callback(void *arg)
{
struct hfp_info *info = (struct hfp_info *)arg;
int err;
if (!info->started)
goto read_write_error;
err = hfp_read(info);
if (err < 0) {
syslog(LOG_ERR, "Read error");
goto read_write_error;
}
/* Ignore the MTU bytes just read if input dev not in present */
if (!info->idev)
buf_increment_read(info->capture_buf, info->packet_size);
if (info->odev) {
err = hfp_write(info);
if (err < 0) {
syslog(LOG_ERR, "Write error");
goto read_write_error;
}
}
return 0;
read_write_error:
hfp_info_stop(info);
return 0;
}
struct hfp_info *hfp_info_create()
{
struct hfp_info *info;
info = (struct hfp_info *)calloc(1, sizeof(*info));
if (!info)
goto error;
info->capture_buf = byte_buffer_create(MAX_HFP_BUF_SIZE_BYTES);
if (!info->capture_buf)
goto error;
info->playback_buf = byte_buffer_create(MAX_HFP_BUF_SIZE_BYTES);
if (!info->playback_buf)
goto error;
return info;
error:
if (info) {
if (info->capture_buf)
byte_buffer_destroy(&info->capture_buf);
if (info->playback_buf)
byte_buffer_destroy(&info->playback_buf);
free(info);
}
return NULL;
}
int hfp_info_running(struct hfp_info *info)
{
return info->started;
}
int hfp_info_start(int fd, unsigned int mtu, struct hfp_info *info)
{
info->fd = fd;
info->mtu = mtu;
/* Make sure buffer size is multiple of packet size, which initially
* set to MTU. */
hfp_info_set_packet_size(info, mtu);
buf_reset(info->playback_buf);
buf_reset(info->capture_buf);
audio_thread_add_callback(info->fd, hfp_info_callback, info);
info->started = 1;
return 0;
}
int hfp_info_stop(struct hfp_info *info)
{
if (!info->started)
return 0;
audio_thread_rm_callback_sync(
cras_iodev_list_get_audio_thread(),
info->fd);
close(info->fd);
info->fd = 0;
info->started = 0;
return 0;
}
void hfp_info_destroy(struct hfp_info *info)
{
if (info->capture_buf)
byte_buffer_destroy(&info->capture_buf);
if (info->playback_buf)
byte_buffer_destroy(&info->playback_buf);
free(info);
}