merge
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
Type 'make' to build the packet decoder, 'hairtunes'.
|
||||
|
||||
To Run make, you need pkg-config installed (mac):
|
||||
brew install pkg-config
|
||||
|
||||
You need the following installed:
|
||||
openssl
|
||||
libao (if you use homebrew, use brew install libao)
|
||||
@@ -10,9 +7,30 @@ You need the following installed:
|
||||
Perl
|
||||
|
||||
Debian/Ubuntu users need:
|
||||
libssl-dev libcrypt-openssl-rsa-perl libao2 libao-dev2 libio-socket-inet6-perl
|
||||
libssl-dev libcrypt-openssl-rsa-perl libao2 libao-dev libio-socket-inet6-perl
|
||||
|
||||
Perl modules (install from CPAN if needed):
|
||||
Perl modules (e.g. `perl -MCPAN -e 'install X'`):
|
||||
HTTP::Message
|
||||
Crypt::OpenSSL::RSA
|
||||
IO::Socket::INET6
|
||||
|
||||
MacOSX:
|
||||
* install XCode
|
||||
* install homebrew (https://github.com/mxcl/homebrew)
|
||||
* export ARCHFLAGS="-arch x86_64"
|
||||
* brew install pkg-config libao
|
||||
* make
|
||||
* perl -MCPAN -e 'install Crypt::OpenSSL::RSA'
|
||||
* perl -MCPAN -e 'install IO::Socket::INET6'
|
||||
* perl shairport.pl
|
||||
|
||||
OSX 10.5 only bundles perl 5.8, which won't work with shairport.
|
||||
After getting a update here (http://www.perl.org/get.html), it worked.
|
||||
|
||||
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
|
||||
* cp org.mafipulation.shairport.plist ~/Library/LaunchAgents/
|
||||
* launchctl load org.mafipulation.shairport.plist
|
||||
* launchctl unload org.mafipulation.shairport.plist (to remove)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
CFLAGS = `pkg-config --cflags --libs ao openssl`
|
||||
#CFLAGS = `pkg-config --cflags --libs openssl` -lportaudio
|
||||
CFLAGS = `pkg-config --cflags --libs openssl ao`
|
||||
|
||||
hairtunes: hairtunes.c alac.c
|
||||
gcc hairtunes.c alac.c -D__i386 -lm $(CFLAGS) -o hairtunes
|
||||
|
||||
@@ -5,11 +5,9 @@ April 11, 2011
|
||||
|
||||
What it is
|
||||
----------
|
||||
This program emulates an Airport Express for the purpose of streaming music from
|
||||
iTunes and compatible iPods. It implements a server for the Apple RAOP protocol.
|
||||
This program emulates an Airport Express for the purpose of streaming music from iTunes and compatible iPods. It implements a server for the Apple RAOP protocol.
|
||||
|
||||
It probably supports multiple simultaneous streams, if your audio output chain
|
||||
(as detected by libao) does so.
|
||||
It probably supports multiple simultaneous streams, if your audio output chain (as detected by libao) does so.
|
||||
|
||||
Setup
|
||||
-----
|
||||
@@ -19,30 +17,22 @@ Setup
|
||||
|
||||
How to use it
|
||||
-------------
|
||||
1. Make sure avahi-daemon is running and the prerequisites are installed (see
|
||||
INSTALL).
|
||||
2. Edit shairport.pl with your favourite text editor to set the access
|
||||
point name and/or password, if desired.
|
||||
1. Make sure avahi-daemon is running and the prerequisites are installed (see INSTALL).
|
||||
2. Edit shairport.pl with your favourite text editor to set the access point name and/or password, if desired.
|
||||
3. `perl shairport.pl`
|
||||
|
||||
The triangle-in-rectangle Airtunes logo will appear in the iTunes status bar of
|
||||
any machine on the network, or on iPod play controls screen. Choose your access
|
||||
point name to start streaming to the Shairport instance.
|
||||
The triangle-in-rectangle Airtunes logo will appear in the iTunes status bar of any machine on the network, or on iPod play controls screen. Choose your access point name to start streaming to the Shairport instance.
|
||||
|
||||
Thanks
|
||||
------
|
||||
Big thanks to David Hammerton for releasing an ALAC decoder, which is reproduced
|
||||
here in full.
|
||||
Thanks to everyone who has worked to reverse engineer the RAOP protocol - after
|
||||
finding the keys, everything else was pretty much trivial.
|
||||
Thanks also to Apple for obfuscating the private key in the ROM image, using a
|
||||
scheme that made the deobfuscation code itself stand out like a flare.
|
||||
Big thanks to David Hammerton for releasing an ALAC decoder, which is reproduced here in full.
|
||||
Thanks to everyone who has worked to reverse engineer the RAOP protocol - after finding the keys, everything else was pretty much trivial.
|
||||
Thanks also to Apple for obfuscating the private key in the ROM image, using a scheme that made the deobfuscation code itself stand out like a flare.
|
||||
Thanks to Ten Thousand Free Men and their Families for having a computer and stuff.
|
||||
Thanks to wtbw.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
* 0.01 April 5, 2011
|
||||
* initial release
|
||||
* 0.02 April 11, 2011
|
||||
|
||||
+108
-23
@@ -33,9 +33,17 @@
|
||||
#include <netinet/in.h>
|
||||
#include <pthread.h>
|
||||
#include <openssl/aes.h>
|
||||
#include <ao/ao.h>
|
||||
#include <math.h>
|
||||
|
||||
#if !defined(HAVE_AO)
|
||||
#define HAVE_AO 1
|
||||
#endif
|
||||
#if HAVE_AO
|
||||
#include <ao/ao.h>
|
||||
#else
|
||||
#include <portaudio.h>
|
||||
#endif
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
#include <samplerate.h>
|
||||
#endif
|
||||
@@ -46,8 +54,12 @@ int debug = 0;
|
||||
#include "alac.h"
|
||||
|
||||
// default buffer - about half a second
|
||||
#define BUFFER_FRAMES 64
|
||||
#define START_FILL 55
|
||||
//Changed these values to make sound synchronized with airport express during multi-room broadcast.
|
||||
|
||||
#define BUFFER_FRAMES 512
|
||||
#define START_FILL 398
|
||||
|
||||
|
||||
#define MAX_PACKET 2048
|
||||
|
||||
typedef unsigned short seq_t;
|
||||
@@ -233,6 +245,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
fprintf(stderr, "bye!\n");
|
||||
fflush(stderr);
|
||||
|
||||
uninit_output();
|
||||
}
|
||||
|
||||
void init_buffer(void) {
|
||||
@@ -398,11 +412,7 @@ int init_rtp(void) {
|
||||
int type = AF_INET;
|
||||
short *sin_port = &si.sin_port;
|
||||
#endif
|
||||
int sock, csock; // data and control (we treat the streams the same here)
|
||||
|
||||
sock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock==-1)
|
||||
die("Can't create socket!");
|
||||
int sock = -1, csock = -1; // data and control (we treat the streams the same here)
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
#ifdef AF_INET6
|
||||
@@ -418,18 +428,29 @@ int init_rtp(void) {
|
||||
si.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
#endif
|
||||
|
||||
unsigned short port = 6000 - 3;
|
||||
do {
|
||||
port += 3;
|
||||
*sin_port = htons(port);
|
||||
} while (bind(sock, (struct sockaddr*)&si, sizeof(si))==-1);
|
||||
unsigned short port = 6000;
|
||||
while(1) {
|
||||
if(sock < 0)
|
||||
sock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
|
||||
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!");
|
||||
|
||||
csock = socket(type, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (csock==-1)
|
||||
die("Can't create socket!");
|
||||
*sin_port = htons(port + 1);
|
||||
if (bind(csock, (struct sockaddr*)&si, sizeof(si))==-1)
|
||||
die("can't bind control socket");
|
||||
*sin_port = htons(port);
|
||||
int bind1 = bind(sock, (struct sockaddr*)&si, sizeof(si));
|
||||
*sin_port = htons(port + 1);
|
||||
int bind2 = bind(csock, (struct sockaddr*)&si, sizeof(si));
|
||||
|
||||
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);
|
||||
@@ -610,7 +631,11 @@ int stuff_buffer(double playback_rate, short *inptr, short *outptr) {
|
||||
}
|
||||
|
||||
void *audio_thread_func(void *arg) {
|
||||
ao_device *dev = arg;
|
||||
#if HAVE_AO
|
||||
ao_device* dev = arg;
|
||||
#else
|
||||
PaStream* stream = arg;
|
||||
#endif
|
||||
int i, play_samples;
|
||||
|
||||
signed short buf_fill;
|
||||
@@ -653,11 +678,23 @@ void *audio_thread_func(void *arg) {
|
||||
#endif
|
||||
play_samples = stuff_buffer(bf_playback_rate, inbuf, outbuf);
|
||||
|
||||
#if HAVE_AO
|
||||
ao_play(dev, (char *)outbuf, play_samples*4);
|
||||
#else
|
||||
int err = Pa_WriteStream(stream, (char *)outbuf, play_samples*4);
|
||||
if( err != paNoError ) {
|
||||
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
|
||||
exit(1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#define NUM_CHANNELS 2
|
||||
|
||||
int init_output(void) {
|
||||
int err;
|
||||
#if HAVE_AO
|
||||
ao_initialize();
|
||||
int driver = ao_default_driver_id();
|
||||
|
||||
@@ -666,12 +703,50 @@ int init_output(void) {
|
||||
|
||||
fmt.bits = 16;
|
||||
fmt.rate = sampling_rate;
|
||||
fmt.channels = 2;
|
||||
fmt.channels = NUM_CHANNELS;
|
||||
fmt.byte_format = AO_FMT_LITTLE;
|
||||
|
||||
ao_device *dev = ao_open_live(driver, &fmt, 0);
|
||||
void* arg = dev;
|
||||
#else
|
||||
err = Pa_Initialize();
|
||||
if( err != paNoError ) {
|
||||
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int err;
|
||||
PaStreamParameters outputParameters;
|
||||
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
|
||||
outputParameters.channelCount = NUM_CHANNELS;
|
||||
outputParameters.sampleFormat = paInt16;
|
||||
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency;
|
||||
outputParameters.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
/* -- setup stream -- */
|
||||
PaStream* stream = NULL;
|
||||
err = Pa_OpenStream(
|
||||
&stream,
|
||||
NULL,
|
||||
&outputParameters,
|
||||
sampling_rate,
|
||||
OUTFRAME_BYTES,
|
||||
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
||||
NULL, /* no callback, use blocking API */
|
||||
NULL ); /* no callback, so no callback userData */
|
||||
if( err != paNoError ) {
|
||||
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
err = Pa_StartStream( stream );
|
||||
if( err != paNoError ) {
|
||||
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void* arg = stream;
|
||||
#endif
|
||||
|
||||
#ifdef FANCY_RESAMPLING
|
||||
if (fancy_resampling)
|
||||
src = src_new(SRC_SINC_MEDIUM_QUALITY, 2, &err);
|
||||
@@ -680,5 +755,15 @@ int init_output(void) {
|
||||
#endif
|
||||
|
||||
pthread_t audio_thread;
|
||||
pthread_create(&audio_thread, NULL, audio_thread_func, dev);
|
||||
pthread_create(&audio_thread, NULL, audio_thread_func, arg);
|
||||
}
|
||||
|
||||
int uninit_output(void) {
|
||||
#if HAVE_AO
|
||||
#else
|
||||
int err = Pa_Terminate();
|
||||
if( err != paNoError )
|
||||
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
+3
-1
@@ -93,7 +93,8 @@ $SIG{CHLD} = \&REAP;
|
||||
|
||||
my %conns;
|
||||
|
||||
$SIG{TERM} = $SIG{INT} = sub {
|
||||
$SIG{TERM} = $SIG{INT} = $SIG{__DIE__} = sub {
|
||||
print "killed\n";
|
||||
map { eval { kill $_->{decoder_pid} } } keys %conns;
|
||||
kill 9, $avahi_publish;
|
||||
exit(0);
|
||||
@@ -105,6 +106,7 @@ my $rsa = Crypt::OpenSSL::RSA->new_private_key($airport_pem) || die "RSA private
|
||||
my $listen;
|
||||
{
|
||||
eval {
|
||||
local $SIG{'__DIE__'};
|
||||
$listen = new IO::Socket::INET6(Listen => 1,
|
||||
Domain => AF_INET6,
|
||||
LocalPort => 5000,
|
||||
|
||||
Reference in New Issue
Block a user