* Re: [Bluez-devel] a2dp alsa plugin "better"
2006-04-27 13:36 ` Brad Midgley
@ 2006-04-27 16:41 ` Sergey Krivov
2006-04-27 17:57 ` Brad Midgley
2006-04-28 19:51 ` Brad Midgley
0 siblings, 2 replies; 8+ messages in thread
From: Sergey Krivov @ 2006-04-27 16:41 UTC (permalink / raw)
To: bluez-devel
[-- Attachment #1: Type: text/plain, Size: 1133 bytes --]
Brad,
--- Brad Midgley <bmidgley@xmission.com> wrote:
> Sergey
>
> > i wrote both of them.
>
> I still have occasional lockups for a2dp_transfer2.
> Could you put in
> comments so I have an easier time figuring out these
> two transfer routines?
i think a fixed the bug, at least in my case lockups
with transfer2 disappeared. i have added comments to
a2dp_transfer function. a2dp_transfer2 is very
similar.
I still use a2dp_transfer since with a2dp_transfer2 i
can not control xmms with headphone buttons.
these functions just work, and i suspect they dont
interact properly with alsa. it would be nice to have
a simple example of alsa plugin to cast the things.
i attach the whole pcm_a2dp.c but you have to copy
just a2dp_transfer, a2dp_transfer, time_to_wait,
sleeptill functions.
>
> We also need to take steps when write() returns an
> unexpected value.
> That may be part of the reason things come crashing
> down.
did you mean write(a2dp->sk,... ?
Sergei
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 1802492780-pcm_a2dp.c --]
[-- Type: text/x-csrc; name="pcm_a2dp.c", Size: 31620 bytes --]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#if 0
DEBUG: _snd_pcm_a2dp_open: name pcm.headphone mode 0
DEBUG: _snd_pcm_a2dp_open: bdaddr/dest is 00:0D:3C:30:32:AD
DEBUG: a2dp_connect: a2dp 0x805b118
DEBUG: connect_l2cap: Connected [imtu 672, omtu 672, flush_to 65535]
DEBUG: connect_stream: Sent the Stream End Point Discovery Command
DEBUG: connect_stream: Got a Stream End Point Discovery Response
DEBUG: connect_stream: received 1 capabilities
DEBUG: process_seid: SEID = 1
DEBUG: process_seid: Requested Capabilities for SEID = 1
DEBUG: process_seid: Got capabilities response
DEBUG: process_seid: Sent set configurations command
DEBUG: process_seid: Set configurations command accepted
DEBUG: process_seid: Sent open stream command
DEBUG: process_seid: Got open stream confirm
DEBUG: connect_l2cap: Connected [imtu 672, omtu 672, flush_to 65535]
DEBUG: connect_stream: Sent stream start
DEBUG: connect_stream: Got start stream confirm
DEBUG: a2dp_constraint: a2dp 0x805b118
Playing WAVE '/usr/share/sounds/linphone/rings/rock.wav' : Signed 16 bit Little Endian, Rate 44100 Hz, Stereo
DEBUG: a2dp_params: a2dp 0x805b118
DEBUG: a2dp_params: format S16_LE rate 44100 channels 2
DEBUG: a2dp_params: frame_bytes 4 period_bytes 8192 period_size 2048 buffer_size 4096
DEBUG: a2dp_prepare: a2dp 0x805b118
#endif
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#include <alsa/asoundlib.h>
#include <alsa/pcm_external.h>
#include <alsa/timer.h>
#include <netinet/in.h>
#include "../sbc/sbc.h"
#include "../a2dp.h"
#define NONSPECAUDIO 1
time_t timestamp=0;
uint16_t seq_num=1;
int frame_count=0;
#define BUFS 1024
static char bufe[BUFS];
int lenbufe=0;
unsigned long nbytes=0;
unsigned long total_time=0;
struct timeval t0;
struct itimerval itimer;
struct sigaction sa;
struct timeval tsend;
int fpr=0;
#define min(X, Y) ((X) < (Y) ? (X) : (Y))
#define DBG(fmt, arg...) printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg)
//#define DBG(D...)
static void a2dp_init(void) __attribute__ ((constructor));
static void a2dp_exit(void) __attribute__ ((destructor));
static void change_endian( void *buf, int size)
{
int i;
char c;
char *ptr;
ptr = buf;
for(i = 0; i < size; i += 2) {
c = ptr[i];
ptr[i] = ptr[i+1];
ptr[i+1] = c;
}
}
// Prepare packet headers
static void init_request(struct avdtp_header * header, int request_id)
{
static int transaction = 0;
header->packet_type = PACKET_TYPE_SINGLE;
header->message_type = MESSAGE_TYPE_COMMAND;
header->transaction_label = transaction;
header->signal_id = request_id;
// clear rfa bits
header->rfa0 = 0;
transaction = (transaction + 1) & 0xf;
}
// Analyse the SEIDs the sink has sent to us
static int process_seid(int s, struct acp_seid_info * get_seid_resp, unsigned short *psm, sbc_t *sbc)
{
int v, size;
int seid = get_seid_resp->acp_seid;
struct getcap_req put_req;
struct getcap_resp cap_resp;
struct set_config s_config;
struct set_config_resp s_resp;
struct open_stream_cmd open_stream;
struct open_stream_rsp open_resp;
DBG("SEID = %d", seid);
memset(&put_req, 0, sizeof(put_req));
init_request(&put_req.header, AVDTP_GET_CAPABILITIES);
put_req.acp_seid = seid;
if (write(s, &put_req, sizeof(put_req)) != sizeof(put_req)) {
DBG("Couldn't request capabilities for SEID = %d", seid);
return (-1);
}
else {
DBG("Requested Capabilities for SEID = %d",seid);
}
if (read(s, &cap_resp, sizeof(cap_resp)) < sizeof(cap_resp) ||
cap_resp.header.message_type == MESSAGE_TYPE_REJECT ||
cap_resp.media_type != AUDIO_MEDIA_TYPE ||
cap_resp.media_codec_type != SBC_MEDIA_CODEC_TYPE) {
DBG("Didn't receive SBC codec parameters (first) for SEID = %d", seid);
return (-1);
}
DBG("Got capabilities response");
memset(&s_config, 0, sizeof(s_config));
init_request(&s_config.header, AVDTP_SET_CONFIGURATION);
s_config.serv_cap = MEDIA_TRANSPORT_CATEGORY;
s_config.acp_seid = seid;
s_config.int_seid = 1; // how should I choose the int_seid??
s_config.cap_type = MEDIA_CODEC;
s_config.length = 6;
s_config.media_type = AUDIO_MEDIA_TYPE;
s_config.media_codec_type = SBC_MEDIA_CODEC_TYPE;
switch(sbc->channels) {
case 1:
v = 8;
break;
case 2:
default:
v = 2;
break;
}
s_config.sbc_elements.channel_mode = v;
switch(sbc->rate) {
case 16000:
v = 8;
break;
case 32000:
v = 4;
break;
case 48000:
v = 1;
break;
case 44100:
default:
v = 2;
break;
}
s_config.sbc_elements.frequency = v;
s_config.sbc_elements.allocation_method = 1 << 1;
switch(sbc->subbands) {
case 4:
v = 2;
break;
case 8:
default:
v = 1;
break;
}
s_config.sbc_elements.subbands = v;
switch(sbc->blocks) {
case 4:
v = 8;
break;
case 8:
v = 4;
break;
case 12:
v = 2;
break;
case 16:
default:
v = 1;
break;
}
s_config.sbc_elements.block_length = v;
s_config.sbc_elements.min_bitpool = cap_resp.sbc_elements.min_bitpool;
s_config.sbc_elements.max_bitpool = cap_resp.sbc_elements.max_bitpool;
if (!(cap_resp.sbc_elements.channel_mode & s_config.sbc_elements.channel_mode)) {
DBG("headset does not support this channel mode");
}
if (!(cap_resp.sbc_elements.frequency & s_config.sbc_elements.frequency)) {
DBG("headset does not support this frequency");
}
if (!(cap_resp.sbc_elements.allocation_method & s_config.sbc_elements.allocation_method)) {
DBG("headset does not support this allocation_method");
}
if (!(cap_resp.sbc_elements.subbands & s_config.sbc_elements.subbands)) {
DBG("headset does not support this subbands setting");
}
if (write(s, &s_config, sizeof(s_config)) != sizeof(s_config)) {
DBG("couldn't set config seid = %d", seid);
return (-1);
}
DBG("Sent set configurations command");
size = read(s, &s_resp, sizeof(s_resp));
if (size == sizeof(s_resp) - 2) {
DBG("Set configurations command accepted");
} else {
DBG("Set configurations command rejected");
}
memset(&open_stream, 0, sizeof(open_stream));
init_request(&open_stream.header, AVDTP_OPEN);
open_stream.acp_seid = seid;
if (write(s, &open_stream, sizeof(open_stream)) != sizeof(open_stream)) {
DBG("Couldn't open stream SEID = %d", seid);
return (-1);
}
DBG("Sent open stream command");
if (read(s, &open_resp, sizeof(open_resp)) < sizeof(open_resp) - 1 ||
open_resp.header.message_type == MESSAGE_TYPE_REJECT) {
DBG("Didn't receive open response confirm for SEID = %d", seid);
return (-1);
}
DBG("Got open stream confirm");
*psm = 25;
return 0;
}
// Detect whether A2DP Sink is present at the destination or not
static int detect_a2dp(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm, unsigned long *flags)
{
sdp_session_t *sess;
sdp_list_t *attrid, *search, *seq, *next;
sdp_data_t *pdlist;
uuid_t group;
uint32_t range = 0x0000ffff;
int err;
sess = sdp_connect(src, dst, SDP_RETRY_IF_BUSY);
if (!sess) {
fprintf(stderr, "Warning: failed to connect to SDP server: %s\n", strerror(errno));
if(psm) *psm = 25;
if(flags) *flags = 0;
return 0;
}
/* 0x1108->all? 0x1101->rf sink 0x111e->handsfree 0x1108->headset */
sdp_uuid16_create(&group, 0x110d);
search = sdp_list_append(0, &group);
attrid = sdp_list_append(0, &range);
err = sdp_service_search_attr_req(sess, search,
SDP_ATTR_REQ_RANGE, attrid, &seq);
sdp_list_free(search, 0);
sdp_list_free(attrid, 0);
if (err) {
fprintf(stderr, "Service Search failed: %s\n", strerror(errno));
sdp_close(sess);
return -1;
}
for (; seq; seq = next) {
sdp_record_t *rec = (sdp_record_t *) seq->data;
fprintf(stderr, "Found A2DP Sink\n");
if (psm)
*psm = 25;
next = seq->next;
free(seq);
sdp_record_free(rec);
}
sdp_uuid16_create(&group, PNP_INFO_SVCLASS_ID);
search = sdp_list_append(0, &group);
attrid = sdp_list_append(0, &range);
err = sdp_service_search_attr_req(sess, search,
SDP_ATTR_REQ_RANGE, attrid, &seq);
sdp_list_free(search, 0);
sdp_list_free(attrid, 0);
if (err)
goto done;
if (flags)
*flags = 0;
for (; seq; seq = next) {
sdp_record_t *rec = (sdp_record_t *) seq->data;
uint16_t vendor, product, version;
pdlist = sdp_data_get(rec, 0x0201);
vendor = pdlist ? pdlist->val.uint16 : 0x0000;
pdlist = sdp_data_get(rec, 0x0202);
product = pdlist ? pdlist->val.uint16 : 0x0000;
pdlist = sdp_data_get(rec, 0x0203);
version = pdlist ? pdlist->val.uint16 : 0x0000;
fprintf(stderr, "Product ID %04x:%04x:%04x\n", vendor, product, version);
if (vendor == 0x1310 && product == 0x0100 && version == 0x0104) {
fprintf(stderr, "Enabling GCT media payload workaround\n");
if (flags)
*flags |= NONSPECAUDIO;
}
next = seq->next;
free(seq);
sdp_record_free(rec);
}
done:
sdp_close(sess);
return 0;
}
// Connecting on PSM 25
static int do_connect(bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint16_t *mtu)
{
struct sockaddr_l2 addr;
struct l2cap_options opts;
int sk;
unsigned int opt;
sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (sk < 0) {
fprintf(stderr, "Can't create socket. %s(%d)\n",
strerror(errno), errno);
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, src);
if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
fprintf(stderr, "Can't bind socket. %s(%d)\n",
strerror(errno), errno);
return -1;
}
/* Get default options */
opt = sizeof(opts);
if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &opt) < 0) {
fprintf(stderr, "Can't get default L2CAP options. %s(%d)\n",
strerror(errno), errno);
return -1;
}
/* Set new options */
if(mtu && *mtu) {
opts.omtu = *mtu;
//opts.imtu = *mtu;
}
if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, opt) < 0) {
fprintf(stderr, "Can't set L2CAP options. %s(%d)\n",
strerror(errno), errno);
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, dst);
addr.l2_psm = htobs(psm);
if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
fprintf(stderr, "Can't connect to %s. %s(%d)\n",
batostr(&addr.l2_bdaddr), strerror(errno), errno);
close(sk);
return -1;
}
opt = sizeof(opts);
if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &opt) < 0) {
fprintf(stderr, "Can't get L2CAP options. %s(%d)\n",
strerror(errno), errno);
close(sk);
return -1;
}
fprintf(stderr, "Connected [imtu %d, omtu %d, flush_to %d]\n",
opts.imtu, opts.omtu, opts.flush_to);
if (mtu)
*mtu = opts.omtu;
return sk;
}
static int connect_stream(bdaddr_t *src, bdaddr_t *dst, int *cmdfd_return, sbc_t *sbc) {
int cmdfd;
struct getcap_req put_req;
struct sepd_resp get_resp;
struct start_stream_cmd start_stream;
struct start_stream_rsp start_resp;
int seid, last_seid_index;
int size;
int i;
unsigned short psm_cmd,psm_stream;
unsigned long flags = 0;
static int streamfd;
uint16_t mtu = 0;
int tries;
fprintf(stderr, "Using address: %s\n", batostr(dst));
if (detect_a2dp(src, dst, &psm_cmd, &flags) < 0) {
fprintf(stderr, "could not find A2DP services on device %s\n", batostr(dst));
exit(-1);
}
else fprintf(stderr, "Found A2DP Sink at the destination\n");
psm_cmd=25;
cmdfd = do_connect(src, dst, psm_cmd, NULL);
if (cmdfd < 0) {
fprintf(stderr, "cannot open psm_cmd = %d\n", psm_cmd);
exit(-1);
}
// avdt_discover_req
memset(&put_req, 0, sizeof(put_req));
init_request(&put_req.header, AVDTP_DISCOVER);
if (write(cmdfd, &put_req, sizeof(put_req)) != sizeof(put_req)) {
DBG("couldn't send avdtp_discover");
close(cmdfd);
exit(-1);
}
else {
DBG("Sent the Stream End Point Discovery Command");
}
tries = 0;
while((size = read(cmdfd, &get_resp, sizeof(get_resp))) < 0 && errno == EAGAIN) {
DBG("retrying discover response read...");
sleep(1);
if(++tries > 10) {
exit(-1);
}
}
if (size < sizeof(get_resp) - MAX_ADDITIONAL_CODEC_OCTETS) {
DBG("couldn't get avdtp_discover");
close(cmdfd);
exit(-1);
}
else {
DBG("Got a Stream End Point Discovery Response");
}
seid = -1;
last_seid_index = MAX_ADDITIONAL_CODEC - ((sizeof(get_resp)-size)/sizeof(struct acp_seid_info));
DBG("received %d capabilities", last_seid_index + 1);
for(i=0; i <= last_seid_index; i++) {
if (process_seid(cmdfd, &get_resp.infos[i], &psm_stream, sbc) == 0) {
seid = get_resp.infos[i].acp_seid;
break;
}
}
if(seid == -1) {
//We have not found the seid that we want
DBG("couldn't locate the correct seid");
exit(-1);
}
// open the stream
streamfd = do_connect(src, dst, psm_stream, &mtu);
if (streamfd < 0) {
DBG("cannot open psm_stream = %d", psm_stream);
exit(-1);
}
// start the stream
memset(&start_stream, 0, sizeof(start_stream));
init_request(&start_stream.header, AVDTP_START);
start_stream.acp_seid = seid;
if (write(cmdfd, &start_stream, sizeof(start_stream)) != sizeof(start_stream)) {
DBG("couldn't send start_stream");
close(streamfd);
close(cmdfd);
exit(-1);
}
DBG("Sent stream start");
if (read(cmdfd, &start_resp, sizeof(start_resp)) < sizeof(start_resp) - 2 ||start_resp.header.message_type == MESSAGE_TYPE_REJECT) {
DBG("didn't receive start_resp confirm for seid = %d", seid);
close(streamfd);
close(cmdfd);
return (-1);
}
DBG("Got start stream confirm");
*cmdfd_return = cmdfd;
return streamfd;
}
typedef struct snd_pcm_a2dp {
snd_pcm_ioplug_t io;
int refcnt;
int timeout;
unsigned long state;
bdaddr_t src;
bdaddr_t dst;
int sk;
int control_sk;
sbc_t sbc;
snd_pcm_sframes_t num;
unsigned char buf[1024];
unsigned int len;
unsigned int frame_bytes;
int use_rfcomm;
} snd_pcm_a2dp_t;
static void inline a2dp_get(snd_pcm_a2dp_t *a2dp)
{
a2dp->refcnt++;
a2dp->timeout = 0;
}
static void inline a2dp_put(snd_pcm_a2dp_t *a2dp)
{
a2dp->refcnt--;
if (a2dp->refcnt <= 0)
a2dp->timeout = 2;
}
static int a2dp_start(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 13;
return 0;
}
static int a2dp_stop(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
return 0;
}
static snd_pcm_sframes_t a2dp_pointer(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
return a2dp->num;
}
static void sleeptill(struct timeval *t, struct timeval *dt)
{
struct timeval tc,dtc;
struct timezone tz;
int i;
i=gettimeofday(&tc,&tz);
if timercmp(t, &tc, <){ // too late to wait
timeradd(&tc, dt, t);
return;
}
usleep(1); //sinchronize with usleep cycle
i=gettimeofday(&tc,&tz);
timersub(t, &tc, &dtc);
if (dtc.tv_sec==0){ timeradd(t, dt, t);}
else {timeradd(&tc, dt, t);return; } //more than a second to sleep, possibly error
if (dtc.tv_usec<=2000) return; //too late to sleep
usleep(dtc.tv_usec-2000); // wake up somewhere in the middle of 4ms
return;
}
// returns time to wait ie difference between tsend and current time
// if time has come, advances tsend
static int time_to_wait(struct timeval *tsend, struct timeval *dt)
{
struct timeval tc,dtc,t2,dt2;
struct timezone tz;
int i;
dt2.tv_sec=0;
dt2.tv_usec=2000;// middle of 4ms
i=gettimeofday(&tc,&tz);
timeradd(&tc, &dt2, &t2);
if timercmp(tsend, &t2, <){ // time has come
timeradd(tsend, dt, tsend);
if timercmp(tsend, &tc, <) timeradd(&tc, dt, tsend); //if tsend<tc; tsend=tc+dt
return 0;
}
timersub(tsend, &tc, &dtc);
return dtc.tv_usec;
}
// transfers around correct time postions
static snd_pcm_sframes_t a2dp_transfer(snd_pcm_ioplug_t *io,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
char *buf;
int len;
struct media_packet_header packet_header;
struct media_payload_header payload_header;
int codesize,datatoread;
unsigned long sleeptime;
struct timeval dt;
codesize=a2dp->sbc.subbands*a2dp->sbc.blocks*a2dp->sbc.channels*2; // size of data encoded by sbc_encode in one call
datatoread=min(codesize,size*a2dp->frame_bytes); // amount of data to read
buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8;
if(lenbufe<codesize && lenbufe+datatoread<sizeof(bufe)){ // if not enough data in bufe to encode and there is space in bufe
memcpy(bufe+lenbufe,buf,datatoread);// we read data to bufe
lenbufe+=datatoread;
}
else{datatoread=0;}//nothing has been read
if(lenbufe>=codesize && a2dp->len + a2dp->sbc.len < 678){ // if enough data in bufe to encode and not enough frame to fill up mtu: encoding
change_endian(bufe,codesize); // changing the endianness
len = sbc_encode(&(a2dp->sbc), bufe, codesize); //encode
memmove(bufe, bufe + len, lenbufe - len); //shift the bufe
lenbufe-=len;
nbytes+=len;
sleeptime += a2dp->sbc.duration;
if (len <= 0)
return len;
frame_count++;
memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len); // copy encoded frames into a2dp->buf
a2dp->len+=a2dp->sbc.len;
if (a2dp->state == BT_CONNECTED)
a2dp->num += len / a2dp->frame_bytes; //update pointer
a2dp->num %=io->buffer_size;
}
if(a2dp->len + a2dp->sbc.len > 678){ // if packet is formed
dt.tv_usec=1000000*a2dp->sbc.subbands*a2dp->sbc.blocks*frame_count/io->rate; // time interval between transmitions
dt.tv_sec=0;
if(time_to_wait(&tsend, &dt)==0){ // time to send data
memset(&payload_header, 0, sizeof(payload_header)); // fill up the headers
memset(&packet_header, 0, sizeof(packet_header)); //---
payload_header.frame_count=frame_count;
packet_header.v = 2;
packet_header.pt = 1;
packet_header.sequence_number = htons(seq_num);
packet_header.timestamp = htonl(timestamp);
packet_header.ssrc = htonl(1);
timestamp += (a2dp->sbc.blocks + 1)*4 * (a2dp->sbc.subbands + 1)*4;
memcpy(a2dp->buf, &packet_header, sizeof(packet_header)); //copy the headers to buf
memcpy(a2dp->buf + sizeof(packet_header), &payload_header, sizeof(payload_header));//---
write(a2dp->sk,a2dp->buf,a2dp->len); // sending the packet
a2dp->len = sizeof(packet_header)+sizeof(payload_header); //inital position in buf, just after headers
frame_count=0;
sleeptime=0;
seq_num++;
}else{usleep(1);}
}
return datatoread / a2dp->frame_bytes;
}
// also works but sleeps between transfers
static snd_pcm_sframes_t a2dp_transfer2(snd_pcm_ioplug_t *io,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset, snd_pcm_uframes_t size)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
char *buf;
int len;
struct media_packet_header packet_header;
struct media_payload_header payload_header;
int codesize,datatoread;
unsigned long sleeptime;
struct timeval dt;
codesize=a2dp->sbc.subbands*a2dp->sbc.blocks*a2dp->sbc.channels*2;
datatoread=min(codesize,size*a2dp->frame_bytes);
buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8;
if(lenbufe<codesize){
memcpy(bufe+lenbufe,buf,datatoread);
lenbufe+=datatoread;
}
else{datatoread=0;}
if(lenbufe>=codesize){ //enough data to encode
change_endian(bufe,codesize); // changing the endianness
len = sbc_encode(&(a2dp->sbc), bufe, codesize); //encode
memmove(bufe, bufe + len, lenbufe - len); //shift the bufe
lenbufe-=len;
nbytes+=len;
sleeptime += a2dp->sbc.duration;
if (len <= 0)
return len;
if(a2dp->len + a2dp->sbc.len > 678) { // time to prepare and send the packet
dt.tv_sec=0;
dt.tv_usec=1000000*a2dp->sbc.subbands*a2dp->sbc.blocks*frame_count/io->rate;
memset(&payload_header, 0, sizeof(payload_header));
memset(&packet_header, 0, sizeof(packet_header));
payload_header.frame_count=frame_count;
packet_header.v = 2;
packet_header.pt = 1;
packet_header.sequence_number = htons(seq_num);
packet_header.timestamp = htonl(timestamp);
packet_header.ssrc = htonl(1);
timestamp += (a2dp->sbc.blocks + 1)*4 * (a2dp->sbc.subbands + 1)*4;
memcpy(a2dp->buf, &packet_header, sizeof(packet_header));
memcpy(a2dp->buf + sizeof(packet_header), &payload_header, sizeof(payload_header));
sleeptill(&tsend, &dt);
write(a2dp->sk,a2dp->buf,a2dp->len);
a2dp->len = sizeof(packet_header)+sizeof(payload_header);
frame_count=0;
sleeptime=0;
seq_num++;
}
frame_count++;
memcpy(a2dp->buf + a2dp->len, a2dp->sbc.data, a2dp->sbc.len);
a2dp->len+=a2dp->sbc.len;
if (a2dp->state == BT_CONNECTED)
a2dp->num += len / a2dp->frame_bytes;
}
return datatoread / a2dp->frame_bytes;
}
static int a2dp_close(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
a2dp_put(a2dp);
return 0;
}
static int a2dp_params(snd_pcm_ioplug_t *io, snd_pcm_hw_params_t *params)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
unsigned int period_bytes;
DBG("a2dp %p", a2dp);
a2dp->frame_bytes = (snd_pcm_format_physical_width(io->format) * io->channels) / 8;
period_bytes = io->period_size * a2dp->frame_bytes;
DBG("format %s rate %d channels %d", snd_pcm_format_name(io->format),
io->rate, io->channels);
DBG("frame_bytes %d period_bytes %d period_size %ld buffer_size %ld",
a2dp->frame_bytes, period_bytes, io->period_size, io->buffer_size);
return 0;
}
static int a2dp_prepare(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 13;
a2dp->num = 0;
a2dp->sbc.rate = io->rate;
a2dp->sbc.channels = io->channels;
a2dp->sbc.subbands = 8; // safe default
a2dp->sbc.blocks = 16; // safe default
a2dp->sbc.bitpool = 32; // recommended value 53, safe default is 32
return 0;
}
static int a2dp_drain(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
DBG("a2dp %p", a2dp);
a2dp->len = 0;
return 0;
}
static int a2dp_descriptors_count(snd_pcm_ioplug_t *io)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
if (a2dp->state == BT_CLOSED)
return 0;
return 1;
}
static int a2dp_descriptors(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int space)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
if (a2dp->state == BT_CLOSED)
return 0;
if (space < 1) {
SNDERR("Can't fill in descriptors");
return 0;
}
pfds[0].fd = a2dp->sk;
pfds[0].events = POLLOUT;
return 1;
}
static int a2dp_poll(snd_pcm_ioplug_t *io, struct pollfd *pfds,
unsigned int nfds, unsigned short *revents)
{
snd_pcm_a2dp_t *a2dp = io->private_data;
*revents = pfds[0].revents;
if (a2dp->state == BT_CLOSED)
return 0;
if (pfds[0].revents & POLLHUP) {
a2dp->state = BT_CLOSED;
snd_pcm_ioplug_reinit_status(&a2dp->io);
}
return 0;
}
static snd_pcm_ioplug_callback_t a2dp_callback = {
.start = a2dp_start,
.stop = a2dp_stop,
.pointer = a2dp_pointer,
.transfer = a2dp_transfer,
.close = a2dp_close,
.hw_params = a2dp_params,
.prepare = a2dp_prepare,
.drain = a2dp_drain,
.poll_descriptors_count = a2dp_descriptors_count,
.poll_descriptors = a2dp_descriptors,
.poll_revents = a2dp_poll,
};
static int a2dp_connect(snd_pcm_a2dp_t *a2dp)
{
struct sockaddr_rc addr;
socklen_t len;
int sk;
int control_sk = -1;
DBG("a2dp %p", a2dp);
if(a2dp->use_rfcomm) {
sk = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
if (sk < 0)
return -errno;
memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, &a2dp->src);
if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sk);
return -errno;
}
memset(&addr, 0, sizeof(addr));
addr.rc_family = AF_BLUETOOTH;
bacpy(&addr.rc_bdaddr, &a2dp->dst);
addr.rc_channel = 1;
if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(sk);
return -errno;
}
memset(&addr, 0, sizeof(addr));
len = sizeof(addr);
if (getsockname(sk, (struct sockaddr *) &addr, &len) < 0) {
close(sk);
return -errno;
}
bacpy(&a2dp->src, &addr.rc_bdaddr);
fcntl(sk, F_SETFL, fcntl(sk, F_GETFL) | O_NONBLOCK);
} else {
sk = connect_stream(&a2dp->src, &a2dp->dst, &control_sk, &a2dp->sbc);
}
a2dp->sk = sk;
a2dp->control_sk = control_sk;
return 0;
}
static int a2dp_constraint(snd_pcm_a2dp_t *a2dp)
{
snd_pcm_ioplug_t *io = &a2dp->io;
snd_pcm_access_t access_list[] = {
SND_PCM_ACCESS_RW_INTERLEAVED,
SND_PCM_ACCESS_MMAP_INTERLEAVED,
};
unsigned int format[2], channel[2], rate[2];
int err;
DBG("a2dp %p", a2dp);
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, 2, access_list);
if (err < 0)
return err;
format[0] = SND_PCM_FORMAT_S16_LE;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, 1, format);
if (err < 0)
return err;
channel[0] = 1;
channel[1] = 2;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_CHANNELS, 2, channel);
if (err < 0)
return err;
rate[0] = 44100;
rate[1] = 48000;
err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_RATE, 2, rate);
if (err < 0)
return err;
err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 8192, 8192);
if (err < 0)
return err;
err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 2, 2);
if (err < 0)
return err;
return 0;
}
#define MAX_CONNECTIONS 10
static snd_pcm_a2dp_t *connections[MAX_CONNECTIONS];
static snd_timer_t *timer = NULL;
static volatile sig_atomic_t __locked = 0;
static inline void a2dp_lock(void)
{
while (__locked)
usleep(100);
__locked = 1;
}
static inline void a2dp_unlock(void)
{
__locked = 0;
}
static inline snd_pcm_a2dp_t *a2dp_alloc(void)
{
snd_pcm_a2dp_t *a2dp;
DBG("init");
a2dp = malloc(sizeof(*a2dp));
if (!a2dp)
return NULL;
memset(a2dp, 0, sizeof(*a2dp));
a2dp->refcnt = 1;
a2dp->state = BT_OPEN;
sbc_init(&a2dp->sbc, SBC_NULL);
return a2dp;
}
static inline void a2dp_free(snd_pcm_a2dp_t *a2dp)
{
if (a2dp->sk > fileno(stderr))
close(a2dp->sk);
sbc_finish(&a2dp->sbc);
free(a2dp);
}
static void a2dp_timer(snd_async_handler_t *async)
{
snd_timer_t *handle = snd_async_handler_get_timer(async);
snd_timer_read_t tr;
int i, ticks = 0;
while (snd_timer_read(handle, &tr, sizeof(tr)) == sizeof(tr))
ticks += tr.ticks;
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++) {
snd_pcm_a2dp_t *a2dp = connections[i];
if (a2dp && a2dp->refcnt <= 0) {
a2dp->timeout = ((a2dp->timeout * 1000) - ticks) / 1000;
if (a2dp->timeout <= 0) {
connections[i] = NULL;
a2dp_free(a2dp);
}
}
}
a2dp_unlock();
}
static void a2dp_init(void)
{
snd_async_handler_t *async;
snd_timer_info_t *info;
snd_timer_params_t *params;
long resolution;
char timername[64];
int err, i;
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++)
connections[i] = NULL;
a2dp_unlock();
snd_timer_info_alloca(&info);
snd_timer_params_alloca(¶ms);
sprintf(timername, "hw:CLASS=%i,SCLASS=%i,CARD=%i,DEV=%i,SUBDEV=%i",
SND_TIMER_CLASS_GLOBAL, SND_TIMER_CLASS_NONE, 0,
SND_TIMER_GLOBAL_SYSTEM, 0);
err = snd_timer_open(&timer, timername, SND_TIMER_OPEN_NONBLOCK);
if (err < 0) {
SNDERR("Can't open global timer");
return;
}
err = snd_timer_info(timer, info);
if (err < 0) {
SNDERR("Can't get global timer info");
return;
}
snd_timer_params_set_auto_start(params, 1);
resolution = snd_timer_info_get_resolution(info);
snd_timer_params_set_ticks(params, 1000000000 / resolution);
if (snd_timer_params_get_ticks(params) < 1)
snd_timer_params_set_ticks(params, 1);
err = snd_timer_params(timer, params);
if (err < 0) {
SNDERR("Can't set global timer parameters");
snd_timer_close(timer);
return;
}
err = snd_async_add_timer_handler(&async, timer, a2dp_timer, NULL);
if (err < 0) {
SNDERR("Can't create global async callback");
snd_timer_close(timer);
return;
}
err = snd_timer_start(timer);
}
static void a2dp_exit(void)
{
int err, i;
err = snd_timer_stop(timer);
err = snd_timer_close(timer);
a2dp_lock();
for (i = 0; i < MAX_CONNECTIONS; i++) {
snd_pcm_a2dp_t *a2dp = connections[i];
if (a2dp) {
connections[i] = NULL;
a2dp_free(a2dp);
}
}
a2dp_unlock();
}
SND_PCM_PLUGIN_DEFINE_FUNC(a2dp)
{
snd_pcm_a2dp_t *a2dp = NULL;
snd_config_iterator_t i, next;
bdaddr_t src, dst;
int err, n, pos = -1, use_rfcomm = 0;
DBG("name %s mode %d", name, mode);
bacpy(&src, BDADDR_ANY);
bacpy(&dst, BDADDR_ANY);
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id, *addr;
if (snd_config_get_id(n, &id) < 0)
continue;
if (!strcmp(id, "comment") || !strcmp(id, "type"))
continue;
if (!strcmp(id, "bdaddr") || !strcmp(id, "dst")) {
if (snd_config_get_string(n, &addr) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
DBG("bdaddr/dest is %s", addr);
str2ba(addr, &dst);
continue;
}
if (!strcmp(id, "local") || !strcmp(id, "src")) {
if (snd_config_get_string(n, &addr) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
str2ba(addr, &src);
continue;
}
if (!strcmp(id, "use_rfcomm")) {
if ((err = snd_config_get_bool(n)) < 0) {
SNDERR("The field use_rfcomm must be a boolean type");
return err;
}
use_rfcomm = err;
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
a2dp_lock();
for (n = 0; n < MAX_CONNECTIONS; n++) {
if (connections[n]) {
if (!bacmp(&connections[n]->dst, &dst) &&
(!bacmp(&connections[n]->src, &src) ||
!bacmp(&src, BDADDR_ANY))) {
a2dp = connections[n];
a2dp_get(a2dp);
break;
}
} else if (pos < 0)
pos = n;
}
if (!a2dp) {
if (pos < 0) {
SNDERR("Too many connections");
return -ENOMEM;
}
a2dp = a2dp_alloc();
if (!a2dp) {
SNDERR("Can't allocate");
return -ENOMEM;
}
connections[pos] = a2dp;
a2dp->state = BT_CONNECT;
bacpy(&a2dp->src, &src);
bacpy(&a2dp->dst, &dst);
a2dp->use_rfcomm = use_rfcomm;
}
a2dp_unlock();
if (a2dp->state != BT_CONNECTED) {
err = a2dp_connect(a2dp);
if (err < 0) {
SNDERR("Can't connect");
goto error;
}
a2dp->state = BT_CONNECTED;
}
a2dp->io.version = SND_PCM_IOPLUG_VERSION;
a2dp->io.name = "Bluetooth Advanced Audio Distribution";
a2dp->io.mmap_rw = 0;
a2dp->io.callback = &a2dp_callback;
a2dp->io.private_data = a2dp;
err = snd_pcm_ioplug_create(&a2dp->io, name, stream, mode);
if (err < 0)
goto error;
err = a2dp_constraint(a2dp);
if (err < 0) {
snd_pcm_ioplug_delete(&a2dp->io);
goto error;
}
*pcmp = a2dp->io.pcm;
return 0;
error:
a2dp_put(a2dp);
return err;
}
SND_PCM_PLUGIN_SYMBOL(a2dp);
^ permalink raw reply [flat|nested] 8+ messages in thread