eclair snapshot
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d5b85d3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+*.o
+*.a
+*.lo
+*.la
+*.so
+.deps
+.libs
+Makefile
+Makefile.in
+aclocal.m4
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+compile
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+autom4te.cache
+
+ylwrap
+bluez.pc
+include/bluetooth
+src/bluetoothd
+audio/telephony.c
+scripts/bluetooth.rules
+
+sbc/sbcdec
+sbc/sbcenc
+sbc/sbcinfo
+sbc/sbctester
+
+tools/avctrl
+tools/avinfo
+tools/bccmd
+tools/ciptool
+tools/dfubabel
+tools/dfutool
+tools/hciattach
+tools/hciconfig
+tools/hcieventmask
+tools/hcisecfilter
+tools/hcitool
+tools/hid2hci
+tools/l2ping
+tools/ppporc
+tools/sdptool
+audio/ipctest
+cups/bluetooth
+test/agent
+test/bdaddr
+test/hciemu
+test/attest
+test/hstest
+test/avtest
+test/l2test
+test/rctest
+test/scotest
+test/sdptest
+test/lmptest
+test/btiotest
+rfcomm/rfcomm
+compat/dund
+compat/hidd
+compat/pand
+common/test_textfile
+
+doc/*.bak
+doc/*.stamp
+doc/bluez.*
+doc/bluez-*.txt
+doc/*.sgml
+doc/version.xml
+doc/xml
+doc/html
+src/bluetoothd.8
+src/hcid.conf.5
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..cd0189e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,46 @@
+Maxim Krasnyansky <maxk@qualcomm.com>
+Marcel Holtmann <marcel@holtmann.org>
+Stephen Crane <steve.crane@rococosoft.com>
+Jean Tourrilhes <jt@hpl.hp.com>
+Jan Beutel <j.beutel@ieee.org>
+Ilguiz Latypov <ilatypov@superbt.com>
+Thomas Moser <thomas.moser@tmoser.ch>
+Nils Faerber <nils@kernelconcepts.de>
+Martin Leopold <martin@leopold.dk>
+Wolfgang Heidrich <wolfgang.heidrich@esk.fhg.de>
+Fabrizio Gennari <fabrizio.gennari@philips.com>
+Brad Midgley <bmidgley@xmission.com>
+Henryk Ploetz <henryk@ploetzli.ch>
+Philip Blundell <pb@nexus.co.uk>
+Johan Hedberg <johan.hedberg@nokia.com>
+Claudio Takahasi <claudio.takahasi@indt.org.br>
+Eduardo Rocha <eduardo.rocha@indt.org.br>
+Denis Kenzior <denis.kenzior@trolltech.com>
+Frederic Dalleau <frederic.dalleau@access-company.com>
+Frederic Danis <frederic.danis@access-company.com>
+Luiz Augusto von Dentz <luiz.dentz@gmail.com>
+Fabien Chevalier <fabchevalier@free.fr>
+Ohad Ben-Cohen <ohad@bencohen.org>
+Daniel Gollub <dgollub@suse.de>
+Tom Patzig <tpatzig@suse.de>
+Kai Vehmanen <kai.vehmanen@nokia.com>
+Vinicius Gomes <vinicius.gomes@openbossa.org>
+Alok Barsode <alok.barsode@azingo.com>
+Bastien Nocera <hadess@hadess.net>
+Albert Huang <albert@csail.mit.edu>
+Glenn Durfee <gdurfee@google.com>
+David Woodhouse <david.woodhouse@intel.com>
+Christian Hoene <hoene@uni-tuebingen.de>
+Pekka Pessi <pekka.pessi@nokia.com>
+Siarhei Siamashka <siarhei.siamashka@nokia.com>
+Nick Pelly <npelly@google.com>
+Lennart Poettering <lennart@poettering.net>
+Gustavo F. Padovan <gustavo@las.ic.unicamp.br>
+Marc-Andre Lureau <marc-andre.lureau@nokia.com>
+Bea Lam <bea.lam@nokia.com>
+Zygo Blaxell <zygo.blaxell@xandros.com>
+Forrest Zhao <forrest.zhao@intel.com>
+Scott Talbot <psyc@stalbot.com>
+Ilya Rubtsov <lusyaru@gmail.com>
+Mario Limonciello <mario_limonciello@dell.com>
+Filippo Giunchedi <filippo@esaurito.net>
diff --git a/Android.mk b/Android.mk
new file mode 100755
index 0000000..456efdd
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,20 @@
+#
+# Copyright (C) 2008 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+ifneq ($(TARGET_SIMULATOR),true)
+ifeq ($(BOARD_HAVE_BLUETOOTH),true)
+  include $(all-subdir-makefiles)
+endif
+endif
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..6d45519
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/COPYING.LIB b/COPYING.LIB
new file mode 100644
index 0000000..1f7c8cc
--- /dev/null
+++ b/COPYING.LIB
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e7066b8
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,1230 @@
+ver 4.47:
+	Add support for RFKILL unblock handling.
+	Add support for serial proxy configurations.
+	Add support for caching service class updates.
+	Fix issues with updating SDP service records.
+	Fix usage of limited discoverable mode.
+	Remove deprecated methods and signals for AudioSource.
+
+ver 4.46:
+	Add support for A2DP sink role.
+	Fix clearing svc_cache before the adapter is up.
+	Fix various pointer after free usages.
+	Fix various memory leaks.
+
+ver 4.45:
+	Fix UDEV_DATADIR fallback if pkg-config fails.
+	Fix adapter cleanup and setup prototypes.
+	Fix double-free with out-of-range devices.
+	Fix inband ring setting to be per-headset.
+	Fix handling of Maemo CSD startup.
+
+ver 4.44:
+	Add some missing manual pages.
+	Fix missing number prefix when installing udev rules.
+	Fix program prefix used in Bluetooth udev rules.
+	Fix three-way calling indicator order.
+	Fix downgrade/upgrade of callheld indicator.
+	Fix +CIEV sending when indicator value changes.
+	Fix signal handling for Maemo telephony driver.
+	Fix parsing issues with messages from Maemo CSD.
+	Fix issue with duplicate active calls.
+
+ver 4.43:
+	Add support for udev based on-demand startup.
+	Fix verbose error reporting of CUPS backend.
+	Fix various string length issues.
+	Fix issues with Maemo telephony driver.
+	Fix another device setup and temporary flag issue.
+	Fix and update example agent implementation.
+
+ver 4.42:
+	Add TI WL1271 to Texas Instruments chip list.
+	Add special udev mode to bluetoothd.
+	Fix regression when there is no agent registered.
+	Fix error return when bonding socket hang up.
+	Fix SCO server socket for HFP handsfree role.
+	Fix shutdown on SCO socket before closing.
+	Fix shutdown on A2DP audio stream channel before closing.
+	Fix issue with asserting on AVDTP reference count bugs.
+	Fix authorization denied issue with certain headsets.
+	Fix AVRCP UNITINFO and SUBUNIT INFO responses.
+	Fix discovery cancel issues in case SDP discovery fails.
+
+ver 4.41:
+	Fix pairing even if the ACL gets dropped before successful SDP.
+	Fix regression which caused device to be removed after pairing.
+	Fix HSP record fetching when remote device doesn't support it.
+	Fix SDP discovery canceling when clearing hs->pending.
+	Fix headset never connecting on the first attempt.
+	Fix headset state tracking if bt_search_service() fails.
+	Fix maximum headset connection count check.
+	Fix AVDTP Discover timeout handling.
+	Fix also UI_SET_KEYBIT for the new pause and play key codes.
+
+ver 4.40:
+	Add telephony driver for oFono telephony stack.
+	Add support for Dell specific HID proxy switching.
+	Add support for running hid2hci from udev.
+	Add mapping for AVRCP Play and Pause to dedicated key codes.
+	Fix AVRCP keycodes to better match existing X keymap support.
+	Fix various quoting issues within telephony support.
+	Fix memory allocation issue when generating PDUs for SDP.
+	Fix race condition on device removal.
+	Fix non-cancelable issue with CreateDevice method.
+	Fix non-working CancelDiscovery method call.
+
+ver 4.39:
+	Add workaround for dealing with unknown inquiry complete.
+	Fix discovering when using software scheduler.
+	Fix wrong NoInputNoOutput IO capability string.
+	Fix race condition with agent during pairing.
+	Fix agent cancellation for security mode 3 acceptor failure.
+	Fix temporary flag removal when device creation fails.
+	Fix hciattach to use ppoll instead of poll.
+	Fix service class update when adapter is down.
+	Fix service classes race condition during startup.
+	Fix release of audio client before freeing the device.
+
+ver 4.38:
+	Add support for builtin plugins.
+	Add framework for adapter operations.
+	Add constants for Enhanced Retransmission modes.
+	Fix HCI socket leak in device_remove_bonding.
+	Fix various format string issues.
+	Fix crashes with various free functions.
+	Fix issues with Headset and A2DP drivers to load again.
+	Fix sending AVRCP button released passthrough messages
+	Fix bug which prevent input devices to work after restart.
+	Fix issue with interpretation of UUID-128 as channel.
+
+ver 4.37:
+	Add version value for Bluetooth 3.0 devices.
+	Add additional L2CAP extended feature mask bits.
+	Add support for loading plugins in priority order.
+	Add support for more detailed usage of disconnect watches.
+	Add support for AVRCP volume control.
+	Add saturated clipping of SBC decoder output to 16-bit.
+	Fix potentially infinite recursion of adapter_up.
+	Fix SCO handling in the case of an incoming call.
+	Fix input service to use confirm callback.
+	Fix cleanup of temporary device entries from storage.
+
+ver 4.36:
+	Add proper tracking of AVCTP connect attempts.
+	Add support to channel pattern in Serial interface.
+	Fix A2DP sink crash if removing device while connecting.
+	Fix error handling if HFP indicators aren't initialized.
+	Fix segfault while handling an incoming SCO connection.
+	Fix Serial.Disconnect to abort connection attempt.
+
+ver 4.35:
+	Add support for Handsfree profile headset role.
+	Add additional checks for open SEIDs from clients.
+	Fix device removal while audio IPC client is connected.
+	Fix device removal when an authorization request is pending.
+	Fix incoming AVDTP connect while authorization in progress.
+	Fix disconnection timers for audio support.
+	Fix various potential NULL pointer deferences.
+	Fix callheld indicator value for multiple calls.
+	Fix voice number type usage.
+	Fix GDBus watch handling.
+
+ver 4.34:
+	Add support for version checks of plugins.
+	Add support for class property on adapter interface.
+	Add support for second SDP attempt after connection reset.
+	Add support for more detailed audio states.
+	Add support for HFP+A2DP auto connection feature.
+	Add support for new and improved audio IPC.
+	Add program for testing audio IPC interface.
+	Fix various AVDTP qualification related issues.
+	Fix broken SDP AttributeIdList parsing.
+	Fix invalid memory access of SDP URL handling.
+	Fix local class of device race conditions.
+	Fix issue with periodic inquiry on startup.
+	Fix missing temporary devices in some situations.
+	Fix SBC alignment issue for encoding with four subbands.
+
+ver 4.33:
+	Add Paired property to the DeviceFound signals.
+	Add support for Headset profile 1.2 version.
+	Fix broken network configuration when IPv6 is disabled.
+	Fix network regression that caused disconnection.
+	Fix SDP truncation of strings with NULL values.
+	Fix service discovery handling of CUPS helper.
+
+ver 4.32:
+	Fix broken SDP record handling.
+	Fix SDP data buffer parsing.
+	Fix more SDP memory leaks.
+	Fix read scan enable calls.
+	Fix A2DP stream handling.
+
+ver 4.31:
+	Add support for new BtIO helper library.
+	Fix AVDTP session close issue.
+	Fix SDP memory leaks.
+	Fix various uninitialized memory issues.
+	Fix duplicate signal emissions.
+	Fix property changes request handling.
+	Fix class of device storage handling.
+
+ver 4.30:
+	Add CID field to L2CAP socket address structure.
+	Fix reset of authentication requirements after bonding.
+	Fix storing of link keys when using dedicated bonding.
+	Fix storing of pre-Bluetooth 2.1 link keys.
+	Fix resetting trust settings on every reboot.
+	Fix handling of local name changes.
+	Fix memory leaks in hciconfig and hcitool
+
+ver 4.29:
+	Use AVRCP version 1.0 for now.
+	Decrease AVDTP idle timeout to one second.
+	Delay AVRCP connection when remote device connects A2DP.
+	Add workaround for AVDTP stream setup with broken headsets.
+	Add missing three-way calling feature bit for Handsfree.
+	Fix handsfree callheld indicator updating.
+	Fix parsing of all AT commands within the buffer.
+	Fix authentication replies when disconnected.
+	Fix handling of debug combination keys.
+	Fix handling of changed combination keys.
+	Fix handling of link keys when using no bonding.
+	Fix handling of invalid/unknown authentication requirements.
+	Fix closing of L2CAP raw socket used for dedicated bonding.
+
+ver 4.28:
+	Add AVDTP signal fragmentation support.
+	Add more SBC performance optimizations.
+	Add more SBC audio quality improvements.
+	Use native byte order for audio plugins.
+	Set the adapter alias only after checking the EIR data.
+	Fix auto-disconnect issue with explicit A2DP connections.
+	Fix invalid memory access of ALSA plugin.
+	Fix compilation with -Wsign-compare.
+
+ver 4.27:
+	Add more SBC optimization (MMX and ARM NEON).
+	Add BT_SECURITY and BT_DEFER_SETUP definitions.
+	Add support for deferred connection setup.
+	Add support for fragmentation of data packets.
+	Add option to trigger dedicated bonding.
+	Follow MITM requirements from remote device.
+	Require MITM for dedicated bonding if capabilities allow it.
+	Fix IO capabilities for non-pairing and pairing cases.
+	Fix no-bonding connections in non-bondable mode.
+	Fix new pairing detection with SSP.
+	Fix bonding with pre-2.1 devices and newer kernels.
+	Fix LIAC setting while toggling Pairable property.
+	Fix device creation for incoming security mode 3 connects.
+	Fix crash within A2DP with bogus pointer.
+	Fix issue with sdp_copy_record() function.
+	Fix crash with extract_des() if sdp_uuid_extract() fails.
+
+ver 4.26:
+	Use of constant shift in SBC quantization code.
+	Add possibility to analyze 4 blocks at once in encoder.
+	Fix correct handling of frame sizes in the encoder.
+	Fix for big endian problems in SBC codec.
+	Fix audio client socket to always be non-blocking.
+	Update telephony support for Maemo.
+
+ver 4.25:
+	Fix receiving data over the audio control socket.
+	Fix subbands selection for joint-stereo in SBC encoder.
+	Add new SBC analysis filter function.
+
+ver 4.24:
+	Fix signal emissions when removing adapters.
+	Fix missing adapter signals on exit.
+	Add support for bringing adapters down on exit.
+	Add support for RememberPowered option.
+	Add support for verbose compiler warnings.
+	Add more options to SBC encoder.
+
+ver 4.23:
+	Update audio IPC for better codec handling.
+	Fix bitstream optimization for SBC encoder.
+	Fix length header values of IPC messages.
+	Fix multiple coding style violations.
+	Fix FindDevice to handle temporary devices.
+	Add configuration option for DeviceID.
+	Add support for InitiallyPowered option.
+	Add missing signals for manager properties.
+	Add telephony support for Maemo.
+
+ver 4.22:
+	Add deny statements to D-Bus access policy.
+	Add support for LegacyPairing property.
+	Add support for global properties.
+	Add more commands to telephony testing script.
+	Add sender checks for serial and network interfaces.
+	Remove deprecated methods and signals from input interface.
+	Remove deprecated methods and signals from network interface.
+	Remove OffMode option and always use device down.
+
+ver 4.21:
+	Fix adapter initialization logic.
+	Fix adapter setup and start security manager early.
+	Fix usage issue with first_init variable.
+
+ver 4.20:
+	Cleanup session handling.
+	Cleanup mode setting handling.
+	Fix issue with concurrent audio clients.
+	Fix issue with HFP/HSP suspending.
+	Fix AT result code syntax handling.
+	Add Handsfree support for AT+NREC.
+	Add PairableTimeout adapter property.
+
+ver 4.19:
+	Fix installation of manual pages for old daemons.
+	Fix D-Bus signal emmissions for CreateDevice.
+	Fix issues with UUID probing.
+	Fix +BSRF syntax issue.
+	Add Pairable adapter property.
+	Add sdp_copy_record() library function.
+
+ver 4.18:
+	Fix release before close issue with RFCOMM TTYs.
+	Fix Connected property on input interface.
+	Fix DeviceFound signals during initial name resolving.
+	Fix service discovery handling.
+	Fix duplicate UUID detection.
+	Fix SBC gain mismatch and decoding handling.
+	Add more options to SBC encoder and decoder.
+	Add special any adapter object for service interface.
+	Add variable prefix to adapter and device object paths.
+
+ver 4.17:
+	Fix SBC encoder not writing last frame.
+	Fix missing timer for A2DP suspend.
+	Add more supported devices to hid2hci utility.
+	Add additional functionality to Handsfree support.
+
+ver 4.16:
+	Fix wrong parameter usage of watch callbacks.
+	Fix parameters for callback upon path removal.
+	Fix unloading of adapter drivers.
+
+ver 4.15:
+	Fix various A2DP state machine issues.
+	Fix some issues with the Handsfree error reporting.
+	Fix format string warnings with recent GCC versions.
+	Remove dependency on GModule.
+
+ver 4.14:
+	Fix types of property arrays.
+	Fix potential crash with input devices.
+	Fix PS3 BD remote input event generation.
+	Allow dynamic adapter driver registration.
+	Update udev rules.
+
+ver 4.13:
+	Fix service discovery and UUID handling.
+	Fix bonding issues with Simple Pairing.
+	Fix file descriptor misuse of SCO connections.
+	Fix various memory leaks in the device handling.
+	Fix AVCTP disconnect handling.
+	Fix GStreamer modes for MP3 encoding.
+	Add operator selection to Handsfree support.
+
+ver 4.12:
+	Fix crash with missing icon value.
+	Fix error checks of HAL plugin.
+	Fix SCO server socket cleanup on exit.
+	Fix memory leaks from DBusPendingCall.
+	Fix handling of pending authorization requests.
+	Fix missing protocol UUIDs in record pattern.
+
+ver 4.11:
+	Change SCO server socket into a generic one.
+	Add test script for dummy telephony plugin.
+	Fix uninitialized reply of multiple GetProperties methods.
+
+ver 4.10:
+	Fix memory leaks with HAL messages.
+	Add more advanced handsfree features.
+	Add properties to audio, input and network interfaces.
+	Stop device discovery timer on device removal.
+
+ver 4.9:
+	Fix signals for Powered and Discoverable properties.
+	Fix handling of Alias and Icon properties.
+	Fix duplicate entries for service UUIDs.
+
+ver 4.8:
+	Fix retrieving of formfactor value.
+	Fix retrieving of local and remote extended features.
+	Fix potential NULL pointer dereference during pairing.
+	Fix crash with browsing due to a remotely initated pairing.
+
+ver 4.7:
+	Fix pairing and service discovery logic.
+	Fix crashes during suspend and resume.
+	Fix race condition within devdown mode.
+	Add RequestSession and ReleaseSession methods.
+	Add Powered and Discoverable properties.
+	Add Devices property and deprecate ListDevices.
+	Add workaround for a broken carkit from Nokia.
+
+ver 4.6:
+	Fix Device ID record handling.
+	Fix service browsing and storage.
+	Fix authentication and encryption for input devices.
+	Fix adapter name initialization.
+
+ver 4.5:
+	Fix initialization issue with new adapters.
+	Send HID authentication request without blocking.
+	Hide the verbose SDP debug behind SDP_DEBUG.
+	Add extra UUIDs for service discovery.
+	Add SCO server socket listener.
+	Add authorization support to service plugin.
+
+ver 4.4:
+	Add temporary fix for the CUPS compile issue.
+	Add service-api.txt to distribution.
+	Mention the variable prefix of an object path
+
+ver 4.3:
+	Add dummy driver for telephony support.
+	Add support for discovery sessions.
+	Add service plugin for external services.
+	Various cleanups.
+
+ver 4.2:
+	Avoid memory copies in A2DP write routine.
+	Fix broken logic with Simple Pairing check and old kernels.
+	Allow non-bondable and outgoing SDP without agent.
+	Only remove the bonding for non-temporary devices.
+	Cleanup various unnecessary includes.
+	Make more unexported functions static.
+	Add basic infrastructure for gtk-doc support.
+
+ver 4.1:
+	Add 30 seconds timeout to BNEP connection setup phase.
+	Avoid memory copies in A2DP write routine for ALSA.
+	Make sure to include compat/sdp.h in the distribution.
+
+ver 4.0:
+	Initial public release.
+
+ver 3.36:
+	Add init routines for TI BRF chips.
+	Add extra attributes to the serial port record.
+	Add example record for headset audio gateway record.
+	Use Handsfree version 0x0105 for the gateway role.
+	Fix SDP record registration with specific record handles.
+	Fix BCSP sent/receive handling.
+	Fix various includes for cross-compilation.
+	Allow link mode settings for outgoing connections.
+	Allow bonding during periodic inquiry.
+
+ver 3.35:
+	Add two additional company identifiers.
+	Add UUID-128 support for service discovery.
+	Fix usage of friendly names for service discovery.
+	Fix authorization when experiemental is disabled.
+	Fix uninitialized variable in passkey request handling.
+	Enable output of timestamps for l2test and rctest.
+
+ver 3.34:
+	Replace various SDP functions with safe versions.
+	Add additional length validation for incoming SDP packets.
+	Use safe function versions for SDP client handling.
+	Fix issue with RemoveDevice during discovery procedure.
+	Fix collect for non-persistent service records.
+
+ver 3.33:
+	Add functions for reading and writing the link policy settings.
+	Add definition for authentication requirements.
+	Add support for handling Simple Pairing.
+	Add Simple Pairing support to Agent interface.
+	Add ReleaseMode method to Adapter interface.
+	Add DiscoverServices method to Device interface.
+	Remove obsolete code and cleanup the repository.
+	Move over to use the libgdbus API.
+	Enable PIE by default if supported.
+
+ver 3.32:
+	Add OCF constants for synchronous flow control enabling.
+	Add support for switching HID proxy devices from Dell.
+	Add more Bluetooth client/server helper functions.
+	Add support for input service idle timeout option.
+	Fix BNEP reconnection handling.
+	Fix return value for snd_pcm_hw_params() calls.
+	Use upper-case addresses for object paths.
+	Remove HAL support helpers.
+	Remove inotify support.
+	Remove service daemon activation handling.
+	Remove uneeded D-Bus API extension.
+
+ver 3.31:
+	Create device object for all pairing cases.
+	Convert authorization to internal function calls.
+	Add initial support for Headset Audio Gateway role.
+	Add generic Bluetooth helper functions for GLib.
+	Fix endiannes handling of connection handles.
+	Don't optimize when debug is enabled.
+
+ver 3.30:
+	Convert audio service into a plugin.
+	Convert input service into a plugin.
+	Convert serial service into a plugin.
+	Convert network service into a plugin.
+	Emit old device signals when a property is changed.
+	Fix missing DiscoverDevices and CancelDiscovery methods.
+	Add another company identifier.
+	Add basic support for Bluetooth sessions.
+	Add avinfo utility for AVDTP/A2DP classification.
+	Remove build option for deprecated sdpd binary.
+
+ver 3.29:
+	Introduce new D-Bus based API.
+	Add more SBC optimizations.
+	Add support for PS3 remote devices.
+	Fix alignment trap in SDP server.
+	Fix memory leak in sdp_get_uuidseq_attr function.
+
+ver 3.28:
+	Add support for MCAP UUIDs.
+	Add support for role switch for audio service.
+	Add disconnect timer for audio service.
+	Add disconnect detection to ALSA plugin.
+	Add more SBC optimizations.
+	Fix alignment issue of SDP server.
+	Remove support for SDP parsing via expat.
+
+ver 3.27:
+	Update uinput.h with extra key definitions.
+	Add support for input connect/disconnect callbacks.
+	Add ifdefs around some baud rate definitions.
+	Add another company identifier.
+	Add proper HFP service level connection handling.
+	Add basic headset automatic disconnect support.
+	Add support for new SBC API.
+	Fix SBC decoder noise at high bitpools.
+	Use 32-bit multipliers for further SBC optimization.
+	Check for RFCOMM connection state in SCO connect callback.
+	Make use of parameters selected in ALSA plugin.
+
+ver 3.26:
+	Fix compilation issues with UCHAR_MAX, USHRT_MAX and UINT_MAX.
+	Improve handling of different audio transports.
+	Enable services by default and keep old daemons disabled.
+
+ver 3.25:
+	Add limited support for Handsfree profile.
+	Add limited support for MPEG12/MP3 codec.
+	Add basic support for UNITINFO and SUBUNITINFO.
+	Add more SBC optimizations.
+	Fix external service (un)registration.
+	Allow GetInfo and GetAddress to fail.
+
+ver 3.24:
+	Add definitions for MDP.
+	Add TCP connection support for serial proxy.
+	Add fix for Logitech HID proxy switching.
+	Add missing macros, MIN, MAX, ABS and CLAMP.
+	Add more SBC encoder optimizations.
+	Add initial mechanism to handle headset commands.
+	Fix connecting to handsfree profile headsets.
+	Use proper function for checking signal name.
+
+ver 3.23:
+	Fix remote name request handling bug.
+	Fix key search function to honor the mmap area size.
+	Fix Avahi integration of network service.
+	Add new plugin communication for audio service.
+	Enable basic AVRCP support by default.
+	More optimizations to the SBC library.
+	Create common error definitions.
+
+ver 3.22:
+	Add missing include file from audio service.
+	Add SBC conformance test utility.
+	Add basic uinput support for AVRCP.
+	Fix L2CAP socket leak in audio service.
+	Fix buffer usage in GStreamer plugin.
+	Fix remote name request event handling.
+
+ver 3.21:
+	Add constant for Bluetooth socket options level.
+	Add initial AVRCP support.
+	Add A2DP sink support to GStreamer plugin.
+	Fix interoperability with A2DP suspend.
+	Fix sign error in 8-subband encoder.
+	Fix handling of service classes length size.
+	Store Extended Inquiry Response data information.
+	Publish device id information through EIR.
+	Support higher baud rates for Ericcson based chips.
+
+ver 3.20:
+	Fix GStreamer plugin file type detection.
+	Fix potential infinite loop in inotify support.
+	Fix D-Bus signatures for dict handling.
+	Fix issues with service activation.
+	Fix SDP failure handling of audio service.
+	Fix various memory leaks in input service.
+	Add secure device creation method to input service.
+	Add service information methods to serial service.
+	Add config file support to network service.
+	Add scripting capability to network service.
+	Add special on-mode handling.
+	Add optimization for SBC encoder.
+	Add tweaks for D-Bus 1.1.x libraries.
+	Add support for inquiry transmit power level.
+
+ver 3.19:
+	Limit range of bitpool announced while in ACP side.
+	Use poll instead of usleep to wait for worker thread.
+	Use default event mask from the specification.
+	Add L2CAP mode constants.
+	Add HID proxy support for Logitech diNovo Edge dongle.
+	Add refresh option to re-request device names.
+	Show correct connection link type.
+
+ver 3.18:
+	Don't allocate memory for the Bluetooth base UUID.
+	Implement proper locking for headsets.
+	Fix various A2DP SEP locking issues.
+	Fix and cleanup audio stream handling.
+	Fix stream starting if suspend request is pending.
+	Fix A2DP and AVDTP endianess problems.
+	Add network timeout and retransmission support.
+	Add more detailed decoding of EIR elements.
+
+ver 3.17:
+	Fix supported commands bit calculation.
+	Fix crashes in audio and network services.
+	Check PAN source and destination roles.
+	Only export the needed symbols for the plugins.
+
+ver 3.16:
+	Update company identifier list.
+	Add support for headsets with SCO audio over HCI.
+	Add support for auto-create through ALSA plugin.
+	Add support for ALSA plugin parameters.
+	Add GStreamer plugin with SBC decoder and encoder.
+	Fix network service NAP, GN and PANU servers.
+	Set EIR information from SDP database.
+
+ver 3.15:
+	Add A2DP support to the audio service.
+	Add proxy support to the serial service.
+	Extract main service class for later use.
+	Set service classes value from SDP database.
+
+ver 3.14:
+	Add missing signals for the adapter interface.
+	Add definitions and functions for Simple Pairing.
+	Add basic commands for Simple Pairing.
+	Add correct Simple Pairing and EIR interaction.
+	Add missing properties for remote information.
+	Add EPoX endian quirk to the input service.
+	Fix HID descriptor import and storage functions.
+	Fix handling of adapters in raw mode.
+	Fix remote device listing methods.
+
+ver 3.13:
+	Fix some issues with the headset support.
+	Fix concurrent pending connection attempts.
+	Fix usage of devname instead of netdev.
+	Add identifier for Nokia SyncML records.
+	Add command for reading the CSR chip revision.
+	Add generic CSR radio test support.
+	Update HCI command table.
+
+ver 3.12:
+	Add missing HCI command text descriptions
+	Add missing HCI commands structures.
+	Add missing HCI event structures.
+	Add common bachk() function.
+	Add support for limited discovery mode.
+	Add support for setting of event mask.
+	Add GetRemoteServiceIdentifiers method.
+	Add skeleton for local D-Bus server.
+	Add headset gain control methods.
+	Fix various headset implementation issues.
+	Fix various serial port service issues.
+	Fix various input service issues.
+	Let CUPS plugin discover printers in range.
+	Improve the BCM2035 UART init routine.
+	Ignore connection events for non-ACL links.
+
+ver 3.11:
+	Update API documentation.
+	Minimize SDP root records and browse groups.
+	Use same decoder for text and URL strings.
+	Fix URL data size handling.
+	Fix SDP pattern extraction for XML.
+	Fix network connection persistent state.
+	Add network connection helper methods.
+	Add initial version of serial port support.
+	Add class of device tracking.
+
+ver 3.10.1:
+	Add option to disable installation of manual pages.
+	Fix input service encryption setup.
+	Fix serial service methods.
+	Fix network service connection handling.
+	Provide a simple init script.
+
+ver 3.10:
+	Add initial version of network service.
+	Add initial version of serial service.
+	Add initial version of input service.
+	Add initial version of audio service.
+	Add authorization framework.
+	Add integer based SBC library.
+	Add version code for Bluetooth 2.1 specification.
+	Add ESCO_LINK connection type constant.
+	Export sdp_uuid32_to_uuid128() function.
+
+ver 3.9:
+	Add RemoteDeviceDisconnectRequested signal.
+	Add updated service framework.
+	Add embedded GLib library.
+	Add support for using system GLib library.
+	Create internal SDP server library.
+
+ver 3.8:
+	Sort discovered devices list based on their RSSI.
+	Send DiscoverableTimeoutChanged signal.
+	Fix local and remote name validity checking.
+	Add ListRemoteDevices and ListRecentRemoteDevices methods.
+	Add basic integration of confirmation concept.
+	Add support for service record description via XML.
+	Add support for external commands to the RFCOMM utility.
+	Add experimental service and authorization API.
+	Add functions for registering binary records.
+
+ver 3.7:
+	Fix class of device handling.
+	Fix error replies with pairing and security mode 3.
+	Fix disconnect method for RFCOMM connections.
+	Add match pattern for service searches.
+	Add support for prioritized watches.
+	Add additional PDU length checks.
+	Fix CSRC value for partial responses.
+
+ver 3.6.1:
+	Fix IO channel race conditions.
+	Fix pairing issues on big endian systems.
+	Fix pairing issues with page timeout errors.
+	Fix pairing state for security mode 3 requests.
+	Switch to user as default security manager mode.
+
+ver 3.6:
+	Update D-Bus based RFCOMM interface support.
+	Use L2CAP raw sockets for HCI connection creation.
+	Add periodic discovery support to the D-Bus interface.
+	Add initial support for device names via EIR.
+	Add proper UTF-8 validation of device names.
+	Add support for the J-Three keyboard.
+	Fix issues with the asynchronous API for SDP.
+
+ver 3.5:
+	Fix and cleanup watch functionality.
+	Add support for periodic inquiry mode.
+	Add support for asynchronous SDP requests.
+	Add more request owner tracking.
+	Add asynchronous API for SDP.
+	Document pageto and discovto options.
+
+ver 3.4:
+	Improve error reporting for failed HCI commands.
+	Improve handling of CancelBonding.
+	Fixed bonding reply message when disconnected.
+	Fix UUID128 string lookup handling.
+	Fix malloc() versus bt_malloc() usage.
+
+ver 3.3:
+	Don't change inquiry mode for Bluetooth 1.1 adapters.
+	Add udev rules for Bluetooth serial PCMCIA cards.
+	Add Cancel and Release methods for passkey agents.
+	Add GetRemoteClass method.
+	Convert to using ppoll() and pselect().
+	Initialize allocated memory to zero.
+	Remove bcm203x firmware loader.
+	Remove kernel specific timeouts.
+	Add additional private data field for SDP sessions.
+	Add host controller to host flow control defines.
+	Add host number of completed packets defines.
+	Initialize various memory to zero before usage.
+
+ver 3.2:
+	Only check for the low-level D-Bus library.
+	Update possible device minor classes.
+	Fix timeout for pending reply.
+	Add more Inquiry with RSSI quirks.
+	Sleep only 100 msecs for device detection.
+	Don't send BondingCreated on link key renewal.
+	Allow storing of all UTF-8 remote device names.
+	Create storage filenames with a generic function.
+	Fix handling of SDP strings.
+	Add adapter type for SDIO cards.
+	Add features bit for link supervision timeout.
+
+ver 3.1:
+	Add missing placeholders for feature bits.
+	Fix handling of raw mode devices.
+	Fix busy loop in UUID extraction routine.
+	Remove inquiry mode setting.
+	Remove auth and encrypt settings.
+
+ver 3.0:
+	Implement the new BlueZ D-Bus API.
+	Fix broken behavior with EVT_CMD_STATUS.
+	Add features bit for pause encryption.
+	Add additional EIR error code.
+	Add more company identifiers.
+	Add another Phonebook Access identifier.
+	Update sniff subrating data structures.
+
+ver 2.25:
+	Use %jx instead of %llx for uint64_t and int64_t.
+	Allow null-terminated text strings.
+	Add UUID for N-Gage games.
+	Add UUID for Apple Macintosh Attributes.
+	Add Apple attributes and iSync records.
+	Add definitions for Apple Agent.
+	Add support for the Handsfree Audio Gateway service.
+	Add support for choosing a specific record handle.
+	Add support for dialup/telephone connections.
+	Add definitions for Apple Agent.
+	Add support for record handle on service registration.
+
+ver 2.24:
+	Fix display of SDP text and data strings.
+	Add support for device scan property.
+	Add support for additional access protocols.
+	Update the D-Bus policy configuration file.
+
+ver 2.23:
+	Update the new D-Bus interface.
+	Make dfutool ready for big endian architectures.
+	Add support for AVRCP specific service records.
+	Add support for writing complex BCCMD commands.
+	Add the new BCCMD interface utility.
+	Add MicroBCSP implementation from CSR.
+	Add constants and definitions for sniff subrating.
+	Add support for allocation of binary text elements.
+	Add HCI emulation tool.
+	Add fake HID support for old EPoX presenters.
+	Reject connections from unknown HID devices.
+	Fix service discovery deadlocks with Samsung D600 phones.
+
+ver 2.22:
+	Remove D-Bus 0.23 support.
+	Add initial version of the new D-Bus interface.
+	Add support for extended inquiry response commands.
+	Add support for the Logitech diNovo Media Desktop Laser.
+	Add compile time buffer checks (FORTIFY SOURCE).
+	Decode reserved LMP feature bits.
+	Fix errno overwrite problems.
+	Fix profile descriptor problem with Samsung phones.
+
+ver 2.21:
+	Move create_dirs() and create_file() into the textfile library.
+	Let textfile_put() also replace the last key value pair.
+	Fix memory leaks with textfile_get() usage.
+	Fix infinite loops and false positive matches.
+	Don't retrieve stored link keys for RAW devices.
+	Document the putkey and delkey commands.
+	Show supported commands also in clear text.
+	Support volatile changes of the BD_ADDR for CSR chips.
+	Add support for identification of supported commands.
+	Add missing OCF declarations for the security filter.
+	Add two new company identifiers.
+
+ver 2.20:
+	Add UUIDs for video distribution profile.
+	Add UUIDs for phonebook access profile.
+	Add attribute identifier for supported repositories.
+	Add definitions for extended inquiry response.
+	Add functions for extended inquiry response.
+	Add support for extended inquiry response.
+	Add support for HotSync service record.
+	Add support for ActiveSync service record.
+	Add ActiveSync networking support.
+	Fix D-Bus crashes with new API versions.
+
+ver 2.19:
+	Fix the GCC 4.0 warnings.
+	Fix the routing for dealing with raw devices.
+	Fix off by one memory allocation error.
+	Fix security problem with escape characters in device name.
+	Add per device service record functions.
+	Send D-Bus signals for inquiry results and remote name resolves.
+	Add support for device specific SDP records.
+
+ver 2.18:
+	Support D-Bus 0.23 and 0.33 API versions.
+	Support reading of complex BCCMD values.
+	Support minimum and maximum encryption key length.
+	Add support for reading and writing the inquiry scan type.
+	Add definitions for connection accept timeout and scan enable.
+	Add support for inquiry scan type.
+	Add tool for the CSR BCCMD interface.
+	Add first draft of the Audio/Video control utility.
+	Add disconnect timer support for the A2DP ALSA plugin.
+	Make SBC parameters configurable.
+	Replace non-printable characters in device names.
+	Remove hci_vhci.h header file.
+	Remove hci_uart.h header file.
+
+ver 2.17:
+	Set the storage directory through ${localstatedir}.
+	Add the textfile library for ASCII based file access.
+	Add support for return link keys event.
+	Add support for voice setting configuration.
+	Add support for page scan timeout configuration.
+	Add support for storing and deleting of stored link keys.
+	Add support for searching for services with UUID-128.
+	Add support for retrieving all possible service records.
+	Add support for a raw mode view of service records.
+	Add support for HID information caching in hidd.
+	Add support for authentication in pand and dund.
+	Add support for changing BD_ADDR of CSR chips.
+	Add pskey utility for changing CSR persistent storage values.
+	Add the firmware upgrade utility.
+	Add connection caching for the A2DP ALSA plugin.
+	Add functions for stored link keys.
+	Add definitions for PIN type and unit key.
+	Add SDP_WAIT_ON_CLOSE flag for sdp_connect().
+	Include stdio.h in bluetooth.h header file.
+	Include sys/socket.h in the header files.
+
+ver 2.16:
+	Store link keys in ASCII based file format.
+	Support device name caching.
+	Support zero length data sizes in l2test.
+	Change default l2ping data size to 44 bytes.
+	Hide the server record and the public browse group root.
+	Read BD_ADDR if not set and if it is a raw device.
+	Add SDP language attributes.
+	Add support for browsing the L2CAP group.
+	Add support for stored pin codes for outgoing connections.
+	Add support for local commands and extended features.
+	Add support for reading CSR panic and fault codes.
+	Add config option for setting the inquiry mode.
+	Add OUI decoding support.
+	Use unlimited inquiry responses as default.
+	Use cached device names for PIN request.
+	Use the clock offset when getting the remote names.
+	Add function for reading local supported commands.
+	Add function for reading local extended features.
+	Add function for reading remote extended features.
+	Add function for getting the remote name with a clock offset.
+	Add function for extracting the OUI from a BD_ADDR.
+	Add inquiry info structure with RSSI and page scan mode.
+	Fix buffer allocation for features to string conversion.
+	Support inquiry with unlimited number of responses.
+
+ver 2.15:
+	Enable the RFCOMM service level security.
+	Add deprecated functions for reading the name.
+	Add command for reading the clock offset.
+	Add command for reading the clock.
+	Add function for reading the clock.
+	Add function for reading the local Bluetooth address.
+	Add function for reading the local supported features.
+	Don't configure raw devices.
+	Don't set inquiry scan or page scan on raw devices.
+	Don't show extended information for raw devices.
+	Support L2CAP signal sizes bigger than 2048 bytes.
+	Cleanup of the socket handling code of the test programs.
+	Use better way for unaligned access.
+	Remove sdp_internal.h and its usage.
+
+ver 2.14:
+	Make use of additional connection information.
+	Use library function for reading the RSSI.
+	Use library function for reading the link quality.
+	Use library function for reading the transmit power level.
+	Use library functions for the link supervision timeout.
+	Add tool for changing the device address.
+	Add function for reading the RSSI.
+	Add function for reading the link quality.
+	Add function for reading the transmit power level.
+	Add functions for the link supervision timeout.
+	Remove deprecated functions.
+	Update AM_PATH_BLUEZ macro.
+
+ver 2.13:
+	Use file permission 0600 for the link key file.
+	Add support for HID attribute descriptions.
+	Add support for Device ID attributes.
+	Add Device ID and HID attribute definitions.
+	Update the UUID constants and its translations.
+	Update L2CAP socket option definitions.
+	Update connection information definitions.
+	Various whitespace cleanups.
+
+ver 2.12:
+	Inherit the device specific options from the default.
+	Use --device for selecting the source device.
+	Add --nosdp option for devices with resource limitation.
+	Add support and parameter option for secure mode.
+	Add a lot of build ids and hardware revisions.
+	Add service classes and profile ids for WAP.
+	Add simple AM_PATH_BLUEZ macro.
+	Update UUID translation tables.
+	Correct kernel interface for CMTP and HIDP support.
+
+ver 2.11:
+	Initial support for the kernel security manager.
+	Various cleanups to avoid inclusion of kernel headers.
+	Fix output when the CUPS backend is called without arguments.
+	Fix problems with a 64 bit userland.
+	Use Bluetooth library functions if available.
+	Use standard numbering scheme of SDP record handles.
+	Use bit zero for vendor packets in the filter type bitmask.
+	Add SIM Access types for service discovery.
+	Add more audio/video profile translations.
+	Add another company identifier.
+	Add the missing HCI error codes.
+	Add RFCOMM socket options.
+	Add definition for the SECURE link mode.
+	Add functions for reading and writing the inquiry mode.
+	Add functions for AFH related settings and information.
+	Add version identifier for the Bluetooth 2.0 specification.
+	Add a master option to the hidd.
+	Add support for changing the link key of a connection.
+	Add support for requesting encryption on keyboards.
+	Add support for revision information of Digianswer devices.
+	Add support for the Zoom, IBM and TDK PCMCIA cards.
+	Add checks for the OpenOBEX and the ALSA libraries.
+	Add experimental mRouter support.
+
+ver 2.10:
+	Use a define for the configuration directory.
+	Fix string initialization for flags translation.
+	Fix and extend the unaligned access macros.
+	Make compiling with debug information optional.
+	Don't override CFLAGS from configure.
+	Check for usb_get_busses() and usb_interrupt_read().
+	Add optional support for compiling with PIE.
+	Make installation of the init scripts optional.
+	Make compiling with debug information optional.
+	Don't override CFLAGS from configure.
+
+ver 2.9:
+	Retry SDP connect if busy in the CUPS backend.
+	Use packet type and allow role switch in hcitool.
+	Use the functions from the USB library for hid2hci.
+	Add Broadcom firmware loader.
+	Add EPoX endian quirk for buggy keyboards.
+	Add L2CAP info type and info result definitions.
+	Add value for L2CAP_CONF_RFC_MODE.
+	Change RSSI value to signed instead of unsigned.
+	Allow UUID32 values as protocol identifiers.
+	Update the autoconf/automake scripts.
+
+ver 2.8:
+	Use LIBS and LDADD instead of LDFLAGS.
+	Use HIDP subclass field for HID boot protocol.
+	Set olen before calling getsockopt() in pand.
+	Restore signals for dev-up script.
+	Add PID file support for pand.
+	Add size parameter to expand_name() in hcid.
+	Add support for audio source and audio sink SDP records.
+	Add support for HID virtual cable unplug.
+	Add support for AmbiCom BT2000C card.
+	Add defines and UUID's for audio/video profiles.
+	Add AVDTP protocol identifier.
+	Add HIDP subclass field.
+	Add PKGConfig support.
+	Fix the event code of inquiry with RSSI.
+	Remove dummy SDP library.
+
+ver 2.7:
+	Fix display of decoded LMP features.
+	Update company identifiers.
+	Add AFH related types.
+	Add first bits from EDR prototyping specification.
+	Add support for inquiry with RSSI.
+	Add HCRP related SDP functions.
+	Add HIDP header file.
+	Add support for getting the AFH channel map.
+	Add support for AFH mode.
+	Add support for inquiry mode.
+	Add Bluetooth backend for CUPS.
+	Add the hid2hci utility.
+	Add the hidd utility.
+	Add the pand utility.
+	Add the dund utility.
+	More endian bug fixes.
+	Give udev some time to create the RFCOMM device nodes.
+	Release the TTY if no device node is found.
+	New startup script for the Bluetooth subsystem.
+	Update to the autoconf stuff.
+
+ver 2.6:
+	Change default prefix to /usr.
+	Add manpages for hcid and hcid.conf.
+	Add the sdpd server daemon.
+	Add the sdptool utility.
+	Add the ciptool utility.
+	Add new company identifiers.
+	Add BNEP and CMTP header files.
+	Add the SDP library.
+	Use R2 for default value of pscan_rep_mode.
+
+ver 2.5:
+	Add decoding of Bluetooth 1.2 features.
+	Add link manager version parameter for Bluetooth 1.2.
+	Add new company identifiers.
+	Add D-Bus support for PIN request.
+	Support for transmit power level.
+	Support for park, sniff and hold mode.
+	Support for role switch.
+	Support for reading the clock offset.
+	Support for requesting authentication.
+	Support for setting connection encryption.
+	Show revision information for Broadcom devices.
+	Replace unprintable characters in device name.
+	Use R1 for default value of pscan_rep_mode.
+	Fix some 64-bit problems.
+	Fix some endian problems.
+	Report an error on PIN helper failure.
+	Update bluepin script for GTK2.
+
+ver 2.4:
+	Increase number of inquiry responses.
+	Support for transmit power level.
+	Display all 8 bytes of the features.
+	Add support for reading and writing of IAC.
+	Correct decoding class of device.
+	Use Ericsson revision command for ST Microelectronics devices.
+	Display AVM firmware version with 'revision' command.
+	New code for CSR specific revision information.
+	Support for ST Microelectronics specific initialization.
+	Support for 3Com card version 3.0.
+	Support for TDK, IBM and Socket cards.
+	Support for initial baud rate.
+	Update man pages.
+	Fixes for some memory leaks.
+
+ver 2.3:
+	Added const qualifiers to appropriate function arguments.
+	Minor fixes.
+	CSR firmware version is now displayed by 'revision' command.
+	Voice command is working properly on big endian machines.
+	Added support for Texas Bluetooth modules.
+	Added support for high UART baud rates on Ericsson modules.
+	BCSP initialization fixes.
+	Support for role switch command (hcitool).
+	RFCOMM config file parser fixes.
+	Update man pages.
+	Removed GLib dependency.
+
+ver 2.2:
+	Updated RFCOMM header file.
+	Additional HCI command and event defines.
+	Support for voice settings (hciconfig).
+	Minor hcitool fixes.
+	Improved configure script.
+	Added Headset testing tool.
+	Updated man pages.
+	RPM package.
+
+ver 2.1.1:
+	Resurrect hci_remote_name.
+
+ver 2.1:
+	Added hci_{read, write}_class_of_dev().
+	Added hci_{read, write}_current_iac_lap().
+	Added hci_write_local_name().
+	Added RFCOMM header file.
+	Minor fixes.
+	Improved BCSP initialization (hciattach).
+	Support for displaying link quality (hcitool).
+	Support for changing link supervision timeout (hcitool).
+	New RFCOMM TTY configuration tool (rfcomm).
+	Minor fixes and updates.
+
+ver 2.0:
+	Additional company IDs.
+	BCSP initialization (hciattach).
+	Minor hciconfig fixes.
+
+ver 2.0-pr13:
+	Support for multiple pairing modes.
+	Link key database handling fixes.
+
+ver 2.0-pre12:
+	Removed max link key limit. Keys never expire.
+	Link key database is always updated. Reread PIN on SIGHUP (hcid).
+	Bluetooth script starts SDPd, if installed.
+	Other minor fixes.
+
+ver 2.0-pre11:
+	Improved link key management and more verbose logging (hcid).
+	Fixed scan command (hcitool).
+
+ver 2.0-pre10:
+	Fix hci_inquiry function to return errors and accept user buffers.
+	New functions hci_devba, hci_devid, hci_for_each_dev and hci_get_route.
+	Additional company IDs.
+	Makefile and other minor fixes.
+	Support for reading RSSI, remote name and changing
+	connection type (hcitool). 
+	Device initialization fixes (hcid).
+	Other minor fixes and improvements.
+	Build environment cleanup and fixes.
+
+ver 2.0-pre9:
+	Improved bluepin. Working X authentication.
+	Improved hcitool. New flexible cmd syntax, additional commands.
+	Human readable display of the device features.
+	LMP features to string translation support.
+	Additional HCI command and event defines.
+	Extended hci_filter API.
+
+ver 2.0-pre8:
+	Additional HCI ioctls and defines.
+	All strings and buffers are allocated dynamically.
+	ba2str, str2ba automatically swap bdaddress.
+	Additional hciconfig commands. Support for ACL and SCO MTU ioctls.
+	Support for Inventel and COM1 UART based devices.
+	Minor hcitool fixes.
+	Improved l2test. New L2CAP test modes.
+	Minor fixes and cleanup.
+
+ver 2.0-pre7:
+	Bluetooth libraries and header files is now a separate package.
+	New build environment uses automake and libtool.
+	Massive header files cleanup.
+	Bluetooth utilities is now a separate package.
+	New build environment uses automake.
+	Moved all config files and security data to /etc/bluetooth.
+	Various cleanups.
+
+ver 2.0-pre6:
+	API cleanup and additions.
+	Improved hcitool.
+	l2test minor output fixes.
+	hciattach opt to display list of supported devices.
+
+ver 2.0-pre4:
+	HCI filter enhancements.
+
+ver 2.0-pre3:
+	Cleanup.
+
+ver 2.0-pre2:
+	Additional HCI library functions.
+	Improved CSR baud rate initialization.
+	PCMCIA scripts fixes and enhancements.
+	Documentation update.
+
+ver 2.0-pre1:
+	New UART initialization utility.
+	Hot plugging support for UART based PCMCIA devices.
+	SCO testing utility.
+	New authentication utility (bluepin).
+	Minor fixes and improvements.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..56b077d
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,236 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005 Free
+Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+   It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring.  (Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.)
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'.  You only need
+`configure.ac' if you want to change it or regenerate `configure' using
+a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes awhile.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Optionally, type `make check' to run any self-tests that come with
+     the package.
+
+  4. Type `make install' to install the programs and any data files and
+     documentation.
+
+  5. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  To also remove the
+     files that `configure' created (so you can compile the package for
+     a different kind of computer), type `make distclean'.  There is
+     also a `make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about.  Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+   You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here
+is an example:
+
+     ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not support the `VPATH'
+variable, you have to compile the package for one architecture at a
+time in the source code directory.  After you have installed the
+package for one architecture, use `make distclean' before reconfiguring
+for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PREFIX'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PREFIX', the package will
+use PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files.  Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS KERNEL-OS
+
+   See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the `--target=TYPE' option to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified `gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).  Here is a another example:
+
+     /bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+Here the `CONFIG_SHELL=/bin/bash' operand causes subsequent
+configuration-related scripts to be executed by `/bin/bash'.
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+     Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally `config.cache'.  FILE defaults to `/dev/null' to
+     disable caching.
+
+`--config-cache'
+`-C'
+     Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to `/dev/null' (any error
+     messages will still be shown).
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options.  Run
+`configure --help' for more details.
+
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..c4c6322
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,20 @@
+
+SUBDIRS = include lib sbc gdbus common plugins src client\
+			network serial input audio tools \
+			rfcomm compat cups test scripts doc
+
+EXTRA_DIST = bluez.m4
+
+pkgconfigdir = $(libdir)/pkgconfig
+
+pkgconfig_DATA = bluez.pc
+
+DISTCHECK_CONFIGURE_FLAGS = --disable-gtk-doc \
+				--disable-udevrules
+
+DISTCLEANFILES = $(pkgconfig_DATA)
+
+MAINTAINERCLEANFILES = Makefile.in \
+	aclocal.m4 configure config.h.in config.sub config.guess \
+	ltmain.sh depcomp compile missing install-sh mkinstalldirs ylwrap
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/NEWS
diff --git a/README b/README
new file mode 100644
index 0000000..3cc6547
--- /dev/null
+++ b/README
@@ -0,0 +1,38 @@
+BlueZ - Bluetooth protocol stack for Linux
+******************************************
+
+Copyright (C) 2000-2001  Qualcomm Incorporated
+Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
+Copyright (C) 2002-2009  Marcel Holtmann <marcel@holtmann.org>
+
+
+Compilation and installation
+============================
+
+In order to compile Bluetooth utilities you need following software packages:
+	- Linux Bluetooth protocol stack (BlueZ)
+	- GCC compiler
+	- D-Bus library
+	- GLib library
+	- USB library (optional)
+	- Lexical Analyzer (flex, lex)
+	- YACC (yacc, bison, byacc)
+
+To configure run:
+	./configure --prefix=/usr --mandir=/usr/share/man \
+		--sysconfdir=/etc --localstatedir=/var --libexecdir=/lib
+
+Configure automatically searches for all required components and packages.
+
+To compile and install run:
+	make && make install
+
+
+Information
+===========
+
+Mailing lists:
+	linux-bluetooth@vger.kernel.org
+
+For additional information about the project visit BlueZ web site:
+	http://www.bluez.org
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644
index 0000000..248fed3
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,380 @@
+AC_DEFUN([AC_PROG_CC_PIE], [
+	AC_CACHE_CHECK([whether ${CC-cc} accepts -fPIE], ac_cv_prog_cc_pie, [
+		echo 'void f(){}' > conftest.c
+		if test -z "`${CC-cc} -fPIE -pie -c conftest.c 2>&1`"; then
+			ac_cv_prog_cc_pie=yes
+		else
+			ac_cv_prog_cc_pie=no
+		fi
+		rm -rf conftest*
+	])
+])
+
+AC_DEFUN([COMPILER_FLAGS], [
+	if (test "${CFLAGS}" = ""); then
+		CFLAGS="-Wall -O2"
+	fi
+	if (test "$USE_MAINTAINER_MODE" = "yes"); then
+		CFLAGS+=" -Werror -Wextra"
+		CFLAGS+=" -Wno-unused-parameter"
+		CFLAGS+=" -Wno-missing-field-initializers"
+		CFLAGS+=" -Wdeclaration-after-statement"
+		CFLAGS+=" -Wmissing-declarations"
+		CFLAGS+=" -Wredundant-decls"
+		CFLAGS+=" -Wcast-align"
+	fi
+])
+
+AC_DEFUN([GTK_DOC_CHECK], [
+	AC_ARG_WITH([html-dir],
+		AS_HELP_STRING([--with-html-dir=PATH], [path to installed docs]),,
+					[with_html_dir='${datadir}/gtk-doc/html'])
+	HTML_DIR="$with_html_dir"
+	AC_SUBST([HTML_DIR])
+
+	AC_ARG_ENABLE([gtk-doc],
+		AS_HELP_STRING([--enable-gtk-doc], [use gtk-doc to build documentation [[default=no]]]),,
+					[enable_gtk_doc=no])
+
+	if test x$enable_gtk_doc = xyes; then
+		ifelse([$1],[],
+			[PKG_CHECK_EXISTS([gtk-doc],,
+				AC_MSG_ERROR([gtk-doc not installed and --enable-gtk-doc requested]))],
+			[PKG_CHECK_EXISTS([gtk-doc >= $1],,
+				AC_MSG_ERROR([You need to have gtk-doc >= $1 installed to build gtk-doc]))])
+	fi
+
+	AC_MSG_CHECKING([whether to build gtk-doc documentation])
+	AC_MSG_RESULT($enable_gtk_doc)
+
+	AC_PATH_PROGS(GTKDOC_CHECK,gtkdoc-check,)
+
+	AM_CONDITIONAL([ENABLE_GTK_DOC], [test x$enable_gtk_doc = xyes])
+	AM_CONDITIONAL([GTK_DOC_USE_LIBTOOL], [test -n "$LIBTOOL"])
+])
+
+AC_DEFUN([AC_FUNC_PPOLL], [
+	AC_CHECK_FUNC(ppoll, dummy=yes, AC_DEFINE(NEED_PPOLL, 1,
+			[Define to 1 if you need the ppoll() function.]))
+])
+
+AC_DEFUN([AC_INIT_BLUEZ], [
+	AC_PREFIX_DEFAULT(/usr/local)
+
+	if (test "${prefix}" = "NONE"); then
+		dnl no prefix and no sysconfdir, so default to /etc
+		if (test "$sysconfdir" = '${prefix}/etc'); then
+			AC_SUBST([sysconfdir], ['/etc'])
+		fi
+
+		dnl no prefix and no localstatedir, so default to /var
+		if (test "$localstatedir" = '${prefix}/var'); then
+			AC_SUBST([localstatedir], ['/var'])
+		fi
+
+		dnl no prefix and no libexecdir, so default to /lib
+		if (test "$libexecdir" = '${exec_prefix}/libexec'); then
+			AC_SUBST([libexecdir], ['/lib'])
+		fi
+
+		dnl no prefix and no mandir, so use ${prefix}/share/man as default
+		if (test "$mandir" = '${prefix}/man'); then
+			AC_SUBST([mandir], ['${prefix}/share/man'])
+		fi
+
+		prefix="${ac_default_prefix}"
+	fi
+
+	if (test "${libdir}" = '${exec_prefix}/lib'); then
+		libdir="${prefix}/lib"
+	fi
+
+	plugindir="${libdir}/bluetooth/plugins"
+
+	if (test "$sysconfdir" = '${prefix}/etc'); then
+		configdir="${prefix}/etc/bluetooth"
+	else
+		configdir="${sysconfdir}/bluetooth"
+	fi
+
+	if (test "$localstatedir" = '${prefix}/var'); then
+		storagedir="${prefix}/var/lib/bluetooth"
+	else
+		storagedir="${localstatedir}/lib/bluetooth"
+	fi
+
+	AC_DEFINE_UNQUOTED(CONFIGDIR, "${configdir}",
+				[Directory for the configuration files])
+	AC_DEFINE_UNQUOTED(STORAGEDIR, "${storagedir}",
+				[Directory for the storage files])
+
+	AC_SUBST(CONFIGDIR, "${configdir}")
+	AC_SUBST(STORAGEDIR, "${storagedir}")
+
+	UDEV_DATADIR="`$PKG_CONFIG --variable=udevdir udev`"
+	if (test -z "${UDEV_DATADIR}"); then
+		UDEV_DATADIR="${sysconfdir}/udev/rules.d"
+	else
+		UDEV_DATADIR="${UDEV_DATADIR}/rules.d"
+	fi
+	AC_SUBST(UDEV_DATADIR)
+])
+
+AC_DEFUN([AC_PATH_DBUS], [
+	PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.0, dummy=yes,
+				AC_MSG_ERROR(D-Bus library is required))
+	AC_CHECK_LIB(dbus-1, dbus_watch_get_unix_fd, dummy=yes,
+		AC_DEFINE(NEED_DBUS_WATCH_GET_UNIX_FD, 1,
+			[Define to 1 if you need the dbus_watch_get_unix_fd() function.]))
+	AC_SUBST(DBUS_CFLAGS)
+	AC_SUBST(DBUS_LIBS)
+])
+
+AC_DEFUN([AC_PATH_GLIB], [
+	PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.14, dummy=yes,
+				AC_MSG_ERROR(GLib library version 2.14 or later is required))
+	AC_SUBST(GLIB_CFLAGS)
+	AC_SUBST(GLIB_LIBS)
+])
+
+AC_DEFUN([AC_PATH_GSTREAMER], [
+	PKG_CHECK_MODULES(GSTREAMER, gstreamer-0.10 gstreamer-plugins-base-0.10, gstreamer_found=yes, gstreamer_found=no)
+	AC_SUBST(GSTREAMER_CFLAGS)
+	AC_SUBST(GSTREAMER_LIBS)
+	GSTREAMER_PLUGINSDIR=`$PKG_CONFIG --variable=pluginsdir gstreamer-0.10`
+	AC_SUBST(GSTREAMER_PLUGINSDIR)
+])
+
+AC_DEFUN([AC_PATH_PULSE], [
+	PKG_CHECK_MODULES(PULSE, libpulse, pulse_found=yes, pulse_found=no)
+	AC_SUBST(PULSE_CFLAGS)
+	AC_SUBST(PULSE_LIBS)
+])
+
+AC_DEFUN([AC_PATH_ALSA], [
+	PKG_CHECK_MODULES(ALSA, alsa, alsa_found=yes, alsa_found=no)
+	AC_CHECK_LIB(rt, clock_gettime, ALSA_LIBS="$ALSA_LIBS -lrt", alsa_found=no)
+	AC_SUBST(ALSA_CFLAGS)
+	AC_SUBST(ALSA_LIBS)
+])
+
+AC_DEFUN([AC_PATH_USB], [
+	PKG_CHECK_MODULES(USB, libusb, usb_found=yes, usb_found=no)
+	AC_SUBST(USB_CFLAGS)
+	AC_SUBST(USB_LIBS)
+	AC_CHECK_LIB(usb, usb_get_busses, dummy=yes,
+		AC_DEFINE(NEED_USB_GET_BUSSES, 1,
+			[Define to 1 if you need the usb_get_busses() function.]))
+	AC_CHECK_LIB(usb, usb_interrupt_read, dummy=yes,
+		AC_DEFINE(NEED_USB_INTERRUPT_READ, 1,
+			[Define to 1 if you need the usb_interrupt_read() function.]))
+])
+
+AC_DEFUN([AC_PATH_NETLINK], [
+	PKG_CHECK_MODULES(NETLINK, libnl-1, netlink_found=yes, netlink_found=no)
+	AC_SUBST(NETLINK_CFLAGS)
+	AC_SUBST(NETLINK_LIBS)
+])
+
+AC_DEFUN([AC_PATH_SNDFILE], [
+	PKG_CHECK_MODULES(SNDFILE, sndfile, sndfile_found=yes, sndfile_found=no)
+	AC_SUBST(SNDFILE_CFLAGS)
+	AC_SUBST(SNDFILE_LIBS)
+])
+
+AC_DEFUN([AC_ARG_BLUEZ], [
+	debug_enable=no
+	optimization_enable=yes
+	fortify_enable=yes
+	pie_enable=yes
+	sndfile_enable=${sndfile_found}
+	netlink_enable=no
+	hal_enable=${hal_found}
+	usb_enable=${usb_found}
+	alsa_enable=${alsa_found}
+	gstreamer_enable=${gstreamer_found}
+	audio_enable=yes
+	input_enable=yes
+	serial_enable=yes
+	network_enable=yes
+	service_enable=yes
+	tools_enable=yes
+	hidd_enable=no
+	pand_enable=no
+	dund_enable=no
+	cups_enable=no
+	test_enable=no
+	bccmd_enable=no
+	pcmcia_enable=no
+	hid2hci_enable=no
+	dfutool_enable=no
+	manpages_enable=yes
+	udevrules_enable=yes
+	configfiles_enable=yes
+	telephony_driver=dummy
+
+	AC_ARG_ENABLE(optimization, AC_HELP_STRING([--disable-optimization], [disable code optimization]), [
+		optimization_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(fortify, AC_HELP_STRING([--disable-fortify], [disable compile time buffer checks]), [
+		fortify_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pie, AC_HELP_STRING([--disable-pie], [disable position independent executables flag]), [
+		pie_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(network, AC_HELP_STRING([--disable-network], [disable network plugin]), [
+		network_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(serial, AC_HELP_STRING([--disable-serial], [disable serial plugin]), [
+		serial_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(input, AC_HELP_STRING([--disable-input], [disable input plugin]), [
+		input_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(audio, AC_HELP_STRING([--disable-audio], [disable audio plugin]), [
+		audio_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(service, AC_HELP_STRING([--disable-service], [disable service plugin]), [
+		service_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(gstreamer, AC_HELP_STRING([--enable-gstreamer], [enable GStreamer support]), [
+		gstreamer_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(alsa, AC_HELP_STRING([--enable-alsa], [enable ALSA support]), [
+		alsa_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(usb, AC_HELP_STRING([--enable-usb], [enable USB support]), [
+		usb_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(netlink, AC_HELP_STRING([--enable-netlink], [enable NETLINK support]), [
+		netlink_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(tools, AC_HELP_STRING([--enable-tools], [install Bluetooth utilities]), [
+		tools_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(bccmd, AC_HELP_STRING([--enable-bccmd], [install BCCMD interface utility]), [
+		bccmd_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pcmcia, AC_HELP_STRING([--enable-pcmcia], [install PCMCIA serial script]), [
+		pcmcia_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(hid2hci, AC_HELP_STRING([--enable-hid2hci], [install HID mode switching utility]), [
+		hid2hci_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(dfutool, AC_HELP_STRING([--enable-dfutool], [install DFU firmware upgrade utility]), [
+		dfutool_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(hidd, AC_HELP_STRING([--enable-hidd], [install HID daemon]), [
+		hidd_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(pand, AC_HELP_STRING([--enable-pand], [install PAN daemon]), [
+		pand_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(dund, AC_HELP_STRING([--enable-dund], [install DUN daemon]), [
+		dund_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(cups, AC_HELP_STRING([--enable-cups], [install CUPS backend support]), [
+		cups_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(test, AC_HELP_STRING([--enable-test], [install test programs]), [
+		test_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(manpages, AC_HELP_STRING([--enable-manpages], [install Bluetooth manual pages]), [
+		manpages_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(udevrules, AC_HELP_STRING([--enable-udevrules], [install Bluetooth udev rules]), [
+		udevrules_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(configfiles, AC_HELP_STRING([--enable-configfiles], [install Bluetooth configuration files]), [
+		configfiles_enable=${enableval}
+	])
+
+	AC_ARG_ENABLE(debug, AC_HELP_STRING([--enable-debug], [enable compiling with debugging information]), [
+		debug_enable=${enableval}
+	])
+
+	AC_ARG_WITH(telephony, AC_HELP_STRING([--with-telephony=DRIVER], [select telephony driver]), [
+		telephony_driver=${withval}
+	])
+
+	AC_SUBST([TELEPHONY_DRIVER], [telephony-${telephony_driver}.c])
+
+	if (test "${fortify_enable}" = "yes"); then
+		CFLAGS="$CFLAGS -D_FORTIFY_SOURCE=2"
+	fi
+
+	if (test "${pie_enable}" = "yes" && test "${ac_cv_prog_cc_pie}" = "yes"); then
+		CFLAGS="$CFLAGS -fPIC"
+		LDFLAGS="$LDFLAGS -pie"
+	fi
+
+	if (test "${debug_enable}" = "yes" && test "${ac_cv_prog_cc_g}" = "yes"); then
+		CFLAGS="$CFLAGS -g"
+	fi
+
+	if (test "${optimization_enable}" = "no"); then
+		CFLAGS="$CFLAGS -O0"
+	fi
+
+	if (test "${usb_enable}" = "yes" && test "${usb_found}" = "yes"); then
+		AC_DEFINE(HAVE_LIBUSB, 1, [Define to 1 if you have USB library.])
+	fi
+
+	AC_SUBST([BLUEZ_CFLAGS], ['-I$(top_builddir)/include'])
+	AC_SUBST([BLUEZ_LIBS], ['$(top_builddir)/lib/libbluetooth.la'])
+
+	AC_SUBST([GDBUS_CFLAGS], ['-I$(top_srcdir)/gdbus'])
+	AC_SUBST([GDBUS_LIBS], ['$(top_builddir)/gdbus/libgdbus.la'])
+
+	AC_SUBST([SBC_CFLAGS], ['-I$(top_srcdir)/sbc'])
+	AC_SUBST([SBC_LIBS], ['$(top_builddir)/sbc/libsbc.la'])
+
+	AM_CONDITIONAL(SNDFILE, test "${sndfile_enable}" = "yes" && test "${sndfile_found}" = "yes")
+	AM_CONDITIONAL(NETLINK, test "${netlink_enable}" = "yes" && test "${netlink_found}" = "yes")
+	AM_CONDITIONAL(USB, test "${usb_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(SBC, test "${alsa_enable}" = "yes" || test "${gstreamer_enable}" = "yes")
+	AM_CONDITIONAL(ALSA, test "${alsa_enable}" = "yes" && test "${alsa_found}" = "yes")
+	AM_CONDITIONAL(GSTREAMER, test "${gstreamer_enable}" = "yes" && test "${gstreamer_found}" = "yes")
+	AM_CONDITIONAL(AUDIOPLUGIN, test "${audio_enable}" = "yes")
+	AM_CONDITIONAL(INPUTPLUGIN, test "${input_enable}" = "yes")
+	AM_CONDITIONAL(SERIALPLUGIN, test "${serial_enable}" = "yes")
+	AM_CONDITIONAL(NETWORKPLUGIN, test "${network_enable}" = "yes")
+	AM_CONDITIONAL(SERVICEPLUGIN, test "${service_enable}" = "yes")
+	AM_CONDITIONAL(HIDD, test "${hidd_enable}" = "yes")
+	AM_CONDITIONAL(PAND, test "${pand_enable}" = "yes")
+	AM_CONDITIONAL(DUND, test "${dund_enable}" = "yes")
+	AM_CONDITIONAL(CUPS, test "${cups_enable}" = "yes")
+	AM_CONDITIONAL(TEST, test "${test_enable}" = "yes")
+	AM_CONDITIONAL(TOOLS, test "${tools_enable}" = "yes")
+	AM_CONDITIONAL(BCCMD, test "${bccmd_enable}" = "yes")
+	AM_CONDITIONAL(PCMCIA, test "${pcmcia_enable}" = "yes")
+	AM_CONDITIONAL(HID2HCI, test "${hid2hci_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(DFUTOOL, test "${dfutool_enable}" = "yes" && test "${usb_found}" = "yes")
+	AM_CONDITIONAL(MANPAGES, test "${manpages_enable}" = "yes")
+	AM_CONDITIONAL(UDEVRULES, test "${udevrules_enable}" = "yes")
+	AM_CONDITIONAL(CONFIGFILES, test "${configfiles_enable}" = "yes")
+])
diff --git a/audio/Android.mk b/audio/Android.mk
new file mode 100755
index 0000000..9537e33
--- /dev/null
+++ b/audio/Android.mk
@@ -0,0 +1,74 @@
+LOCAL_PATH:= $(call my-dir)
+
+# A2DP plugin
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	a2dp.c \
+	avdtp.c \
+	control.c \
+	device.c \
+	gateway.c \
+	headset.c \
+	ipc.c \
+	main.c \
+	manager.c \
+	module-bluetooth-sink.c \
+	sink.c \
+	source.c \
+	telephony-dummy.c \
+	unix.c
+
+LOCAL_CFLAGS:= \
+	-DVERSION=\"4.47\" \
+	-DSTORAGEDIR=\"/data/misc/bluetoothd\" \
+	-DCONFIGDIR=\"/etc/bluez\" \
+	-DANDROID \
+	-D__S_IFREG=0100000  # missing from bionic stat.h
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../include \
+	$(LOCAL_PATH)/../common \
+	$(LOCAL_PATH)/../gdbus \
+	$(LOCAL_PATH)/../src \
+	$(call include-path-for, glib) \
+	$(call include-path-for, dbus)
+
+LOCAL_SHARED_LIBRARIES := \
+	libbluetooth \
+	libbluetoothd \
+	libdbus
+
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/bluez-plugin
+LOCAL_UNSTRIPPED_PATH := $(TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED)/bluez-plugin
+LOCAL_MODULE := audio
+
+include $(BUILD_SHARED_LIBRARY)
+
+#
+# liba2dp
+# This is linked to Audioflinger so **LGPL only**
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	liba2dp.c \
+	ipc.c \
+	../sbc/sbc.c.arm \
+	../sbc/sbc_primitives.c \
+	../sbc/sbc_primitives_neon.c
+
+# to improve SBC performance
+LOCAL_CFLAGS:= -funroll-loops
+
+LOCAL_C_INCLUDES:= \
+	$(LOCAL_PATH)/../sbc \
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils
+
+LOCAL_MODULE := liba2dp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/audio/Makefile.am b/audio/Makefile.am
new file mode 100644
index 0000000..52ad212
--- /dev/null
+++ b/audio/Makefile.am
@@ -0,0 +1,87 @@
+
+BUILT_SOURCES = telephony.c
+
+if AUDIOPLUGIN
+plugindir = $(libdir)/bluetooth/plugins
+
+plugin_LTLIBRARIES = audio.la
+
+audio_la_SOURCES = main.c \
+	ipc.h ipc.c unix.h unix.c manager.h manager.c telephony.h \
+	device.h device.c headset.h headset.c gateway.h gateway.c \
+	avdtp.h avdtp.c a2dp.h a2dp.c sink.h sink.c source.h source.c \
+	control.h control.c
+
+nodist_audio_la_SOURCES = $(BUILT_SOURCES)
+
+audio_la_LDFLAGS = -module -avoid-version -no-undefined
+
+LDADD = $(top_builddir)/common/libhelper.a \
+		@GDBUS_LIBS@ @GLIB_LIBS@ @DBUS_LIBS@ @BLUEZ_LIBS@
+
+if ALSA
+alsadir = $(libdir)/alsa-lib
+
+alsa_LTLIBRARIES = libasound_module_pcm_bluetooth.la libasound_module_ctl_bluetooth.la
+
+libasound_module_pcm_bluetooth_la_SOURCES = pcm_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_pcm_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_pcm_.*
+libasound_module_pcm_bluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @ALSA_LIBS@
+libasound_module_pcm_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@
+
+libasound_module_ctl_bluetooth_la_SOURCES = ctl_bluetooth.c rtp.h ipc.h ipc.c
+libasound_module_ctl_bluetooth_la_LDFLAGS = -module -avoid-version #-export-symbols-regex [_]*snd_ctl_.*
+libasound_module_ctl_bluetooth_la_LIBADD = @BLUEZ_LIBS@ @ALSA_LIBS@
+libasound_module_ctl_bluetooth_la_CFLAGS = @ALSA_CFLAGS@ @BLUEZ_CFLAGS@
+
+if CONFIGFILES
+alsaconfdir = $(sysconfdir)/alsa
+
+alsaconf_DATA = bluetooth.conf
+endif
+endif
+
+if GSTREAMER
+gstreamerdir = $(libdir)/gstreamer-0.10
+
+gstreamer_LTLIBRARIES = libgstbluetooth.la
+
+libgstbluetooth_la_SOURCES = gstbluetooth.c \
+				gstsbcenc.h gstsbcenc.c \
+				gstsbcdec.h gstsbcdec.c \
+				gstsbcparse.h gstsbcparse.c \
+				gstavdtpsink.h gstavdtpsink.c \
+				gsta2dpsink.h gsta2dpsink.c \
+				gstsbcutil.h gstsbcutil.c \
+				gstrtpsbcpay.h gstrtpsbcpay.c \
+				rtp.h ipc.h ipc.c
+libgstbluetooth_la_LDFLAGS = -module -avoid-version
+libgstbluetooth_la_LIBADD = @SBC_LIBS@ @BLUEZ_LIBS@ @GSTREAMER_LIBS@ \
+						-lgstaudio-0.10 -lgstrtp-0.10
+libgstbluetooth_la_CFLAGS = -fvisibility=hidden -fno-strict-aliasing \
+			 @GSTREAMER_CFLAGS@ @BLUEZ_CFLAGS@ @SBC_CFLAGS@
+endif
+endif
+
+noinst_LTLIBRARIES = libipc.la
+
+libipc_la_SOURCES = ipc.h ipc.c
+
+noinst_PROGRAMS = ipctest
+
+ipctest_LDADD= libipc.la @SBC_LIBS@ @GLIB_LIBS@
+
+AM_CFLAGS = -fvisibility=hidden @SBC_CFLAGS@ \
+		@BLUEZ_CFLAGS@ @DBUS_CFLAGS@ @GLIB_CFLAGS@ @GDBUS_CFLAGS@
+
+CLEANFILES = $(BUILT_SOURCES)
+
+INCLUDES = -I$(top_srcdir)/common -I$(top_srcdir)/src
+
+EXTRA_DIST = audio.conf telephony-dummy.c telephony-maemo.c telephony-ofono.c \
+		bluetooth.conf
+
+MAINTAINERCLEANFILES = Makefile.in
+
+telephony.c: @TELEPHONY_DRIVER@
+	@if [ ! -e $@ ] ; then $(LN_S) $< $@ ; fi
diff --git a/audio/a2dp.c b/audio/a2dp.c
new file mode 100644
index 0000000..45be5d4
--- /dev/null
+++ b/audio/a2dp.c
@@ -0,0 +1,1626 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <dbus/dbus.h>
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "logging.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "sink.h"
+#include "source.h"
+#include "a2dp.h"
+#include "sdpd.h"
+
+/* The duration that streams without users are allowed to stay in
+ * STREAMING state. */
+#define SUSPEND_TIMEOUT 5
+#define RECONFIGURE_TIMEOUT 500
+
+#ifndef MIN
+# define MIN(x, y) ((x) < (y) ? (x) : (y))
+#endif
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+struct a2dp_sep {
+	uint8_t type;
+	uint8_t codec;
+	struct avdtp_local_sep *sep;
+	struct avdtp *session;
+	struct avdtp_stream *stream;
+	guint suspend_timer;
+	gboolean locked;
+	gboolean suspending;
+	gboolean starting;
+};
+
+struct a2dp_setup_cb {
+	a2dp_config_cb_t config_cb;
+	a2dp_stream_cb_t resume_cb;
+	a2dp_stream_cb_t suspend_cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct a2dp_setup {
+	struct audio_device *dev;
+	struct avdtp *session;
+	struct a2dp_sep *sep;
+	struct avdtp_stream *stream;
+	struct avdtp_error *err;
+	GSList *client_caps;
+	gboolean reconfigure;
+	gboolean canceled;
+	gboolean start;
+	GSList *cb;
+	int ref;
+};
+
+static DBusConnection *connection = NULL;
+
+struct a2dp_server {
+	bdaddr_t src;
+	GSList *sinks;
+	GSList *sources;
+	uint32_t source_record_id;
+	uint32_t sink_record_id;
+};
+
+static GSList *servers = NULL;
+static GSList *setups = NULL;
+static unsigned int cb_id = 0;
+
+static struct a2dp_setup *setup_ref(struct a2dp_setup *setup)
+{
+	setup->ref++;
+
+	debug("setup_ref(%p): ref=%d", setup, setup->ref);
+
+	return setup;
+}
+
+static void setup_free(struct a2dp_setup *s)
+{
+	debug("setup_free(%p)", s);
+	setups = g_slist_remove(setups, s);
+	if (s->session)
+		avdtp_unref(s->session);
+	g_slist_foreach(s->cb, (GFunc) g_free, NULL);
+	g_slist_free(s->cb);
+	g_free(s);
+}
+
+static void setup_unref(struct a2dp_setup *setup)
+{
+	setup->ref--;
+
+	debug("setup_unref(%p): ref=%d", setup, setup->ref);
+
+	if (setup->ref <= 0)
+		setup_free(setup);
+}
+
+static struct audio_device *a2dp_get_dev(struct avdtp *session)
+{
+	bdaddr_t src, dst;
+
+	avdtp_get_peers(session, &src, &dst);
+
+	return manager_find_device(NULL, &src, &dst, NULL, FALSE);
+}
+
+static gboolean finalize_config(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+		struct avdtp_stream *stream = s->err ? NULL : s->stream;
+
+		if (!cb->config_cb)
+			continue;
+
+		cb->config_cb(s->session, s->sep, stream, s->err,
+							cb->user_data);
+		cb->config_cb = NULL;
+		setup_unref(s);
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_config_errno(struct a2dp_setup *s, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	s->err = err ? &avdtp_err : NULL;
+
+	return finalize_config(s);
+}
+
+static gboolean finalize_resume(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb && cb->resume_cb) {
+			cb->resume_cb(s->session, s->err, cb->user_data);
+			cb->resume_cb = NULL;
+			setup_unref(s);
+		}
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_suspend(struct a2dp_setup *s)
+{
+	GSList *l;
+
+	setup_ref(s);
+	for (l = s->cb; l != NULL; l = l->next) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb->suspend_cb) {
+			cb->suspend_cb(s->session, s->err, cb->user_data);
+			cb->suspend_cb = NULL;
+			setup_unref(s);
+		}
+	}
+
+	setup_unref(s);
+	return FALSE;
+}
+
+static gboolean finalize_suspend_errno(struct a2dp_setup *s, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, -err);
+	s->err = err ? &avdtp_err : NULL;
+
+	return finalize_suspend(s);
+}
+
+static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
+{
+	GSList *l;
+
+	for (l = setups; l != NULL; l = l->next) {
+		struct a2dp_setup *setup = l->data;
+
+		if (setup->session == session)
+			return setup;
+	}
+
+	return NULL;
+}
+
+static struct a2dp_setup *find_setup_by_dev(struct audio_device *dev)
+{
+	GSList *l;
+
+	for (l = setups; l != NULL; l = l->next) {
+		struct a2dp_setup *setup = l->data;
+
+		if (setup->dev == dev)
+			return setup;
+	}
+
+	return NULL;
+}
+
+static void stream_state_changed(struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data)
+{
+	struct a2dp_sep *sep = user_data;
+
+	if (new_state != AVDTP_STATE_IDLE)
+		return;
+
+	if (sep->suspend_timer) {
+		g_source_remove(sep->suspend_timer);
+		sep->suspend_timer = 0;
+	}
+
+	if (sep->session) {
+		avdtp_unref(sep->session);
+		sep->session = NULL;
+	}
+
+	sep->stream = NULL;
+
+}
+
+static gboolean sbc_setconf_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				GSList *caps, uint8_t *err,
+				uint8_t *category, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct audio_device *dev;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap;
+	struct sbc_codec_cap *sbc_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Ind", sep);
+	else
+		debug("Source %p: Set_Configuration_Ind", sep);
+
+	dev = a2dp_get_dev(session);
+	if (!dev) {
+		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		*category = 0x00;
+		return FALSE;
+	}
+
+	/* Check bipool range */
+	for (codec_cap = NULL; caps; caps = g_slist_next(caps)) {
+		cap = caps->data;
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		if (cap->length < sizeof(struct sbc_codec_cap))
+			continue;
+
+		codec_cap = (void *) cap->data;
+
+		if (codec_cap->media_codec_type != A2DP_CODEC_SBC)
+			continue;
+
+		sbc_cap = (void *) codec_cap;
+
+		if (sbc_cap->min_bitpool < MIN_BITPOOL ||
+					sbc_cap->max_bitpool > MAX_BITPOOL) {
+			*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+			*category = AVDTP_MEDIA_CODEC;
+			return FALSE;
+		}
+
+		break;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, stream);
+
+	return TRUE;
+}
+
+static gboolean sbc_getcap_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				GSList **caps, uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct sbc_codec_cap sbc_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Capability_Ind", sep);
+	else
+		debug("Source %p: Get_Capability_Ind", sep);
+
+	*caps = NULL;
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	memset(&sbc_cap, 0, sizeof(struct sbc_codec_cap));
+
+	sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC;
+
+#ifdef ANDROID
+	sbc_cap.frequency = SBC_SAMPLING_FREQ_44100;
+#else
+	sbc_cap.frequency = ( SBC_SAMPLING_FREQ_48000 |
+				SBC_SAMPLING_FREQ_44100 |
+				SBC_SAMPLING_FREQ_32000 |
+				SBC_SAMPLING_FREQ_16000 );
+#endif
+
+	sbc_cap.channel_mode = ( SBC_CHANNEL_MODE_JOINT_STEREO |
+					SBC_CHANNEL_MODE_STEREO |
+					SBC_CHANNEL_MODE_DUAL_CHANNEL |
+					SBC_CHANNEL_MODE_MONO );
+
+	sbc_cap.block_length = ( SBC_BLOCK_LENGTH_16 |
+					SBC_BLOCK_LENGTH_12 |
+					SBC_BLOCK_LENGTH_8 |
+					SBC_BLOCK_LENGTH_4 );
+
+	sbc_cap.subbands = ( SBC_SUBBANDS_8 | SBC_SUBBANDS_4 );
+
+	sbc_cap.allocation_method = ( SBC_ALLOCATION_LOUDNESS |
+					SBC_ALLOCATION_SNR );
+
+	sbc_cap.min_bitpool = MIN_BITPOOL;
+	sbc_cap.max_bitpool = MAX_BITPOOL;
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap,
+						sizeof(sbc_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+	return TRUE;
+}
+
+static gboolean mpeg_setconf_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				GSList *caps, uint8_t *err,
+				uint8_t *category, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct audio_device *dev;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Ind", sep);
+	else
+		debug("Source %p: Set_Configuration_Ind", sep);
+
+	dev = a2dp_get_dev(session);
+	if (!dev) {
+		*err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		*category = 0x00;
+		return FALSE;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, stream);
+
+	return TRUE;
+}
+
+static gboolean mpeg_getcap_ind(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				GSList **caps, uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct avdtp_service_capability *media_transport, *media_codec;
+	struct mpeg_codec_cap mpeg_cap;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Capability_Ind", sep);
+	else
+		debug("Source %p: Get_Capability_Ind", sep);
+
+	*caps = NULL;
+
+	media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
+						NULL, 0);
+
+	*caps = g_slist_append(*caps, media_transport);
+
+	memset(&mpeg_cap, 0, sizeof(struct mpeg_codec_cap));
+
+	mpeg_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO;
+	mpeg_cap.cap.media_codec_type = A2DP_CODEC_MPEG12;
+
+	mpeg_cap.frequency = ( MPEG_SAMPLING_FREQ_48000 |
+				MPEG_SAMPLING_FREQ_44100 |
+				MPEG_SAMPLING_FREQ_32000 |
+				MPEG_SAMPLING_FREQ_24000 |
+				MPEG_SAMPLING_FREQ_22050 |
+				MPEG_SAMPLING_FREQ_16000 );
+
+	mpeg_cap.channel_mode = ( MPEG_CHANNEL_MODE_JOINT_STEREO |
+					MPEG_CHANNEL_MODE_STEREO |
+					MPEG_CHANNEL_MODE_DUAL_CHANNEL |
+					MPEG_CHANNEL_MODE_MONO );
+
+	mpeg_cap.layer = ( MPEG_LAYER_MP3 | MPEG_LAYER_MP2 | MPEG_LAYER_MP1 );
+
+	mpeg_cap.bitrate = 0xFFFF;
+
+	media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &mpeg_cap,
+						sizeof(mpeg_cap));
+
+	*caps = g_slist_append(*caps, media_codec);
+
+	return TRUE;
+}
+
+static void setconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+	struct audio_device *dev;
+	int ret;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Cfm", sep);
+	else
+		debug("Source %p: Set_Configuration_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+
+	if (err) {
+		if (setup) {
+			setup->err = err;
+			finalize_config(setup);
+		}
+		return;
+	}
+
+	avdtp_stream_add_cb(session, stream, stream_state_changed, a2dp_sep);
+	a2dp_sep->stream = stream;
+
+	if (!setup)
+		return;
+
+	dev = a2dp_get_dev(session);
+
+	/* Notify D-Bus interface of the new stream */
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SOURCE)
+		sink_new_stream(dev, session, setup->stream);
+	else
+		source_new_stream(dev, session, setup->stream);
+
+	ret = avdtp_open(session, stream);
+	if (ret < 0) {
+		error("Error on avdtp_open %s (%d)", strerror(-ret), -ret);
+		setup->stream = NULL;
+		finalize_config_errno(setup, ret);
+	}
+}
+
+static gboolean getconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Get_Configuration_Ind", sep);
+	else
+		debug("Source %p: Get_Configuration_Ind", sep);
+	return TRUE;
+}
+
+static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Set_Configuration_Cfm", sep);
+	else
+		debug("Source %p: Set_Configuration_Cfm", sep);
+}
+
+static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Open_Ind", sep);
+	else
+		debug("Source %p: Open_Ind", sep);
+	return TRUE;
+}
+
+static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Open_Cfm", sep);
+	else
+		debug("Source %p: Open_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (setup->reconfigure)
+		setup->reconfigure = FALSE;
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_config(setup);
+}
+
+static gboolean suspend_timeout(struct a2dp_sep *sep)
+{
+	if (avdtp_suspend(sep->session, sep->stream) == 0)
+		sep->suspending = TRUE;
+
+	sep->suspend_timer = 0;
+
+	avdtp_unref(sep->session);
+	sep->session = NULL;
+
+	return FALSE;
+}
+
+static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Start_Ind", sep);
+	else
+		debug("Source %p: Start_Ind", sep);
+
+	setup = find_setup_by_session(session);
+	if (setup) {
+		if (setup->canceled)
+			setup_unref(setup);
+		else
+			finalize_resume(setup);
+	}
+
+	if (!a2dp_sep->locked) {
+		a2dp_sep->session = avdtp_ref(session);
+		a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT,
+						(GSourceFunc) suspend_timeout,
+						a2dp_sep);
+	}
+
+	return TRUE;
+}
+
+static void start_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Start_Cfm", sep);
+	else
+		debug("Source %p: Start_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_resume(setup);
+}
+
+static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Suspend_Ind", sep);
+	else
+		debug("Source %p: Suspend_Ind", sep);
+
+	if (a2dp_sep->suspend_timer) {
+		g_source_remove(a2dp_sep->suspend_timer);
+		a2dp_sep->suspend_timer = 0;
+		avdtp_unref(a2dp_sep->session);
+		a2dp_sep->session = NULL;
+	}
+
+	return TRUE;
+}
+
+static void suspend_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+	gboolean start;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Suspend_Cfm", sep);
+	else
+		debug("Source %p: Suspend_Cfm", sep);
+
+	a2dp_sep->suspending = FALSE;
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	start = setup->start;
+	setup->start = FALSE;
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+		finalize_suspend(setup);
+	}
+	else
+		finalize_suspend_errno(setup, 0);
+
+	if (!start)
+		return;
+
+	if (err) {
+		setup->err = err;
+		finalize_suspend(setup);
+	} else if (avdtp_start(session, a2dp_sep->stream) < 0) {
+		struct avdtp_error start_err;
+		error("avdtp_start failed");
+		avdtp_error_init(&start_err, AVDTP_ERROR_ERRNO, EIO);
+		setup->err = err;
+		finalize_suspend(setup);
+	}
+}
+
+static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Close_Ind", sep);
+	else
+		debug("Source %p: Close_Ind", sep);
+
+	return TRUE;
+}
+
+static gboolean a2dp_reconfigure(gpointer data)
+{
+	struct a2dp_setup *setup = data;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	GSList *l;
+	int posix_err;
+
+	for (l = setup->client_caps; l != NULL; l = l->next) {
+		cap = l->data;
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec_cap = (void *) cap->data;
+		break;
+	}
+
+	if (!codec_cap) {
+		error("Cannot find capabilities to reconfigure");
+		posix_err = -EINVAL;
+		goto failed;
+	}
+
+	posix_err = avdtp_get_seps(setup->session, AVDTP_SEP_TYPE_SINK,
+					codec_cap->media_type,
+					codec_cap->media_codec_type,
+					&lsep, &rsep);
+	if (posix_err < 0) {
+		error("No matching ACP and INT SEPs found");
+		goto failed;
+	}
+
+	posix_err = avdtp_set_configuration(setup->session, rsep, lsep,
+						setup->client_caps,
+						&setup->stream);
+	if (posix_err < 0) {
+		error("avdtp_set_configuration: %s", strerror(-posix_err));
+		goto failed;
+	}
+
+	return FALSE;
+
+failed:
+	finalize_config_errno(setup, posix_err);
+	return FALSE;
+}
+
+static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Close_Cfm", sep);
+	else
+		debug("Source %p: Close_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+		finalize_config(setup);
+		return;
+	}
+
+	if (setup->reconfigure)
+		g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup);
+}
+
+static gboolean abort_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Abort_Ind", sep);
+	else
+		debug("Source %p: Abort_Ind", sep);
+
+	a2dp_sep->stream = NULL;
+
+	return TRUE;
+}
+
+static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: Abort_Cfm", sep);
+	else
+		debug("Source %p: Abort_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	setup_unref(setup);
+}
+
+static gboolean reconf_ind(struct avdtp *session, struct avdtp_local_sep *sep,
+				uint8_t *err, void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: ReConfigure_Ind", sep);
+	else
+		debug("Source %p: ReConfigure_Ind", sep);
+	return TRUE;
+}
+
+static void reconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data)
+{
+	struct a2dp_sep *a2dp_sep = user_data;
+	struct a2dp_setup *setup;
+
+	if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK)
+		debug("Sink %p: ReConfigure_Cfm", sep);
+	else
+		debug("Source %p: ReConfigure_Cfm", sep);
+
+	setup = find_setup_by_session(session);
+	if (!setup)
+		return;
+
+	if (setup->canceled) {
+		if (!err)
+			avdtp_close(session, stream);
+		setup_unref(setup);
+		return;
+	}
+
+	if (err) {
+		setup->stream = NULL;
+		setup->err = err;
+	}
+
+	finalize_config(setup);
+}
+
+static struct avdtp_sep_cfm cfm = {
+	.set_configuration	= setconf_cfm,
+	.get_configuration	= getconf_cfm,
+	.open			= open_cfm,
+	.start			= start_cfm,
+	.suspend		= suspend_cfm,
+	.close			= close_cfm,
+	.abort			= abort_cfm,
+	.reconfigure		= reconf_cfm
+};
+
+static struct avdtp_sep_ind sbc_ind = {
+	.get_capability		= sbc_getcap_ind,
+	.set_configuration	= sbc_setconf_ind,
+	.get_configuration	= getconf_ind,
+	.open			= open_ind,
+	.start			= start_ind,
+	.suspend		= suspend_ind,
+	.close			= close_ind,
+	.abort			= abort_ind,
+	.reconfigure		= reconf_ind
+};
+
+static struct avdtp_sep_ind mpeg_ind = {
+	.get_capability		= mpeg_getcap_ind,
+	.set_configuration	= mpeg_setconf_ind,
+	.get_configuration	= getconf_ind,
+	.open			= open_ind,
+	.start			= start_ind,
+	.suspend		= suspend_ind,
+	.close			= close_ind,
+	.abort			= abort_ind,
+	.reconfigure		= reconf_ind
+};
+
+static sdp_record_t *a2dp_record(uint8_t type)
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVDTP_UUID, ver = 0x0100, feat = 0x000F;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	if (type == AVDTP_SEP_TYPE_SOURCE)
+		sdp_uuid16_create(&a2dp_uuid, AUDIO_SOURCE_SVCLASS_ID);
+	else
+		sdp_uuid16_create(&a2dp_uuid, AUDIO_SINK_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &a2dp_uuid);
+	sdp_set_service_classes(record, svclass_id);
+
+	sdp_uuid16_create(&profile[0].uuid, ADVANCED_AUDIO_PROFILE_ID);
+	profile[0].version = 0x0100;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap_uuid);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avdtp_uuid, AVDTP_UUID);
+	proto[1] = sdp_list_append(0, &avdtp_uuid);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	if (type == AVDTP_SEP_TYPE_SOURCE)
+		sdp_set_info_attr(record, "Audio Source", 0, 0);
+	else
+		sdp_set_info_attr(record, "Audio Sink", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static struct a2dp_sep *a2dp_add_sep(struct a2dp_server *server, uint8_t type,
+					uint8_t codec)
+{
+	struct a2dp_sep *sep;
+	GSList **l;
+	uint32_t *record_id;
+	sdp_record_t *record;
+	struct avdtp_sep_ind *ind;
+
+	sep = g_new0(struct a2dp_sep, 1);
+
+	ind = (codec == A2DP_CODEC_MPEG12) ? &mpeg_ind : &sbc_ind;
+	sep->sep = avdtp_register_sep(&server->src, type,
+					AVDTP_MEDIA_TYPE_AUDIO, codec, ind,
+					&cfm, sep);
+	if (sep->sep == NULL) {
+		g_free(sep);
+		return NULL;
+	}
+
+	sep->codec = codec;
+	sep->type = type;
+
+	if (type == AVDTP_SEP_TYPE_SOURCE) {
+		l = &server->sources;
+		record_id = &server->source_record_id;
+	} else {
+		l = &server->sinks;
+		record_id = &server->sink_record_id;
+	}
+
+	if (*record_id != 0)
+		goto add;
+
+	record = a2dp_record(type);
+	if (!record) {
+		error("Unable to allocate new service record");
+		avdtp_unregister_sep(sep->sep);
+		g_free(sep);
+		return NULL;
+	}
+
+	if (add_record_to_server(&server->src, record) < 0) {
+		error("Unable to register A2DP service record");\
+		sdp_record_free(record);
+		avdtp_unregister_sep(sep->sep);
+		g_free(sep);
+		return NULL;
+	}
+	*record_id = record->handle;
+
+add:
+	*l = g_slist_append(*l, sep);
+
+	return sep;
+}
+
+static struct a2dp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct a2dp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+	int sbc_srcs = 1, sbc_sinks = 1;
+	int mpeg12_srcs = 0, mpeg12_sinks = 0;
+	gboolean source = TRUE, sink = FALSE;
+	char *str;
+	GError *err = NULL;
+	int i;
+	struct a2dp_server *server;
+
+	if (!config)
+		goto proceed;
+
+	str = g_key_file_get_string(config, "General", "Enable", &err);
+
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		if (strstr(str, "Sink"))
+			source = TRUE;
+		if (strstr(str, "Source"))
+			sink = TRUE;
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "General", "Disable", &err);
+
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		if (strstr(str, "Sink"))
+			source = FALSE;
+		if (strstr(str, "Source"))
+			sink = FALSE;
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "SBCSources", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		sbc_srcs = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "MPEG12Sources", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		mpeg12_srcs = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "SBCSinks", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		sbc_sinks = atoi(str);
+		g_free(str);
+	}
+
+	str = g_key_file_get_string(config, "A2DP", "MPEG12Sinks", &err);
+	if (err) {
+		debug("audio.conf: %s", err->message);
+		g_clear_error(&err);
+	} else {
+		mpeg12_sinks = atoi(str);
+		g_free(str);
+	}
+
+proceed:
+	if (!connection)
+		connection = dbus_connection_ref(conn);
+
+	server = find_server(servers, src);
+	if (!server) {
+		int av_err;
+
+		server = g_new0(struct a2dp_server, 1);
+		if (!server)
+			return -ENOMEM;
+
+		av_err = avdtp_init(src, config);
+		if (av_err < 0)
+			return av_err;
+
+		bacpy(&server->src, src);
+		servers = g_slist_append(servers, server);
+	}
+
+	if (source) {
+		for (i = 0; i < sbc_srcs; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_SBC);
+
+		for (i = 0; i < mpeg12_srcs; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SOURCE,
+					A2DP_CODEC_MPEG12);
+	}
+
+	if (sink) {
+		for (i = 0; i < sbc_sinks; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_SBC);
+
+		for (i = 0; i < mpeg12_sinks; i++)
+			a2dp_add_sep(server, AVDTP_SEP_TYPE_SINK,
+					A2DP_CODEC_MPEG12);
+	}
+
+	return 0;
+}
+
+static void a2dp_unregister_sep(struct a2dp_sep *sep)
+{
+	avdtp_unregister_sep(sep->sep);
+	g_free(sep);
+}
+
+void a2dp_unregister(const bdaddr_t *src)
+{
+	struct a2dp_server *server;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	g_slist_foreach(server->sinks, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_free(server->sinks);
+
+	g_slist_foreach(server->sources, (GFunc) a2dp_unregister_sep, NULL);
+	g_slist_free(server->sources);
+
+	avdtp_exit(src);
+
+	if (server->source_record_id)
+		remove_record_from_server(server->source_record_id);
+
+	if (server->sink_record_id)
+		remove_record_from_server(server->sink_record_id);
+
+	servers = g_slist_remove(servers, server);
+	g_free(server);
+
+	if (servers)
+		return;
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
+
+struct a2dp_sep *a2dp_get(struct avdtp *session,
+				struct avdtp_remote_sep *rsep)
+{
+	GSList *l;
+	struct a2dp_server *server;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	bdaddr_t src;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return NULL;
+
+	cap = avdtp_get_codec(rsep);
+	codec_cap = (void *) cap->data;
+
+	if (avdtp_get_type(rsep) == AVDTP_SEP_TYPE_SINK)
+		l = server->sources;
+	else
+		l = server->sinks;
+
+	for (; l != NULL; l = l->next) {
+		struct a2dp_sep *sep = l->data;
+
+		if (sep->locked)
+			continue;
+
+		if (sep->codec != codec_cap->media_codec_type)
+			continue;
+
+		if (!sep->stream || avdtp_has_stream(session, sep->stream))
+			return sep;
+	}
+
+	return NULL;
+}
+
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	GSList *l;
+	struct a2dp_server *server;
+	struct a2dp_setup *setup;
+	struct a2dp_sep *tmp;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_remote_sep *rsep;
+	struct avdtp_service_capability *cap;
+	struct avdtp_media_codec_capability *codec_cap = NULL;
+	int posix_err;
+	bdaddr_t src;
+	uint8_t remote_type;
+
+	avdtp_get_peers(session, &src, NULL);
+	server = find_server(servers, &src);
+	if (!server)
+		return 0;
+
+	for (l = caps; l != NULL; l = l->next) {
+		cap = l->data;
+
+		if (cap->category != AVDTP_MEDIA_CODEC)
+			continue;
+
+		codec_cap = (void *) cap->data;
+		break;
+	}
+
+	if (!codec_cap)
+		return 0;
+
+	if (sep->codec != codec_cap->media_codec_type)
+		return 0;
+
+	debug("a2dp_config: selected SEP %p", sep->sep);
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->config_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+	setup->client_caps = caps;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		if (sep->type == AVDTP_SEP_TYPE_SOURCE) {
+			l = server->sources;
+			remote_type = AVDTP_SEP_TYPE_SINK;
+		} else {
+			remote_type = AVDTP_SEP_TYPE_SOURCE;
+			l = server->sinks;
+		}
+
+		for (; l != NULL; l = l->next) {
+			tmp = l->data;
+			if (avdtp_has_stream(session, tmp->stream))
+				break;
+		}
+
+		if (l != NULL) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, tmp->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+			break;
+		}
+
+		if (avdtp_get_seps(session, remote_type,
+				codec_cap->media_type,
+				codec_cap->media_codec_type,
+				&lsep, &rsep) < 0) {
+			error("No matching ACP and INT SEPs found");
+			goto failed;
+		}
+
+		posix_err = avdtp_set_configuration(session, rsep, lsep,
+							caps, &setup->stream);
+		if (posix_err < 0) {
+			error("avdtp_set_configuration: %s",
+				strerror(-posix_err));
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_OPEN:
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_stream_has_capabilities(setup->stream, caps)) {
+			debug("Configuration match: resuming");
+			g_idle_add((GSourceFunc) finalize_config, setup);
+		} else if (!setup->reconfigure) {
+			setup->reconfigure = TRUE;
+			if (avdtp_close(session, sep->stream) < 0) {
+				error("avdtp_close failed");
+				goto failed;
+			}
+		}
+		break;
+	default:
+		error("SEP in bad state for requesting a new stream");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->resume_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		if (avdtp_start(session, sep->stream) < 0) {
+			error("avdtp_start failed");
+			goto failed;
+		}
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (!sep->suspending && sep->suspend_timer) {
+			g_source_remove(sep->suspend_timer);
+			sep->suspend_timer = 0;
+			avdtp_unref(sep->session);
+			sep->session = NULL;
+		}
+		if (sep->suspending)
+			setup->start = TRUE;
+		else
+			g_idle_add((GSourceFunc) finalize_resume, setup);
+		break;
+	default:
+		error("SEP in bad state for resume");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+
+	cb_data = g_new0(struct a2dp_setup_cb, 1);
+	cb_data->suspend_cb = cb;
+	cb_data->user_data = user_data;
+	cb_data->id = ++cb_id;
+
+	setup = find_setup_by_session(session);
+	if (!setup) {
+		setup = g_new0(struct a2dp_setup, 1);
+		setup->session = avdtp_ref(session);
+		setup->dev = a2dp_get_dev(session);
+		setups = g_slist_append(setups, setup);
+	}
+
+	setup_ref(setup);
+	setup->cb = g_slist_append(setup->cb, cb_data);
+	setup->sep = sep;
+	setup->stream = sep->stream;
+
+	switch (avdtp_sep_get_state(sep->sep)) {
+	case AVDTP_STATE_IDLE:
+		error("a2dp_suspend: no stream to suspend");
+		goto failed;
+		break;
+	case AVDTP_STATE_OPEN:
+		g_idle_add((GSourceFunc) finalize_suspend, setup);
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_suspend(session, sep->stream) < 0) {
+			error("avdtp_suspend failed");
+			goto failed;
+		}
+		break;
+	default:
+		error("SEP in bad state for suspend");
+		goto failed;
+	}
+
+	return cb_data->id;
+
+failed:
+	setup_unref(setup);
+	cb_id--;
+	return 0;
+}
+
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id)
+{
+	struct a2dp_setup_cb *cb_data;
+	struct a2dp_setup *setup;
+	GSList *l;
+
+	debug("a2dp_cancel()");
+
+	setup = find_setup_by_dev(dev);
+	if (!setup)
+		return FALSE;
+
+	for (cb_data = NULL, l = setup->cb; l != NULL; l = g_slist_next(l)) {
+		struct a2dp_setup_cb *cb = l->data;
+
+		if (cb->id == id) {
+			cb_data = cb;
+			break;
+		}
+	}
+
+	if (!cb_data)
+		error("a2dp_cancel: no matching callback with id %u", id);
+
+	setup->cb = g_slist_remove(setup->cb, cb_data);
+	g_free(cb_data);
+
+	if (!setup->cb) {
+		setup->canceled = TRUE;
+		setup->sep = NULL;
+	}
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session)
+{
+	if (sep->locked)
+		return FALSE;
+
+	debug("SEP %p locked", sep->sep);
+	sep->locked = TRUE;
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session)
+{
+	avdtp_state_t state;
+
+	state = avdtp_sep_get_state(sep->sep);
+
+	sep->locked = FALSE;
+
+	debug("SEP %p unlocked", sep->sep);
+
+	if (!sep->stream || state == AVDTP_STATE_IDLE)
+		return TRUE;
+
+	switch (state) {
+	case AVDTP_STATE_OPEN:
+		/* Set timer here */
+		break;
+	case AVDTP_STATE_STREAMING:
+		if (avdtp_suspend(session, sep->stream) == 0)
+			sep->suspending = TRUE;
+		break;
+	default:
+		break;
+	}
+
+	return TRUE;
+}
+
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep)
+{
+	return sep->locked;
+}
+
+static int stream_cmp(gconstpointer data, gconstpointer user_data)
+{
+	const struct a2dp_sep *sep = data;
+	const struct avdtp_stream *stream = user_data;
+
+	return (sep->stream != stream);
+}
+
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+				struct avdtp_stream *stream)
+{
+	struct a2dp_server *server;
+	bdaddr_t src, dst;
+	GSList *l;
+
+	avdtp_get_peers(session, &src, &dst);
+
+	for (l = servers; l; l = l->next) {
+		server = l->data;
+
+		if (bacmp(&src, &server->src) == 0)
+			break;
+	}
+
+	if (!l)
+		return NULL;
+
+	l = g_slist_find_custom(server->sources, stream, stream_cmp);
+	if (l)
+		return l->data;
+
+	l = g_slist_find_custom(server->sinks, stream, stream_cmp);
+	if (l)
+		return l->data;
+
+	return NULL;
+}
diff --git a/audio/a2dp.h b/audio/a2dp.h
new file mode 100644
index 0000000..f08c643
--- /dev/null
+++ b/audio/a2dp.h
@@ -0,0 +1,149 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define A2DP_CODEC_SBC			0x00
+#define A2DP_CODEC_MPEG12		0x01
+#define A2DP_CODEC_MPEG24		0x02
+#define A2DP_CODEC_ATRAC		0x03
+
+#define SBC_SAMPLING_FREQ_16000		(1 << 3)
+#define SBC_SAMPLING_FREQ_32000		(1 << 2)
+#define SBC_SAMPLING_FREQ_44100		(1 << 1)
+#define SBC_SAMPLING_FREQ_48000		1
+
+#define SBC_CHANNEL_MODE_MONO		(1 << 3)
+#define SBC_CHANNEL_MODE_DUAL_CHANNEL	(1 << 2)
+#define SBC_CHANNEL_MODE_STEREO		(1 << 1)
+#define SBC_CHANNEL_MODE_JOINT_STEREO	1
+
+#define SBC_BLOCK_LENGTH_4		(1 << 3)
+#define SBC_BLOCK_LENGTH_8		(1 << 2)
+#define SBC_BLOCK_LENGTH_12		(1 << 1)
+#define SBC_BLOCK_LENGTH_16		1
+
+#define SBC_SUBBANDS_4			(1 << 1)
+#define SBC_SUBBANDS_8			1
+
+#define SBC_ALLOCATION_SNR		(1 << 1)
+#define SBC_ALLOCATION_LOUDNESS		1
+
+#define MPEG_CHANNEL_MODE_MONO		(1 << 3)
+#define MPEG_CHANNEL_MODE_DUAL_CHANNEL	(1 << 2)
+#define MPEG_CHANNEL_MODE_STEREO	(1 << 1)
+#define MPEG_CHANNEL_MODE_JOINT_STEREO	1
+
+#define MPEG_LAYER_MP1			(1 << 2)
+#define MPEG_LAYER_MP2			(1 << 1)
+#define MPEG_LAYER_MP3			1
+
+#define MPEG_SAMPLING_FREQ_16000	(1 << 5)
+#define MPEG_SAMPLING_FREQ_22050	(1 << 4)
+#define MPEG_SAMPLING_FREQ_24000	(1 << 3)
+#define MPEG_SAMPLING_FREQ_32000	(1 << 2)
+#define MPEG_SAMPLING_FREQ_44100	(1 << 1)
+#define MPEG_SAMPLING_FREQ_48000	1
+
+#define MAX_BITPOOL 64
+#define MIN_BITPOOL 2
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct sbc_codec_cap {
+	struct avdtp_media_codec_capability cap;
+	uint8_t channel_mode:4;
+	uint8_t frequency:4;
+	uint8_t allocation_method:2;
+	uint8_t subbands:2;
+	uint8_t block_length:4;
+	uint8_t min_bitpool;
+	uint8_t max_bitpool;
+} __attribute__ ((packed));
+
+struct mpeg_codec_cap {
+	struct avdtp_media_codec_capability cap;
+	uint8_t channel_mode:4;
+	uint8_t crc:1;
+	uint8_t layer:3;
+	uint8_t frequency:6;
+	uint8_t mpf:1;
+	uint8_t rfa:1;
+	uint16_t bitrate;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct sbc_codec_cap {
+	struct avdtp_media_codec_capability cap;
+	uint8_t frequency:4;
+	uint8_t channel_mode:4;
+	uint8_t block_length:4;
+	uint8_t subbands:2;
+	uint8_t allocation_method:2;
+	uint8_t min_bitpool;
+	uint8_t max_bitpool;
+} __attribute__ ((packed));
+
+struct mpeg_codec_cap {
+	struct avdtp_media_codec_capability cap;
+	uint8_t layer:3;
+	uint8_t crc:1;
+	uint8_t channel_mode:4;
+	uint8_t rfa:1;
+	uint8_t mpf:1;
+	uint8_t frequency:6;
+	uint16_t bitrate;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct a2dp_sep;
+
+typedef void (*a2dp_config_cb_t) (struct avdtp *session, struct a2dp_sep *sep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+typedef void (*a2dp_stream_cb_t) (struct avdtp *session,
+					struct avdtp_error *err,
+					void *user_data);
+
+int a2dp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void a2dp_unregister(const bdaddr_t *src);
+
+struct a2dp_sep *a2dp_get(struct avdtp *session, struct avdtp_remote_sep *sep);
+unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_config_cb_t cb, GSList *caps,
+				void *user_data);
+unsigned int a2dp_resume(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
+unsigned int a2dp_suspend(struct avdtp *session, struct a2dp_sep *sep,
+				a2dp_stream_cb_t cb, void *user_data);
+gboolean a2dp_cancel(struct audio_device *dev, unsigned int id);
+
+gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session);
+gboolean a2dp_sep_get_lock(struct a2dp_sep *sep);
+struct a2dp_sep *a2dp_get_sep(struct avdtp *session,
+				struct avdtp_stream *stream);
diff --git a/audio/audio.conf b/audio/audio.conf
new file mode 100644
index 0000000..a093ffb
--- /dev/null
+++ b/audio/audio.conf
@@ -0,0 +1,43 @@
+# Configuration file for the audio service
+
+# This section contains options which are not specific to any
+# particular interface
+[General]
+Enable=Source,Control,Sink
+Disable=Headset,Gateway
+
+# Switch to master role for incoming connections (defaults to true)
+#Master=true
+
+# If we want to disable support for specific services
+# Defaults to supporting all implemented services
+#Disable=Control,Source
+
+# SCO routing. Either PCM or HCI (in which case audio is routed to/from ALSA)
+# Defaults to HCI
+#SCORouting=PCM
+
+# Automatically connect both A2DP and HFP/HSP profiles for incoming
+# connections. Some headsets that support both profiles will only connect the
+# other one automatically so the default setting of true is usually a good
+# idea.
+#AutoConnect=true
+
+# Headset interface specific options (i.e. options which affect how the audio
+# service interacts with remote headset devices)
+#[Headset]
+
+# Set to true to support HFP (in addition to HSP only which is the default)
+# Defaults to false
+#HFP=true
+
+# Maximum number of connected HSP/HFP devices per adapter. Defaults to 1
+#MaxConnections=1
+
+# Just an example of potential config options for the other interfaces
+[A2DP]
+SBCSources=1
+MPEG12Sources=0
+
+[AVRCP]
+InputDeviceName=AVRCP
diff --git a/audio/avdtp.c b/audio/avdtp.c
new file mode 100644
index 0000000..939db10
--- /dev/null
+++ b/audio/avdtp.c
@@ -0,0 +1,3510 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+
+#include "logging.h"
+
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "device.h"
+#include "manager.h"
+#include "control.h"
+#include "avdtp.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "sink.h"
+#include "source.h"
+
+#include <bluetooth/l2cap.h>
+
+#define AVDTP_PSM 25
+
+#define MAX_SEID 0x3E
+
+#define AVDTP_DISCOVER				0x01
+#define AVDTP_GET_CAPABILITIES			0x02
+#define AVDTP_SET_CONFIGURATION			0x03
+#define AVDTP_GET_CONFIGURATION			0x04
+#define AVDTP_RECONFIGURE			0x05
+#define AVDTP_OPEN				0x06
+#define AVDTP_START				0x07
+#define AVDTP_CLOSE				0x08
+#define AVDTP_SUSPEND				0x09
+#define AVDTP_ABORT				0x0A
+#define AVDTP_SECURITY_CONTROL			0x0B
+
+#define AVDTP_PKT_TYPE_SINGLE			0x00
+#define AVDTP_PKT_TYPE_START			0x01
+#define AVDTP_PKT_TYPE_CONTINUE			0x02
+#define AVDTP_PKT_TYPE_END			0x03
+
+#define AVDTP_MSG_TYPE_COMMAND			0x00
+#define AVDTP_MSG_TYPE_ACCEPT			0x02
+#define AVDTP_MSG_TYPE_REJECT			0x03
+
+#define REQ_TIMEOUT 4
+#define DISCONNECT_TIMEOUT 1
+#define STREAM_TIMEOUT 20
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_common_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint8_t signal_id:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint8_t no_of_packets;
+	uint8_t signal_id:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+	uint8_t message_type:2;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct seid_info {
+	uint8_t rfa0:1;
+	uint8_t inuse:1;
+	uint8_t seid:6;
+	uint8_t rfa2:3;
+	uint8_t type:1;
+	uint8_t media_type:4;
+} __attribute__ ((packed));
+
+struct seid {
+	uint8_t rfa0:2;
+	uint8_t seid:6;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_common_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+	uint8_t rfa0:2;
+	uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+	uint8_t no_of_packets;
+	uint8_t rfa0:2;
+	uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct seid_info {
+	uint8_t seid:6;
+	uint8_t inuse:1;
+	uint8_t rfa0:1;
+	uint8_t media_type:4;
+	uint8_t type:1;
+	uint8_t rfa2:3;
+} __attribute__ ((packed));
+
+struct seid {
+	uint8_t seid:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+/* packets */
+
+struct discover_resp {
+	struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct start_req {
+	struct seid first_seid;
+	struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct suspend_req {
+	struct seid first_seid;
+	struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct seid_rej {
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct conf_rej {
+	uint8_t category;
+	uint8_t error;
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct seid_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+struct setconf_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+	uint8_t rfa1:2;
+	uint8_t int_seid:6;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+	uint8_t rfa0:2;
+	uint8_t acp_seid:6;
+
+	uint8_t serv_cap;
+	uint8_t serv_cap_len;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+	uint8_t int_seid:6;
+	uint8_t rfa1:2;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+	uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+	uint8_t acp_seid:6;
+	uint8_t rfa0:2;
+
+	uint8_t serv_cap;
+	uint8_t serv_cap_len;
+
+	uint8_t caps[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct in_buf {
+	gboolean active;
+	int no_of_packets;
+	uint8_t transaction;
+	uint8_t message_type;
+	uint8_t signal_id;
+	uint8_t buf[1024];
+	uint8_t data_size;
+};
+
+struct pending_req {
+	uint8_t transaction;
+	uint8_t signal_id;
+	void *data;
+	size_t data_size;
+	struct avdtp_stream *stream; /* Set if the request targeted a stream */
+	guint timeout;
+};
+
+struct avdtp_remote_sep {
+	uint8_t seid;
+	uint8_t type;
+	uint8_t media_type;
+	struct avdtp_service_capability *codec;
+	GSList *caps; /* of type struct avdtp_service_capability */
+	struct avdtp_stream *stream;
+};
+
+struct avdtp_server {
+	bdaddr_t src;
+	GIOChannel *io;
+	GSList *seps;
+	GSList *sessions;
+};
+
+struct avdtp_local_sep {
+	avdtp_state_t state;
+	struct avdtp_stream *stream;
+	struct seid_info info;
+	uint8_t codec;
+	GSList *caps;
+	struct avdtp_sep_ind *ind;
+	struct avdtp_sep_cfm *cfm;
+	void *user_data;
+	struct avdtp_server *server;
+};
+
+struct stream_callback {
+	avdtp_stream_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avdtp_state_callback {
+	avdtp_session_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avdtp_stream {
+	GIOChannel *io;
+	uint16_t imtu;
+	uint16_t omtu;
+	struct avdtp *session;
+	struct avdtp_local_sep *lsep;
+	uint8_t rseid;
+	GSList *caps;
+	GSList *callbacks;
+	struct avdtp_service_capability *codec;
+	guint io_id;		/* Transport GSource ID */
+	guint timer;		/* Waiting for other side to close or open
+				 * the transport channel */
+	gboolean open_acp;	/* If we are in ACT role for Open */
+	gboolean close_int;	/* If we are in INT role for Close */
+	gboolean abort_int;	/* If we are in INT role for Abort */
+	guint idle_timer;
+};
+
+/* Structure describing an AVDTP connection between two devices */
+
+struct avdtp {
+	int ref;
+	int free_lock;
+
+	struct avdtp_server *server;
+	bdaddr_t dst;
+
+	avdtp_session_state_t state;
+
+	/* True if the session should be automatically disconnected */
+	gboolean auto_dc;
+
+	GIOChannel *io;
+	guint io_id;
+
+	GSList *seps; /* Elements of type struct avdtp_remote_sep * */
+
+	GSList *streams; /* Elements of type struct avdtp_stream * */
+
+	GSList *req_queue; /* Elements of type struct pending_req * */
+	GSList *prio_queue; /* Same as req_queue but is processed before it */
+
+	struct avdtp_stream *pending_open;
+
+	uint16_t imtu;
+	uint16_t omtu;
+
+	struct in_buf in;
+
+	char *buf;
+
+	avdtp_discover_cb_t discov_cb;
+	void *user_data;
+
+	struct pending_req *req;
+
+	guint dc_timer;
+
+	/* Attempt stream setup instead of disconnecting */
+	gboolean stream_setup;
+
+	DBusPendingCall *pending_auth;
+};
+
+static GSList *servers = NULL;
+
+static GSList *avdtp_callbacks = NULL;
+
+static gboolean auto_connect = TRUE;
+
+static int send_request(struct avdtp *session, gboolean priority,
+			struct avdtp_stream *stream, uint8_t signal_id,
+			void *buffer, size_t size);
+static gboolean avdtp_parse_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size);
+static gboolean avdtp_parse_rej(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size);
+static int process_queue(struct avdtp *session);
+static void connection_lost(struct avdtp *session, int err);
+static void avdtp_sep_set_state(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				avdtp_state_t state);
+
+static struct avdtp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct avdtp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+static const char *avdtp_statestr(avdtp_state_t state)
+{
+	switch (state) {
+	case AVDTP_STATE_IDLE:
+		return "IDLE";
+	case AVDTP_STATE_CONFIGURED:
+		return "CONFIGURED";
+	case AVDTP_STATE_OPEN:
+		return "OPEN";
+	case AVDTP_STATE_STREAMING:
+		return "STREAMING";
+	case AVDTP_STATE_CLOSING:
+		return "CLOSING";
+	case AVDTP_STATE_ABORTING:
+		return "ABORTING";
+	default:
+		return "<unknown state>";
+	}
+}
+
+static gboolean try_send(int sk, void *data, size_t len)
+{
+	int err;
+
+	do {
+		err = send(sk, data, len, 0);
+	} while (err < 0 && errno == EINTR);
+
+	if (err < 0) {
+		error("send: %s (%d)", strerror(errno), errno);
+		return FALSE;
+	} else if ((size_t) err != len) {
+		error("try_send: complete buffer not sent (%d/%zu bytes)",
+								err, len);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean avdtp_send(struct avdtp *session, uint8_t transaction,
+				uint8_t message_type, uint8_t signal_id,
+				void *data, size_t len)
+{
+	unsigned int cont_fragments, sent;
+	struct avdtp_start_header start;
+	struct avdtp_continue_header cont;
+	int sock;
+
+	if (session->io == NULL) {
+		error("avdtp_send: session is closed");
+		return FALSE;
+	}
+
+	sock = g_io_channel_unix_get_fd(session->io);
+
+	/* Single packet - no fragmentation */
+	if (sizeof(struct avdtp_single_header) + len <= session->omtu) {
+		struct avdtp_single_header single;
+
+		memset(&single, 0, sizeof(single));
+
+		single.transaction = transaction;
+		single.packet_type = AVDTP_PKT_TYPE_SINGLE;
+		single.message_type = message_type;
+		single.signal_id = signal_id;
+
+		memcpy(session->buf, &single, sizeof(single));
+		memcpy(session->buf + sizeof(single), data, len);
+
+		return try_send(sock, session->buf, sizeof(single) + len);
+	}
+
+	/* Count the number of needed fragments */
+	cont_fragments = (len - (session->omtu - sizeof(start))) /
+					(session->omtu - sizeof(cont)) + 1;
+
+	debug("avdtp_send: %zu bytes split into %d fragments", len,
+							cont_fragments + 1);
+
+	/* Send the start packet */
+	memset(&start, 0, sizeof(start));
+	start.transaction = transaction;
+	start.packet_type = AVDTP_PKT_TYPE_START;
+	start.message_type = message_type;
+	start.no_of_packets = cont_fragments + 1;
+	start.signal_id = signal_id;
+
+	memcpy(session->buf, &start, sizeof(start));
+	memcpy(session->buf + sizeof(start), data,
+					session->omtu - sizeof(start));
+
+	if (!try_send(sock, session->buf, session->omtu))
+		return FALSE;
+
+	debug("avdtp_send: first packet with %zu bytes sent",
+						session->omtu - sizeof(start));
+
+	sent = session->omtu - sizeof(start);
+
+	/* Send the continue fragments and the end packet */
+	while (sent < len) {
+		int left, to_copy;
+
+		left = len - sent;
+		if (left + sizeof(cont) > session->omtu) {
+			cont.packet_type = AVDTP_PKT_TYPE_CONTINUE;
+			to_copy = session->omtu - sizeof(cont);
+			debug("avdtp_send: sending continue with %d bytes",
+								to_copy);
+		} else {
+			cont.packet_type = AVDTP_PKT_TYPE_END;
+			to_copy = left;
+			debug("avdtp_send: sending end with %d bytes",
+								to_copy);
+		}
+
+		cont.transaction = transaction;
+		cont.message_type = message_type;
+
+		memcpy(session->buf, &cont, sizeof(cont));
+		memcpy(session->buf + sizeof(cont), data + sent, to_copy);
+
+		if (!try_send(sock, session->buf, to_copy + sizeof(cont)))
+			return FALSE;
+
+		sent += to_copy;
+	}
+
+	return TRUE;
+}
+
+static void pending_req_free(struct pending_req *req)
+{
+	if (req->timeout)
+		g_source_remove(req->timeout);
+	g_free(req->data);
+	g_free(req);
+}
+
+static void close_stream(struct avdtp_stream *stream)
+{
+	int sock;
+
+	if (stream->io == NULL)
+		return;
+
+	sock = g_io_channel_unix_get_fd(stream->io);
+
+	shutdown(sock, SHUT_RDWR);
+
+	g_io_channel_shutdown(stream->io, FALSE, NULL);
+
+	g_io_channel_unref(stream->io);
+	stream->io = NULL;
+}
+
+static gboolean stream_close_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+
+	debug("Timed out waiting for peer to close the transport channel");
+
+	stream->timer = 0;
+
+	close_stream(stream);
+
+	return FALSE;
+}
+
+static gboolean stream_open_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+
+	debug("Timed out waiting for peer to open the transport channel");
+
+	stream->timer = 0;
+
+	stream->session->pending_open = NULL;
+
+	avdtp_abort(stream->session, stream);
+
+	return FALSE;
+}
+
+static gboolean disconnect_timeout(gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	struct audio_device *dev;
+	gboolean stream_setup;
+
+	session->dc_timer = 0;
+	stream_setup = session->stream_setup;
+	session->stream_setup = FALSE;
+
+	dev = manager_get_device(&session->server->src, &session->dst, FALSE);
+
+	if (dev && dev->sink && stream_setup)
+		sink_setup_stream(dev->sink, session);
+	else if (dev && dev->source && stream_setup)
+		source_setup_stream(dev->source, session);
+	else
+		connection_lost(session, ETIMEDOUT);
+
+	return FALSE;
+}
+
+static void remove_disconnect_timer(struct avdtp *session)
+{
+	g_source_remove(session->dc_timer);
+	session->dc_timer = 0;
+	session->stream_setup = FALSE;
+}
+
+static void set_disconnect_timer(struct avdtp *session)
+{
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT,
+						disconnect_timeout,
+						session);
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id)
+{
+	err->type = type;
+	switch (type) {
+	case AVDTP_ERROR_ERRNO:
+		err->err.posix_errno = id;
+		break;
+	case AVDTP_ERROR_ERROR_CODE:
+		err->err.error_code = id;
+		break;
+	}
+}
+
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err)
+{
+	return err->type;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+	assert(err->type == AVDTP_ERROR_ERROR_CODE);
+	return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+	assert(err->type == AVDTP_ERROR_ERRNO);
+	return err->err.posix_errno;
+}
+
+static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session,
+							uint8_t rseid)
+{
+	GSList *l;
+
+	for (l = session->streams; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_stream *stream = l->data;
+
+		if (stream->rseid == rseid)
+			return stream;
+	}
+
+	return NULL;
+}
+
+static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
+{
+	GSList *l;
+
+	for (l = seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *sep = l->data;
+
+		if (sep->seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static void avdtp_set_state(struct avdtp *session,
+					avdtp_session_state_t new_state)
+{
+	GSList *l;
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	avdtp_session_state_t old_state = session->state;
+
+	session->state = new_state;
+
+	avdtp_get_peers(session, &src, &dst);
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (dev == NULL) {
+		error("avdtp_set_state(): no matching audio device");
+		return;
+	}
+
+	for (l = avdtp_callbacks; l != NULL; l = l->next) {
+		struct avdtp_state_callback *cb = l->data;
+		cb->cb(dev, session, old_state, new_state, cb->user_data);
+	}
+}
+
+static void stream_free(struct avdtp_stream *stream)
+{
+	struct avdtp_remote_sep *rsep;
+
+	stream->lsep->info.inuse = 0;
+	stream->lsep->stream = NULL;
+
+	rsep = find_remote_sep(stream->session->seps, stream->rseid);
+	if (rsep)
+		rsep->stream = NULL;
+
+	if (stream->timer)
+		g_source_remove(stream->timer);
+
+	if (stream->io)
+		g_io_channel_unref(stream->io);
+
+	if (stream->io_id)
+		g_source_remove(stream->io_id);
+
+	g_slist_foreach(stream->callbacks, (GFunc) g_free, NULL);
+	g_slist_free(stream->callbacks);
+
+	g_slist_foreach(stream->caps, (GFunc) g_free, NULL);
+	g_slist_free(stream->caps);
+
+	g_free(stream);
+}
+
+static gboolean stream_timeout(gpointer user_data)
+{
+	struct avdtp_stream *stream = user_data;
+	struct avdtp *session = stream->session;
+
+	avdtp_close(session, stream);
+
+	stream->idle_timer = 0;
+
+	return FALSE;
+}
+
+static gboolean transport_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct avdtp_stream *stream = data;
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (stream->close_int && sep->cfm && sep->cfm->close)
+		sep->cfm->close(stream->session, sep, stream, NULL,
+				sep->user_data);
+
+	if (!(cond & G_IO_NVAL))
+		close_stream(stream);
+
+	stream->io_id = 0;
+
+	if (!stream->abort_int)
+		avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE);
+
+	return FALSE;
+}
+
+static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
+					uint16_t imtu, uint16_t omtu)
+{
+	struct avdtp_stream *stream = session->pending_open;
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	session->pending_open = NULL;
+
+	if (stream->timer) {
+		g_source_remove(stream->timer);
+		stream->timer = 0;
+	}
+
+	if (io == NULL) {
+		if (!stream->open_acp && sep->cfm && sep->cfm->open) {
+			struct avdtp_error err;
+			avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+			sep->cfm->open(session, sep, NULL, &err,
+					sep->user_data);
+		}
+		return;
+	}
+
+	stream->io = g_io_channel_ref(io);
+	stream->omtu = omtu;
+	stream->imtu = imtu;
+
+	if (!stream->open_acp && sep->cfm && sep->cfm->open)
+		sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+	stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) transport_cb, stream);
+}
+
+static int pending_req_cmp(gconstpointer a, gconstpointer b)
+{
+	const struct pending_req *req = a;
+	const struct avdtp_stream *stream = b;
+
+	if (req->stream == stream)
+		return 0;
+
+	return -1;
+}
+
+static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream)
+{
+	GSList *l;
+	struct pending_req *req;
+
+	while ((l = g_slist_find_custom(session->prio_queue, stream,
+							pending_req_cmp))) {
+		req = l->data;
+		pending_req_free(req);
+		session->prio_queue = g_slist_remove(session->prio_queue, req);
+	}
+
+	while ((l = g_slist_find_custom(session->req_queue, stream,
+							pending_req_cmp))) {
+		req = l->data;
+		pending_req_free(req);
+		session->req_queue = g_slist_remove(session->req_queue, req);
+	}
+}
+
+static void avdtp_sep_set_state(struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				avdtp_state_t state)
+{
+	struct avdtp_stream *stream = sep->stream;
+	avdtp_state_t old_state;
+	struct avdtp_error err, *err_ptr = NULL;
+	GSList *l;
+
+	if (!stream) {
+		error("Error changing sep state: stream not available");
+		return;
+	}
+
+	if (sep->state == state) {
+		avdtp_error_init(&err, AVDTP_ERROR_ERRNO, EIO);
+		debug("stream state change failed: %s", avdtp_strerror(&err));
+		err_ptr = &err;
+	} else {
+		err_ptr = NULL;
+		debug("stream state changed: %s -> %s",
+				avdtp_statestr(sep->state),
+				avdtp_statestr(state));
+	}
+
+	old_state = sep->state;
+	sep->state = state;
+
+	for (l = stream->callbacks; l != NULL; l = g_slist_next(l)) {
+		struct stream_callback *cb = l->data;
+		cb->cb(stream, old_state, state, err_ptr, cb->user_data);
+	}
+
+	switch (state) {
+	case AVDTP_STATE_OPEN:
+		if (old_state > AVDTP_STATE_OPEN && session->auto_dc)
+			stream->idle_timer = g_timeout_add_seconds(STREAM_TIMEOUT,
+								stream_timeout,
+								stream);
+		break;
+	case AVDTP_STATE_STREAMING:
+	case AVDTP_STATE_CLOSING:
+	case AVDTP_STATE_ABORTING:
+		if (stream->idle_timer) {
+			g_source_remove(stream->idle_timer);
+			stream->idle_timer = 0;
+		}
+		break;
+	case AVDTP_STATE_IDLE:
+		if (stream->idle_timer) {
+			g_source_remove(stream->idle_timer);
+			stream->idle_timer = 0;
+		}
+		session->streams = g_slist_remove(session->streams, stream);
+		if (session->pending_open == stream)
+			handle_transport_connect(session, NULL, 0, 0);
+		if (session->req && session->req->stream == stream)
+			session->req->stream = NULL;
+		/* Remove pending commands for this stream from the queue */
+		cleanup_queue(session, stream);
+		stream_free(stream);
+		if (session->ref == 1 && !session->streams)
+			set_disconnect_timer(session);
+		break;
+	default:
+		break;
+	}
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+	struct avdtp_error avdtp_err;
+
+	avdtp_error_init(&avdtp_err, AVDTP_ERROR_ERRNO, err);
+
+	if (!session->discov_cb)
+		return;
+
+	session->discov_cb(session, session->seps,
+				err ? &avdtp_err : NULL,
+				session->user_data);
+
+	session->discov_cb = NULL;
+	session->user_data = NULL;
+}
+
+static void release_stream(struct avdtp_stream *stream, struct avdtp *session)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->abort &&
+				(sep->state != AVDTP_STATE_ABORTING ||
+							stream->abort_int))
+		sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+}
+
+static void connection_lost(struct avdtp *session, int err)
+{
+	char address[18];
+
+	ba2str(&session->dst, address);
+	debug("Disconnected from %s", address);
+
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING && err != EACCES)
+		btd_cancel_authorization(&session->server->src, &session->dst);
+
+	session->free_lock = 1;
+
+	finalize_discovery(session, err);
+
+	g_slist_foreach(session->streams, (GFunc) release_stream, session);
+	session->streams = NULL;
+
+	session->free_lock = 0;
+
+	if (session->io) {
+		g_io_channel_shutdown(session->io, FALSE, NULL);
+		g_io_channel_unref(session->io);
+		session->io = NULL;
+	}
+
+	avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED);
+
+	if (session->io_id) {
+		g_source_remove(session->io_id);
+		session->io_id = 0;
+	}
+
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	session->auto_dc = TRUE;
+
+	if (session->ref != 1)
+		error("connection_lost: ref count not 1 after all callbacks");
+	else
+		avdtp_unref(session);
+}
+
+void avdtp_unref(struct avdtp *session)
+{
+	struct avdtp_server *server;
+
+	if (!session)
+		return;
+
+	session->ref--;
+
+	debug("avdtp_unref(%p): ref=%d", session, session->ref);
+
+	if (session->ref == 1) {
+		if (session->state == AVDTP_SESSION_STATE_CONNECTING &&
+								session->io) {
+			btd_cancel_authorization(&session->server->src,
+							&session->dst);
+			g_io_channel_shutdown(session->io, TRUE, NULL);
+			g_io_channel_unref(session->io);
+			session->io = NULL;
+		}
+
+		if (session->io)
+			set_disconnect_timer(session);
+		else if (!session->free_lock) /* Drop the local ref if we
+						 aren't connected */
+			session->ref--;
+	}
+
+	if (session->ref > 0)
+		return;
+
+	server = session->server;
+
+	debug("avdtp_unref(%p): freeing session and removing from list",
+			session);
+
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+
+	server->sessions = g_slist_remove(server->sessions, session);
+
+	if (session->req)
+		pending_req_free(session->req);
+
+	g_slist_foreach(session->seps, (GFunc) g_free, NULL);
+	g_slist_free(session->seps);
+
+	g_free(session->buf);
+
+	g_free(session);
+}
+
+struct avdtp *avdtp_ref(struct avdtp *session)
+{
+	session->ref++;
+	debug("avdtp_ref(%p): ref=%d", session, session->ref);
+	if (session->dc_timer)
+		remove_disconnect_timer(session);
+	return session;
+}
+
+static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp_server *server,
+							uint8_t seid)
+{
+	GSList *l;
+
+	for (l = server->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_local_sep *sep = l->data;
+
+		if (sep->info.seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static struct avdtp_local_sep *find_local_sep(struct avdtp_server *server,
+						uint8_t type,
+						uint8_t media_type,
+						uint8_t codec)
+{
+	GSList *l;
+
+	for (l = server->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_local_sep *sep = l->data;
+
+		if (sep->info.inuse)
+			continue;
+
+		if (sep->info.type == type &&
+				sep->info.media_type == media_type &&
+				sep->codec == codec)
+			return sep;
+	}
+
+	return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+				struct avdtp_service_capability **codec)
+{
+	GSList *caps;
+	int processed;
+
+	for (processed = 0, caps = NULL; processed + 2 < size;) {
+		struct avdtp_service_capability *cap;
+		uint8_t length, category;
+
+		category = data[0];
+		length = data[1];
+
+		if (processed + 2 + length > size) {
+			error("Invalid capability data in getcap resp");
+			break;
+		}
+
+		cap = g_malloc(sizeof(struct avdtp_service_capability) +
+					length);
+		memcpy(cap, data, 2 + length);
+
+		processed += 2 + length;
+		data += 2 + length;
+
+		caps = g_slist_append(caps, cap);
+
+		if (category == AVDTP_MEDIA_CODEC &&
+				length >=
+				sizeof(struct avdtp_media_codec_capability))
+			*codec = cap;
+	}
+
+	return caps;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction,
+							void *buf, int size)
+{
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+								0, NULL, 0);
+}
+
+static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction,
+							void *buf, int size)
+{
+	GSList *l;
+	unsigned int rsp_size, sep_count, i;
+	struct seid_info *seps;
+	gboolean ret;
+
+	sep_count = g_slist_length(session->server->seps);
+
+	if (sep_count == 0) {
+		uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND;
+		return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_DISCOVER, &err, sizeof(err));
+	}
+
+	rsp_size = sep_count * sizeof(struct seid_info);
+
+	seps = g_new0(struct seid_info, sep_count);
+
+	for (l = session->server->seps, i = 0; l != NULL; l = l->next, i++) {
+		struct avdtp_local_sep *sep = l->data;
+
+		memcpy(&seps[i], &sep->info, sizeof(struct seid_info));
+	}
+
+	ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_DISCOVER, seps, rsp_size);
+	g_free(seps);
+
+	return ret;
+}
+
+static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, unsigned int size)
+{
+	GSList *l, *caps;
+	struct avdtp_local_sep *sep = NULL;
+	unsigned int rsp_size;
+	uint8_t err, buf[1024], *ptr = buf;
+
+	if (size < sizeof(struct seid_req)) {
+		err = AVDTP_BAD_LENGTH;
+		goto failed;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (!sep->ind->get_capability(session, sep, &caps, &err,
+					sep->user_data))
+		goto failed;
+
+	for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (rsp_size + cap->length + 2 > sizeof(buf))
+			break;
+
+		memcpy(ptr, cap, cap->length + 2);
+		rsp_size += cap->length + 2;
+		ptr += cap->length + 2;
+
+		g_free(cap);
+	}
+
+	g_slist_free(caps);
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_GET_CAPABILITIES, buf, rsp_size);
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_GET_CAPABILITIES, &err, sizeof(err));
+}
+
+static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
+				struct setconf_req *req, unsigned int size)
+{
+	struct conf_rej rej;
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err, category = 0x00;
+	struct audio_device *dev;
+	bdaddr_t src, dst;
+	GSList *l;
+
+	if (size < sizeof(struct setconf_req)) {
+		error("Too short getcap request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->stream) {
+		err = AVDTP_SEP_IN_USE;
+		goto failed;
+	}
+
+	avdtp_get_peers(session, &src, &dst);
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (!dev) {
+		error("Unable to get a audio device object");
+		goto failed;
+	}
+
+	switch (sep->info.type) {
+	case AVDTP_SEP_TYPE_SOURCE:
+		if (!dev->sink) {
+			btd_device_add_uuid(dev->btd_dev, A2DP_SINK_UUID);
+			if (!dev->sink) {
+				error("Unable to get a audio sink object");
+				goto failed;
+			}
+		}
+		break;
+	case AVDTP_SEP_TYPE_SINK:
+		/* Do source_init() here when it's implemented */
+		break;
+	}
+
+	stream = g_new0(struct avdtp_stream, 1);
+	stream->session = session;
+	stream->lsep = sep;
+	stream->rseid = req->int_seid;
+	stream->caps = caps_to_list(req->caps,
+					size - sizeof(struct setconf_req),
+					&stream->codec);
+
+	/* Verify that the Media Transport capability's length = 0. Reject otherwise */
+	for (l = stream->caps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (cap->category == AVDTP_MEDIA_TRANSPORT && cap->length != 0) {
+			err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT;
+			goto failed_stream;
+		}
+	}
+
+	if (sep->ind && sep->ind->set_configuration) {
+		if (!sep->ind->set_configuration(session, sep, stream,
+							stream->caps, &err,
+							&category,
+							sep->user_data))
+			goto failed_stream;
+	}
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+					AVDTP_SET_CONFIGURATION, NULL, 0)) {
+		stream_free(stream);
+		return FALSE;
+	}
+
+	sep->stream = stream;
+	session->streams = g_slist_append(session->streams, stream);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+	return TRUE;
+
+failed_stream:
+	stream_free(stream);
+failed:
+	rej.error = err;
+	rej.category = category;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_SET_CONFIGURATION, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	GSList *l;
+	struct avdtp_local_sep *sep = NULL;
+	int rsp_size;
+	uint8_t err;
+	uint8_t buf[1024];
+	uint8_t *ptr = buf;
+
+	if (size < (int) sizeof(struct seid_req)) {
+		error("Too short getconf request");
+		return FALSE;
+	}
+
+	memset(buf, 0, sizeof(buf));
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+	if (!sep->stream || !sep->stream->caps) {
+		err = AVDTP_UNSUPPORTED_CONFIGURATION;
+		goto failed;
+	}
+
+	for (l = sep->stream->caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (rsp_size + cap->length + 2 > (int) sizeof(buf))
+			break;
+
+		memcpy(ptr, cap, cap->length + 2);
+		rsp_size += cap->length + 2;
+		ptr += cap->length + 2;
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+				AVDTP_GET_CONFIGURATION, buf, rsp_size);
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_GET_CONFIGURATION, &err, sizeof(err));
+}
+
+static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	return avdtp_unknown_cmd(session, transaction, (void *) req, size);
+}
+
+static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short abort request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->state != AVDTP_STATE_CONFIGURED) {
+		err = AVDTP_BAD_STATE;
+		goto failed;
+	}
+
+	stream = sep->stream;
+
+	if (sep->ind && sep->ind->open) {
+		if (!sep->ind->open(session, sep, stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_OPEN, NULL, 0))
+		return FALSE;
+
+	stream->open_acp = TRUE;
+	session->pending_open = stream;
+	stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+						stream_open_timeout,
+						stream);
+
+	return TRUE;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_OPEN, &err, sizeof(err));
+}
+
+static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction,
+				struct start_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	struct stream_rej rej;
+	struct seid *seid;
+	uint8_t err, failed_seid;
+	int seid_count, i;
+
+	if (size < sizeof(struct start_req)) {
+		error("Too short start request");
+		return FALSE;
+	}
+
+	seid_count = 1 + size - sizeof(struct start_req);
+
+	seid = &req->first_seid;
+
+	for (i = 0; i < seid_count; i++, seid++) {
+		failed_seid = seid->seid;
+
+		sep = find_local_sep_by_seid(session->server,
+					req->first_seid.seid);
+		if (!sep || !sep->stream) {
+			err = AVDTP_BAD_ACP_SEID;
+			goto failed;
+		}
+
+		stream = sep->stream;
+
+		if (sep->state != AVDTP_STATE_OPEN) {
+			err = AVDTP_BAD_STATE;
+			goto failed;
+		}
+
+		if (sep->ind && sep->ind->start) {
+			if (!sep->ind->start(session, sep, stream, &err,
+						sep->user_data))
+				goto failed;
+		}
+
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_START, NULL, 0);
+
+failed:
+	memset(&rej, 0, sizeof(rej));
+	rej.acp_seid = failed_seid;
+	rej.error = err;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_START, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	uint8_t err;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short close request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep || !sep->stream) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->state != AVDTP_STATE_OPEN &&
+			sep->state != AVDTP_STATE_STREAMING) {
+		err = AVDTP_BAD_STATE;
+		goto failed;
+	}
+
+	stream = sep->stream;
+
+	if (sep->ind && sep->ind->close) {
+		if (!sep->ind->close(session, sep, stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+	if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_CLOSE, NULL, 0))
+		return FALSE;
+
+	stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+					stream_close_timeout,
+					stream);
+
+	return TRUE;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_CLOSE, &err, sizeof(err));
+}
+
+static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction,
+				struct suspend_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	struct avdtp_stream *stream;
+	struct stream_rej rej;
+	struct seid *seid;
+	uint8_t err, failed_seid;
+	int seid_count, i;
+
+	if (size < sizeof(struct suspend_req)) {
+		error("Too short suspend request");
+		return FALSE;
+	}
+
+	seid_count = 1 + size - sizeof(struct suspend_req);
+
+	seid = &req->first_seid;
+
+	for (i = 0; i < seid_count; i++, seid++) {
+		failed_seid = seid->seid;
+
+		sep = find_local_sep_by_seid(session->server,
+					req->first_seid.seid);
+		if (!sep || !sep->stream) {
+			err = AVDTP_BAD_ACP_SEID;
+			goto failed;
+		}
+
+		stream = sep->stream;
+
+		if (sep->state != AVDTP_STATE_STREAMING) {
+			err = AVDTP_BAD_STATE;
+			goto failed;
+		}
+
+		if (sep->ind && sep->ind->suspend) {
+			if (!sep->ind->suspend(session, sep, stream, &err,
+						sep->user_data))
+				goto failed;
+		}
+
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+	}
+
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_SUSPEND, NULL, 0);
+
+failed:
+	memset(&rej, 0, sizeof(rej));
+	rej.acp_seid = failed_seid;
+	rej.error = err;
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+				AVDTP_SUSPEND, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction,
+				struct seid_req *req, unsigned int size)
+{
+	struct avdtp_local_sep *sep;
+	uint8_t err;
+	gboolean ret;
+
+	if (size < sizeof(struct seid_req)) {
+		error("Too short abort request");
+		return FALSE;
+	}
+
+	sep = find_local_sep_by_seid(session->server, req->acp_seid);
+	if (!sep || !sep->stream) {
+		err = AVDTP_BAD_ACP_SEID;
+		goto failed;
+	}
+
+	if (sep->ind && sep->ind->abort) {
+		if (!sep->ind->abort(session, sep, sep->stream, &err,
+					sep->user_data))
+			goto failed;
+	}
+
+	ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+						AVDTP_ABORT, NULL, 0);
+	if (ret)
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+	return ret;
+
+failed:
+	return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+					AVDTP_ABORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction,
+					struct seid_req *req, int size)
+{
+	return avdtp_unknown_cmd(session, transaction, (void *) req, size);
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
+				uint8_t signal_id, void *buf, int size)
+{
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		debug("Received DISCOVER_CMD");
+		return avdtp_discover_cmd(session, transaction, buf, size);
+	case AVDTP_GET_CAPABILITIES:
+		debug("Received  GET_CAPABILITIES_CMD");
+		return avdtp_getcap_cmd(session, transaction, buf, size);
+	case AVDTP_SET_CONFIGURATION:
+		debug("Received SET_CONFIGURATION_CMD");
+		return avdtp_setconf_cmd(session, transaction, buf, size);
+	case AVDTP_GET_CONFIGURATION:
+		debug("Received GET_CONFIGURATION_CMD");
+		return avdtp_getconf_cmd(session, transaction, buf, size);
+	case AVDTP_RECONFIGURE:
+		debug("Received RECONFIGURE_CMD");
+		return avdtp_reconf_cmd(session, transaction, buf, size);
+	case AVDTP_OPEN:
+		debug("Received OPEN_CMD");
+		return avdtp_open_cmd(session, transaction, buf, size);
+	case AVDTP_START:
+		debug("Received START_CMD");
+		return avdtp_start_cmd(session, transaction, buf, size);
+	case AVDTP_CLOSE:
+		debug("Received CLOSE_CMD");
+		return avdtp_close_cmd(session, transaction, buf, size);
+	case AVDTP_SUSPEND:
+		debug("Received SUSPEND_CMD");
+		return avdtp_suspend_cmd(session, transaction, buf, size);
+	case AVDTP_ABORT:
+		debug("Received ABORT_CMD");
+		return avdtp_abort_cmd(session, transaction, buf, size);
+	case AVDTP_SECURITY_CONTROL:
+		debug("Received SECURITY_CONTROL_CMD");
+		return avdtp_secctl_cmd(session, transaction, buf, size);
+	default:
+		debug("Received unknown request id %u", signal_id);
+		return avdtp_unknown_cmd(session, transaction, buf, size);
+	}
+}
+
+enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS };
+
+static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session,
+							void *buf, size_t size)
+{
+	struct avdtp_common_header *header = buf;
+	struct avdtp_single_header *single = (void *) session->buf;
+	struct avdtp_start_header *start = (void *) session->buf;
+	void *payload;
+	gsize payload_size;
+
+	switch (header->packet_type) {
+	case AVDTP_PKT_TYPE_SINGLE:
+		if (size < sizeof(*single)) {
+			error("Received too small single packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (session->in.active) {
+			error("SINGLE: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(*single);
+		payload_size = size - sizeof(*single);
+
+		session->in.active = TRUE;
+		session->in.data_size = 0;
+		session->in.no_of_packets = 1;
+		session->in.transaction = header->transaction;
+		session->in.message_type = header->message_type;
+		session->in.signal_id = single->signal_id;
+
+		break;
+	case AVDTP_PKT_TYPE_START:
+		if (size < sizeof(*start)) {
+			error("Received too small start packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (session->in.active) {
+			error("START: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+
+		session->in.active = TRUE;
+		session->in.data_size = 0;
+		session->in.transaction = header->transaction;
+		session->in.message_type = header->message_type;
+		session->in.no_of_packets = start->no_of_packets;
+		session->in.signal_id = start->signal_id;
+
+		payload = session->buf + sizeof(*start);
+		payload_size = size - sizeof(*start);
+
+		break;
+	case AVDTP_PKT_TYPE_CONTINUE:
+		if (size < sizeof(struct avdtp_continue_header)) {
+			error("Received too small continue packet (%zu bytes)",
+									size);
+			return PARSE_ERROR;
+		}
+		if (!session->in.active) {
+			error("CONTINUE: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+		if (session->in.transaction != header->transaction) {
+			error("Continue transaction id doesn't match");
+			return PARSE_ERROR;
+		}
+		if (session->in.no_of_packets <= 1) {
+			error("Too few continue packets");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(struct avdtp_continue_header);
+		payload_size = size - sizeof(struct avdtp_continue_header);
+
+		break;
+	case AVDTP_PKT_TYPE_END:
+		if (size < sizeof(struct avdtp_continue_header)) {
+			error("Received too small end packet (%zu bytes)", size);
+			return PARSE_ERROR;
+		}
+		if (!session->in.active) {
+			error("END: Invalid AVDTP packet fragmentation");
+			return PARSE_ERROR;
+		}
+		if (session->in.transaction != header->transaction) {
+			error("End transaction id doesn't match");
+			return PARSE_ERROR;
+		}
+		if (session->in.no_of_packets > 1) {
+			error("Got an end packet too early");
+			return PARSE_ERROR;
+		}
+
+		payload = session->buf + sizeof(struct avdtp_continue_header);
+		payload_size = size - sizeof(struct avdtp_continue_header);
+
+		break;
+	default:
+		error("Invalid AVDTP packet type 0x%02X", header->packet_type);
+		return PARSE_ERROR;
+	}
+
+	if (session->in.data_size + payload_size >
+					sizeof(session->in.buf)) {
+		error("Not enough incoming buffer space!");
+		return PARSE_ERROR;
+	}
+
+	memcpy(session->in.buf + session->in.data_size, payload, payload_size);
+	session->in.data_size += payload_size;
+
+	if (session->in.no_of_packets > 1) {
+		session->in.no_of_packets--;
+		debug("Received AVDTP fragment. %d to go",
+						session->in.no_of_packets);
+		return PARSE_FRAGMENT;
+	}
+
+	session->in.active = FALSE;
+
+	return PARSE_SUCCESS;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct avdtp *session = data;
+	struct avdtp_common_header *header;
+	gsize size;
+
+	debug("session_cb");
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	header = (void *) session->buf;
+
+	if (cond & (G_IO_HUP | G_IO_ERR))
+		goto failed;
+
+	if (g_io_channel_read(chan, session->buf, session->imtu, &size)
+							!= G_IO_ERROR_NONE) {
+		error("IO Channel read error");
+		goto failed;
+	}
+
+	if (size < sizeof(struct avdtp_common_header)) {
+		error("Received too small packet (%zu bytes)", size);
+		goto failed;
+	}
+
+	switch (avdtp_parse_data(session, session->buf, size)) {
+	case PARSE_ERROR:
+		goto failed;
+	case PARSE_FRAGMENT:
+		return TRUE;
+	case PARSE_SUCCESS:
+		break;
+	}
+
+	if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) {
+		if (!avdtp_parse_cmd(session, session->in.transaction,
+					session->in.signal_id,
+					session->in.buf,
+					session->in.data_size)) {
+			error("Unable to handle command. Disconnecting");
+			goto failed;
+		}
+
+		if (session->ref == 1 && !session->streams && !session->req)
+			set_disconnect_timer(session);
+
+		if (session->streams && session->dc_timer)
+			remove_disconnect_timer(session);
+
+		return TRUE;
+	}
+
+	if (session->req == NULL) {
+		error("No pending request, ignoring message");
+		return TRUE;
+	}
+
+	if (header->transaction != session->req->transaction) {
+		error("Transaction label doesn't match");
+		return TRUE;
+	}
+
+	if (session->in.signal_id != session->req->signal_id) {
+		error("Reponse signal doesn't match");
+		return TRUE;
+	}
+
+	g_source_remove(session->req->timeout);
+	session->req->timeout = 0;
+
+	switch (header->message_type) {
+	case AVDTP_MSG_TYPE_ACCEPT:
+		if (!avdtp_parse_resp(session, session->req->stream,
+						session->in.transaction,
+						session->in.signal_id,
+						session->in.buf,
+						session->in.data_size)) {
+			error("Unable to parse accept response");
+			goto failed;
+		}
+		break;
+	case AVDTP_MSG_TYPE_REJECT:
+		if (!avdtp_parse_rej(session, session->req->stream,
+						session->in.transaction,
+						session->in.signal_id,
+						session->in.buf,
+						session->in.data_size)) {
+			error("Unable to parse reject response");
+			goto failed;
+		}
+		break;
+	default:
+		error("Unknown message type 0x%02X", header->message_type);
+		break;
+	}
+
+	pending_req_free(session->req);
+	session->req = NULL;
+
+	process_queue(session);
+
+	return TRUE;
+
+failed:
+	connection_lost(session, EIO);
+
+	return FALSE;
+}
+
+static struct avdtp *find_session(GSList *list, const bdaddr_t *dst)
+{
+	GSList *l;
+
+	for (l = list; l != NULL; l = g_slist_next(l)) {
+		struct avdtp *s = l->data;
+
+		if (bacmp(dst, &s->dst))
+			continue;
+
+		return s;
+	}
+
+	return NULL;
+}
+
+static struct avdtp *avdtp_get_internal(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct avdtp_server *server;
+	struct avdtp *session;
+
+	assert(src != NULL);
+	assert(dst != NULL);
+
+	server = find_server(servers, src);
+	if (server == NULL)
+		return NULL;
+
+	session = find_session(server->sessions, dst);
+	if (session) {
+		if (session->pending_auth)
+			return NULL;
+		else
+			return session;
+	}
+
+	session = g_new0(struct avdtp, 1);
+
+	session->server = server;
+	bacpy(&session->dst, dst);
+	session->ref = 1;
+	/* We don't use avdtp_set_state() here since this isn't a state change
+	 * but just setting of the initial state */
+	session->state = AVDTP_SESSION_STATE_DISCONNECTED;
+	session->auto_dc = TRUE;
+
+	server->sessions = g_slist_append(server->sessions, session);
+
+	return session;
+}
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst)
+{
+	struct avdtp *session;
+
+	session = avdtp_get_internal(src, dst);
+
+	if (!session)
+		return NULL;
+
+	return avdtp_ref(session);
+}
+
+static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	char address[18];
+	GError *gerr = NULL;
+
+	if (err) {
+		error("%s", err->message);
+		goto failed;
+	}
+
+	if (!session->io)
+		session->io = g_io_channel_ref(chan);
+
+	bt_io_get(chan, BT_IO_L2CAP, &gerr,
+			BT_IO_OPT_OMTU, &session->omtu,
+			BT_IO_OPT_IMTU, &session->imtu,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		goto failed;
+	}
+
+	ba2str(&session->dst, address);
+	debug("AVDTP: connected %s channel to %s",
+			session->pending_open ? "transport" : "signaling",
+			address);
+
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+		debug("AVDTP imtu=%u, omtu=%u", session->imtu, session->omtu);
+
+		session->buf = g_malloc0(session->imtu);
+		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTED);
+
+		if (session->io_id)
+			g_source_remove(session->io_id);
+
+		/* This watch should be low priority since otherwise the
+		 * connect callback might be dispatched before the session
+		 * callback if the kernel wakes us up at the same time for
+		 * them. This could happen if a headset is very quick in
+		 * sending the Start command after connecting the stream
+		 * transport channel.
+		 */
+		session->io_id = g_io_add_watch_full(chan,
+						G_PRIORITY_LOW,
+						G_IO_IN | G_IO_ERR | G_IO_HUP
+						| G_IO_NVAL,
+						(GIOFunc) session_cb, session,
+						NULL);
+
+		if (session->stream_setup) {
+			set_disconnect_timer(session);
+			avdtp_set_auto_disconnect(session, FALSE);
+		}
+	} else if (session->pending_open)
+		handle_transport_connect(session, chan, session->imtu,
+								session->omtu);
+	else
+		goto failed;
+
+	process_queue(session);
+
+	return;
+
+failed:
+	if (session->pending_open) {
+		struct avdtp_stream *stream = session->pending_open;
+
+		handle_transport_connect(session, NULL, 0, 0);
+
+		if (avdtp_abort(session, stream) < 0)
+			avdtp_sep_set_state(session, stream->lsep,
+						AVDTP_STATE_IDLE);
+	} else
+		connection_lost(session, EIO);
+
+	return;
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct avdtp *session = user_data;
+	GError *err = NULL;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		connection_lost(session, EACCES);
+		return;
+	}
+
+	if (!bt_io_accept(session->io, avdtp_connect_cb, session, NULL,
+								&err)) {
+		error("bt_io_accept: %s", err->message);
+		connection_lost(session, EACCES);
+		g_error_free(err);
+		return;
+	}
+
+	/* This is so that avdtp_connect_cb will know to do the right thing
+	 * with respect to the disconnect timer */
+	session->stream_setup = TRUE;
+}
+
+static void avdtp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+	struct avdtp *session;
+	struct audio_device *dev;
+	char address[18];
+	bdaddr_t src, dst;
+	int perr;
+	GError *err = NULL;
+
+	bt_io_get(chan, BT_IO_L2CAP, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, address,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		goto drop;
+	}
+
+	debug("AVDTP: incoming connect from %s", address);
+
+	session = avdtp_get_internal(&src, &dst);
+	if (!session)
+		goto drop;
+
+	/* This state (ie, session is already *connecting*) happens when the
+	 * device initiates a connect (really a config'd L2CAP channel) even
+	 * though there is a connect we initiated in progress. In sink.c &
+	 * source.c, this state is referred to as XCASE connect:connect.
+	 * Abort the device's channel in favor of our own.
+	 */
+	if (session->state == AVDTP_SESSION_STATE_CONNECTING) {
+		debug("avdtp_confirm_cb: connect already in progress"
+						" (XCASE connect:connect)");
+		goto drop;
+	}
+
+	if (session->pending_open && session->pending_open->open_acp) {
+		if (!bt_io_accept(chan, avdtp_connect_cb, session, NULL, NULL))
+			goto drop;
+		return;
+	}
+
+	if (session->io) {
+		error("Refusing unexpected connect from %s", address);
+		goto drop;
+	}
+
+	dev = manager_get_device(&src, &dst, FALSE);
+	if (!dev) {
+		dev = manager_get_device(&src, &dst, TRUE);
+		if (!dev) {
+			error("Unable to get audio device object for %s",
+					address);
+			goto drop;
+		}
+		btd_device_add_uuid(dev->btd_dev, ADVANCED_AUDIO_UUID);
+	}
+
+	session->io = g_io_channel_ref(chan);
+	avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+
+	session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+					(GIOFunc) session_cb, session);
+
+	perr = audio_device_request_authorization(dev, ADVANCED_AUDIO_UUID,
+							auth_cb, session);
+	if (perr < 0) {
+		avdtp_unref(session);
+		goto drop;
+	}
+
+	dev->auto_connect = auto_connect;
+
+	return;
+
+drop:
+	g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static int l2cap_connect(struct avdtp *session)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_connect(BT_IO_L2CAP, avdtp_connect_cb, session,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
+				BT_IO_OPT_DEST_BDADDR, &session->dst,
+				BT_IO_OPT_PSM, AVDTP_PSM,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+		return -EIO;
+	}
+
+	g_io_channel_unref(io);
+
+	return 0;
+}
+
+static void queue_request(struct avdtp *session, struct pending_req *req,
+			gboolean priority)
+{
+	if (priority)
+		session->prio_queue = g_slist_append(session->prio_queue, req);
+	else
+		session->req_queue = g_slist_append(session->req_queue, req);
+}
+
+static uint8_t req_get_seid(struct pending_req *req)
+{
+	if (req->signal_id == AVDTP_DISCOVER)
+		return 0;
+
+	return ((struct seid_req *) (req->data))->acp_seid;
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+	struct avdtp *session = user_data;
+	struct pending_req *req;
+	struct seid_req sreq;
+	struct avdtp_local_sep *lsep;
+	struct avdtp_stream *stream;
+	uint8_t seid;
+	struct avdtp_error err;
+
+	req = session->req;
+	session->req = NULL;
+
+	avdtp_error_init(&err, AVDTP_ERROR_ERRNO, ETIMEDOUT);
+
+	seid = req_get_seid(req);
+	if (seid)
+		stream = find_stream_by_rseid(session, seid);
+	else
+		stream = NULL;
+
+	if (stream)
+		lsep = stream->lsep;
+	else
+		lsep = NULL;
+
+	switch (req->signal_id) {
+	case AVDTP_RECONFIGURE:
+		error("Reconfigure request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+			lsep->cfm->reconfigure(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_OPEN:
+		error("Open request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->open)
+			lsep->cfm->open(session, lsep, stream, &err,
+					lsep->user_data);
+		break;
+	case AVDTP_START:
+		error("Start request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->start)
+			lsep->cfm->start(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_SUSPEND:
+		error("Suspend request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->suspend)
+			lsep->cfm->suspend(session, lsep, stream, &err,
+						lsep->user_data);
+		break;
+	case AVDTP_CLOSE:
+		error("Close request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->close) {
+			lsep->cfm->close(session, lsep, stream, &err,
+						lsep->user_data);
+			if (stream)
+				stream->close_int = FALSE;
+		}
+		break;
+	case AVDTP_SET_CONFIGURATION:
+		error("SetConfiguration request timed out");
+		if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+			lsep->cfm->set_configuration(session, lsep, stream,
+							&err, lsep->user_data);
+		goto failed;
+	case AVDTP_DISCOVER:
+		error("Discover request timed out");
+		goto failed;
+	case AVDTP_GET_CAPABILITIES:
+		error("GetCapabilities request timed out");
+		goto failed;
+	case AVDTP_ABORT:
+		error("Abort request timed out");
+		goto failed;
+	}
+
+	if (!stream)
+		goto failed;
+
+	memset(&sreq, 0, sizeof(sreq));
+	sreq.acp_seid = seid;
+
+	if (send_request(session, TRUE, stream, AVDTP_ABORT,
+						&sreq, sizeof(sreq)) < 0) {
+		error("Unable to send abort request");
+		goto failed;
+	}
+
+	stream->abort_int = TRUE;
+
+	goto done;
+
+failed:
+	connection_lost(session, ETIMEDOUT);
+done:
+	pending_req_free(req);
+	return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+			struct pending_req *req)
+{
+	static int transaction = 0;
+	int err;
+
+	if (session->state == AVDTP_SESSION_STATE_DISCONNECTED) {
+		err = l2cap_connect(session);
+		if (err < 0)
+			goto failed;
+		avdtp_set_state(session, AVDTP_SESSION_STATE_CONNECTING);
+	}
+
+	if (session->state < AVDTP_SESSION_STATE_CONNECTED ||
+			session->req != NULL) {
+		queue_request(session, req, priority);
+		return 0;
+	}
+
+	req->transaction = transaction++;
+	transaction %= 16;
+
+	/* FIXME: Should we retry to send if the buffer
+	was not totally sent or in case of EINTR? */
+	if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND,
+				req->signal_id, req->data, req->data_size)) {
+		err = -EIO;
+		goto failed;
+	}
+
+
+	session->req = req;
+
+	req->timeout = g_timeout_add_seconds(REQ_TIMEOUT,
+					request_timeout,
+					session);
+	return 0;
+
+failed:
+	g_free(req->data);
+	g_free(req);
+	return err;
+}
+
+static int send_request(struct avdtp *session, gboolean priority,
+			struct avdtp_stream *stream, uint8_t signal_id,
+			void *buffer, size_t size)
+{
+	struct pending_req *req;
+
+	req = g_new0(struct pending_req, 1);
+	req->signal_id = signal_id;
+	req->data = g_malloc(size);
+	memcpy(req->data, buffer, size);
+	req->data_size = size;
+	req->stream = stream;
+
+	return send_req(session, priority, req);
+}
+
+static gboolean avdtp_discover_resp(struct avdtp *session,
+					struct discover_resp *resp, int size)
+{
+	int sep_count, i;
+
+	sep_count = size / sizeof(struct seid_info);
+
+	for (i = 0; i < sep_count; i++) {
+		struct avdtp_remote_sep *sep;
+		struct avdtp_stream *stream;
+		struct seid_req req;
+		int ret;
+
+		debug("seid %d type %d media %d in use %d",
+				resp->seps[i].seid, resp->seps[i].type,
+				resp->seps[i].media_type, resp->seps[i].inuse);
+
+		stream = find_stream_by_rseid(session, resp->seps[i].seid);
+
+		sep = find_remote_sep(session->seps, resp->seps[i].seid);
+		if (!sep) {
+			if (resp->seps[i].inuse && !stream)
+				continue;
+			sep = g_new0(struct avdtp_remote_sep, 1);
+			session->seps = g_slist_append(session->seps, sep);
+		}
+
+		sep->stream = stream;
+		sep->seid = resp->seps[i].seid;
+		sep->type = resp->seps[i].type;
+		sep->media_type = resp->seps[i].media_type;
+
+		memset(&req, 0, sizeof(req));
+		req.acp_seid = sep->seid;
+
+		ret = send_request(session, TRUE, NULL,
+					AVDTP_GET_CAPABILITIES,
+					&req, sizeof(req));
+		if (ret < 0) {
+			finalize_discovery(session, -ret);
+			break;
+		}
+	}
+
+	return TRUE;
+}
+
+static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
+						struct getcap_resp *resp,
+						unsigned int size)
+{
+	struct avdtp_remote_sep *sep;
+	uint8_t seid;
+
+	/* Check for minimum required packet size includes:
+	 *   1. getcap resp header
+	 *   2. media transport capability (2 bytes)
+	 *   3. media codec capability type + length (2 bytes)
+	 *   4. the actual media codec elements
+	 * */
+	if (size < (sizeof(struct getcap_resp) + 4 +
+				sizeof(struct avdtp_media_codec_capability))) {
+		error("Too short getcap resp packet");
+		return FALSE;
+	}
+
+	seid = ((struct seid_req *) session->req->data)->acp_seid;
+
+	sep = find_remote_sep(session->seps, seid);
+
+	debug("seid %d type %d media %d", sep->seid,
+					sep->type, sep->media_type);
+
+	if (sep->caps) {
+		g_slist_foreach(sep->caps, (GFunc) g_free, NULL);
+		g_slist_free(sep->caps);
+		sep->caps = NULL;
+		sep->codec = NULL;
+	}
+
+	sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+					&sep->codec);
+
+	return TRUE;
+}
+
+static gboolean avdtp_set_configuration_resp(struct avdtp *session,
+						struct avdtp_stream *stream,
+						struct avdtp_single_header *resp,
+						int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->set_configuration)
+		sep->cfm->set_configuration(session, sep, stream, NULL,
+						sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+	return TRUE;
+}
+
+static gboolean avdtp_reconfigure_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct avdtp_single_header *resp, int size)
+{
+	return TRUE;
+}
+
+static gboolean avdtp_open_resp(struct avdtp *session, struct avdtp_stream *stream,
+				struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (l2cap_connect(session) < 0) {
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+		return FALSE;
+	}
+
+	session->pending_open = stream;
+
+	return TRUE;
+}
+
+static gboolean avdtp_start_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	if (sep->cfm && sep->cfm->start)
+		sep->cfm->start(session, sep, stream, NULL, sep->user_data);
+
+	/* We might be in STREAMING already if both sides send START_CMD at the
+	 * same time and the one in SNK role doesn't reject it as it should */
+	if (sep->state != AVDTP_STATE_STREAMING)
+		avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+
+	return TRUE;
+}
+
+static gboolean avdtp_close_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+	close_stream(stream);
+
+	return TRUE;
+}
+
+static gboolean avdtp_suspend_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					void *data, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+	if (sep->cfm && sep->cfm->suspend)
+		sep->cfm->suspend(session, sep, stream, NULL, sep->user_data);
+
+	return TRUE;
+}
+
+static gboolean avdtp_abort_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					struct seid_rej *resp, int size)
+{
+	struct avdtp_local_sep *sep = stream->lsep;
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+	if (sep->cfm && sep->cfm->abort)
+		sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+	avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+
+	return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size)
+{
+	struct pending_req *next;
+
+	if (session->prio_queue)
+		next = session->prio_queue->data;
+	else if (session->req_queue)
+		next = session->req_queue->data;
+	else
+		next = NULL;
+
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		debug("DISCOVER request succeeded");
+		return avdtp_discover_resp(session, buf, size);
+	case AVDTP_GET_CAPABILITIES:
+		debug("GET_CAPABILITIES request succeeded");
+		if (!avdtp_get_capabilities_resp(session, buf, size))
+			return FALSE;
+		if (!(next && next->signal_id == AVDTP_GET_CAPABILITIES))
+			finalize_discovery(session, 0);
+		return TRUE;
+	}
+
+	/* The remaining commands require an existing stream so bail out
+	 * here if the stream got unexpectedly disconnected */
+	if (!stream) {
+		debug("AVDTP: stream was closed while waiting for reply");
+		return TRUE;
+	}
+
+	switch (signal_id) {
+	case AVDTP_SET_CONFIGURATION:
+		debug("SET_CONFIGURATION request succeeded");
+		return avdtp_set_configuration_resp(session, stream,
+								buf, size);
+	case AVDTP_RECONFIGURE:
+		debug("RECONFIGURE request succeeded");
+		return avdtp_reconfigure_resp(session, stream, buf, size);
+	case AVDTP_OPEN:
+		debug("OPEN request succeeded");
+		return avdtp_open_resp(session, stream, buf, size);
+	case AVDTP_SUSPEND:
+		debug("SUSPEND request succeeded");
+		return avdtp_suspend_resp(session, stream, buf, size);
+	case AVDTP_START:
+		debug("START request succeeded");
+		return avdtp_start_resp(session, stream, buf, size);
+	case AVDTP_CLOSE:
+		debug("CLOSE request succeeded");
+		return avdtp_close_resp(session, stream, buf, size);
+	case AVDTP_ABORT:
+		debug("ABORT request succeeded");
+		return avdtp_abort_resp(session, stream, buf, size);
+	}
+
+	error("Unknown signal id in accept response: %u", signal_id);
+	return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size,
+					struct avdtp_error *err)
+{
+	if (size < sizeof(struct seid_rej)) {
+		error("Too small packet for seid_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
+				struct avdtp_error *err, uint8_t *category)
+{
+	if (size < sizeof(struct conf_rej)) {
+		error("Too small packet for conf_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	if (category)
+		*category = rej->category;
+
+	return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size,
+					struct avdtp_error *err,
+					uint8_t *acp_seid)
+{
+	if (size < sizeof(struct stream_rej)) {
+		error("Too small packet for stream_rej");
+		return FALSE;
+	}
+
+	avdtp_error_init(err, AVDTP_ERROR_ERROR_CODE, rej->error);
+
+	if (acp_seid)
+		*acp_seid = rej->acp_seid;
+
+	return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+					struct avdtp_stream *stream,
+					uint8_t transaction, uint8_t signal_id,
+					void *buf, int size)
+{
+	struct avdtp_error err;
+	uint8_t acp_seid, category;
+	struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+	switch (signal_id) {
+	case AVDTP_DISCOVER:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("DISCOVER request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		return TRUE;
+	case AVDTP_GET_CAPABILITIES:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("GET_CAPABILITIES request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		return TRUE;
+	case AVDTP_OPEN:
+		if (!seid_rej_to_err(buf, size, &err))
+			return FALSE;
+		error("OPEN request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->open)
+			sep->cfm->open(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	case AVDTP_SET_CONFIGURATION:
+		if (!conf_rej_to_err(buf, size, &err, &category))
+			return FALSE;
+		error("SET_CONFIGURATION request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->set_configuration)
+			sep->cfm->set_configuration(session, sep, stream,
+							&err, sep->user_data);
+		return TRUE;
+	case AVDTP_RECONFIGURE:
+		if (!conf_rej_to_err(buf, size, &err, &category))
+			return FALSE;
+		error("RECONFIGURE request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->reconfigure)
+			sep->cfm->reconfigure(session, sep, stream, &err,
+						sep->user_data);
+		return TRUE;
+	case AVDTP_START:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("START request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->start)
+			sep->cfm->start(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	case AVDTP_SUSPEND:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("SUSPEND request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->suspend)
+			sep->cfm->suspend(session, sep, stream, &err,
+						sep->user_data);
+		return TRUE;
+	case AVDTP_CLOSE:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("CLOSE request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->close) {
+			sep->cfm->close(session, sep, stream, &err,
+					sep->user_data);
+			stream->close_int = FALSE;
+		}
+		return TRUE;
+	case AVDTP_ABORT:
+		if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+			return FALSE;
+		error("ABORT request rejected: %s (%d)",
+				avdtp_strerror(&err), err.err.error_code);
+		if (sep && sep->cfm && sep->cfm->abort)
+			sep->cfm->abort(session, sep, stream, &err,
+					sep->user_data);
+		return TRUE;
+	default:
+		error("Unknown reject response signal id: %u", signal_id);
+		return TRUE;
+	}
+}
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct avdtp_server *server;
+	struct avdtp *session;
+
+	server = find_server(servers, src);
+	if (!server)
+		return FALSE;
+
+	session = find_session(server->sessions, dst);
+	if (!session)
+		return FALSE;
+
+	if (session->state != AVDTP_SESSION_STATE_DISCONNECTED)
+		return TRUE;
+
+	return FALSE;
+}
+
+struct avdtp_service_capability *avdtp_stream_get_codec(
+						struct avdtp_stream *stream)
+{
+	GSList *l;
+
+	for (l = stream->caps; l; l = l->next) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (cap->category == AVDTP_MEDIA_CODEC)
+			return cap;
+	}
+
+	return NULL;
+}
+
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+				struct avdtp_service_capability *cap)
+{
+	GSList *l;
+	struct avdtp_service_capability *stream_cap;
+
+	for (l = stream->caps; l; l = g_slist_next(l)) {
+		stream_cap = l->data;
+
+		if (stream_cap->category != cap->category ||
+			stream_cap->length != cap->length)
+			continue;
+
+		if (memcmp(stream_cap->data, cap->data, cap->length) == 0)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+					GSList *caps)
+{
+	GSList *l;
+
+	for (l = caps; l; l = g_slist_next(l)) {
+		struct avdtp_service_capability *cap = l->data;
+
+		if (!avdtp_stream_has_capability(stream, cap))
+			return FALSE;
+	}
+
+	return TRUE;
+}
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+					uint16_t *imtu, uint16_t *omtu,
+					GSList **caps)
+{
+	if (stream->io == NULL)
+		return FALSE;
+
+	if (sock)
+		*sock = g_io_channel_unix_get_fd(stream->io);
+
+	if (omtu)
+		*omtu = stream->omtu;
+
+	if (imtu)
+		*imtu = stream->imtu;
+
+	if (caps)
+		*caps = stream->caps;
+
+	return TRUE;
+}
+
+static int process_queue(struct avdtp *session)
+{
+	GSList **queue, *l;
+	struct pending_req *req;
+
+	if (session->req)
+		return 0;
+
+	if (session->prio_queue)
+		queue = &session->prio_queue;
+	else
+		queue = &session->req_queue;
+
+	if (!*queue)
+		return 0;
+
+	l = *queue;
+	req = l->data;
+
+	*queue = g_slist_remove(*queue, req);
+
+	return send_req(session, FALSE, req);
+}
+
+struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+						uint8_t seid)
+{
+	GSList *l;
+
+	for (l = session->seps; l; l = l->next) {
+		struct avdtp_remote_sep *sep = l->data;
+
+		if (sep->seid == seid)
+			return sep;
+	}
+
+	return NULL;
+}
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep)
+{
+	return sep->seid;
+}
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep)
+{
+	return sep->type;
+}
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
+{
+	return sep->codec;
+}
+
+struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep)
+{
+	return sep->stream;
+}
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+							void *data, int length)
+{
+	struct avdtp_service_capability *cap;
+
+	if (category < AVDTP_MEDIA_TRANSPORT || category > AVDTP_MEDIA_CODEC)
+		return NULL;
+
+	cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
+	cap->category = category;
+	cap->length = length;
+	memcpy(cap->data, data, length);
+
+	return cap;
+}
+
+static gboolean process_discover(gpointer data)
+{
+	struct avdtp *session = data;
+
+	finalize_discovery(session, 0);
+
+	return FALSE;
+}
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+			void *user_data)
+{
+	int ret;
+
+	if (session->discov_cb)
+		return -EBUSY;
+
+	if (session->seps) {
+		session->discov_cb = cb;
+		session->user_data = user_data;
+		g_idle_add(process_discover, session);
+		return 0;
+	}
+
+	ret = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0);
+	if (ret == 0) {
+		session->discov_cb = cb;
+		session->user_data = user_data;
+	}
+
+	return ret;
+}
+
+int avdtp_get_seps(struct avdtp *session, uint8_t acp_type, uint8_t media_type,
+			uint8_t codec, struct avdtp_local_sep **lsep,
+			struct avdtp_remote_sep **rsep)
+{
+	GSList *l;
+	uint8_t int_type;
+
+	int_type = acp_type == AVDTP_SEP_TYPE_SINK ?
+				AVDTP_SEP_TYPE_SOURCE : AVDTP_SEP_TYPE_SINK;
+
+	*lsep = find_local_sep(session->server, int_type, media_type, codec);
+	if (!*lsep)
+		return -EINVAL;
+
+	for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+		struct avdtp_remote_sep *sep = l->data;
+		struct avdtp_service_capability *cap;
+		struct avdtp_media_codec_capability *codec_data;
+
+		if (sep->type != acp_type)
+			continue;
+
+		if (sep->media_type != media_type)
+			continue;
+
+		if (!sep->codec)
+			continue;
+
+		cap = sep->codec;
+		codec_data = (void *) cap->data;
+
+		if (codec_data->media_codec_type != codec)
+			continue;
+
+		if (!sep->stream) {
+			*rsep = sep;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+				struct avdtp_stream *stream,
+				unsigned int id)
+{
+	GSList *l;
+	struct stream_callback *cb;
+
+	if (!stream)
+		return FALSE;
+
+	for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+		struct stream_callback *tmp = l->data;
+		if (tmp && tmp->id == id) {
+			cb = tmp;
+			break;
+		}
+	}
+
+	if (!cb)
+		return FALSE;
+
+	stream->callbacks = g_slist_remove(stream->callbacks, cb);
+	g_free(cb);
+
+	return TRUE;
+}
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+					struct avdtp_stream *stream,
+					avdtp_stream_state_cb cb, void *data)
+{
+	struct stream_callback *stream_cb;
+	static unsigned int id = 0;
+
+	stream_cb = g_new(struct stream_callback, 1);
+	stream_cb->cb = cb;
+	stream_cb->user_data = data;
+	stream_cb->id = ++id;
+
+	stream->callbacks = g_slist_append(stream->callbacks, stream_cb);;
+
+	return stream_cb->id;
+}
+
+int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (session->state < AVDTP_SESSION_STATE_CONNECTED)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION,
+							&req, sizeof(req));
+}
+
+static void copy_capabilities(gpointer data, gpointer user_data)
+{
+	struct avdtp_service_capability *src_cap = data;
+	struct avdtp_service_capability *dst_cap;
+	GSList **l = user_data;
+
+	dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data,
+					src_cap->length);
+
+	*l = g_slist_append(*l, dst_cap);
+}
+
+int avdtp_set_configuration(struct avdtp *session,
+				struct avdtp_remote_sep *rsep,
+				struct avdtp_local_sep *lsep,
+				GSList *caps,
+				struct avdtp_stream **stream)
+{
+	struct setconf_req *req;
+	struct avdtp_stream *new_stream;
+	unsigned char *ptr;
+	int ret, caps_len;
+	struct avdtp_service_capability *cap;
+	GSList *l;
+
+	if (session->state != AVDTP_SESSION_STATE_CONNECTED)
+		return -ENOTCONN;
+
+	if (!(lsep && rsep))
+		return -EINVAL;
+
+	debug("avdtp_set_configuration(%p): int_seid=%u, acp_seid=%u",
+			session, lsep->info.seid, rsep->seid);
+
+	new_stream = g_new0(struct avdtp_stream, 1);
+	new_stream->session = session;
+	new_stream->lsep = lsep;
+	new_stream->rseid = rsep->seid;
+
+	g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+	/* Calculate total size of request */
+	for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		caps_len += cap->length + 2;
+	}
+
+	req = g_malloc0(sizeof(struct setconf_req) + caps_len);
+
+	req->int_seid = lsep->info.seid;
+	req->acp_seid = rsep->seid;
+
+	/* Copy the capabilities into the request */
+	for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		memcpy(ptr, cap, cap->length + 2);
+		ptr += cap->length + 2;
+	}
+
+	ret = send_request(session, FALSE, new_stream,
+				AVDTP_SET_CONFIGURATION, req,
+				sizeof(struct setconf_req) + caps_len);
+	if (ret < 0)
+		stream_free(new_stream);
+	else {
+		lsep->info.inuse = 1;
+		lsep->stream = new_stream;
+		rsep->stream = new_stream;
+		session->streams = g_slist_append(session->streams, new_stream);
+		if (stream)
+			*stream = new_stream;
+	}
+
+	g_free(req);
+
+	return ret;
+}
+
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+			struct avdtp_stream *stream)
+{
+	struct reconf_req *req;
+	unsigned char *ptr;
+	int caps_len, err;
+	GSList *l;
+	struct avdtp_service_capability *cap;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state != AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	/* Calculate total size of request */
+	for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		caps_len += cap->length + 2;
+	}
+
+	req = g_malloc0(sizeof(struct reconf_req) + caps_len);
+
+	req->acp_seid = stream->rseid;
+
+	/* Copy the capabilities into the request */
+	for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+		cap = l->data;
+		memcpy(ptr, cap, cap->length + 2);
+		ptr += cap->length + 2;
+	}
+
+	err = send_request(session, FALSE, stream, AVDTP_RECONFIGURE, req,
+						sizeof(*req) + caps_len);
+	g_free(req);
+
+	return err;
+}
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state > AVDTP_STATE_CONFIGURED)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_OPEN,
+							&req, sizeof(req));
+}
+
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct start_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state != AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	if (stream->close_int == TRUE) {
+		error("avdtp_start: rejecting start since close is initiated");
+		return -EINVAL;
+	}
+
+	memset(&req, 0, sizeof(req));
+	req.first_seid.seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_START,
+							&req, sizeof(req));
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+	int ret;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state < AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	if (stream->close_int == TRUE) {
+		error("avdtp_close: rejecting since close is already initiated");
+		return -EINVAL;
+	}
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	ret = send_request(session, FALSE, stream, AVDTP_CLOSE,
+							&req, sizeof(req));
+	if (ret == 0)
+		stream->close_int = TRUE;
+
+	return ret;
+}
+
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state <= AVDTP_STATE_OPEN || stream->close_int)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	return send_request(session, FALSE, stream, AVDTP_SUSPEND,
+							&req, sizeof(req));
+}
+
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
+{
+	struct seid_req req;
+	int ret;
+
+	if (!g_slist_find(session->streams, stream))
+		return -EINVAL;
+
+	if (stream->lsep->state <= AVDTP_STATE_OPEN)
+		return -EINVAL;
+
+	memset(&req, 0, sizeof(req));
+	req.acp_seid = stream->rseid;
+
+	ret = send_request(session, TRUE, stream, AVDTP_ABORT,
+							&req, sizeof(req));
+	if (ret == 0)
+		stream->abort_int = TRUE;
+
+	return ret;
+}
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+						uint8_t media_type,
+						uint8_t codec_type,
+						struct avdtp_sep_ind *ind,
+						struct avdtp_sep_cfm *cfm,
+						void *user_data)
+{
+	struct avdtp_server *server;
+	struct avdtp_local_sep *sep;
+
+	server = find_server(servers, src);
+	if (!server)
+		return NULL;
+
+	if (g_slist_length(server->seps) > MAX_SEID)
+		return NULL;
+
+	sep = g_new0(struct avdtp_local_sep, 1);
+
+	sep->state = AVDTP_STATE_IDLE;
+	sep->info.seid = g_slist_length(server->seps) + 1;
+	sep->info.type = type;
+	sep->info.media_type = media_type;
+	sep->codec = codec_type;
+	sep->ind = ind;
+	sep->cfm = cfm;
+	sep->user_data = user_data;
+	sep->server = server;
+
+	debug("SEP %p registered: type:%d codec:%d seid:%d", sep,
+			sep->info.type, sep->codec, sep->info.seid);
+	server->seps = g_slist_append(server->seps, sep);
+
+	return sep;
+}
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep)
+{
+	struct avdtp_server *server;
+
+	if (!sep)
+		return -EINVAL;
+
+	server = sep->server;
+	server->seps = g_slist_remove(server->seps, sep);
+
+	if (sep->stream)
+		release_stream(sep->stream, sep->stream->session);
+
+	g_free(sep);
+
+	return 0;
+}
+
+static GIOChannel *avdtp_server_socket(const bdaddr_t *src, gboolean master)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_listen(BT_IO_L2CAP, NULL, avdtp_confirm_cb,
+				NULL, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, src,
+				BT_IO_OPT_PSM, AVDTP_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+const char *avdtp_strerror(struct avdtp_error *err)
+{
+	if (err->type == AVDTP_ERROR_ERRNO)
+		return strerror(err->err.posix_errno);
+
+	switch(err->err.error_code) {
+	case AVDTP_BAD_HEADER_FORMAT:
+		return "Bad Header Format";
+	case AVDTP_BAD_LENGTH:
+		return "Bad Packet Lenght";
+	case AVDTP_BAD_ACP_SEID:
+		return "Bad Acceptor SEID";
+	case AVDTP_SEP_IN_USE:
+		return "Stream End Point in Use";
+	case AVDTP_SEP_NOT_IN_USE:
+		return "Stream End Point Not in Use";
+	case AVDTP_BAD_SERV_CATEGORY:
+		return "Bad Service Category";
+	case AVDTP_BAD_PAYLOAD_FORMAT:
+		return "Bad Payload format";
+	case AVDTP_NOT_SUPPORTED_COMMAND:
+		return "Command Not Supported";
+	case AVDTP_INVALID_CAPABILITIES:
+		return "Invalid Capabilities";
+	case AVDTP_BAD_RECOVERY_TYPE:
+		return "Bad Recovery Type";
+	case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT:
+		return "Bad Media Transport Format";
+	case AVDTP_BAD_RECOVERY_FORMAT:
+		return "Bad Recovery Format";
+	case AVDTP_BAD_ROHC_FORMAT:
+		return "Bad Header Compression Format";
+	case AVDTP_BAD_CP_FORMAT:
+		return "Bad Content Protetion Format";
+	case AVDTP_BAD_MULTIPLEXING_FORMAT:
+		return "Bad Multiplexing Format";
+	case AVDTP_UNSUPPORTED_CONFIGURATION:
+		return "Configuration not supported";
+	case AVDTP_BAD_STATE:
+		return "Bad State";
+	default:
+		return "Unknow error";
+	}
+}
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep)
+{
+	return sep->state;
+}
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst)
+{
+	if (src)
+		bacpy(src, &session->server->src);
+	if (dst)
+		bacpy(dst, &session->dst);
+}
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config)
+{
+	GError *err = NULL;
+	gboolean tmp, master = TRUE;
+	struct avdtp_server *server;
+
+	if (config) {
+		tmp = g_key_file_get_boolean(config, "General",
+							"Master", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_clear_error(&err);
+		} else
+			master = tmp;
+
+		tmp = g_key_file_get_boolean(config, "General", "AutoConnect",
+									&err);
+		if (err)
+			g_clear_error(&err);
+		else
+			auto_connect = tmp;
+	}
+
+	server = g_new0(struct avdtp_server, 1);
+	if (!server)
+		return -ENOMEM;
+
+	server->io = avdtp_server_socket(src, master);
+	if (!server->io) {
+		g_free(server);
+		return -1;
+	}
+
+	bacpy(&server->src, src);
+
+	servers = g_slist_append(servers, server);
+
+	return 0;
+}
+
+void avdtp_exit(const bdaddr_t *src)
+{
+	struct avdtp_server *server;
+	GSList *l;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	for (l = server->sessions; l; l = l->next) {
+		struct avdtp *session = l->data;
+
+		connection_lost(session, -ECONNABORTED);
+	}
+
+	servers = g_slist_remove(servers, server);
+
+	g_io_channel_shutdown(server->io, TRUE, NULL);
+	g_io_channel_unref(server->io);
+	g_free(server);
+}
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream)
+{
+	return g_slist_find(session->streams, stream) ? TRUE : FALSE;
+}
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc)
+{
+	session->auto_dc = auto_dc;
+}
+
+gboolean avdtp_stream_setup_active(struct avdtp *session)
+{
+	return session->stream_setup;
+}
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data)
+{
+	struct avdtp_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct avdtp_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	avdtp_callbacks = g_slist_append(avdtp_callbacks, state_cb);;
+
+	return state_cb->id;
+}
+
+gboolean avdtp_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = avdtp_callbacks; l != NULL; l = l->next) {
+		struct avdtp_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			avdtp_callbacks = g_slist_remove(avdtp_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/avdtp.h b/audio/avdtp.h
new file mode 100644
index 0000000..81b8f58
--- /dev/null
+++ b/audio/avdtp.h
@@ -0,0 +1,298 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+typedef enum {
+	AVDTP_ERROR_ERRNO,
+	AVDTP_ERROR_ERROR_CODE
+} avdtp_error_type_t;
+
+typedef enum {
+	AVDTP_SESSION_STATE_DISCONNECTED,
+	AVDTP_SESSION_STATE_CONNECTING,
+	AVDTP_SESSION_STATE_CONNECTED
+} avdtp_session_state_t;
+
+struct avdtp;
+struct avdtp_stream;
+struct avdtp_local_sep;
+struct avdtp_remote_sep;
+struct avdtp_error {
+	avdtp_error_type_t type;
+	union {
+		uint8_t error_code;
+		int posix_errno;
+	} err;
+};
+
+/* SEP capability categories */
+#define AVDTP_MEDIA_TRANSPORT			0x01
+#define AVDTP_REPORTING				0x02
+#define AVDTP_RECOVERY				0x03
+#define AVDTP_CONTENT_PROTECTION		0x04
+#define AVDTP_HEADER_COMPRESSION		0x05
+#define AVDTP_MULTIPLEXING			0x06
+#define AVDTP_MEDIA_CODEC			0x07
+
+/* AVDTP error definitions */
+#define AVDTP_BAD_HEADER_FORMAT			0x01
+#define AVDTP_BAD_LENGTH			0x11
+#define AVDTP_BAD_ACP_SEID			0x12
+#define AVDTP_SEP_IN_USE			0x13
+#define AVDTP_SEP_NOT_IN_USE			0x14
+#define AVDTP_BAD_SERV_CATEGORY			0x17
+#define AVDTP_BAD_PAYLOAD_FORMAT		0x18
+#define AVDTP_NOT_SUPPORTED_COMMAND		0x19
+#define AVDTP_INVALID_CAPABILITIES		0x1A
+#define AVDTP_BAD_RECOVERY_TYPE			0x22
+#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT	0x23
+#define AVDTP_BAD_RECOVERY_FORMAT		0x25
+#define AVDTP_BAD_ROHC_FORMAT			0x26
+#define AVDTP_BAD_CP_FORMAT			0x27
+#define AVDTP_BAD_MULTIPLEXING_FORMAT		0x28
+#define AVDTP_UNSUPPORTED_CONFIGURATION		0x29
+#define AVDTP_BAD_STATE				0x31
+
+/* SEP types definitions */
+#define AVDTP_SEP_TYPE_SOURCE			0x00
+#define AVDTP_SEP_TYPE_SINK			0x01
+
+/* Media types definitions */
+#define AVDTP_MEDIA_TYPE_AUDIO			0x00
+#define AVDTP_MEDIA_TYPE_VIDEO			0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA		0x02
+
+typedef enum {
+	AVDTP_STATE_IDLE,
+	AVDTP_STATE_CONFIGURED,
+	AVDTP_STATE_OPEN,
+	AVDTP_STATE_STREAMING,
+	AVDTP_STATE_CLOSING,
+	AVDTP_STATE_ABORTING,
+} avdtp_state_t;
+
+struct avdtp_service_capability {
+	uint8_t category;
+	uint8_t length;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_media_codec_capability {
+	uint8_t rfa0:4;
+	uint8_t media_type:4;
+	uint8_t media_codec_type;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_media_codec_capability {
+	uint8_t media_type:4;
+	uint8_t rfa0:4;
+	uint8_t media_codec_type;
+	uint8_t data[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+typedef void (*avdtp_session_state_cb) (struct audio_device *dev,
+					struct avdtp *session,
+					avdtp_session_state_t old_state,
+					avdtp_session_state_t new_state,
+					void *user_data);
+
+typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream,
+					avdtp_state_t old_state,
+					avdtp_state_t new_state,
+					struct avdtp_error *err,
+					void *user_data);
+
+/* Callbacks for when a reply is received to a command that we sent */
+struct avdtp_sep_cfm {
+	void (*set_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+	void (*get_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					struct avdtp_error *err,
+					void *user_data);
+	void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data);
+	void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+			struct avdtp_stream *stream, struct avdtp_error *err,
+			void *user_data);
+	void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+	void (*reconfigure) (struct avdtp *session,
+				struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream,
+				struct avdtp_error *err, void *user_data);
+};
+
+/* Callbacks for indicating when we received a new command. The return value
+ * indicates whether the command should be rejected or accepted */
+struct avdtp_sep_ind {
+	gboolean (*get_capability) (struct avdtp *session,
+					struct avdtp_local_sep *sep,
+					GSList **caps, uint8_t *err,
+					void *user_data);
+	gboolean (*set_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					struct avdtp_stream *stream,
+					GSList *caps, uint8_t *err,
+					uint8_t *category, void *user_data);
+	gboolean (*get_configuration) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					uint8_t *err, void *user_data);
+	gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*suspend) (struct avdtp *session,
+				struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*abort) (struct avdtp *session, struct avdtp_local_sep *sep,
+				struct avdtp_stream *stream, uint8_t *err,
+				void *user_data);
+	gboolean (*reconfigure) (struct avdtp *session,
+					struct avdtp_local_sep *lsep,
+					uint8_t *err, void *user_data);
+};
+
+typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
+					struct avdtp_error *err, void *user_data);
+
+struct avdtp *avdtp_get(bdaddr_t *src, bdaddr_t *dst);
+
+void avdtp_unref(struct avdtp *session);
+struct avdtp *avdtp_ref(struct avdtp *session);
+
+gboolean avdtp_is_connected(const bdaddr_t *src, const bdaddr_t *dst);
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+							void *data, int size);
+
+struct avdtp_remote_sep *avdtp_get_remote_sep(struct avdtp *session,
+						uint8_t seid);
+
+uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep);
+
+uint8_t avdtp_get_type(struct avdtp_remote_sep *sep);
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+
+struct avdtp_stream *avdtp_get_stream(struct avdtp_remote_sep *sep);
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+			void *user_data);
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream);
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+					struct avdtp_stream *stream,
+					avdtp_stream_state_cb cb, void *data);
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+				struct avdtp_stream *stream,
+				unsigned int id);
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+					uint16_t *imtu, uint16_t *omtu,
+					GSList **caps);
+struct avdtp_service_capability *avdtp_stream_get_codec(
+						struct avdtp_stream *stream);
+gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+				struct avdtp_service_capability *cap);
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+					GSList *caps);
+
+unsigned int avdtp_add_state_cb(avdtp_session_state_cb cb, void *user_data);
+
+gboolean avdtp_remove_state_cb(unsigned int id);
+
+int avdtp_set_configuration(struct avdtp *session,
+				struct avdtp_remote_sep *rsep,
+				struct avdtp_local_sep *lsep,
+				GSList *caps,
+				struct avdtp_stream **stream);
+
+int avdtp_get_configuration(struct avdtp *session,
+				struct avdtp_stream *stream);
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_reconfigure(struct avdtp *session, GSList *caps,
+			struct avdtp_stream *stream);
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+
+struct avdtp_local_sep *avdtp_register_sep(const bdaddr_t *src, uint8_t type,
+						uint8_t media_type,
+						uint8_t codec_type,
+						struct avdtp_sep_ind *ind,
+						struct avdtp_sep_cfm *cfm,
+						void *user_data);
+
+/* Find a matching pair of local and remote SEP ID's */
+int avdtp_get_seps(struct avdtp *session, uint8_t type, uint8_t media,
+			uint8_t codec, struct avdtp_local_sep **lsep,
+			struct avdtp_remote_sep **rsep);
+
+int avdtp_unregister_sep(struct avdtp_local_sep *sep);
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep);
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id);
+const char *avdtp_strerror(struct avdtp_error *err);
+avdtp_error_type_t avdtp_error_type(struct avdtp_error *err);
+int avdtp_error_error_code(struct avdtp_error *err);
+int avdtp_error_posix_errno(struct avdtp_error *err);
+
+void avdtp_get_peers(struct avdtp *session, bdaddr_t *src, bdaddr_t *dst);
+
+void avdtp_set_auto_disconnect(struct avdtp *session, gboolean auto_dc);
+gboolean avdtp_stream_setup_active(struct avdtp *session);
+
+int avdtp_init(const bdaddr_t *src, GKeyFile *config);
+void avdtp_exit(const bdaddr_t *src);
diff --git a/audio/bluetooth.conf b/audio/bluetooth.conf
new file mode 100644
index 0000000..55b51e4
--- /dev/null
+++ b/audio/bluetooth.conf
@@ -0,0 +1,36 @@
+# Please note that this ALSA configuration file fragment needs be enabled in
+# /etc/asound.conf or a similar configuration file with directives similar to
+# the following:
+#
+#@hooks [
+#	{
+#		func load
+#		files [
+#			"/etc/alsa/bluetooth.conf"
+#		]
+#		errors false
+#	}
+#]
+
+pcm.rawbluetooth {
+	@args [ ADDRESS ]
+	@args.ADDRESS {
+		type string
+	}
+	type bluetooth
+	device $ADDRESS
+}
+
+pcm.bluetooth {
+	@args [ ADDRESS ]
+	@args.ADDRESS {
+		type string
+	}
+	type plug
+	slave {
+		pcm {
+			type bluetooth
+			device $ADDRESS
+		}
+	}
+}
diff --git a/audio/control.c b/audio/control.c
new file mode 100644
index 0000000..e55d1f4
--- /dev/null
+++ b/audio/control.c
@@ -0,0 +1,1160 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/l2cap.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "error.h"
+#include "uinput.h"
+#include "adapter.h"
+#include "../src/device.h"
+#include "device.h"
+#include "manager.h"
+#include "avdtp.h"
+#include "control.h"
+#include "sdpd.h"
+#include "glib-helper.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#define AVCTP_PSM 23
+
+/* Message types */
+#define AVCTP_COMMAND		0
+#define AVCTP_RESPONSE		1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE	0
+#define AVCTP_PACKET_START	1
+#define AVCTP_PACKET_CONTINUE	2
+#define AVCTP_PACKET_END	3
+
+/* ctype entries */
+#define CTYPE_CONTROL		0x0
+#define CTYPE_STATUS		0x1
+#define CTYPE_NOT_IMPLEMENTED	0x8
+#define CTYPE_ACCEPTED		0x9
+#define CTYPE_REJECTED		0xA
+#define CTYPE_STABLE		0xC
+
+/* opcodes */
+#define OP_UNITINFO		0x30
+#define OP_SUBUNITINFO		0x31
+#define OP_PASSTHROUGH		0x7c
+
+/* subunits of interest */
+#define SUBUNIT_PANEL		0x09
+
+/* operands in passthrough commands */
+#define VOL_UP_OP		0x41
+#define VOL_DOWN_OP		0x42
+#define MUTE_OP			0x43
+#define PLAY_OP			0x44
+#define STOP_OP			0x45
+#define PAUSE_OP		0x46
+#define RECORD_OP		0x47
+#define REWIND_OP		0x48
+#define FAST_FORWARD_OP		0x49
+#define EJECT_OP		0x4a
+#define FORWARD_OP		0x4b
+#define BACKWARD_OP		0x4c
+
+static DBusConnection *connection = NULL;
+static gchar *input_device_name = NULL;
+static GSList *servers = NULL;
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+	uint8_t ipid:1;
+	uint8_t cr:1;
+	uint8_t packet_type:2;
+	uint8_t transaction:4;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avrcp_header {
+	uint8_t code:4;
+	uint8_t _hdr0:4;
+	uint8_t subunit_id:3;
+	uint8_t subunit_type:5;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 3
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+	uint8_t transaction:4;
+	uint8_t packet_type:2;
+	uint8_t cr:1;
+	uint8_t ipid:1;
+	uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_LENGTH 3
+
+struct avrcp_header {
+	uint8_t _hdr0:4;
+	uint8_t code:4;
+	uint8_t subunit_type:5;
+	uint8_t subunit_id:3;
+	uint8_t opcode;
+} __attribute__ ((packed));
+#define AVRCP_HEADER_LENGTH 3
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_state_callback {
+	avctp_state_cb cb;
+	void *user_data;
+	unsigned int id;
+};
+
+struct avctp_server {
+	bdaddr_t src;
+	GIOChannel *io;
+	uint32_t tg_record_id;
+#ifndef ANDROID
+	uint32_t ct_record_id;
+#endif
+};
+
+struct control {
+	struct audio_device *dev;
+
+	avctp_state_t state;
+
+	int uinput;
+
+	GIOChannel *io;
+	guint io_id;
+
+	uint16_t mtu;
+
+	gboolean target;
+};
+
+static struct {
+	const char *name;
+	uint8_t avrcp;
+	uint16_t uinput;
+} key_map[] = {
+	{ "PLAY",		PLAY_OP,		KEY_PLAYCD },
+	{ "STOP",		STOP_OP,		KEY_STOPCD },
+	{ "PAUSE",		PAUSE_OP,		KEY_PAUSECD },
+	{ "FORWARD",		FORWARD_OP,		KEY_NEXTSONG },
+	{ "BACKWARD",		BACKWARD_OP,		KEY_PREVIOUSSONG },
+	{ "REWIND",		REWIND_OP,		KEY_REWIND },
+	{ "FAST FORWARD",	FAST_FORWARD_OP,	KEY_FASTFORWARD },
+	{ NULL }
+};
+
+static GSList *avctp_callbacks = NULL;
+
+static sdp_record_t *avrcp_ct_record()
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap, avctp, avrct;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	/* Service Class ID List */
+	sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &avrct);
+	sdp_set_service_classes(record, svclass_id);
+
+	/* Protocol Descriptor List */
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avctp, AVCTP_UUID);
+	proto[1] = sdp_list_append(0, &avctp);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	/* Bluetooth Profile Descriptor List */
+	sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+	profile[0].version = ver;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	sdp_set_info_attr(record, "AVRCP CT", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static sdp_record_t *avrcp_tg_record()
+{
+	sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+	uuid_t root_uuid, l2cap, avctp, avrtg;
+	sdp_profile_desc_t profile[1];
+	sdp_list_t *aproto, *proto[2];
+	sdp_record_t *record;
+	sdp_data_t *psm, *version, *features;
+	uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f;
+
+	record = sdp_record_alloc();
+	if (!record)
+		return NULL;
+
+	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+	root = sdp_list_append(0, &root_uuid);
+	sdp_set_browse_groups(record, root);
+
+	/* Service Class ID List */
+	sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+	svclass_id = sdp_list_append(0, &avrtg);
+	sdp_set_service_classes(record, svclass_id);
+
+	/* Protocol Descriptor List */
+	sdp_uuid16_create(&l2cap, L2CAP_UUID);
+	proto[0] = sdp_list_append(0, &l2cap);
+	psm = sdp_data_alloc(SDP_UINT16, &lp);
+	proto[0] = sdp_list_append(proto[0], psm);
+	apseq = sdp_list_append(0, proto[0]);
+
+	sdp_uuid16_create(&avctp, AVCTP_UUID);
+	proto[1] = sdp_list_append(0, &avctp);
+	version = sdp_data_alloc(SDP_UINT16, &ver);
+	proto[1] = sdp_list_append(proto[1], version);
+	apseq = sdp_list_append(apseq, proto[1]);
+
+	aproto = sdp_list_append(0, apseq);
+	sdp_set_access_protos(record, aproto);
+
+	/* Bluetooth Profile Descriptor List */
+	sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+	profile[0].version = ver;
+	pfseq = sdp_list_append(0, &profile[0]);
+	sdp_set_profile_descs(record, pfseq);
+
+	features = sdp_data_alloc(SDP_UINT16, &feat);
+	sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+	sdp_set_info_attr(record, "AVRCP TG", 0, 0);
+
+	free(psm);
+	free(version);
+	sdp_list_free(proto[0], 0);
+	sdp_list_free(proto[1], 0);
+	sdp_list_free(apseq, 0);
+	sdp_list_free(aproto, 0);
+	sdp_list_free(pfseq, 0);
+	sdp_list_free(root, 0);
+	sdp_list_free(svclass_id, 0);
+
+	return record;
+}
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+	struct uinput_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.type	= type;
+	event.code	= code;
+	event.value	= value;
+
+	return write(fd, &event, sizeof(event));
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+	if (fd < 0)
+		return;
+
+	send_event(fd, EV_KEY, key, pressed);
+	send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static void handle_panel_passthrough(struct control *control,
+					const unsigned char *operands,
+					int operand_count)
+{
+	const char *status;
+	int pressed, i;
+
+	if (operand_count == 0)
+		return;
+
+	if (operands[0] & 0x80) {
+		status = "released";
+		pressed = 0;
+	} else {
+		status = "pressed";
+		pressed = 1;
+	}
+
+	for (i = 0; key_map[i].name != NULL; i++) {
+		if ((operands[0] & 0x7F) == key_map[i].avrcp) {
+			debug("AVRCP: %s %s", key_map[i].name, status);
+			send_key(control->uinput, key_map[i].uinput, pressed);
+			break;
+		}
+	}
+
+	if (key_map[i].name == NULL)
+		debug("AVRCP: unknown button 0x%02X %s",
+						operands[0] & 0x7F, status);
+}
+
+static void avctp_disconnected(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (!control)
+		return;
+
+	if (control->io) {
+		g_io_channel_shutdown(control->io, TRUE, NULL);
+		g_io_channel_unref(control->io);
+		control->io = NULL;
+	}
+
+	if (control->io_id) {
+		g_source_remove(control->io_id);
+		control->io_id = 0;
+	}
+
+	if (control->uinput >= 0) {
+		ioctl(control->uinput, UI_DEV_DESTROY);
+		close(control->uinput);
+		control->uinput = -1;
+	}
+}
+
+static void avctp_set_state(struct control *control, avctp_state_t new_state)
+{
+	GSList *l;
+	struct audio_device *dev = control->dev;
+	avdtp_session_state_t old_state = control->state;
+	gboolean value;
+
+	switch (new_state) {
+	case AVCTP_STATE_DISCONNECTED:
+		avctp_disconnected(control->dev);
+
+		if (old_state != AVCTP_STATE_CONNECTED)
+			break;
+
+		value = FALSE;
+		g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE,
+					"Disconnected", DBUS_TYPE_INVALID);
+		emit_property_changed(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE, "Connected",
+					DBUS_TYPE_BOOLEAN, &value);
+		break;
+	case AVCTP_STATE_CONNECTING:
+		break;
+	case AVCTP_STATE_CONNECTED:
+		value = TRUE;
+		g_dbus_emit_signal(control->dev->conn, control->dev->path,
+				AUDIO_CONTROL_INTERFACE, "Connected",
+				DBUS_TYPE_INVALID);
+		emit_property_changed(control->dev->conn, control->dev->path,
+				AUDIO_CONTROL_INTERFACE, "Connected",
+				DBUS_TYPE_BOOLEAN, &value);
+		break;
+	default:
+		error("Invalid AVCTP state %d", new_state);
+		return;
+	}
+
+	control->state = new_state;
+
+	for (l = avctp_callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		cb->cb(control->dev, old_state, new_state, cb->user_data);
+	}
+}
+
+static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
+				gpointer data)
+{
+	struct control *control = data;
+	unsigned char buf[1024], *operands;
+	struct avctp_header *avctp;
+	struct avrcp_header *avrcp;
+	int ret, packet_size, operand_count, sock;
+
+	if (!(cond | G_IO_IN))
+		goto failed;
+
+	sock = g_io_channel_unix_get_fd(control->io);
+
+	ret = read(sock, buf, sizeof(buf));
+	if (ret <= 0)
+		goto failed;
+
+	debug("Got %d bytes of data for AVCTP session %p", ret, control);
+
+	if ((unsigned int) ret < sizeof(struct avctp_header)) {
+		error("Too small AVCTP packet");
+		goto failed;
+	}
+
+	packet_size = ret;
+
+	avctp = (struct avctp_header *) buf;
+
+	debug("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+			"PID 0x%04X",
+			avctp->transaction, avctp->packet_type,
+			avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+	ret -= sizeof(struct avctp_header);
+	if ((unsigned int) ret < sizeof(struct avrcp_header)) {
+		error("Too small AVRCP packet");
+		goto failed;
+	}
+
+	avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+
+	ret -= sizeof(struct avrcp_header);
+
+	operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header);
+	operand_count = ret;
+
+	debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
+			"opcode 0x%02X, %d operands",
+			avctp->cr ? "response" : "command",
+			avrcp->code, avrcp->subunit_type, avrcp->subunit_id,
+			avrcp->opcode, operand_count);
+
+	if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_NOT_IMPLEMENTED;
+	} else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+		avctp->ipid = 1;
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_REJECTED;
+	} else if (avctp->cr == AVCTP_COMMAND &&
+			avrcp->code == CTYPE_CONTROL &&
+			avrcp->subunit_type == SUBUNIT_PANEL &&
+			avrcp->opcode == OP_PASSTHROUGH) {
+		handle_panel_passthrough(control, operands, operand_count);
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_ACCEPTED;
+	} else if (avctp->cr == AVCTP_COMMAND &&
+			avrcp->code == CTYPE_STATUS &&
+			(avrcp->opcode == OP_UNITINFO
+			|| avrcp->opcode == OP_SUBUNITINFO)) {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_STABLE;
+		/* The first operand should be 0x07 for the UNITINFO response.
+		 * Neither AVRCP (section 22.1, page 117) nor AVC Digital
+		 * Interface Command Set (section 9.2.1, page 45) specs
+		 * explain this value but both use it */
+		if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO)
+			operands[0] = 0x07;
+		if (operand_count >= 2)
+			operands[1] = SUBUNIT_PANEL << 3;
+		debug("reply to %s", avrcp->opcode == OP_UNITINFO ?
+				"OP_UNITINFO" : "OP_SUBUNITINFO");
+	} else {
+		avctp->cr = AVCTP_RESPONSE;
+		avrcp->code = CTYPE_REJECTED;
+	}
+	ret = write(sock, buf, packet_size);
+
+	return TRUE;
+
+failed:
+	debug("AVCTP session %p got disconnected", control);
+	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+	return FALSE;
+}
+
+static int uinput_create(char *name)
+{
+	struct uinput_dev dev;
+	int fd, err, i;
+
+	fd = open("/dev/uinput", O_RDWR);
+	if (fd < 0) {
+		fd = open("/dev/input/uinput", O_RDWR);
+		if (fd < 0) {
+			fd = open("/dev/misc/uinput", O_RDWR);
+			if (fd < 0) {
+				err = errno;
+				error("Can't open input device: %s (%d)",
+							strerror(err), err);
+				return -err;
+			}
+		}
+	}
+
+	memset(&dev, 0, sizeof(dev));
+	if (name)
+		strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
+
+	dev.id.bustype = BUS_BLUETOOTH;
+	dev.id.vendor  = 0x0000;
+	dev.id.product = 0x0000;
+	dev.id.version = 0x0000;
+
+	if (write(fd, &dev, sizeof(dev)) < 0) {
+		err = errno;
+		error("Can't write device information: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	ioctl(fd, UI_SET_EVBIT, EV_KEY);
+	ioctl(fd, UI_SET_EVBIT, EV_REL);
+	ioctl(fd, UI_SET_EVBIT, EV_REP);
+	ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+	for (i = 0; key_map[i].name != NULL; i++)
+		ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+	if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+		err = errno;
+		error("Can't create uinput device: %s (%d)",
+						strerror(err), err);
+		close(fd);
+		errno = err;
+		return -err;
+	}
+
+	return fd;
+}
+
+static void init_uinput(struct control *control)
+{
+	char address[18], *name;
+
+	ba2str(&control->dev->dst, address);
+
+	/* Use device name from config file if specified */
+	name = input_device_name;
+	if (!name)
+		name = address;
+
+	control->uinput = uinput_create(name);
+	if (control->uinput < 0)
+		error("AVRCP: failed to init uinput for %s", address);
+	else
+		debug("AVRCP: uinput initialized for %s", address);
+}
+
+static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
+{
+	struct control *control = data;
+	char address[18];
+	uint16_t imtu;
+	GError *gerr = NULL;
+
+	if (err) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		return;
+	}
+
+	bt_io_get(chan, BT_IO_L2CAP, &gerr,
+			BT_IO_OPT_DEST, &address,
+			BT_IO_OPT_IMTU, &imtu,
+			BT_IO_OPT_INVALID);
+	if (gerr) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", gerr->message);
+		g_error_free(gerr);
+		return;
+	}
+
+	debug("AVCTP: connected to %s", address);
+
+	if (!control->io)
+		control->io = g_io_channel_ref(chan);
+
+	init_uinput(control);
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTED);
+	control->mtu = imtu;
+	control->io_id = g_io_add_watch(chan,
+				G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) control_cb, control);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct control *control = user_data;
+	GError *err = NULL;
+
+	if (derr && dbus_error_is_set(derr)) {
+		error("Access denied: %s", derr->message);
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		return;
+	}
+
+	if (!bt_io_accept(control->io, avctp_connect_cb, control,
+								NULL, &err)) {
+		error("bt_io_accept: %s", err->message);
+		g_error_free(err);
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+	}
+}
+
+static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
+{
+	struct control *control = NULL;
+	struct audio_device *dev;
+	char address[18];
+	bdaddr_t src, dst;
+	GError *err = NULL;
+
+	bt_io_get(chan, BT_IO_L2CAP, &err,
+			BT_IO_OPT_SOURCE_BDADDR, &src,
+			BT_IO_OPT_DEST_BDADDR, &dst,
+			BT_IO_OPT_DEST, address,
+			BT_IO_OPT_INVALID);
+	if (err) {
+		error("%s", err->message);
+		g_error_free(err);
+		g_io_channel_shutdown(chan, TRUE, NULL);
+		return;
+	}
+
+	dev = manager_get_device(&src, &dst, TRUE);
+	if (!dev) {
+		error("Unable to get audio device object for %s", address);
+		goto drop;
+	}
+
+	if (!dev->control) {
+		btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
+		if (!dev->control)
+			goto drop;
+	}
+
+	control = dev->control;
+
+	if (control->io) {
+		error("Refusing unexpected connect from %s", address);
+		goto drop;
+	}
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTING);
+	control->io = g_io_channel_ref(chan);
+
+	if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
+						auth_cb, dev->control) < 0)
+		goto drop;
+
+	return;
+
+drop:
+	if (!control || !control->io)
+		g_io_channel_shutdown(chan, TRUE, NULL);
+	if (control)
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
+{
+	GError *err = NULL;
+	GIOChannel *io;
+
+	io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
+				NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, src,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+				BT_IO_OPT_MASTER, master,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("%s", err->message);
+		g_error_free(err);
+	}
+
+	return io;
+}
+
+gboolean avrcp_connect(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (control->state > AVCTP_STATE_DISCONNECTED)
+		return TRUE;
+
+	avctp_set_state(control, AVCTP_STATE_CONNECTING);
+
+	io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_PSM, AVCTP_PSM,
+				BT_IO_OPT_INVALID);
+	if (err) {
+		avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+		error("%s", err->message);
+		g_error_free(err);
+		return FALSE;
+	}
+
+	control->io = io;
+
+	return TRUE;
+}
+
+void avrcp_disconnect(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (!(control && control->io))
+		return;
+
+	avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+}
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
+{
+	sdp_record_t *record;
+	gboolean tmp, master = TRUE;
+	GError *err = NULL;
+	struct avctp_server *server;
+
+	if (config) {
+		tmp = g_key_file_get_boolean(config, "General",
+							"Master", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			g_error_free(err);
+		} else
+			master = tmp;
+		err = NULL;
+		input_device_name = g_key_file_get_string(config,
+			"AVRCP", "InputDeviceName", &err);
+		if (err) {
+			debug("audio.conf: %s", err->message);
+			input_device_name = NULL;
+			g_error_free(err);
+		}
+	}
+
+	server = g_new0(struct avctp_server, 1);
+	if (!server)
+		return -ENOMEM;
+
+	if (!connection)
+		connection = dbus_connection_ref(conn);
+
+	record = avrcp_tg_record();
+	if (!record) {
+		error("Unable to allocate new service record");
+		return -1;
+	}
+
+	if (add_record_to_server(src, record) < 0) {
+		error("Unable to register AVRCP target service record");
+		sdp_record_free(record);
+		return -1;
+	}
+	server->tg_record_id = record->handle;
+
+#ifndef ANDROID
+	record = avrcp_ct_record();
+	if (!record) {
+		error("Unable to allocate new service record");
+		return -1;
+	}
+
+	if (add_record_to_server(src, record) < 0) {
+		error("Unable to register AVRCP controller service record");
+		sdp_record_free(record);
+		return -1;
+	}
+	server->ct_record_id = record->handle;
+#endif
+
+	server->io = avctp_server_socket(src, master);
+	if (!server->io) {
+#ifndef ANDROID
+		remove_record_from_server(server->ct_record_id);
+#endif
+		remove_record_from_server(server->tg_record_id);
+		g_free(server);
+		return -1;
+	}
+
+	bacpy(&server->src, src);
+
+	servers = g_slist_append(servers, server);
+
+	return 0;
+}
+
+static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
+{
+	GSList *l;
+
+	for (l = list; l; l = l->next) {
+		struct avctp_server *server = l->data;
+
+		if (bacmp(&server->src, src) == 0)
+			return server;
+	}
+
+	return NULL;
+}
+
+void avrcp_unregister(const bdaddr_t *src)
+{
+	struct avctp_server *server;
+
+	server = find_server(servers, src);
+	if (!server)
+		return;
+
+	servers = g_slist_remove(servers, server);
+
+#ifndef ANDROID
+	remove_record_from_server(server->ct_record_id);
+#endif
+	remove_record_from_server(server->tg_record_id);
+
+	g_io_channel_shutdown(server->io, TRUE, NULL);
+	g_io_channel_unref(server->io);
+	g_free(server);
+
+	if (servers)
+		return;
+
+	dbus_connection_unref(connection);
+	connection = NULL;
+}
+
+static DBusMessage *control_is_connected(DBusConnection *conn,
+						DBusMessage *msg,
+						void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	dbus_bool_t connected;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	connected = (control->state == AVCTP_STATE_CONNECTED);
+
+	dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
+					DBUS_TYPE_INVALID);
+
+	return reply;
+}
+
+static int avctp_send_passthrough(struct control *control, uint8_t op)
+{
+	unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2];
+	struct avctp_header *avctp = (void *) buf;
+	struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+	uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
+	int err, sk = g_io_channel_unix_get_fd(control->io);
+	static uint8_t transaction = 0;
+
+	memset(buf, 0, sizeof(buf));
+
+	avctp->transaction = transaction++;
+	avctp->packet_type = AVCTP_PACKET_SINGLE;
+	avctp->cr = AVCTP_COMMAND;
+	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+	avrcp->code = CTYPE_CONTROL;
+	avrcp->subunit_type = SUBUNIT_PANEL;
+	avrcp->opcode = OP_PASSTHROUGH;
+
+	operands[0] = op & 0x7f;
+	operands[1] = 0;
+
+	err = write(sk, buf, sizeof(buf));
+	if (err < 0)
+		return err;
+
+	/* Button release */
+	avctp->transaction = transaction++;
+	operands[0] |= 0x80;
+
+	return write(sk, buf, sizeof(buf));
+}
+
+static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	int err;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (control->state != AVCTP_STATE_CONNECTED)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotConnected",
+					"Device not Connected");
+
+	if (!control->target)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotSupported",
+					"AVRCP Target role not supported");
+
+	err = avctp_send_passthrough(control, VOL_UP_OP);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(-err));
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessage *reply;
+	int err;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (control->state != AVCTP_STATE_CONNECTED)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotConnected",
+					"Device not Connected");
+
+	if (!control->target)
+		return g_dbus_create_error(msg,
+					ERROR_INTERFACE ".NotSupported",
+					"AVRCP Target role not supported");
+
+	err = avctp_send_passthrough(control, VOL_DOWN_OP);
+	if (err < 0)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+							strerror(-err));
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *control_get_properties(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	gboolean value;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Connected */
+	value = (device->control->state == AVCTP_STATE_CONNECTED);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable control_methods[] = {
+	{ "IsConnected",	"",	"b",	control_is_connected,
+						G_DBUS_METHOD_FLAG_DEPRECATED },
+	{ "GetProperties",	"",	"a{sv}",control_get_properties },
+	{ "VolumeUp",		"",	"",	volume_up },
+	{ "VolumeDown",		"",	"",	volume_down },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable control_signals[] = {
+	{ "Connected",			"",	G_DBUS_SIGNAL_FLAG_DEPRECATED},
+	{ "Disconnected",		"",	G_DBUS_SIGNAL_FLAG_DEPRECATED},
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+static void path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+	struct control *control = dev->control;
+
+	debug("Unregistered interface %s on path %s",
+		AUDIO_CONTROL_INTERFACE, dev->path);
+
+	if (control->state != AVCTP_STATE_DISCONNECTED)
+		avctp_disconnected(dev);
+
+	g_free(control);
+	dev->control = NULL;
+}
+
+void control_unregister(struct audio_device *dev)
+{
+	g_dbus_unregister_interface(dev->conn, dev->path,
+		AUDIO_CONTROL_INTERFACE);
+}
+
+void control_update(struct audio_device *dev, uint16_t uuid16)
+{
+	struct control *control = dev->control;
+
+	if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+		control->target = TRUE;
+}
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16)
+{
+	struct control *control;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_CONTROL_INTERFACE,
+					control_methods, control_signals, NULL,
+					dev, path_unregister))
+		return NULL;
+
+	debug("Registered interface %s on path %s",
+		AUDIO_CONTROL_INTERFACE, dev->path);
+
+	control = g_new0(struct control, 1);
+	control->dev = dev;
+	control->state = AVCTP_STATE_DISCONNECTED;
+	control->uinput = -1;
+
+	if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
+		control->target = TRUE;
+
+	return control;
+}
+
+gboolean control_is_active(struct audio_device *dev)
+{
+	struct control *control = dev->control;
+
+	if (control && control->state != AVCTP_STATE_DISCONNECTED)
+		return TRUE;
+
+	return FALSE;
+}
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
+{
+	struct avctp_state_callback *state_cb;
+	static unsigned int id = 0;
+
+	state_cb = g_new(struct avctp_state_callback, 1);
+	state_cb->cb = cb;
+	state_cb->user_data = user_data;
+	state_cb->id = ++id;
+
+	avctp_callbacks = g_slist_append(avctp_callbacks, state_cb);
+
+	return state_cb->id;
+}
+
+gboolean avctp_remove_state_cb(unsigned int id)
+{
+	GSList *l;
+
+	for (l = avctp_callbacks; l != NULL; l = l->next) {
+		struct avctp_state_callback *cb = l->data;
+		if (cb && cb->id == id) {
+			avctp_callbacks = g_slist_remove(avctp_callbacks, cb);
+			g_free(cb);
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff --git a/audio/control.h b/audio/control.h
new file mode 100644
index 0000000..ad0aca5
--- /dev/null
+++ b/audio/control.h
@@ -0,0 +1,50 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
+
+typedef enum {
+	AVCTP_STATE_DISCONNECTED = 0,
+	AVCTP_STATE_CONNECTING,
+	AVCTP_STATE_CONNECTED
+} avctp_state_t;
+
+typedef void (*avctp_state_cb) (struct audio_device *dev,
+				avctp_state_t old_state,
+				avctp_state_t new_state,
+				void *user_data);
+
+unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
+gboolean avctp_remove_state_cb(unsigned int id);
+
+int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
+void avrcp_unregister(const bdaddr_t *src);
+
+gboolean avrcp_connect(struct audio_device *dev);
+void avrcp_disconnect(struct audio_device *dev);
+
+struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+void control_update(struct audio_device *dev, uint16_t uuid16);
+void control_unregister(struct audio_device *dev);
+gboolean control_is_active(struct audio_device *dev);
diff --git a/audio/ctl_bluetooth.c b/audio/ctl_bluetooth.c
new file mode 100644
index 0000000..2e12f65
--- /dev/null
+++ b/audio/ctl_bluetooth.c
@@ -0,0 +1,384 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <alsa/asoundlib.h>
+#include <alsa/control_external.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "ipc.h"
+
+#ifdef ENABLE_DEBUG
+#define DBG(fmt, arg...)  printf("DEBUG: %s: " fmt "\n" , __FUNCTION__ , ## arg)
+#else
+#define DBG(fmt, arg...)
+#endif
+
+#define BLUETOOTH_MINVOL 0
+#define BLUETOOTH_MAXVOL 15
+
+struct bluetooth_data {
+	snd_ctl_ext_t ext;
+	int sock;
+};
+
+enum {
+	BLUETOOTH_PLAYBACK,
+	BLUETOOTH_CAPTURE,
+};
+
+static const char *vol_devices[2] = {
+	[BLUETOOTH_PLAYBACK]	= "Playback volume",
+	[BLUETOOTH_CAPTURE]	= "Capture volume",
+};
+
+static void bluetooth_exit(struct bluetooth_data *data)
+{
+	if (data == NULL)
+		return;
+
+	if (data->sock >= 0)
+		bt_audio_service_close(data->sock);
+
+	free(data);
+}
+
+static void bluetooth_close(snd_ctl_ext_t *ext)
+{
+	struct bluetooth_data *data = ext->private_data;
+
+	DBG("ext %p", ext);
+
+	bluetooth_exit(data);
+}
+
+static int bluetooth_elem_count(snd_ctl_ext_t *ext)
+{
+	DBG("ext %p", ext);
+
+	return 2;
+}
+
+static int bluetooth_elem_list(snd_ctl_ext_t *ext,
+				unsigned int offset, snd_ctl_elem_id_t *id)
+{
+	DBG("ext %p offset %d id %p", ext, offset, id);
+
+	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+
+	if (offset > 1)
+		return -EINVAL;
+
+	snd_ctl_elem_id_set_name(id, vol_devices[offset]);
+
+	return 0;
+}
+
+static snd_ctl_ext_key_t bluetooth_find_elem(snd_ctl_ext_t *ext,
+						const snd_ctl_elem_id_t *id)
+{
+	const char *name = snd_ctl_elem_id_get_name(id);
+	int i;
+
+	DBG("ext %p id %p name '%s'", ext, id, name);
+
+	for (i = 0; i < 2; i++)
+		if (strcmp(name, vol_devices[i]) == 0)
+			return i;
+
+	return SND_CTL_EXT_KEY_NOT_FOUND;
+}
+
+static int bluetooth_get_attribute(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+			int *type, unsigned int *acc, unsigned int *count)
+{
+	DBG("ext %p key %ld", ext, key);
+
+	*type = SND_CTL_ELEM_TYPE_INTEGER;
+	*acc = SND_CTL_EXT_ACCESS_READWRITE;
+	*count = 1;
+
+	return 0;
+}
+
+static int bluetooth_get_integer_info(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+					long *imin, long *imax, long *istep)
+{
+	DBG("ext %p key %ld", ext, key);
+
+	*istep = 1;
+	*imin  = BLUETOOTH_MINVOL;
+	*imax  = BLUETOOTH_MAXVOL;
+
+	return 0;
+}
+
+static int bluetooth_send_ctl(struct bluetooth_data *data,
+			uint8_t mode, uint8_t key, struct bt_control_rsp *rsp)
+{
+	int ret;
+	struct bt_control_req *req = (void *) rsp;
+	bt_audio_error_t *err = (void *) rsp;
+	const char *type, *name;
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_CONTROL;
+	req->h.length = sizeof(*req);
+
+	req->mode = mode;
+	req->key = key;
+
+	ret = send(data->sock, req, BT_SUGGESTED_BUFFER_SIZE, MSG_NOSIGNAL);
+	if (ret <= 0) {
+		SYSERR("Unable to request new volume value to server");
+		return  -errno;
+	}
+
+	ret = recv(data->sock, rsp, BT_SUGGESTED_BUFFER_SIZE, 0);
+	if (ret <= 0) {
+		SNDERR("Unable to receive new volume value from server");
+		return  -errno;
+	}
+
+	if (rsp->h.type == BT_ERROR) {
+		SNDERR("BT_CONTROL failed : %s (%d)",
+					strerror(err->posix_errno),
+					err->posix_errno);
+		return -err->posix_errno;
+	}
+
+	type = bt_audio_strtype(rsp->h.type);
+	if (!type) {
+		SNDERR("Bogus message type %d "
+				"received from audio service",
+				rsp->h.type);
+		return -EINVAL;
+	}
+
+	name = bt_audio_strname(rsp->h.name);
+	if (!name) {
+		SNDERR("Bogus message name %d "
+				"received from audio service",
+				rsp->h.name);
+		return -EINVAL;
+	}
+
+	if (rsp->h.name != BT_CONTROL) {
+		SNDERR("Unexpected message %s received", type);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bluetooth_read_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+								long *value)
+{
+	struct bluetooth_data *data = ext->private_data;
+	int ret;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_control_rsp *rsp = (void *) buf;
+
+	DBG("ext %p key %ld", ext, key);
+
+	memset(buf, 0, sizeof(buf));
+	*value = 0;
+
+	ret = bluetooth_send_ctl(data, key, 0, rsp);
+	if (ret < 0)
+		goto done;
+
+	*value = rsp->key;
+done:
+	return ret;
+}
+
+static int bluetooth_write_integer(snd_ctl_ext_t *ext, snd_ctl_ext_key_t key,
+								long *value)
+{
+	struct bluetooth_data *data = ext->private_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_control_rsp *rsp = (void *) buf;
+	long current;
+	int ret, keyvalue;
+
+	DBG("ext %p key %ld", ext, key);
+
+	ret = bluetooth_read_integer(ext, key, &current);
+	if (ret < 0)
+		return ret;
+
+	if (*value == current)
+		return 0;
+
+	while (*value != current) {
+		keyvalue = (*value > current) ? BT_CONTROL_KEY_VOL_UP :
+				BT_CONTROL_KEY_VOL_DOWN;
+
+		ret = bluetooth_send_ctl(data, key, keyvalue, rsp);
+		if (ret < 0)
+			break;
+
+		current = keyvalue;
+	}
+
+	return ret;
+}
+
+static int bluetooth_read_event(snd_ctl_ext_t *ext, snd_ctl_elem_id_t *id,
+						unsigned int *event_mask)
+{
+	struct bluetooth_data *data = ext->private_data;
+	char buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_control_ind *ind = (void *) buf;
+	int ret;
+	const char *type, *name;
+
+	DBG("ext %p id %p", ext, id);
+
+	memset(buf, 0, sizeof(buf));
+
+	ret = recv(data->sock, ind, BT_SUGGESTED_BUFFER_SIZE, MSG_DONTWAIT);
+	if (ret < 0) {
+		SNDERR("Failed while receiving data: %s (%d)",
+				strerror(errno), errno);
+		return -errno;
+	}
+
+	type = bt_audio_strtype(ind->h.type);
+	if (!type) {
+		SNDERR("Bogus message type %d "
+				"received from audio service",
+				ind->h.type);
+		return -EAGAIN;
+	}
+
+	name = bt_audio_strname(ind->h.name);
+	if (!name) {
+		SNDERR("Bogus message name %d "
+				"received from audio service",
+				ind->h.name);
+		return -EAGAIN;
+	}
+
+	if (ind->h.name != BT_CONTROL) {
+		SNDERR("Unexpected message %s received", name);
+		return -EAGAIN;
+	}
+
+	snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
+	snd_ctl_elem_id_set_name(id, ind->mode == BLUETOOTH_PLAYBACK ?
+				vol_devices[BLUETOOTH_PLAYBACK] :
+				vol_devices[BLUETOOTH_CAPTURE]);
+	*event_mask = SND_CTL_EVENT_MASK_VALUE;
+
+	return 1;
+}
+
+static snd_ctl_ext_callback_t bluetooth_callback = {
+	.close			= bluetooth_close,
+	.elem_count		= bluetooth_elem_count,
+	.elem_list		= bluetooth_elem_list,
+	.find_elem		= bluetooth_find_elem,
+	.get_attribute		= bluetooth_get_attribute,
+	.get_integer_info	= bluetooth_get_integer_info,
+	.read_integer		= bluetooth_read_integer,
+	.write_integer		= bluetooth_write_integer,
+	.read_event		= bluetooth_read_event,
+};
+
+static int bluetooth_init(struct bluetooth_data *data)
+{
+	int sk;
+
+	if (!data)
+		return -EINVAL;
+
+	memset(data, 0, sizeof(struct bluetooth_data));
+
+	data->sock = -1;
+
+	sk = bt_audio_service_open();
+	if (sk < 0)
+		return -errno;
+
+	data->sock = sk;
+
+	return 0;
+}
+
+SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth);
+
+SND_CTL_PLUGIN_DEFINE_FUNC(bluetooth)
+{
+	struct bluetooth_data *data;
+	int err;
+
+	DBG("Bluetooth Control plugin");
+
+	data = malloc(sizeof(struct bluetooth_data));
+	if (!data) {
+		err = -ENOMEM;
+		goto error;
+	}
+
+	err = bluetooth_init(data);
+	if (err < 0)
+		goto error;
+
+	data->ext.version = SND_CTL_EXT_VERSION;
+	data->ext.card_idx = -1;
+
+	strncpy(data->ext.id, "bluetooth", sizeof(data->ext.id) - 1);
+	strncpy(data->ext.driver, "Bluetooth-Audio", sizeof(data->ext.driver) - 1);
+	strncpy(data->ext.name, "Bluetooth Audio", sizeof(data->ext.name) - 1);
+	strncpy(data->ext.longname, "Bluetooth Audio", sizeof(data->ext.longname) - 1);
+	strncpy(data->ext.mixername, "Bluetooth Audio", sizeof(data->ext.mixername) - 1);
+
+	data->ext.callback = &bluetooth_callback;
+	data->ext.poll_fd = data->sock;
+	data->ext.private_data = data;
+
+	err = snd_ctl_ext_create(&data->ext, name, mode);
+	if (err < 0)
+		goto error;
+
+	*handlep = data->ext.handle;
+
+	return 0;
+
+error:
+	bluetooth_exit(data);
+
+	return err;
+}
+
+SND_CTL_PLUGIN_SYMBOL(bluetooth);
diff --git a/audio/device.c b/audio/device.c
new file mode 100644
index 0000000..9662dec
--- /dev/null
+++ b/audio/device.c
@@ -0,0 +1,799 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "logging.h"
+#include "textfile.h"
+#include "../src/adapter.h"
+#include "../src/device.h"
+
+#include "error.h"
+#include "ipc.h"
+#include "dbus-common.h"
+#include "device.h"
+#include "unix.h"
+#include "avdtp.h"
+#include "control.h"
+#include "headset.h"
+#include "gateway.h"
+#include "sink.h"
+#include "source.h"
+
+#define AUDIO_INTERFACE "org.bluez.Audio"
+
+#define CONTROL_CONNECT_TIMEOUT 2
+#define AVDTP_CONNECT_TIMEOUT 1
+#define HEADSET_CONNECT_TIMEOUT 1
+
+typedef enum {
+	AUDIO_STATE_DISCONNECTED,
+	AUDIO_STATE_CONNECTING,
+	AUDIO_STATE_CONNECTED,
+} audio_state_t;
+
+struct service_auth {
+	service_auth_cb cb;
+	void *user_data;
+};
+
+struct dev_priv {
+	audio_state_t state;
+
+	headset_state_t hs_state;
+	sink_state_t sink_state;
+	avctp_state_t avctp_state;
+	GSList *auths;
+
+	DBusMessage *conn_req;
+	DBusMessage *dc_req;
+
+	guint control_timer;
+	guint avdtp_timer;
+	guint headset_timer;
+
+	gboolean authorized;
+	guint auth_idle_id;
+};
+
+static unsigned int sink_callback_id = 0;
+static unsigned int avctp_callback_id = 0;
+static unsigned int avdtp_callback_id = 0;
+static unsigned int headset_callback_id = 0;
+
+static void device_free(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (dev->conn)
+		dbus_connection_unref(dev->conn);
+
+	btd_device_unref(dev->btd_dev);
+
+	if (priv) {
+		if (priv->control_timer)
+			g_source_remove(priv->control_timer);
+		if (priv->avdtp_timer)
+			g_source_remove(priv->avdtp_timer);
+		if (priv->headset_timer)
+			g_source_remove(priv->headset_timer);
+		if (priv->dc_req)
+			dbus_message_unref(priv->dc_req);
+		if (priv->conn_req)
+			dbus_message_unref(priv->conn_req);
+		g_free(priv);
+	}
+
+	g_free(dev->path);
+	g_free(dev);
+}
+
+static const char *state2str(audio_state_t state)
+{
+	switch (state) {
+	case AUDIO_STATE_DISCONNECTED:
+		return "disconnected";
+	case AUDIO_STATE_CONNECTING:
+		return "connecting";
+	case AUDIO_STATE_CONNECTED:
+		return "connected";
+	default:
+		error("Invalid audio state %d", state);
+		return NULL;
+	}
+}
+
+static void device_set_state(struct audio_device *dev, audio_state_t new_state)
+{
+	struct dev_priv *priv = dev->priv;
+	const char *state_str;
+	DBusMessage *reply = NULL;
+
+	state_str = state2str(new_state);
+	if (!state_str)
+		return;
+
+	if (new_state == AUDIO_STATE_DISCONNECTED)
+		priv->authorized = FALSE;
+
+	if (dev->priv->state == new_state) {
+		debug("state change attempted from %s to %s",
+							state_str, state_str);
+		return;
+	}
+
+	dev->priv->state = new_state;
+
+	if (priv->dc_req && new_state == AUDIO_STATE_DISCONNECTED) {
+		reply = dbus_message_new_method_return(priv->dc_req);
+		dbus_message_unref(priv->dc_req);
+		priv->dc_req = NULL;
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	if (priv->conn_req && new_state != AUDIO_STATE_CONNECTING) {
+		if (new_state == AUDIO_STATE_CONNECTED)
+			reply = dbus_message_new_method_return(priv->conn_req);
+		else
+			reply = g_dbus_create_error(priv->conn_req,
+							ERROR_INTERFACE
+							".ConnectFailed",
+							"Connecting failed");
+		dbus_message_unref(priv->conn_req);
+		priv->conn_req = NULL;
+		g_dbus_send_message(dev->conn, reply);
+	}
+
+	emit_property_changed(dev->conn, dev->path,
+				AUDIO_INTERFACE, "State",
+				DBUS_TYPE_STRING, &state_str);
+}
+
+static gboolean control_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+
+	dev->priv->control_timer = 0;
+
+	if (dev->control)
+		avrcp_connect(dev);
+
+	return FALSE;
+}
+
+static gboolean device_set_control_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->control)
+		return FALSE;
+
+	if (priv->control_timer)
+		return FALSE;
+
+	priv->control_timer = g_timeout_add_seconds(CONTROL_CONNECT_TIMEOUT,
+							control_connect_timeout,
+							dev);
+
+	return TRUE;
+}
+
+static void device_remove_control_timer(struct audio_device *dev)
+{
+	if (dev->priv->control_timer)
+		g_source_remove(dev->priv->control_timer);
+	dev->priv->control_timer = 0;
+}
+
+static gboolean avdtp_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+
+	dev->priv->avdtp_timer = 0;
+
+	if (dev->sink) {
+		struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!session)
+			return FALSE;
+
+		sink_setup_stream(dev->sink, session);
+		avdtp_unref(session);
+	}
+
+	return FALSE;
+}
+
+static gboolean device_set_avdtp_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->sink)
+		return FALSE;
+
+	if (priv->avdtp_timer)
+		return FALSE;
+
+	priv->avdtp_timer = g_timeout_add_seconds(AVDTP_CONNECT_TIMEOUT,
+							avdtp_connect_timeout,
+							dev);
+
+	return TRUE;
+}
+
+static void device_remove_avdtp_timer(struct audio_device *dev)
+{
+	if (dev->priv->avdtp_timer)
+		g_source_remove(dev->priv->avdtp_timer);
+	dev->priv->avdtp_timer = 0;
+}
+
+static gboolean headset_connect_timeout(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	dev->priv->headset_timer = 0;
+
+	if (dev->headset == NULL)
+		return FALSE;
+
+	if (headset_config_stream(dev, FALSE, NULL, NULL) == 0) {
+		if (priv->state != AUDIO_STATE_CONNECTED &&
+				(priv->sink_state == SINK_STATE_CONNECTED ||
+				priv->sink_state == SINK_STATE_PLAYING))
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+	}
+
+	return FALSE;
+}
+
+static gboolean device_set_headset_timer(struct audio_device *dev)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->headset)
+		return FALSE;
+
+	if (priv->headset_timer)
+		return FALSE;
+
+	priv->headset_timer = g_timeout_add_seconds(HEADSET_CONNECT_TIMEOUT,
+						headset_connect_timeout, dev);
+
+	return TRUE;
+}
+
+static void device_remove_headset_timer(struct audio_device *dev)
+{
+	if (dev->priv->headset_timer)
+		g_source_remove(dev->priv->headset_timer);
+	dev->priv->headset_timer = 0;
+}
+
+static void device_avdtp_cb(struct audio_device *dev, struct avdtp *session,
+				avdtp_session_state_t old_state,
+				avdtp_session_state_t new_state,
+				void *user_data)
+{
+	if (!dev->sink || !dev->control)
+		return;
+
+	if (new_state == AVDTP_SESSION_STATE_CONNECTED) {
+		if (avdtp_stream_setup_active(session))
+			device_set_control_timer(dev);
+		else
+			avrcp_connect(dev);
+	}
+}
+
+static void device_sink_cb(struct audio_device *dev,
+				sink_state_t old_state,
+				sink_state_t new_state,
+				void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->sink)
+		return;
+
+	priv->sink_state = new_state;
+
+	switch (new_state) {
+	case SINK_STATE_DISCONNECTED:
+		if (dev->control) {
+			device_remove_control_timer(dev);
+			avrcp_disconnect(dev);
+		}
+		if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+		else if (old_state == SINK_STATE_CONNECTING) {
+			switch (priv->hs_state) {
+			case HEADSET_STATE_CONNECTED:
+			case HEADSET_STATE_PLAY_IN_PROGRESS:
+			case HEADSET_STATE_PLAYING:
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			default:
+				break;
+			}
+		}
+		break;
+	case SINK_STATE_CONNECTING:
+		device_remove_avdtp_timer(dev);
+		if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTING);
+		break;
+	case SINK_STATE_CONNECTED:
+		if (old_state == SINK_STATE_PLAYING)
+			break;
+#ifdef ANDROID
+		android_set_high_priority(&dev->dst);
+#endif
+		if (dev->auto_connect) {
+			if (!dev->headset)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			if (priv->hs_state == HEADSET_STATE_DISCONNECTED)
+				device_set_headset_timer(dev);
+			else if (priv->hs_state == HEADSET_STATE_CONNECTED)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+		} else if (priv->hs_state != HEADSET_STATE_CONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case SINK_STATE_PLAYING:
+		break;
+	}
+}
+
+static void device_avctp_cb(struct audio_device *dev,
+				avctp_state_t old_state,
+				avctp_state_t new_state,
+				void *user_data)
+{
+	if (!dev->control)
+		return;
+
+	dev->priv->avctp_state = new_state;
+
+	switch (new_state) {
+	case AVCTP_STATE_DISCONNECTED:
+		break;
+	case AVCTP_STATE_CONNECTING:
+		device_remove_control_timer(dev);
+		break;
+	case AVCTP_STATE_CONNECTED:
+		break;
+	}
+}
+
+static void device_headset_cb(struct audio_device *dev,
+				headset_state_t old_state,
+				headset_state_t new_state,
+				void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+
+	if (!dev->headset)
+		return;
+
+	priv->hs_state = new_state;
+
+	switch (new_state) {
+	case HEADSET_STATE_DISCONNECTED:
+		device_remove_avdtp_timer(dev);
+		if (priv->sink_state != SINK_STATE_DISCONNECTED &&
+						dev->sink && priv->dc_req) {
+			sink_shutdown(dev->sink);
+			break;
+		}
+		if (priv->sink_state == SINK_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_DISCONNECTED);
+		else if (old_state == HEADSET_STATE_CONNECT_IN_PROGRESS &&
+				(priv->sink_state == SINK_STATE_CONNECTED ||
+				priv->sink_state == SINK_STATE_PLAYING))
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case HEADSET_STATE_CONNECT_IN_PROGRESS:
+		device_remove_headset_timer(dev);
+		if (priv->sink_state == SINK_STATE_DISCONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTING);
+		break;
+	case HEADSET_STATE_CONNECTED:
+		if (old_state == HEADSET_STATE_CONNECTED ||
+				old_state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+				old_state == HEADSET_STATE_PLAYING)
+			break;
+		if (dev->auto_connect) {
+			if (!dev->sink)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+			else if (priv->sink_state == SINK_STATE_DISCONNECTED)
+				device_set_avdtp_timer(dev);
+			else if (priv->sink_state == SINK_STATE_CONNECTED)
+				device_set_state(dev, AUDIO_STATE_CONNECTED);
+		} else if (priv->sink_state != SINK_STATE_CONNECTED)
+			device_set_state(dev, AUDIO_STATE_CONNECTED);
+		break;
+	case HEADSET_STATE_PLAY_IN_PROGRESS:
+		break;
+	case HEADSET_STATE_PLAYING:
+		break;
+	}
+}
+
+static DBusMessage *dev_connect(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *dev = data;
+	struct dev_priv *priv = dev->priv;
+
+	if (priv->state == AUDIO_STATE_CONNECTING)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress",
+						"Connect in Progress");
+	else if (priv->state == AUDIO_STATE_CONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".AlreadyConnected",
+						"Already Connected");
+
+	dev->auto_connect = TRUE;
+
+	if (dev->headset)
+		headset_config_stream(dev, FALSE, NULL, NULL);
+
+	if (priv->state != AUDIO_STATE_CONNECTING && dev->sink) {
+		struct avdtp *session = avdtp_get(&dev->src, &dev->dst);
+
+		if (!session)
+			return g_dbus_create_error(msg, ERROR_INTERFACE
+					".Failed",
+					"Failed to get AVDTP session");
+
+		sink_setup_stream(dev->sink, session);
+		avdtp_unref(session);
+	}
+
+	/* The previous calls should cause a call to the state callback to
+	 * indicate AUDIO_STATE_CONNECTING */
+	if (priv->state != AUDIO_STATE_CONNECTING)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+				".ConnectFailed",
+				"Headset connect failed");
+
+	priv->conn_req = dbus_message_ref(msg);
+
+	return NULL;
+}
+
+static DBusMessage *dev_disconnect(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *dev = data;
+	struct dev_priv *priv = dev->priv;
+
+	if (priv->state == AUDIO_STATE_DISCONNECTED)
+		return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected",
+						"Not connected");
+
+	if (priv->dc_req)
+		return dbus_message_new_method_return(msg);
+
+	priv->dc_req = dbus_message_ref(msg);
+
+	if (priv->hs_state != HEADSET_STATE_DISCONNECTED)
+		headset_shutdown(dev);
+	else if (dev->sink && priv->sink_state != SINK_STATE_DISCONNECTED)
+		sink_shutdown(dev->sink);
+	else {
+		dbus_message_unref(priv->dc_req);
+		priv->dc_req = NULL;
+		return dbus_message_new_method_return(msg);
+	}
+
+	return NULL;
+}
+
+static DBusMessage *dev_get_properties(DBusConnection *conn, DBusMessage *msg,
+								void *data)
+{
+	struct audio_device *device = data;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	const char *state;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* State */
+	state = state2str(device->priv->state);
+	if (state)
+		dict_append_entry(&dict, "State", DBUS_TYPE_STRING, &state);
+
+	dbus_message_iter_close_container(&iter, &dict);
+
+	return reply;
+}
+
+static GDBusMethodTable dev_methods[] = {
+	{ "Connect",		"",	"",	dev_connect,
+						G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect",		"",	"",	dev_disconnect },
+	{ "GetProperties",	"",	"a{sv}",dev_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable dev_signals[] = {
+	{ "PropertyChanged",		"sv"	},
+	{ NULL, NULL }
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+					struct btd_device *device,
+					const char *path, const bdaddr_t *src,
+					const bdaddr_t *dst)
+{
+	struct audio_device *dev;
+
+	if (!conn || !path)
+		return NULL;
+
+	dev = g_new0(struct audio_device, 1);
+
+	dev->btd_dev = btd_device_ref(device);
+	dev->path = g_strdup(path);
+	bacpy(&dev->dst, dst);
+	bacpy(&dev->src, src);
+	dev->conn = dbus_connection_ref(conn);
+	dev->priv = g_new0(struct dev_priv, 1);
+	dev->priv->state = AUDIO_STATE_DISCONNECTED;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_INTERFACE,
+					dev_methods, dev_signals, NULL,
+					dev, NULL)) {
+		error("Unable to register %s on %s", AUDIO_INTERFACE,
+								dev->path);
+		device_free(dev);
+		return NULL;
+	}
+
+	debug("Registered interface %s on path %s", AUDIO_INTERFACE,
+								dev->path);
+
+	if (sink_callback_id == 0)
+		sink_callback_id = sink_add_state_cb(device_sink_cb, NULL);
+
+	if (avdtp_callback_id == 0)
+		avdtp_callback_id = avdtp_add_state_cb(device_avdtp_cb, NULL);
+	if (avctp_callback_id == 0)
+		avctp_callback_id = avctp_add_state_cb(device_avctp_cb, NULL);
+
+	if (headset_callback_id == 0)
+		headset_callback_id = headset_add_state_cb(device_headset_cb,
+									NULL);
+
+	return dev;
+}
+
+gboolean audio_device_is_active(struct audio_device *dev,
+						const char *interface)
+{
+	if (!interface) {
+		if ((dev->sink || dev->source) &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+			return TRUE;
+
+		if (dev->headset && headset_is_active(dev))
+			return TRUE;
+	}
+	else if (!strcmp(interface, AUDIO_SINK_INTERFACE) && dev->sink &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_SOURCE_INTERFACE) && dev->source &&
+			avdtp_is_connected(&dev->src, &dev->dst))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_HEADSET_INTERFACE) && dev->headset &&
+			headset_is_active(dev))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_CONTROL_INTERFACE) && dev->control &&
+			control_is_active(dev))
+		return TRUE;
+	else if (!strcmp(interface, AUDIO_GATEWAY_INTERFACE) && dev->gateway &&
+			gateway_is_connected(dev))
+		return TRUE;
+
+	return FALSE;
+}
+
+void audio_device_unregister(struct audio_device *device)
+{
+	unix_device_removed(device);
+
+	if (device->headset)
+		headset_unregister(device);
+
+	if (device->sink)
+		sink_unregister(device);
+
+	if (device->source)
+		source_unregister(device);
+
+	if (device->control)
+		control_unregister(device);
+
+	g_dbus_unregister_interface(device->conn, device->path,
+						AUDIO_INTERFACE);
+
+	device_free(device);
+}
+
+static void auth_cb(DBusError *derr, void *user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	if (derr == NULL)
+		priv->authorized = TRUE;
+
+	while (priv->auths) {
+		struct service_auth *auth = priv->auths->data;
+
+		auth->cb(derr, auth->user_data);
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+}
+
+static gboolean auth_idle_cb(gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct dev_priv *priv = dev->priv;
+
+	priv->auth_idle_id = 0;
+
+	auth_cb(NULL, dev);
+
+	return FALSE;
+}
+
+static gboolean audio_device_is_connected(struct audio_device *dev)
+{
+	if (dev->headset) {
+		headset_state_t state = headset_get_state(dev);
+
+		if (state == HEADSET_STATE_CONNECTED ||
+				state == HEADSET_STATE_PLAY_IN_PROGRESS ||
+				state == HEADSET_STATE_PLAYING)
+			return TRUE;
+	}
+
+	if (dev->sink) {
+		sink_state_t state = sink_get_state(dev);
+
+		if (state == SINK_STATE_CONNECTED ||
+				state == SINK_STATE_PLAYING)
+			return TRUE;
+	}
+
+	if (dev->source) {
+		source_state_t state = source_get_state(dev);
+
+		if (state == SOURCE_STATE_CONNECTED ||
+				state == SOURCE_STATE_PLAYING)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+int audio_device_request_authorization(struct audio_device *dev,
+					const char *uuid, service_auth_cb cb,
+					void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+	struct service_auth *auth;
+	int err;
+
+	auth = g_try_new0(struct service_auth, 1);
+	if (!auth)
+		return -ENOMEM;
+
+	auth->cb = cb;
+	auth->user_data = user_data;
+
+	priv->auths = g_slist_append(priv->auths, auth);
+	if (g_slist_length(priv->auths) > 1)
+		return 0;
+
+	if (priv->authorized || audio_device_is_connected(dev)) {
+		priv->auth_idle_id = g_idle_add(auth_idle_cb, dev);
+		return 0;
+	}
+
+	err = btd_request_authorization(&dev->src, &dev->dst, uuid, auth_cb,
+					dev);
+	if (err < 0) {
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+
+	return err;
+}
+
+int audio_device_cancel_authorization(struct audio_device *dev,
+					authorization_cb cb, void *user_data)
+{
+	struct dev_priv *priv = dev->priv;
+	GSList *l, *next;
+
+	for (l = priv->auths; l != NULL; l = next) {
+		struct service_auth *auth = priv->auths->data;
+
+		next = g_slist_next(l);
+
+		if (cb && auth->cb != cb)
+			continue;
+
+		if (user_data && auth->user_data != user_data)
+			continue;
+
+		priv->auths = g_slist_remove(priv->auths, auth);
+		g_free(auth);
+	}
+
+	if (g_slist_length(priv->auths) == 0) {
+		if (priv->auth_idle_id > 0) {
+			g_source_remove(priv->auth_idle_id);
+			priv->auth_idle_id = 0;
+		} else
+			btd_cancel_authorization(&dev->src, &dev->dst);
+	}
+
+	return 0;
+}
diff --git a/audio/device.h b/audio/device.h
new file mode 100644
index 0000000..061c3da
--- /dev/null
+++ b/audio/device.h
@@ -0,0 +1,87 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define GENERIC_AUDIO_UUID	"00001203-0000-1000-8000-00805F9B34FB"
+
+#define HSP_HS_UUID		"00001108-0000-1000-8000-00805F9B34FB"
+#define HSP_AG_UUID		"00001112-0000-1000-8000-00805F9B34FB"
+
+#define HFP_HS_UUID		"0000111E-0000-1000-8000-00805F9B34FB"
+#define HFP_AG_UUID		"0000111F-0000-1000-8000-00805F9B34FB"
+
+#define ADVANCED_AUDIO_UUID	"0000110D-0000-1000-8000-00805F9B34FB"
+
+#define A2DP_SOURCE_UUID	"0000110A-0000-1000-8000-00805F9B34FB"
+#define A2DP_SINK_UUID		"0000110B-0000-1000-8000-00805F9B34FB"
+
+#define AVRCP_REMOTE_UUID	"0000110E-0000-1000-8000-00805F9B34FB"
+#define AVRCP_TARGET_UUID	"0000110C-0000-1000-8000-00805F9B34FB"
+
+/* Move these to respective .h files once they exist */
+#define AUDIO_SOURCE_INTERFACE		"org.bluez.AudioSource"
+#define AUDIO_CONTROL_INTERFACE		"org.bluez.Control"
+
+struct source;
+struct control;
+struct target;
+struct sink;
+struct headset;
+struct gateway;
+struct dev_priv;
+
+struct audio_device {
+	struct btd_device *btd_dev;
+
+	DBusConnection *conn;
+	char *path;
+	bdaddr_t src;
+	bdaddr_t dst;
+
+	gboolean auto_connect;
+
+	struct headset *headset;
+	struct gateway *gateway;
+	struct sink *sink;
+	struct source *source;
+	struct control *control;
+	struct target *target;
+
+	struct dev_priv *priv;
+};
+
+struct audio_device *audio_device_register(DBusConnection *conn,
+					struct btd_device *device,
+					const char *path, const bdaddr_t *src,
+					const bdaddr_t *dst);
+
+void audio_device_unregister(struct audio_device *device);
+
+gboolean audio_device_is_active(struct audio_device *dev,
+						const char *interface);
+
+typedef void (*authorization_cb) (DBusError *derr, void *user_data);
+
+int audio_device_request_authorization(struct audio_device *dev,
+					const char *uuid, authorization_cb cb,
+					void *user_data);
diff --git a/audio/gateway.c b/audio/gateway.c
new file mode 100644
index 0000000..09a401c
--- /dev/null
+++ b/audio/gateway.c
@@ -0,0 +1,1223 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2008-2009  Leonid Movshovich <event.riga@gmail.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/sco.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include "glib-helper.h"
+#include "device.h"
+#include "gateway.h"
+#include "logging.h"
+#include "error.h"
+#include "btio.h"
+#include "dbus-common.h"
+
+#define RFCOMM_BUF_SIZE 256
+/* not-more-then-16 defined by GSM + 1 for NULL + padding */
+#define AG_INDICATOR_DESCR_SIZE 20
+#define AG_CALLER_NUM_SIZE 64	/* size of number + type */
+
+/* commands */
+#define AG_FEATURES "AT+BRSF=26\r"     /* = 0x7F = All features supported */
+#define AG_INDICATORS_SUPP "AT+CIND=?\r"
+#define AG_INDICATORS_VAL "AT+CIND?\r"
+#define AG_INDICATORS_ENABLE "AT+CMER=3,0,0,1\r"
+#define AG_HOLD_MPTY_SUPP "AT+CHLD=?\r"
+#define AG_CALLER_IDENT_ENABLE "AT+CLIP=1\r"
+#define AG_CARRIER_FORMAT "AT+COPS=3,0\r"
+#define AG_EXTENDED_RESULT_CODE "AT+CMEE=1\r"
+
+#define AG_FEATURE_3WAY 0x1
+#define AG_FEATURE_EXTENDED_RES_CODE 0x100
+/* Hold and multipary AG features.
+ * Comments below are copied from hands-free spec for reference */
+/* Releases all held calls or sets User Determined User Busy (UDUB)
+ * for a waiting call */
+#define AG_CHLD_0 0x01
+/* Releases all active calls (if any exist) and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_1 0x02
+/* Releases specified active call only <x> */
+#define AG_CHLD_1x 0x04
+/* Places all active calls (if any exist) on hold and accepts the other
+ * (held or waiting) call */
+#define AG_CHLD_2 0x08
+/* Request private consultation mode with specified call <x> (Place all
+ * calls on hold EXCEPT the call <x>) */
+#define AG_CHLD_2x 0x10
+/* Adds a held call to the conversation */
+#define AG_CHLD_3 0x20
+/* Connects the two calls and disconnects the subscriber from both calls
+ * (Explicit Call Transfer). Support for this value and its associated
+ * functionality is optional for the HF. */
+#define AG_CHLD_4 0x40
+
+#define OK_RESPONSE "\r\nOK\r\n"
+#define ERROR_RESPONSE "\r\nERROR\r\n"
+
+struct indicator {
+	gchar descr[AG_INDICATOR_DESCR_SIZE];
+	gint value;
+};
+
+struct gateway {
+	gateway_state_t state;
+	GIOChannel *rfcomm;
+	guint rfcomm_watch_id;
+	GIOChannel *sco;
+	gateway_stream_cb_t sco_start_cb;
+	void *sco_start_cb_data;
+	DBusMessage *connect_message;
+	guint ag_features;
+	guint hold_multiparty_features;
+	GSList *indies;
+	gboolean is_dialing;
+	gboolean call_active;
+
+	int sp_gain;
+	int mic_gain;
+};
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
+					struct audio_device *device);
+
+int gateway_close(struct audio_device *device);
+
+static void rfcomm_start_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	gw->rfcomm_watch_id = g_io_add_watch(gw->rfcomm,
+			G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+			(GIOFunc) rfcomm_ag_data_cb, dev);
+}
+
+static void rfcomm_stop_watch(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	g_source_remove(gw->rfcomm_watch_id);
+}
+
+static gboolean io_channel_write_all(GIOChannel *io, gchar *data,
+					gsize count)
+{
+	gsize written = 0;
+	GIOStatus status;
+
+	while (count > 0) {
+		status = g_io_channel_write_chars(io, data, count, &written,
+						NULL);
+		if (status != G_IO_STATUS_NORMAL)
+			return FALSE;
+
+		data += written;
+		count -= written;
+	}
+	return TRUE;
+}
+
+/* it's worth to mention that data and response could be the same pointers */
+static gboolean rfcomm_send_and_read(struct gateway *gw, gchar *data,
+                                    gchar *response, gsize count)
+{
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read = 0;
+	gboolean got_ok = FALSE;
+	gboolean got_error = FALSE;
+	gchar *resp_buf = response;
+	gsize toread = RFCOMM_BUF_SIZE - 1;
+	GIOStatus status;
+
+	if (!io_channel_write_all(rfcomm, data, count))
+		return FALSE;
+
+	while (!(got_ok || got_error)) {
+		status = g_io_channel_read_chars(rfcomm, resp_buf, toread,
+						&read, NULL);
+		if (status == G_IO_STATUS_NORMAL)
+			resp_buf[read] = '\0';
+		else {
+			debug("rfcomm_send_and_read(): %m");
+			return FALSE;
+		}
+		got_ok = NULL != strstr(resp_buf, OK_RESPONSE);
+		got_error = NULL != strstr(resp_buf, ERROR_RESPONSE);
+		resp_buf += read;
+		toread -= read;
+	}
+	return TRUE;
+}
+
+/* get <descr> from the names: (<descr>, (<values>)), (<descr>, (<values>))
+ * ... */
+static GSList *parse_indicator_names(gchar *names, GSList *indies)
+{
+	gchar *current = names - 1;
+	GSList *result = indies;
+	gchar *next;
+	struct indicator *ind;
+
+	while (current != NULL) {
+		current += 2;
+		next = strstr(current, ",(");
+		ind = g_slice_new(struct indicator);
+		strncpy(ind->descr, current, 20);
+		ind->descr[(intptr_t) next - (intptr_t) current] = '\0';
+		result = g_slist_append(result, (gpointer) ind);
+		current = strstr(next + 1, ",(");
+	}
+	return result;
+}
+
+/* get values from <val0>,<val1>,... */
+static GSList *parse_indicator_values(gchar *values, GSList *indies)
+{
+	gint val;
+	gchar *current = values - 1;
+	GSList *runner = indies;
+	struct indicator *ind;
+
+	while (current != NULL) {
+		current += 1;
+		sscanf(current, "%d", &val);
+		current = strchr(current, ',');
+		ind = g_slist_nth_data(runner, 0);
+		ind->value = val;
+		runner = g_slist_next(runner);
+	}
+	return indies;
+}
+
+/* get values from <val0>,<val1>,... */
+static guint get_hold_mpty_features(gchar *features)
+{
+	guint result = 0;
+
+	if (strstr(features, "0"))
+		result |= AG_CHLD_0;
+
+	if (strstr(features, "1"))
+		result |= AG_CHLD_1;
+
+	if (strstr(features, "1x"))
+		result |= AG_CHLD_1x;
+
+	if (strstr(features, "2"))
+		result |= AG_CHLD_2;
+
+	if (strstr(features, "2x"))
+		result |= AG_CHLD_2x;
+
+	if (strstr(features, "3"))
+		result |= AG_CHLD_3;
+
+	if (strstr(features, "4"))
+		result |= AG_CHLD_4;
+
+	return result;
+}
+
+static gboolean establish_service_level_conn(struct gateway *gw)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	gboolean res;
+
+	debug("at the begin of establish_service_level_conn()");
+	res = rfcomm_send_and_read(gw, AG_FEATURES, buf,
+				sizeof(AG_FEATURES) - 1);
+	if (!res || sscanf(buf, "\r\n+BRSF:%d", &gw->ag_features) != 1)
+		return FALSE;
+
+	debug("features are 0x%X", gw->ag_features);
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_SUPP, buf,
+				sizeof(AG_INDICATORS_SUPP) - 1);
+	if (!res || !strstr(buf, "+CIND:"))
+		return FALSE;
+
+	gw->indies = parse_indicator_names(strchr(buf, '('), NULL);
+
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_VAL, buf,
+		sizeof(AG_INDICATORS_VAL) - 1);
+	if (!res || !strstr(buf, "+CIND:"))
+		return FALSE;
+
+	gw->indies = parse_indicator_values(strchr(buf, ':') + 1, gw->indies);
+
+	res = rfcomm_send_and_read(gw, AG_INDICATORS_ENABLE, buf,
+				sizeof(AG_INDICATORS_ENABLE) - 1);
+	if (!res || !strstr(buf, "OK"))
+		return FALSE;
+
+	if ((gw->ag_features & AG_FEATURE_3WAY) != 0) {
+		res = rfcomm_send_and_read(gw, AG_HOLD_MPTY_SUPP, buf,
+				sizeof(AG_HOLD_MPTY_SUPP) - 1);
+		if (!res || !strstr(buf, "+CHLD:")) {
+			g_slice_free1(RFCOMM_BUF_SIZE, buf);
+			return FALSE;
+		}
+		gw->hold_multiparty_features = get_hold_mpty_features(
+							strchr(buf, '('));
+
+	} else
+		gw->hold_multiparty_features = 0;
+
+	debug("Service layer connection successfully established!");
+	rfcomm_send_and_read(gw, AG_CALLER_IDENT_ENABLE, buf,
+			sizeof(AG_CALLER_IDENT_ENABLE) - 1);
+	rfcomm_send_and_read(gw, AG_CARRIER_FORMAT, buf,
+			sizeof(AG_CARRIER_FORMAT) - 1);
+	if ((gw->ag_features & AG_FEATURE_EXTENDED_RES_CODE) != 0)
+		rfcomm_send_and_read(gw, AG_EXTENDED_RESULT_CODE, buf,
+			sizeof(AG_EXTENDED_RESULT_CODE) - 1);
+
+	return TRUE;
+}
+
+static void process_ind_change(struct audio_device *dev, guint index,
+							gint value)
+{
+	struct gateway *gw = dev->gateway;
+	struct indicator *ind = g_slist_nth_data(gw->indies, index - 1);
+	gchar *name = ind->descr;
+
+	ind->value = value;
+
+	debug("at the begin of process_ind_change, name is %s", name);
+	if (!strcmp(name, "\"call\"")) {
+		if (value > 0) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallStarted", DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+			gw->call_active = TRUE;
+		} else {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallEnded", DBUS_TYPE_INVALID);
+			gw->call_active = FALSE;
+		}
+
+	} else if (!strcmp(name, "\"callsetup\"")) {
+		if (value == 0 && gw->is_dialing) {
+			g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallTerminated",
+					DBUS_TYPE_INVALID);
+			gw->is_dialing = FALSE;
+		} else if (!gw->is_dialing && value > 0)
+			gw->is_dialing = TRUE;
+
+	} else if (!strcmp(name, "\"callheld\"")) {
+		/* FIXME: The following code is based on assumptions only.
+		 * Has to be tested for interoperability
+		 * I assume that callheld=2 would be sent when dial from HF
+		 * failed in case of 3-way call
+		 * Unfortunately this path is not covered by the HF spec so
+		 * the code has to be tested for interop
+		*/
+		/* '2' means: all calls held, no active calls */
+		if (value == 2) {
+			if (gw->is_dialing) {
+				g_dbus_emit_signal(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"CallTerminated",
+					DBUS_TYPE_INVALID);
+				gw->is_dialing = FALSE;
+			}
+		}
+	} else if (!strcmp(name, "\"service\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "RegistrationStatus",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"signal\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "SignalStrength",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"roam\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "RoamingStatus",
+				DBUS_TYPE_UINT16, &value);
+	else if (!strcmp(name, "\"battchg\""))
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE, "BatteryCharge",
+				DBUS_TYPE_UINT16, &value);
+}
+
+static void process_ring(struct audio_device *device, GIOChannel *chan,
+			gchar *buf)
+{
+	gchar number[AG_CALLER_NUM_SIZE];
+	gchar *cli;
+	gchar *sep;
+	gsize read;
+	GIOStatus status;
+
+	rfcomm_stop_watch(device);
+	status = g_io_channel_read_chars(chan, buf, RFCOMM_BUF_SIZE - 1, &read, NULL);
+	if (status != G_IO_STATUS_NORMAL)
+		return;
+
+	debug("at the begin of process_ring");
+	if (strlen(buf) > AG_CALLER_NUM_SIZE + 10)
+		error("process_ring(): buf is too long '%s'", buf);
+	else if ((cli = strstr(buf, "\r\n+CLIP"))) {
+		if (sscanf(cli, "\r\n+CLIP: \"%s", number) == 1) {
+			sep = strchr(number, '"');
+			sep[0] = '\0';
+
+			/* FIXME:signal will be emitted on each RING+CLIP.
+			 * That's bad */
+			cli = number;
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE, "Ring",
+					DBUS_TYPE_STRING, &cli,
+					DBUS_TYPE_INVALID);
+			device->gateway->is_dialing = TRUE;
+		} else
+			error("process_ring(): '%s' in place of +CLIP after RING", buf);
+
+	}
+
+	rfcomm_start_watch(device);
+}
+
+static gboolean rfcomm_ag_data_cb(GIOChannel *chan, GIOCondition cond,
+					struct audio_device *device)
+{
+	gchar buf[RFCOMM_BUF_SIZE];
+	struct gateway *gw;
+	gsize read;
+	/* some space for value */
+	gchar indicator[AG_INDICATOR_DESCR_SIZE + 4];
+	gint value;
+	guint index;
+	gchar *sep;
+
+	debug("at the begin of rfcomm_ag_data_cb()");
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	gw = device->gateway;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		debug("connection with remote BT is closed");
+		gateway_close(device);
+		return FALSE;
+	}
+
+	if (g_io_channel_read_chars(chan, buf, sizeof(buf) - 1, &read, NULL)
+			!= G_IO_STATUS_NORMAL)
+		return TRUE;
+	buf[read] = '\0';
+
+	if (strlen(buf) > AG_INDICATOR_DESCR_SIZE + 14)
+		error("rfcomm_ag_data_cb(): buf is too long '%s'", buf);
+	else if (sscanf(buf, "\r\n+CIEV:%s\r\n", indicator) == 1) {
+		sep = strchr(indicator, ',');
+		sep[0] = '\0';
+		sep += 1;
+		index = atoi(indicator);
+		value = atoi(sep);
+		process_ind_change(device, index, value);
+	} else if (strstr(buf, "RING"))
+		process_ring(device, chan, buf);
+	else if (sscanf(buf, "\r\n+BVRA:%d\r\n", &value) == 1) {
+		if (value == 0)
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"VoiceRecognitionActive",
+					DBUS_TYPE_INVALID);
+		else
+			g_dbus_emit_signal(device->conn, device->path,
+					AUDIO_GATEWAY_INTERFACE,
+					"VoiceRecognitionInactive",
+					DBUS_TYPE_INVALID);
+	} else if (sscanf(buf, "\r\n+VGS:%d\r\n", &value) == 1) {
+		gw->sp_gain = value;
+		emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE, "SpeakerGain",
+				DBUS_TYPE_UINT16, &value);
+	} else if (sscanf(buf, "\r\n+VGM:%d\r\n", &value) == 1) {
+		gw->mic_gain = value;
+		emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE, "MicrophoneGain",
+				DBUS_TYPE_UINT16, &value);
+	} else
+		error("rfcomm_ag_data_cb(): read wrong data '%s'", buf);
+
+	return TRUE;
+}
+
+static gboolean sco_io_cb(GIOChannel *chan, GIOCondition cond,
+			struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (cond & G_IO_NVAL)
+		return FALSE;
+
+	if (cond & (G_IO_ERR | G_IO_HUP)) {
+		debug("sco connection is released");
+		g_io_channel_shutdown(gw->sco, TRUE, NULL);
+		g_io_channel_unref(gw->sco);
+		gw->sco = NULL;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void sco_connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+	struct audio_device *dev = (struct audio_device *) user_data;
+	struct gateway *gw = dev->gateway;
+
+	debug("at the begin of sco_connect_cb() in gateway.c");
+
+	if (err) {
+		error("sco_connect_cb(): %s", err->message);
+		/* not sure, but from other point of view,
+		 * what is the reason to have headset which
+		 * cannot play audio? */
+		if (gw->sco_start_cb)
+			gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+		gateway_close(dev);
+		return;
+	}
+
+	gw->sco = g_io_channel_ref(chan);
+	if (gw->sco_start_cb)
+		gw->sco_start_cb(dev, gw->sco_start_cb_data);
+
+	/* why is this here? */
+	fcntl(g_io_channel_unix_get_fd(chan), F_SETFL, 0);
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+				(GIOFunc) sco_io_cb, dev);
+}
+
+static void rfcomm_connect_cb(GIOChannel *chan, GError *err,
+				gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	struct gateway *gw = dev->gateway;
+	DBusMessage *conn_mes = gw->connect_message;
+	gchar gw_addr[18];
+	GIOFlags flags;
+
+	if (err) {
+		error("connect(): %s", err->message);
+		if (gw->sco_start_cb)
+			gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+		return;
+	}
+
+	ba2str(&dev->dst, gw_addr);
+	/* Blocking mode should be default, but just in case: */
+	flags = g_io_channel_get_flags(chan);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	flags &= G_IO_FLAG_MASK;
+	g_io_channel_set_flags(chan, flags, NULL);
+	g_io_channel_set_encoding(chan, NULL, NULL);
+	g_io_channel_set_buffered(chan, FALSE);
+	if (!gw->rfcomm)
+		gw->rfcomm = g_io_channel_ref(chan);
+
+	if (establish_service_level_conn(dev->gateway)) {
+		gboolean value = TRUE;
+
+		debug("%s: Connected to %s", dev->path, gw_addr);
+		rfcomm_start_watch(dev);
+		if (conn_mes) {
+			DBusMessage *reply =
+				dbus_message_new_method_return(conn_mes);
+			dbus_connection_send(dev->conn, reply, NULL);
+			dbus_message_unref(reply);
+			dbus_message_unref(conn_mes);
+			gw->connect_message = NULL;
+		}
+
+		gw->state = GATEWAY_STATE_CONNECTED;
+		emit_property_changed(dev->conn, dev->path,
+				AUDIO_GATEWAY_INTERFACE,
+				"Connected", DBUS_TYPE_BOOLEAN,	&value);
+		return;
+	} else
+		error("%s: Failed to establish service layer connection to %s",
+			dev->path, gw_addr);
+
+	if (NULL != gw->sco_start_cb)
+		gw->sco_start_cb(NULL, gw->sco_start_cb_data);
+
+	gateway_close(dev);
+}
+
+static void get_record_cb(sdp_list_t *recs, int perr, gpointer user_data)
+{
+	struct audio_device *dev = user_data;
+	DBusMessage *msg = dev->gateway->connect_message;
+	int ch = -1;
+	sdp_list_t *protos, *classes;
+	uuid_t uuid;
+	gateway_stream_cb_t sco_cb;
+	GIOChannel *io;
+	GError *err = NULL;
+
+	if (perr < 0) {
+		error("Unable to get service record: %s (%d)", strerror(-perr),
+					-perr);
+		goto fail;
+	}
+
+	if (!recs || !recs->data) {
+		error("No records found");
+		goto fail;
+	}
+
+	if (sdp_get_service_classes(recs->data, &classes) < 0) {
+		error("Unable to get service classes from record");
+		goto fail;
+	}
+
+	if (sdp_get_access_protos(recs->data, &protos) < 0) {
+		error("Unable to get access protocols from record");
+		goto fail;
+	}
+
+	memcpy(&uuid, classes->data, sizeof(uuid));
+	sdp_list_free(classes, free);
+
+	if (!sdp_uuid128_to_uuid(&uuid) || uuid.type != SDP_UUID16 ||
+			uuid.value.uuid16 != HANDSFREE_AGW_SVCLASS_ID) {
+		sdp_list_free(protos, NULL);
+		error("Invalid service record or not HFP");
+		goto fail;
+	}
+
+	ch = sdp_get_proto_port(protos, RFCOMM_UUID);
+	sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
+	sdp_list_free(protos, NULL);
+	if (ch <= 0) {
+		error("Unable to extract RFCOMM channel from service record");
+		goto fail;
+	}
+
+	io = bt_io_connect(BT_IO_RFCOMM, rfcomm_connect_cb, dev, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_CHANNEL, ch,
+				BT_IO_OPT_INVALID);
+	if (!io) {
+		error("Unable to connect: %s", err->message);
+		if (msg) {
+			error_common_reply(dev->conn, msg, ERROR_INTERFACE
+						".ConnectionAttemptFailed",
+						err->message);
+			msg = NULL;
+		}
+		g_error_free(err);
+		gateway_close(dev);
+	}
+
+	g_io_channel_unref(io);
+	return;
+
+fail:
+	if (msg)
+		error_common_reply(dev->conn, msg, ERROR_INTERFACE
+					".NotSupported", "Not supported");
+
+	dev->gateway->connect_message = NULL;
+
+	sco_cb = dev->gateway->sco_start_cb;
+	if (sco_cb)
+		sco_cb(NULL, dev->gateway->sco_start_cb_data);
+}
+
+static int get_records(struct audio_device *device)
+{
+	uuid_t uuid;
+
+	sdp_uuid16_create(&uuid, HANDSFREE_AGW_SVCLASS_ID);
+	return bt_search_service(&device->src, &device->dst, &uuid,
+				get_record_cb, device, NULL);
+}
+
+static DBusMessage *ag_connect(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *au_dev = (struct audio_device *) data;
+	struct gateway *gw = au_dev->gateway;
+
+	debug("at the begin of ag_connect()");
+	if (gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".AlreadyConnected",
+					"Already Connected");
+
+	gw->connect_message = dbus_message_ref(msg);
+	if (get_records(au_dev) < 0) {
+		dbus_message_unref(gw->connect_message);
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".ConnectAttemptFailed",
+					"Connect Attempt Failed");
+	}
+	debug("at the end of ag_connect()");
+	return NULL;
+}
+
+static DBusMessage *ag_disconnect(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply = NULL;
+	char gw_addr[18];
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+						".NotConnected",
+						"Device not Connected");
+
+	gateway_close(device);
+	ba2str(&device->dst, gw_addr);
+	debug("Disconnected from %s, %s", gw_addr, device->path);
+
+	return reply;
+}
+
+static DBusMessage *process_ag_reponse(DBusMessage *msg, gchar *response)
+{
+	DBusMessage *reply;
+
+
+	debug("in process_ag_reponse, response is %s", response);
+	if (strstr(response, OK_RESPONSE))
+		reply = dbus_message_new_method_return(msg);
+	else {
+		/* FIXME: some code should be here to processes errors
+		 *  in better fasion */
+		debug("AG responded with '%s' to %s method call", response,
+				dbus_message_get_member(msg));
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE
+					".OperationFailed",
+					"Operation failed.See log for details");
+	}
+	return reply;
+}
+
+static DBusMessage *process_simple(DBusMessage *msg, struct audio_device *dev,
+					gchar *data)
+{
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+
+	rfcomm_stop_watch(dev);
+	rfcomm_send_and_read(gw, data, buf, strlen(data));
+	rfcomm_start_watch(dev);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_ANSWER "ATA\r"
+
+static DBusMessage *ag_answer(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *dev = data;
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	if (gw->call_active)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".CallAlreadyAnswered",
+					"Call AlreadyAnswered");
+
+	return process_simple(msg, dev, AG_ANSWER);
+}
+
+#define AG_HANGUP "AT+CHUP\r"
+
+static DBusMessage *ag_terminate_call(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *dev = data;
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	return process_simple(msg, dev, AG_HANGUP);
+}
+
+/* according to GSM spec */
+#define ALLOWED_NUMBER_SYMBOLS "1234567890*#ABCD"
+#define AG_PLACE_CALL "ATD%s;\r"
+/* dialing from memory is not supported as headset spec doesn't define a way
+ * to retreive phone memory entries.
+ */
+static DBusMessage *ag_call(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint atd_len;
+	DBusMessage *result;
+
+	debug("at the begin of ag_call()");
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS))
+		return dbus_message_new_error(msg,
+			ERROR_INTERFACE ".BadNumber",
+			"Number contains characters which are not allowed");
+
+	atd_len = sprintf(buf, AG_PLACE_CALL, number);
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, buf, buf, atd_len);
+	rfcomm_start_watch(device);
+
+	result = process_ag_reponse(msg, buf);
+	return result;
+}
+
+#define AG_GET_CARRIER "AT+COPS?\r"
+
+static DBusMessage *ag_get_operator(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *dev = (struct audio_device *) data;
+	struct gateway *gw = dev->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	GIOChannel *rfcomm = gw->rfcomm;
+	gsize read;
+	gchar *result, *sep;
+	DBusMessage *reply;
+	GIOStatus status;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	rfcomm_stop_watch(dev);
+	io_channel_write_all(rfcomm, AG_GET_CARRIER, strlen(AG_GET_CARRIER));
+
+	status = g_io_channel_read_chars(rfcomm, buf, RFCOMM_BUF_SIZE - 1,
+						&read, NULL);
+	rfcomm_start_watch(dev);
+	if (G_IO_STATUS_NORMAL == status) {
+		buf[read] = '\0';
+		if (strstr(buf, "+COPS")) {
+			if (!strrchr(buf, ','))
+				result = "0";
+			else {
+				result = strchr(buf, '\"') + 1;
+				sep = strchr(result, '\"');
+				sep[0] = '\0';
+			}
+
+			reply = dbus_message_new_method_return(msg);
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						&result, DBUS_TYPE_INVALID);
+		} else {
+			info("ag_get_operator(): '+COPS' expected but"
+				" '%s' received", buf);
+			reply = dbus_message_new_error(msg, ERROR_INTERFACE
+						".Failed",
+						"Unexpected response from AG");
+		}
+	} else {
+		error("ag_get_operator(): %m");
+		reply = dbus_message_new_error(msg, ERROR_INTERFACE
+					".ConnectionFailed",
+					"Failed to receive response from AG");
+	}
+
+	return reply;
+}
+
+#define AG_SEND_DTMF "AT+VTS=%c\r"
+static DBusMessage *ag_send_dtmf(DBusConnection *conn, DBusMessage *msg,
+				void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number;
+	gint com_len;
+	gboolean got_ok = TRUE;
+	gint num_len;
+	gint i = 0;
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &number,
+				DBUS_TYPE_INVALID);
+	if (strlen(number) != strspn(number, ALLOWED_NUMBER_SYMBOLS))
+		return dbus_message_new_error(msg,
+			ERROR_INTERFACE ".BadNumber",
+			"Number contains characters which are not allowed");
+
+	num_len = strlen(number);
+	rfcomm_stop_watch(device);
+	while (i < num_len && got_ok) {
+		com_len = sprintf(buf, AG_SEND_DTMF, number[i]);
+		rfcomm_send_and_read(gw, buf, buf, com_len);
+		got_ok = NULL != strstr(buf, OK_RESPONSE);
+		i += 1;
+	}
+	rfcomm_start_watch(device);
+	return process_ag_reponse(msg, buf);
+}
+
+#define AG_GET_SUBSCRIBER_NUMS "AT+CNUM\r"
+#define CNUM_LEN 5             /* length of "+CNUM" string */
+#define MAX_NUMBER_CNT 16
+static DBusMessage *ag_get_subscriber_num(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	gchar buf[RFCOMM_BUF_SIZE];
+	gchar *number, *end;
+	DBusMessage *reply = dbus_message_new_method_return(msg);
+
+	if (!gw->rfcomm)
+		return g_dbus_create_error(msg, ERROR_INTERFACE
+					".NotConnected",
+					"Not Connected");
+
+	rfcomm_stop_watch(device);
+	rfcomm_send_and_read(gw, AG_GET_SUBSCRIBER_NUMS, buf,
+			strlen(AG_GET_SUBSCRIBER_NUMS));
+	rfcomm_start_watch(device);
+
+	if (strlen(buf) > AG_CALLER_NUM_SIZE + 21)
+		error("ag_get_subscriber_num(): buf is too long '%s'", buf);
+	else if (strstr(buf, "+CNUM")) {
+		number = strchr(buf, ',');
+		number++;
+		end = strchr(number, ',');
+		if (end) {
+			*end = '\0';
+			dbus_message_append_args(reply, DBUS_TYPE_STRING,
+						&number, DBUS_TYPE_INVALID);
+		}
+	} else
+		error("ag_get_subscriber_num(): read wrong data '%s'", buf);
+
+	return reply;
+}
+
+static DBusMessage *ag_get_properties(DBusConnection *conn, DBusMessage *msg,
+					void *data)
+{
+	struct audio_device *device = data;
+	struct gateway *gw = device->gateway;
+	DBusMessage *reply;
+	DBusMessageIter iter;
+	DBusMessageIter dict;
+	gboolean value;
+	guint index = 0;
+	struct indicator *ind;
+
+	reply = dbus_message_new_method_return(msg);
+	if (!reply)
+		return NULL;
+
+	dbus_message_iter_init_append(reply, &iter);
+
+	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+	/* Connected */
+	value = gateway_is_connected(device);
+	dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+
+	if (!value)
+		goto done;
+
+	while ((ind = g_slist_nth_data(gw->indies, index))) {
+		if(!strcmp(ind->descr, "\"service\""))
+			dict_append_entry(&dict, "RegistrationStatus",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"signal\""))
+			dict_append_entry(&dict, "SignalStrength",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"roam\""))
+			dict_append_entry(&dict, "RoamingStatus",
+					DBUS_TYPE_UINT16, &ind->value);
+		else if (!strcmp(ind->descr, "\"battchg\""))
+			dict_append_entry(&dict, "BatteryCharge",
+					DBUS_TYPE_UINT16, &ind->value);
+		index++;
+	}
+
+	/* SpeakerGain */
+	dict_append_entry(&dict, "SpeakerGain", DBUS_TYPE_UINT16,
+				&device->gateway->sp_gain);
+
+	/* MicrophoneGain */
+	dict_append_entry(&dict, "MicrophoneGain", DBUS_TYPE_UINT16,
+				&device->gateway->mic_gain);
+done:
+	dbus_message_iter_close_container(&iter, &dict);
+	return reply;
+}
+
+static GDBusMethodTable gateway_methods[] = {
+	{ "Connect", "", "", ag_connect, G_DBUS_METHOD_FLAG_ASYNC },
+	{ "Disconnect", "", "", ag_disconnect },
+	{ "AnswerCall", "", "", ag_answer },
+	{ "TerminateCall", "", "", ag_terminate_call },
+	{ "Call", "s", "", ag_call },
+	{ "GetOperatorName", "", "s", ag_get_operator },
+	{ "SendDTMF", "s", "", ag_send_dtmf },
+	{ "GetSubscriberNumber", "", "s", ag_get_subscriber_num },
+	{ "GetProperties", "", "a{sv}", ag_get_properties },
+	{ NULL, NULL, NULL, NULL }
+};
+
+static GDBusSignalTable gateway_signals[] = {
+	{ "Ring", "s" },
+	{ "CallTerminated", "" },
+	{ "CallStarted", "" },
+	{ "CallEnded", "" },
+	{ "PropertyChanged", "sv" },
+	{ NULL, NULL }
+};
+
+struct gateway *gateway_init(struct audio_device *dev)
+{
+	struct gateway *gw;
+
+	if (!g_dbus_register_interface(dev->conn, dev->path,
+					AUDIO_GATEWAY_INTERFACE,
+					gateway_methods, gateway_signals,
+					NULL, dev, NULL))
+		return NULL;
+
+	debug("in gateway_init, dev is %p", dev);
+	gw = g_new0(struct gateway, 1);
+	gw->indies = NULL;
+	gw->is_dialing = FALSE;
+	gw->call_active = FALSE;
+	gw->state = GATEWAY_STATE_DISCONNECTED;
+	return gw;
+
+}
+
+gboolean gateway_is_connected(struct audio_device *dev)
+{
+	return (dev && dev->gateway &&
+			dev->gateway->state == GATEWAY_STATE_CONNECTED);
+}
+
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *io)
+{
+	if (!io)
+		return -EINVAL;
+
+	g_io_channel_ref(io);
+	dev->gateway->rfcomm = io;
+
+	return 0;
+}
+
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *io)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (gw->sco)
+		return -EISCONN;
+
+	gw->sco = g_io_channel_ref(io);
+
+	g_io_add_watch(gw->sco, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+                                (GIOFunc) sco_io_cb, dev);
+	return 0;
+}
+
+void gateway_start_service(struct audio_device *device)
+{
+	rfcomm_connect_cb(device->gateway->rfcomm, NULL, device);
+}
+
+static void indicator_slice_free(gpointer mem)
+{
+	g_slice_free(struct indicator, mem);
+}
+
+int gateway_close(struct audio_device *device)
+{
+	struct gateway *gw = device->gateway;
+	GIOChannel *rfcomm = gw->rfcomm;
+	GIOChannel *sco = gw->sco;
+	gboolean value = FALSE;
+
+	g_slist_foreach(gw->indies, (GFunc) indicator_slice_free, NULL);
+	g_slist_free(gw->indies);
+	if (rfcomm) {
+		g_io_channel_shutdown(rfcomm, TRUE, NULL);
+		g_io_channel_unref(rfcomm);
+		gw->rfcomm = NULL;
+	}
+
+	if (sco) {
+		g_io_channel_shutdown(sco, TRUE, NULL);
+		g_io_channel_unref(sco);
+		gw->sco = NULL;
+		gw->sco_start_cb = NULL;
+		gw->sco_start_cb_data = NULL;
+	}
+
+	gw->state = GATEWAY_STATE_DISCONNECTED;
+
+	emit_property_changed(device->conn, device->path,
+				AUDIO_GATEWAY_INTERFACE,
+				"Connected", DBUS_TYPE_BOOLEAN, &value);
+	return 0;
+}
+
+/* These are functions to be called from unix.c for audio system
+ * ifaces (alsa, gstreamer, etc.) */
+gboolean gateway_request_stream(struct audio_device *dev,
+				gateway_stream_cb_t cb, void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+	GError *err = NULL;
+	GIOChannel *io;
+
+	if (!gw->rfcomm) {
+		gw->sco_start_cb = cb;
+		gw->sco_start_cb_data = user_data;
+		get_records(dev);
+	} else if (!gw->sco) {
+		gw->sco_start_cb = cb;
+		gw->sco_start_cb_data = user_data;
+		io = bt_io_connect(BT_IO_SCO, sco_connect_cb, dev, NULL, &err,
+				BT_IO_OPT_SOURCE_BDADDR, &dev->src,
+				BT_IO_OPT_DEST_BDADDR, &dev->dst,
+				BT_IO_OPT_INVALID);
+		if (!io) {
+			error("%s", err->message);
+			g_error_free(err);
+			return FALSE;
+		}
+	} else {
+		if (cb)
+			cb(dev, user_data);
+	}
+
+	return TRUE;
+}
+
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t sco_cb,
+				void *user_data)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw->rfcomm) {
+		gw->sco_start_cb = sco_cb;
+		gw->sco_start_cb_data = user_data;
+		return get_records(dev);
+	}
+
+	if (sco_cb)
+		sco_cb(dev, user_data);
+
+	return 0;
+}
+
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id)
+{
+	gateway_close(dev);
+	return TRUE;
+}
+
+int gateway_get_sco_fd(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw || !gw->sco)
+		return -1;
+
+	return g_io_channel_unix_get_fd(gw->sco);
+}
+
+void gateway_suspend_stream(struct audio_device *dev)
+{
+	struct gateway *gw = dev->gateway;
+
+	if (!gw || !gw->sco)
+		return;
+
+	g_io_channel_shutdown(gw->sco, TRUE, NULL);
+	g_io_channel_unref(gw->sco);
+	gw->sco = NULL;
+	gw->sco_start_cb = NULL;
+	gw->sco_start_cb_data = NULL;
+}
+
diff --git a/audio/gateway.h b/audio/gateway.h
new file mode 100644
index 0000000..e539469
--- /dev/null
+++ b/audio/gateway.h
@@ -0,0 +1,47 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2006-2007  Nokia Corporation
+ *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#define AUDIO_GATEWAY_INTERFACE "org.bluez.HeadsetGateway"
+
+#define DEFAULT_HSP_HS_CHANNEL 6
+#define DEFAULT_HFP_HS_CHANNEL 7
+
+typedef enum {
+	GATEWAY_STATE_DISCONNECTED,
+	GATEWAY_STATE_CONNECTED
+} gateway_state_t;
+
+typedef void (*gateway_stream_cb_t) (struct audio_device *dev, void *user_data);
+struct gateway *gateway_init(struct audio_device *device);
+gboolean gateway_is_connected(struct audio_device *dev);
+int gateway_connect_rfcomm(struct audio_device *dev, GIOChannel *chan);
+int gateway_connect_sco(struct audio_device *dev, GIOChannel *chan);
+void gateway_start_service(struct audio_device *device);
+gboolean gateway_request_stream(struct audio_device *dev,
+			gateway_stream_cb_t cb, void *user_data);
+int gateway_config_stream(struct audio_device *dev, gateway_stream_cb_t cb,
+			void *user_data);
+gboolean gateway_cancel_stream(struct audio_device *dev, unsigned int id);
+int gateway_get_sco_fd(struct audio_device *dev);
+void gateway_suspend_stream(struct audio_device *dev);
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c
new file mode 100644
index 0000000..0753f38
--- /dev/null
+++ b/audio/gsta2dpsink.c
@@ -0,0 +1,703 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <pthread.h>
+
+#include "gsta2dpsink.h"
+
+GType gst_avdtp_sink_get_type(void);
+
+GST_DEBUG_CATEGORY_STATIC(gst_a2dp_sink_debug);
+#define GST_CAT_DEFAULT gst_a2dp_sink_debug
+
+#define A2DP_SBC_RTP_PAYLOAD_TYPE 1
+#define TEMPLATE_MAX_BITPOOL_STR "64"
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+enum {
+	PROP_0,
+	PROP_DEVICE,
+	PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBin, GST_TYPE_BIN);
+
+static const GstElementDetails gst_a2dp_sink_details =
+	GST_ELEMENT_DETAILS("Bluetooth A2DP sink",
+				"Sink/Audio",
+				"Plays audio to an A2DP device",
+				"Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate gst_a2dp_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+			GST_STATIC_CAPS("audio/x-sbc, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+				"blocks = (int) { 4, 8, 12, 16 }, "
+				"subbands = (int) { 4, 8 }, "
+				"allocation = (string) { \"snr\", \"loudness\" }, "
+				"bitpool = (int) [ 2, "
+				TEMPLATE_MAX_BITPOOL_STR " ]; "
+				"audio/mpeg"
+				));
+
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event);
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps);
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad);
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self);
+static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self);
+static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self);
+
+static void gst_a2dp_sink_finalize(GObject *obj)
+{
+	GstA2dpSink *self = GST_A2DP_SINK(obj);
+
+	g_mutex_free(self->cb_mutex);
+
+	G_OBJECT_CLASS(parent_class)->finalize(obj);
+}
+
+static GstState gst_a2dp_sink_get_state(GstA2dpSink *self)
+{
+	GstState current, pending;
+
+	gst_element_get_state(GST_ELEMENT(self), &current, &pending, 0);
+	if (pending == GST_STATE_VOID_PENDING)
+		return current;
+
+	return pending;
+}
+
+/*
+ * Helper function to create elements, add to the bin and link it
+ * to another element.
+ */
+static GstElement *gst_a2dp_sink_init_element(GstA2dpSink *self,
+			const gchar *elementname, const gchar *name,
+			GstElement *link_to)
+{
+	GstElement *element;
+	GstState state;
+
+	GST_LOG_OBJECT(self, "Initializing %s", elementname);
+
+	element = gst_element_factory_make(elementname, name);
+	if (element == NULL) {
+		GST_DEBUG_OBJECT(self, "Couldn't create %s", elementname);
+		return NULL;
+	}
+
+	if (!gst_bin_add(GST_BIN(self), element)) {
+		GST_DEBUG_OBJECT(self, "failed to add %s to the bin",
+						elementname);
+		goto cleanup_and_fail;
+	}
+
+	state = gst_a2dp_sink_get_state(self);
+	if (gst_element_set_state(element, state) ==
+			GST_STATE_CHANGE_FAILURE) {
+		GST_DEBUG_OBJECT(self, "%s failed to go to playing",
+						elementname);
+		goto remove_element_and_fail;
+	}
+
+	if (link_to != NULL)
+		if (!gst_element_link(link_to, element)) {
+			GST_DEBUG_OBJECT(self, "couldn't link %s",
+					elementname);
+			goto remove_element_and_fail;
+		}
+
+	return element;
+
+remove_element_and_fail:
+	gst_element_set_state(element, GST_STATE_NULL);
+	gst_bin_remove(GST_BIN(self), element);
+	return NULL;
+
+cleanup_and_fail:
+	if (element != NULL)
+		g_object_unref(G_OBJECT(element));
+
+	return NULL;
+}
+
+static void gst_a2dp_sink_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_set_details(element_class,
+		&gst_a2dp_sink_details);
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&gst_a2dp_sink_factory));
+}
+
+static void gst_a2dp_sink_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstA2dpSink *self = GST_A2DP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (self->sink != NULL)
+			gst_avdtp_sink_set_device(self->sink,
+				g_value_get_string(value));
+
+		if (self->device != NULL)
+			g_free(self->device);
+		self->device = g_value_dup_string(value);
+		break;
+
+	case PROP_AUTOCONNECT:
+		self->autoconnect = g_value_get_boolean(value);
+
+		if (self->sink != NULL)
+			g_object_set(G_OBJECT(self->sink), "auto-connect",
+					self->autoconnect, NULL);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_a2dp_sink_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstA2dpSink *self = GST_A2DP_SINK(object);
+	gchar *device;
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (self->sink != NULL) {
+			device = gst_avdtp_sink_get_device(self->sink);
+			if (device != NULL)
+				g_value_take_string(value, device);
+		}
+		break;
+	case PROP_AUTOCONNECT:
+		if (self->sink != NULL)
+			g_object_get(G_OBJECT(self->sink), "auto-connect",
+				&self->autoconnect, NULL);
+
+		g_value_set_boolean(value, self->autoconnect);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static gboolean gst_a2dp_sink_init_ghost_pad(GstA2dpSink *self)
+{
+	GstPad *capsfilter_pad;
+
+	/* we search for the capsfilter sinkpad */
+	capsfilter_pad = gst_element_get_static_pad(self->capsfilter, "sink");
+
+	/* now we add a ghostpad */
+	self->ghostpad = GST_GHOST_PAD(gst_ghost_pad_new("sink",
+		capsfilter_pad));
+	g_object_unref(capsfilter_pad);
+
+	/* the getcaps of our ghostpad must reflect the device caps */
+	gst_pad_set_getcaps_function(GST_PAD(self->ghostpad),
+				gst_a2dp_sink_get_caps);
+	self->ghostpad_setcapsfunc = GST_PAD_SETCAPSFUNC(self->ghostpad);
+	gst_pad_set_setcaps_function(GST_PAD(self->ghostpad),
+			GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps));
+
+	/* we need to handle events on our own and we also need the eventfunc
+	 * of the ghostpad for forwarding calls */
+	self->ghostpad_eventfunc = GST_PAD_EVENTFUNC(GST_PAD(self->ghostpad));
+	gst_pad_set_event_function(GST_PAD(self->ghostpad),
+			gst_a2dp_sink_handle_event);
+
+	if (!gst_element_add_pad(GST_ELEMENT(self), GST_PAD(self->ghostpad)))
+		GST_ERROR_OBJECT(self, "failed to add ghostpad");
+
+	return TRUE;
+}
+
+static void gst_a2dp_sink_remove_dynamic_elements(GstA2dpSink *self)
+{
+	if (self->rtp) {
+		GST_LOG_OBJECT(self, "removing rtp element from the bin");
+		if (!gst_bin_remove(GST_BIN(self), GST_ELEMENT(self->rtp)))
+			GST_WARNING_OBJECT(self, "failed to remove rtp "
+					"element from bin");
+		else
+			self->rtp = NULL;
+	}
+}
+
+static GstStateChangeReturn gst_a2dp_sink_change_state(GstElement *element,
+			GstStateChange transition)
+{
+	GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
+	GstA2dpSink *self = GST_A2DP_SINK(element);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_READY_TO_PAUSED:
+		self->taglist = gst_tag_list_new();
+
+		gst_a2dp_sink_init_fakesink(self);
+		break;
+
+	case GST_STATE_CHANGE_NULL_TO_READY:
+		self->sink_is_in_bin = FALSE;
+		self->sink = GST_AVDTP_SINK(gst_element_factory_make(
+				"avdtpsink", "avdtpsink"));
+		if (self->sink == NULL) {
+			GST_WARNING_OBJECT(self, "failed to create avdtpsink");
+			return GST_STATE_CHANGE_FAILURE;
+		}
+
+		if (self->device != NULL)
+			gst_avdtp_sink_set_device(self->sink,
+					self->device);
+
+		g_object_set(G_OBJECT(self->sink), "auto-connect",
+					self->autoconnect, NULL);
+
+		ret = gst_element_set_state(GST_ELEMENT(self->sink),
+			GST_STATE_READY);
+		break;
+	default:
+		break;
+	}
+
+	if (ret == GST_STATE_CHANGE_FAILURE)
+		return ret;
+
+	ret = GST_ELEMENT_CLASS(parent_class)->change_state(element,
+								transition);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_PAUSED_TO_READY:
+		if (self->taglist) {
+			gst_tag_list_free(self->taglist);
+			self->taglist = NULL;
+		}
+		if (self->newseg_event != NULL) {
+			gst_event_unref(self->newseg_event);
+			self->newseg_event = NULL;
+		}
+		gst_a2dp_sink_remove_fakesink(self);
+		break;
+
+	case GST_STATE_CHANGE_READY_TO_NULL:
+		if (self->sink_is_in_bin) {
+			if (!gst_bin_remove(GST_BIN(self),
+						GST_ELEMENT(self->sink)))
+				GST_WARNING_OBJECT(self, "Failed to remove "
+						"avdtpsink from bin");
+		} else if (self->sink != NULL) {
+			gst_element_set_state(GST_ELEMENT(self->sink),
+					GST_STATE_NULL);
+			g_object_unref(G_OBJECT(self->sink));
+		}
+
+		self->sink = NULL;
+
+		gst_a2dp_sink_remove_dynamic_elements(self);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	object_class->set_property = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_set_property);
+	object_class->get_property = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_get_property);
+
+	object_class->finalize = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_finalize);
+
+	element_class->change_state = GST_DEBUG_FUNCPTR(
+					gst_a2dp_sink_change_state);
+
+	g_object_class_install_property(object_class, PROP_DEVICE,
+			g_param_spec_string("device", "Device",
+			"Bluetooth remote device address",
+			NULL, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+			g_param_spec_boolean("auto-connect", "Auto-connect",
+			"Automatically attempt to connect to device",
+			DEFAULT_AUTOCONNECT, G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(gst_a2dp_sink_debug, "a2dpsink", 0,
+				"A2DP sink element");
+}
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self)
+{
+	return gst_avdtp_sink_get_device_caps(self->sink);
+}
+
+static GstCaps *gst_a2dp_sink_get_caps(GstPad *pad)
+{
+	GstCaps *caps;
+	GstCaps *caps_aux;
+	GstA2dpSink *self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+
+	if (self->sink == NULL) {
+		GST_DEBUG_OBJECT(self, "a2dpsink isn't initialized "
+			"returning template caps");
+		caps = gst_static_pad_template_get_caps(
+				&gst_a2dp_sink_factory);
+	} else {
+		GST_LOG_OBJECT(self, "Getting device caps");
+		caps = gst_a2dp_sink_get_device_caps(self);
+		if (caps == NULL)
+			caps = gst_static_pad_template_get_caps(
+					&gst_a2dp_sink_factory);
+	}
+	caps_aux = gst_caps_copy(caps);
+	g_object_set(self->capsfilter, "caps", caps_aux, NULL);
+	gst_caps_unref(caps_aux);
+	return caps;
+}
+
+static gboolean gst_a2dp_sink_init_avdtp_sink(GstA2dpSink *self)
+{
+	GstElement *sink;
+
+	/* check if we don't need a new sink */
+	if (self->sink_is_in_bin)
+		return TRUE;
+
+	if (self->sink == NULL)
+		sink = gst_element_factory_make("avdtpsink", "avdtpsink");
+	else
+		sink = GST_ELEMENT(self->sink);
+
+	if (sink == NULL) {
+		GST_ERROR_OBJECT(self, "Couldn't create avdtpsink");
+		return FALSE;
+	}
+
+	if (!gst_bin_add(GST_BIN(self), sink)) {
+		GST_ERROR_OBJECT(self, "failed to add avdtpsink "
+			"to the bin");
+		goto cleanup_and_fail;
+	}
+
+	if (gst_element_set_state(sink, GST_STATE_READY) ==
+			GST_STATE_CHANGE_FAILURE) {
+		GST_ERROR_OBJECT(self, "avdtpsink failed to go to ready");
+		goto remove_element_and_fail;
+	}
+
+	if (!gst_element_link(GST_ELEMENT(self->rtp), sink)) {
+		GST_ERROR_OBJECT(self, "couldn't link rtpsbcpay "
+			"to avdtpsink");
+		goto remove_element_and_fail;
+	}
+
+	self->sink = GST_AVDTP_SINK(sink);
+	self->sink_is_in_bin = TRUE;
+	g_object_set(G_OBJECT(self->sink), "device", self->device, NULL);
+
+	gst_element_set_state(sink, GST_STATE_PAUSED);
+
+	return TRUE;
+
+remove_element_and_fail:
+	gst_element_set_state(sink, GST_STATE_NULL);
+	gst_bin_remove(GST_BIN(self), sink);
+	return FALSE;
+
+cleanup_and_fail:
+	if (sink != NULL)
+		g_object_unref(G_OBJECT(sink));
+
+	return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_sbc_element(GstA2dpSink *self)
+{
+	GstElement *rtppay;
+
+	/* if we already have a rtp, we don't need a new one */
+	if (self->rtp != NULL)
+		return TRUE;
+
+	rtppay = gst_a2dp_sink_init_element(self, "rtpsbcpay", "rtp",
+						self->capsfilter);
+	if (rtppay == NULL)
+		return FALSE;
+
+	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+	g_object_set(G_OBJECT(self->rtp), "min-frames", -1, NULL);
+
+	gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_rtp_mpeg_element(GstA2dpSink *self)
+{
+	GstElement *rtppay;
+
+	/* check if we don't need a new rtp */
+	if (self->rtp)
+		return TRUE;
+
+	GST_LOG_OBJECT(self, "Initializing rtp mpeg element");
+	/* if capsfilter is not created then we can't have our rtp element */
+	if (self->capsfilter == NULL)
+		return FALSE;
+
+	rtppay = gst_a2dp_sink_init_element(self, "rtpmpapay", "rtp",
+					self->capsfilter);
+	if (rtppay == NULL)
+		return FALSE;
+
+	self->rtp = GST_BASE_RTP_PAYLOAD(rtppay);
+
+	gst_element_set_state(rtppay, GST_STATE_PAUSED);
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_init_dynamic_elements(GstA2dpSink *self,
+						GstCaps *caps)
+{
+	GstStructure *structure;
+	GstEvent *event;
+	GstPad *capsfilterpad;
+	gboolean crc;
+	gchar *mode = NULL;
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	/* before everything we need to remove fakesink */
+	gst_a2dp_sink_remove_fakesink(self);
+
+	/* first, we need to create our rtp payloader */
+	if (gst_structure_has_name(structure, "audio/x-sbc")) {
+		GST_LOG_OBJECT(self, "sbc media received");
+		if (!gst_a2dp_sink_init_rtp_sbc_element(self))
+			return FALSE;
+	} else if (gst_structure_has_name(structure, "audio/mpeg")) {
+		GST_LOG_OBJECT(self, "mp3 media received");
+		if (!gst_a2dp_sink_init_rtp_mpeg_element(self))
+			return FALSE;
+	} else {
+		GST_ERROR_OBJECT(self, "Unexpected media type");
+		return FALSE;
+	}
+
+	if (!gst_a2dp_sink_init_avdtp_sink(self))
+		return FALSE;
+
+	/* check if we should push the taglist FIXME should we push this?
+	 * we can send the tags directly if needed */
+	if (self->taglist != NULL &&
+			gst_structure_has_name(structure, "audio/mpeg")) {
+
+		event = gst_event_new_tag(self->taglist);
+
+		/* send directly the crc */
+		if (gst_tag_list_get_boolean(self->taglist, "has-crc", &crc))
+			gst_avdtp_sink_set_crc(self->sink, crc);
+
+		if (gst_tag_list_get_string(self->taglist, "channel-mode",
+				&mode))
+			gst_avdtp_sink_set_channel_mode(self->sink, mode);
+
+		capsfilterpad = gst_ghost_pad_get_target(self->ghostpad);
+		gst_pad_send_event(capsfilterpad, event);
+		self->taglist = NULL;
+		g_free(mode);
+	}
+
+	if (!gst_avdtp_sink_set_device_caps(self->sink, caps))
+		return FALSE;
+
+	g_object_set(G_OBJECT(self->rtp), "mtu",
+		gst_avdtp_sink_get_link_mtu(self->sink), NULL);
+
+	/* we forward our new segment here if we have one */
+	if (self->newseg_event) {
+		gst_pad_send_event(GST_BASE_RTP_PAYLOAD_SINKPAD(self->rtp),
+					self->newseg_event);
+		self->newseg_event = NULL;
+	}
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_set_caps(GstPad *pad, GstCaps *caps)
+{
+	GstA2dpSink *self;
+
+	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+	GST_INFO_OBJECT(self, "setting caps");
+
+	/* now we know the caps */
+	gst_a2dp_sink_init_dynamic_elements(self, caps);
+
+	return self->ghostpad_setcapsfunc(GST_PAD(self->ghostpad), caps);
+}
+
+/* used for catching newsegment events while we don't have a sink, for
+ * later forwarding it to the sink */
+static gboolean gst_a2dp_sink_handle_event(GstPad *pad, GstEvent *event)
+{
+	GstA2dpSink *self;
+	GstTagList *taglist = NULL;
+	GstObject *parent;
+
+	self = GST_A2DP_SINK(GST_PAD_PARENT(pad));
+	parent = gst_element_get_parent(GST_ELEMENT(self->sink));
+
+	if (GST_EVENT_TYPE(event) == GST_EVENT_NEWSEGMENT &&
+			parent != GST_OBJECT_CAST(self)) {
+		if (self->newseg_event != NULL)
+			gst_event_unref(self->newseg_event);
+		self->newseg_event = gst_event_ref(event);
+
+	} else if (GST_EVENT_TYPE(event) == GST_EVENT_TAG &&
+			parent != GST_OBJECT_CAST(self)) {
+		if (self->taglist == NULL)
+			gst_event_parse_tag(event, &self->taglist);
+		else {
+			gst_event_parse_tag(event, &taglist);
+			gst_tag_list_insert(self->taglist, taglist,
+					GST_TAG_MERGE_REPLACE);
+		}
+	}
+
+	if (parent != NULL)
+		gst_object_unref(GST_OBJECT(parent));
+
+	return self->ghostpad_eventfunc(GST_PAD(self->ghostpad), event);
+}
+
+static gboolean gst_a2dp_sink_init_caps_filter(GstA2dpSink *self)
+{
+	GstElement *element;
+
+	element = gst_element_factory_make("capsfilter", "filter");
+	if (element == NULL)
+		goto failed;
+
+	if (!gst_bin_add(GST_BIN(self), element))
+		goto failed;
+
+	self->capsfilter = element;
+	return TRUE;
+
+failed:
+	GST_ERROR_OBJECT(self, "Failed to initialize caps filter");
+	return FALSE;
+}
+
+static gboolean gst_a2dp_sink_init_fakesink(GstA2dpSink *self)
+{
+	if (self->fakesink != NULL)
+		return TRUE;
+
+	g_mutex_lock(self->cb_mutex);
+	self->fakesink = gst_a2dp_sink_init_element(self, "fakesink",
+			"fakesink", self->capsfilter);
+	g_mutex_unlock(self->cb_mutex);
+
+	if (!self->fakesink)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean gst_a2dp_sink_remove_fakesink(GstA2dpSink *self)
+{
+	g_mutex_lock(self->cb_mutex);
+
+	if (self->fakesink != NULL) {
+		gst_element_set_locked_state(self->fakesink, TRUE);
+		gst_element_set_state(self->fakesink, GST_STATE_NULL);
+
+		gst_bin_remove(GST_BIN(self), self->fakesink);
+		self->fakesink = NULL;
+	}
+
+	g_mutex_unlock(self->cb_mutex);
+
+	return TRUE;
+}
+
+static void gst_a2dp_sink_init(GstA2dpSink *self,
+			GstA2dpSinkClass *klass)
+{
+	self->sink = NULL;
+	self->fakesink = NULL;
+	self->rtp = NULL;
+	self->device = NULL;
+	self->autoconnect = DEFAULT_AUTOCONNECT;
+	self->capsfilter = NULL;
+	self->newseg_event = NULL;
+	self->taglist = NULL;
+	self->ghostpad = NULL;
+	self->sink_is_in_bin = FALSE;
+
+	self->cb_mutex = g_mutex_new();
+
+	/* we initialize our capsfilter */
+	gst_a2dp_sink_init_caps_filter(self);
+	g_object_set(self->capsfilter, "caps",
+		gst_static_pad_template_get_caps(&gst_a2dp_sink_factory),
+		NULL);
+
+	gst_a2dp_sink_init_fakesink(self);
+
+	gst_a2dp_sink_init_ghost_pad(self);
+
+}
+
+gboolean gst_a2dp_sink_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "a2dpsink",
+			GST_RANK_MARGINAL, GST_TYPE_A2DP_SINK);
+}
+
diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h
new file mode 100644
index 0000000..902b678
--- /dev/null
+++ b/audio/gsta2dpsink.h
@@ -0,0 +1,84 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifndef __GST_A2DP_SINK_H__
+#define __GST_A2DP_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include "gstavdtpsink.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_A2DP_SINK \
+	(gst_a2dp_sink_get_type())
+#define GST_A2DP_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_A2DP_SINK,GstA2dpSink))
+#define GST_A2DP_SINK_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_A2DP_SINK,GstA2dpSinkClass))
+#define GST_IS_A2DP_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_A2DP_SINK))
+#define GST_IS_A2DP_SINK_CLASS(obj) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SINK))
+
+typedef struct _GstA2dpSink GstA2dpSink;
+typedef struct _GstA2dpSinkClass GstA2dpSinkClass;
+
+struct _GstA2dpSink {
+	GstBin bin;
+
+	GstBaseRTPPayload *rtp;
+	GstAvdtpSink *sink;
+	GstElement *capsfilter;
+	GstElement *fakesink;
+
+	gchar *device;
+	gboolean autoconnect;
+	gboolean sink_is_in_bin;
+
+	GstGhostPad *ghostpad;
+	GstPadSetCapsFunction ghostpad_setcapsfunc;
+	GstPadEventFunction ghostpad_eventfunc;
+
+	GstEvent *newseg_event;
+	/* Store the tags received before the a2dpsender sink is created
+	 * when it is created we forward this to it */
+	GstTagList *taglist;
+
+	GMutex *cb_mutex;
+};
+
+struct _GstA2dpSinkClass {
+	GstBinClass parent_class;
+};
+
+//GType gst_a2dp_sink_get_type(void);
+
+gboolean gst_a2dp_sink_plugin_init (GstPlugin * plugin);
+
+GstCaps *gst_a2dp_sink_get_device_caps(GstA2dpSink *self);
+
+G_END_DECLS
+
+#endif
+
diff --git a/audio/gstavdtpsink.c b/audio/gstavdtpsink.c
new file mode 100644
index 0000000..bc25bd1
--- /dev/null
+++ b/audio/gstavdtpsink.c
@@ -0,0 +1,1434 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <unistd.h>
+#include <sys/un.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <netinet/in.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include <gst/rtp/gstrtpbuffer.h>
+
+#include "ipc.h"
+#include "rtp.h"
+
+#include "gstavdtpsink.h"
+
+GST_DEBUG_CATEGORY_STATIC(avdtp_sink_debug);
+#define GST_CAT_DEFAULT avdtp_sink_debug
+
+#define BUFFER_SIZE 2048
+#define TEMPLATE_MAX_BITPOOL 64
+#define CRC_PROTECTED 1
+#define CRC_UNPROTECTED 0
+
+#define DEFAULT_AUTOCONNECT TRUE
+
+#define GST_AVDTP_SINK_MUTEX_LOCK(s) G_STMT_START {	\
+		g_mutex_lock(s->sink_lock);		\
+	} G_STMT_END
+
+#define GST_AVDTP_SINK_MUTEX_UNLOCK(s) G_STMT_START {	\
+		g_mutex_unlock(s->sink_lock);		\
+	} G_STMT_END
+
+
+struct bluetooth_data {
+	struct bt_get_capabilities_rsp *caps; /* Bluetooth device caps */
+	guint link_mtu;
+
+	gchar buffer[BUFFER_SIZE];	/* Codec transfer buffer */
+};
+
+#define IS_SBC(n) (strcmp((n), "audio/x-sbc") == 0)
+#define IS_MPEG_AUDIO(n) (strcmp((n), "audio/mpeg") == 0)
+
+enum {
+	PROP_0,
+	PROP_DEVICE,
+	PROP_AUTOCONNECT
+};
+
+GST_BOILERPLATE(GstAvdtpSink, gst_avdtp_sink, GstBaseSink,
+			GST_TYPE_BASE_SINK);
+
+static const GstElementDetails avdtp_sink_details =
+	GST_ELEMENT_DETAILS("Bluetooth AVDTP sink",
+				"Sink/Audio",
+				"Plays audio to an A2DP device",
+				"Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate avdtp_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("application/x-rtp, "
+				"media = (string) \"audio\","
+				"payload = (int) "
+					GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+				"clock-rate = (int) { 16000, 32000, "
+					"44100, 48000 }, "
+				"encoding-name = (string) \"SBC\"; "
+				"application/x-rtp, "
+				"media = (string) \"audio\", "
+				"payload = (int) "
+				GST_RTP_PAYLOAD_MPA_STRING ", "
+				"clock-rate = (int) 90000; "
+				"application/x-rtp, "
+				"media = (string) \"audio\", "
+				"payload = (int) "
+				GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+				"clock-rate = (int) 90000, "
+				"encoding-name = (string) \"MPA\""
+				));
+
+static GIOError gst_avdtp_sink_audioservice_send(GstAvdtpSink *self,
+					const bt_audio_msg_header_t *msg);
+static GIOError gst_avdtp_sink_audioservice_expect(
+				GstAvdtpSink *self,
+				bt_audio_msg_header_t *outmsg,
+				guint8 expected_name);
+
+
+static void gst_avdtp_sink_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&avdtp_sink_factory));
+
+	gst_element_class_set_details(element_class, &avdtp_sink_details);
+}
+
+static gboolean gst_avdtp_sink_stop(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	GST_INFO_OBJECT(self, "stop");
+
+	if (self->watch_id != 0) {
+		g_source_remove(self->watch_id);
+		self->watch_id = 0;
+	}
+
+	if (self->server) {
+		bt_audio_service_close(g_io_channel_unix_get_fd(self->server));
+		g_io_channel_unref(self->server);
+		self->server = NULL;
+	}
+
+	if (self->stream) {
+		g_io_channel_shutdown(self->stream, TRUE, NULL);
+		g_io_channel_unref(self->stream);
+		self->stream = NULL;
+	}
+
+	if (self->data) {
+		g_free(self->data);
+		self->data = NULL;
+	}
+
+	if (self->stream_caps) {
+		gst_caps_unref(self->stream_caps);
+		self->stream_caps = NULL;
+	}
+
+	if (self->dev_caps) {
+		gst_caps_unref(self->dev_caps);
+		self->dev_caps = NULL;
+	}
+
+	return TRUE;
+}
+
+static void gst_avdtp_sink_finalize(GObject *object)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(object);
+
+	if (self->data)
+		gst_avdtp_sink_stop(GST_BASE_SINK(self));
+
+	if (self->device)
+		g_free(self->device);
+
+	g_mutex_free(self->sink_lock);
+
+	G_OBJECT_CLASS(parent_class)->finalize(object);
+}
+
+static void gst_avdtp_sink_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		if (sink->device)
+			g_free(sink->device);
+		sink->device = g_value_dup_string(value);
+		break;
+
+	case PROP_AUTOCONNECT:
+		sink->autoconnect = g_value_get_boolean(value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_avdtp_sink_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(object);
+
+	switch (prop_id) {
+	case PROP_DEVICE:
+		g_value_set_string(value, sink->device);
+		break;
+
+	case PROP_AUTOCONNECT:
+		g_value_set_boolean(value, sink->autoconnect);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static gint gst_avdtp_sink_bluetooth_recvmsg_fd(GstAvdtpSink *sink)
+{
+	int err, ret;
+
+	ret = bt_audio_service_get_data_fd(
+			g_io_channel_unix_get_fd(sink->server));
+
+	if (ret < 0) {
+		err = errno;
+		GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)",
+				strerror(err), err);
+		return -err;
+	}
+
+	sink->stream = g_io_channel_unix_new(ret);
+	GST_DEBUG_OBJECT(sink, "stream_fd=%d", ret);
+
+	return 0;
+}
+
+static codec_capabilities_t *gst_avdtp_find_caps(GstAvdtpSink *sink,
+						uint8_t codec_type)
+{
+	struct bt_get_capabilities_rsp *rsp = sink->data->caps;
+	codec_capabilities_t *codec = (void *) rsp->data;
+	int bytes_left = rsp->h.length - sizeof(*rsp);
+
+	while (bytes_left > 0) {
+		if ((codec->type == codec_type) &&
+				!(codec->lock & BT_WRITE_LOCK))
+			break;
+
+		bytes_left -= codec->length;
+		codec = (void *) codec + codec->length;
+	}
+
+	if (bytes_left <= 0)
+		return NULL;
+
+	return codec;
+}
+
+static gboolean gst_avdtp_sink_init_sbc_pkt_conf(GstAvdtpSink *sink,
+					GstCaps *caps,
+					sbc_capabilities_t *pkt)
+{
+	sbc_capabilities_t *cfg;
+	const GValue *value = NULL;
+	const char *pref, *name;
+	gint rate, subbands, blocks;
+	GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+	cfg = (void *) gst_avdtp_find_caps(sink, BT_A2DP_SBC_SINK);
+	name = gst_structure_get_name(structure);
+
+	if (!(IS_SBC(name))) {
+		GST_ERROR_OBJECT(sink, "Unexpected format %s, "
+				"was expecting sbc", name);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "rate");
+	rate = g_value_get_int(value);
+	if (rate == 44100)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_44100;
+	else if (rate == 48000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_48000;
+	else if (rate == 32000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_32000;
+	else if (rate == 16000)
+		cfg->frequency = BT_SBC_SAMPLING_FREQ_16000;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid rate while setting caps");
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "mode");
+	pref = g_value_get_string(value);
+	if (strcmp(pref, "mono") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_MONO;
+	else if (strcmp(pref, "dual") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (strcmp(pref, "stereo") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO;
+	else if (strcmp(pref, "joint") == 0)
+		cfg->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid mode %s", pref);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "allocation");
+	pref = g_value_get_string(value);
+	if (strcmp(pref, "loudness") == 0)
+		cfg->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS;
+	else if (strcmp(pref, "snr") == 0)
+		cfg->allocation_method = BT_A2DP_ALLOCATION_SNR;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "subbands");
+	subbands = g_value_get_int(value);
+	if (subbands == 8)
+		cfg->subbands = BT_A2DP_SUBBANDS_8;
+	else if (subbands == 4)
+		cfg->subbands = BT_A2DP_SUBBANDS_4;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid subbands %d", subbands);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "blocks");
+	blocks = g_value_get_int(value);
+	if (blocks == 16)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_16;
+	else if (blocks == 12)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_12;
+	else if (blocks == 8)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_8;
+	else if (blocks == 4)
+		cfg->block_length = BT_A2DP_BLOCK_LENGTH_4;
+	else {
+		GST_ERROR_OBJECT(sink, "Invalid blocks %d", blocks);
+		return FALSE;
+	}
+
+	value = gst_structure_get_value(structure, "bitpool");
+	cfg->max_bitpool = cfg->min_bitpool = g_value_get_int(value);
+
+	memcpy(pkt, cfg, sizeof(*pkt));
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_conf_recv_stream_fd(
+					GstAvdtpSink *self)
+{
+	struct bluetooth_data *data = self->data;
+	gint ret;
+	GIOError err;
+	GError *gerr = NULL;
+	GIOStatus status;
+	GIOFlags flags;
+	gsize read;
+
+	ret = gst_avdtp_sink_bluetooth_recvmsg_fd(self);
+	if (ret < 0)
+		return FALSE;
+
+	if (!self->stream) {
+		GST_ERROR_OBJECT(self, "Error while configuring device: "
+				"could not acquire audio socket");
+		return FALSE;
+	}
+
+	/* set stream socket to nonblock */
+	GST_LOG_OBJECT(self, "setting stream socket to nonblock");
+	flags = g_io_channel_get_flags(self->stream);
+	flags |= G_IO_FLAG_NONBLOCK;
+	status = g_io_channel_set_flags(self->stream, flags, &gerr);
+	if (status != G_IO_STATUS_NORMAL) {
+		if (gerr)
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server socket to nonblock: "
+				"%s", gerr->message);
+		else
+			GST_WARNING_OBJECT(self, "Error while "
+					"setting server "
+					"socket to nonblock");
+	}
+
+	/* It is possible there is some outstanding
+	data in the pipe - we have to empty it */
+	GST_LOG_OBJECT(self, "emptying stream pipe");
+	while (1) {
+		err = g_io_channel_read(self->stream, data->buffer,
+					(gsize) data->link_mtu,
+					&read);
+		if (err != G_IO_ERROR_NONE || read <= 0)
+			break;
+	}
+
+	/* set stream socket to block */
+	GST_LOG_OBJECT(self, "setting stream socket to block");
+	flags = g_io_channel_get_flags(self->stream);
+	flags &= ~G_IO_FLAG_NONBLOCK;
+	status = g_io_channel_set_flags(self->stream, flags, &gerr);
+	if (status != G_IO_STATUS_NORMAL) {
+		if (gerr)
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server socket to block:"
+				"%s", gerr->message);
+		else
+			GST_WARNING_OBJECT(self, "Error while "
+				"setting server "
+				"socket to block");
+	}
+
+	memset(data->buffer, 0, sizeof(data->buffer));
+
+	return TRUE;
+}
+
+static gboolean server_callback(GIOChannel *chan,
+					GIOCondition cond, gpointer data)
+{
+	if (cond & G_IO_HUP || cond & G_IO_NVAL)
+		return FALSE;
+	else if (cond & G_IO_ERR)
+		GST_WARNING_OBJECT(GST_AVDTP_SINK(data),
+					"Untreated callback G_IO_ERR");
+
+	return TRUE;
+}
+
+static GstStructure *gst_avdtp_sink_parse_sbc_caps(
+			GstAvdtpSink *self, sbc_capabilities_t *sbc)
+{
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean mono, stereo;
+
+	structure = gst_structure_empty_new("audio/x-sbc");
+	value = g_value_init(g_new0(GValue, 1), G_TYPE_STRING);
+
+	/* mode */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) {
+		g_value_set_static_string(value, "mono");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) {
+		g_value_set_static_string(value, "stereo");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) {
+		g_value_set_static_string(value, "dual");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) {
+		g_value_set_static_string(value, "joint");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "mode", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* subbands */
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	value = g_value_init(value, G_TYPE_INT);
+	if (sbc->subbands & BT_A2DP_SUBBANDS_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->subbands & BT_A2DP_SUBBANDS_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "subbands", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* blocks */
+	value = g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_16) {
+		g_value_set_int(value, 16);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_12) {
+		g_value_set_int(value, 12);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_8) {
+		g_value_set_int(value, 8);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->block_length & BT_A2DP_BLOCK_LENGTH_4) {
+		g_value_set_int(value, 4);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "blocks", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* allocation */
+	g_value_init(value, G_TYPE_STRING);
+	list = g_value_init(g_new0(GValue,1), GST_TYPE_LIST);
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) {
+		g_value_set_static_string(value, "loudness");
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->allocation_method & BT_A2DP_ALLOCATION_SNR) {
+		g_value_set_static_string(value, "snr");
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "allocation", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* rate */
+	g_value_init(value, G_TYPE_INT);
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (sbc->frequency & BT_SBC_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* bitpool */
+	value = g_value_init(value, GST_TYPE_INT_RANGE);
+	gst_value_set_int_range(value,
+			MIN(sbc->min_bitpool, TEMPLATE_MAX_BITPOOL),
+			MIN(sbc->max_bitpool, TEMPLATE_MAX_BITPOOL));
+	gst_structure_set_value(structure, "bitpool", value);
+	g_value_unset(value);
+
+	/* channels */
+	mono = FALSE;
+	stereo = FALSE;
+	if (sbc->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((sbc->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(sbc->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static GstStructure *gst_avdtp_sink_parse_mpeg_caps(
+			GstAvdtpSink *self, mpeg_capabilities_t *mpeg)
+{
+	GstStructure *structure;
+	GValue *value;
+	GValue *list;
+	gboolean valid_layer = FALSE;
+	gboolean mono, stereo;
+
+	if (!mpeg)
+		return NULL;
+
+	GST_LOG_OBJECT(self, "parsing mpeg caps");
+
+	structure = gst_structure_empty_new("audio/mpeg");
+	value = g_new0(GValue, 1);
+	g_value_init(value, G_TYPE_INT);
+
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	g_value_set_int(value, 1);
+	gst_value_list_prepend_value(list, value);
+	g_value_set_int(value, 2);
+	gst_value_list_prepend_value(list, value);
+	gst_structure_set_value(structure, "mpegversion", list);
+	g_free(list);
+
+	/* layer */
+	GST_LOG_OBJECT(self, "setting mpeg layer");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->layer & BT_MPEG_LAYER_1) {
+		g_value_set_int(value, 1);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_2) {
+		g_value_set_int(value, 2);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (mpeg->layer & BT_MPEG_LAYER_3) {
+		g_value_set_int(value, 3);
+		gst_value_list_prepend_value(list, value);
+		valid_layer = TRUE;
+	}
+	if (list) {
+		gst_structure_set_value(structure, "layer", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	if (!valid_layer) {
+		gst_structure_free(structure);
+		g_free(value);
+		return NULL;
+	}
+
+	/* rate */
+	GST_LOG_OBJECT(self, "setting mpeg rate");
+	list = g_value_init(g_new0(GValue, 1), GST_TYPE_LIST);
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_48000) {
+		g_value_set_int(value, 48000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_44100) {
+		g_value_set_int(value, 44100);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_32000) {
+		g_value_set_int(value, 32000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_24000) {
+		g_value_set_int(value, 24000);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_22050) {
+		g_value_set_int(value, 22050);
+		gst_value_list_prepend_value(list, value);
+	}
+	if (mpeg->frequency & BT_MPEG_SAMPLING_FREQ_16000) {
+		g_value_set_int(value, 16000);
+		gst_value_list_prepend_value(list, value);
+	}
+	g_value_unset(value);
+	if (list) {
+		gst_structure_set_value(structure, "rate", list);
+		g_free(list);
+		list = NULL;
+	}
+
+	/* channels */
+	GST_LOG_OBJECT(self, "setting mpeg channels");
+	mono = FALSE;
+	stereo = FALSE;
+	if (mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_MONO)
+		mono = TRUE;
+	if ((mpeg->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) ||
+			(mpeg->channel_mode &
+			BT_A2DP_CHANNEL_MODE_JOINT_STEREO))
+		stereo = TRUE;
+
+	if (mono && stereo) {
+		g_value_init(value, GST_TYPE_INT_RANGE);
+		gst_value_set_int_range(value, 1, 2);
+	} else {
+		g_value_init(value, G_TYPE_INT);
+		if (mono)
+			g_value_set_int(value, 1);
+		else if (stereo)
+			g_value_set_int(value, 2);
+		else {
+			GST_ERROR_OBJECT(self,
+				"Unexpected number of channels");
+			g_value_set_int(value, 0);
+		}
+	}
+	gst_structure_set_value(structure, "channels", value);
+	g_free(value);
+
+	return structure;
+}
+
+static gboolean gst_avdtp_sink_update_caps(GstAvdtpSink *self)
+{
+	sbc_capabilities_t *sbc;
+	mpeg_capabilities_t *mpeg;
+	GstStructure *sbc_structure;
+	GstStructure *mpeg_structure;
+	gchar *tmp;
+
+	GST_LOG_OBJECT(self, "updating device caps");
+
+	sbc = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+	mpeg = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+	sbc_structure = gst_avdtp_sink_parse_sbc_caps(self, sbc);
+	mpeg_structure = gst_avdtp_sink_parse_mpeg_caps(self, mpeg);
+
+	if (self->dev_caps != NULL)
+		gst_caps_unref(self->dev_caps);
+	self->dev_caps = gst_caps_new_full(sbc_structure, NULL);
+	if (mpeg_structure != NULL)
+		gst_caps_append_structure(self->dev_caps, mpeg_structure);
+
+	tmp = gst_caps_to_string(self->dev_caps);
+	GST_DEBUG_OBJECT(self, "Device capabilities: %s", tmp);
+	g_free(tmp);
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_get_capabilities(GstAvdtpSink *self)
+{
+	gchar *buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_get_capabilities_req *req = (void *) buf;
+	struct bt_get_capabilities_rsp *rsp = (void *) buf;
+	GIOError io_error;
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_GET_CAPABILITIES;
+	req->h.length = sizeof(*req);
+
+	if (self->device == NULL)
+		return FALSE;
+	strncpy(req->destination, self->device, 18);
+	if (self->autoconnect)
+		req->flags |= BT_FLAG_AUTOCONNECT;
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while asking device caps");
+		return FALSE;
+	}
+
+	rsp->h.length = 0;
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_GET_CAPABILITIES);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while getting device caps");
+		return FALSE;
+	}
+
+	self->data->caps = g_malloc0(rsp->h.length);
+	memcpy(self->data->caps, rsp, rsp->h.length);
+	if (!gst_avdtp_sink_update_caps(self)) {
+		GST_WARNING_OBJECT(self, "failed to update capabilities");
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gint gst_avdtp_sink_get_channel_mode(const gchar *mode)
+{
+	if (strcmp(mode, "stereo") == 0)
+		return BT_A2DP_CHANNEL_MODE_STEREO;
+	else if (strcmp(mode, "joint-stereo") == 0)
+		return BT_A2DP_CHANNEL_MODE_JOINT_STEREO;
+	else if (strcmp(mode, "dual-channel") == 0)
+		return BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL;
+	else if (strcmp(mode, "mono") == 0)
+		return BT_A2DP_CHANNEL_MODE_MONO;
+	else
+		return -1;
+}
+
+static void gst_avdtp_sink_tag(const GstTagList *taglist,
+			const gchar *tag, gpointer user_data)
+{
+	gboolean crc;
+	gchar *channel_mode = NULL;
+	GstAvdtpSink *self = GST_AVDTP_SINK(user_data);
+
+	if (strcmp(tag, "has-crc") == 0) {
+
+		if (!gst_tag_list_get_boolean(taglist, tag, &crc)) {
+			GST_WARNING_OBJECT(self, "failed to get crc tag");
+			return;
+		}
+
+		gst_avdtp_sink_set_crc(self, crc);
+
+	} else if (strcmp(tag, "channel-mode") == 0) {
+
+		if (!gst_tag_list_get_string(taglist, tag, &channel_mode)) {
+			GST_WARNING_OBJECT(self,
+				"failed to get channel-mode tag");
+			return;
+		}
+
+		self->channel_mode = gst_avdtp_sink_get_channel_mode(
+					channel_mode);
+		if (self->channel_mode == -1)
+			GST_WARNING_OBJECT(self, "Received invalid channel "
+					"mode: %s", channel_mode);
+		g_free(channel_mode);
+
+	} else
+		GST_DEBUG_OBJECT(self, "received unused tag: %s", tag);
+}
+
+static gboolean gst_avdtp_sink_event(GstBaseSink *basesink,
+			GstEvent *event)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	GstTagList *taglist = NULL;
+
+	if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
+		/* we check the tags, mp3 has tags that are importants and
+		 * are outside caps */
+		gst_event_parse_tag(event, &taglist);
+		gst_tag_list_foreach(taglist, gst_avdtp_sink_tag, self);
+	}
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_start(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	gint sk;
+	gint err;
+
+	GST_INFO_OBJECT(self, "start");
+
+	self->watch_id = 0;
+
+	sk = bt_audio_service_open();
+	if (sk <= 0) {
+		err = errno;
+		GST_ERROR_OBJECT(self, "Cannot open connection to bt "
+			"audio service: %s %d", strerror(err), err);
+		goto failed;
+	}
+
+	self->server = g_io_channel_unix_new(sk);
+	self->watch_id = g_io_add_watch(self->server, G_IO_HUP | G_IO_ERR |
+					G_IO_NVAL, server_callback, self);
+
+	self->data = g_new0(struct bluetooth_data, 1);
+
+	self->stream = NULL;
+	self->stream_caps = NULL;
+	self->mp3_using_crc = -1;
+	self->channel_mode = -1;
+
+	if (!gst_avdtp_sink_get_capabilities(self)) {
+		GST_ERROR_OBJECT(self, "failed to get capabilities "
+				"from device");
+		goto failed;
+	}
+
+	return TRUE;
+
+failed:
+	bt_audio_service_close(sk);
+	return FALSE;
+}
+
+static gboolean gst_avdtp_sink_stream_start(GstAvdtpSink *self)
+{
+	gchar buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_start_stream_req *req = (void *) buf;
+	struct bt_start_stream_rsp *rsp = (void *) buf;
+	struct bt_new_stream_ind *ind = (void *) buf;
+	GIOError io_error;
+
+	memset(req, 0, sizeof(buf));
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_START_STREAM;
+	req->h.length = sizeof(*req);
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"start packet");
+		return FALSE;
+	}
+
+	rsp->h.length = sizeof(*rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_START_STREAM);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while stream "
+			"start confirmation");
+		return FALSE;
+	}
+
+	ind->h.length = sizeof(*ind);
+	io_error = gst_avdtp_sink_audioservice_expect(self, &ind->h,
+			BT_NEW_STREAM);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving "
+			"stream filedescriptor");
+		return FALSE;
+	}
+
+	if (!gst_avdtp_sink_conf_recv_stream_fd(self))
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_init_mp3_pkt_conf(
+		GstAvdtpSink *self, GstCaps *caps,
+		mpeg_capabilities_t *pkt)
+{
+	const GValue *value = NULL;
+	gint rate, layer;
+	const gchar *name;
+	GstStructure *structure = gst_caps_get_structure(caps, 0);
+
+	name = gst_structure_get_name(structure);
+
+	if (!(IS_MPEG_AUDIO(name))) {
+		GST_ERROR_OBJECT(self, "Unexpected format %s, "
+				"was expecting mp3", name);
+		return FALSE;
+	}
+
+	/* layer */
+	value = gst_structure_get_value(structure, "layer");
+	layer = g_value_get_int(value);
+	if (layer == 1)
+		pkt->layer = BT_MPEG_LAYER_1;
+	else if (layer == 2)
+		pkt->layer = BT_MPEG_LAYER_2;
+	else if (layer == 3)
+		pkt->layer = BT_MPEG_LAYER_3;
+	else {
+		GST_ERROR_OBJECT(self, "Unexpected layer: %d", layer);
+		return FALSE;
+	}
+
+	/* crc */
+	if (self->mp3_using_crc != -1)
+		pkt->crc = self->mp3_using_crc;
+	else {
+		GST_ERROR_OBJECT(self, "No info about crc was received, "
+				" can't proceed");
+		return FALSE;
+	}
+
+	/* channel mode */
+	if (self->channel_mode != -1)
+		pkt->channel_mode = self->channel_mode;
+	else {
+		GST_ERROR_OBJECT(self, "No info about channel mode "
+				"received, can't proceed");
+		return FALSE;
+	}
+
+	/* mpf - we will only use the mandatory one */
+	pkt->mpf = 0;
+
+	value = gst_structure_get_value(structure, "rate");
+	rate = g_value_get_int(value);
+	if (rate == 44100)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_44100;
+	else if (rate == 48000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_48000;
+	else if (rate == 32000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_32000;
+	else if (rate == 24000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_24000;
+	else if (rate == 22050)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_22050;
+	else if (rate == 16000)
+		pkt->frequency = BT_MPEG_SAMPLING_FREQ_16000;
+	else {
+		GST_ERROR_OBJECT(self, "Invalid rate while setting caps");
+		return FALSE;
+	}
+
+	/* vbr - we always say its vbr, we don't have how to know it */
+	pkt->bitrate = 0x8000;
+
+	return TRUE;
+}
+
+static gboolean gst_avdtp_sink_configure(GstAvdtpSink *self,
+			GstCaps *caps)
+{
+	gchar buf[BT_SUGGESTED_BUFFER_SIZE];
+	struct bt_open_req *open_req = (void *) buf;
+	struct bt_open_rsp *open_rsp = (void *) buf;
+	struct bt_set_configuration_req *req = (void *) buf;
+	struct bt_set_configuration_rsp *rsp = (void *) buf;
+	gboolean ret;
+	GIOError io_error;
+	gchar *temp;
+	GstStructure *structure;
+	codec_capabilities_t *codec = NULL;
+
+	temp = gst_caps_to_string(caps);
+	GST_DEBUG_OBJECT(self, "configuring device with caps: %s", temp);
+	g_free(temp);
+
+	structure = gst_caps_get_structure(caps, 0);
+
+	if (gst_structure_has_name(structure, "audio/x-sbc"))
+		codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_SBC_SINK);
+	else if (gst_structure_has_name(structure, "audio/mpeg"))
+		codec = (void *) gst_avdtp_find_caps(self, BT_A2DP_MPEG12_SINK);
+
+	if (codec == NULL) {
+		GST_ERROR_OBJECT(self, "Couldn't parse caps "
+				"to packet configuration");
+		return FALSE;
+	}
+
+	memset(req, 0, BT_SUGGESTED_BUFFER_SIZE);
+	open_req->h.type = BT_REQUEST;
+	open_req->h.name = BT_OPEN;
+	open_req->h.length = sizeof(*open_req);
+
+	strncpy(open_req->destination, self->device, 18);
+	open_req->seid = codec->seid;
+	open_req->lock = BT_WRITE_LOCK;
+
+	io_error = gst_avdtp_sink_audioservice_send(self, &open_req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"open packet");
+		return FALSE;
+	}
+
+	open_rsp->h.length = sizeof(*open_rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&open_rsp->h, BT_OPEN);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving device "
+					"confirmation");
+		return FALSE;
+	}
+
+	memset(req, 0, sizeof(buf));
+	req->h.type = BT_REQUEST;
+	req->h.name = BT_SET_CONFIGURATION;
+	req->h.length = sizeof(*req);
+
+	if (codec->type == BT_A2DP_SBC_SINK)
+		ret = gst_avdtp_sink_init_sbc_pkt_conf(self, caps,
+				(void *) &req->codec);
+	else
+		ret = gst_avdtp_sink_init_mp3_pkt_conf(self, caps,
+				(void *) &req->codec);
+
+	if (!ret) {
+		GST_ERROR_OBJECT(self, "Couldn't parse caps "
+				"to packet configuration");
+		return FALSE;
+	}
+
+	req->h.length += req->codec.length - sizeof(req->codec);
+	io_error = gst_avdtp_sink_audioservice_send(self, &req->h);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error ocurred while sending "
+					"configurarion packet");
+		return FALSE;
+	}
+
+	rsp->h.length = sizeof(*rsp);
+	io_error = gst_avdtp_sink_audioservice_expect(self,
+			&rsp->h, BT_SET_CONFIGURATION);
+	if (io_error != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while receiving device "
+					"confirmation");
+		return FALSE;
+	}
+
+	self->data->link_mtu = rsp->link_mtu;
+
+	return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_preroll(GstBaseSink *basesink,
+					GstBuffer *buffer)
+{
+	GstAvdtpSink *sink = GST_AVDTP_SINK(basesink);
+	gboolean ret;
+
+	GST_AVDTP_SINK_MUTEX_LOCK(sink);
+
+	ret = gst_avdtp_sink_stream_start(sink);
+
+	GST_AVDTP_SINK_MUTEX_UNLOCK(sink);
+
+	if (!ret)
+		return GST_FLOW_ERROR;
+
+	return GST_FLOW_OK;
+}
+
+static GstFlowReturn gst_avdtp_sink_render(GstBaseSink *basesink,
+					GstBuffer *buffer)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+	gsize ret;
+	GIOError err;
+
+	err = g_io_channel_write(self->stream,
+				(gchar *) GST_BUFFER_DATA(buffer),
+				(gsize) (GST_BUFFER_SIZE(buffer)), &ret);
+
+	if (err != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error while writting to socket: %d %s",
+				errno, strerror(errno));
+		return GST_FLOW_ERROR;
+	}
+
+	return GST_FLOW_OK;
+}
+
+static gboolean gst_avdtp_sink_unlock(GstBaseSink *basesink)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	if (self->stream != NULL)
+		g_io_channel_flush(self->stream, NULL);
+
+	return TRUE;
+}
+
+static GstFlowReturn gst_avdtp_sink_buffer_alloc(GstBaseSink *basesink,
+				guint64 offset, guint size, GstCaps *caps,
+				GstBuffer **buf)
+{
+	GstAvdtpSink *self = GST_AVDTP_SINK(basesink);
+
+	*buf = gst_buffer_new_and_alloc(size);
+	if (!(*buf)) {
+		GST_ERROR_OBJECT(self, "buffer allocation failed");
+		return GST_FLOW_ERROR;
+	}
+
+	gst_buffer_set_caps(*buf, caps);
+
+	GST_BUFFER_OFFSET(*buf) = offset;
+
+	return GST_FLOW_OK;
+}
+
+static void gst_avdtp_sink_class_init(GstAvdtpSinkClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	object_class->finalize = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_finalize);
+	object_class->set_property = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_set_property);
+	object_class->get_property = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_get_property);
+
+	basesink_class->start = GST_DEBUG_FUNCPTR(gst_avdtp_sink_start);
+	basesink_class->stop = GST_DEBUG_FUNCPTR(gst_avdtp_sink_stop);
+	basesink_class->render = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_render);
+	basesink_class->preroll = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_preroll);
+	basesink_class->unlock = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_unlock);
+	basesink_class->event = GST_DEBUG_FUNCPTR(
+					gst_avdtp_sink_event);
+
+	basesink_class->buffer_alloc =
+		GST_DEBUG_FUNCPTR(gst_avdtp_sink_buffer_alloc);
+
+	g_object_class_install_property(object_class, PROP_DEVICE,
+					g_param_spec_string("device", "Device",
+					"Bluetooth remote device address",
+					NULL, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_AUTOCONNECT,
+					g_param_spec_boolean("auto-connect",
+					"Auto-connect",
+					"Automatically attempt to connect "
+					"to device", DEFAULT_AUTOCONNECT,
+					G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(avdtp_sink_debug, "avdtpsink", 0,
+				"A2DP headset sink element");
+}
+
+static void gst_avdtp_sink_init(GstAvdtpSink *self,
+			GstAvdtpSinkClass *klass)
+{
+	self->device = NULL;
+	self->data = NULL;
+
+	self->stream = NULL;
+
+	self->dev_caps = NULL;
+
+	self->autoconnect = DEFAULT_AUTOCONNECT;
+
+	self->sink_lock = g_mutex_new();
+
+	/* FIXME this is for not synchronizing with clock, should be tested
+	 * with devices to see the behaviour
+	gst_base_sink_set_sync(GST_BASE_SINK(self), FALSE);
+	*/
+}
+
+static GIOError gst_avdtp_sink_audioservice_send(
+					GstAvdtpSink *self,
+					const bt_audio_msg_header_t *msg)
+{
+	GIOError error;
+	gsize written;
+	const char *type, *name;
+	uint16_t length;
+
+	length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	error = g_io_channel_write(self->server, (const gchar *) msg, length,
+								&written);
+	if (error != G_IO_ERROR_NONE)
+		GST_ERROR_OBJECT(self, "Error sending data to audio service:"
+			" %s(%d)", strerror(errno), errno);
+
+	type = bt_audio_strtype(msg->type);
+	name = bt_audio_strname(msg->name);
+
+	GST_DEBUG_OBJECT(self, "sent: %s -> %s", type, name);
+
+	return error;
+}
+
+static GIOError gst_avdtp_sink_audioservice_recv(
+					GstAvdtpSink *self,
+					bt_audio_msg_header_t *inmsg)
+{
+	GIOError status;
+	gsize bytes_read;
+	const char *type, *name;
+	uint16_t length;
+
+	length = inmsg->length ? inmsg->length : BT_SUGGESTED_BUFFER_SIZE;
+
+	status = g_io_channel_read(self->server, (gchar *) inmsg, length,
+								&bytes_read);
+	if (status != G_IO_ERROR_NONE) {
+		GST_ERROR_OBJECT(self, "Error receiving data from "
+				"audio service");
+		return status;
+	}
+
+	type = bt_audio_strtype(inmsg->type);
+	if (!type) {
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "Bogus message type %d "
+				"received from audio service",
+				inmsg->type);
+	}
+
+	name = bt_audio_strname(inmsg->name);
+	if (!name) {
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "Bogus message name %d "
+				"received from audio service",
+				inmsg->name);
+	}
+
+	if (inmsg->type == BT_ERROR) {
+		bt_audio_error_t *err = (void *) inmsg;
+		status = G_IO_ERROR_INVAL;
+		GST_ERROR_OBJECT(self, "%s failed : "
+					"%s(%d)",
+					name,
+					strerror(err->posix_errno),
+					err->posix_errno);
+	}
+
+	GST_DEBUG_OBJECT(self, "received: %s <- %s", type, name);
+
+	return status;
+}
+
+static GIOError gst_avdtp_sink_audioservice_expect(
+			GstAvdtpSink *self, bt_audio_msg_header_t *outmsg,
+			guint8 expected_name)
+{
+	GIOError status;
+
+	status = gst_avdtp_sink_audioservice_recv(self, outmsg);
+	if (status != G_IO_ERROR_NONE)
+		return status;
+
+	if (outmsg->name != expected_name)
+		status = G_IO_ERROR_INVAL;
+
+	return status;
+}
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "avdtpsink", GST_RANK_NONE,
+							GST_TYPE_AVDTP_SINK);
+}
+
+
+/* public functions */
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink)
+{
+	if (sink->dev_caps == NULL)
+		return NULL;
+
+	return gst_caps_copy(sink->dev_caps);
+}
+
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *self,
+			GstCaps *caps)
+{
+	gboolean ret;
+
+	GST_DEBUG_OBJECT(self, "setting device caps");
+	GST_AVDTP_SINK_MUTEX_LOCK(self);
+	ret = gst_avdtp_sink_configure(self, caps);
+
+	if (self->stream_caps)
+		gst_caps_unref(self->stream_caps);
+	self->stream_caps = gst_caps_ref(caps);
+
+	GST_AVDTP_SINK_MUTEX_UNLOCK(self);
+
+	return ret;
+}
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink)
+{
+	return sink->data->link_mtu;
+}
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *self, const gchar *dev)
+{
+	if (self->device != NULL)
+		g_free(self->device);
+
+	GST_LOG_OBJECT(self, "Setting device: %s", dev);
+	self->device = g_strdup(dev);
+}
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *self)
+{
+	return g_strdup(self->device);
+}
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc)
+{
+	gint new_crc;
+
+	new_crc = crc ? CRC_PROTECTED : CRC_UNPROTECTED;
+
+	/* test if we already received a different crc */
+	if (self->mp3_using_crc != -1 && new_crc != self->mp3_using_crc) {
+		GST_WARNING_OBJECT(self, "crc changed during stream");
+		return;
+	}
+	self->mp3_using_crc = new_crc;
+
+}
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+			const gchar *mode)
+{
+	gint new_mode;
+
+	new_mode = gst_avdtp_sink_get_channel_mode(mode);
+
+	if (self->channel_mode != -1 && new_mode != self->channel_mode) {
+		GST_WARNING_OBJECT(self, "channel mode changed during stream");
+		return;
+	}
+
+	self->channel_mode = new_mode;
+	if (self->channel_mode == -1)
+		GST_WARNING_OBJECT(self, "Received invalid channel "
+				"mode: %s", mode);
+}
diff --git a/audio/gstavdtpsink.h b/audio/gstavdtpsink.h
new file mode 100644
index 0000000..b4bee39
--- /dev/null
+++ b/audio/gstavdtpsink.h
@@ -0,0 +1,101 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifndef __GST_AVDTP_SINK_H
+#define __GST_AVDTP_SINK_H
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_AVDTP_SINK \
+	(gst_avdtp_sink_get_type())
+#define GST_AVDTP_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AVDTP_SINK,\
+		GstAvdtpSink))
+#define GST_AVDTP_SINK_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_AVDTP_SINK,\
+		GstAvdtpSinkClass))
+#define GST_IS_AVDTP_SINK(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AVDTP_SINK))
+#define GST_IS_AVDTP_SINK_CLASS(obj) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_AVDTP_SINK))
+
+typedef struct _GstAvdtpSink GstAvdtpSink;
+typedef struct _GstAvdtpSinkClass GstAvdtpSinkClass;
+
+struct bluetooth_data;
+
+struct _GstAvdtpSink {
+	GstBaseSink sink;
+
+	gchar *device;
+	GIOChannel *stream;
+
+	struct bluetooth_data *data;
+	gboolean autoconnect;
+	GIOChannel *server;
+
+	/* mp3 stream data (outside caps data)*/
+	gint mp3_using_crc;
+	gint channel_mode;
+
+	/* stream connection data */
+	GstCaps *stream_caps;
+
+	GstCaps *dev_caps;
+
+	GMutex *sink_lock;
+
+	guint watch_id;
+};
+
+struct _GstAvdtpSinkClass {
+	GstBaseSinkClass parent_class;
+};
+
+//GType gst_avdtp_sink_get_type(void);
+
+GstCaps *gst_avdtp_sink_get_device_caps(GstAvdtpSink *sink);
+gboolean gst_avdtp_sink_set_device_caps(GstAvdtpSink *sink,
+			GstCaps *caps);
+
+guint gst_avdtp_sink_get_link_mtu(GstAvdtpSink *sink);
+
+void gst_avdtp_sink_set_device(GstAvdtpSink *sink,
+		const gchar* device);
+
+gchar *gst_avdtp_sink_get_device(GstAvdtpSink *sink);
+
+gboolean gst_avdtp_sink_plugin_init(GstPlugin *plugin);
+
+void gst_avdtp_sink_set_crc(GstAvdtpSink *self, gboolean crc);
+
+void gst_avdtp_sink_set_channel_mode(GstAvdtpSink *self,
+			const gchar *mode);
+
+
+G_END_DECLS
+
+#endif /* __GST_AVDTP_SINK_H */
diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c
new file mode 100644
index 0000000..7775b48
--- /dev/null
+++ b/audio/gstbluetooth.c
@@ -0,0 +1,106 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <gst/gst.h>
+
+#include "gstsbcutil.h"
+#include <sbc.h>
+
+#include "gstsbcenc.h"
+#include "gstsbcdec.h"
+#include "gstsbcparse.h"
+#include "gstavdtpsink.h"
+#include "gsta2dpsink.h"
+#include "gstrtpsbcpay.h"
+
+static GstStaticCaps sbc_caps = GST_STATIC_CAPS("audio/x-sbc");
+
+#define SBC_CAPS (gst_static_caps_get(&sbc_caps))
+
+static void sbc_typefind(GstTypeFind *tf, gpointer ignore)
+{
+	GstCaps *caps;
+	guint8 *aux;
+	sbc_t sbc;
+	guint8 *data = gst_type_find_peek(tf, 0, 32);
+
+	if (sbc_init(&sbc, 0) < 0)
+		return;
+
+	if (data == NULL || *data != 0x9c)	/* SBC syncword */
+		return;
+
+	aux = g_new(guint8, 32);
+	memcpy(aux, data, 32);
+	sbc_parse(&sbc, aux, 32);
+	g_free(aux);
+	caps = gst_sbc_parse_caps_from_sbc(&sbc);
+	sbc_finish(&sbc);
+
+	gst_type_find_suggest(tf, GST_TYPE_FIND_POSSIBLE, caps);
+	gst_caps_unref(caps);
+}
+
+static gchar *sbc_exts[] = { "sbc", NULL };
+
+static gboolean plugin_init(GstPlugin *plugin)
+{
+	GST_INFO("Bluetooth plugin %s", VERSION);
+
+	if (gst_type_find_register(plugin, "sbc",
+			GST_RANK_PRIMARY, sbc_typefind, sbc_exts,
+					SBC_CAPS, NULL, NULL) == FALSE)
+		return FALSE;
+
+	if (!gst_sbc_enc_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_sbc_dec_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_sbc_parse_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_avdtp_sink_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_a2dp_sink_plugin_init(plugin))
+		return FALSE;
+
+	if (!gst_rtp_sbc_pay_plugin_init(plugin))
+		return FALSE;
+
+	return TRUE;
+}
+
+extern GstPluginDesc gst_plugin_desc __attribute__ ((visibility("default")));
+
+GST_PLUGIN_DEFINE(GST_VERSION_MAJOR, GST_VERSION_MINOR,
+	"bluetooth", "Bluetooth plugin library",
+	plugin_init, VERSION, "LGPL", "BlueZ", "http://www.bluez.org/")
diff --git a/audio/gstrtpsbcpay.c b/audio/gstrtpsbcpay.c
new file mode 100644
index 0000000..78ffb2a
--- /dev/null
+++ b/audio/gstrtpsbcpay.c
@@ -0,0 +1,351 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstrtpsbcpay.h"
+#include <math.h>
+#include <string.h>
+
+#define RTP_SBC_PAYLOAD_HEADER_SIZE 1
+#define DEFAULT_MIN_FRAMES 0
+#define RTP_SBC_HEADER_TOTAL (12 + RTP_SBC_PAYLOAD_HEADER_SIZE)
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct rtp_payload {
+	guint8 frame_count:4;
+	guint8 rfa0:1;
+	guint8 is_last_fragment:1;
+	guint8 is_first_fragment:1;
+	guint8 is_fragmented:1;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct rtp_payload {
+	guint8 is_fragmented:1;
+	guint8 is_first_fragment:1;
+	guint8 is_last_fragment:1;
+	guint8 rfa0:1;
+	guint8 frame_count:4;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+enum {
+	PROP_0,
+	PROP_MIN_FRAMES
+};
+
+GST_DEBUG_CATEGORY_STATIC(gst_rtp_sbc_pay_debug);
+#define GST_CAT_DEFAULT gst_rtp_sbc_pay_debug
+
+GST_BOILERPLATE(GstRtpSBCPay, gst_rtp_sbc_pay, GstBaseRTPPayload,
+		GST_TYPE_BASE_RTP_PAYLOAD);
+
+static const GstElementDetails gst_rtp_sbc_pay_details =
+	GST_ELEMENT_DETAILS("RTP packet payloader",
+				"Codec/Payloader/Network",
+				"Payload SBC audio as RTP packets",
+				"Thiago Sousa Santos "
+				"<thiagoss@lcc.ufcg.edu.br>");
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-sbc, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+				"blocks = (int) { 4, 8, 12, 16 }, "
+				"subbands = (int) { 4, 8 }, "
+				"allocation = (string) { \"snr\", \"loudness\" }, "
+				"bitpool = (int) [ 2, 64 ]")
+	);
+
+static GstStaticPadTemplate gst_rtp_sbc_pay_src_factory =
+	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS(
+			"application/x-rtp, "
+			"media = (string) \"audio\","
+			"payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
+			"clock-rate = (int) { 16000, 32000, 44100, 48000 },"
+			"encoding-name = (string) \"SBC\"")
+	);
+
+static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id,
+				const GValue *value, GParamSpec *pspec);
+static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id,
+				GValue *value, GParamSpec *pspec);
+
+static gint gst_rtp_sbc_pay_get_frame_len(gint subbands, gint channels,
+		gint blocks, gint bitpool, const gchar *channel_mode)
+{
+	gint len;
+	gint join;
+
+	len = 4 + (4 * subbands * channels)/8;
+
+	if (strcmp(channel_mode, "mono") == 0 ||
+		strcmp(channel_mode, "dual") == 0)
+		len += ((blocks * channels * bitpool) + 7) / 8;
+	else {
+		join = strcmp(channel_mode, "joint") == 0 ? 1 : 0;
+		len += ((join * subbands + blocks * bitpool) + 7) / 8;
+	}
+
+	return len;
+}
+
+static gboolean gst_rtp_sbc_pay_set_caps(GstBaseRTPPayload *payload,
+			GstCaps *caps)
+{
+	GstRtpSBCPay *sbcpay;
+	gint rate, subbands, channels, blocks, bitpool;
+	gint frame_len;
+	const gchar *channel_mode;
+	GstStructure *structure;
+
+	sbcpay = GST_RTP_SBC_PAY(payload);
+
+	structure = gst_caps_get_structure(caps, 0);
+	if (!gst_structure_get_int(structure, "rate", &rate))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "channels", &channels))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "blocks", &blocks))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "bitpool", &bitpool))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "subbands", &subbands))
+		return FALSE;
+
+	channel_mode = gst_structure_get_string(structure, "mode");
+	if (!channel_mode)
+		return FALSE;
+
+	frame_len = gst_rtp_sbc_pay_get_frame_len(subbands, channels, blocks,
+				bitpool, channel_mode);
+
+	sbcpay->frame_length = frame_len;
+
+	gst_basertppayload_set_options(payload, "audio", TRUE, "SBC", rate);
+
+	GST_DEBUG_OBJECT(payload, "calculated frame length: %d ", frame_len);
+
+	return gst_basertppayload_set_outcaps(payload, NULL);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_flush_buffers(GstRtpSBCPay *sbcpay)
+{
+	guint available;
+	guint max_payload;
+	GstBuffer *outbuf;
+	guint8 *payload_data;
+	guint frame_count;
+	guint payload_length;
+	struct rtp_payload *payload;
+
+	if (sbcpay->frame_length == 0) {
+		GST_ERROR_OBJECT(sbcpay, "Frame length is 0");
+		return GST_FLOW_ERROR;
+	}
+
+	available = gst_adapter_available(sbcpay->adapter);
+
+	max_payload = gst_rtp_buffer_calc_payload_len(
+		GST_BASE_RTP_PAYLOAD_MTU(sbcpay) - RTP_SBC_PAYLOAD_HEADER_SIZE,
+		0, 0);
+
+	max_payload = MIN(max_payload, available);
+	frame_count = max_payload / sbcpay->frame_length;
+	payload_length = frame_count * sbcpay->frame_length;
+	if (payload_length == 0) /* Nothing to send */
+		return GST_FLOW_OK;
+
+	outbuf = gst_rtp_buffer_new_allocate(payload_length +
+			RTP_SBC_PAYLOAD_HEADER_SIZE, 0, 0);
+
+	gst_rtp_buffer_set_payload_type(outbuf,
+			GST_BASE_RTP_PAYLOAD_PT(sbcpay));
+
+	payload_data = gst_rtp_buffer_get_payload(outbuf);
+	payload = (struct rtp_payload *) payload_data;
+	memset(payload, 0, sizeof(struct rtp_payload));
+	payload->frame_count = frame_count;
+
+	gst_adapter_copy(sbcpay->adapter, payload_data +
+			RTP_SBC_PAYLOAD_HEADER_SIZE, 0, payload_length);
+	gst_adapter_flush(sbcpay->adapter, payload_length);
+
+	GST_BUFFER_TIMESTAMP(outbuf) = sbcpay->timestamp;
+	GST_DEBUG_OBJECT(sbcpay, "Pushing %d bytes", payload_length);
+
+	return gst_basertppayload_push(GST_BASE_RTP_PAYLOAD(sbcpay), outbuf);
+}
+
+static GstFlowReturn gst_rtp_sbc_pay_handle_buffer(GstBaseRTPPayload *payload,
+			GstBuffer *buffer)
+{
+	GstRtpSBCPay *sbcpay;
+	guint available;
+
+	/* FIXME check for negotiation */
+
+	sbcpay = GST_RTP_SBC_PAY(payload);
+	sbcpay->timestamp = GST_BUFFER_TIMESTAMP(buffer);
+
+	gst_adapter_push(sbcpay->adapter, buffer);
+
+	available = gst_adapter_available(sbcpay->adapter);
+	if (available + RTP_SBC_HEADER_TOTAL >=
+				GST_BASE_RTP_PAYLOAD_MTU(sbcpay) ||
+			(available >
+				(sbcpay->min_frames * sbcpay->frame_length)))
+		return gst_rtp_sbc_pay_flush_buffers(sbcpay);
+
+	return GST_FLOW_OK;
+}
+
+static gboolean gst_rtp_sbc_pay_handle_event(GstPad *pad,
+				GstEvent *event)
+{
+	GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(GST_PAD_PARENT(pad));
+
+	switch (GST_EVENT_TYPE(event)) {
+	case GST_EVENT_EOS:
+		gst_rtp_sbc_pay_flush_buffers(sbcpay);
+		break;
+	default:
+		break;
+	}
+
+	return FALSE;
+}
+
+static void gst_rtp_sbc_pay_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&gst_rtp_sbc_pay_sink_factory));
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&gst_rtp_sbc_pay_src_factory));
+
+	gst_element_class_set_details(element_class, &gst_rtp_sbc_pay_details);
+}
+
+static void gst_rtp_sbc_pay_finalize(GObject *object)
+{
+	GstRtpSBCPay *sbcpay = GST_RTP_SBC_PAY(object);
+	g_object_unref(sbcpay->adapter);
+
+	GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object));
+}
+
+static void gst_rtp_sbc_pay_class_init(GstRtpSBCPayClass *klass)
+{
+	GObjectClass *gobject_class;
+	GstBaseRTPPayloadClass *payload_class =
+		GST_BASE_RTP_PAYLOAD_CLASS(klass);
+
+	gobject_class = G_OBJECT_CLASS(klass);
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobject_class->finalize = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_finalize);
+	gobject_class->set_property = GST_DEBUG_FUNCPTR(
+			gst_rtp_sbc_pay_set_property);
+	gobject_class->get_property = GST_DEBUG_FUNCPTR(
+			gst_rtp_sbc_pay_get_property);
+
+	payload_class->set_caps = GST_DEBUG_FUNCPTR(gst_rtp_sbc_pay_set_caps);
+	payload_class->handle_buffer = GST_DEBUG_FUNCPTR(
+			gst_rtp_sbc_pay_handle_buffer);
+	payload_class->handle_event = GST_DEBUG_FUNCPTR(
+			gst_rtp_sbc_pay_handle_event);
+
+	/* properties */
+	g_object_class_install_property(G_OBJECT_CLASS(klass),
+		PROP_MIN_FRAMES,
+		g_param_spec_int("min-frames", "minimum frame number",
+		"Minimum quantity of frames to send in one packet "
+		"(-1 for maximum allowed by the mtu)",
+		-1, G_MAXINT, DEFAULT_MIN_FRAMES, G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(gst_rtp_sbc_pay_debug, "rtpsbcpay", 0,
+				"RTP SBC payloader");
+}
+
+static void gst_rtp_sbc_pay_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstRtpSBCPay *sbcpay;
+
+	sbcpay = GST_RTP_SBC_PAY(object);
+
+	switch (prop_id) {
+	case PROP_MIN_FRAMES:
+		sbcpay->min_frames = g_value_get_int(value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+	break;
+	}
+}
+
+static void gst_rtp_sbc_pay_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstRtpSBCPay *sbcpay;
+
+	sbcpay = GST_RTP_SBC_PAY(object);
+
+	switch (prop_id) {
+	case PROP_MIN_FRAMES:
+		g_value_set_int(value, sbcpay->min_frames);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+	break;
+	}
+}
+
+static void gst_rtp_sbc_pay_init(GstRtpSBCPay *self, GstRtpSBCPayClass *klass)
+{
+	self->adapter = gst_adapter_new();
+	self->frame_length = 0;
+	self->timestamp = 0;
+
+	self->min_frames = DEFAULT_MIN_FRAMES;
+}
+
+gboolean gst_rtp_sbc_pay_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "rtpsbcpay", GST_RANK_NONE,
+							GST_TYPE_RTP_SBC_PAY);
+}
+
diff --git a/audio/gstrtpsbcpay.h b/audio/gstrtpsbcpay.h
new file mode 100644
index 0000000..7b314aa
--- /dev/null
+++ b/audio/gstrtpsbcpay.h
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/rtp/gstbasertppayload.h>
+#include <gst/base/gstadapter.h>
+#include <gst/rtp/gstrtpbuffer.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_RTP_SBC_PAY \
+	(gst_rtp_sbc_pay_get_type())
+#define GST_RTP_SBC_PAY(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_SBC_PAY,\
+		GstRtpSBCPay))
+#define GST_RTP_SBC_PAY_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_SBC_PAY,\
+		GstRtpSBCPayClass))
+#define GST_IS_RTP_SBC_PAY(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_SBC_PAY))
+#define GST_IS_RTP_SBC_PAY_CLASS(obj) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_SBC_PAY))
+
+typedef struct _GstRtpSBCPay GstRtpSBCPay;
+typedef struct _GstRtpSBCPayClass GstRtpSBCPayClass;
+
+struct _GstRtpSBCPay {
+	GstBaseRTPPayload base;
+
+	GstAdapter *adapter;
+	GstClockTime timestamp;
+
+	guint frame_length;
+
+	guint min_frames;
+};
+
+struct _GstRtpSBCPayClass {
+	GstBaseRTPPayloadClass parent_class;
+};
+
+//GType gst_rtp_sbc_pay_get_type(void);
+
+gboolean gst_rtp_sbc_pay_plugin_init (GstPlugin * plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcdec.c b/audio/gstsbcdec.c
new file mode 100644
index 0000000..4b46f52
--- /dev/null
+++ b/audio/gstsbcdec.c
@@ -0,0 +1,222 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstsbcutil.h"
+#include "gstsbcdec.h"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_dec_debug);
+#define GST_CAT_DEFAULT sbc_dec_debug
+
+GST_BOILERPLATE(GstSbcDec, gst_sbc_dec, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_dec_details =
+	GST_ELEMENT_DETAILS("Bluetooth SBC decoder",
+				"Codec/Decoder/Audio",
+				"Decode a SBC audio stream",
+				"Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_dec_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-sbc"));
+
+static GstStaticPadTemplate sbc_dec_src_factory =
+	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-raw-int, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"endianness = (int) BYTE_ORDER, "
+				"signed = (boolean) true, "
+				"width = (int) 16, "
+				"depth = (int) 16"));
+
+static GstFlowReturn sbc_dec_chain(GstPad *pad, GstBuffer *buffer)
+{
+	GstSbcDec *dec = GST_SBC_DEC(gst_pad_get_parent(pad));
+	GstFlowReturn res = GST_FLOW_OK;
+	guint size, codesize, offset = 0;
+	guint8 *data;
+
+	codesize = sbc_get_codesize(&dec->sbc);
+
+	if (dec->buffer) {
+		GstBuffer *temp = buffer;
+		buffer = gst_buffer_span(dec->buffer, 0, buffer,
+			GST_BUFFER_SIZE(dec->buffer) + GST_BUFFER_SIZE(buffer));
+		gst_buffer_unref(temp);
+		gst_buffer_unref(dec->buffer);
+		dec->buffer = NULL;
+	}
+
+	data = GST_BUFFER_DATA(buffer);
+	size = GST_BUFFER_SIZE(buffer);
+
+	while (offset < size) {
+		GstBuffer *output;
+		GstPadTemplate *template;
+		GstCaps *caps;
+		int consumed;
+
+		res = gst_pad_alloc_buffer_and_set_caps(dec->srcpad,
+						GST_BUFFER_OFFSET_NONE,
+						codesize, NULL, &output);
+
+		if (res != GST_FLOW_OK)
+			goto done;
+
+		consumed = sbc_decode(&dec->sbc, data + offset, size - offset,
+					GST_BUFFER_DATA(output), codesize,
+					NULL);
+		if (consumed <= 0)
+			break;
+
+		/* we will reuse the same caps object */
+		if (dec->outcaps == NULL) {
+			caps = gst_caps_new_simple("audio/x-raw-int",
+					"rate", G_TYPE_INT,
+					gst_sbc_parse_rate_from_sbc(
+						dec->sbc.frequency),
+					"channels", G_TYPE_INT,
+					gst_sbc_get_channel_number(
+						dec->sbc.mode),
+					NULL);
+
+			template = gst_static_pad_template_get(&sbc_dec_src_factory);
+
+			dec->outcaps = gst_caps_intersect(caps,
+						gst_pad_template_get_caps(template));
+
+			gst_caps_unref(caps);
+		}
+
+		gst_buffer_set_caps(output, dec->outcaps);
+
+		/* FIXME get a real timestamp */
+		GST_BUFFER_TIMESTAMP(output) = GST_CLOCK_TIME_NONE;
+
+		res = gst_pad_push(dec->srcpad, output);
+		if (res != GST_FLOW_OK)
+			goto done;
+
+		offset += consumed;
+	}
+
+	if (offset < size)
+		dec->buffer = gst_buffer_create_sub(buffer,
+							offset, size - offset);
+
+done:
+	gst_buffer_unref(buffer);
+	gst_object_unref(dec);
+
+	return res;
+}
+
+static GstStateChangeReturn sbc_dec_change_state(GstElement *element,
+						GstStateChange transition)
+{
+	GstSbcDec *dec = GST_SBC_DEC(element);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_READY_TO_PAUSED:
+		GST_DEBUG("Setup subband codec");
+		if (dec->buffer) {
+			gst_buffer_unref(dec->buffer);
+			dec->buffer = NULL;
+		}
+		sbc_init(&dec->sbc, 0);
+		dec->outcaps = NULL;
+		break;
+
+	case GST_STATE_CHANGE_PAUSED_TO_READY:
+		GST_DEBUG("Finish subband codec");
+		if (dec->buffer) {
+			gst_buffer_unref(dec->buffer);
+			dec->buffer = NULL;
+		}
+		sbc_finish(&dec->sbc);
+		if (dec->outcaps) {
+			gst_caps_unref(dec->outcaps);
+			dec->outcaps = NULL;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return parent_class->change_state(element, transition);
+}
+
+static void gst_sbc_dec_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&sbc_dec_sink_factory));
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&sbc_dec_src_factory));
+
+	gst_element_class_set_details(element_class, &sbc_dec_details);
+}
+
+static void gst_sbc_dec_class_init(GstSbcDecClass *klass)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	element_class->change_state = GST_DEBUG_FUNCPTR(sbc_dec_change_state);
+
+	GST_DEBUG_CATEGORY_INIT(sbc_dec_debug, "sbcdec", 0,
+						"SBC decoding element");
+}
+
+static void gst_sbc_dec_init(GstSbcDec *self, GstSbcDecClass *klass)
+{
+	self->sinkpad = gst_pad_new_from_static_template(
+			&sbc_dec_sink_factory, "sink");
+	gst_pad_set_chain_function(self->sinkpad, GST_DEBUG_FUNCPTR(
+			sbc_dec_chain));
+	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
+
+	self->srcpad = gst_pad_new_from_static_template(
+			&sbc_dec_src_factory, "src");
+	gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
+
+	self->outcaps = NULL;
+}
+
+gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "sbcdec", GST_RANK_PRIMARY,
+							GST_TYPE_SBC_DEC);
+}
+
+
diff --git a/audio/gstsbcdec.h b/audio/gstsbcdec.h
new file mode 100644
index 0000000..383a3bf
--- /dev/null
+++ b/audio/gstsbcdec.h
@@ -0,0 +1,66 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#include <gst/gst.h>
+
+#include "sbc.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SBC_DEC \
+	(gst_sbc_dec_get_type())
+#define GST_SBC_DEC(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_DEC,GstSbcDec))
+#define GST_SBC_DEC_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_DEC,GstSbcDecClass))
+#define GST_IS_SBC_DEC(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_DEC))
+#define GST_IS_SBC_DEC_CLASS(obj) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_DEC))
+
+typedef struct _GstSbcDec GstSbcDec;
+typedef struct _GstSbcDecClass GstSbcDecClass;
+
+struct _GstSbcDec {
+	GstElement element;
+
+	GstPad *sinkpad;
+	GstPad *srcpad;
+
+	GstBuffer *buffer;
+
+	/* caps for outgoing buffers */
+	GstCaps *outcaps;
+
+	sbc_t sbc;
+};
+
+struct _GstSbcDecClass {
+	GstElementClass parent_class;
+};
+
+//GType gst_sbc_dec_get_type(void);
+
+gboolean gst_sbc_dec_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcenc.c b/audio/gstsbcenc.c
new file mode 100644
index 0000000..0ecf39c
--- /dev/null
+++ b/audio/gstsbcenc.c
@@ -0,0 +1,602 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstsbcutil.h"
+#include "gstsbcenc.h"
+
+#define SBC_ENC_DEFAULT_MODE SBC_MODE_AUTO
+#define SBC_ENC_DEFAULT_BLOCKS 0
+#define SBC_ENC_DEFAULT_SUB_BANDS 0
+#define SBC_ENC_DEFAULT_ALLOCATION SBC_AM_AUTO
+#define SBC_ENC_DEFAULT_RATE 0
+#define SBC_ENC_DEFAULT_CHANNELS 0
+
+#define SBC_ENC_BITPOOL_AUTO 1
+#define SBC_ENC_BITPOOL_MIN 2
+#define SBC_ENC_BITPOOL_MIN_STR "2"
+#define SBC_ENC_BITPOOL_MAX 64
+#define SBC_ENC_BITPOOL_MAX_STR "64"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_enc_debug);
+#define GST_CAT_DEFAULT sbc_enc_debug
+
+#define GST_TYPE_SBC_MODE (gst_sbc_mode_get_type())
+
+static GType gst_sbc_mode_get_type(void)
+{
+	static GType sbc_mode_type = 0;
+	static GEnumValue sbc_modes[] = {
+		{  SBC_MODE_MONO,		"Mono",		"mono"	},
+		{  SBC_MODE_DUAL_CHANNEL,	"Dual Channel",	"dual"	},
+		{  SBC_MODE_STEREO,		"Stereo",	"stereo"},
+		{  SBC_MODE_JOINT_STEREO,	"Joint Stereo",	"joint"	},
+		{  SBC_MODE_AUTO,		"Auto",		"auto"	},
+		{ -1, NULL, NULL}
+	};
+
+	if (!sbc_mode_type)
+		sbc_mode_type = g_enum_register_static("GstSbcMode", sbc_modes);
+
+	return sbc_mode_type;
+}
+
+#define GST_TYPE_SBC_ALLOCATION (gst_sbc_allocation_get_type())
+
+static GType gst_sbc_allocation_get_type(void)
+{
+	static GType sbc_allocation_type = 0;
+	static GEnumValue sbc_allocations[] = {
+		{ SBC_AM_LOUDNESS,	"Loudness",	"loudness" },
+		{ SBC_AM_SNR,		"SNR",		"snr" },
+		{ SBC_AM_AUTO,		"Auto",		"auto" },
+		{ -1, NULL, NULL}
+	};
+
+	if (!sbc_allocation_type)
+		sbc_allocation_type = g_enum_register_static(
+				"GstSbcAllocation", sbc_allocations);
+
+	return sbc_allocation_type;
+}
+
+#define GST_TYPE_SBC_BLOCKS (gst_sbc_blocks_get_type())
+
+static GType gst_sbc_blocks_get_type(void)
+{
+	static GType sbc_blocks_type = 0;
+	static GEnumValue sbc_blocks[] = {
+		{ 0,	"Auto",		"auto" },
+		{ 4,	"4",		"4" },
+		{ 8,	"8",		"8" },
+		{ 12,	"12",		"12" },
+		{ 16,	"16",		"16" },
+		{ -1, NULL, NULL}
+	};
+
+	if (!sbc_blocks_type)
+		sbc_blocks_type = g_enum_register_static(
+				"GstSbcBlocks", sbc_blocks);
+
+	return sbc_blocks_type;
+}
+
+#define GST_TYPE_SBC_SUBBANDS (gst_sbc_subbands_get_type())
+
+static GType gst_sbc_subbands_get_type(void)
+{
+	static GType sbc_subbands_type = 0;
+	static GEnumValue sbc_subbands[] = {
+		{ 0,	"Auto",		"auto" },
+		{ 4,	"4 subbands",	"4" },
+		{ 8,	"8 subbands",	"8" },
+		{ -1, NULL, NULL}
+	};
+
+	if (!sbc_subbands_type)
+		sbc_subbands_type = g_enum_register_static(
+				"GstSbcSubbands", sbc_subbands);
+
+	return sbc_subbands_type;
+}
+
+enum {
+	PROP_0,
+	PROP_MODE,
+	PROP_ALLOCATION,
+	PROP_BLOCKS,
+	PROP_SUBBANDS,
+	PROP_BITPOOL
+};
+
+GST_BOILERPLATE(GstSbcEnc, gst_sbc_enc, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_enc_details =
+	GST_ELEMENT_DETAILS("Bluetooth SBC encoder",
+				"Codec/Encoder/Audio",
+				"Encode a SBC audio stream",
+				"Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_enc_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-raw-int, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"endianness = (int) BYTE_ORDER, "
+				"signed = (boolean) true, "
+				"width = (int) 16, "
+				"depth = (int) 16"));
+
+static GstStaticPadTemplate sbc_enc_src_factory =
+	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-sbc, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+				"blocks = (int) { 4, 8, 12, 16 }, "
+				"subbands = (int) { 4, 8 }, "
+				"allocation = (string) { \"snr\", \"loudness\" }, "
+				"bitpool = (int) [ " SBC_ENC_BITPOOL_MIN_STR
+				", " SBC_ENC_BITPOOL_MAX_STR " ]"));
+
+gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps);
+
+static GstCaps *sbc_enc_generate_srcpad_caps(GstSbcEnc *enc)
+{
+	GstCaps *src_caps;
+	GstStructure *structure;
+	GEnumValue *enum_value;
+	GEnumClass *enum_class;
+	GValue *value;
+
+	src_caps = gst_caps_copy(gst_pad_get_pad_template_caps(enc->srcpad));
+	structure = gst_caps_get_structure(src_caps, 0);
+
+	value = g_new0(GValue, 1);
+
+	if (enc->rate != 0)
+		gst_sbc_util_set_structure_int_param(structure, "rate",
+			enc->rate, value);
+
+	if (enc->channels != 0)
+		gst_sbc_util_set_structure_int_param(structure, "channels",
+			enc->channels, value);
+
+	if (enc->subbands != 0)
+		gst_sbc_util_set_structure_int_param(structure, "subbands",
+			enc->subbands, value);
+
+	if (enc->blocks != 0)
+		gst_sbc_util_set_structure_int_param(structure, "blocks",
+			enc->blocks, value);
+
+	if (enc->bitpool != SBC_ENC_BITPOOL_AUTO)
+		gst_sbc_util_set_structure_int_param(structure, "bitpool",
+			enc->bitpool, value);
+
+	if (enc->mode != SBC_ENC_DEFAULT_MODE) {
+		enum_class = g_type_class_ref(GST_TYPE_SBC_MODE);
+		enum_value = g_enum_get_value(enum_class, enc->mode);
+		gst_sbc_util_set_structure_string_param(structure, "mode",
+			enum_value->value_nick, value);
+		g_type_class_unref(enum_class);
+	}
+
+	if (enc->allocation != SBC_AM_AUTO) {
+		enum_class = g_type_class_ref(GST_TYPE_SBC_ALLOCATION);
+		enum_value = g_enum_get_value(enum_class, enc->allocation);
+		gst_sbc_util_set_structure_string_param(structure, "allocation",
+			enum_value->value_nick, value);
+		g_type_class_unref(enum_class);
+	}
+
+	g_free(value);
+
+	return src_caps;
+}
+
+static GstCaps *sbc_enc_src_getcaps(GstPad *pad)
+{
+	GstSbcEnc *enc;
+
+	enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
+
+	return sbc_enc_generate_srcpad_caps(enc);
+}
+
+static gboolean sbc_enc_src_setcaps(GstPad *pad, GstCaps *caps)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
+
+	GST_LOG_OBJECT(enc, "setting srcpad caps");
+
+	return gst_sbc_enc_fill_sbc_params(enc, caps);
+}
+
+static GstCaps *sbc_enc_src_caps_fixate(GstSbcEnc *enc, GstCaps *caps)
+{
+	gchar *error_message = NULL;
+	GstCaps *result;
+
+	result = gst_sbc_util_caps_fixate(caps, &error_message);
+
+	if (!result) {
+		GST_WARNING_OBJECT(enc, "Invalid input caps caused parsing "
+				"error: %s", error_message);
+		g_free(error_message);
+		return NULL;
+	}
+
+	return result;
+}
+
+static GstCaps *sbc_enc_get_fixed_srcpad_caps(GstSbcEnc *enc)
+{
+	GstCaps *caps;
+	gboolean res = TRUE;
+	GstCaps *result_caps = NULL;
+
+	caps = gst_pad_get_allowed_caps(enc->srcpad);
+	if (caps == NULL)
+		caps = sbc_enc_src_getcaps(enc->srcpad);
+
+	if (caps == GST_CAPS_NONE || gst_caps_is_empty(caps)) {
+		res = FALSE;
+		goto done;
+	}
+
+	result_caps = sbc_enc_src_caps_fixate(enc, caps);
+
+done:
+	gst_caps_unref(caps);
+
+	if (!res)
+		return NULL;
+
+	return result_caps;
+}
+
+static gboolean sbc_enc_sink_setcaps(GstPad *pad, GstCaps *caps)
+{
+	GstSbcEnc *enc;
+	GstStructure *structure;
+	GstCaps *src_caps;
+	gint rate, channels;
+	gboolean res;
+
+	enc = GST_SBC_ENC(GST_PAD_PARENT(pad));
+	structure = gst_caps_get_structure(caps, 0);
+
+	if (!gst_structure_get_int(structure, "rate", &rate))
+		return FALSE;
+	if (!gst_structure_get_int(structure, "channels", &channels))
+		return FALSE;
+
+	enc->rate = rate;
+	enc->channels = channels;
+
+	src_caps = sbc_enc_get_fixed_srcpad_caps(enc);
+	if (!src_caps)
+		return FALSE;
+	res = gst_pad_set_caps(enc->srcpad, src_caps);
+	gst_caps_unref(src_caps);
+
+	return res;
+}
+
+gboolean gst_sbc_enc_fill_sbc_params(GstSbcEnc *enc, GstCaps *caps)
+{
+	if (!gst_caps_is_fixed(caps)) {
+		GST_DEBUG_OBJECT(enc, "didn't receive fixed caps, "
+				"returning false");
+		return FALSE;
+	}
+
+	if (!gst_sbc_util_fill_sbc_params(&enc->sbc, caps))
+		return FALSE;
+
+	if (enc->rate != 0 && gst_sbc_parse_rate_from_sbc(enc->sbc.frequency)
+				 != enc->rate)
+		goto fail;
+
+	if (enc->channels != 0 && gst_sbc_get_channel_number(enc->sbc.mode)
+				!= enc->channels)
+		goto fail;
+
+	if (enc->blocks != 0 && gst_sbc_parse_blocks_from_sbc(enc->sbc.blocks)
+				 != enc->blocks)
+		goto fail;
+
+	if (enc->subbands != 0 && gst_sbc_parse_subbands_from_sbc(
+				enc->sbc.subbands) != enc->subbands)
+		goto fail;
+
+	if (enc->mode != SBC_ENC_DEFAULT_MODE && enc->sbc.mode != enc->mode)
+		goto fail;
+
+	if (enc->allocation != SBC_AM_AUTO &&
+				enc->sbc.allocation != enc->allocation)
+		goto fail;
+
+	if (enc->bitpool != SBC_ENC_BITPOOL_AUTO &&
+				enc->sbc.bitpool != enc->bitpool)
+		goto fail;
+
+	enc->codesize = sbc_get_codesize(&enc->sbc);
+	enc->frame_length = sbc_get_frame_length(&enc->sbc);
+	enc->frame_duration = sbc_get_frame_duration(&enc->sbc);
+
+	GST_DEBUG_OBJECT(enc, "codesize: %d, frame_length: %d, frame_duration:"
+			" %d", enc->codesize, enc->frame_length,
+			enc->frame_duration);
+
+	return TRUE;
+
+fail:
+	memset(&enc->sbc, 0, sizeof(sbc_t));
+	return FALSE;
+}
+
+static GstFlowReturn sbc_enc_chain(GstPad *pad, GstBuffer *buffer)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(gst_pad_get_parent(pad));
+	GstAdapter *adapter = enc->adapter;
+	GstFlowReturn res = GST_FLOW_OK;
+
+	gst_adapter_push(adapter, buffer);
+
+	while (gst_adapter_available(adapter) >= enc->codesize &&
+							res == GST_FLOW_OK) {
+		GstBuffer *output;
+		GstCaps *caps;
+		const guint8 *data;
+		gint consumed;
+
+		caps = GST_PAD_CAPS(enc->srcpad);
+		res = gst_pad_alloc_buffer_and_set_caps(enc->srcpad,
+						GST_BUFFER_OFFSET_NONE,
+						enc->frame_length, caps,
+						&output);
+		if (res != GST_FLOW_OK)
+			goto done;
+
+		data = gst_adapter_peek(adapter, enc->codesize);
+
+		consumed = sbc_encode(&enc->sbc, (gpointer) data,
+					enc->codesize,
+					GST_BUFFER_DATA(output),
+					GST_BUFFER_SIZE(output), NULL);
+		if (consumed <= 0) {
+			GST_DEBUG_OBJECT(enc, "comsumed < 0, codesize: %d",
+					enc->codesize);
+			break;
+		}
+		gst_adapter_flush(adapter, consumed);
+
+		GST_BUFFER_TIMESTAMP(output) = GST_BUFFER_TIMESTAMP(buffer);
+		/* we have only 1 frame */
+		GST_BUFFER_DURATION(output) = enc->frame_duration;
+
+		res = gst_pad_push(enc->srcpad, output);
+
+		if (res != GST_FLOW_OK)
+			goto done;
+	}
+
+done:
+	gst_object_unref(enc);
+
+	return res;
+}
+
+static GstStateChangeReturn sbc_enc_change_state(GstElement *element,
+						GstStateChange transition)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(element);
+
+	switch (transition) {
+	case GST_STATE_CHANGE_READY_TO_PAUSED:
+		GST_DEBUG("Setup subband codec");
+		sbc_init(&enc->sbc, 0);
+		break;
+
+	case GST_STATE_CHANGE_PAUSED_TO_READY:
+		GST_DEBUG("Finish subband codec");
+		sbc_finish(&enc->sbc);
+		break;
+
+	default:
+		break;
+	}
+
+	return parent_class->change_state(element, transition);
+}
+
+static void gst_sbc_enc_dispose(GObject *object)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(object);
+
+	if (enc->adapter != NULL)
+		g_object_unref(G_OBJECT(enc->adapter));
+
+	enc->adapter = NULL;
+}
+
+static void gst_sbc_enc_base_init(gpointer g_class)
+{
+	GstElementClass *element_class = GST_ELEMENT_CLASS(g_class);
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&sbc_enc_sink_factory));
+
+	gst_element_class_add_pad_template(element_class,
+		gst_static_pad_template_get(&sbc_enc_src_factory));
+
+	gst_element_class_set_details(element_class, &sbc_enc_details);
+}
+
+static void gst_sbc_enc_set_property(GObject *object, guint prop_id,
+					const GValue *value, GParamSpec *pspec)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(object);
+
+	/* changes to those properties will only happen on the next caps
+	 * negotiation */
+
+	switch (prop_id) {
+	case PROP_MODE:
+		enc->mode = g_value_get_enum(value);
+		break;
+	case PROP_ALLOCATION:
+		enc->allocation = g_value_get_enum(value);
+		break;
+	case PROP_BLOCKS:
+		enc->blocks = g_value_get_enum(value);
+		break;
+	case PROP_SUBBANDS:
+		enc->subbands = g_value_get_enum(value);
+		break;
+	case PROP_BITPOOL:
+		enc->bitpool = g_value_get_int(value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_sbc_enc_get_property(GObject *object, guint prop_id,
+					GValue *value, GParamSpec *pspec)
+{
+	GstSbcEnc *enc = GST_SBC_ENC(object);
+
+	switch (prop_id) {
+	case PROP_MODE:
+		g_value_set_enum(value, enc->mode);
+		break;
+	case PROP_ALLOCATION:
+		g_value_set_enum(value, enc->allocation);
+		break;
+	case PROP_BLOCKS:
+		g_value_set_enum(value, enc->blocks);
+		break;
+	case PROP_SUBBANDS:
+		g_value_set_enum(value, enc->subbands);
+		break;
+	case PROP_BITPOOL:
+		g_value_set_int(value, enc->bitpool);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+		break;
+	}
+}
+
+static void gst_sbc_enc_class_init(GstSbcEncClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS(klass);
+	GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	object_class->set_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_set_property);
+	object_class->get_property = GST_DEBUG_FUNCPTR(gst_sbc_enc_get_property);
+	object_class->dispose = GST_DEBUG_FUNCPTR(gst_sbc_enc_dispose);
+
+	element_class->change_state = GST_DEBUG_FUNCPTR(sbc_enc_change_state);
+
+	g_object_class_install_property(object_class, PROP_MODE,
+			g_param_spec_enum("mode", "Mode",
+				"Encoding mode", GST_TYPE_SBC_MODE,
+				SBC_ENC_DEFAULT_MODE, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_ALLOCATION,
+			g_param_spec_enum("allocation", "Allocation",
+				"Allocation method", GST_TYPE_SBC_ALLOCATION,
+				SBC_ENC_DEFAULT_ALLOCATION, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_BLOCKS,
+			g_param_spec_enum("blocks", "Blocks",
+				"Blocks", GST_TYPE_SBC_BLOCKS,
+				SBC_ENC_DEFAULT_BLOCKS, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_SUBBANDS,
+			g_param_spec_enum("subbands", "Sub bands",
+				"Number of sub bands", GST_TYPE_SBC_SUBBANDS,
+				SBC_ENC_DEFAULT_SUB_BANDS, G_PARAM_READWRITE));
+
+	g_object_class_install_property(object_class, PROP_BITPOOL,
+			g_param_spec_int("bitpool", "Bitpool",
+				"Bitpool (use 1 for automatic selection)",
+				SBC_ENC_BITPOOL_AUTO, SBC_ENC_BITPOOL_MAX,
+				SBC_ENC_BITPOOL_AUTO, G_PARAM_READWRITE));
+
+	GST_DEBUG_CATEGORY_INIT(sbc_enc_debug, "sbcenc", 0,
+						"SBC encoding element");
+}
+
+static void gst_sbc_enc_init(GstSbcEnc *self, GstSbcEncClass *klass)
+{
+	self->sinkpad = gst_pad_new_from_static_template(
+		&sbc_enc_sink_factory, "sink");
+	gst_pad_set_setcaps_function(self->sinkpad,
+			GST_DEBUG_FUNCPTR(sbc_enc_sink_setcaps));
+	gst_element_add_pad(GST_ELEMENT(self), self->sinkpad);
+
+	self->srcpad = gst_pad_new_from_static_template(
+		&sbc_enc_src_factory, "src");
+	gst_pad_set_getcaps_function(self->srcpad,
+		GST_DEBUG_FUNCPTR(sbc_enc_src_getcaps));
+	gst_pad_set_setcaps_function(self->srcpad,
+		GST_DEBUG_FUNCPTR(sbc_enc_src_setcaps));
+	gst_element_add_pad(GST_ELEMENT(self), self->srcpad);
+
+	gst_pad_set_chain_function(self->sinkpad,
+		GST_DEBUG_FUNCPTR(sbc_enc_chain));
+
+	self->subbands = SBC_ENC_DEFAULT_SUB_BANDS;
+	self->blocks = SBC_ENC_DEFAULT_BLOCKS;
+	self->mode = SBC_ENC_DEFAULT_MODE;
+	self->allocation = SBC_ENC_DEFAULT_ALLOCATION;
+	self->rate = SBC_ENC_DEFAULT_RATE;
+	self->channels = SBC_ENC_DEFAULT_CHANNELS;
+	self->bitpool = SBC_ENC_BITPOOL_AUTO;
+
+	self->frame_length = 0;
+	self->frame_duration = 0;
+
+	self->adapter = gst_adapter_new();
+}
+
+gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin)
+{
+	return gst_element_register(plugin, "sbcenc",
+			GST_RANK_NONE, GST_TYPE_SBC_ENC);
+}
+
+
diff --git a/audio/gstsbcenc.h b/audio/gstsbcenc.h
new file mode 100644
index 0000000..6d69922
--- /dev/null
+++ b/audio/gstsbcenc.h
@@ -0,0 +1,75 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/base/gstadapter.h>
+
+#include "sbc.h"
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_SBC_ENC \
+	(gst_sbc_enc_get_type())
+#define GST_SBC_ENC(obj) \
+	(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SBC_ENC,GstSbcEnc))
+#define GST_SBC_ENC_CLASS(klass) \
+	(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SBC_ENC,GstSbcEncClass))
+#define GST_IS_SBC_ENC(obj) \
+	(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SBC_ENC))
+#define GST_IS_SBC_ENC_CLASS(obj) \
+	(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SBC_ENC))
+
+typedef struct _GstSbcEnc GstSbcEnc;
+typedef struct _GstSbcEncClass GstSbcEncClass;
+
+struct _GstSbcEnc {
+	GstElement element;
+
+	GstPad *sinkpad;
+	GstPad *srcpad;
+	GstAdapter *adapter;
+
+	gint rate;
+	gint channels;
+	gint mode;
+	gint blocks;
+	gint allocation;
+	gint subbands;
+	gint bitpool;
+
+	guint codesize;
+	gint frame_length;
+	gint frame_duration;
+
+	sbc_t sbc;
+};
+
+struct _GstSbcEncClass {
+	GstElementClass parent_class;
+};
+
+//GType gst_sbc_enc_get_type(void);
+
+gboolean gst_sbc_enc_plugin_init(GstPlugin *plugin);
+
+G_END_DECLS
diff --git a/audio/gstsbcparse.c b/audio/gstsbcparse.c
new file mode 100644
index 0000000..30d56ba
--- /dev/null
+++ b/audio/gstsbcparse.c
@@ -0,0 +1,219 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2009  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
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "gstsbcutil.h"
+#include "gstsbcparse.h"
+
+GST_DEBUG_CATEGORY_STATIC(sbc_parse_debug);
+#define GST_CAT_DEFAULT sbc_parse_debug
+
+GST_BOILERPLATE(GstSbcParse, gst_sbc_parse, GstElement, GST_TYPE_ELEMENT);
+
+static const GstElementDetails sbc_parse_details =
+	GST_ELEMENT_DETAILS("Bluetooth SBC parser",
+				"Codec/Parser/Audio",
+				"Parse a SBC audio stream",
+				"Marcel Holtmann <marcel@holtmann.org>");
+
+static GstStaticPadTemplate sbc_parse_sink_factory =
+	GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-sbc,"
+				"parsed = (boolean) false"));
+
+static GstStaticPadTemplate sbc_parse_src_factory =
+	GST_STATIC_PAD_TEMPLATE("src", GST_PAD_SRC, GST_PAD_ALWAYS,
+		GST_STATIC_CAPS("audio/x-sbc, "
+				"rate = (int) { 16000, 32000, 44100, 48000 }, "
+				"channels = (int) [ 1, 2 ], "
+				"mode = (string) { \"mono\", \"dual\", \"stereo\", \"joint\" }, "
+				"blocks = (int) { 4, 8, 12, 16 }, "
+				"subbands = (int) { 4, 8 }, "
+				"allocation = (string) { \"snr\", \"loudness\" },"
+				"bitpool = (int) [ 2, 64 ],"
+				"parsed = (boolean) true"));
+
+static GstFlowReturn sbc_parse_chain(GstPad *pad, GstBuffer *buffer)
+{
+	GstSbcParse *parse = GST_SBC_PARSE(gst_pad_get_parent(pad));
+	GstFlowReturn res = GST_FLOW_OK;
+	guint size, offset = 0;
+	guint8 *data;
+
+	/* FIXME use a gstadpter */
+	if (parse->buffer) {
+		GstBuffer *temp;
+		temp = buffer;
+		buffer = gst_buffer_span(parse->buffer, 0, buffer,
+			GST_BUFFER_SIZE(parse->buffer)
+			+ GST_BUFFER_SIZE(buffer));
+		gst_buffer_unref(parse->buffer);
+		gst_buffer_unref(temp);
+		parse->buffer = NULL;