Refactor and rewrite.
Perl script is removed in favour of C. Stale documentation and initfiles are removed for now also. C interface works, but still needs work (for example, mDNS is missing) - see TODO.
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
/usr/ports/security/p5-Crypt-RSA
|
||||
/usr/ports/security/p5-Crypt-OpenSSL-RSA
|
||||
net/p5-Net-SDP
|
||||
/usr/ports/net/p5-IO-Socket-INET6
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
Type `make` to build the packet decoder, `hairtunes`.
|
||||
|
||||
You need the following installed:
|
||||
|
||||
* openssl
|
||||
* libao
|
||||
* Perl 5.10 or higher
|
||||
* Linux: avahi (or, for embedded systems, howl)
|
||||
* Windows/Mac OS X: Bonjour
|
||||
|
||||
Perl modules (install from CPAN if needed e.g. `perl -MCPAN -e 'install X'`):
|
||||
|
||||
* HTTP::Request
|
||||
* HTTP::Message
|
||||
* Crypt::OpenSSL::RSA
|
||||
* IO::Socket::INET6
|
||||
* Net::SDP
|
||||
|
||||
## Debian/Ubuntu:
|
||||
|
||||
apt-get install build-essential libssl-dev libcrypt-openssl-rsa-perl libao-dev libio-socket-inet6-perl libwww-perl avahi-utils pkg-config
|
||||
make
|
||||
perl shairport.pl
|
||||
|
||||
## Redhat/Fedora:
|
||||
|
||||
yum install openssl-devel libao libao-devel perl-Crypt-OpenSSL-RSA perl-IO-Socket-INET6 perl-libwww-perl avahi-tools
|
||||
make
|
||||
perl shairport.pl
|
||||
|
||||
## Gentoo/Funtoo
|
||||
|
||||
echo "media-libs/libao alsa" >> /etc/portage/package.use
|
||||
echo "net-dns/avahi mdnsresponder-compat" >> /etc/portage/package.use
|
||||
echo "dev-perl/HTTP-Message ~x86" >> /etc/portage/package.keywords
|
||||
echo "dev-perl/HTTP-Date ~x86" >> /etc/portage/package.keywords
|
||||
echo "dev-perl/Encode-Locale ~x86" >> /etc/portage/package.keywords
|
||||
echo "dev-perl/LWP-MediaTypes ~x86" >> /etc/portage/package.keywords
|
||||
emerge libao avahi dev-perl/HTTP-Message dev-perl/Crypt-OpenSSL-RSA dev-perl/IO-Socket-INET6
|
||||
rc-update add dbus default
|
||||
rc-update add avahi-daemon default
|
||||
/etc/init.d/dbus start
|
||||
/etc/init.d/avahi-daemon start
|
||||
make
|
||||
perl shairport.pl
|
||||
|
||||
## OpenBSD
|
||||
|
||||
pkg_add -r gmake libao avahi p5-libwww p5-Crypt-OpenSSL-RSA p5-IO-Socket-INET6
|
||||
gmake
|
||||
perl shairport.pl
|
||||
|
||||
## FreeBSD
|
||||
|
||||
pkg_add -r gmake libao avahi p5-libwww p5-Crypt-OpenSSL-RSA p5-Net-SDP p5-IO-Socket-INET6
|
||||
gmake
|
||||
# ipv6 does not work for me, but maybe works for you? might be due to not
|
||||
# having local ipv6 working dns names?
|
||||
# force ipv4 on FreeBSD with '-4' on the command line.
|
||||
perl shairport.pl -4
|
||||
|
||||
## Mac OS X:
|
||||
|
||||
* install XCode
|
||||
* install [Homebrew](https://github.com/mxcl/homebrew) or [MacPorts](http://www.macports.org/)
|
||||
* type:
|
||||
|
||||
$ export ARCHFLAGS="-arch x86_64" # (replace x86_64 by your arch)
|
||||
$ brew install pkg-config libao # for [Homebrew](https://github.com/mxcl/homebrew)
|
||||
$ port install pkgconfig libao # for [MacPorts](http://www.macports.org/)
|
||||
$ make
|
||||
$ perl -MCPAN -e 'install Crypt::OpenSSL::RSA' # (may require sudo)
|
||||
$ perl -MCPAN -e 'install IO::Socket::INET6' # (may require sudo)
|
||||
$ perl shairport.pl
|
||||
|
||||
Users of OS X 10.5 and below will need to install a newer Perl (via `port`/`brew`).
|
||||
|
||||
### How to run as a daemon on Mac 10.6
|
||||
|
||||
$ cp hairtunes shairport.pl /usr/local/bin
|
||||
$ vi /usr/local/bin/shairport.pl # change the path of hairtunes from ./hairtunes to /usr/local/bin/hairtunes
|
||||
$ mkdir -p ~/Library/LaunchAgents
|
||||
$ cp org.mafipulation.shairport.plist ~/Library/LaunchAgents/
|
||||
$ launchctl load org.mafipulation.shairport.plist
|
||||
$ launchctl unload org.mafipulation.shairport.plist # (to remove)
|
||||
|
||||
## Windows
|
||||
|
||||
* Download and install Cygwin.
|
||||
* During setup, select
|
||||
* openssl-devel
|
||||
* openssl
|
||||
* libao
|
||||
* libao-devel
|
||||
* gcc4
|
||||
* make
|
||||
* pkg-config
|
||||
* perl
|
||||
* Install [Bonjour for Windows](http://support.apple.com/kb/DL999)
|
||||
* Launch the Cygwin Bash shell
|
||||
* type:
|
||||
|
||||
$ make
|
||||
$ perl -MCPAN -e 'install Crypt::OpenSSL::RSA'
|
||||
$ perl -MCPAN -e 'install IO::Socket::INET6'
|
||||
$ perl shairport.pl
|
||||
@@ -1,40 +1,6 @@
|
||||
SRCS := shairport.c rtsp.c common.c rtp.c player.c alac.c $(wildcard audio_*.c)
|
||||
|
||||
MY_CFLAGS= $(shell pkg-config --cflags ao)
|
||||
MY_LDFLAGS= $(shell pkg-config --libs ao)
|
||||
ifeq ($(shell uname),FreeBSD)
|
||||
MY_LDFLAGS+= -lssl
|
||||
else
|
||||
MY_CFLAGS+= $(shell pkg-config --cflags openssl)
|
||||
MY_LDFLAGS+= $(shell pkg-config --libs openssl)
|
||||
endif
|
||||
|
||||
CFLAGS:=-O2 -Wall $(MY_CFLAGS)
|
||||
LDFLAGS:=-lm -lpthread $(MY_LDFLAGS)
|
||||
|
||||
OBJS=socketlib.o shairport.o alac.o hairtunes.o
|
||||
all: hairtunes shairport
|
||||
|
||||
hairtunes: hairtunes.c alac.o
|
||||
$(CC) $(CFLAGS) -DHAIRTUNES_STANDALONE hairtunes.c alac.o -o $@ $(LDFLAGS)
|
||||
|
||||
shairport: $(OBJS)
|
||||
$(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
-@rm -rf hairtunes shairport $(OBJS)
|
||||
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
prefix=/usr/local
|
||||
install: hairtunes shairport
|
||||
install -D -m 0755 hairtunes $(DESTDIR)$(prefix)/bin/hairtunes
|
||||
install -D -m 0755 shairport.pl $(DESTDIR)$(prefix)/bin/shairport.pl
|
||||
install -D -m 0755 shairport $(DESTDIR)$(prefix)/bin/shairport
|
||||
|
||||
.PHONY: all clean install
|
||||
|
||||
.SILENT: clean
|
||||
LIBS := -lcrypto -lm -lao -lpthread
|
||||
|
||||
shairport: $(SRCS)
|
||||
gcc -ggdb -Wall $(SRCS) $(LIBS) -o shairport
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
- make BUFFER_FRAMES adjustable
|
||||
- use getopt
|
||||
- copyright updates
|
||||
- signal handlers
|
||||
- pluggable audio drivers
|
||||
- audio usage
|
||||
- finish ao
|
||||
- makefile
|
||||
- documentation
|
||||
- PKGBUILD, initscript, systemd, launchd plist
|
||||
- mDNS
|
||||
- volume control
|
||||
- tidy debug printing
|
||||
@@ -55,7 +55,7 @@ static const int host_bigendian = 0;
|
||||
struct {signed int x:24;} se_struct_24;
|
||||
#define SignExtend24(val) (se_struct_24.x = val)
|
||||
|
||||
void allocate_buffers(alac_file *alac)
|
||||
void alac_allocate_buffers(alac_file *alac)
|
||||
{
|
||||
alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4);
|
||||
alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4);
|
||||
@@ -110,7 +110,7 @@ void alac_set_info(alac_file *alac, char *inputbuffer)
|
||||
if (!host_bigendian)
|
||||
_Swap32(alac->setinfo_8a_rate);
|
||||
|
||||
allocate_buffers(alac);
|
||||
alac_allocate_buffers(alac);
|
||||
|
||||
}
|
||||
|
||||
@@ -687,9 +687,9 @@ static void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b,
|
||||
|
||||
}
|
||||
|
||||
void decode_frame(alac_file *alac,
|
||||
unsigned char *inbuffer,
|
||||
void *outbuffer, int *outputsize)
|
||||
void alac_decode_frame(alac_file *alac,
|
||||
unsigned char *inbuffer,
|
||||
void *outbuffer, int *outputsize)
|
||||
{
|
||||
int channels;
|
||||
int32_t outputsamples = alac->setinfo_max_samples_per_frame;
|
||||
@@ -1104,7 +1104,7 @@ void decode_frame(alac_file *alac,
|
||||
}
|
||||
}
|
||||
|
||||
alac_file *create_alac(int samplesize, int numchannels)
|
||||
alac_file *alac_create(int samplesize, int numchannels)
|
||||
{
|
||||
alac_file *newfile = malloc(sizeof(alac_file));
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
#ifndef __ALAC__DECOMP_H
|
||||
#define __ALAC__DECOMP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct alac_file alac_file;
|
||||
|
||||
alac_file *create_alac(int samplesize, int numchannels);
|
||||
void decode_frame(alac_file *alac,
|
||||
unsigned char *inbuffer,
|
||||
void *outbuffer, int *outputsize);
|
||||
alac_file *alac_create(int samplesize, int numchannels);
|
||||
void alac_decode_frame(alac_file *alac,
|
||||
unsigned char *inbuffer,
|
||||
void *outbuffer, int *outputsize);
|
||||
void alac_set_info(alac_file *alac, char *inputbuffer);
|
||||
void allocate_buffers(alac_file *alac);
|
||||
void alac_allocate_buffers(alac_file *alac);
|
||||
|
||||
struct alac_file
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef _AUDIO_H
|
||||
#define _AUDIO_H
|
||||
|
||||
typedef struct {
|
||||
// start of program
|
||||
int (*init)(int argc, char **argv);
|
||||
// at end of program
|
||||
void (*deinit)(void);
|
||||
|
||||
void (*start)(int sample_rate);
|
||||
// block of samples
|
||||
void (*play)(short buf[], int samples);
|
||||
void (*stop)(void);
|
||||
|
||||
// may be NULL, in which case soft volume is applied
|
||||
void (*volume)(double vol);
|
||||
} audio_ops;
|
||||
|
||||
extern audio_ops audio_dummy;
|
||||
extern audio_ops audio_ao;
|
||||
|
||||
#endif //_AUDIO_H
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <memory.h>
|
||||
#include <ao/ao.h>
|
||||
#include "common.h"
|
||||
#include "audio.h"
|
||||
|
||||
ao_device *dev = NULL;
|
||||
|
||||
static int init(int argc, char **argv) {
|
||||
printf("ao: init\n");
|
||||
ao_initialize();
|
||||
ao_sample_format fmt;
|
||||
memset(&fmt, 0, sizeof(fmt));
|
||||
|
||||
fmt.bits = 16;
|
||||
fmt.rate = 44100;
|
||||
fmt.channels = 2;
|
||||
fmt.byte_format = AO_FMT_NATIVE;
|
||||
|
||||
int driver = ao_default_driver_id();
|
||||
|
||||
dev = ao_open_live(driver, &fmt, NULL);
|
||||
|
||||
return dev ? 0 : 1;
|
||||
}
|
||||
|
||||
static void deinit(void) {
|
||||
if (dev)
|
||||
ao_close(dev);
|
||||
}
|
||||
|
||||
static void start(int sample_rate) {
|
||||
printf("ao: start\n");
|
||||
if (sample_rate != 44100)
|
||||
die("unexpceted sample rate!\n");
|
||||
}
|
||||
|
||||
static void play(short buf[], int samples) {
|
||||
ao_play(dev, (char*)buf, samples*4);
|
||||
}
|
||||
|
||||
static void stop(void) {
|
||||
}
|
||||
|
||||
audio_ops audio_ao = {
|
||||
.init = &init,
|
||||
.deinit = &deinit,
|
||||
.start = &start,
|
||||
.stop = &stop,
|
||||
.play = &play,
|
||||
.volume = NULL
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include "audio.h"
|
||||
|
||||
int Fs;
|
||||
long long starttime, samples_played;
|
||||
|
||||
static int dummy_init(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dummy_deinit(void) {
|
||||
}
|
||||
|
||||
static void dummy_start(int sample_rate) {
|
||||
Fs = sample_rate;
|
||||
starttime = 0;
|
||||
samples_played = 0;
|
||||
printf("dummy audio output started at Fs=%d Hz\n", sample_rate);
|
||||
}
|
||||
|
||||
static void dummy_play(short buf[], int samples) {
|
||||
struct timeval tv;
|
||||
|
||||
// this is all a bit expensive but it's long-term stable.
|
||||
gettimeofday(&tv, NULL);
|
||||
|
||||
long long nowtime = tv.tv_usec + 1e6*tv.tv_sec;
|
||||
|
||||
if (!starttime)
|
||||
starttime = nowtime;
|
||||
|
||||
samples_played += samples;
|
||||
|
||||
long long finishtime = starttime + samples_played * 1e6 / Fs;
|
||||
|
||||
usleep(finishtime - nowtime);
|
||||
}
|
||||
|
||||
static void dummy_stop(void) {
|
||||
printf("dummy audio stopped\n");
|
||||
}
|
||||
|
||||
audio_ops audio_dummy = {
|
||||
.init = &dummy_init,
|
||||
.deinit = &dummy_deinit,
|
||||
.start = &dummy_start,
|
||||
.stop = &dummy_stop,
|
||||
.play = &dummy_play,
|
||||
.volume = NULL
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <memory.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
#include "common.h"
|
||||
|
||||
shairport_cfg config;
|
||||
|
||||
int debuglev = 1;
|
||||
|
||||
void die(char *format, ...) {
|
||||
fprintf(stderr, "FATAL: ");
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void warn(char *format, ...) {
|
||||
fprintf(stderr, "WARNING: ");
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void debug(char *format, ...) {
|
||||
if (!debuglev)
|
||||
return;
|
||||
fprintf(stderr, "DEBUG: ");
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
|
||||
char *base64_enc(uint8_t *input, int length) {
|
||||
int ret;
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
BIO_write(b64, input, length);
|
||||
ret = BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
|
||||
char *buf = (char *)malloc(bptr->length);
|
||||
if (bptr->length) {
|
||||
memcpy(buf, bptr->data, bptr->length-1);
|
||||
buf[bptr->length-1] = 0;
|
||||
}
|
||||
|
||||
BIO_free_all(bmem);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen) {
|
||||
int ret;
|
||||
BIO *bmem, *b64;
|
||||
int inlen = strlen(input);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
b64 = BIO_push(b64, bmem);
|
||||
|
||||
// Apple cut the padding off their challenges; restore it
|
||||
BIO_write(bmem, input, inlen);
|
||||
while (inlen++ & 3)
|
||||
BIO_write(bmem, "=", 1);
|
||||
ret = BIO_flush(bmem);
|
||||
|
||||
int bufsize = strlen(input)*3/4 + 1;
|
||||
uint8_t *buf = malloc(bufsize);
|
||||
int nread;
|
||||
|
||||
nread = BIO_read(b64, buf, bufsize);
|
||||
|
||||
BIO_free_all(bmem);
|
||||
|
||||
*outlen = nread;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char super_secret_key[] =
|
||||
"-----BEGIN RSA PRIVATE KEY-----\n"
|
||||
"MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\n"
|
||||
"wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\n"
|
||||
"wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\n"
|
||||
"/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\n"
|
||||
"UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\n"
|
||||
"BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\n"
|
||||
"LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\n"
|
||||
"NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\n"
|
||||
"lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\n"
|
||||
"aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\n"
|
||||
"a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\n"
|
||||
"oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\n"
|
||||
"oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\n"
|
||||
"k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\n"
|
||||
"AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\n"
|
||||
"cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\n"
|
||||
"54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\n"
|
||||
"17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\n"
|
||||
"1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\n"
|
||||
"LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\n"
|
||||
"2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\n"
|
||||
"-----END RSA PRIVATE KEY-----";
|
||||
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode) {
|
||||
static RSA *rsa = NULL;
|
||||
|
||||
if (!rsa) {
|
||||
BIO *bmem = BIO_new_mem_buf(super_secret_key, -1);
|
||||
rsa = PEM_read_bio_RSAPrivateKey(bmem, NULL, NULL, NULL);
|
||||
BIO_free(bmem);
|
||||
}
|
||||
|
||||
uint8_t *out = malloc(RSA_size(rsa));
|
||||
switch (mode) {
|
||||
case RSA_MODE_AUTH:
|
||||
*outlen = RSA_private_encrypt(inlen, input, out, rsa,
|
||||
RSA_PKCS1_PADDING);
|
||||
break;
|
||||
case RSA_MODE_KEY:
|
||||
*outlen = RSA_private_decrypt(inlen, input, out, rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
break;
|
||||
default:
|
||||
die("bad rsa mode");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef _COMMON_H
|
||||
#define _COMMON_H
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <stdint.h>
|
||||
#include "audio.h"
|
||||
|
||||
typedef struct {
|
||||
char *password;
|
||||
char hw_addr[6];
|
||||
int port;
|
||||
audio_ops *output;
|
||||
int buffer_start_fill;
|
||||
} shairport_cfg;
|
||||
|
||||
void die(char *format, ...);
|
||||
void warn(char *format, ...);
|
||||
void debug(char *format, ...);
|
||||
|
||||
uint8_t *base64_dec(char *input, int *outlen);
|
||||
char *base64_enc(uint8_t *input, int length);
|
||||
|
||||
#define RSA_MODE_AUTH (0)
|
||||
#define RSA_MODE_KEY (1)
|
||||
uint8_t *rsa_apply(uint8_t *input, int inlen, int *outlen, int mode);
|
||||
|
||||
extern shairport_cfg config;
|
||||
|
||||
#endif // _COMMON_H
|
||||
-964
@@ -1,964 +0,0 @@
|
||||
/*
|
||||
* HairTunes - RAOP packet handler and slave-clocked replay engine
|
||||
* Copyright (c) James Laird 2011
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <math.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "hairtunes.h"
|
||||
#include <sys/signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <ao/ao.h>
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
static int debug = 0;
|
||||
|
||||
#include "alac.h"
|
||||
|
||||
// and how full it needs to be to begin (must be <BUFFER_FRAMES)
|
||||
#define START_FILL 282
|
||||
|
||||
#define MAX_PACKET 2048
|
||||
|
||||
typedef unsigned short seq_t;
|
||||
|
||||
// global options (constant after init)
|
||||
static unsigned char aeskey[16], aesiv[16];
|
||||
static AES_KEY aes;
|
||||
static char *rtphost = 0;
|
||||
static int dataport = 0, controlport = 0, timingport = 0;
|
||||
static int fmtp[32];
|
||||
static int sampling_rate;
|
||||
static int frame_size;
|
||||
|
||||
static int buffer_start_fill;
|
||||
|
||||
static char *libao_driver = NULL;
|
||||
static char *libao_devicename = NULL;
|
||||
static char *libao_deviceid = NULL; // ao_options expects "char*"
|
||||
|
||||
// FIFO name and file handle
|
||||
static char *pipename = NULL;
|
||||
static int pipe_handle = -1;
|
||||
|
||||
#define FRAME_BYTES (4*frame_size)
|
||||
// maximal resampling shift - conservative
|
||||
#define OUTFRAME_BYTES (4*(frame_size+3))
|
||||
|
||||
|
||||
static alac_file *decoder_info;
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
static int fancy_resampling = 1;
|
||||
static SRC_STATE *src;
|
||||
#endif
|
||||
|
||||
static int init_rtp(void);
|
||||
static void init_buffer(void);
|
||||
static int init_output(void);
|
||||
static void rtp_request_resend(seq_t first, seq_t last);
|
||||
static void ab_resync(void);
|
||||
|
||||
// interthread variables
|
||||
// stdin->decoder
|
||||
static double volume = 1.0;
|
||||
static int fix_volume = 0x10000;
|
||||
static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
|
||||
|
||||
typedef struct audio_buffer_entry { // decoded audio packets
|
||||
int ready;
|
||||
signed short *data;
|
||||
} abuf_t;
|
||||
static abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
|
||||
|
||||
// mutex-protected variables
|
||||
static seq_t ab_read, ab_write;
|
||||
static int ab_buffering = 1, ab_synced = 0;
|
||||
static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t ab_buffer_ready = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
static void die(char *why) {
|
||||
fprintf(stderr, "FATAL: %s\n", why);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
#ifdef HAIRTUNES_STANDALONE
|
||||
static int hex2bin(unsigned char *buf, char *hex) {
|
||||
int i, j;
|
||||
if (strlen(hex) != 0x20)
|
||||
return 1;
|
||||
for (i=0; i<0x10; i++) {
|
||||
if (!sscanf(hex, "%2X", &j))
|
||||
return 1;
|
||||
hex += 2;
|
||||
*buf++ = j;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int init_decoder(void) {
|
||||
alac_file *alac;
|
||||
|
||||
frame_size = fmtp[1]; // stereo samples
|
||||
sampling_rate = fmtp[11];
|
||||
|
||||
int sample_size = fmtp[3];
|
||||
if (sample_size != 16)
|
||||
die("only 16-bit samples supported!");
|
||||
|
||||
alac = create_alac(sample_size, 2);
|
||||
if (!alac)
|
||||
return 1;
|
||||
decoder_info = alac;
|
||||
|
||||
alac->setinfo_max_samples_per_frame = frame_size;
|
||||
alac->setinfo_7a = fmtp[2];
|
||||
alac->setinfo_sample_size = sample_size;
|
||||
alac->setinfo_rice_historymult = fmtp[4];
|
||||
alac->setinfo_rice_initialhistory = fmtp[5];
|
||||
alac->setinfo_rice_kmodifier = fmtp[6];
|
||||
alac->setinfo_7f = fmtp[7];
|
||||
alac->setinfo_80 = fmtp[8];
|
||||
alac->setinfo_82 = fmtp[9];
|
||||
alac->setinfo_86 = fmtp[10];
|
||||
alac->setinfo_8a_rate = fmtp[11];
|
||||
allocate_buffers(alac);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hairtunes_init(char *pAeskey, char *pAesiv, char *fmtpstr, int pCtrlPort, int pTimingPort,
|
||||
int pDataPort, char *pRtpHost, char*pPipeName, char *pLibaoDriver, char *pLibaoDeviceName, char *pLibaoDeviceId,
|
||||
int bufStartFill)
|
||||
{
|
||||
if(pAeskey != NULL)
|
||||
memcpy(aeskey, pAeskey, sizeof(aeskey));
|
||||
if(pAesiv != NULL)
|
||||
memcpy(aesiv, pAesiv, sizeof(aesiv));
|
||||
if(pRtpHost != NULL)
|
||||
rtphost = pRtpHost;
|
||||
if(pPipeName != NULL)
|
||||
pipename = pPipeName;
|
||||
if(pLibaoDriver != NULL)
|
||||
libao_driver = pLibaoDriver;
|
||||
if(pLibaoDeviceName != NULL)
|
||||
libao_devicename = pLibaoDeviceName;
|
||||
if(pLibaoDeviceId != NULL)
|
||||
libao_deviceid = pLibaoDeviceId;
|
||||
|
||||
controlport = pCtrlPort;
|
||||
timingport = pTimingPort;
|
||||
dataport = pDataPort;
|
||||
if(bufStartFill < 0)
|
||||
bufStartFill = START_FILL;
|
||||
buffer_start_fill = bufStartFill;
|
||||
|
||||
AES_set_decrypt_key(aeskey, 128, &aes);
|
||||
|
||||
memset(fmtp, 0, sizeof(fmtp));
|
||||
int i = 0;
|
||||
char *arg;
|
||||
while ( (arg = strsep(&fmtpstr, " \t")) )
|
||||
fmtp[i++] = atoi(arg);
|
||||
|
||||
init_decoder();
|
||||
init_buffer();
|
||||
init_rtp(); // open a UDP listen port and start a listener; decode into ring buffer
|
||||
fflush(stdout);
|
||||
init_output(); // resample and output from ring buffer
|
||||
|
||||
char line[128];
|
||||
int in_line = 0;
|
||||
int n;
|
||||
double f;
|
||||
while (fgets(line + in_line, sizeof(line) - in_line, stdin)) {
|
||||
n = strlen(line);
|
||||
if (line[n-1] != '\n') {
|
||||
in_line = strlen(line) - 1;
|
||||
if (n == sizeof(line)-1)
|
||||
in_line = 0;
|
||||
continue;
|
||||
}
|
||||
if (sscanf(line, "vol: %lf\n", &f)) {
|
||||
assert(f<=0);
|
||||
if (debug)
|
||||
fprintf(stderr, "VOL: %lf\n", f);
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
volume = pow(10.0,0.05*f);
|
||||
fix_volume = 65536.0 * volume;
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(line, "exit\n")) {
|
||||
exit(0);
|
||||
}
|
||||
if (!strcmp(line, "flush\n")) {
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
ab_resync();
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
if (debug)
|
||||
fprintf(stderr, "FLUSH\n");
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "bye!\n");
|
||||
fflush(stderr);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#ifdef AF_INET6
|
||||
static int ipv4_only = 0;
|
||||
#else
|
||||
static int ipv4_only = 1;
|
||||
#endif
|
||||
|
||||
#ifdef HAIRTUNES_STANDALONE
|
||||
int main(int argc, char **argv) {
|
||||
char *hexaeskey = 0, *hexaesiv = 0;
|
||||
char *fmtpstr = 0;
|
||||
char *arg;
|
||||
assert(RAND_MAX >= 0x10000); // XXX move this to compile time
|
||||
while ( (arg = *++argv) ) {
|
||||
if (!strcasecmp(arg, "iv")) {
|
||||
hexaesiv = *++argv;
|
||||
argc--;
|
||||
} else
|
||||
if (!strcasecmp(arg, "ipv4_only")) {
|
||||
ipv4_only = 1;
|
||||
} else
|
||||
if (!strcasecmp(arg, "key")) {
|
||||
hexaeskey = *++argv;
|
||||
argc--;
|
||||
} else
|
||||
if (!strcasecmp(arg, "fmtp")) {
|
||||
fmtpstr = *++argv;
|
||||
} else
|
||||
if (!strcasecmp(arg, "cport")) {
|
||||
controlport = atoi(*++argv);
|
||||
} else
|
||||
if (!strcasecmp(arg, "tport")) {
|
||||
timingport = atoi(*++argv);
|
||||
} else
|
||||
if (!strcasecmp(arg, "dport")) {
|
||||
dataport = atoi(*++argv);
|
||||
} else
|
||||
if (!strcasecmp(arg, "host")) {
|
||||
rtphost = *++argv;
|
||||
} else
|
||||
if (!strcasecmp(arg, "pipe")) {
|
||||
if (libao_driver || libao_devicename || libao_deviceid ) {
|
||||
die("Option 'pipe' may not be combined with 'ao_driver', 'ao_devicename' or 'ao_deviceid'");
|
||||
}
|
||||
|
||||
pipename = *++argv;
|
||||
} else
|
||||
if (!strcasecmp(arg, "ao_driver")) {
|
||||
if (pipename) {
|
||||
die("Option 'ao_driver' may not be combined with 'pipe'");
|
||||
}
|
||||
|
||||
libao_driver = *++argv;
|
||||
} else
|
||||
if (!strcasecmp(arg, "ao_devicename")) {
|
||||
if (pipename || libao_deviceid ) {
|
||||
die("Option 'ao_devicename' may not be combined with 'pipe' or 'ao_deviceid'");
|
||||
}
|
||||
|
||||
libao_devicename = *++argv;
|
||||
} else
|
||||
if (!strcasecmp(arg, "ao_deviceid")) {
|
||||
if (pipename || libao_devicename) {
|
||||
die("Option 'ao_deviceid' may not be combined with 'pipe' or 'ao_devicename'");
|
||||
}
|
||||
|
||||
libao_deviceid = *++argv;
|
||||
}
|
||||
#ifdef FANCY_RESAMPLING
|
||||
else
|
||||
if (!strcasecmp(arg, "resamp")) {
|
||||
fancy_resampling = atoi(*++argv);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!hexaeskey || !hexaesiv)
|
||||
die("Must supply AES key and IV!");
|
||||
|
||||
if (hex2bin(aesiv, hexaesiv))
|
||||
die("can't understand IV");
|
||||
if (hex2bin(aeskey, hexaeskey))
|
||||
die("can't understand key");
|
||||
return hairtunes_init(NULL, NULL, fmtpstr, controlport, timingport, dataport,
|
||||
NULL, NULL, NULL, NULL, NULL, START_FILL);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void init_buffer(void) {
|
||||
int i;
|
||||
for (i=0; i<BUFFER_FRAMES; i++)
|
||||
audio_buffer[i].data = malloc(OUTFRAME_BYTES);
|
||||
ab_resync();
|
||||
}
|
||||
|
||||
static void ab_resync(void) {
|
||||
int i;
|
||||
for (i=0; i<BUFFER_FRAMES; i++)
|
||||
audio_buffer[i].ready = 0;
|
||||
ab_synced = 0;
|
||||
ab_buffering = 1;
|
||||
}
|
||||
|
||||
// the sequence numbers will wrap pretty often.
|
||||
// this returns true if the second arg is after the first
|
||||
static inline int seq_order(seq_t a, seq_t b) {
|
||||
signed short d = b - a;
|
||||
return d > 0;
|
||||
}
|
||||
|
||||
static void alac_decode(short *dest, char *buf, int len) {
|
||||
unsigned char packet[MAX_PACKET];
|
||||
assert(len<=MAX_PACKET);
|
||||
|
||||
unsigned char iv[16];
|
||||
int aeslen = len & ~0xf;
|
||||
memcpy(iv, aesiv, sizeof(iv));
|
||||
AES_cbc_encrypt((unsigned char*)buf, packet, aeslen, &aes, iv, AES_DECRYPT);
|
||||
memcpy(packet+aeslen, buf+aeslen, len-aeslen);
|
||||
|
||||
int outsize;
|
||||
|
||||
decode_frame(decoder_info, packet, dest, &outsize);
|
||||
|
||||
assert(outsize == FRAME_BYTES);
|
||||
}
|
||||
|
||||
static void buffer_put_packet(seq_t seqno, char *data, int len) {
|
||||
abuf_t *abuf = 0;
|
||||
short buf_fill;
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
if (!ab_synced) {
|
||||
ab_write = seqno;
|
||||
ab_read = seqno-1;
|
||||
ab_synced = 1;
|
||||
}
|
||||
if (seqno == ab_write+1) { // expected packet
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
ab_write = seqno;
|
||||
} else if (seq_order(ab_write, seqno)) { // newer than expected
|
||||
rtp_request_resend(ab_write+1, seqno-1);
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
ab_write = seqno;
|
||||
} else if (seq_order(ab_read, seqno)) { // late but not yet played
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
} else { // too late.
|
||||
fprintf(stderr, "\nlate packet %04X (%04X:%04X)\n", seqno, ab_read, ab_write);
|
||||
}
|
||||
buf_fill = ab_write - ab_read;
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
if (abuf) {
|
||||
alac_decode(abuf->data, data, len);
|
||||
abuf->ready = 1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
if (ab_buffering && buf_fill >= buffer_start_fill) {
|
||||
ab_buffering = 0;
|
||||
pthread_cond_signal(&ab_buffer_ready);
|
||||
}
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
}
|
||||
|
||||
static int rtp_sockets[2]; // data, control
|
||||
#ifdef AF_INET6
|
||||
static struct sockaddr_in6 rtp_client6;
|
||||
#endif
|
||||
static struct sockaddr_in rtp_client4;
|
||||
|
||||
struct sockaddr * get_rtp_client_sockaddr(void) {
|
||||
|
||||
#ifdef AF_INET6
|
||||
if (!ipv4_only) {
|
||||
return ((struct sockaddr*)&rtp_client6);
|
||||
}
|
||||
#endif
|
||||
return ((struct sockaddr*)&rtp_client4);
|
||||
}
|
||||
|
||||
socklen_t get_rtp_sockaddr_len(void) {
|
||||
#ifdef AF_INET6
|
||||
if (!ipv4_only) {
|
||||
return (sizeof(&rtp_client6));
|
||||
}
|
||||
#endif
|
||||
return (sizeof(rtp_client4));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void *rtp_thread_func(void *arg) {
|
||||
socklen_t si_len;
|
||||
char packet[MAX_PACKET];
|
||||
char *pktp;
|
||||
seq_t seqno;
|
||||
ssize_t plen;
|
||||
int sock = rtp_sockets[0], csock = rtp_sockets[1];
|
||||
int readsock;
|
||||
char type;
|
||||
struct sockaddr *rtp_client;
|
||||
|
||||
rtp_client = get_rtp_client_sockaddr();
|
||||
si_len = get_rtp_sockaddr_len();
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sock, &fds);
|
||||
FD_SET(csock, &fds);
|
||||
|
||||
while (select(csock>sock ? csock+1 : sock+1, &fds, 0, 0, 0)!=-1) {
|
||||
if (FD_ISSET(sock, &fds)) {
|
||||
readsock = sock;
|
||||
} else {
|
||||
readsock = csock;
|
||||
}
|
||||
FD_SET(sock, &fds);
|
||||
FD_SET(csock, &fds);
|
||||
|
||||
plen = recvfrom(readsock, packet, sizeof(packet), 0, rtp_client, &si_len);
|
||||
if (plen < 0)
|
||||
continue;
|
||||
assert(plen<=MAX_PACKET);
|
||||
|
||||
type = packet[1] & ~0x80;
|
||||
if (type == 0x60 || type == 0x56) { // audio data / resend
|
||||
pktp = packet;
|
||||
if (type==0x56) {
|
||||
pktp += 4;
|
||||
plen -= 4;
|
||||
}
|
||||
seqno = ntohs(*(unsigned short *)(pktp+2));
|
||||
|
||||
// adjust pointer and length
|
||||
pktp += 12;
|
||||
plen -= 12;
|
||||
|
||||
// check if packet contains enough content to be reasonable
|
||||
if (plen >= 16) {
|
||||
buffer_put_packet(seqno, pktp, plen);
|
||||
} else {
|
||||
// resync?
|
||||
if (type == 0x56 && seqno == 0) {
|
||||
fprintf(stderr, "Suspected resync request packet received. Initiating resync.\n");
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
ab_resync();
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void rtp_request_resend(seq_t first, seq_t last) {
|
||||
if (seq_order(last, first))
|
||||
return;
|
||||
|
||||
fprintf(stderr, "requesting resend on %d packets (port %d)\n", last-first+1, controlport);
|
||||
|
||||
char req[8]; // *not* a standard RTCP NACK
|
||||
req[0] = 0x80;
|
||||
req[1] = 0x55|0x80; // Apple 'resend'
|
||||
*(unsigned short *)(req+2) = htons(1); // our seqnum
|
||||
*(unsigned short *)(req+4) = htons(first); // missed seqnum
|
||||
*(unsigned short *)(req+6) = htons(last-first+1); // count
|
||||
|
||||
struct sockaddr *rtp_client = get_rtp_client_sockaddr();
|
||||
|
||||
if (ipv4_only) {
|
||||
((struct sockaddr_in *)rtp_client)->sin_port = htons(controlport);
|
||||
}
|
||||
#ifdef AF_INET6
|
||||
else {
|
||||
((struct sockaddr_in6 *)rtp_client)->sin6_port = htons(controlport);
|
||||
}
|
||||
#endif
|
||||
sendto(rtp_sockets[1], req, sizeof(req), 0, rtp_client, get_rtp_sockaddr_len());
|
||||
}
|
||||
|
||||
|
||||
static int init_rtp(void) {
|
||||
struct sockaddr_in si;
|
||||
int type = AF_INET;
|
||||
struct sockaddr* si_p = (struct sockaddr*)&si;
|
||||
socklen_t si_len = sizeof(si);
|
||||
unsigned short *sin_port = &si.sin_port;
|
||||
memset(&si, 0, sizeof(si));
|
||||
#ifdef AF_INET6
|
||||
struct sockaddr_in6 si6;
|
||||
|
||||
if (!ipv4_only) {
|
||||
type = AF_INET6;
|
||||
si_p = (struct sockaddr*)&si6;
|
||||
si_len = sizeof(si6);
|
||||
sin_port = &si6.sin6_port;
|
||||
memset(&si6, 0, sizeof(si6));
|
||||
}
|
||||
#endif
|
||||
|
||||
si.sin_family = AF_INET;
|
||||
#ifdef SIN_LEN
|
||||
si.sin_len = sizeof(si);
|
||||
#endif
|
||||
si.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
#ifdef AF_INET6
|
||||
if (!ipv4_only) {
|
||||
si6.sin6_family = AF_INET6;
|
||||
#ifdef SIN6_LEN
|
||||
si6.sin6_len = sizeof(si);
|
||||
#endif
|
||||
si6.sin6_addr = in6addr_any;
|
||||
si6.sin6_flowinfo = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int sock = -1, csock = -1; // data and control (we treat the streams the same here)
|
||||
unsigned short port = 6000;
|
||||
while(1) {
|
||||
if(sock < 0)
|
||||
sock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
|
||||
#ifdef AF_INET6
|
||||
if(sock==-1 && type == AF_INET6) {
|
||||
// try fallback to IPv4
|
||||
type = AF_INET;
|
||||
si_p = (struct sockaddr*)&si;
|
||||
si_len = sizeof(si);
|
||||
sin_port = &si.sin_port;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (sock==-1)
|
||||
die("Can't create data socket!");
|
||||
|
||||
if(csock < 0)
|
||||
csock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (csock==-1)
|
||||
die("Can't create control socket!");
|
||||
|
||||
*sin_port = htons(port);
|
||||
int bind1 = bind(sock, si_p, si_len);
|
||||
*sin_port = htons(port + 1);
|
||||
int bind2 = bind(csock, si_p, si_len);
|
||||
|
||||
if(bind1 != -1 && bind2 != -1) break;
|
||||
if(bind1 != -1) { close(sock); sock = -1; }
|
||||
if(bind2 != -1) { close(csock); csock = -1; }
|
||||
|
||||
port += 3;
|
||||
}
|
||||
|
||||
printf("port: %d\n", port); // let our handler know where we end up listening
|
||||
printf("cport: %d\n", port+1);
|
||||
|
||||
pthread_t rtp_thread;
|
||||
rtp_sockets[0] = sock;
|
||||
rtp_sockets[1] = csock;
|
||||
pthread_create(&rtp_thread, NULL, rtp_thread_func, (void *)rtp_sockets);
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
static short lcg_rand(void) {
|
||||
static unsigned long lcg_prev = 12345;
|
||||
lcg_prev = lcg_prev * 69069 + 3;
|
||||
return lcg_prev & 0xffff;
|
||||
}
|
||||
|
||||
static inline short dithered_vol(short sample) {
|
||||
static short rand_a, rand_b;
|
||||
long out;
|
||||
|
||||
out = (long)sample * fix_volume;
|
||||
if (fix_volume < 0x10000) {
|
||||
rand_b = rand_a;
|
||||
rand_a = lcg_rand();
|
||||
out += rand_a;
|
||||
out -= rand_b;
|
||||
}
|
||||
return out>>16;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
double hist[2];
|
||||
double a[2];
|
||||
double b[3];
|
||||
} biquad_t;
|
||||
|
||||
static void biquad_init(biquad_t *bq, double a[], double b[]) {
|
||||
bq->hist[0] = bq->hist[1] = 0.0;
|
||||
memcpy(bq->a, a, 2*sizeof(double));
|
||||
memcpy(bq->b, b, 3*sizeof(double));
|
||||
}
|
||||
|
||||
static void biquad_lpf(biquad_t *bq, double freq, double Q) {
|
||||
double w0 = 2.0 * M_PI * freq * frame_size / (double)sampling_rate;
|
||||
double alpha = sin(w0)/(2.0*Q);
|
||||
|
||||
double a_0 = 1.0 + alpha;
|
||||
double b[3], a[2];
|
||||
b[0] = (1.0-cos(w0))/(2.0*a_0);
|
||||
b[1] = (1.0-cos(w0))/a_0;
|
||||
b[2] = b[0];
|
||||
a[0] = -2.0*cos(w0)/a_0;
|
||||
a[1] = (1-alpha)/a_0;
|
||||
|
||||
biquad_init(bq, a, b);
|
||||
}
|
||||
|
||||
static double biquad_filt(biquad_t *bq, double in) {
|
||||
double w = in - bq->a[0]*bq->hist[0] - bq->a[1]*bq->hist[1];
|
||||
double out = bq->b[1]*bq->hist[0] + bq->b[2]*bq->hist[1] + bq->b[0]*w;
|
||||
bq->hist[1] = bq->hist[0];
|
||||
bq->hist[0] = w;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static double bf_playback_rate = 1.0;
|
||||
|
||||
static double bf_est_drift = 0.0; // local clock is slower by
|
||||
static biquad_t bf_drift_lpf;
|
||||
static double bf_est_err = 0.0, bf_last_err;
|
||||
static biquad_t bf_err_lpf, bf_err_deriv_lpf;
|
||||
static double desired_fill;
|
||||
static int fill_count;
|
||||
|
||||
static void bf_est_reset(short fill) {
|
||||
biquad_lpf(&bf_drift_lpf, 1.0/180.0, 0.3);
|
||||
biquad_lpf(&bf_err_lpf, 1.0/10.0, 0.25);
|
||||
biquad_lpf(&bf_err_deriv_lpf, 1.0/2.0, 0.2);
|
||||
fill_count = 0;
|
||||
bf_playback_rate = 1.0;
|
||||
bf_est_err = bf_last_err = 0;
|
||||
desired_fill = fill_count = 0;
|
||||
}
|
||||
|
||||
static void bf_est_update(short fill) {
|
||||
if (fill_count < 1000) {
|
||||
desired_fill += (double)fill/1000.0;
|
||||
fill_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
#define CONTROL_A (1e-4)
|
||||
#define CONTROL_B (1e-1)
|
||||
|
||||
double buf_delta = fill - desired_fill;
|
||||
bf_est_err = biquad_filt(&bf_err_lpf, buf_delta);
|
||||
double err_deriv = biquad_filt(&bf_err_deriv_lpf, bf_est_err - bf_last_err);
|
||||
double adj_error = CONTROL_A * bf_est_err;
|
||||
|
||||
bf_est_drift = biquad_filt(&bf_drift_lpf, CONTROL_B*(adj_error + err_deriv) + bf_est_drift);
|
||||
|
||||
if (debug)
|
||||
fprintf(stderr, "bf %d err %f drift %f desiring %f ed %f estd %f\n",
|
||||
fill, bf_est_err, bf_est_drift, desired_fill, err_deriv, err_deriv + adj_error);
|
||||
bf_playback_rate = 1.0 + adj_error + bf_est_drift;
|
||||
|
||||
bf_last_err = bf_est_err;
|
||||
}
|
||||
|
||||
// get the next frame, when available. return 0 if underrun/stream reset.
|
||||
static short *buffer_get_frame(void) {
|
||||
short buf_fill;
|
||||
seq_t read;
|
||||
abuf_t *abuf = 0;
|
||||
unsigned short next;
|
||||
int i;
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
|
||||
buf_fill = ab_write - ab_read;
|
||||
if (buf_fill < 1 || !ab_synced || ab_buffering) { // init or underrun. stop and wait
|
||||
if (ab_synced)
|
||||
fprintf(stderr, "\nunderrun.\n");
|
||||
|
||||
ab_buffering = 1;
|
||||
pthread_cond_wait(&ab_buffer_ready, &ab_mutex);
|
||||
ab_read++;
|
||||
buf_fill = ab_write - ab_read;
|
||||
bf_est_reset(buf_fill);
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (buf_fill >= BUFFER_FRAMES) { // overrunning! uh-oh. restart at a sane distance
|
||||
fprintf(stderr, "\noverrun.\n");
|
||||
ab_read = ab_write - buffer_start_fill;
|
||||
}
|
||||
read = ab_read;
|
||||
ab_read++;
|
||||
buf_fill = ab_write - ab_read;
|
||||
bf_est_update(buf_fill);
|
||||
|
||||
// check if t+16, t+32, t+64, t+128, ... (START_FILL / 2)
|
||||
// packets have arrived... last-chance resend
|
||||
if (!ab_buffering) {
|
||||
for (i = 16; i < (START_FILL / 2); i = (i * 2)) {
|
||||
next = ab_read + i;
|
||||
abuf = audio_buffer + BUFIDX(next);
|
||||
if (!abuf->ready) {
|
||||
rtp_request_resend(next, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abuf_t *curframe = audio_buffer + BUFIDX(read);
|
||||
if (!curframe->ready) {
|
||||
fprintf(stderr, "\nmissing frame.\n");
|
||||
memset(curframe->data, 0, FRAME_BYTES);
|
||||
}
|
||||
curframe->ready = 0;
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
return curframe->data;
|
||||
}
|
||||
|
||||
static int stuff_buffer(double playback_rate, short *inptr, short *outptr) {
|
||||
int i;
|
||||
int stuffsamp = frame_size;
|
||||
int stuff = 0;
|
||||
double p_stuff;
|
||||
|
||||
p_stuff = 1.0 - pow(1.0 - fabs(playback_rate-1.0), frame_size);
|
||||
|
||||
if (rand() < p_stuff * RAND_MAX) {
|
||||
stuff = playback_rate > 1.0 ? -1 : 1;
|
||||
stuffsamp = rand() % (frame_size - 1);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
for (i=0; i<stuffsamp; i++) { // the whole frame, if no stuffing
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
};
|
||||
if (stuff) {
|
||||
if (stuff==1) {
|
||||
if (debug)
|
||||
fprintf(stderr, "+++++++++\n");
|
||||
// interpolate one sample
|
||||
*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
|
||||
*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
|
||||
} else if (stuff==-1) {
|
||||
if (debug)
|
||||
fprintf(stderr, "---------\n");
|
||||
inptr++;
|
||||
inptr++;
|
||||
}
|
||||
for (i=stuffsamp; i<frame_size + stuff; i++) {
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
|
||||
return frame_size + stuff;
|
||||
}
|
||||
|
||||
static void *audio_thread_func(void *arg) {
|
||||
ao_device* dev = arg;
|
||||
int play_samples;
|
||||
|
||||
signed short buf_fill __attribute__((unused));
|
||||
signed short *inbuf, *outbuf, *silence;
|
||||
outbuf = malloc(OUTFRAME_BYTES);
|
||||
silence = malloc(OUTFRAME_BYTES);
|
||||
int i;
|
||||
|
||||
for (i=0; i<OUTFRAME_BYTES/2; i++) {
|
||||
silence[i] = 0;
|
||||
}
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
float *frame, *outframe;
|
||||
SRC_DATA srcdat;
|
||||
if (fancy_resampling) {
|
||||
frame = malloc(frame_size*2*sizeof(float));
|
||||
outframe = malloc(2*frame_size*2*sizeof(float));
|
||||
|
||||
srcdat.data_in = frame;
|
||||
srcdat.data_out = outframe;
|
||||
srcdat.input_frames = FRAME_BYTES;
|
||||
srcdat.output_frames = 2*FRAME_BYTES;
|
||||
srcdat.src_ratio = 1.0;
|
||||
srcdat.end_of_input = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
if (ab_buffering) {
|
||||
inbuf = silence;
|
||||
} else {
|
||||
do {
|
||||
inbuf = buffer_get_frame();
|
||||
} while (!inbuf);
|
||||
}
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
if (fancy_resampling) {
|
||||
int i;
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
for (i=0; i<2*FRAME_BYTES; i++) {
|
||||
frame[i] = (float)inbuf[i] / 32768.0;
|
||||
frame[i] *= volume;
|
||||
}
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
srcdat.src_ratio = bf_playback_rate;
|
||||
src_process(src, &srcdat);
|
||||
assert(srcdat.input_frames_used == FRAME_BYTES);
|
||||
src_float_to_short_array(outframe, outbuf, FRAME_BYTES*2);
|
||||
play_samples = srcdat.output_frames_gen;
|
||||
} else
|
||||
#endif
|
||||
|
||||
play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf);
|
||||
|
||||
if (pipename) {
|
||||
if (pipe_handle == -1) {
|
||||
// attempt to open pipe - block if there are no readers
|
||||
pipe_handle = open(pipename, O_WRONLY);
|
||||
}
|
||||
|
||||
// only write if pipe open (there's a reader)
|
||||
if (pipe_handle != -1) {
|
||||
if (write(pipe_handle, outbuf, play_samples*4) == -1) {
|
||||
// write failed - do anything here?
|
||||
// SIGPIPE is handled elsewhere...
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ao_play(dev, (char *)outbuf, play_samples*4);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NUM_CHANNELS 2
|
||||
|
||||
static void handle_broken_fifo() {
|
||||
close(pipe_handle);
|
||||
pipe_handle = -1;
|
||||
}
|
||||
|
||||
static void init_pipe(const char* pipe) {
|
||||
// make the FIFO and catch the broken pipe signal
|
||||
mknod(pipe, S_IFIFO | 0644, 0);
|
||||
signal(SIGPIPE, handle_broken_fifo);
|
||||
}
|
||||
|
||||
static void* init_ao(void) {
|
||||
ao_initialize();
|
||||
|
||||
int driver;
|
||||
if (libao_driver) {
|
||||
// if a libao driver is specified on the command line, use that
|
||||
driver = ao_driver_id(libao_driver);
|
||||
if (driver == -1) {
|
||||
die("Could not find requested ao driver");
|
||||
}
|
||||
} else {
|
||||
// otherwise choose the default
|
||||
driver = ao_default_driver_id();
|
||||
}
|
||||
|
||||
ao_sample_format fmt;
|
||||
memset(&fmt, 0, sizeof(fmt));
|
||||
|
||||
fmt.bits = 16;
|
||||
fmt.rate = sampling_rate;
|
||||
fmt.channels = NUM_CHANNELS;
|
||||
fmt.byte_format = AO_FMT_NATIVE;
|
||||
|
||||
ao_option *ao_opts = NULL;
|
||||
if(libao_deviceid) {
|
||||
ao_append_option(&ao_opts, "id", libao_deviceid);
|
||||
} else if(libao_devicename){
|
||||
ao_append_option(&ao_opts, "dev", libao_devicename);
|
||||
// Old libao versions (for example, 0.8.8) only support
|
||||
// "dsp" instead of "dev".
|
||||
ao_append_option(&ao_opts, "dsp", libao_devicename);
|
||||
}
|
||||
|
||||
ao_device *dev = ao_open_live(driver, &fmt, ao_opts);
|
||||
if (dev == NULL) {
|
||||
die("Could not open ao device");
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static int init_output(void) {
|
||||
void* arg = 0;
|
||||
|
||||
if (pipename) {
|
||||
init_pipe(pipename);
|
||||
} else {
|
||||
arg = init_ao();
|
||||
}
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
int err;
|
||||
if (fancy_resampling)
|
||||
src = src_new(SRC_SINC_MEDIUM_QUALITY, 2, &err);
|
||||
else
|
||||
src = 0;
|
||||
#endif
|
||||
|
||||
pthread_t audio_thread;
|
||||
pthread_create(&audio_thread, NULL, audio_thread_func, arg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
#ifndef _HAIRTUNES_H_
|
||||
#define _HAIRTUNES_H_
|
||||
int hairtunes_init(char *pAeskey, char *pAesiv, char *fmtpstr, int pCtrlPort, int pTimingPort,
|
||||
int pDataPort, char *pRtpHost, char*pPipeName, char *pLibaoDriver, char *pLibaoDeviceName, char *pLibaoDeviceId,
|
||||
int bufStartFill);
|
||||
|
||||
// default buffer size
|
||||
// needs to be a power of 2 because of the way BUFIDX(seqno) works
|
||||
#define BUFFER_FRAMES 512
|
||||
|
||||
#endif
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>org.mafipulation.shairport</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/local/bin/shairport.pl</string>
|
||||
<string>--apname</string>
|
||||
<string>RJ</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,478 @@
|
||||
// slave-clocked stream player
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <math.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/signal.h>
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
#include "rtp.h"
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
|
||||
#include "alac.h"
|
||||
|
||||
// parameters from the source
|
||||
static unsigned char *aesiv;
|
||||
static AES_KEY aes;
|
||||
static int sampling_rate, frame_size;
|
||||
|
||||
#define FRAME_BYTES (4*frame_size)
|
||||
// maximal resampling shift - conservative
|
||||
#define OUTFRAME_BYTES (4*(frame_size+3))
|
||||
|
||||
static pthread_t player_thread;
|
||||
static int please_stop;
|
||||
|
||||
static alac_file *decoder_info;
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
static int fancy_resampling = 1;
|
||||
static SRC_STATE *src;
|
||||
#endif
|
||||
|
||||
|
||||
// interthread variables
|
||||
static double volume = 1.0;
|
||||
static int fix_volume = 0x10000;
|
||||
static pthread_mutex_t vol_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
// default buffer size
|
||||
// needs to be a power of 2 because of the way BUFIDX(seqno) works
|
||||
#define BUFFER_FRAMES 512
|
||||
#define START_FILL 250
|
||||
#define MAX_PACKET 2048
|
||||
|
||||
typedef struct audio_buffer_entry { // decoded audio packets
|
||||
int ready;
|
||||
signed short *data;
|
||||
} abuf_t;
|
||||
static abuf_t audio_buffer[BUFFER_FRAMES];
|
||||
#define BUFIDX(seqno) ((seq_t)(seqno) % BUFFER_FRAMES)
|
||||
|
||||
// mutex-protected variables
|
||||
static seq_t ab_read, ab_write;
|
||||
static int ab_buffering = 1, ab_synced = 0;
|
||||
static pthread_mutex_t ab_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t ab_buffer_ready = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
static void ab_resync(void) {
|
||||
int i;
|
||||
for (i=0; i<BUFFER_FRAMES; i++)
|
||||
audio_buffer[i].ready = 0;
|
||||
ab_synced = 0;
|
||||
ab_buffering = 1;
|
||||
}
|
||||
|
||||
// the sequence numbers will wrap pretty often.
|
||||
// this returns true if the second arg is after the first
|
||||
static inline int seq_order(seq_t a, seq_t b) {
|
||||
signed short d = b - a;
|
||||
return d > 0;
|
||||
}
|
||||
|
||||
static void alac_decode(short *dest, uint8_t *buf, int len) {
|
||||
unsigned char packet[MAX_PACKET];
|
||||
assert(len<=MAX_PACKET);
|
||||
|
||||
unsigned char iv[16];
|
||||
int aeslen = len & ~0xf;
|
||||
memcpy(iv, aesiv, sizeof(iv));
|
||||
AES_cbc_encrypt(buf, packet, aeslen, &aes, iv, AES_DECRYPT);
|
||||
memcpy(packet+aeslen, buf+aeslen, len-aeslen);
|
||||
|
||||
int outsize;
|
||||
|
||||
alac_decode_frame(decoder_info, packet, dest, &outsize);
|
||||
|
||||
assert(outsize == FRAME_BYTES);
|
||||
}
|
||||
|
||||
|
||||
static int init_decoder(int32_t fmtp[12]) {
|
||||
alac_file *alac;
|
||||
|
||||
frame_size = fmtp[1]; // stereo samples
|
||||
sampling_rate = fmtp[11];
|
||||
|
||||
int sample_size = fmtp[3];
|
||||
if (sample_size != 16)
|
||||
die("only 16-bit samples supported!");
|
||||
|
||||
alac = alac_create(sample_size, 2);
|
||||
if (!alac)
|
||||
return 1;
|
||||
decoder_info = alac;
|
||||
|
||||
alac->setinfo_max_samples_per_frame = frame_size;
|
||||
alac->setinfo_7a = fmtp[2];
|
||||
alac->setinfo_sample_size = sample_size;
|
||||
alac->setinfo_rice_historymult = fmtp[4];
|
||||
alac->setinfo_rice_initialhistory = fmtp[5];
|
||||
alac->setinfo_rice_kmodifier = fmtp[6];
|
||||
alac->setinfo_7f = fmtp[7];
|
||||
alac->setinfo_80 = fmtp[8];
|
||||
alac->setinfo_82 = fmtp[9];
|
||||
alac->setinfo_86 = fmtp[10];
|
||||
alac->setinfo_8a_rate = fmtp[11];
|
||||
alac_allocate_buffers(alac);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
static int init_src(void) {
|
||||
int err;
|
||||
if (fancy_resampling)
|
||||
src = src_new(SRC_SINC_MEDIUM_QUALITY, 2, &err);
|
||||
else
|
||||
src = 0;
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void init_buffer(void) {
|
||||
int i;
|
||||
for (i=0; i<BUFFER_FRAMES; i++)
|
||||
audio_buffer[i].data = malloc(OUTFRAME_BYTES);
|
||||
ab_resync();
|
||||
}
|
||||
|
||||
void player_put_packet(seq_t seqno, uint8_t *data, int len) {
|
||||
abuf_t *abuf = 0;
|
||||
short buf_fill;
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
if (!ab_synced) {
|
||||
ab_write = seqno;
|
||||
ab_read = seqno-1;
|
||||
ab_synced = 1;
|
||||
}
|
||||
if (seqno == ab_write+1) { // expected packet
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
ab_write = seqno;
|
||||
} else if (seq_order(ab_write, seqno)) { // newer than expected
|
||||
rtp_request_resend(ab_write+1, seqno-1);
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
ab_write = seqno;
|
||||
} else if (seq_order(ab_read, seqno)) { // late but not yet played
|
||||
abuf = audio_buffer + BUFIDX(seqno);
|
||||
} else { // too late.
|
||||
warn("late packet %04X (%04X:%04X)\n", seqno, ab_read, ab_write);
|
||||
}
|
||||
buf_fill = ab_write - ab_read;
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
if (abuf) {
|
||||
alac_decode(abuf->data, data, len);
|
||||
abuf->ready = 1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
if (ab_buffering && buf_fill >= config.buffer_start_fill) {
|
||||
debug("buffering over. starting play\n");
|
||||
ab_buffering = 0;
|
||||
pthread_cond_signal(&ab_buffer_ready);
|
||||
}
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
}
|
||||
|
||||
|
||||
static short lcg_rand(void) {
|
||||
static unsigned long lcg_prev = 12345;
|
||||
lcg_prev = lcg_prev * 69069 + 3;
|
||||
return lcg_prev & 0xffff;
|
||||
}
|
||||
|
||||
static inline short dithered_vol(short sample) {
|
||||
static short rand_a, rand_b;
|
||||
long out;
|
||||
|
||||
out = (long)sample * fix_volume;
|
||||
if (fix_volume < 0x10000) {
|
||||
rand_b = rand_a;
|
||||
rand_a = lcg_rand();
|
||||
out += rand_a;
|
||||
out -= rand_b;
|
||||
}
|
||||
return out>>16;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
double hist[2];
|
||||
double a[2];
|
||||
double b[3];
|
||||
} biquad_t;
|
||||
|
||||
static void biquad_init(biquad_t *bq, double a[], double b[]) {
|
||||
bq->hist[0] = bq->hist[1] = 0.0;
|
||||
memcpy(bq->a, a, 2*sizeof(double));
|
||||
memcpy(bq->b, b, 3*sizeof(double));
|
||||
}
|
||||
|
||||
static void biquad_lpf(biquad_t *bq, double freq, double Q) {
|
||||
double w0 = 2.0 * M_PI * freq * frame_size / (double)sampling_rate;
|
||||
double alpha = sin(w0)/(2.0*Q);
|
||||
|
||||
double a_0 = 1.0 + alpha;
|
||||
double b[3], a[2];
|
||||
b[0] = (1.0-cos(w0))/(2.0*a_0);
|
||||
b[1] = (1.0-cos(w0))/a_0;
|
||||
b[2] = b[0];
|
||||
a[0] = -2.0*cos(w0)/a_0;
|
||||
a[1] = (1-alpha)/a_0;
|
||||
|
||||
biquad_init(bq, a, b);
|
||||
}
|
||||
|
||||
static double biquad_filt(biquad_t *bq, double in) {
|
||||
double w = in - bq->a[0]*bq->hist[0] - bq->a[1]*bq->hist[1];
|
||||
double out = bq->b[1]*bq->hist[0] + bq->b[2]*bq->hist[1] + bq->b[0]*w;
|
||||
bq->hist[1] = bq->hist[0];
|
||||
bq->hist[0] = w;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static double bf_playback_rate = 1.0;
|
||||
|
||||
static double bf_est_drift = 0.0; // local clock is slower by
|
||||
static biquad_t bf_drift_lpf;
|
||||
static double bf_est_err = 0.0, bf_last_err;
|
||||
static biquad_t bf_err_lpf, bf_err_deriv_lpf;
|
||||
static double desired_fill;
|
||||
static int fill_count;
|
||||
|
||||
static void bf_est_reset(short fill) {
|
||||
biquad_lpf(&bf_drift_lpf, 1.0/180.0, 0.3);
|
||||
biquad_lpf(&bf_err_lpf, 1.0/10.0, 0.25);
|
||||
biquad_lpf(&bf_err_deriv_lpf, 1.0/2.0, 0.2);
|
||||
fill_count = 0;
|
||||
bf_playback_rate = 1.0;
|
||||
bf_est_err = bf_last_err = 0;
|
||||
desired_fill = fill_count = 0;
|
||||
}
|
||||
|
||||
static void bf_est_update(short fill) {
|
||||
if (fill_count < 1000) {
|
||||
desired_fill += (double)fill/1000.0;
|
||||
fill_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
#define CONTROL_A (1e-4)
|
||||
#define CONTROL_B (1e-1)
|
||||
|
||||
double buf_delta = fill - desired_fill;
|
||||
bf_est_err = biquad_filt(&bf_err_lpf, buf_delta);
|
||||
double err_deriv = biquad_filt(&bf_err_deriv_lpf, bf_est_err - bf_last_err);
|
||||
double adj_error = CONTROL_A * bf_est_err;
|
||||
|
||||
bf_est_drift = biquad_filt(&bf_drift_lpf, CONTROL_B*(adj_error + err_deriv) + bf_est_drift);
|
||||
|
||||
debug("bf %d err %f drift %f desiring %f ed %f estd %f\n",
|
||||
fill, bf_est_err, bf_est_drift, desired_fill, err_deriv, err_deriv + adj_error);
|
||||
bf_playback_rate = 1.0 + adj_error + bf_est_drift;
|
||||
|
||||
bf_last_err = bf_est_err;
|
||||
}
|
||||
|
||||
// get the next frame, when available. return 0 if underrun/stream reset.
|
||||
static short *buffer_get_frame(void) {
|
||||
short buf_fill;
|
||||
seq_t read;
|
||||
abuf_t *abuf = 0;
|
||||
unsigned short next;
|
||||
int i;
|
||||
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
|
||||
buf_fill = ab_write - ab_read;
|
||||
if (buf_fill < 1 || !ab_synced || ab_buffering) { // init or underrun. stop and wait
|
||||
if (ab_synced)
|
||||
warn("underrun.\n");
|
||||
|
||||
ab_buffering = 1;
|
||||
pthread_cond_wait(&ab_buffer_ready, &ab_mutex);
|
||||
ab_read++;
|
||||
buf_fill = ab_write - ab_read;
|
||||
bf_est_reset(buf_fill);
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (buf_fill >= BUFFER_FRAMES) { // overrunning! uh-oh. restart at a sane distance
|
||||
warn("overrun.\n");
|
||||
ab_read = ab_write - config.buffer_start_fill;
|
||||
}
|
||||
read = ab_read;
|
||||
ab_read++;
|
||||
buf_fill = ab_write - ab_read;
|
||||
bf_est_update(buf_fill);
|
||||
|
||||
// check if t+16, t+32, t+64, t+128, ... (START_FILL / 2)
|
||||
// packets have arrived... last-chance resend
|
||||
if (!ab_buffering) {
|
||||
for (i = 16; i < (START_FILL / 2); i = (i * 2)) {
|
||||
next = ab_read + i;
|
||||
abuf = audio_buffer + BUFIDX(next);
|
||||
if (!abuf->ready) {
|
||||
rtp_request_resend(next, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abuf_t *curframe = audio_buffer + BUFIDX(read);
|
||||
if (!curframe->ready) {
|
||||
warn("missing frame.\n");
|
||||
memset(curframe->data, 0, FRAME_BYTES);
|
||||
}
|
||||
curframe->ready = 0;
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
|
||||
return curframe->data;
|
||||
}
|
||||
|
||||
static int stuff_buffer(double playback_rate, short *inptr, short *outptr) {
|
||||
int i;
|
||||
int stuffsamp = frame_size;
|
||||
int stuff = 0;
|
||||
double p_stuff;
|
||||
|
||||
p_stuff = 1.0 - pow(1.0 - fabs(playback_rate-1.0), frame_size);
|
||||
|
||||
if (rand() < p_stuff * RAND_MAX) {
|
||||
stuff = playback_rate > 1.0 ? -1 : 1;
|
||||
stuffsamp = rand() % (frame_size - 1);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
for (i=0; i<stuffsamp; i++) { // the whole frame, if no stuffing
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
};
|
||||
if (stuff) {
|
||||
if (stuff==1) {
|
||||
debug("+++++++++\n");
|
||||
// interpolate one sample
|
||||
*outptr++ = dithered_vol(((long)inptr[-2] + (long)inptr[0]) >> 1);
|
||||
*outptr++ = dithered_vol(((long)inptr[-1] + (long)inptr[1]) >> 1);
|
||||
} else if (stuff==-1) {
|
||||
debug("---------\n");
|
||||
inptr++;
|
||||
inptr++;
|
||||
}
|
||||
for (i=stuffsamp; i<frame_size + stuff; i++) {
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
*outptr++ = dithered_vol(*inptr++);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
|
||||
return frame_size + stuff;
|
||||
}
|
||||
|
||||
static void *player_thread_func(void *arg) {
|
||||
int play_samples;
|
||||
|
||||
signed short *inbuf, *outbuf, *silence;
|
||||
outbuf = malloc(OUTFRAME_BYTES);
|
||||
silence = malloc(OUTFRAME_BYTES);
|
||||
memset(silence, 0, OUTFRAME_BYTES);
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
float *frame, *outframe;
|
||||
SRC_DATA srcdat;
|
||||
if (fancy_resampling) {
|
||||
frame = malloc(frame_size*2*sizeof(float));
|
||||
outframe = malloc(2*frame_size*2*sizeof(float));
|
||||
|
||||
srcdat.data_in = frame;
|
||||
srcdat.data_out = outframe;
|
||||
srcdat.input_frames = FRAME_BYTES;
|
||||
srcdat.output_frames = 2*FRAME_BYTES;
|
||||
srcdat.src_ratio = 1.0;
|
||||
srcdat.end_of_input = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
while (!please_stop) {
|
||||
do {
|
||||
inbuf = ab_buffering ? silence : buffer_get_frame();
|
||||
} while (!inbuf);
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
if (fancy_resampling) {
|
||||
int i;
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
for (i=0; i<2*FRAME_BYTES; i++) {
|
||||
frame[i] = (float)inbuf[i] / 32768.0;
|
||||
frame[i] *= volume;
|
||||
}
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
srcdat.src_ratio = bf_playback_rate;
|
||||
src_process(src, &srcdat);
|
||||
assert(srcdat.input_frames_used == FRAME_BYTES);
|
||||
src_float_to_short_array(outframe, outbuf, FRAME_BYTES*2);
|
||||
play_samples = srcdat.output_frames_gen;
|
||||
} else
|
||||
#endif
|
||||
play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf);
|
||||
|
||||
config.output->play(outbuf, play_samples);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// takes the volume as specified by the airplay protocol
|
||||
void player_volume(double f) {
|
||||
double linear_volume = pow(10.0, 0.05*f);
|
||||
|
||||
if (config.output->volume) {
|
||||
config.output->volume(linear_volume);
|
||||
} else {
|
||||
pthread_mutex_lock(&vol_mutex);
|
||||
volume = linear_volume;
|
||||
fix_volume = 65536.0 * volume;
|
||||
pthread_mutex_unlock(&vol_mutex);
|
||||
}
|
||||
}
|
||||
void player_flush(void) {
|
||||
pthread_mutex_lock(&ab_mutex);
|
||||
ab_resync();
|
||||
pthread_mutex_unlock(&ab_mutex);
|
||||
}
|
||||
|
||||
int player_play(stream_cfg *stream) {
|
||||
AES_set_decrypt_key(stream->aeskey, 128, &aes);
|
||||
aesiv = stream->aesiv;
|
||||
init_decoder(stream->fmtp);
|
||||
init_buffer();
|
||||
#ifdef FANCY_RESAMPLING
|
||||
init_src();
|
||||
#endif
|
||||
|
||||
please_stop = 0;
|
||||
config.output->start(sampling_rate);
|
||||
pthread_create(&player_thread, NULL, player_thread_func, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void player_stop(void) {
|
||||
please_stop = 1;
|
||||
pthread_join(player_thread, NULL);
|
||||
config.output->stop();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef _PLAYER_H
|
||||
#define _PLAYER_H
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t aesiv[16], aeskey[16];
|
||||
int32_t fmtp[12];
|
||||
} stream_cfg;
|
||||
|
||||
typedef unsigned short seq_t;
|
||||
|
||||
int player_play(stream_cfg *cfg);
|
||||
void player_stop(void);
|
||||
|
||||
void player_volume(double f);
|
||||
void player_flush(void);
|
||||
void player_resync(void);
|
||||
|
||||
void player_put_packet(seq_t seqno, uint8_t *data, int len);
|
||||
|
||||
#endif //_PLAYER_H
|
||||
@@ -0,0 +1,157 @@
|
||||
// only one RTP session can be active at a time.
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <memory.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
|
||||
static int running = 0;
|
||||
static int please_shutdown;
|
||||
|
||||
static struct sockaddr rtp_client;
|
||||
static int sock;
|
||||
static pthread_t rtp_thread;
|
||||
|
||||
static void *rtp_receiver(void *arg) {
|
||||
// we inherit the signal mask (SIGUSR1)
|
||||
uint8_t packet[2048], *pktp;
|
||||
|
||||
ssize_t nread;
|
||||
while (1) {
|
||||
if (please_shutdown)
|
||||
break;
|
||||
nread = recv(sock, packet, sizeof(packet), 0);
|
||||
if (nread < 0)
|
||||
break;
|
||||
|
||||
ssize_t plen = nread;
|
||||
uint8_t type = packet[1] & ~0x80;
|
||||
if (type == 0x60 || type == 0x56) { // audio data / resend
|
||||
pktp = packet;
|
||||
if (type==0x56) {
|
||||
pktp += 4;
|
||||
plen -= 4;
|
||||
}
|
||||
seq_t seqno = ntohs(*(unsigned short *)(pktp+2));
|
||||
|
||||
pktp += 12;
|
||||
plen -= 12;
|
||||
|
||||
// check if packet contains enough content to be reasonable
|
||||
if (plen >= 16) {
|
||||
player_put_packet(seqno, pktp, plen);
|
||||
continue;
|
||||
}
|
||||
if (type == 0x56 && seqno == 0) {
|
||||
debug("Suspected resync request packet received.\n");
|
||||
player_flush();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
warn("Unknown RTP packet of type 0x%02X length %d\n", type, nread);
|
||||
}
|
||||
|
||||
debug("RTP thread interrupted. terminating.\n");
|
||||
close(sock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int bind_port(struct sockaddr *remote) {
|
||||
struct addrinfo hints, *info;
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = remote->sa_family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
int ret = getaddrinfo(NULL, "0", &hints, &info);
|
||||
|
||||
if (ret < 0)
|
||||
die("failed to get usable addrinfo?! %s", gai_strerror(ret));
|
||||
|
||||
sock = socket(remote->sa_family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
ret = bind(sock, info->ai_addr, info->ai_addrlen);
|
||||
|
||||
if (ret < 0)
|
||||
die("could not bind a UDP port!");
|
||||
|
||||
int sport;
|
||||
struct sockaddr local;
|
||||
socklen_t local_len = sizeof(local);
|
||||
getsockname(sock, &local, &local_len);
|
||||
#ifdef AF_INET6
|
||||
if (local.sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&local;
|
||||
sport = htons(sa6->sin6_port);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
struct sockaddr_in *sa = (struct sockaddr_in*)&local;
|
||||
sport = htons(sa->sin_port);
|
||||
}
|
||||
|
||||
return sport;
|
||||
}
|
||||
|
||||
|
||||
int rtp_setup(struct sockaddr *remote, int cport, int tport) {
|
||||
if (running)
|
||||
die("rtp_setup called with active stream!\n");
|
||||
|
||||
debug("rtp_setup: cport=%d tport=%d\n", cport, tport);
|
||||
|
||||
// we do our own timing and ignore the timing port.
|
||||
// an audio perfectionist may wish to learn the protocol.
|
||||
|
||||
memcpy(&rtp_client, remote, sizeof(rtp_client));
|
||||
#ifdef AF_INET6
|
||||
if (rtp_client.sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)&rtp_client;
|
||||
sa6->sin6_port = htons(cport);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
struct sockaddr_in *sa = (struct sockaddr_in*)&rtp_client;
|
||||
sa->sin_port = htons(cport);
|
||||
}
|
||||
|
||||
int sport = bind_port(remote);
|
||||
|
||||
debug("rtp listening on port %d\n", sport);
|
||||
|
||||
please_shutdown = 0;
|
||||
pthread_create(&rtp_thread, NULL, &rtp_receiver, NULL);
|
||||
|
||||
running = 1;
|
||||
return sport;
|
||||
}
|
||||
|
||||
void rtp_shutdown(void) {
|
||||
if (!running)
|
||||
die("rtp_shutdown called without active stream!\n");
|
||||
|
||||
please_shutdown = 1;
|
||||
pthread_kill(rtp_thread, SIGUSR1);
|
||||
void *retval;
|
||||
pthread_join(rtp_thread, &retval);
|
||||
running = 0;
|
||||
}
|
||||
|
||||
void rtp_request_resend(seq_t first, seq_t last) {
|
||||
if (!running)
|
||||
die("rtp_request_resend called without active stream!\n");
|
||||
|
||||
warn("requesting resend on %d packets\n", last-first+1);
|
||||
char req[8]; // *not* a standard RTCP NACK
|
||||
req[0] = 0x80;
|
||||
req[1] = 0x55|0x80; // Apple 'resend'
|
||||
*(unsigned short *)(req+2) = htons(1); // our seqnum
|
||||
*(unsigned short *)(req+4) = htons(first); // missed seqnum
|
||||
*(unsigned short *)(req+6) = htons(last-first+1); // count
|
||||
|
||||
sendto(sock, req, sizeof(req), 0, &rtp_client, sizeof(rtp_client));
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef _RTP_H
|
||||
#define _RTP_H
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
int rtp_setup(struct sockaddr *remote, int controlport, int timingport);
|
||||
void rtp_shutdown(void);
|
||||
void rtp_request_resend(seq_t first, seq_t last);
|
||||
|
||||
#endif // _RTP_H
|
||||
@@ -0,0 +1,723 @@
|
||||
#include <memory.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <signal.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/md5.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "player.h"
|
||||
#include "rtp.h"
|
||||
|
||||
// only one thread is allowed to use the player at once.
|
||||
// it monitors the request variable (at least when interrupted)
|
||||
static pthread_mutex_t playing_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int please_shutdown = 0;
|
||||
static pthread_t playing_thread = 0;
|
||||
|
||||
typedef struct {
|
||||
stream_cfg stream;
|
||||
struct sockaddr remote;
|
||||
} rtsp_conn_info;
|
||||
|
||||
static inline int rtsp_playing(void) {
|
||||
return playing_thread == pthread_self();
|
||||
}
|
||||
|
||||
static void rtsp_take_player(void) {
|
||||
if (pthread_mutex_trylock(&playing_mutex)) {
|
||||
debug("another thread is playing, shutting it down\n");
|
||||
// XXX minor race condition between please_shutdown and signal delivery
|
||||
please_shutdown = 1;
|
||||
pthread_kill(playing_thread, SIGUSR1);
|
||||
pthread_mutex_lock(&playing_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void rtsp_shutdown_stream(void) {
|
||||
rtsp_take_player();
|
||||
pthread_mutex_unlock(&playing_mutex);
|
||||
}
|
||||
|
||||
|
||||
// park a null at the line ending, and return the next line pointer
|
||||
// accept \r, \n, or \r\n
|
||||
static char *nextline(char *in, int inbuf) {
|
||||
char *out = NULL;
|
||||
while (inbuf) {
|
||||
if (*in == '\r') {
|
||||
*in++ = 0;
|
||||
out = in;
|
||||
}
|
||||
if (*in == '\n') {
|
||||
*in++ = 0;
|
||||
out = in;
|
||||
}
|
||||
|
||||
if (out)
|
||||
break;
|
||||
|
||||
in++;
|
||||
inbuf--;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int nheaders;
|
||||
char *name[16];
|
||||
char *value[16];
|
||||
|
||||
int contentlength;
|
||||
char *content;
|
||||
|
||||
// for requests
|
||||
char method[16];
|
||||
|
||||
// for responses
|
||||
int respcode;
|
||||
} rtsp_message;
|
||||
|
||||
static rtsp_message * msg_init(void) {
|
||||
rtsp_message *msg = malloc(sizeof(rtsp_message));
|
||||
memset(msg, 0, sizeof(rtsp_message));
|
||||
return msg;
|
||||
}
|
||||
|
||||
static int msg_add_header(rtsp_message *msg, char *name, char *value) {
|
||||
if (msg->nheaders >= sizeof(msg->name)/sizeof(char*)) {
|
||||
warn("too many headers?!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
msg->name[msg->nheaders] = strdup(name);
|
||||
msg->value[msg->nheaders] = strdup(value);
|
||||
msg->nheaders++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *msg_get_header(rtsp_message *msg, char *name) {
|
||||
int i;
|
||||
for (i=0; i<msg->nheaders; i++)
|
||||
if (!strcasecmp(msg->name[i], name))
|
||||
return msg->value[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void msg_free(rtsp_message *msg) {
|
||||
int i;
|
||||
for (i=0; i<msg->nheaders; i++) {
|
||||
free(msg->name[i]);
|
||||
free(msg->value[i]);
|
||||
}
|
||||
if (msg->content)
|
||||
free(msg->content);
|
||||
free(msg);
|
||||
}
|
||||
|
||||
|
||||
static int msg_handle_line(rtsp_message **pmsg, char *line) {
|
||||
rtsp_message *msg = *pmsg;
|
||||
|
||||
if (!msg) {
|
||||
msg = msg_init();
|
||||
*pmsg = msg;
|
||||
char *sp, *p;
|
||||
|
||||
debug("received request: %s\n", line);
|
||||
|
||||
p = strtok_r(line, " ", &sp);
|
||||
if (!p)
|
||||
goto fail;
|
||||
strncpy(msg->method, p, sizeof(msg->method)-1);
|
||||
|
||||
p = strtok_r(NULL, " ", &sp);
|
||||
if (!p)
|
||||
goto fail;
|
||||
|
||||
p = strtok_r(NULL, " ", &sp);
|
||||
if (!p)
|
||||
goto fail;
|
||||
if (strcmp(p, "RTSP/1.0"))
|
||||
goto fail;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strlen(line)) {
|
||||
char *p;
|
||||
p = strstr(line, ": ");
|
||||
if (!p) {
|
||||
warn("bad header: >>%s<<\n", line);
|
||||
goto fail;
|
||||
}
|
||||
*p = 0;
|
||||
p += 2;
|
||||
msg_add_header(msg, line, p);
|
||||
debug(" %s: %s\n", line, p);
|
||||
return -1;
|
||||
} else {
|
||||
char *cl = msg_get_header(msg, "Content-Length");
|
||||
if (cl)
|
||||
return atoi(cl);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
fail:
|
||||
*pmsg = NULL;
|
||||
msg_free(msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static rtsp_message * rtsp_read_request(int fd) {
|
||||
ssize_t buflen = 512;
|
||||
char *buf = malloc(buflen+1);
|
||||
|
||||
rtsp_message *msg = NULL;
|
||||
|
||||
ssize_t nread;
|
||||
ssize_t inbuf = 0;
|
||||
int msg_size = -1;
|
||||
|
||||
while (msg_size < 0) {
|
||||
if (please_shutdown) {
|
||||
debug("RTSP shutdown requested\n");
|
||||
goto shutdown;
|
||||
}
|
||||
nread = read(fd, buf+inbuf, buflen - inbuf);
|
||||
if (!nread) {
|
||||
debug("RTSP connection closed\n");
|
||||
goto shutdown;
|
||||
}
|
||||
if (nread < 0) {
|
||||
if (errno==EINTR)
|
||||
continue;
|
||||
perror("read failure");
|
||||
goto shutdown;
|
||||
}
|
||||
inbuf += nread;
|
||||
|
||||
char *next;
|
||||
while (msg_size < 0 && (next = nextline(buf, inbuf))) {
|
||||
msg_size = msg_handle_line(&msg, buf);
|
||||
|
||||
if (!msg) {
|
||||
warn("no RTSP header received\n");
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
inbuf -= next-buf;
|
||||
if (inbuf)
|
||||
memmove(buf, next, inbuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (msg_size > buflen) {
|
||||
buf = realloc(buf, msg_size);
|
||||
if (!buf) {
|
||||
warn("too much content");
|
||||
goto shutdown;
|
||||
}
|
||||
buflen = msg_size;
|
||||
}
|
||||
|
||||
while (inbuf < msg_size) {
|
||||
nread = read(fd, buf+inbuf, msg_size-inbuf);
|
||||
if (!nread)
|
||||
goto shutdown;
|
||||
if (nread==EINTR)
|
||||
continue;
|
||||
if (nread < 0) {
|
||||
perror("read failure");
|
||||
goto shutdown;
|
||||
}
|
||||
inbuf += nread;
|
||||
}
|
||||
|
||||
msg->contentlength = inbuf;
|
||||
msg->content = buf;
|
||||
return msg;
|
||||
|
||||
shutdown:
|
||||
free(buf);
|
||||
if (msg) {
|
||||
msg_free(msg);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void msg_write_response(int fd, rtsp_message *resp) {
|
||||
char rbuf[30];
|
||||
int nrbuf;
|
||||
nrbuf = snprintf(rbuf, sizeof(rbuf),
|
||||
"RTSP/1.0 %d %s\r\n", resp->respcode,
|
||||
resp->respcode==200 ? "OK" : "Error");
|
||||
write(fd, rbuf, nrbuf);
|
||||
debug("sending response: %s", rbuf);
|
||||
int i;
|
||||
for (i=0; i<resp->nheaders; i++) {
|
||||
debug(" %s: %s\n", resp->name[i], resp->value[i]);
|
||||
write(fd, resp->name[i], strlen(resp->name[i]));
|
||||
write(fd, ": ", 2);
|
||||
write(fd, resp->value[i], strlen(resp->value[i]));
|
||||
write(fd, "\r\n", 2);
|
||||
}
|
||||
write(fd, "\r\n", 2);
|
||||
}
|
||||
|
||||
static void handle_options(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
resp->respcode = 200;
|
||||
msg_add_header(resp, "Public",
|
||||
"ANNOUNCE, SETUP, RECORD, "
|
||||
"PAUSE, FLUSH, TEARDOWN, "
|
||||
"OPTIONS, GET_PARAMETER, SET_PARAMETER");
|
||||
}
|
||||
|
||||
static void handle_teardown(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
if (!rtsp_playing())
|
||||
return;
|
||||
resp->respcode = 200;
|
||||
msg_add_header(resp, "Connection", "close");
|
||||
please_shutdown = 1;
|
||||
}
|
||||
|
||||
static void handle_flush(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
if (!rtsp_playing())
|
||||
return;
|
||||
player_flush();
|
||||
resp->respcode = 200;
|
||||
}
|
||||
|
||||
static void handle_setup(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
playing_thread = pthread_self();
|
||||
|
||||
int cport, tport;
|
||||
char *hdr = msg_get_header(req, "Transport");
|
||||
if (!hdr)
|
||||
return;
|
||||
|
||||
char *p;
|
||||
p = strstr(hdr, "control_port=");
|
||||
if (!p)
|
||||
return;
|
||||
p = strchr(p, '=') + 1;
|
||||
cport = atoi(p);
|
||||
|
||||
p = strstr(hdr, "timing_port=");
|
||||
if (!p)
|
||||
return;
|
||||
p = strchr(p, '=') + 1;
|
||||
tport = atoi(p);
|
||||
|
||||
rtsp_take_player();
|
||||
int sport = rtp_setup(&conn->remote, cport, tport);
|
||||
if (!sport)
|
||||
return;
|
||||
|
||||
player_play(&conn->stream);
|
||||
|
||||
char *resphdr = malloc(strlen(hdr) + 20);
|
||||
strcpy(resphdr, hdr);
|
||||
sprintf(resphdr + strlen(resphdr), ";server_port=%d", sport);
|
||||
msg_add_header(resp, "Transport", resphdr);
|
||||
|
||||
resp->respcode = 200;
|
||||
}
|
||||
|
||||
static void handle_ignore(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
resp->respcode = 200;
|
||||
}
|
||||
|
||||
static void handle_announce(rtsp_conn_info *conn,
|
||||
rtsp_message *req, rtsp_message *resp) {
|
||||
|
||||
char *paesiv = NULL;
|
||||
char *prsaaeskey = NULL;
|
||||
char *pfmtp = NULL;
|
||||
char *cp = req->content;
|
||||
int cp_left = req->contentlength;
|
||||
char *next;
|
||||
while (cp) {
|
||||
next = nextline(cp, cp_left);
|
||||
cp_left -= next-cp;
|
||||
|
||||
if (!strncmp(cp, "a=fmtp:", 7))
|
||||
pfmtp = cp+7;
|
||||
|
||||
if (!strncmp(cp, "a=aesiv:", 8))
|
||||
paesiv = cp+8;
|
||||
|
||||
if (!strncmp(cp, "a=rsaaeskey:", 12))
|
||||
prsaaeskey = cp+12;
|
||||
|
||||
cp = next;
|
||||
}
|
||||
|
||||
if (!paesiv || !prsaaeskey || !pfmtp) {
|
||||
warn("required params missing from announce\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int len, keylen;
|
||||
uint8_t *aesiv = base64_dec(paesiv, &len);
|
||||
if (len != 16) {
|
||||
warn("client announced aeskey of %d bytes, wanted 16\n", len);
|
||||
free(aesiv);
|
||||
return;
|
||||
}
|
||||
memcpy(conn->stream.aesiv, aesiv, 16);
|
||||
free(aesiv);
|
||||
|
||||
uint8_t *rsaaeskey = base64_dec(prsaaeskey, &len);
|
||||
uint8_t *aeskey = rsa_apply(rsaaeskey, len, &keylen, RSA_MODE_KEY);
|
||||
free(rsaaeskey);
|
||||
if (keylen != 16) {
|
||||
warn("client announced rsaaeskey of %d bytes, wanted 16\n", keylen);
|
||||
free(aeskey);
|
||||
return;
|
||||
}
|
||||
memcpy(conn->stream.aeskey, aeskey, 16);
|
||||
free(aeskey);
|
||||
|
||||
int i;
|
||||
for (i=0; i<sizeof(conn->stream.fmtp)/sizeof(conn->stream.fmtp[0]); i++)
|
||||
conn->stream.fmtp[i] = atoi(strsep(&pfmtp, " \t"));
|
||||
|
||||
resp->respcode = 200;
|
||||
}
|
||||
|
||||
|
||||
static struct method_handler {
|
||||
char *method;
|
||||
void (*handler)(rtsp_conn_info *conn, rtsp_message *req,
|
||||
rtsp_message *resp);
|
||||
} method_handlers[] = {
|
||||
{"OPTIONS", handle_options},
|
||||
{"ANNOUNCE", handle_announce},
|
||||
{"FLUSH", handle_flush},
|
||||
{"TEARDOWN", handle_teardown},
|
||||
{"SETUP", handle_setup},
|
||||
{"GET_PARAMETER", handle_ignore},
|
||||
{"SET_PARAMETER", handle_ignore}, // XXX
|
||||
{"RECORD", handle_ignore},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static void apple_challenge(int fd, rtsp_message *req, rtsp_message *resp) {
|
||||
char *hdr = msg_get_header(req, "Apple-Challenge");
|
||||
if (!hdr)
|
||||
return;
|
||||
|
||||
struct sockaddr fdsa;
|
||||
socklen_t sa_len = sizeof(fdsa);
|
||||
getsockname(fd, &fdsa, &sa_len);
|
||||
|
||||
int chall_len;
|
||||
uint8_t *chall = base64_dec(hdr, &chall_len);
|
||||
uint8_t buf[48], *bp = buf;
|
||||
int i;
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
if (chall_len > 16) {
|
||||
warn("oversized Apple-Challenge!\n");
|
||||
free(chall);
|
||||
return;
|
||||
}
|
||||
memcpy(bp, chall, chall_len);
|
||||
free(chall);
|
||||
bp += chall_len;
|
||||
|
||||
#ifdef AF_INET6
|
||||
if (fdsa.sa_family == AF_INET6) {
|
||||
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)(&fdsa);
|
||||
memcpy(bp, sa6->sin6_addr.s6_addr, 16);
|
||||
bp += 16;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
struct sockaddr_in *sa = (struct sockaddr_in*)(&fdsa);
|
||||
unsigned int ip = sa->sin_addr.s_addr;
|
||||
for (i=0; i<4; i++) {
|
||||
*bp++ = ip & 0xff;
|
||||
ip >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<6; i++)
|
||||
*bp++ = config.hw_addr[i];
|
||||
|
||||
int buflen, resplen;
|
||||
buflen = bp-buf;
|
||||
if (buflen < 0x20)
|
||||
buflen = 0x20;
|
||||
|
||||
uint8_t *challresp = rsa_apply(buf, buflen, &resplen, RSA_MODE_AUTH);
|
||||
char *encoded = base64_enc(challresp, resplen);
|
||||
|
||||
// strip the padding.
|
||||
char *padding = strchr(encoded, '=');
|
||||
if (padding)
|
||||
*padding = 0;
|
||||
|
||||
msg_add_header(resp, "Apple-Response", encoded);
|
||||
free(challresp);
|
||||
free(encoded);
|
||||
}
|
||||
|
||||
static char *make_nonce(void) {
|
||||
uint8_t random[8];
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
if (fd < 0)
|
||||
die("could not open /dev/random!");
|
||||
read(fd, random, sizeof(random));
|
||||
close(fd);
|
||||
return base64_enc(random, 8);
|
||||
}
|
||||
|
||||
static int rtsp_auth(char **nonce, rtsp_message *req, rtsp_message *resp) {
|
||||
|
||||
if (!config.password)
|
||||
return 0;
|
||||
if (!*nonce) {
|
||||
*nonce = make_nonce();
|
||||
goto authenticate;
|
||||
}
|
||||
|
||||
char *hdr = msg_get_header(req, "Authorization");
|
||||
if (!hdr || strncmp(hdr, "Digest ", 7))
|
||||
goto authenticate;
|
||||
|
||||
char *realm = strstr(hdr, "realm=\"");
|
||||
char *username = strstr(hdr, "username=\"");
|
||||
char *response = strstr(hdr, "response=\"");
|
||||
char *uri = strstr(hdr, "uri=\"");
|
||||
|
||||
if (!realm || !username || !response || !uri)
|
||||
goto authenticate;
|
||||
|
||||
char *quote;
|
||||
realm = strchr(realm, '"') + 1;
|
||||
if (!(quote = strchr(realm, '"')))
|
||||
goto authenticate;
|
||||
*quote = 0;
|
||||
username = strchr(username, '"') + 1;
|
||||
if (!(quote = strchr(username, '"')))
|
||||
goto authenticate;
|
||||
*quote = 0;
|
||||
response = strchr(response, '"') + 1;
|
||||
if (!(quote = strchr(response, '"')))
|
||||
goto authenticate;
|
||||
*quote = 0;
|
||||
uri = strchr(uri, '"') + 1;
|
||||
if (!(quote = strchr(uri, '"')))
|
||||
goto authenticate;
|
||||
*quote = 0;
|
||||
|
||||
uint8_t digest_urp[16], digest_mu[16], digest_total[16];
|
||||
MD5_CTX ctx;
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, username, strlen(username));
|
||||
MD5_Update(&ctx, ":", 1);
|
||||
MD5_Update(&ctx, realm, strlen(realm));
|
||||
MD5_Update(&ctx, ":", 1);
|
||||
MD5_Update(&ctx, config.password, strlen(config.password));
|
||||
MD5_Final(digest_urp, &ctx);
|
||||
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, req->method, strlen(req->method));
|
||||
MD5_Update(&ctx, ":", 1);
|
||||
MD5_Update(&ctx, uri, strlen(uri));
|
||||
MD5_Final(digest_mu, &ctx);
|
||||
|
||||
int i;
|
||||
char buf[33];
|
||||
for (i=0; i<16; i++)
|
||||
sprintf(buf + 2*i, "%02X", digest_urp[i]);
|
||||
MD5_Init(&ctx);
|
||||
MD5_Update(&ctx, buf, 32);
|
||||
MD5_Update(&ctx, ":", 1);
|
||||
MD5_Update(&ctx, *nonce, strlen(*nonce));
|
||||
MD5_Update(&ctx, ":", 1);
|
||||
for (i=0; i<16; i++)
|
||||
sprintf(buf + 2*i, "%02X", digest_mu[i]);
|
||||
MD5_Update(&ctx, buf, 32);
|
||||
MD5_Final(digest_total, &ctx);
|
||||
|
||||
for (i=0; i<16; i++)
|
||||
sprintf(buf + 2*i, "%02X", digest_total[i]);
|
||||
|
||||
if (!strcmp(response, buf))
|
||||
return 0;
|
||||
warn("auth failed\n");
|
||||
|
||||
authenticate:
|
||||
resp->respcode = 401;
|
||||
int hdrlen = strlen(*nonce) + 40;
|
||||
char *authhdr = malloc(hdrlen);
|
||||
snprintf(authhdr, hdrlen, "Digest realm=\"taco\", nonce=\"%s\"", *nonce);
|
||||
msg_add_header(resp, "WWW-Authenticate", authhdr);
|
||||
free(authhdr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void *rtsp_conversation_thread_func(void *vfd) {
|
||||
// SIGUSR1 is used to interrupt this thread if blocked for read
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, SIGUSR1);
|
||||
pthread_sigmask(SIG_UNBLOCK, &set, NULL);
|
||||
|
||||
rtsp_conn_info conn;
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
|
||||
int fd = *(int*)vfd;
|
||||
socklen_t slen = sizeof(conn.remote);
|
||||
|
||||
fd = accept(fd, &conn.remote, &slen);
|
||||
if (fd < 0) {
|
||||
perror("failed to accept connection");
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
rtsp_message *req, *resp;
|
||||
char *hdr, *auth_nonce = NULL;
|
||||
while ((req = rtsp_read_request(fd))) {
|
||||
resp = msg_init();
|
||||
resp->respcode = 400;
|
||||
|
||||
apple_challenge(fd, req, resp);
|
||||
hdr = msg_get_header(req, "CSeq");
|
||||
if (hdr)
|
||||
msg_add_header(resp, "CSeq", hdr);
|
||||
msg_add_header(resp, "Audio-Jack-Status", "connected; type=analog");
|
||||
|
||||
if (rtsp_auth(&auth_nonce, req, resp))
|
||||
goto respond;
|
||||
|
||||
struct method_handler *mh;
|
||||
for (mh=method_handlers; mh->method; mh++) {
|
||||
if (!strcmp(mh->method, req->method)) {
|
||||
mh->handler(&conn, req, resp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
respond:
|
||||
msg_write_response(fd, resp);
|
||||
msg_free(req);
|
||||
msg_free(resp);
|
||||
}
|
||||
|
||||
shutdown:
|
||||
if (fd > 0)
|
||||
close(fd);
|
||||
if (rtsp_playing()) {
|
||||
rtp_shutdown();
|
||||
player_stop();
|
||||
please_shutdown = 0;
|
||||
pthread_mutex_unlock(&playing_mutex);
|
||||
}
|
||||
if (auth_nonce)
|
||||
free(auth_nonce);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rtsp_listen_loop(void) {
|
||||
struct addrinfo hints, *info, *p;
|
||||
char portstr[6];
|
||||
int sockfd[2];
|
||||
int nsock = 0;
|
||||
int ret;
|
||||
memset(sockfd, 0, sizeof(sockfd));
|
||||
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
snprintf(portstr, 6, "%d", config.port);
|
||||
|
||||
ret = getaddrinfo(NULL, portstr, &hints, &info);
|
||||
if (ret) {
|
||||
die("getaddrinfo failed: %s\n", gai_strerror(ret));
|
||||
}
|
||||
|
||||
for (p=info; p; p=p->ai_next) {
|
||||
int fd = socket(p->ai_family, p->ai_socktype, IPPROTO_TCP);
|
||||
int yes = 1;
|
||||
|
||||
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
|
||||
#ifdef AF_INET6
|
||||
// some systems don't support v4 access on v6 sockets, but some do.
|
||||
// since we need to account for two sockets we might as well always.
|
||||
if (p->ai_family == AF_INET6)
|
||||
ret |= setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
|
||||
#endif
|
||||
|
||||
if (!ret)
|
||||
ret = bind(fd, p->ai_addr, p->ai_addrlen);
|
||||
|
||||
if (ret) {
|
||||
perror("could not bind a listen socket");
|
||||
continue;
|
||||
}
|
||||
|
||||
listen(fd, 5);
|
||||
sockfd[nsock++] = fd;
|
||||
}
|
||||
|
||||
freeaddrinfo(info);
|
||||
|
||||
if (!nsock)
|
||||
die("could not bind any listen sockets!");
|
||||
|
||||
|
||||
int maxfd = sockfd[0];
|
||||
if (sockfd[1]>maxfd)
|
||||
maxfd = sockfd[1];
|
||||
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(sockfd[0], &fds);
|
||||
if (sockfd[1])
|
||||
FD_SET(sockfd[1], &fds);
|
||||
|
||||
printf("Listening for connections.\n");
|
||||
|
||||
int acceptfd;
|
||||
while (select(maxfd+1, &fds, 0, 0, 0) >= 0) {
|
||||
if (FD_ISSET(sockfd[0], &fds))
|
||||
acceptfd = sockfd[0];
|
||||
if (FD_ISSET(sockfd[1], &fds) && sockfd[1])
|
||||
acceptfd = sockfd[1];
|
||||
|
||||
// for now, we do not track these and let them die of natural causes.
|
||||
// XXX: this leaks threads; they need to be culled with pthread_tryjoin_np.
|
||||
// XXX: acceptfd could change before the thread is up. which should never happen, but still.
|
||||
pthread_t rtsp_conversation_thread;
|
||||
pthread_create(&rtsp_conversation_thread, NULL, rtsp_conversation_thread_func, &acceptfd);
|
||||
|
||||
FD_SET(sockfd[0], &fds);
|
||||
if (sockfd[1])
|
||||
FD_SET(sockfd[1], &fds);
|
||||
}
|
||||
perror("select");
|
||||
die("fell out of the RTSP select loop\n");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef _RTSP_H
|
||||
#define _RTSP_H
|
||||
|
||||
void rtsp_listen_loop(void);
|
||||
void rtsp_shutdown_stream(void);
|
||||
|
||||
#endif // _RTSP_H
|
||||
+41
-1171
File diff suppressed because it is too large
Load Diff
-57
@@ -1,57 +0,0 @@
|
||||
#ifndef __SHAIRPORT_H__
|
||||
#define __SHAIRPORT_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include "socketlib.h"
|
||||
#include <regex.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <regex.h>
|
||||
|
||||
|
||||
#define HWID_SIZE 6
|
||||
#define SHAIRPORT_LOG 1
|
||||
#define LOG_INFO 1
|
||||
#define LOG_DEBUG 5
|
||||
#define LOG_DEBUG_V 6
|
||||
#define LOG_DEBUG_VV 7
|
||||
|
||||
struct shairbuffer
|
||||
{
|
||||
char *data;
|
||||
int current;
|
||||
int maxsize;
|
||||
int marker;
|
||||
};
|
||||
|
||||
struct keyring
|
||||
{
|
||||
char *aeskey;
|
||||
char *aesiv;
|
||||
char *fmt;
|
||||
};
|
||||
|
||||
struct comms
|
||||
{
|
||||
int in[2];
|
||||
int out[2];
|
||||
};
|
||||
|
||||
struct connection
|
||||
{
|
||||
struct shairbuffer recv;
|
||||
struct shairbuffer resp;
|
||||
struct keyring *keys; // Does not point to malloc'd memory.
|
||||
struct comms *hairtunes;
|
||||
int clientSocket;
|
||||
char *password;
|
||||
};
|
||||
|
||||
void sim(int pLevel, char *pValue1, char *pValue2);
|
||||
|
||||
#endif
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This starts and stops shairport
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: shairport
|
||||
# Required-Start: $network
|
||||
# Required-Stop:
|
||||
# Short-Description: shairport - Airtunes emulator!
|
||||
# Description: Airtunes emulator!
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
### END INIT INFO
|
||||
|
||||
|
||||
# Source function library.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
NAME=ShairPort
|
||||
DAEMON="/usr/local/bin/shairport.pl"
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
DAEMON_ARGS="-w $PIDFILE -a $NAME"
|
||||
|
||||
[ -x $binary ] || exit 0
|
||||
|
||||
RETVAL=0
|
||||
|
||||
start() {
|
||||
echo -n "Starting shairport: "
|
||||
start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
|
||||
--exec "$DAEMON" -b --oknodo -- $DAEMON_ARGS
|
||||
log_end_msg $?
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n "Shutting down shairport: "
|
||||
start-stop-daemon --stop --quiet --pidfile "$PIDFILE" \
|
||||
--retry 1 --oknodo
|
||||
log_end_msg $?
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status shairport
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|status|restart}"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
|
||||
-763
@@ -1,763 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
# ShairPort - Airtunes compatible server
|
||||
# Copyright (c) 2011 James Laird
|
||||
# All rights reserved.
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation
|
||||
# files (the "Software"), to deal in the Software without
|
||||
# restriction, including without limitation the rights to use,
|
||||
# copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the
|
||||
# Software is furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use 5.10.0;
|
||||
# For given() { when() { } ... }
|
||||
use feature ":5.10";
|
||||
|
||||
use Getopt::Long;
|
||||
use FindBin;
|
||||
use File::Basename;
|
||||
|
||||
use IO::Select;
|
||||
use IO::Socket;
|
||||
use MIME::Base64;
|
||||
use HTTP::Request;
|
||||
use HTTP::Response;
|
||||
use URI::Escape;
|
||||
use IPC::Open2;
|
||||
use Crypt::OpenSSL::RSA;
|
||||
use Digest::MD5 qw(md5 md5_hex);
|
||||
use POSIX qw(:sys_wait_h setsid);
|
||||
eval "use IO::Socket::INET6;";
|
||||
use Net::SDP;
|
||||
|
||||
my $shairportversion = "0.05";
|
||||
|
||||
my $apname = "ShairPort $$ on " . `hostname`;
|
||||
my $port = 5002;
|
||||
# password - required to connect
|
||||
# for no password, set:
|
||||
my $password = '';
|
||||
# output to a pipe?
|
||||
my $pipepath;
|
||||
# detach
|
||||
my $daemon;
|
||||
# ao options
|
||||
my $libao_driver;
|
||||
my $libao_devicename;
|
||||
my $libao_deviceid;
|
||||
# suppose hairtunes is under same directory
|
||||
my $hairtunes_cli = $FindBin::Bin . '/hairtunes';
|
||||
# Integrate with Squeezebox Server
|
||||
my $squeeze;
|
||||
# SBS CLI port
|
||||
my $cliport;
|
||||
# SB target
|
||||
my $mac;
|
||||
# SB volume
|
||||
my $volume;
|
||||
# custom play and stop program
|
||||
my $play_prog;
|
||||
my $stop_prog;
|
||||
# output debugging information
|
||||
my $verbose;
|
||||
# where to write PID
|
||||
my $writepid;
|
||||
# show help
|
||||
my $help;
|
||||
|
||||
|
||||
my $ipv4_only;
|
||||
|
||||
unless (-x $hairtunes_cli) {
|
||||
say "Can't find the 'hairtunes' decoder binary, you need to build this before using ShairPort.";
|
||||
say "Read the INSTALL instructions!";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
GetOptions("a|apname=s" => \$apname,
|
||||
"p|password=s" => \$password,
|
||||
"o|server_port=s" => \$port,
|
||||
"i|pipe=s" => \$pipepath,
|
||||
"d" => \$daemon,
|
||||
"ao_driver=s" => \$libao_driver,
|
||||
"ao_devicename=s" => \$libao_devicename,
|
||||
"ao_deviceid=s" => \$libao_deviceid,
|
||||
"v|verbose" => \$verbose,
|
||||
"4" => \$ipv4_only,
|
||||
"w|writepid=s" => \$writepid,
|
||||
"s|squeezebox" => \$squeeze,
|
||||
"c|cliport=s" => \$cliport,
|
||||
"m|mac=s" => \$mac,
|
||||
"play_prog=s" => \$play_prog,
|
||||
"stop_prog=s" => \$stop_prog,
|
||||
"l|volume=s" => \$volume,
|
||||
"h|help" => \$help);
|
||||
|
||||
sub usage {
|
||||
print "ShairPort version $shairportversion - Airport Express emulator\n".
|
||||
"Usage:\n".
|
||||
basename($0) . " [OPTION...]\n".
|
||||
"\n".
|
||||
"Options:\n".
|
||||
" -a, --apname=AirPort Sets AirPort name\n".
|
||||
" -p, --password=secret Sets password\n",
|
||||
" -o, --server_port=5002 Sets Port for Avahi/dns-sd/howl\n",
|
||||
" -i, --pipe=pipepath Sets the path to a named pipe for output\n",
|
||||
" --ao_driver=driver Sets the ao driver (optional)\n",
|
||||
" --ao_devicename=devicename Sets the ao device name (optional)\n",
|
||||
" --ao_deviceid=id Sets the ao device id (optional)\n",
|
||||
" -s --squeezebox Enables local Squeezebox Server integration\n",
|
||||
" -c --cliport=port Sets the SBS CLI port\n",
|
||||
" -m --mac=address Sets the SB target device\n",
|
||||
" -l --volume=level Sets the SB volume level (in %)\n",
|
||||
" --play_prog=cmdline Program to start on 1st connection\n",
|
||||
" --stop_prog=cmdline Program to start on last disconnection\n",
|
||||
" -d Daemon mode\n",
|
||||
" -w --writepid=path Write PID to this location\n",
|
||||
" -v --verbose Print debugging messages\n",
|
||||
" -h, --help This help\n",
|
||||
"\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
if (defined($help) && $help == 1) { usage(); }
|
||||
# ensure that $verbose is set, one way or another...
|
||||
if (defined($verbose) && $verbose) {
|
||||
$verbose = 1;
|
||||
} else {
|
||||
$verbose = 0;
|
||||
}
|
||||
|
||||
$cliport = ( (defined( $cliport ) && $cliport && $cliport =~ m/^[0-9]+/ && $cliport > 1023 && $cliport < 65536 ) ? $cliport : 9090 );
|
||||
|
||||
$volume = ( (defined( $volume ) && $volume && $volume =~ m/^[0-9]+/ && $volume >= 0 && $volume <= 101 ) ? $volume : undef );
|
||||
|
||||
if (defined($squeeze) && $squeeze) {
|
||||
my $players;
|
||||
my @details;
|
||||
|
||||
my $response;
|
||||
my $socket = IO::Socket::INET -> new (
|
||||
PeerAddr => "127.0.0.1"
|
||||
, PeerPort => $cliport
|
||||
, Proto => 'tcp'
|
||||
, Timeout => 1
|
||||
);
|
||||
if( !( $socket ) ) {
|
||||
print "ERROR: Could not create socket to interface with SqueezeBox Server on port $cliport: $!\n";
|
||||
print "WARN: Disabling Squeezebox Server integration\n";
|
||||
undef $squeeze;
|
||||
}
|
||||
if( $squeeze ) {
|
||||
print $socket "player count ?\n";
|
||||
$response = <$socket>;
|
||||
if( !( defined( $response ) ) ) {
|
||||
print "ERROR: Could not communicate with SqueezeBox Server on port $cliport\n";
|
||||
print "WARN: Disabling Squeezebox Server integration\n";
|
||||
undef $squeeze;
|
||||
} else {
|
||||
( $players ) = ( $response =~ m/^player count ([0-9]+)$/ );
|
||||
print $socket "players 0 $players\n";
|
||||
$response = <$socket>;
|
||||
@details = split( /playerindex%3A/ , $response );
|
||||
close( $socket );
|
||||
shift( @details );
|
||||
for( my $n = 0; $n <= scalar( @details ); $n++ ) {
|
||||
if( defined( $details[ $n ] ) ) {
|
||||
my $address = $details[ $n ];
|
||||
$address =~ s/^.*playerid%3A([[:xdigit:]%]+)\s.*$/$1/;
|
||||
$address =~ s/%3A/:/g;
|
||||
chomp $address;
|
||||
$details[ $n ] = $address;
|
||||
}
|
||||
}
|
||||
print "Discovered players: $players\n" if $verbose;
|
||||
print "Player MAC addresses:\n" if $verbose;
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( ( defined( $address ) && $address ) && $verbose );
|
||||
}
|
||||
|
||||
if( defined( $mac ) && $mac ) {
|
||||
chomp $mac;
|
||||
if( !( grep { lc( $_ ) eq lc( $mac ) } @details ) ) {
|
||||
print "ERROR: Invalid or non-present MAC specified.\n\n";
|
||||
print "Please select a target MAC address from:\n";
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( defined( $address ) && $address );
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
if( 1 == $players ) {
|
||||
$mac = $details[ 0 ];
|
||||
print "WARN: No Squeezebox player specified, using $mac.\n";
|
||||
} else {
|
||||
print "ERROR: No Squeezebox player specified, please select a target MAC address with the '--mac' option with a value from:\n";
|
||||
foreach my $address (@details) {
|
||||
print "\t$address\n" if( defined( $address ) && $address );
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
$mac = uri_escape( $mac ) if( defined( $mac ) && $mac );
|
||||
}
|
||||
}
|
||||
};
|
||||
chomp $apname;
|
||||
|
||||
my @hw_addr = +(map(ord, split(//, md5($apname))))[0..5];
|
||||
|
||||
sub POPE {
|
||||
print "Broken pipe\n" if $verbose;
|
||||
$SIG{PIPE} = \&POPE;
|
||||
}
|
||||
$SIG{PIPE} = \&POPE;
|
||||
|
||||
|
||||
our $avahi_publish;
|
||||
our $squeezebox_setup;
|
||||
|
||||
sub REAP {
|
||||
my $pid = waitpid( -1, WNOHANG );
|
||||
given( $pid ) {
|
||||
when( $avahi_publish ) {
|
||||
die( "avahi daemon terminated or 'avahi-publish-service' binary not found" );
|
||||
}
|
||||
when( $squeezebox_setup ) {
|
||||
print( "Squeezebox configuration routine completed\n" ) if $verbose;
|
||||
}
|
||||
}
|
||||
print("Child exited\n") if $verbose;
|
||||
$SIG{CHLD} = \&REAP;
|
||||
};
|
||||
$SIG{CHLD} = \&REAP;
|
||||
|
||||
my %conns;
|
||||
|
||||
$SIG{TERM} = $SIG{INT} = sub {
|
||||
print basename($0) . " killed\n";
|
||||
map { eval { kill $_->{decoder_pid} } } keys %conns;
|
||||
kill 9, $avahi_publish if $avahi_publish;
|
||||
# Clean up any running squeezebox_setup processes...
|
||||
my $child;
|
||||
do {
|
||||
$child = waitpid( -1, WNOHANG );
|
||||
} while $child > 0;
|
||||
exit 0;
|
||||
};
|
||||
$SIG{__DIE__} = sub {
|
||||
map { eval { kill $_->{decoder_pid} } } keys %conns;
|
||||
kill 9, $avahi_publish if $avahi_publish;
|
||||
# Clean up any running squeezebox_setup processes...
|
||||
my $child;
|
||||
do {
|
||||
$child = waitpid( -1, WNOHANG );
|
||||
} while $child > 0;
|
||||
};
|
||||
|
||||
$avahi_publish = fork();
|
||||
my $pw_clause = (length $password) ? "pw=true" : "pw=false";
|
||||
if ($avahi_publish==0) {
|
||||
{ exec 'avahi-publish-service',
|
||||
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
|
||||
"_raop._tcp",
|
||||
$port,
|
||||
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
|
||||
{ exec 'dns-sd', '-R',
|
||||
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
|
||||
"_raop._tcp",
|
||||
".",
|
||||
$port,
|
||||
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
|
||||
{ exec 'mDNSPublish',
|
||||
join('', map { sprintf "%02X", $_ } @hw_addr) . "\@$apname",
|
||||
"_raop._tcp",
|
||||
$port,
|
||||
"tp=UDP","sm=false","sv=false","ek=1","et=0,1","cn=0,1","ch=2","ss=16","sr=44100",$pw_clause,"vn=3","txtvers=1"; };
|
||||
die "could not run avahi-publish-service nor dns-sd nor mDNSPublish";
|
||||
}
|
||||
|
||||
my $airport_pem = join '', <DATA>;
|
||||
my $rsa = Crypt::OpenSSL::RSA->new_private_key($airport_pem) || die "RSA private key import failed";
|
||||
|
||||
my $listen;
|
||||
{
|
||||
if (!defined($ipv4_only)) {
|
||||
eval {
|
||||
local $SIG{__DIE__};
|
||||
$listen = new IO::Socket::INET6(Listen => 1,
|
||||
Domain => AF_INET6,
|
||||
LocalPort => $port,
|
||||
ReuseAddr => 1,
|
||||
Proto => 'tcp');
|
||||
};
|
||||
if ($@) {
|
||||
print "**************************************\n",
|
||||
"* IO::Socket::INET6 not present! *\n",
|
||||
"* Install this if iTunes won't play. *\n",
|
||||
"**************************************\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
$listen ||= new IO::Socket::INET(Listen => 1,
|
||||
LocalPort => $port,
|
||||
ReuseAddr => 1,
|
||||
Proto => 'tcp');
|
||||
}
|
||||
die "Can't listen on port " . $port . ": $!" unless $listen;
|
||||
|
||||
sub ip6bin {
|
||||
my $ip = shift;
|
||||
$ip =~ /((.*)::)?(.+)/;
|
||||
my @left = split /:/, $2;
|
||||
my @right = split /:/, $3;
|
||||
my @mid;
|
||||
my $pad = 8 - ($#left + $#right + 2);
|
||||
if ($pad > 0) {
|
||||
@mid = (0) x $pad;
|
||||
}
|
||||
|
||||
pack('S>*', map { hex } (@left, @mid, @right));
|
||||
}
|
||||
|
||||
my $sel = new IO::Select($listen);
|
||||
|
||||
if ($daemon) {
|
||||
chdir "/" or die "Could not chdir to '/': $!";
|
||||
umask 0;
|
||||
open STDIN, "/dev/null" or die "Could not redirect /dev/null to STDIN(0): $!";
|
||||
open STDOUT, ">/dev/null" or die "Could not redirect STDOUT(1) to /dev/null: $!";
|
||||
defined( my $pid = fork() ) or die "Could not fork: $!";
|
||||
exit 0 if $pid;
|
||||
setsid() or die "Could not start new session: $!";
|
||||
open STDERR, ">&STDOUT" or die "Could not dup STDOUT(1)";
|
||||
}
|
||||
if (defined($writepid) && $writepid) {
|
||||
open PID, ">$writepid" or die "Could not create PID file '$writepid': $!";
|
||||
print PID $$;
|
||||
close PID;
|
||||
}
|
||||
|
||||
print "Listening...\n" if $verbose;
|
||||
|
||||
sub performSqueezeboxSetup {
|
||||
$squeezebox_setup = fork();
|
||||
if( 0 == $squeezebox_setup ) {
|
||||
my $items;
|
||||
my @favourites;
|
||||
my @ids;
|
||||
my $index;
|
||||
|
||||
my $response;
|
||||
|
||||
my $findFavourites = sub {
|
||||
my ( $socket ) = @_;
|
||||
print $socket "favorites items\n";
|
||||
$response = <$socket>;
|
||||
$response =~ s/^\s*favorites\s+items\s+count%3A([0-9]+)\s*$/$1/;
|
||||
$items = $response;
|
||||
print "Found $items favourites...\n" if $verbose;
|
||||
|
||||
print $socket "favorites items 0 $items want_url%3A1\n";
|
||||
$response = <$socket>;
|
||||
undef( @favourites );
|
||||
@favourites = split( /id%3A/ , $response );
|
||||
@ids = split( /id%3A/ , $response );
|
||||
shift( @favourites );
|
||||
shift( @ids );
|
||||
for( my $n = 0; $n <= scalar( @favourites ); $n++ ) {
|
||||
if( defined( $favourites[ $n ] ) ) {
|
||||
my $url = $favourites[ $n ];
|
||||
$url =~ s/^.*url%3A([^ ]+)\s.*$/$1/;
|
||||
$url = uri_unescape( $url );
|
||||
chomp $url;
|
||||
print "\tFavourite with URL '$url' " if $verbose;
|
||||
$favourites[ $n ] = $url;
|
||||
}
|
||||
if( defined( $ids[ $n ] ) ) {
|
||||
my $id = $ids[ $n ];
|
||||
$id =~ s/^([^ ]+)\s.*$/$1/;
|
||||
chomp $id;
|
||||
my @components = split( /\./, $id );
|
||||
shift ( @components );
|
||||
$id = join( '.', @components );
|
||||
print "and ID '$id'\n" if $verbose;
|
||||
$ids[ $n ] = $id;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
my $socket = IO::Socket::INET -> new (
|
||||
PeerAddr => "127.0.0.1"
|
||||
, PeerPort => ( (defined( $cliport ) and $cliport ) ? $cliport : 9090 )
|
||||
, Proto => 'tcp'
|
||||
, Timeout => 1
|
||||
) or die "Could not create socket: $!";
|
||||
|
||||
&$findFavourites( $socket );
|
||||
|
||||
print "Favourites URLs:\n" if $verbose;
|
||||
foreach my $url (@favourites) {
|
||||
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
|
||||
}
|
||||
my $okay = 1;
|
||||
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
|
||||
$okay = 0;
|
||||
print "INFO: AirPlay 'wavin' Favourite does not exist - creating... ";
|
||||
print $socket "favorites add url%3Awavin%3Aairplay title%3AAirPlay\n";
|
||||
$response = <$socket>;
|
||||
if( $response =~ m/\scount%3A1/ ) {
|
||||
print "done\nINFO: AirPlay 'wavin' favourite successfully created.\n";
|
||||
$okay = 1;
|
||||
} else {
|
||||
print "failed\nWARN: Could not create AirPlay favourite\n";
|
||||
print " Server response was $response\n";
|
||||
}
|
||||
if( $okay ) {
|
||||
&$findFavourites( $socket );
|
||||
|
||||
print "Updated Favourites URLs:\n" if $verbose;
|
||||
foreach my $url (@favourites) {
|
||||
print "\t$url\n" if( ( defined( $url ) && $url ) && $verbose );
|
||||
}
|
||||
if( !( grep { $_ =~ m/^wavin:/ } @favourites ) ) {
|
||||
print "WARN: Cloud not identify AirPlay Favourite, even after creating it - disabling SqueezeBox integration\n";
|
||||
$squeeze = 0;
|
||||
$okay = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( $okay ) {
|
||||
for ( my $n = 0 ; !( defined( $index ) ) && $n <= scalar( @favourites ) ; $n++ ) {
|
||||
if( $favourites[ $n ] =~ m/^wavin:/ ) {
|
||||
$index = $n;
|
||||
print "Found favourite '" . $favourites[ $index ] . "' with ID '" . $ids[ $index ] . "' at position $index.\n" if $verbose;
|
||||
}
|
||||
}
|
||||
|
||||
print "Turning on player (if off)... " if $verbose;
|
||||
print $socket "$mac power 1\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
print "Stopping player (if playing)... " if $verbose;
|
||||
print $socket "$mac stop\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
print "Unmuting player (if muted)... " if $verbose;
|
||||
print $socket "$mac mixer muting 0\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
if( defined( $volume ) ) {
|
||||
print "Setting player volume to $volume... " if $verbose;
|
||||
print $socket "$mac mixer volume $volume\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
}
|
||||
|
||||
print "Showing message... " if $verbose;
|
||||
print $socket "$mac show line2%3AStarting%20AirPlay duration%3A5 brightness%3ApowerOn font%3Ahuge\n";
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
|
||||
if( defined( $index ) ) {
|
||||
print "Playing favourite... " if $verbose;
|
||||
my $id = uri_escape( $ids[ $index ] );
|
||||
print $socket "$mac favorites playlist play item_id%3A$id\n";
|
||||
} else {
|
||||
print "Resuming play... " if $verbose;
|
||||
print $socket "$mac play\n";
|
||||
}
|
||||
$response = <$socket>;
|
||||
print "$response\n" if $verbose;
|
||||
}
|
||||
close( $socket );
|
||||
|
||||
exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
while (1) {
|
||||
printf "about to select\n" if $verbose;
|
||||
my @waiting = $sel->can_read;
|
||||
foreach my $fh (@waiting) {
|
||||
if ($fh==$listen) {
|
||||
my $new = $listen->accept;
|
||||
printf "New connection from %s\n", $new->peerhost if $verbose;
|
||||
|
||||
$sel->add($new);
|
||||
$new->blocking(0);
|
||||
$conns{$new} = {fh => $fh};
|
||||
|
||||
if (defined($squeeze) && $squeeze) {
|
||||
&performSqueezeboxSetup();
|
||||
}
|
||||
|
||||
# the 2nd connection is a player connection
|
||||
if (defined($play_prog) && $sel->count() == 2) {
|
||||
printf "play prog: $play_prog\n" if ($verbose);
|
||||
system($play_prog);
|
||||
}
|
||||
} else {
|
||||
if (eof($fh)) {
|
||||
print "Closed: $fh\n" if $verbose;
|
||||
$sel->remove($fh);
|
||||
close $fh;
|
||||
# Prevent warnings when decoder_pid isn't defined
|
||||
# (e.g. client connected, but playback not started)
|
||||
if (defined($conns{$fh}{decoder_pid})) {
|
||||
eval { kill $conns{$fh}{decoder_pid} };
|
||||
}
|
||||
delete $conns{$fh};
|
||||
|
||||
# 1 connection means no connection
|
||||
if (defined($stop_prog) && $sel->count() == 1) {
|
||||
system($stop_prog);
|
||||
}
|
||||
next;
|
||||
}
|
||||
if (exists $conns{$fh}) {
|
||||
conn_handle_data($fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit(1); # Unreachable
|
||||
|
||||
|
||||
sub conn_handle_data {
|
||||
my $fh = shift;
|
||||
my $conn = $conns{$fh};
|
||||
|
||||
printf "handle data 1\n" if ($verbose);
|
||||
|
||||
if ($conn->{req_need}) {
|
||||
if (length($conn->{data}) >= $conn->{req_need}) {
|
||||
$conn->{req}->content(substr($conn->{data}, 0, $conn->{req_need}, ''));
|
||||
conn_handle_request($fh, $conn);
|
||||
}
|
||||
undef $conn->{req_need};
|
||||
return;
|
||||
}
|
||||
|
||||
read $fh, my $data, 4096;
|
||||
$conn->{data} .= $data;
|
||||
|
||||
if ($conn->{data} =~ /(\r\n\r\n|\n\n|\r\r)/) {
|
||||
my $req_data = substr($conn->{data}, 0, $+[0], '');
|
||||
$conn->{req} = HTTP::Request->parse($req_data);
|
||||
printf "REQ: %s\n", $conn->{req}->method if $verbose;
|
||||
conn_handle_request($fh, $conn);
|
||||
conn_handle_data($fh) if length($conn->{data});
|
||||
}
|
||||
}
|
||||
|
||||
sub digest_ok {
|
||||
my ($req, $conn) = @_;
|
||||
my $authz = $req->header('Authorization');
|
||||
return 0 unless $authz =~ s/^Digest\s+//i;
|
||||
return 0 unless length $conn->{nonce};
|
||||
my @authz = split /,\s*/, $authz;
|
||||
my %authz = map { /(.+)="(.+)"/; ($1, $2) } @authz;
|
||||
|
||||
# not a standard digest - uses capital hex digits, in conflict with the RFC
|
||||
my $digest = uc md5_hex (
|
||||
uc(md5_hex($authz{username} . ':' . $authz{realm} . ':' . $password))
|
||||
. ':' . $authz{nonce} . ':' .
|
||||
uc(md5_hex($req->method . ':' . $authz{uri}))
|
||||
);
|
||||
|
||||
return $digest eq $authz{response};
|
||||
}
|
||||
|
||||
sub conn_handle_request {
|
||||
my ($fh, $conn) = @_;
|
||||
|
||||
my $req = $conn->{req};;
|
||||
my $clen = $req->header('content-length') // 0;
|
||||
if ($clen > 0 && !length($req->content)) {
|
||||
$conn->{req_need} = $clen;
|
||||
return; # need more!
|
||||
}
|
||||
|
||||
my $resp = HTTP::Response->new(200);
|
||||
$resp->request($req);
|
||||
$resp->protocol($req->protocol);
|
||||
|
||||
$resp->header('CSeq', $req->header('CSeq'));
|
||||
$resp->header('Audio-Jack-Status', 'connected; type=analog');
|
||||
|
||||
if (my $chall = $req->header('Apple-Challenge')) {
|
||||
my $data = decode_base64($chall);
|
||||
my $ip = $fh->sockhost;
|
||||
if ($ip =~ /((\d+\.){3}\d+)$/) { # IPv4
|
||||
$data .= join '', map { chr } split(/\./, $1);
|
||||
} else {
|
||||
$data .= ip6bin($ip);
|
||||
}
|
||||
|
||||
$data .= join '', map { chr } @hw_addr;
|
||||
$data .= chr(0) x (0x20-length($data));
|
||||
|
||||
$rsa->use_pkcs1_padding; # this isn't hashed before signing
|
||||
my $signature = encode_base64 $rsa->private_encrypt($data), '';
|
||||
$signature =~ s/=*$//;
|
||||
$resp->header('Apple-Response', $signature);
|
||||
}
|
||||
|
||||
if (length $password) {
|
||||
if (!digest_ok($req, $conn)) {
|
||||
my $nonce = md5_hex(map { rand } 1..20);
|
||||
$conn->{nonce} = $nonce;
|
||||
$resp->header('WWW-Authenticate', "Digest realm=\"$apname\", nonce=\"$nonce\"");
|
||||
$resp->code(401);
|
||||
$req->method('DENIED');
|
||||
}
|
||||
}
|
||||
|
||||
for ($req->method) {
|
||||
/^OPTIONS$/ && do {
|
||||
$resp->header('Public', 'ANNOUNCE, SETUP, RECORD, PAUSE, FLUSH, TEARDOWN, OPTIONS, GET_PARAMETER, SET_PARAMETER');
|
||||
last;
|
||||
};
|
||||
|
||||
/^ANNOUNCE$/ && do {
|
||||
my $sdp = Net::SDP->new($req->content);
|
||||
my $audio = $sdp->media_desc_of_type('audio');
|
||||
|
||||
print $audio->as_string();
|
||||
print $audio->attribute('aesiv');
|
||||
|
||||
die("no AESIV") unless my $aesiv = decode_base64($audio->attribute('aesiv'));
|
||||
die("no AESKEY") unless my $rsaaeskey = decode_base64($audio->attribute('rsaaeskey'));
|
||||
$rsa->use_pkcs1_oaep_padding;
|
||||
my $aeskey = $rsa->decrypt($rsaaeskey) || die "RSA decrypt failed";
|
||||
|
||||
$conn->{aesiv} = $aesiv;
|
||||
$conn->{aeskey} = $aeskey;
|
||||
$conn->{fmtp} = $audio->attribute('fmtp');
|
||||
last;
|
||||
};
|
||||
|
||||
/^SETUP$/ && do {
|
||||
my $transport = $req->header('Transport');
|
||||
$transport =~ s/;control_port=(\d+)//;
|
||||
my $cport = $1;
|
||||
$transport =~ s/;timing_port=(\d+)//;
|
||||
my $tport = $1;
|
||||
$transport =~ s/;server_port=(\d+)//;
|
||||
my $dport = $1;
|
||||
$resp->header('Session', 'DEADBEEF');
|
||||
|
||||
my %dec_args = (
|
||||
iv => unpack('H*', $conn->{aesiv}),
|
||||
key => unpack('H*', $conn->{aeskey}),
|
||||
fmtp => $conn->{fmtp},
|
||||
cport => $cport,
|
||||
tport => $tport,
|
||||
dport => $dport,
|
||||
# host => 'unused',
|
||||
);
|
||||
$dec_args{pipe} = $pipepath if defined $pipepath;
|
||||
$dec_args{ao_driver} = $libao_driver if defined $libao_driver;
|
||||
$dec_args{ao_devicename} = $libao_devicename if defined $libao_devicename;
|
||||
$dec_args{ao_deviceid} = $libao_deviceid if defined $libao_deviceid;
|
||||
|
||||
my $dec = '"' . $hairtunes_cli . '"' . join(' ', '', map { sprintf "%s '%s'", $_, $dec_args{$_} } keys(%dec_args));
|
||||
|
||||
if ($ipv4_only) {
|
||||
$dec .= " ipv4_only";
|
||||
}
|
||||
|
||||
print "decode command: $dec\n" if ($verbose);
|
||||
my $decoder = open2(my $dec_out, my $dec_in, $dec);
|
||||
|
||||
$conn->{decoder_pid} = $decoder;
|
||||
$conn->{decoder_fh} = $dec_in;
|
||||
my $portdesc = <$dec_out>;
|
||||
die("Expected port number from decoder; got $portdesc") unless $portdesc =~ /^port: (\d+)/;
|
||||
my $port = $1;
|
||||
print "launched decoder: $decoder on port: $port\n" if $verbose;
|
||||
$resp->header('Transport', $req->header('Transport') . ";server_port=$port");
|
||||
last;
|
||||
};
|
||||
|
||||
/^RECORD$/ && last;
|
||||
/^FLUSH$/ && do {
|
||||
my $dfh = $conn->{decoder_fh};
|
||||
print $dfh "flush\n";
|
||||
last;
|
||||
};
|
||||
/^TEARDOWN$/ && do {
|
||||
$resp->header('Connection', 'close');
|
||||
close $conn->{decoder_fh};
|
||||
last;
|
||||
};
|
||||
/^SET_PARAMETER$/ && do {
|
||||
my @lines = split /[\r\n]+/, $req->content;
|
||||
printf("SET_PARAMETER req: " . $req->content . "\n") if ($verbose);
|
||||
my %content = map { /^(\S+): (.+)/; (lc $1, $2) } @lines;
|
||||
my $cfh = $conn->{decoder_fh};
|
||||
if (exists $content{volume}) {
|
||||
printf("sending-> vol: %f\n", $content{volume}) if ($verbose);
|
||||
printf $cfh "vol: %f\n", $content{volume};
|
||||
} else {
|
||||
printf("unable to perform content for req: " . $req->content . "\n") if ($verbose);
|
||||
|
||||
}
|
||||
last;
|
||||
};
|
||||
/^GET_PARAMETER$/ && last;
|
||||
/^DENIED$/ && last;
|
||||
die("Unknown method: $_");
|
||||
}
|
||||
|
||||
printf("%s", $resp->as_string("\r\n")) if ($verbose);
|
||||
print $fh $resp->as_string("\r\n");
|
||||
$fh->flush;
|
||||
}
|
||||
|
||||
__DATA__
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt
|
||||
wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U
|
||||
wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf
|
||||
/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/
|
||||
UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW
|
||||
BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa
|
||||
LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5
|
||||
NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm
|
||||
lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz
|
||||
aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu
|
||||
a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM
|
||||
oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z
|
||||
oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+
|
||||
k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL
|
||||
AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA
|
||||
cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf
|
||||
54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov
|
||||
17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc
|
||||
1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI
|
||||
LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ
|
||||
2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* Socket Library - Common socket functions, based on stevens networking book.
|
||||
* Copyright (c) M. Andrew Webster 2011
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include "socketlib.h"
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <openssl/sha.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/buffer.h>
|
||||
|
||||
int common_setup(struct addrinfo *pAddrInfo)
|
||||
{
|
||||
int tSock;
|
||||
//printAddrs(pAddrInfo);
|
||||
tSock = socket(pAddrInfo->ai_family, pAddrInfo->ai_socktype, 0);
|
||||
#ifdef AF_INET6
|
||||
if((tSock==-1) && (pAddrInfo->ai_family == AF_INET6) && (errno == EAFNOSUPPORT))
|
||||
{
|
||||
//Fallback to ipv4
|
||||
perror("Failed to create ipv6 socket. Trying ipv4");
|
||||
pAddrInfo->ai_family = AF_INET;
|
||||
tSock = socket(pAddrInfo->ai_family, pAddrInfo->ai_socktype, 0);
|
||||
}
|
||||
#endif
|
||||
return tSock;
|
||||
}
|
||||
|
||||
int setup_client(struct addrinfo *server_host)
|
||||
{
|
||||
int tSockDesc = -1;
|
||||
int tIdx = 0;
|
||||
|
||||
while(tIdx++ < RETRY_COUNT)
|
||||
{
|
||||
tSockDesc = common_setup(server_host);
|
||||
if (tSockDesc < 0 && tIdx >= RETRY_COUNT)
|
||||
{
|
||||
perror("Error: Could not create socket");
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
if (connect(tSockDesc, server_host->ai_addr, server_host->ai_addrlen) >= 0)
|
||||
{
|
||||
return tSockDesc;
|
||||
}
|
||||
else
|
||||
{
|
||||
close(tSockDesc);
|
||||
perror("Error: Could not connect to server");
|
||||
struct timeval tRes;
|
||||
delay(RETRY_DELAY, &tRes);
|
||||
}
|
||||
}
|
||||
printf("%d Retry attempts exceeded\n", RETRY_COUNT);
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo)
|
||||
{
|
||||
struct addrinfo hints;
|
||||
int tError = 0;
|
||||
memset(&hints, 0 , sizeof(hints));
|
||||
|
||||
hints.ai_family = pFamily;
|
||||
hints.ai_socktype = pSockType;
|
||||
if(pHostname == NULL)
|
||||
{
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
}
|
||||
|
||||
tError = getaddrinfo(pHostname, pService, &hints, pAddrInfo);
|
||||
if(tError != 0)
|
||||
{
|
||||
printf("Error getting address info\n");
|
||||
}
|
||||
return tError;
|
||||
}
|
||||
|
||||
int setup_server(struct addrinfo *server_addr)
|
||||
{
|
||||
int tSock = common_setup(server_addr);
|
||||
if (tSock < 0)
|
||||
{
|
||||
perror("Error: Could not create server socket");
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
int tEnable = 1;
|
||||
setsockopt(tSock, SOL_SOCKET, SO_REUSEADDR, &tEnable, sizeof (tEnable));
|
||||
if (bind(tSock, server_addr->ai_addr, server_addr->ai_addrlen) < 0)
|
||||
{
|
||||
close(tSock);
|
||||
perror("Error: Could not bind socket");
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
if (listen(tSock, 5) < 0)
|
||||
{
|
||||
close(tSock);
|
||||
perror("Error: Unable to listen on server socket");
|
||||
return ERROR;
|
||||
}
|
||||
|
||||
return tSock;
|
||||
}
|
||||
|
||||
|
||||
int acceptClient(int pSock, struct addrinfo *server_addr)
|
||||
{
|
||||
int tAccept = accept(pSock, server_addr->ai_addr, &server_addr->ai_addrlen);
|
||||
// close the listen socket. Not expecting any more clients.
|
||||
if (tAccept < 0)
|
||||
{
|
||||
perror("Error: Unable to accept connection to server socket");
|
||||
return ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
//printf("..Accepted on socket: %d\n", tAccept);
|
||||
}
|
||||
return tAccept;
|
||||
}
|
||||
|
||||
int setupListenServer(struct addrinfo **pAddrInfo, int pPort)
|
||||
{
|
||||
char tService[SERVLEN];
|
||||
sprintf(tService, "%d", pPort); // copies port to string
|
||||
int tFamily = AF_INET;
|
||||
#ifdef AF_INET6
|
||||
//printf("Listening on IPv6 Socket\n");
|
||||
tFamily = AF_INET6;
|
||||
#else
|
||||
//printf("Listening on IPv4 Socket");
|
||||
#endif
|
||||
if(getAddr(NULL, tService, tFamily, SOCK_STREAM, pAddrInfo))
|
||||
{
|
||||
return ERROR; // getAddr prints out error message
|
||||
}
|
||||
|
||||
int tSocketDescriptor = setup_server(*pAddrInfo);
|
||||
char tAddr[INET6_ADDRSTRLEN];
|
||||
socklen_t tSize = INET6_ADDRSTRLEN;
|
||||
inet_ntop((*pAddrInfo)->ai_family, (*pAddrInfo)->ai_addr, tAddr, tSize);
|
||||
//printf("Size is: %d\n", tSize);
|
||||
return tSocketDescriptor;
|
||||
}
|
||||
|
||||
void delay(long pMillisecs, struct timeval *pRes)
|
||||
{
|
||||
pRes->tv_sec = pMillisecs / 1000;
|
||||
pRes->tv_usec = (pMillisecs - (pRes->tv_sec * 1000)) * 1000;
|
||||
select(0,NULL,NULL,NULL,pRes);
|
||||
}
|
||||
|
||||
static int getCorrectedEncodeSize(int pSize)
|
||||
{
|
||||
if(pSize % 4 == 0)
|
||||
{
|
||||
return pSize;
|
||||
}
|
||||
else if((pSize + 1) % 4 == 0)
|
||||
{
|
||||
return pSize+1;
|
||||
}
|
||||
else if((pSize + 2) % 4 == 0)
|
||||
{
|
||||
return pSize+2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid encoded data, no other cases are possible.
|
||||
printf("Unrecoverable error....base64 values are incorrectly encoded\n");
|
||||
return pSize;
|
||||
}
|
||||
}
|
||||
|
||||
// From http://www.ioncannon.net/programming/34/howto-base64-encode-with-cc-and-openssl/
|
||||
|
||||
//int main(int argc, char **argv)
|
||||
//{
|
||||
// char *output = decode_base64("WU9ZTyEA\n\0", strlen("WU9ZTyEA\n\0"));
|
||||
// printf("Unbase64: *%s*\n", output);
|
||||
// free(output);/
|
||||
//}
|
||||
|
||||
char *decode_base64(unsigned char *pInput, int pLength, int *pActualLength)
|
||||
{
|
||||
// Needs All NO_NL flags for proper RSA AES KEY Descrypt
|
||||
BIO *b64, *bmem;
|
||||
unsigned char *input = pInput;
|
||||
int length = getCorrectedEncodeSize(pLength);
|
||||
if(pLength != length)
|
||||
{
|
||||
input = malloc(length * sizeof(unsigned char));
|
||||
memset(input, 0, length);
|
||||
memcpy(input, pInput, pLength);
|
||||
memset(input+pLength, '=', length-pLength);
|
||||
printf("Fixed value: [%.*s]\n", length, input);
|
||||
}
|
||||
char *buffer = (char *)malloc(length);
|
||||
memset(buffer, 0, length);
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
|
||||
bmem = BIO_new_mem_buf(input, length);
|
||||
BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_push(b64, bmem);
|
||||
|
||||
BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL);
|
||||
|
||||
*pActualLength = BIO_read(bmem, buffer, length);
|
||||
|
||||
BIO_free_all(bmem);
|
||||
|
||||
if(pLength != length)
|
||||
{
|
||||
free(input);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char *encode_base64(unsigned char *input, int length)
|
||||
{
|
||||
BIO *bmem, *b64;
|
||||
BUF_MEM *bptr;
|
||||
|
||||
b64 = BIO_new(BIO_f_base64());
|
||||
// This enables/disables nls
|
||||
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
|
||||
bmem = BIO_new(BIO_s_mem());
|
||||
|
||||
b64 = BIO_push(b64, bmem);
|
||||
|
||||
BIO_write(b64, input, length);
|
||||
(void)BIO_flush(b64);
|
||||
BIO_get_mem_ptr(b64, &bptr);
|
||||
|
||||
char *buff = (char *)malloc(bptr->length);
|
||||
memcpy(buff, bptr->data, bptr->length-1);
|
||||
buff[bptr->length-1] = 0;
|
||||
|
||||
BIO_free_all(b64);
|
||||
|
||||
return buff;
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/* Name: M. Andrew Webster
|
||||
* Based on stevens networking book
|
||||
* Description: function prototypes for socket library
|
||||
*/
|
||||
|
||||
#ifndef _SOCKETLIB_H
|
||||
#define _SOCKETLIB_H
|
||||
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
|
||||
#define MAX_SIZE 8192
|
||||
#define PORT 5002
|
||||
#define RETRY_COUNT 7
|
||||
#define SERVLEN 80
|
||||
#define RETRY_DELAY 1000
|
||||
|
||||
#define ERROR -1
|
||||
#define DEFAULT_IP "127.0.0.1"
|
||||
#define DEFAULT_UNIX "/unix"
|
||||
|
||||
int setup_client(struct addrinfo *server_info);
|
||||
int setup_server(struct addrinfo *server_address);
|
||||
int setupListenServer(struct addrinfo **pAddrInfo, int pPort);
|
||||
int acceptClient(int pSock, struct addrinfo *server_addr);
|
||||
void delay(long pMillisecs, struct timeval *pRes);
|
||||
int getAddr(char *pHostname, char *pService, int pFamily, int pSockType, struct addrinfo **pAddrInfo);
|
||||
|
||||
// All calls to decode and encode need to be freed
|
||||
char *decode_base64(unsigned char *input, int length, int *tActualLength);
|
||||
// All calls to decode and encode need to be freed
|
||||
char *encode_base64(unsigned char *input, int length);
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user