From ad58f7a2caeeecf25a9d69aecf96a5fcc77fffcd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Juho=20V=C3=A4h=C3=A4-Herttua?= Date: Sat, 19 May 2012 13:18:58 +0300 Subject: [PATCH] Add autotools build scripts and new shairplay program --- .gitignore | 9 ++ Makefile.am | 4 + autogen.sh | 3 + configure.ac | 80 +++++++++++ include/Makefile.am | 1 + m4/pkg.m4 | 155 ++++++++++++++++++++ src/Makefile.am | 14 ++ src/lib/Makefile.am | 13 ++ src/lib/alac/Makefile.am | 2 + src/lib/crypto/Makefile.am | 3 + src/shairplay.c | 287 +++++++++++++++++++++++++++++++++++++ 11 files changed, 571 insertions(+) create mode 100644 Makefile.am create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 include/Makefile.am create mode 100644 m4/pkg.m4 create mode 100644 src/Makefile.am create mode 100644 src/lib/Makefile.am create mode 100644 src/lib/alac/Makefile.am create mode 100644 src/lib/crypto/Makefile.am create mode 100644 src/shairplay.c diff --git a/.gitignore b/.gitignore index 24b7486..22f0161 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,15 @@ project.xcworkspace xcuserdata +# autotools +Makefile +*.lo +*.in +.deps +.libs +*.la +*.a + # SCons specific build .sconf_temp diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..02deebb --- /dev/null +++ b/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = include src + +ACLOCAL_AMFLAGS = -I m4 + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..872167c --- /dev/null +++ b/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf -vif diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..7fcb650 --- /dev/null +++ b/configure.ac @@ -0,0 +1,80 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.61) +AC_INIT([shairplay], [0.9.0], [juhovh@iki.fi]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_SRCDIR([src/shairplay.c]) +AC_CONFIG_HEADER([config.h]) +AM_INIT_AUTOMAKE([foreign]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_LIBTOOL + +# Checks for libraries. + +# Checks for header files. +AC_HEADER_STDC + +# Checks for typedefs, structures, and compiler characteristics. + +# Checks for library functions. + + + +# Custom check for os, similar to webkit +AC_MSG_CHECKING([for native Win32]) +case "$host" in + *-*-mingw*) + os_win32=yes + ;; + *) + os_win32=no + ;; +esac +AC_MSG_RESULT([$os_win32]) + +case "$host" in + *-*-linux*) + os_linux=yes + ;; + *-*-freebsd*) + os_freebsd=yes + ;; + *-*-darwin*) + os_darwin=yes + ;; +esac + +case "$host_os" in + gnu* | linux* | k*bsd*-gnu) + os_gnu=yes + ;; + *) + os_gnu=no + ;; +esac + +# OS conditionals +AM_CONDITIONAL([OS_WIN32],[test "$os_win32" = "yes"]) +AM_CONDITIONAL([OS_UNIX],[test "$os_win32" = "no"]) +AM_CONDITIONAL([OS_LINUX],[test "$os_linux" = "yes"]) +AM_CONDITIONAL([OS_GNU],[test "$os_gnu" = "yes"]) +AM_CONDITIONAL([OS_FREEBSD],[test "$os_freebsd" = "yes"]) + +# Custom check for libao +PKG_CHECK_MODULES([libao], [ao >= 1.1.0], [HAVE_LIBAO=1], [HAVE_LIBAO=0]) +AM_CONDITIONAL([USE_LIBAO], [test "$HAVE_LIBAO" -eq 1]) + + + +AC_CONFIG_FILES( + [Makefile] + [include/Makefile] + [src/Makefile] + [src/lib/Makefile] + [src/lib/alac/Makefile] + [src/lib/crypto/Makefile] +) +AC_OUTPUT diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..282bd89 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1 @@ +nobase_include_HEADERS = shairplay/dnssd.h shairplay/raop.h diff --git a/m4/pkg.m4 b/m4/pkg.m4 new file mode 100644 index 0000000..a0b9cd4 --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,155 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi + +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# +# Similar to PKG_CHECK_MODULES, make sure that the first instance of +# this or PKG_CHECK_MODULES is called, or make sure to call +# PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_ifval([$2], [$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$$1"; then + pkg_cv_[]$1="$$1" + elif test -n "$PKG_CONFIG"; then + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors "$2" 2>&1` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors "$2" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + ifelse([$4], , [AC_MSG_ERROR(dnl +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT +])], + [AC_MSG_RESULT([no]) + $4]) +elif test $pkg_failed = untried; then + ifelse([$4], , [AC_MSG_FAILURE(dnl +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see .])], + [$4]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + ifelse([$3], , :, [$3]) +fi[]dnl +])# PKG_CHECK_MODULES diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..e9428d1 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,14 @@ +SUBDIRS = lib + +AM_CPPFLAGS = -I$(top_srcdir)/include + +if USE_LIBAO +bin_PROGRAMS = shairplay +shairplay_SOURCES = shairplay.c +shairplay_LDADD = lib/libshairplay.la +shairplay_CFLAGS = +shairplay_LDFLAGS = -static-libtool-libs + +shairplay_CFLAGS += $(libao_CFLAGS) +shairplay_LDADD += $(libao_LIBS) +endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 0000000..30d1c5a --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,13 @@ +SUBDIRS = crypto alac + +AM_CPPFLAGS = -I$(top_srcdir)/include/shairplay + +lib_LTLIBRARIES = libshairplay.la +libshairplay_la_SOURCES = base64.c base64.h digest.c digest.h dnssd.c dnssdint.h http_parser.c http_parser.h http_request.c http_request.h http_response.c http_response.h httpd.c httpd.h logger.c logger.h netutils.c netutils.h raop.c raop_buffer.c raop_buffer.h raop_rtp.c raop_rtp.h rsakey.c rsakey.h rsapem.c rsapem.h sdp.c sdp.h utils.c utils.h compat.h memalign.h sockets.h threads.h + +# This library depends on 3rd party libraries +libshairplay_la_LIBADD = crypto/libcrypto.la alac/libalac.la +libshairplay_la_LDFLAGS = -version-info 0:0:0 +if OS_WIN32 +libshairplay_la_LDFLAGS += -no-undefined -lws2_32 -lwinmm +endif diff --git a/src/lib/alac/Makefile.am b/src/lib/alac/Makefile.am new file mode 100644 index 0000000..c1fbf5e --- /dev/null +++ b/src/lib/alac/Makefile.am @@ -0,0 +1,2 @@ +noinst_LTLIBRARIES = libalac.la +libalac_la_SOURCES = alac.c alac.h stdint_win.h diff --git a/src/lib/crypto/Makefile.am b/src/lib/crypto/Makefile.am new file mode 100644 index 0000000..26bd535 --- /dev/null +++ b/src/lib/crypto/Makefile.am @@ -0,0 +1,3 @@ +noinst_LTLIBRARIES = libcrypto.la +libcrypto_la_SOURCES = bigint.c bigint.h bigint_impl.h aes.c hmac.c md5.c rc4.c sha1.c crypto.h os_port.h config.h + diff --git a/src/shairplay.c b/src/shairplay.c new file mode 100644 index 0000000..0486f6c --- /dev/null +++ b/src/shairplay.c @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#endif + +#include +#include + +#include + +#include "config.h" + +typedef struct { + char apname[56]; + char password[56]; + unsigned short port; + + char ao_driver[56]; + char ao_devicename[56]; + char ao_deviceid[16]; +} shairplay_options_t; + +typedef struct { + ao_device *device; + + int buffering; + int buflen; + char buffer[8192]; + + float volume; +} shairplay_session_t; + + +static ao_device * +audio_open_device(shairplay_options_t *opt, int bits, int channels, int samplerate) +{ + ao_device *device = NULL; + ao_option *ao_options = NULL; + ao_sample_format format; + int driver_id; + + /* Get the libao driver ID */ + if (strlen(opt->ao_driver)) { + driver_id = ao_driver_id(opt->ao_driver); + } else { + driver_id = ao_default_driver_id(); + } + + /* Add all available libao options */ + ao_append_option(&ao_options, "client_name", opt->apname); + if (strlen(opt->ao_devicename)) { + ao_append_option(&ao_options, "dev", opt->ao_devicename); + } + if (strlen(opt->ao_deviceid)) { + ao_append_option(&ao_options, "id", opt->ao_deviceid); + } + + /* Set audio format */ + memset(&format, 0, sizeof(format)); + format.bits = bits; + format.channels = channels; + format.rate = samplerate; + format.byte_format = AO_FMT_NATIVE; + + /* Try opening the actual device */ + device = ao_open_live(driver_id, &format, ao_options); + ao_free_options(ao_options); + return device; +} + +static void * +audio_init(void *cls, int bits, int channels, int samplerate) +{ + shairplay_options_t *options = cls; + shairplay_session_t *session; + + session = calloc(1, sizeof(shairplay_session_t)); + assert(session); + + session->device = audio_open_device(options, bits, channels, samplerate); + if (session->device == NULL) { + printf("Error opening device %d\n", errno); + } + assert(session->device); + + session->buffering = 1; + session->volume = 1.0f; + return session; +} + +static int +audio_output(shairplay_session_t *session, const void *buffer, int buflen) +{ + short *shortbuf; + char tmpbuf[4096]; + int tmpbuflen, i; + + tmpbuflen = (buflen > sizeof(tmpbuf)) ? sizeof(tmpbuf) : buflen; + memcpy(tmpbuf, buffer, tmpbuflen); + if (ao_is_big_endian()) { + for (i=0; ivolume; + } + ao_play(session->device, tmpbuf, tmpbuflen); + return tmpbuflen; +} + +static void +audio_process(void *cls, void *opaque, const void *buffer, int buflen) +{ + shairplay_session_t *session = opaque; + int processed; + + if (session->buffering) { + printf("Buffering...\n"); + if (session->buflen+buflen < sizeof(session->buffer)) { + memcpy(session->buffer+session->buflen, buffer, buflen); + session->buflen += buflen; + return; + } + session->buffering = 0; + printf("Finished buffering...\n"); + + processed = 0; + while (processed < session->buflen) { + processed += audio_output(session, + session->buffer+processed, + session->buflen-processed); + } + session->buflen = 0; + } + + processed = 0; + while (processed < buflen) { + processed += audio_output(session, + buffer+processed, + buflen-processed); + } +} + +static void +audio_destroy(void *cls, void *opaque) +{ + shairplay_session_t *session = opaque; + + ao_close(session->device); + free(session); +} + +static void +audio_set_volume(void *cls, void *opaque, float volume) +{ + shairplay_session_t *session = opaque; + session->volume = pow(10.0, 0.05*volume); +} + +static int +parse_options(shairplay_options_t *opt, int argc, char *argv[]) +{ + char *path = argv[0]; + char *arg; + + strcpy(opt->apname, "Shairplay"); + opt->port = 5000; + + while ((arg = *++argv)) { + if (!strcmp(arg, "-a")) { + strncpy(opt->apname, *++argv, sizeof(opt->apname)-1); + } else if (!strncmp(arg, "--apname=", 9)) { + strncpy(opt->apname, arg+9, sizeof(opt->apname)-1); + } else if (!strcmp(arg, "-p")) { + strncpy(opt->password, *++argv, sizeof(opt->password)-1); + } else if (!strncmp(arg, "--password=", 11)) { + strncpy(opt->password, arg+11, sizeof(opt->password)-1); + } else if (!strcmp(arg, "-o")) { + opt->port = atoi(*++argv); + } else if (!strncmp(arg, "--server_port=", 14)) { + opt->port = atoi(arg+14); + } else if (!strncmp(arg, "--ao_driver=", 12)) { + strncpy(opt->ao_driver, arg+12, sizeof(opt->ao_driver)-1); + } else if (!strncmp(arg, "--ao_devicename=", 16)) { + strncpy(opt->ao_devicename, arg+16, sizeof(opt->ao_devicename)-1); + } else if (!strncmp(arg, "--ao_deviceid=", 14)) { + strncpy(opt->ao_deviceid, arg+14, sizeof(opt->ao_deviceid)-1); + } else if (!strcmp(arg, "-h") || !strcmp(arg, "--help")) { + fprintf(stderr, "Shairplay version %s\n", VERSION); + fprintf(stderr, "Usage: %s [OPTION...]\n", path); + fprintf(stderr, "\n"); + fprintf(stderr, " -a, --apname=AirPort Sets Airport name\n"); + fprintf(stderr, " -p, --password=secret Sets password\n"); + fprintf(stderr, " -o, --server_port=5000 Sets port for RAOP service\n"); + fprintf(stderr, " --ao_driver=driver Sets the ao driver (optional)\n"); + fprintf(stderr, " --ao_devicename=devicename Sets the ao device name (optional)\n"); + fprintf(stderr, " --ao_deviceid=id Sets the ao device id (optional)\n"); + fprintf(stderr, " -h, --help This help\n"); + fprintf(stderr, "\n"); + return 1; + } + } + + /* Set default values for apname and port */ + if (!strlen(opt->apname)) { + strncpy(opt->apname, "Shairplay", sizeof(opt->apname)-1); + } + if (!opt->port) { + opt->port = 5000; + } + return 0; +} + +int +main(int argc, char *argv[]) +{ + const char hwaddr[] = { 0x48, 0x5d, 0x60, 0x7c, 0xee, 0x22 }; + + shairplay_options_t options; + ao_device *device = NULL; + + dnssd_t *dnssd; + raop_t *raop; + raop_callbacks_t raop_cbs; + + memset(&options, 0, sizeof(options)); + if (parse_options(&options, argc, argv)) { + return 0; + } + + ao_initialize(); + + device = audio_open_device(&options, 16, 2, 44100); + if (device == NULL) { + fprintf(stderr, "Error opening audio device %d\n", errno); + fprintf(stderr, "Please check your libao settings and try again\n"); + return -1; + } else { + ao_close(device); + device = NULL; + } + + memset(&raop_cbs, 0, sizeof(raop_cbs)); + raop_cbs.cls = &options; + raop_cbs.audio_init = audio_init; + raop_cbs.audio_process = audio_process; + raop_cbs.audio_destroy = audio_destroy; + raop_cbs.audio_set_volume = audio_set_volume; + + raop = raop_init_from_keyfile(10, &raop_cbs, "airport.key"); + if (raop == NULL) { + fprintf(stderr, "Could not initialize the RAOP service\n"); + return -1; + } + + raop_set_log_level(raop, RAOP_LOG_DEBUG); + raop_start(raop, &options.port, hwaddr, sizeof(hwaddr), NULL); + + dnssd = dnssd_init(NULL); + dnssd_register_raop(dnssd, options.apname, options.port, hwaddr, sizeof(hwaddr), 0); + +#ifndef WIN32 + sleep(100); +#else + Sleep(100*1000); +#endif + + dnssd_unregister_raop(dnssd); + dnssd_destroy(dnssd); + + raop_stop(raop); + raop_destroy(raop); + + ao_shutdown(); + + return 0; +} -- 2.34.1