Return-Path: Message-ID: <20060427164150.14607.qmail@web31011.mail.mud.yahoo.com> From: Sergey Krivov Subject: Re: [Bluez-devel] a2dp alsa plugin "better" To: bluez-devel@lists.sourceforge.net In-Reply-To: <4450C8D8.1030402@xmission.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="0-1716792089-1146156110=:14508" Sender: bluez-devel-admin@lists.sourceforge.net Errors-To: bluez-devel-admin@lists.sourceforge.net Reply-To: bluez-devel@lists.sourceforge.net List-Unsubscribe: , List-Id: BlueZ development List-Post: List-Help: List-Subscribe: , List-Archive: Date: Thu, 27 Apr 2006 09:41:50 -0700 (PDT) --0-1716792089-1146156110=:14508 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Content-Id: Content-Disposition: inline Brad, --- Brad Midgley 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 --0-1716792089-1146156110=:14508 Content-Type: text/x-csrc; name="pcm_a2dp.c" Content-Description: 1802492780-pcm_a2dp.c Content-Disposition: inline; filename="pcm_a2dp.c" /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2005 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 tsendprivate_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 && 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){ //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); --0-1716792089-1146156110=:14508-- ------------------------------------------------------- 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