?? a2dpd.py
字號:
#! /usr/bin/env python"""Simple multi-client A2DP server* Copyright (C) 2006 Sergei Krivov <krivov@yahoo.com>** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program 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 General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, write to the Free Software* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.""""""be sure to have installed:libsbc (http://sbc.sf.net); check for /usr/lib/libsbc.so.pybluez (http://org.csail.mit.edu/pybluez/)put this in your .asoundrc filepcm.a2dpcopy { type plug slave { pcm "tee:default,'/tmp/a2dpipe', raw" rate 48000 channels 2 }}pcm.a2dp { type plug slave { pcm "file:'/tmp/a2dpipe', raw" rate 48000 channels 2 }}make pipe by: mkfifo /tmp/a2dpipestart by python a2dpd.pystop by killing the processselect alsa output pcm device a2dpcopyfor mplayer: -ao alsa:device=a2dpcopyIn such setup the sound will be present in the alsa default deviceand be copied to the headphones. The sound timing is driven by alsa,if headphones are disconnected or out of reach the server will justdrop packets, and alsa device will notice nothing. The sound betweenalsa device and the headphones is out of sync by constant delayof about 800 ms.To send sound just to headphones select alsa output pcm device a2dpand set ismaster=1 close to the end of this file.If the server uses alsa timing (i.e., ismaster==0), some dropoutscan be experienced due to (if) the timing differencesin alsa and headphones.A2DP server works with many ACP (headphones) simultaneously if thedefault link policy is set up as master, i.e., setlm master in /etc/bluetooth/hcid.conf To initiate connection with the server press PLAY button on headphones.To stop connection just switch off the headphones."""import structimport ctypesimport timeimport threadingimport avdtpimport bluetoothimport arrayimport gcimport osimport avrcp_lircdimport pyDaemonimport ConfigParser##### useful functions ################################################################################class timer: def __init__(self): self.time=time.time() self.tsend=0 def dt(self): return (time.time()-self.time)*1000000 def start(self): self.time=time.time() def time_to_wait(self,dt): tc=time.time()*1000000 if self.tsend<tc+2000: # time has come, 2000 to be in the middle of 4 ms resolution self.tsend+=dt if tc>self.tsend+dt*20:# current time > time to send next packet print "OUT OF SYNC tc-tsend=%g>0" %(tc-self.tsend) self.tsend=tc+dt return 0# time has come, nothing to wait else: return self.tsend-tc ### A2DP server code ###############################pipeclosed=0class reading_thread ( threading.Thread ): def __init__ ( self, pipename, ldata): self.pipename=pipename self.ldata=ldata threading.Thread.__init__ ( self ) def run (self): global ldata_lock global ismaster mdata=10 mread=4096 pipe=open(self.pipename,'rb',0) pipeclosed=0 while True: data=pipe.read(mread) if len(data)==0: self.ldata.append(data) # to indicate the end of stream if deb: print "closed pipe" pipe.close() pipe=open(self.pipename,'rb',0) if deb: print "opened pipe" pipeclosed=0 while ismaster and len(self.ldata)>mdata:time.sleep(0.0001) if len(self.ldata)<=mdata: ldata_lock.acquire() self.ldata.append(data) ldata_lock.release() def update_minmax(var,val): if var==None:return val,val else: return min(var[0],val),max(var[1],val)class encoding_thread( threading.Thread ): def __init__ ( self, ldata, lpackets,codec,commands=[]): self.ldata=ldata self.lpackets=lpackets self.codec=codec self.commands=commands threading.Thread.__init__ ( self ) def run (self): global ldata_lock global lpackets_lock packet_header_size=13 min_encoding_size=512 mtu=672 mpackets=5 seq_number=0 frame_count=0 timestamp=0 buf=(ctypes.c_ubyte*mtu)() lenbuf=0 data="" elapsedtime=0 mmdata=None mmpackets=None timer1=timer() igc=0 gc.disable() pipeclosed=0 while True: igc+=1 if (igc>100 and len(lpackets)==0) or igc>150: gc.collect() igc=0 time.sleep(0.00001) # some sleep if 'quit' in commands: break mmdata=update_minmax(mmdata,len(ldata)) mmpackets=update_minmax(mmpackets,len(lpackets)) if len(ldata)>0 and len(lpackets)<mpackets: ldata_lock.acquire() rdata=ldata[0] del ldata[0] ldata_lock.release() if len(rdata)==0:pipeclosed=1 adata=array.array('H')#change endiannes adata.fromstring(rdata) adata.byteswap() rdata=adata.tostring() data+=rdata# while len(data)>=min_encoding_size: #encode dry=pipeclosed and len(data)==0 while len(data)>0 or dry: #encode dry=pipeclosed and len(data)==0 and lenbuf>0 l,pendata,lendata,duration=self.codec.encode(data) data=data[l:] if lenbuf+lendata+packet_header_size>mtu or dry: #enough for packet packetdata=(ctypes.c_ubyte*lenbuf)() ctypes.memmove(packetdata,buf,lenbuf) packet=avdtp.media_packet(packetdata,int(timestamp),frame_count,seq_number) par=self.codec.par duration2=1000000.*par.subbands*par.block_length/par.frequency duration2=duration2*frame_count lpackets_lock.acquire() self.lpackets.append((duration2,packet)) lpackets_lock.release() timestamp=elapsedtime # for the following packet seq_number=(seq_number+1) & 0xffff lenbuf=0 frame_count=0 if seq_number%1024==0: if deb: print "%i packets,dt per packet=%g<>%g ms,ldata=[%i:%i],lpackets=[%i:%i]"\ %(1024,timer1.dt()/1024,duration2,mmdata[0],mmdata[1],mmpackets[0],mmpackets[1]) timer1.start() mmdata=None mmpackets=None ctypes.memmove(ctypes.addressof(buf)+lenbuf,pendata,lendata) lenbuf+=lendata frame_count+=1 elapsedtime+=duration if dry and len(data)==0 and lenbuf==0:pipeclosed=0class sending_thread(threading.Thread): def __init__ ( self, lpackets,lacp,commands=[]): self.lpackets=lpackets self.lacp=lacp self.commands=commands threading.Thread.__init__ ( self ) def run (self): global lpackets_lock timer0=timer() while True: while len(self.lpackets)==0 or len(self.lacp)==0: time.sleep(0.0001) lpackets_lock.acquire() dt,packet=self.lpackets[0] del self.lpackets[0] lpackets_lock.release() while timer0.time_to_wait(dt):time.sleep(0.001) for acp in self.lacp: try: avdtp.send_packet(acp.stream,packet) except bluetooth.BluetoothError: # close and remove dead ACP if deb: print "stream error ",acp.addr acp.stream.close() acp.sock.close() if deb: print "close stream ",acp.addr try: lacp.remove(acp) except: pass if deb: print "remove stream, number of ACP ",len(lacp) class acp_listen_thread(threading.Thread): def __init__ ( self, acp): self.acp=acp threading.Thread.__init__ ( self ) def run(self): while True: try: resp=avdtp.receive_response(self.acp.sock,avdtp.message_single) if resp.header.signal_id==avdtp.AVDTP_CLOSE: if deb: print "received AVDTP_CLOSE ", self.acp.addr break avdtp.print_fields(resp) except bluetooth.BluetoothError: # close and remove dead ACP break if deb: print "removing ACP ", self.acp.addr self.acp.stream.close() self.acp.sock.close() try: self.acp.lacp.remove(self.acp) except: pass if deb:print 'number of ACP', len(lacp) class acp: # container class to keep information about ACP def __init__(self,addr,codecs,lacp): self.addr=addr self.lacp=lacp self.stream,self.sock,self.seid,self.codec=connect(addr,codecs) self.listen_thread=acp_listen_thread(self) self.listen_thread.start() def connect(addr,codecs): """connects to a2dp sink on address addr """ sock=avdtp.avdtp_connect(addr,25) # connect to the ACP side lsep=avdtp.avdtp_discover(sock) # discover set of codecs on ACP side seid,codec=avdtp.avdtp_set_configuration(sock,lsep,codecs) # select and set codec parameters stream=avdtp.avdtp_open(addr,sock,seid) # open audio stream avdtp.avdtp_start(sock,seid) #start audio stream return stream,sock,seid,codecdef avdtp_discover_abort(sock): cmd=avdtp.receive_response(sock,avdtp.message_single) cmd.header.message_type=avdtp.MESSAGE_TYPE_COMMAND cmd.header.signal_id=avdtp.AVDTP_ABORT avdtp.send_packet(sock,cmd)def advertise_a2dp(): """advertise a2dp source""" server_sock=bluetooth.BluetoothSocket( bluetooth.L2CAP ) port=25 server_sock.bind(("",port)) server_sock.listen(1) if deb:print "listening on port %d" % port uuid = "110a" profile=[bluetooth.ADVANCED_AUDIO_PROFILE] classes=[bluetooth.AUDIO_SOURCE_CLASS,] bluetooth.advertise_service( server_sock, "Audio Source", service_id = "", service_classes = [], \ profiles = [], provider = "", description = "") client_sock,(address,port) = server_sock.accept() if deb: print "Accepted connection from ",address return client_sock,address,portdef set_realtime(): libc=ctypes.CDLL('libc.so.6') SCHED_FIFO=1 schp=ctypes.c_int(libc.sched_get_priority_max(SCHED_FIFO)) if libc.sched_setscheduler(0,SCHED_FIFO,ctypes.byref(schp)): print "can not set realtime privs, do you have root privs?" return 0 else: print "successfully set realtime privs" return 1 if __name__=="__main__": pyDaemon.createDaemon() cfg=ConfigParser.ConfigParser() cfg.read('/etc/a2dpd.conf') def getval(var,default): if cfg.has_option('a2dpd',var): return eval(cfg.get('a2dpd',var)) else: return default ismaster=getval('ismaster',0) deb=getval('deb',0) bitpool=getval('bitpool',32) freq=getval('freq',48000) myaddr=getval('myaddr',[]) RT=set_realtime() avdtp.deb=deb lacp=[] commands=[] ldata=[] lpackets=[] ldata_lock=threading.Lock() lpackets_lock=threading.Lock() sbc_codec=avdtp.sbc(freq,2,bitpool=bitpool) # initialize sbc codec codecs={avdtp.SBC_MEDIA_CODEC_TYPE:sbc_codec} # list the available codecs avrcp_lircd.deb=deb lircd=avrcp_lircd.lircd_emu() avrcp=avrcp_lircd.avrcp_server(lircd.send) avrcp.start() reader=reading_thread('/tmp/a2dpipe',ldata) reader.start() encoder=encoding_thread(ldata,lpackets,sbc_codec,commands) encoder.start() for addr in myaddr: try: lacp.append(acp(addr,codecs,lacp)) except bluetooth.BluetoothError,what: if deb:print 'BluetoothError: %s ' %(what) sender=sending_thread(lpackets,lacp,commands) sender.start() while True: # dynamically manages new ACP# to initiate connection press PLAY button on the headphones sock2,addr,port=advertise_a2dp() avdtp_discover_abort(sock2) sock2.close() try: lacp.append(acp(addr,codecs,lacp)) except bluetooth.BluetoothError,what: if deb: print 'BluetoothError: %s, try again', what if deb: print 'number of ACP', len(lacp)
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -