mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-10-02 16:52:42 +00:00
Add apexsink for audio output to Apple AirPort Express Wireless devices. Fixes bug #542510.
Original commit message from CVS: Patch by: Jérémie Bernard <gremimail at gmail dot com> * configure.ac: * ext/apexsink/LGPL-3.0.txt: * ext/apexsink/Makefile.am: * ext/apexsink/gstapexplugin.c: (plugin_init): * ext/apexsink/gstapexraop.c: (g_strdel), (gst_apexraop_send), (gst_apexraop_recv), (gst_apexraop_new), (gst_apexraop_free), (gst_apexraop_set_host), (gst_apexraop_get_host), (gst_apexraop_set_port), (gst_apexraop_get_port), (gst_apexraop_set_useragent), (gst_apexraop_get_useragent), (gst_apexraop_connect), (gst_apexraop_get_jacktype), (gst_apexraop_get_jackstatus), (gst_apexraop_close), (gst_apexraop_set_volume), (gst_apexraop_write_bits), (gst_apexraop_write), (gst_apexraop_flush): * ext/apexsink/gstapexraop.h: * ext/apexsink/gstapexsink.c: (gst_apexsink_jackstatus_get_type), (gst_apexsink_jacktype_get_type), (gst_apexsink_interfaces_init), (gst_apexsink_implements_interface_init), (gst_apexsink_mixer_interface_init), (gst_apexsink_interface_supported), (gst_apexsink_mixer_list_tracks), (gst_apexsink_mixer_set_volume), (gst_apexsink_mixer_get_volume), (gst_apexsink_base_init), (gst_apexsink_class_init), (gst_apexsink_init), (gst_apexsink_set_property), (gst_apexsink_get_property), (gst_apexsink_finalise), (gst_apexsink_open), (gst_apexsink_prepare), (gst_apexsink_write), (gst_apexsink_unprepare), (gst_apexsink_delay), (gst_apexsink_reset), (gst_apexsink_close): * ext/apexsink/gstapexsink.h: Add apexsink for audio output to Apple AirPort Express Wireless devices. Fixes bug #542510.
This commit is contained in:
parent
53025584c3
commit
a72dc6992e
9 changed files with 1847 additions and 0 deletions
35
ChangeLog
35
ChangeLog
|
@ -1,3 +1,38 @@
|
||||||
|
2008-08-28 Sebastian Dröge <sebastian.droege@collabora.co.uk>
|
||||||
|
|
||||||
|
Patch by: Jérémie Bernard <gremimail at gmail dot com>
|
||||||
|
|
||||||
|
* configure.ac:
|
||||||
|
* ext/apexsink/LGPL-3.0.txt:
|
||||||
|
* ext/apexsink/Makefile.am:
|
||||||
|
* ext/apexsink/gstapexplugin.c: (plugin_init):
|
||||||
|
* ext/apexsink/gstapexraop.c: (g_strdel), (gst_apexraop_send),
|
||||||
|
(gst_apexraop_recv), (gst_apexraop_new), (gst_apexraop_free),
|
||||||
|
(gst_apexraop_set_host), (gst_apexraop_get_host),
|
||||||
|
(gst_apexraop_set_port), (gst_apexraop_get_port),
|
||||||
|
(gst_apexraop_set_useragent), (gst_apexraop_get_useragent),
|
||||||
|
(gst_apexraop_connect), (gst_apexraop_get_jacktype),
|
||||||
|
(gst_apexraop_get_jackstatus), (gst_apexraop_close),
|
||||||
|
(gst_apexraop_set_volume), (gst_apexraop_write_bits),
|
||||||
|
(gst_apexraop_write), (gst_apexraop_flush):
|
||||||
|
* ext/apexsink/gstapexraop.h:
|
||||||
|
* ext/apexsink/gstapexsink.c: (gst_apexsink_jackstatus_get_type),
|
||||||
|
(gst_apexsink_jacktype_get_type), (gst_apexsink_interfaces_init),
|
||||||
|
(gst_apexsink_implements_interface_init),
|
||||||
|
(gst_apexsink_mixer_interface_init),
|
||||||
|
(gst_apexsink_interface_supported),
|
||||||
|
(gst_apexsink_mixer_list_tracks), (gst_apexsink_mixer_set_volume),
|
||||||
|
(gst_apexsink_mixer_get_volume), (gst_apexsink_base_init),
|
||||||
|
(gst_apexsink_class_init), (gst_apexsink_init),
|
||||||
|
(gst_apexsink_set_property), (gst_apexsink_get_property),
|
||||||
|
(gst_apexsink_finalise), (gst_apexsink_open),
|
||||||
|
(gst_apexsink_prepare), (gst_apexsink_write),
|
||||||
|
(gst_apexsink_unprepare), (gst_apexsink_delay),
|
||||||
|
(gst_apexsink_reset), (gst_apexsink_close):
|
||||||
|
* ext/apexsink/gstapexsink.h:
|
||||||
|
Add apexsink for audio output to Apple AirPort Express Wireless
|
||||||
|
devices. Fixes bug #542510.
|
||||||
|
|
||||||
2008-08-28 Wim Taymans <wim.taymans@collabora.co.uk>
|
2008-08-28 Wim Taymans <wim.taymans@collabora.co.uk>
|
||||||
|
|
||||||
* gst/rtpmanager/gstrtpsession.c: (gst_rtp_session_send_rtcp),
|
* gst/rtpmanager/gstrtpsession.c: (gst_rtp_session_send_rtcp),
|
||||||
|
|
125
configure.ac
125
configure.ac
|
@ -346,6 +346,129 @@ AG_GST_CHECK_FEATURE(AMRWB, [amrwb library], amrwb, [
|
||||||
AC_SUBST(AMRWB_LIBS))
|
AC_SUBST(AMRWB_LIBS))
|
||||||
])
|
])
|
||||||
|
|
||||||
|
dnl *** apexsink ***
|
||||||
|
translit(dnm, m, l) AM_CONDITIONAL(USE_APEXSINK, true)
|
||||||
|
AG_GST_CHECK_FEATURE(APEXSINK, [AirPort Express Wireless sink], apexsink, [
|
||||||
|
|
||||||
|
HAVE_APEXSINK="yes"
|
||||||
|
# The big search for OpenSSL
|
||||||
|
# copied from openssh's configure.ac
|
||||||
|
AC_ARG_WITH(ssl-dir,
|
||||||
|
[ --with-ssl-dir=PATH Specify path to OpenSSL installation ],
|
||||||
|
[
|
||||||
|
if test "x$withval" != "xno" ; then
|
||||||
|
tryssldir=$withval
|
||||||
|
fi
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
AC_SEARCH_LIBS(socket, [socket])
|
||||||
|
AC_SEARCH_LIBS(gethostbyname, [nsl])
|
||||||
|
|
||||||
|
saved_LIBS="$LIBS"
|
||||||
|
saved_LDFLAGS="$LDFLAGS"
|
||||||
|
saved_CPPFLAGS="$CPPFLAGS"
|
||||||
|
if test "x$prefix" != "xNONE" ; then
|
||||||
|
tryssldir="$tryssldir $prefix"
|
||||||
|
fi
|
||||||
|
AC_CACHE_CHECK([for OpenSSL directory], ac_cv_openssldir, [
|
||||||
|
for ssldir in $tryssldir "" /usr/local/openssl /usr/lib/openssl /usr/local/ssl /usr/lib/ssl /usr/local /usr/athena /usr/pkg /opt /opt/openssl ; do
|
||||||
|
CPPFLAGS="$saved_CPPFLAGS"
|
||||||
|
LDFLAGS="$saved_LDFLAGS"
|
||||||
|
LIBS="$saved_LIBS -lssl -lcrypto"
|
||||||
|
|
||||||
|
# Skip directories if they don't exist
|
||||||
|
if test ! -z "$ssldir" -a ! -d "$ssldir" ; then
|
||||||
|
continue;
|
||||||
|
fi
|
||||||
|
if test ! -z "$ssldir" -a "x$ssldir" != "x/usr"; then
|
||||||
|
# Try to use $ssldir/lib if it exists, otherwise
|
||||||
|
# $ssldir
|
||||||
|
if test -d "$ssldir/lib" ; then
|
||||||
|
LDFLAGS="-L$ssldir/lib $saved_LDFLAGS"
|
||||||
|
if test ! -z "$need_dash_r" ; then
|
||||||
|
LDFLAGS="-R$ssldir/lib $LDFLAGS"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LDFLAGS="-L$ssldir $saved_LDFLAGS"
|
||||||
|
if test ! -z "$need_dash_r" ; then
|
||||||
|
LDFLAGS="-R$ssldir $LDFLAGS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Try to use $ssldir/include if it exists, otherwise
|
||||||
|
# $ssldir
|
||||||
|
if test -d "$ssldir/include" ; then
|
||||||
|
CPPFLAGS="-I$ssldir/include $saved_CPPFLAGS"
|
||||||
|
else
|
||||||
|
CPPFLAGS="-I$ssldir $saved_CPPFLAGS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Basic test to check for compatible version and correct linking
|
||||||
|
# *does not* test for RSA - that comes later.
|
||||||
|
AC_TRY_RUN(
|
||||||
|
[
|
||||||
|
#include <string.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
char a[2048];
|
||||||
|
memset(a, 0, sizeof(a));
|
||||||
|
RAND_add(a, sizeof(a), sizeof(a));
|
||||||
|
return(RAND_status() <= 0);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
found_crypto=1
|
||||||
|
break;
|
||||||
|
], []
|
||||||
|
)
|
||||||
|
|
||||||
|
if test ! -z "$found_crypto" ; then
|
||||||
|
break;
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if test -z "$found_crypto" ; then
|
||||||
|
HAVE_APEXSINK="no"
|
||||||
|
fi
|
||||||
|
if test -z "$ssldir" ; then
|
||||||
|
ssldir="(system)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ac_cv_openssldir=$ssldir
|
||||||
|
])
|
||||||
|
if (test ! -z "$ac_cv_openssldir" && test "x$ac_cv_openssldir" != "x(system)") ;
|
||||||
|
then
|
||||||
|
dnl Need to recover ssldir - test above runs in subshell
|
||||||
|
ssldir=$ac_cv_openssldir
|
||||||
|
if test ! -z "$ssldir" -a "x$ssldir" != "x/usr"; then
|
||||||
|
# Try to use $ssldir/lib if it exists, otherwise
|
||||||
|
# $ssldir
|
||||||
|
if test -d "$ssldir/lib" ; then
|
||||||
|
APEXSINK_LIBS="-L$ssldir/lib $saved_LDFLAGS"
|
||||||
|
if test ! -z "$need_dash_r" ; then
|
||||||
|
APEXSINK_LIBS="-R$ssldir/lib $LDFLAGS"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
APEXSINK_LDFLAGS="-L$ssldir $saved_LDFLAGS"
|
||||||
|
if test ! -z "$need_dash_r" ; then
|
||||||
|
APEXSINK_LIBS="-R$ssldir $LDFLAGS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Try to use $ssldir/include if it exists, otherwise
|
||||||
|
# $ssldir
|
||||||
|
if test -d "$ssldir/include" ; then
|
||||||
|
APEXSINK_CFLAGS="-I$ssldir/include $saved_CPPFLAGS"
|
||||||
|
else
|
||||||
|
APEXSINK_CFLAGS="-I$ssldir $saved_CPPFLAGS"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
APEXSINK_LIBS="$APEXSINK_LIBS $LIBS"
|
||||||
|
LIBS="$saved_LIBS"
|
||||||
|
])
|
||||||
|
|
||||||
dnl *** BZ2 ***
|
dnl *** BZ2 ***
|
||||||
translit(dnm, m, l) AM_CONDITIONAL(USE_BZ2, true)
|
translit(dnm, m, l) AM_CONDITIONAL(USE_BZ2, true)
|
||||||
AG_GST_CHECK_FEATURE(BZ2, [bz2 library], bz2, [
|
AG_GST_CHECK_FEATURE(BZ2, [bz2 library], bz2, [
|
||||||
|
@ -1146,6 +1269,7 @@ AM_CONDITIONAL(USE_QUICKTIME, false)
|
||||||
AM_CONDITIONAL(USE_VCD, false)
|
AM_CONDITIONAL(USE_VCD, false)
|
||||||
AM_CONDITIONAL(USE_ALSA, false)
|
AM_CONDITIONAL(USE_ALSA, false)
|
||||||
AM_CONDITIONAL(USE_AMRWB, false)
|
AM_CONDITIONAL(USE_AMRWB, false)
|
||||||
|
AM_CONDITIONAL(USE_APEXSINK, false)
|
||||||
AM_CONDITIONAL(USE_BZ2, false)
|
AM_CONDITIONAL(USE_BZ2, false)
|
||||||
AM_CONDITIONAL(USE_CDAUDIO, false)
|
AM_CONDITIONAL(USE_CDAUDIO, false)
|
||||||
AM_CONDITIONAL(USE_CELT, false)
|
AM_CONDITIONAL(USE_CELT, false)
|
||||||
|
@ -1308,6 +1432,7 @@ examples/directfb/Makefile
|
||||||
examples/switch/Makefile
|
examples/switch/Makefile
|
||||||
ext/amrwb/Makefile
|
ext/amrwb/Makefile
|
||||||
ext/alsaspdif/Makefile
|
ext/alsaspdif/Makefile
|
||||||
|
ext/apexsink/Makefile
|
||||||
ext/bz2/Makefile
|
ext/bz2/Makefile
|
||||||
ext/cdaudio/Makefile
|
ext/cdaudio/Makefile
|
||||||
ext/celt/Makefile
|
ext/celt/Makefile
|
||||||
|
|
165
ext/apexsink/LGPL-3.0.txt
Normal file
165
ext/apexsink/LGPL-3.0.txt
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
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 that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU 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 as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
8
ext/apexsink/Makefile.am
Normal file
8
ext/apexsink/Makefile.am
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
plugin_LTLIBRARIES = libgstapexsink.la
|
||||||
|
|
||||||
|
libgstapexsink_la_SOURCES = gstapexplugin.c gstapexraop.c gstapexsink.c
|
||||||
|
libgstapexsink_la_CFLAGS = $(APEXSINK_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS)
|
||||||
|
libgstapexsink_la_LIBADD = $(APEXSINK_LIBS) $(GST_PLUGINS_BASE_LIBS) $(GST_LIBS) -lgstaudio-$(GST_MAJORMINOR) -lgstinterfaces-$(GST_MAJORMINOR)
|
||||||
|
libgstapexsink_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||||
|
|
||||||
|
noinst_HEADERS = gstapexraop.h gstapexsink.h
|
44
ext/apexsink/gstapexplugin.c
Normal file
44
ext/apexsink/gstapexplugin.c
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/* GStreamer AirPort Express Plugin
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
|
||||||
|
*
|
||||||
|
* gstapexpugin.c
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gstapexsink.h>
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
plugin_init (GstPlugin * plugin)
|
||||||
|
{
|
||||||
|
return gst_element_register (plugin, GST_APEX_SINK_NAME, GST_RANK_PRIMARY,
|
||||||
|
GST_TYPE_APEX_SINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* plugin export resolution */
|
||||||
|
GST_PLUGIN_DEFINE
|
||||||
|
(GST_VERSION_MAJOR,
|
||||||
|
GST_VERSION_MINOR,
|
||||||
|
"apex",
|
||||||
|
"Apple AirPort Express Plugin",
|
||||||
|
plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|
690
ext/apexsink/gstapexraop.c
Normal file
690
ext/apexsink/gstapexraop.c
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
/* GStreamer - Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
|
||||||
|
*
|
||||||
|
* RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
|
||||||
|
* This interface accepts RAW PCM data and set it as AES encrypted ALAC while performing emission.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
|
||||||
|
*
|
||||||
|
* gstapexraop.c
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "gstapexraop.h"
|
||||||
|
|
||||||
|
/* private constants */
|
||||||
|
#define GST_APEX_RAOP_VOLUME_MIN -144
|
||||||
|
#define GST_APEX_RAOP_VOLUME_MAX 0
|
||||||
|
|
||||||
|
#define GST_APEX_RAOP_HDR_DEFAULT_LENGTH 1024
|
||||||
|
#define GST_APEX_RAOP_SDP_DEFAULT_LENGTH 2048
|
||||||
|
|
||||||
|
const static gchar GST_APEX_RAOP_RSA_PUBLIC_MOD[] =
|
||||||
|
"59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
|
||||||
|
"5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
|
||||||
|
"KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
|
||||||
|
"OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
|
||||||
|
"Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
|
||||||
|
"imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
|
||||||
|
|
||||||
|
const static gchar GST_APEX_RAOP_RSA_PUBLIC_EXP[] = "AQAB";
|
||||||
|
|
||||||
|
const static gchar GST_APEX_RAOP_USER_AGENT[] =
|
||||||
|
"iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)";
|
||||||
|
|
||||||
|
const static guchar GST_APEX_RAOP_FRAME_HEADER[] = {
|
||||||
|
0x24, 0x00, 0x00, 0x00,
|
||||||
|
0xF0, 0xFF, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16;
|
||||||
|
|
||||||
|
const static int GST_APEX_RAOP_ALAC_HEADER_SIZE = 3;
|
||||||
|
|
||||||
|
/* string extra utility */
|
||||||
|
static gint
|
||||||
|
g_strdel (gchar * str, gchar rc)
|
||||||
|
{
|
||||||
|
int i = 0, j = 0, len, num = 0;
|
||||||
|
len = strlen (str);
|
||||||
|
while (i < len) {
|
||||||
|
if (str[i] == rc) {
|
||||||
|
for (j = i; j < len; j++)
|
||||||
|
str[j] = str[j + 1];
|
||||||
|
len--;
|
||||||
|
num++;
|
||||||
|
} else {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* socket utilities */
|
||||||
|
static int
|
||||||
|
gst_apexraop_send (int desc, void *data, size_t len)
|
||||||
|
{
|
||||||
|
int total = 0, bytesleft = len, n = 0;
|
||||||
|
|
||||||
|
while (total < len) {
|
||||||
|
n = send (desc, ((const char *) data) + total, bytesleft, 0);
|
||||||
|
if (n == -1)
|
||||||
|
break;
|
||||||
|
total += n;
|
||||||
|
bytesleft -= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n == -1 ? -1 : total;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
gst_apexraop_recv (int desc, void *data, size_t len)
|
||||||
|
{
|
||||||
|
bzero (data, len);
|
||||||
|
return recv (desc, data, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public opaque handle resolution */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
guchar aes_ky[AES_BLOCK_SIZE]; /* AES random key */
|
||||||
|
guchar aes_iv[AES_BLOCK_SIZE]; /* AES random initial vector */
|
||||||
|
|
||||||
|
guchar url_abspath[16]; /* header url random absolute path addon, ANNOUNCE id */
|
||||||
|
gint cseq; /* header rtsp inc cseq */
|
||||||
|
guchar cid[24]; /* header client instance id */
|
||||||
|
gchar *session; /* header raop negotiated session id, once SETUP performed */
|
||||||
|
gchar *ua; /* header user agent */
|
||||||
|
|
||||||
|
GstApExJackType jack_type; /* APEX connected jack type, once ANNOUNCE performed */
|
||||||
|
GstApExJackStatus jack_status; /* APEX connected jack status, once ANNOUNCE performed */
|
||||||
|
|
||||||
|
gchar *host; /* APEX target ip */
|
||||||
|
guint ctrl_port; /* APEX target control port */
|
||||||
|
guint data_port; /* APEX negotiated data port, once SETUP performed */
|
||||||
|
|
||||||
|
int ctrl_sd; /* control socket */
|
||||||
|
struct sockaddr_in ctrl_sd_in;
|
||||||
|
|
||||||
|
int data_sd; /* data socket */
|
||||||
|
struct sockaddr_in data_sd_in;
|
||||||
|
}
|
||||||
|
_GstApExRAOP;
|
||||||
|
|
||||||
|
/* raop apex struct allocation */
|
||||||
|
GstApExRAOP *
|
||||||
|
gst_apexraop_new (const gchar * host, const guint16 port)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *apexraop;
|
||||||
|
|
||||||
|
apexraop = (_GstApExRAOP *) g_malloc0 (sizeof (_GstApExRAOP));
|
||||||
|
|
||||||
|
apexraop->host = g_strdup (host);
|
||||||
|
apexraop->ctrl_port = port;
|
||||||
|
apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT);
|
||||||
|
apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
|
||||||
|
apexraop->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
|
||||||
|
|
||||||
|
return (GstApExRAOP *) apexraop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex struct freeing */
|
||||||
|
void
|
||||||
|
gst_apexraop_free (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
g_free (conn->host);
|
||||||
|
g_free (conn->session);
|
||||||
|
g_free (conn->ua);
|
||||||
|
g_free (conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* host affectation */
|
||||||
|
void
|
||||||
|
gst_apexraop_set_host (GstApExRAOP * con, const gchar * host)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
g_free (conn->host);
|
||||||
|
conn->host = g_strdup (host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* host reader */
|
||||||
|
gchar *
|
||||||
|
gst_apexraop_get_host (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
return g_strdup (conn->host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control port affectation */
|
||||||
|
void
|
||||||
|
gst_apexraop_set_port (GstApExRAOP * con, const guint16 port)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
conn->ctrl_port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* control port reader */
|
||||||
|
guint16
|
||||||
|
gst_apexraop_get_port (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
return conn->ctrl_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user agent affectation */
|
||||||
|
void
|
||||||
|
gst_apexraop_set_useragent (GstApExRAOP * con, const gchar * useragent)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
g_free (conn->ua);
|
||||||
|
conn->ua = g_strdup (useragent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* user agent reader */
|
||||||
|
gchar *
|
||||||
|
gst_apexraop_get_useragent (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
return g_strdup (conn->ua);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex connection sequence */
|
||||||
|
GstRTSPStatusCode
|
||||||
|
gst_apexraop_connect (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
gchar *ac, *ky, *iv, *s, inaddr[INET_ADDRSTRLEN],
|
||||||
|
creq[GST_APEX_RAOP_SDP_DEFAULT_LENGTH],
|
||||||
|
hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH], *req;
|
||||||
|
RSA *rsa;
|
||||||
|
guchar *mod, *exp, buf[4 + 8 + 16], rsakey[512];
|
||||||
|
gsize size;
|
||||||
|
struct sockaddr_in ioaddr;
|
||||||
|
socklen_t iolen;
|
||||||
|
GstRTSPStatusCode res;
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
if ((conn->ctrl_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
|
||||||
|
conn->ctrl_sd_in.sin_family = AF_INET;
|
||||||
|
conn->ctrl_sd_in.sin_port = htons (conn->ctrl_port);
|
||||||
|
|
||||||
|
if (!inet_aton (conn->host, &conn->ctrl_sd_in.sin_addr)) {
|
||||||
|
struct hostent *hp = (struct hostent *) gethostbyname (conn->host);
|
||||||
|
if (hp == NULL)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
memcpy (&conn->ctrl_sd_in.sin_addr, hp->h_addr, hp->h_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect (conn->ctrl_sd, (struct sockaddr *) &conn->ctrl_sd_in,
|
||||||
|
sizeof (conn->ctrl_sd_in)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
|
||||||
|
RAND_bytes (buf, sizeof (buf));
|
||||||
|
sprintf ((gchar *) conn->url_abspath, "%lu", *((gulong *) buf));
|
||||||
|
ac = g_base64_encode (buf + 12, 16);
|
||||||
|
g_strdel (ac, '=');
|
||||||
|
sprintf ((char *) conn->cid, "%08lx%08lx", *((gulong *) (buf + 4)),
|
||||||
|
*((gulong *) (buf + 8)));
|
||||||
|
|
||||||
|
RAND_bytes (conn->aes_ky, AES_BLOCK_SIZE);
|
||||||
|
RAND_bytes (conn->aes_iv, AES_BLOCK_SIZE);
|
||||||
|
|
||||||
|
rsa = RSA_new ();
|
||||||
|
mod = g_base64_decode (GST_APEX_RAOP_RSA_PUBLIC_MOD, &size);
|
||||||
|
rsa->n = BN_bin2bn (mod, size, NULL);
|
||||||
|
exp = g_base64_decode (GST_APEX_RAOP_RSA_PUBLIC_EXP, &size);
|
||||||
|
rsa->e = BN_bin2bn (exp, size, NULL);
|
||||||
|
size =
|
||||||
|
RSA_public_encrypt (AES_BLOCK_SIZE, conn->aes_ky, rsakey, rsa,
|
||||||
|
RSA_PKCS1_OAEP_PADDING);
|
||||||
|
|
||||||
|
ky = g_base64_encode (rsakey, size);
|
||||||
|
iv = g_base64_encode (conn->aes_iv, AES_BLOCK_SIZE);
|
||||||
|
g_strdel (ky, '=');
|
||||||
|
g_strdel (iv, '=');
|
||||||
|
|
||||||
|
iolen = sizeof (struct sockaddr);
|
||||||
|
getsockname (conn->ctrl_sd, (struct sockaddr *) &ioaddr, &iolen);
|
||||||
|
inet_ntop (AF_INET, &(ioaddr.sin_addr), inaddr, INET_ADDRSTRLEN);
|
||||||
|
|
||||||
|
sprintf (creq,
|
||||||
|
"v=0\r\n"
|
||||||
|
"o=iTunes %s 0 IN IP4 %s\r\n"
|
||||||
|
"s=iTunes\r\n"
|
||||||
|
"c=IN IP4 %s\r\n"
|
||||||
|
"t=0 0\r\n"
|
||||||
|
"m=audio 0 RTP/AVP 96\r\n"
|
||||||
|
"a=rtpmap:96 AppleLossless\r\n"
|
||||||
|
"a=fmtp:96 %d 0 %d 40 10 14 %d 255 0 0 %d\r\n"
|
||||||
|
"a=rsaaeskey:%s\r\n"
|
||||||
|
"a=aesiv:%s\r\n",
|
||||||
|
conn->url_abspath,
|
||||||
|
inaddr,
|
||||||
|
conn->host,
|
||||||
|
GST_APEX_RAOP_SAMPLES_PER_FRAME,
|
||||||
|
GST_APEX_RAOP_BYTES_PER_CHANNEL * 8,
|
||||||
|
GST_APEX_RAOP_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv);
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"ANNOUNCE rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Content-Type: application/sdp\r\n"
|
||||||
|
"Content-Length: %d\r\n"
|
||||||
|
"Apple-Challenge: %s\r\n",
|
||||||
|
conn->host,
|
||||||
|
conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, strlen (creq), ac);
|
||||||
|
|
||||||
|
RSA_free (rsa);
|
||||||
|
g_free (ky);
|
||||||
|
g_free (iv);
|
||||||
|
g_free (ac);
|
||||||
|
g_free (mod);
|
||||||
|
g_free (exp);
|
||||||
|
|
||||||
|
req = g_strconcat (hreq, "\r\n", creq, NULL);
|
||||||
|
|
||||||
|
if (gst_apexraop_send (conn->ctrl_sd, req, strlen (req)) <= 0) {
|
||||||
|
g_free (req);
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (req);
|
||||||
|
|
||||||
|
if (gst_apexraop_recv (conn->ctrl_sd, hreq,
|
||||||
|
GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
sscanf (hreq, "%*s %d", (int *) &res);
|
||||||
|
|
||||||
|
if (res != GST_RTSP_STS_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
s = g_strrstr (hreq, "Audio-Jack-Status");
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
|
gchar status[128];
|
||||||
|
sscanf (s, "%*s %s", status);
|
||||||
|
|
||||||
|
if (strcmp (status, "connected;") == 0)
|
||||||
|
conn->jack_status = GST_APEX_JACK_STATUS_CONNECTED;
|
||||||
|
else if (strcmp (status, "disconnected;") == 0)
|
||||||
|
conn->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
|
||||||
|
else
|
||||||
|
conn->jack_status = GST_APEX_JACK_STATUS_UNDEFINED;
|
||||||
|
|
||||||
|
s = g_strrstr (s, "type=");
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
|
strtok (s, "=");
|
||||||
|
s = strtok (NULL, "\n");
|
||||||
|
|
||||||
|
if (strcmp (s, "analog"))
|
||||||
|
conn->jack_type = GST_APEX_JACK_TYPE_ANALOG;
|
||||||
|
else if (strcmp (s, "digital"))
|
||||||
|
conn->jack_type = GST_APEX_JACK_TYPE_DIGITAL;
|
||||||
|
else
|
||||||
|
conn->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"SETUP rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Transport: RTP/AVP/TCP;unicast;interleaved=0-1;mode=record\r\n"
|
||||||
|
"\r\n", conn->host, conn->url_abspath, ++conn->cseq, conn->cid, conn->ua);
|
||||||
|
|
||||||
|
if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
if (gst_apexraop_recv (conn->ctrl_sd, hreq,
|
||||||
|
GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
sscanf (hreq, "%*s %d", (int *) &res);
|
||||||
|
|
||||||
|
if (res != GST_RTSP_STS_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
s = g_strrstr (hreq, "Session");
|
||||||
|
|
||||||
|
if (s != NULL) {
|
||||||
|
gchar session[128];
|
||||||
|
sscanf (s, "%*s %s", session);
|
||||||
|
conn->session = g_strdup (session);
|
||||||
|
} else
|
||||||
|
return GST_RTSP_STS_PRECONDITION_FAILED;
|
||||||
|
|
||||||
|
s = g_strrstr (hreq, "server_port");
|
||||||
|
if (s != NULL) {
|
||||||
|
sscanf (s, "server_port=%d", &conn->data_port);
|
||||||
|
} else
|
||||||
|
return GST_RTSP_STS_PRECONDITION_FAILED;
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"RECORD rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Session: %s\r\n"
|
||||||
|
"Range: npt=0-\r\n"
|
||||||
|
"RTP-Info: seq=0;rtptime=0\r\n"
|
||||||
|
"\r\n",
|
||||||
|
conn->host,
|
||||||
|
conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
|
||||||
|
|
||||||
|
if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
if (gst_apexraop_recv (conn->ctrl_sd, hreq,
|
||||||
|
GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
sscanf (hreq, "%*s %d", (int *) &res);
|
||||||
|
|
||||||
|
if (res != GST_RTSP_STS_OK)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
|
||||||
|
conn->data_sd_in.sin_family = AF_INET;
|
||||||
|
conn->data_sd_in.sin_port = htons (conn->data_port);
|
||||||
|
|
||||||
|
memcpy (&conn->data_sd_in.sin_addr, &conn->ctrl_sd_in.sin_addr,
|
||||||
|
sizeof (conn->ctrl_sd_in.sin_addr));
|
||||||
|
|
||||||
|
if (connect (conn->data_sd, (struct sockaddr *) &conn->data_sd_in,
|
||||||
|
sizeof (conn->data_sd_in)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex jack type access */
|
||||||
|
GstApExJackType
|
||||||
|
gst_apexraop_get_jacktype (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
return GST_APEX_JACK_TYPE_UNDEFINED;
|
||||||
|
|
||||||
|
return conn->jack_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex jack status access */
|
||||||
|
GstApExJackStatus
|
||||||
|
gst_apexraop_get_jackstatus (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
return GST_APEX_JACK_STATUS_UNDEFINED;
|
||||||
|
|
||||||
|
return conn->jack_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex sockets close */
|
||||||
|
void
|
||||||
|
gst_apexraop_close (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
gchar hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH];
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"TEARDOWN rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Session: %s\r\n"
|
||||||
|
"\r\n",
|
||||||
|
conn->host,
|
||||||
|
conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
|
||||||
|
|
||||||
|
gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq));
|
||||||
|
gst_apexraop_recv (conn->ctrl_sd, hreq, GST_APEX_RAOP_HDR_DEFAULT_LENGTH);
|
||||||
|
|
||||||
|
if (conn->ctrl_sd != 0)
|
||||||
|
close (conn->ctrl_sd);
|
||||||
|
if (conn->data_sd != 0)
|
||||||
|
close (conn->data_sd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex volume set */
|
||||||
|
GstRTSPStatusCode
|
||||||
|
gst_apexraop_set_volume (GstApExRAOP * con, const guint volume)
|
||||||
|
{
|
||||||
|
gint v;
|
||||||
|
gchar creq[GST_APEX_RAOP_SDP_DEFAULT_LENGTH],
|
||||||
|
hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH], *req, vol[128];
|
||||||
|
GstRTSPStatusCode res;
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
v = GST_APEX_RAOP_VOLUME_MIN + (GST_APEX_RAOP_VOLUME_MAX -
|
||||||
|
GST_APEX_RAOP_VOLUME_MIN) * volume / 100.;
|
||||||
|
sprintf (vol, "volume: %d.000000\r\n", v);
|
||||||
|
|
||||||
|
sprintf (creq, "%s\r\n", vol);
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"SET_PARAMETER rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Session: %s\r\n"
|
||||||
|
"Content-Type: text/parameters\r\n"
|
||||||
|
"Content-Length: %d\r\n",
|
||||||
|
conn->host,
|
||||||
|
conn->url_abspath,
|
||||||
|
++conn->cseq, conn->cid, conn->ua, conn->session, strlen (creq)
|
||||||
|
);
|
||||||
|
|
||||||
|
req = g_strconcat (hreq, "\r\n", creq, NULL);
|
||||||
|
|
||||||
|
if (gst_apexraop_send (conn->ctrl_sd, req, strlen (req)) <= 0) {
|
||||||
|
g_free (req);
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (req);
|
||||||
|
|
||||||
|
if (gst_apexraop_recv (conn->ctrl_sd, hreq,
|
||||||
|
GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
sscanf (hreq, "%*s %d", (int *) &res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex raw data alac encapsulation, encryption and emission, http://wiki.multimedia.cx/index.php?title=Apple_Lossless_Audio_Coding */
|
||||||
|
static void inline
|
||||||
|
gst_apexraop_write_bits (guchar * buffer, int data, int numbits,
|
||||||
|
int *bit_offset, int *byte_offset)
|
||||||
|
{
|
||||||
|
const static guchar masks[] =
|
||||||
|
{ 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };
|
||||||
|
|
||||||
|
if (((*bit_offset) != 0) && (((*bit_offset) + numbits) > 8)) {
|
||||||
|
gint numwritebits;
|
||||||
|
guchar bitstowrite;
|
||||||
|
|
||||||
|
numwritebits = 8 - (*bit_offset);
|
||||||
|
bitstowrite =
|
||||||
|
(guchar) ((data >> (numbits - numwritebits)) << (8 - (*bit_offset) -
|
||||||
|
numwritebits));
|
||||||
|
buffer[(*byte_offset)] |= bitstowrite;
|
||||||
|
numbits -= numwritebits;
|
||||||
|
(*bit_offset) = 0;
|
||||||
|
(*byte_offset)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (numbits >= 8) {
|
||||||
|
guchar bitstowrite;
|
||||||
|
|
||||||
|
bitstowrite = (guchar) ((data >> (numbits - 8)) & 0xFF);
|
||||||
|
buffer[(*byte_offset)] |= bitstowrite;
|
||||||
|
numbits -= 8;
|
||||||
|
(*bit_offset) = 0;
|
||||||
|
(*byte_offset)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numbits > 0) {
|
||||||
|
guchar bitstowrite;
|
||||||
|
bitstowrite =
|
||||||
|
(guchar) ((data & masks[numbits]) << (8 - (*bit_offset) - numbits));
|
||||||
|
buffer[(*byte_offset)] |= bitstowrite;
|
||||||
|
(*bit_offset) += numbits;
|
||||||
|
if ((*bit_offset) == 8) {
|
||||||
|
(*byte_offset)++;
|
||||||
|
(*bit_offset) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guint
|
||||||
|
gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
|
||||||
|
{
|
||||||
|
guchar *buffer, *frame_data;
|
||||||
|
gushort len;
|
||||||
|
gint bit_offset, byte_offset, i, out_len, res;
|
||||||
|
EVP_CIPHER_CTX aes_ctx;
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
buffer =
|
||||||
|
(guchar *) g_malloc0 (GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||||
|
GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
|
||||||
|
|
||||||
|
memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, GST_APEX_RAOP_FRAME_HEADER_SIZE);
|
||||||
|
|
||||||
|
len =
|
||||||
|
length + GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||||
|
GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
|
||||||
|
buffer[2] = len >> 8;
|
||||||
|
buffer[3] = len & 0xff;
|
||||||
|
|
||||||
|
bit_offset = 0;
|
||||||
|
byte_offset = 0;
|
||||||
|
frame_data = buffer + GST_APEX_RAOP_FRAME_HEADER_SIZE;
|
||||||
|
|
||||||
|
gst_apexraop_write_bits (frame_data, 1, 3, &bit_offset, &byte_offset); /* channels, 0 mono, 1 stereo */
|
||||||
|
gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset); /* unknown */
|
||||||
|
gst_apexraop_write_bits (frame_data, 0, 8, &bit_offset, &byte_offset); /* unknown (12 bits) */
|
||||||
|
gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset);
|
||||||
|
gst_apexraop_write_bits (frame_data, 0, 1, &bit_offset, &byte_offset); /* has size flag */
|
||||||
|
gst_apexraop_write_bits (frame_data, 0, 2, &bit_offset, &byte_offset); /* unknown */
|
||||||
|
gst_apexraop_write_bits (frame_data, 1, 1, &bit_offset, &byte_offset); /* no compression flag */
|
||||||
|
|
||||||
|
for (i = 0; i < length; i += 2) {
|
||||||
|
gst_apexraop_write_bits (frame_data, ((guchar *) rawdata)[i + 1], 8,
|
||||||
|
&bit_offset, &byte_offset);
|
||||||
|
gst_apexraop_write_bits (frame_data, ((guchar *) rawdata)[i], 8,
|
||||||
|
&bit_offset, &byte_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX_init (&aes_ctx);
|
||||||
|
EVP_CipherInit_ex (&aes_ctx, EVP_aes_128_cbc (), NULL, conn->aes_ky,
|
||||||
|
conn->aes_iv, AES_ENCRYPT);
|
||||||
|
EVP_CipherUpdate (&aes_ctx, frame_data, &out_len, frame_data, /*( */
|
||||||
|
GST_APEX_RAOP_ALAC_HEADER_SIZE +
|
||||||
|
length /*) / AES_BLOCK_SIZE * AES_BLOCK_SIZE */ );
|
||||||
|
EVP_CIPHER_CTX_cleanup (&aes_ctx);
|
||||||
|
|
||||||
|
res =
|
||||||
|
gst_apexraop_send (conn->data_sd, buffer,
|
||||||
|
GST_APEX_RAOP_FRAME_HEADER_SIZE + GST_APEX_RAOP_ALAC_HEADER_SIZE +
|
||||||
|
length);
|
||||||
|
|
||||||
|
g_free (buffer);
|
||||||
|
|
||||||
|
return (guint) ((res >=
|
||||||
|
(GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||||
|
GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res -
|
||||||
|
GST_APEX_RAOP_FRAME_HEADER_SIZE -
|
||||||
|
GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex buffer flush */
|
||||||
|
GstRTSPStatusCode
|
||||||
|
gst_apexraop_flush (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
gchar hreq[GST_APEX_RAOP_HDR_DEFAULT_LENGTH];
|
||||||
|
GstRTSPStatusCode res;
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
sprintf (hreq,
|
||||||
|
"FLUSH rtsp://%s/%s RTSP/1.0\r\n"
|
||||||
|
"CSeq: %d\r\n"
|
||||||
|
"Client-Instance: %s\r\n"
|
||||||
|
"User-Agent: %s\r\n"
|
||||||
|
"Session: %s\r\n"
|
||||||
|
"RTP-Info: seq=0;rtptime=0\r\n"
|
||||||
|
"\r\n",
|
||||||
|
conn->host,
|
||||||
|
conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
|
||||||
|
|
||||||
|
if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
if (gst_apexraop_recv (conn->ctrl_sd, hreq,
|
||||||
|
GST_APEX_RAOP_HDR_DEFAULT_LENGTH) <= 0)
|
||||||
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
||||||
|
sscanf (hreq, "%*s %d", (int *) &res);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
124
ext/apexsink/gstapexraop.h
Normal file
124
ext/apexsink/gstapexraop.h
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
/* GStreamer - Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
|
||||||
|
*
|
||||||
|
* RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
|
||||||
|
* This interface accepts RAW PCM data and set it as AES encrypted ALAC while performing emission.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
|
||||||
|
*
|
||||||
|
* gstapexraop.h
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GST_APEXRAOP_H__
|
||||||
|
#define __GST_APEXRAOP_H__
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/rtsp/gstrtspdefs.h>
|
||||||
|
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/aes.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/* raop fixed parameters */
|
||||||
|
#define GST_APEX_RAOP_BITRATE 44100
|
||||||
|
#define GST_APEX_RAOP_SAMPLES_PER_FRAME 4096
|
||||||
|
#define GST_APEX_RAOP_BYTES_PER_CHANNEL 2
|
||||||
|
#define GST_APEX_RAOP_CHANNELS 2
|
||||||
|
#define GST_APEX_RAOP_BYTES_PER_SAMPLE (GST_APEX_RAOP_CHANNELS * GST_APEX_RAOP_BYTES_PER_CHANNEL)
|
||||||
|
|
||||||
|
/* gst associated caps fields specification */
|
||||||
|
#define GST_APEX_RAOP_INPUT_TYPE "audio/x-raw-int"
|
||||||
|
#define GST_APEX_RAOP_INPUT_WIDTH "16"
|
||||||
|
#define GST_APEX_RAOP_INPUT_DEPTH GST_APEX_RAOP_INPUT_WIDTH
|
||||||
|
#define GST_APEX_RAOP_INPUT_ENDIAN "LITTLE_ENDIAN"
|
||||||
|
#define GST_APEX_RAOP_INPUT_CHANNELS "2"
|
||||||
|
#define GST_APEX_RAOP_INPUT_BIT_RATE "44100"
|
||||||
|
#define GST_APEX_RAOP_INPUT_SIGNED "TRUE"
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_APEX_JACK_TYPE_UNDEFINED = 0,
|
||||||
|
GST_APEX_JACK_TYPE_ANALOG,
|
||||||
|
GST_APEX_JACK_TYPE_DIGITAL,
|
||||||
|
}
|
||||||
|
GstApExJackType;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_APEX_JACK_STATUS_UNDEFINED = 0,
|
||||||
|
GST_APEX_JACK_STATUS_DISCONNECTED,
|
||||||
|
GST_APEX_JACK_STATUS_CONNECTED,
|
||||||
|
}
|
||||||
|
GstApExJackStatus;
|
||||||
|
|
||||||
|
/* raop context handle */
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
} GstApExRAOP;
|
||||||
|
|
||||||
|
/* host might be null and port might be 0 while instanciating */
|
||||||
|
GstApExRAOP *gst_apexraop_new (const gchar * host, const guint16 port);
|
||||||
|
void gst_apexraop_free (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* must not be connected yet while setting the host target */
|
||||||
|
void gst_apexraop_set_host (GstApExRAOP * conn, const gchar * host);
|
||||||
|
gchar *gst_apexraop_get_host (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* must not be connected yet while setting the port target */
|
||||||
|
void gst_apexraop_set_port (GstApExRAOP * conn, const guint16 port);
|
||||||
|
guint16 gst_apexraop_get_port (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* optional affectation, default iTunes user agent internaly used */
|
||||||
|
void gst_apexraop_set_useragent (GstApExRAOP * conn, const gchar * useragent);
|
||||||
|
gchar *gst_apexraop_get_useragent (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* once allocation and configuration performed, manages the raop ANNOUNCE, SETUP and RECORD sequences,
|
||||||
|
* open both ctrl and data channels */
|
||||||
|
GstRTSPStatusCode gst_apexraop_connect (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* close the currently used session, manages raop TEARDOWN sequence and closes the used sockets */
|
||||||
|
void gst_apexraop_close (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* once connected, set the apex target volume, manages SET_PARAMETER sequence */
|
||||||
|
GstRTSPStatusCode gst_apexraop_set_volume (GstApExRAOP * conn,
|
||||||
|
const guint volume);
|
||||||
|
|
||||||
|
/* write raw samples typed as defined by the fixed raop parameters, flush the apex buffer */
|
||||||
|
guint gst_apexraop_write (GstApExRAOP * conn, gpointer rawdata, guint length);
|
||||||
|
GstRTSPStatusCode gst_apexraop_flush (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* retrieve the connected apex jack type and status */
|
||||||
|
GstApExJackType gst_apexraop_get_jacktype (GstApExRAOP * conn);
|
||||||
|
GstApExJackStatus gst_apexraop_get_jackstatus (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
571
ext/apexsink/gstapexsink.c
Normal file
571
ext/apexsink/gstapexsink.c
Normal file
|
@ -0,0 +1,571 @@
|
||||||
|
/* GStreamer - AirPort Express Audio Sink -
|
||||||
|
*
|
||||||
|
* Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
|
||||||
|
* RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
|
||||||
|
*
|
||||||
|
* RAW PCM input only as defined by the following GST_STATIC_PAD_TEMPLATE
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
|
||||||
|
*
|
||||||
|
* gstapexsink.c
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "gstapexsink.h"
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (apexsink_debug);
|
||||||
|
#define GST_CAT_DEFAULT apexsink_debug
|
||||||
|
|
||||||
|
static GstStaticPadTemplate gst_apexsink_sink_factory = GST_STATIC_PAD_TEMPLATE
|
||||||
|
("sink",
|
||||||
|
GST_PAD_SINK,
|
||||||
|
GST_PAD_ALWAYS,
|
||||||
|
GST_STATIC_CAPS
|
||||||
|
(GST_APEX_RAOP_INPUT_TYPE ","
|
||||||
|
"width = (int) " GST_APEX_RAOP_INPUT_WIDTH ","
|
||||||
|
"depth = (int) " GST_APEX_RAOP_INPUT_DEPTH ","
|
||||||
|
"endianness = (int) " GST_APEX_RAOP_INPUT_ENDIAN ","
|
||||||
|
"channels = (int) " GST_APEX_RAOP_INPUT_CHANNELS ","
|
||||||
|
"rate = (int) " GST_APEX_RAOP_INPUT_BIT_RATE ","
|
||||||
|
"signed = (boolean) " GST_APEX_RAOP_INPUT_SIGNED)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
APEX_PROP_HOST = 1,
|
||||||
|
APEX_PROP_PORT,
|
||||||
|
APEX_PROP_VOLUME,
|
||||||
|
APEX_PROP_JACK_TYPE,
|
||||||
|
APEX_PROP_JACK_STATUS,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DEFAULT_APEX_HOST ""
|
||||||
|
#define DEFAULT_APEX_PORT 5000
|
||||||
|
#define DEFAULT_APEX_VOLUME 75
|
||||||
|
#define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_UNDEFINED
|
||||||
|
#define DEFAULT_APEX_JACK_STATUS GST_APEX_JACK_STATUS_UNDEFINED
|
||||||
|
|
||||||
|
/* genum apex jack resolution */
|
||||||
|
GType
|
||||||
|
gst_apexsink_jackstatus_get_type (void)
|
||||||
|
{
|
||||||
|
static GType jackstatus_type = 0;
|
||||||
|
static GEnumValue jackstatus[] = {
|
||||||
|
{GST_APEX_JACK_STATUS_UNDEFINED, "GST_APEX_JACK_STATUS_UNDEFINED",
|
||||||
|
"Jack status undefined"},
|
||||||
|
{GST_APEX_JACK_STATUS_DISCONNECTED, "GST_APEX_JACK_STATUS_DISCONNECTED",
|
||||||
|
"Jack disconnected"},
|
||||||
|
{GST_APEX_JACK_STATUS_CONNECTED, "GST_APEX_JACK_STATUS_CONNECTED",
|
||||||
|
"Jack connected"},
|
||||||
|
{0, NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!jackstatus_type) {
|
||||||
|
jackstatus_type = g_enum_register_static ("GstApExJackStatus", jackstatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jackstatus_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
GType
|
||||||
|
gst_apexsink_jacktype_get_type (void)
|
||||||
|
{
|
||||||
|
static GType jacktype_type = 0;
|
||||||
|
static GEnumValue jacktype[] = {
|
||||||
|
{GST_APEX_JACK_TYPE_UNDEFINED, "GST_APEX_JACK_TYPE_UNDEFINED",
|
||||||
|
"Undefined jack type"},
|
||||||
|
{GST_APEX_JACK_TYPE_ANALOG, "GST_APEX_JACK_TYPE_ANALOG", "Analog jack"},
|
||||||
|
{GST_APEX_JACK_TYPE_DIGITAL, "GST_APEX_JACK_TYPE_DIGITAL", "Digital jack"},
|
||||||
|
{0, NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!jacktype_type) {
|
||||||
|
jacktype_type = g_enum_register_static ("GstApExJackType", jacktype);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jacktype_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void gst_apexsink_base_init (gpointer g_class);
|
||||||
|
static void gst_apexsink_class_init (GstApExSinkClass * klass);
|
||||||
|
static void gst_apexsink_init (GstApExSink * apexsink,
|
||||||
|
GstApExSinkClass * g_class);
|
||||||
|
|
||||||
|
static void gst_apexsink_set_property (GObject * object, guint prop_id,
|
||||||
|
const GValue * value, GParamSpec * pspec);
|
||||||
|
static void gst_apexsink_get_property (GObject * object, guint prop_id,
|
||||||
|
GValue * value, GParamSpec * pspec);
|
||||||
|
static void gst_apexsink_finalise (GObject * object);
|
||||||
|
|
||||||
|
static gboolean gst_apexsink_open (GstAudioSink * asink);
|
||||||
|
static gboolean gst_apexsink_prepare (GstAudioSink * asink,
|
||||||
|
GstRingBufferSpec * spec);
|
||||||
|
static guint gst_apexsink_write (GstAudioSink * asink, gpointer data,
|
||||||
|
guint length);
|
||||||
|
static gboolean gst_apexsink_unprepare (GstAudioSink * asink);
|
||||||
|
static guint gst_apexsink_delay (GstAudioSink * asink);
|
||||||
|
static void gst_apexsink_reset (GstAudioSink * asink);
|
||||||
|
static gboolean gst_apexsink_close (GstAudioSink * asink);
|
||||||
|
|
||||||
|
/* mixer interface standard api */
|
||||||
|
static void gst_apexsink_interfaces_init (GType type);
|
||||||
|
static void gst_apexsink_implements_interface_init (GstImplementsInterfaceClass
|
||||||
|
* iface);
|
||||||
|
static void gst_apexsink_mixer_interface_init (GstMixerClass * iface);
|
||||||
|
|
||||||
|
static gboolean gst_apexsink_interface_supported (GstImplementsInterface *
|
||||||
|
iface, GType iface_type);
|
||||||
|
static const GList *gst_apexsink_mixer_list_tracks (GstMixer * mixer);
|
||||||
|
static void gst_apexsink_mixer_set_volume (GstMixer * mixer,
|
||||||
|
GstMixerTrack * track, gint * volumes);
|
||||||
|
static void gst_apexsink_mixer_get_volume (GstMixer * mixer,
|
||||||
|
GstMixerTrack * track, gint * volumes);
|
||||||
|
|
||||||
|
GST_BOILERPLATE_FULL (GstApExSink, gst_apexsink, GstAudioSink,
|
||||||
|
GST_TYPE_AUDIO_SINK, gst_apexsink_interfaces_init);
|
||||||
|
|
||||||
|
/* apex sink interface(s) stuff */
|
||||||
|
static void
|
||||||
|
gst_apexsink_interfaces_init (GType type)
|
||||||
|
{
|
||||||
|
static const GInterfaceInfo implements_interface_info =
|
||||||
|
{ (GInterfaceInitFunc) gst_apexsink_implements_interface_init, NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
static const GInterfaceInfo mixer_interface_info =
|
||||||
|
{ (GInterfaceInitFunc) gst_apexsink_mixer_interface_init, NULL, NULL };
|
||||||
|
|
||||||
|
g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
|
||||||
|
&implements_interface_info);
|
||||||
|
g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_interface_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_apexsink_implements_interface_init (GstImplementsInterfaceClass * iface)
|
||||||
|
{
|
||||||
|
iface->supported = gst_apexsink_interface_supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_apexsink_mixer_interface_init (GstMixerClass * iface)
|
||||||
|
{
|
||||||
|
GST_MIXER_TYPE (iface) = GST_MIXER_SOFTWARE;
|
||||||
|
|
||||||
|
iface->list_tracks = gst_apexsink_mixer_list_tracks;
|
||||||
|
iface->set_volume = gst_apexsink_mixer_set_volume;
|
||||||
|
iface->get_volume = gst_apexsink_mixer_get_volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_apexsink_interface_supported (GstImplementsInterface * iface,
|
||||||
|
GType iface_type)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const GList *
|
||||||
|
gst_apexsink_mixer_list_tracks (GstMixer * mixer)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = GST_APEX_SINK (mixer);
|
||||||
|
|
||||||
|
return apexsink->tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_apexsink_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
|
||||||
|
gint * volumes)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = GST_APEX_SINK (mixer);
|
||||||
|
|
||||||
|
apexsink->volume = volumes[0];
|
||||||
|
|
||||||
|
if (apexsink->gst_apexraop != NULL)
|
||||||
|
gst_apexraop_set_volume (apexsink->gst_apexraop, apexsink->volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_apexsink_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
|
||||||
|
gint * volumes)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = GST_APEX_SINK (mixer);
|
||||||
|
|
||||||
|
volumes[0] = apexsink->volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink base init */
|
||||||
|
static void
|
||||||
|
gst_apexsink_base_init (gpointer g_class)
|
||||||
|
{
|
||||||
|
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
|
||||||
|
|
||||||
|
gst_element_class_set_details_simple (element_class,
|
||||||
|
"Apple AirPort Express Audio Sink", "Sink/Audio/Wireless",
|
||||||
|
"Output stream to an AirPort Express",
|
||||||
|
"Jérémie Bernard [GRemi] <gremimail@gmail.com>");
|
||||||
|
gst_element_class_add_pad_template (element_class,
|
||||||
|
gst_static_pad_template_get (&gst_apexsink_sink_factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink class init */
|
||||||
|
static void
|
||||||
|
gst_apexsink_class_init (GstApExSinkClass * klass)
|
||||||
|
{
|
||||||
|
GST_DEBUG_CATEGORY_INIT (apexsink_debug, GST_APEX_SINK_NAME, 0,
|
||||||
|
"AirPort Express sink");
|
||||||
|
|
||||||
|
parent_class = g_type_class_peek_parent (klass);
|
||||||
|
|
||||||
|
((GObjectClass *) klass)->get_property =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_get_property);
|
||||||
|
((GObjectClass *) klass)->set_property =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_set_property);
|
||||||
|
((GObjectClass *) klass)->finalize =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_finalise);
|
||||||
|
|
||||||
|
((GstAudioSinkClass *) klass)->open = GST_DEBUG_FUNCPTR (gst_apexsink_open);
|
||||||
|
((GstAudioSinkClass *) klass)->prepare =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_prepare);
|
||||||
|
((GstAudioSinkClass *) klass)->write = GST_DEBUG_FUNCPTR (gst_apexsink_write);
|
||||||
|
((GstAudioSinkClass *) klass)->unprepare =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_unprepare);
|
||||||
|
((GstAudioSinkClass *) klass)->delay = GST_DEBUG_FUNCPTR (gst_apexsink_delay);
|
||||||
|
((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset);
|
||||||
|
((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close);
|
||||||
|
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST,
|
||||||
|
g_param_spec_string ("host", "Host", "AirPort Express target host",
|
||||||
|
DEFAULT_APEX_HOST, G_PARAM_READWRITE));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_PORT,
|
||||||
|
g_param_spec_uint ("port", "Port", "AirPort Express target port", 0,
|
||||||
|
32000, DEFAULT_APEX_PORT, G_PARAM_READWRITE));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_VOLUME,
|
||||||
|
g_param_spec_uint ("volume", "Volume", "AirPort Express target volume", 0,
|
||||||
|
100, DEFAULT_APEX_VOLUME, G_PARAM_READWRITE));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_JACK_TYPE,
|
||||||
|
g_param_spec_enum ("jack_type", "Jack Type",
|
||||||
|
"AirPort Express connected jack type", GST_APEX_SINK_JACKTYPE_TYPE,
|
||||||
|
DEFAULT_APEX_JACK_TYPE, G_PARAM_READABLE));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass,
|
||||||
|
APEX_PROP_JACK_STATUS, g_param_spec_enum ("jack_status", "Jack Status",
|
||||||
|
"AirPort Express jack connection status",
|
||||||
|
GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
|
||||||
|
G_PARAM_READABLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink plugin instance init */
|
||||||
|
static void
|
||||||
|
gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
|
||||||
|
{
|
||||||
|
GstMixerTrack *track = NULL;
|
||||||
|
|
||||||
|
track = g_object_new (GST_TYPE_MIXER_TRACK, NULL);
|
||||||
|
track->label = g_strdup ("Airport Express");
|
||||||
|
track->num_channels = GST_APEX_RAOP_CHANNELS;
|
||||||
|
track->min_volume = 0;
|
||||||
|
track->max_volume = 100;
|
||||||
|
track->flags = GST_MIXER_TRACK_OUTPUT;
|
||||||
|
|
||||||
|
apexsink->host = g_strdup (DEFAULT_APEX_HOST);
|
||||||
|
apexsink->port = DEFAULT_APEX_PORT;
|
||||||
|
apexsink->volume = DEFAULT_APEX_VOLUME;
|
||||||
|
apexsink->gst_apexraop = NULL;
|
||||||
|
apexsink->tracks = g_list_append (apexsink->tracks, track);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink,
|
||||||
|
"ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d\%\"",
|
||||||
|
apexsink->host, apexsink->port, apexsink->volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apex sink set property */
|
||||||
|
static void
|
||||||
|
gst_apexsink_set_property (GObject * object, guint prop_id,
|
||||||
|
const GValue * value, GParamSpec * pspec)
|
||||||
|
{
|
||||||
|
GstApExSink *sink = GST_APEX_SINK (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case APEX_PROP_HOST:
|
||||||
|
{
|
||||||
|
if (sink->gst_apexraop == NULL) {
|
||||||
|
g_free (sink->host);
|
||||||
|
sink->host = g_value_dup_string (value);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (sink, "ApEx sink target set to \"%s\"", sink->host);
|
||||||
|
} else
|
||||||
|
G_OBJECT_WARN_INVALID_PSPEC (object, "host", prop_id, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_PORT:
|
||||||
|
{
|
||||||
|
if (sink->gst_apexraop == NULL) {
|
||||||
|
sink->port = g_value_get_uint (value);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (sink, "ApEx port set to \"%d\"", sink->port);
|
||||||
|
} else
|
||||||
|
G_OBJECT_WARN_INVALID_PSPEC (object, "port", prop_id, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_VOLUME:
|
||||||
|
{
|
||||||
|
sink->volume = g_value_get_uint (value);
|
||||||
|
|
||||||
|
if (sink->gst_apexraop != NULL)
|
||||||
|
gst_apexraop_set_volume (sink->gst_apexraop, sink->volume);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (sink, "ApEx volume set to \"%d\%\"", sink->volume);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apex sink get property */
|
||||||
|
static void
|
||||||
|
gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
|
||||||
|
GParamSpec * pspec)
|
||||||
|
{
|
||||||
|
GstApExSink *sink = GST_APEX_SINK (object);
|
||||||
|
|
||||||
|
switch (prop_id) {
|
||||||
|
case APEX_PROP_HOST:
|
||||||
|
{
|
||||||
|
g_value_set_string (value, sink->host);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_PORT:
|
||||||
|
{
|
||||||
|
g_value_set_uint (value, sink->port);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_VOLUME:
|
||||||
|
{
|
||||||
|
g_value_set_uint (value, sink->volume);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_JACK_TYPE:
|
||||||
|
{
|
||||||
|
g_value_set_enum (value, gst_apexraop_get_jacktype (sink->gst_apexraop));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_JACK_STATUS:
|
||||||
|
{
|
||||||
|
g_value_set_enum (value,
|
||||||
|
gst_apexraop_get_jackstatus (sink->gst_apexraop));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* apex sink finalize */
|
||||||
|
static void
|
||||||
|
gst_apexsink_finalise (GObject * object)
|
||||||
|
{
|
||||||
|
GstApExSink *sink = GST_APEX_SINK (object);
|
||||||
|
|
||||||
|
if (sink->tracks) {
|
||||||
|
g_list_foreach (sink->tracks, (GFunc) g_object_unref, NULL);
|
||||||
|
g_list_free (sink->tracks);
|
||||||
|
sink->tracks = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (sink->host);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink open : open the device */
|
||||||
|
static gboolean
|
||||||
|
gst_apexsink_open (GstAudioSink * asink)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
apexsink->gst_apexraop = gst_apexraop_new (apexsink->host, apexsink->port);
|
||||||
|
|
||||||
|
if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) {
|
||||||
|
GST_ERROR_OBJECT (apexsink,
|
||||||
|
"%s : network or RAOP failure, connection refused or timeout, RTSP code=%d",
|
||||||
|
apexsink->host, res);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink,
|
||||||
|
"OPEN : ApEx sink successfully connected to \"%s:%d\", ANNOUNCE, SETUP and RECORD requests performed",
|
||||||
|
apexsink->host, apexsink->port);
|
||||||
|
|
||||||
|
switch (gst_apexraop_get_jackstatus (apexsink->gst_apexraop)) {
|
||||||
|
case GST_APEX_JACK_STATUS_CONNECTED:
|
||||||
|
{
|
||||||
|
GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack is connected");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GST_APEX_JACK_STATUS_DISCONNECTED:
|
||||||
|
{
|
||||||
|
GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack is disconnected !");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack status is undefined !");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gst_apexraop_get_jacktype (apexsink->gst_apexraop)) {
|
||||||
|
case GST_APEX_JACK_TYPE_ANALOG:
|
||||||
|
{
|
||||||
|
GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is analog");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GST_APEX_JACK_TYPE_DIGITAL:
|
||||||
|
{
|
||||||
|
GST_INFO_OBJECT (apexsink, "OPEN : ApEx jack type is digital");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
GST_WARNING_OBJECT (apexsink, "OPEN : ApEx jack type is undefined !");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((res =
|
||||||
|
gst_apexraop_set_volume (apexsink->gst_apexraop,
|
||||||
|
apexsink->volume)) != GST_RTSP_STS_OK) {
|
||||||
|
GST_WARNING_OBJECT (apexsink,
|
||||||
|
"%s : could not set initial volume to \"%d\%\", RTSP code=%d",
|
||||||
|
apexsink->host, apexsink->volume, res);
|
||||||
|
} else {
|
||||||
|
GST_INFO_OBJECT (apexsink,
|
||||||
|
"OPEN : ApEx sink successfully set volume to \"%d\%\"",
|
||||||
|
apexsink->volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prepare sink : configure the device with the specified format */
|
||||||
|
static gboolean
|
||||||
|
gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
apexsink->latency_time = spec->latency_time;
|
||||||
|
|
||||||
|
spec->segsize =
|
||||||
|
GST_APEX_RAOP_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
|
||||||
|
spec->segtotal = 1;
|
||||||
|
|
||||||
|
bzero (spec->silence_sample, sizeof (spec->silence_sample));
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink,
|
||||||
|
"PREPARE : ApEx sink ready to stream at %dHz, %d bytes per sample, %d channels, %d bytes segments (%dkB/s)",
|
||||||
|
spec->rate, spec->bytes_per_sample, spec->channels, spec->segsize,
|
||||||
|
spec->rate * spec->bytes_per_sample / 1000);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink write : write samples to the device */
|
||||||
|
static guint
|
||||||
|
gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
if (gst_apexraop_write (apexsink->gst_apexraop, data, length) != length) {
|
||||||
|
GST_INFO_OBJECT (apexsink,
|
||||||
|
"WRITE : %d bytes not fully sended, skipping frame samples...", length);
|
||||||
|
} else {
|
||||||
|
GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sended", length);
|
||||||
|
|
||||||
|
usleep ((gulong) ((length * 1000000.) / (GST_APEX_RAOP_BITRATE *
|
||||||
|
GST_APEX_RAOP_BYTES_PER_SAMPLE) - apexsink->latency_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unprepare sink : undo operations done by prepare */
|
||||||
|
static gboolean
|
||||||
|
gst_apexsink_unprepare (GstAudioSink * asink)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink, "UNPREPARE");
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* delay sink : get the estimated number of samples written but not played yet by the device */
|
||||||
|
static guint
|
||||||
|
gst_apexsink_delay (GstAudioSink * asink)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink, "DELAY");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset sink : unblock writes and flush the device */
|
||||||
|
static void
|
||||||
|
gst_apexsink_reset (GstAudioSink * asink)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink, "RESET : flushing buffer...");
|
||||||
|
|
||||||
|
if ((res = gst_apexraop_flush (apexsink->gst_apexraop)) == GST_RTSP_STS_OK) {
|
||||||
|
GST_INFO_OBJECT (apexsink, "RESET : ApEx buffer flush success");
|
||||||
|
} else {
|
||||||
|
GST_WARNING_OBJECT (apexsink,
|
||||||
|
"RESET : could not flush ApEx buffer, RTSP code=%d", res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sink close : close the device */
|
||||||
|
static gboolean
|
||||||
|
gst_apexsink_close (GstAudioSink * asink)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
|
gst_apexraop_close (apexsink->gst_apexraop);
|
||||||
|
gst_apexraop_free (apexsink->gst_apexraop);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (apexsink, "CLOSE : ApEx sink closed connection");
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
85
ext/apexsink/gstapexsink.h
Normal file
85
ext/apexsink/gstapexsink.h
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/* GStreamer - AirPort Express (ApEx) Audio Sink -
|
||||||
|
*
|
||||||
|
* Remote Audio Access Protocol (RAOP) as used in Apple iTunes to stream music to the Airport Express (ApEx) -
|
||||||
|
* RAOP is based on the Real Time Streaming Protocol (RTSP) but with an extra challenge-response RSA based authentication step.
|
||||||
|
*
|
||||||
|
* RAW PCM input only as defined by the following GST_STATIC_PAD_TEMPLATE regarding the expected gstapexraop input format.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008 Jérémie Bernard [GRemi] <gremimail@gmail.com>
|
||||||
|
*
|
||||||
|
* gstapexsink.h
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Library General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 2 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
|
||||||
|
* Library General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Library General Public
|
||||||
|
* License along with this library; if not, write to the
|
||||||
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||||
|
* Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GST_APEXSINK_H__
|
||||||
|
#define __GST_APEXSINK_H__
|
||||||
|
|
||||||
|
#include "gstapexraop.h"
|
||||||
|
|
||||||
|
#include <gst/audio/gstaudiosink.h>
|
||||||
|
#include <gst/interfaces/mixer.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
/* standard gstreamer macros */
|
||||||
|
#define GST_TYPE_APEX_SINK (gst_apexsink_get_type())
|
||||||
|
#define GST_APEX_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_APEX_SINK,GstApExSink))
|
||||||
|
#define GST_APEX_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_APEX_SINK,GstApExSinkClass))
|
||||||
|
#define GST_IS_APEX_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_APEX_SINK))
|
||||||
|
#define GST_IS_APEX_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_APEX_SINK))
|
||||||
|
#define GST_APEX_SINK_CAST(obj) ((GstApExSink*)(obj))
|
||||||
|
#define GST_APEX_SINK_NAME "apexsink"
|
||||||
|
#define GST_APEX_SINK_JACKTYPE_TYPE (gst_apexsink_jacktype_get_type())
|
||||||
|
#define GST_APEX_SINK_JACKSTATUS_TYPE (gst_apexsink_jackstatus_get_type())
|
||||||
|
/* ApEx classes declaration */
|
||||||
|
typedef struct _GstApExSink GstApExSink;
|
||||||
|
typedef struct _GstApExSinkClass GstApExSinkClass;
|
||||||
|
|
||||||
|
struct _GstApExSink
|
||||||
|
{
|
||||||
|
/* base definition */
|
||||||
|
GstAudioSink sink;
|
||||||
|
|
||||||
|
/* public read/write sink properties */
|
||||||
|
gchar *host;
|
||||||
|
guint port;
|
||||||
|
guint volume;
|
||||||
|
|
||||||
|
/* private attributes : latency time local copy, tracks list of the mixer interface */
|
||||||
|
guint64 latency_time;
|
||||||
|
GList *tracks;
|
||||||
|
|
||||||
|
/* private apex client */
|
||||||
|
GstApExRAOP *gst_apexraop;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _GstApExSinkClass
|
||||||
|
{
|
||||||
|
GstAudioSinkClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* genum jack access */
|
||||||
|
GType gst_apexsink_jackstatus_get_type (void);
|
||||||
|
GType gst_apexsink_jacktype_get_type (void);
|
||||||
|
|
||||||
|
/* audio sink standard api */
|
||||||
|
GType gst_apexsink_get_type (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue