Return-Path: Message-ID: <20060418162647.2732.qmail@web31011.mail.mud.yahoo.com> From: Sergey Krivov To: bluez-devel@lists.sourceforge.net MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="0-78864107-1145377607=:537" Subject: [Bluez-devel] pcm_a2dp_works 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: Tue, 18 Apr 2006 09:26:47 -0700 (PDT) --0-78864107-1145377607=:537 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Content-Id: Content-Disposition: inline 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 --0-78864107-1145377607=:537 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);} 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 && 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){ //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); --0-78864107-1145377607=:537-- ------------------------------------------------------- This SF.Net email is sponsored by xPML, a groundbreaking scripting language that extends applications into web and mobile media. Attend the live webcast and join the prime developer group breaking into this new coding territory! http://sel.as-us.falkag.net/sel?cmd=lnk&kid=110944&bid=241720&dat=121642 _______________________________________________ Bluez-devel mailing list Bluez-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/bluez-devel