* [Bluez-devel] pcm_a2dp_works
@ 2006-04-18 16:26 Sergey Krivov
2006-04-24 16:45 ` Brad Midgley
2006-04-26 6:19 ` Brad Midgley
0 siblings, 2 replies; 5+ messages in thread
From: Sergey Krivov @ 2006-04-18 16:26 UTC (permalink / raw)
To: bluez-devel
[-- Attachment #1: Type: text/plain, Size: 722 bytes --]
Hi,
i have made the pcm_a2dp plugin to work. mainly it was
cut and past from a2play program. now i can select
headphone as my alsa output device. XMMS and mplayer
both work.
however, when XMMS finishes a song it does not start
to play the next one. Also, video in mplayer with the
sound through headphones does not work. It goes out of
sync and says something about buggy driver. I guess
the alsa part of the pcm_a2dp should also be fixed.
Any suggestion for the similar plugin code? So the cut
and past approach will work again?
I attach the modified pcm_a2dp.c
__________________________________________________
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: 30805 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);}
usleep(dtc.tv_usec-2000); // somewhere in the middle of 4ms
return;
}
// returns time to wait
static int time_to_wait(struct timeval *t, struct timeval *dt)
{
struct timeval tc,dtc,t2,dt2;
struct timezone tz;
int i;
dt2.tv_sec=0;
dt2.tv_usec=2000;
i=gettimeofday(&tc,&tz);
timeradd(&tc, &dt2, &t2);
if timercmp(t, &t2, <){ // time has come
timeradd(t, dt, t);
if timercmp(t, &tc, <) timeradd(&tc, dt, t); //large interval
return 0;
}
timersub(t, &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;
datatoread=min(codesize,size*a2dp->frame_bytes);
buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8;
if(lenbufe<codesize && lenbufe+datatoread<sizeof(bufe)){
memcpy(bufe+lenbufe,buf,datatoread);
lenbufe+=datatoread;
}
else{datatoread=0;}
if(lenbufe>=codesize && a2dp->len + a2dp->sbc.len < 678){ //coding
change_endian(bufe,codesize); // changing the endianness
len = sbc_encode(&(a2dp->sbc), bufe, codesize); //encode
memmove(bufe, bufe + codesize, 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);
a2dp->len+=a2dp->sbc.len;
if (a2dp->state == BT_CONNECTED)
a2dp->num += len / a2dp->frame_bytes;
}
if(a2dp->len + a2dp->sbc.len > 678){
dt.tv_usec=1000000*a2dp->sbc.subbands*a2dp->sbc.blocks*frame_count/io->rate;
dt.tv_sec=0;
if(time_to_wait(&tsend, &dt)==0){ // time to clean the buffer
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));
write(a2dp->sk,a2dp->buf,a2dp->len);
a2dp->len = sizeof(packet_header)+sizeof(payload_header);
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;
}
if(lenbufe>=codesize){ //coding
change_endian(bufe,codesize); // changing the endianness
len = sbc_encode(&(a2dp->sbc), bufe, codesize); //encode
memmove(bufe, bufe + codesize, 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 clean the buffer
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] 5+ messages in thread* Re: [Bluez-devel] pcm_a2dp_works
2006-04-18 16:26 [Bluez-devel] pcm_a2dp_works Sergey Krivov
@ 2006-04-24 16:45 ` Brad Midgley
2006-04-26 6:19 ` Brad Midgley
1 sibling, 0 replies; 5+ messages in thread
From: Brad Midgley @ 2006-04-24 16:45 UTC (permalink / raw)
To: bluez-devel
Sergey
> i have made the pcm_a2dp plugin to work. mainly it was
> cut and past from a2play program. now i can select
> headphone as my alsa output device. XMMS and mplayer
> both work.
I am very excited to see you have run with this and got it working. I
have been out of town and unable to try it but I applied it anyway (what
we had before didn't work at all... :)
> however, when XMMS finishes a song it does not start
> to play the next one.
There may be some more things we need to fix up in the plugin like
capabilities negotiation. Also, Marcel had worked on connection caching
so the connection wouldn't drop and have to be reestablished between
songs on xmms. I'm not sure if the code we have now does that right.
I'll be back tonight and be able to try things. Thanks tons.
Brad
-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [Bluez-devel] pcm_a2dp_works
2006-04-18 16:26 [Bluez-devel] pcm_a2dp_works Sergey Krivov
2006-04-24 16:45 ` Brad Midgley
@ 2006-04-26 6:19 ` Brad Midgley
2006-04-26 8:27 ` Brad Midgley
2006-04-26 10:51 ` Sergey Krivov
1 sibling, 2 replies; 5+ messages in thread
From: Brad Midgley @ 2006-04-26 6:19 UTC (permalink / raw)
To: bluez-devel
Sergey
> i have made the pcm_a2dp plugin to work. mainly it was
> cut and past from a2play program. now i can select
> headphone as my alsa output device. XMMS and mplayer
> both work.
I made some changes to what you gave us. I made it use the system's
libsbc since the sbc inside the project is not built properly as a
shared lib (install libsbc from cvs in the
http://sourceforge.net/projects/sbc project)
I had to recode to retry some connections since alsa was interrupting
system calls.
However, I'm still finding that
aplay -B 1000000 -D pcm.headphone ~/sound.wav
will segfault. It's when the memmove at line 706 is being called with a
0 for the length to copy.
fwiw, my sound.wav is available at bluetooth-alsa.sf.net/sound.wav
Brad
-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [Bluez-devel] pcm_a2dp_works
2006-04-26 6:19 ` Brad Midgley
@ 2006-04-26 8:27 ` Brad Midgley
2006-04-26 10:51 ` Sergey Krivov
1 sibling, 0 replies; 5+ messages in thread
From: Brad Midgley @ 2006-04-26 8:27 UTC (permalink / raw)
To: bluez-devel
guys
woooooo... after more tweaks the alsa plugin is finally working (mostly)
for me. I've only played through xmms and aplay. The strangeness now is
the xmms display looks like it's jittering around and it gets "stuck" at
the end of a song, sometimes crashing with "alsa mixer timed out."
If it doesn't crash, I can click the next-track button or drag the
slider to the end of the song to have it start the next one.
i've elaborated in the alsa-plugins/BUILD document. i'm requiring the
external libsbc build now since the version in the project isn't
properly built for use by shared objects and the codec is going to be
maintained externally anyway.
brad
-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [Bluez-devel] pcm_a2dp_works
2006-04-26 6:19 ` Brad Midgley
2006-04-26 8:27 ` Brad Midgley
@ 2006-04-26 10:51 ` Sergey Krivov
1 sibling, 0 replies; 5+ messages in thread
From: Sergey Krivov @ 2006-04-26 10:51 UTC (permalink / raw)
To: bluez-devel
Hi Brad,
I have not had any problems with the sound.wav file.
i guess it is system specific and i am lucky. I am on
FC5 with HP headphones.
Nice that you made it work too, i have the same
simptomps with xmms, however most annoing is that i
can not watch movies with mplayer.
Sergey
__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com
-------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Bluez-devel mailing list
Bluez-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/bluez-devel
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2006-04-26 10:51 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-04-18 16:26 [Bluez-devel] pcm_a2dp_works Sergey Krivov
2006-04-24 16:45 ` Brad Midgley
2006-04-26 6:19 ` Brad Midgley
2006-04-26 8:27 ` Brad Midgley
2006-04-26 10:51 ` Sergey Krivov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.