Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ios9 airplay fairlplay #60

Open
wants to merge 31 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e49634a
add new features global for airplay
foxsen Jan 10, 2015
53aa064
add a new response init func for airplay usage
foxsen Jan 10, 2015
359492d
Add preliminary airplay support
foxsen Jan 10, 2015
f65a81d
add aac_eld codec support
foxsen Apr 12, 2015
af087ec
debug version; rsa version works
foxsen Apr 19, 2015
db26e00
fix aes decrypt call, now working with fairplay
foxsen Apr 20, 2015
1c9293a
rename fpsetup.* to fairplay.*
foxsen Apr 20, 2015
f98fe39
add checking for fairplay calls
foxsen Apr 20, 2015
111afb5
add command line option --enable-airplay to start up airplay service
foxsen Apr 20, 2015
c3e14b2
more comments to help understand current status
foxsen Apr 20, 2015
b829c8f
update README.md
foxsen Apr 20, 2015
2b07e2b
add doc for --enable_airplay
foxsen Apr 20, 2015
7d21d71
use public fairplay server
foxsen Apr 20, 2015
85cc2d7
seperate audio related code from shairplay.c
foxsen Apr 21, 2015
dd79c89
audio seperation
foxsen Apr 21, 2015
a796eb6
disable ipv6 for now, it somehow does not work
foxsen Apr 21, 2015
77eb4fe
use a thread for audio output to improve sync
foxsen Apr 21, 2015
b74f572
revise the protocol to fairplay server
foxsen Apr 24, 2015
7fbabbb
add conditional variable operations to avoid busy buffer dequeue loop
foxsen Apr 24, 2015
9bde639
forgot to change back IP to public one
foxsen Apr 24, 2015
6ca0028
fix TEARDOWN logic
foxsen Apr 24, 2015
d829ce3
Update README.md
klattimer May 15, 2015
b9cb8d8
Merge pull request #1 from klattimer/master
May 16, 2015
9ab543b
Update README.md
mathewpeterson Sep 1, 2015
da3ec6c
Fixes typo in README.md
mathewpeterson Sep 1, 2015
2fd7080
Merge pull request #2 from mathewpeterson/update-readme
Sep 9, 2015
43ec5bf
initialize server_fd6 to -1 when ENABLE_IPV6 is not set
foxsen Nov 25, 2015
9c2fae6
use closesocket instead of close to be compatible for windows
foxsen Nov 25, 2015
6c47b83
fix wrong usage of aac apis
foxsen Nov 25, 2015
6da48dd
Update README.md
Nov 25, 2015
247bfa8
Update README.md
Nov 25, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ Shairplay
Free portable AirPlay server implementation similar to [ShairPort](https://github.com/abrasive/shairport).

Currently only AirPort Express emulation is supported.
Update by foxsen, 2015/11/25:
* small fixes for AAC-ELD/ipv6 handling etc. Thanks to HaoLi for pointing them out. (untested)
* I have figured out the different handling of iso9 and will update the code when having more time.
* I am also learned most of the necessary information for the video mirroring part, but don't have enough time and interest to implement all the details and maintain them. If some guys would like to implement a fully open source shairplay that can handle both audio and video, I would like to help. Instead of the protocol implementation, I am interested more on the fairplay part and will try to get the hidden private key at spare time. I think I am close to that.

Update by foxsen, 2015/4/20:
Experimental support for fairplay protocol and airplay:
* fairplay encrypted audio is supported (et == 3)
* AAC-ELD audio is supported
* airplay service framework is added, up to the point that the mirroring
connection starts streaming. But the UI and callbacks need to be done.
* fairplay support is performed via interactions with a server

Disclaimer
----------
Expand All @@ -18,10 +30,13 @@ First you need to install some dependencies, for example on Ubuntu you would
write:
```
sudo apt-get install autoconf automake libtool
sudo apt-get install libltdl-dev libao-dev libavahi-compat-libdnssd-dev
sudo apt-get install libltdl-dev libao-dev libavahi-compat-libdnssd-dev \
libplist-dev libfdk-aac-dev libssl-dev
sudo apt-get install avahi-daemon
```

Note: The Raspbian apt repositories do not have [libplist](https://github.com/Chronic-Dev/libplist) or [libfdk-aac](https://github.com/mstorsjo/fdk-aac). You will need to compile these from source.

```
./autogen.sh
./configure
Expand All @@ -46,6 +61,7 @@ Usage: shairplay [OPTION...]
--ao_driver=driver Sets the ao driver (optional)
--ao_devicename=devicename Sets the ao device name (optional)
--ao_deviceid=id Sets the ao device id (optional)
--enable_airplay Start airplay service
-h, --help This help
```

Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ AC_CONFIG_FILES(
[src/Makefile]
[src/lib/Makefile]
[src/lib/alac/Makefile]
[src/lib/aac_eld/Makefile]
[src/lib/crypto/Makefile]
)
AC_OUTPUT
61 changes: 61 additions & 0 deletions include/shairplay/airplay.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#ifndef AIRPLAY_H
#define AIRPLAY_H

#if defined (WIN32) && defined(DLL_EXPORT)
# define AIRPLAY_API __declspec(dllexport)
#else
# define AIRPLAY_API
#endif

#ifdef __cplusplus
extern "C" {
#endif


/* Define syslog style log levels */
#define AIRPLAY_LOG_EMERG 0 /* system is unusable */
#define AIRPLAY_LOG_ALERT 1 /* action must be taken immediately */
#define AIRPLAY_LOG_CRIT 2 /* critical conditions */
#define AIRPLAY_LOG_ERR 3 /* error conditions */
#define AIRPLAY_LOG_WARNING 4 /* warning conditions */
#define AIRPLAY_LOG_NOTICE 5 /* normal but significant condition */
#define AIRPLAY_LOG_INFO 6 /* informational */
#define AIRPLAY_LOG_DEBUG 7 /* debug-level messages */


typedef struct airplay_s airplay_t;

typedef void (*airplay_log_callback_t)(void *cls, int level, const char *msg);

struct airplay_callbacks_s {
void* cls;

/* Compulsory callback functions */
void* (*audio_init)(void *cls, int bits, int channels, int samplerate);
void (*audio_process)(void *cls, void *session, const void *buffer, int buflen);
void (*audio_destroy)(void *cls, void *session);

/* Optional but recommended callback functions */
void (*audio_flush)(void *cls, void *session);
void (*audio_set_volume)(void *cls, void *session, float volume);
void (*audio_set_metadata)(void *cls, void *session, const void *buffer, int buflen);
void (*audio_set_coverart)(void *cls, void *session, const void *buffer, int buflen);
};
typedef struct airplay_callbacks_s airplay_callbacks_t;

AIRPLAY_API airplay_t *airplay_init(int max_clients, airplay_callbacks_t *callbacks, const char *pemkey, int *error);
AIRPLAY_API airplay_t *airplay_init_from_keyfile(int max_clients, airplay_callbacks_t *callbacks, const char *keyfile, int *error);

AIRPLAY_API void airplay_set_log_level(airplay_t *airplay, int level);
AIRPLAY_API void airplay_set_log_callback(airplay_t *airplay, airplay_log_callback_t callback, void *cls);

AIRPLAY_API int airplay_start(airplay_t *airplay, unsigned short *port, const char *hwaddr, int hwaddrlen, const char *password);
AIRPLAY_API int airplay_is_running(airplay_t *airplay);
AIRPLAY_API void airplay_stop(airplay_t *airplay);

AIRPLAY_API void airplay_destroy(airplay_t *airplay);

#ifdef __cplusplus
}
#endif
#endif
5 changes: 3 additions & 2 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ AM_CPPFLAGS = -I$(top_srcdir)/include

if HAVE_LIBAO
bin_PROGRAMS = shairplay
shairplay_SOURCES = shairplay.c
shairplay_SOURCES = shairplay.c audio.h audio.c
shairplay_LDADD = lib/libshairplay.la
shairplay_CFLAGS =
shairplay_LDFLAGS = -static-libtool-libs

shairplay_CFLAGS += $(libao_CFLAGS)
shairplay_LDADD += $(libao_LIBS)
shairplay_LDADD += $(libao_LIBS)
endif

219 changes: 219 additions & 0 deletions src/audio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* Copyright (C) 2012-2013 Juho Vähä-Herttua
*
* 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#ifdef WIN32
# include <windows.h>
#endif

#include <ao/ao.h>
#include "shairplay/raop.h"
//#include "lib/threads.h"

typedef struct {
ao_device *device;

int buffering;
int buflen;
char buffer[8192];

float volume;

// int running;
// thread_handle_t thread;
} shairplay_session_t;

static char *ao_driver = NULL, *ao_devicename = NULL, *ao_deviceid = NULL;

static ao_device *
audio_open_device(char *driver, char *devicename, char *deviceid, int bits, int channels, int samplerate)
{
ao_device *device = NULL;
ao_option *ao_options = NULL;
ao_sample_format format;
int driver_id;

/* Get the libao driver ID */
if (strlen(driver)) {
driver_id = ao_driver_id(driver);
} else {
driver_id = ao_default_driver_id();
}

/* Add all available libao options */
if (strlen(devicename)) {
ao_append_option(&ao_options, "dev", devicename);
}
if (strlen(deviceid)) {
ao_append_option(&ao_options, "id", deviceid);
}


/* Set audio format */
memset(&format, 0, sizeof(format));
format.bits = bits;
format.channels = channels;
format.rate = samplerate;
format.byte_format = AO_FMT_NATIVE;

/* Try opening the actual device */
device = ao_open_live(driver_id, &format, ao_options);
ao_free_options(ao_options);
return device;
}

static void *
audio_init(void *cls, int bits, int channels, int samplerate)
{
shairplay_session_t *session;

session = calloc(1, sizeof(shairplay_session_t));
assert(session);

session->device = audio_open_device(ao_driver, ao_devicename, ao_deviceid, bits, channels, samplerate);
if (session->device == NULL) {
printf("Error opening device %d\n", errno);
}
assert(session->device);

session->buffering = 1;
session->volume = 1.0f;

//session->running = 1;
//THREAD_CREATE(&session->thread, audio_thread, cls);
return session;
}

static int
audio_output(shairplay_session_t *session, const void *buffer, int buflen)
{
short *shortbuf;
char tmpbuf[4096];
int tmpbuflen, i;

tmpbuflen = (buflen > sizeof(tmpbuf)) ? sizeof(tmpbuf) : buflen;
memcpy(tmpbuf, buffer, tmpbuflen);
if (ao_is_big_endian()) {
for (i=0; i<tmpbuflen/2; i++) {
char tmpch = tmpbuf[i*2];
tmpbuf[i*2] = tmpbuf[i*2+1];
tmpbuf[i*2+1] = tmpch;
}
}
shortbuf = (short *)tmpbuf;
for (i=0; i<tmpbuflen/2; i++) {
shortbuf[i] = shortbuf[i] * session->volume;
}
ao_play(session->device, tmpbuf, tmpbuflen);
return tmpbuflen;
}

static void
audio_process(void *cls, void *opaque, const void *buffer, int buflen)
{
shairplay_session_t *session = opaque;
int processed;

if (session->buffering) {
printf("Buffering... %d %d\n", session->buflen + buflen, sizeof(session->buffer));
if (session->buflen+buflen < sizeof(session->buffer)) {
memcpy(session->buffer+session->buflen, buffer, buflen);
session->buflen += buflen;
return;
}
session->buffering = 0;
printf("Finished buffering...\n");

processed = 0;
while (processed < session->buflen) {
processed += audio_output(session,
session->buffer+processed,
session->buflen-processed);
}
session->buflen = 0;
}

processed = 0;
while (processed < buflen) {
processed += audio_output(session,
buffer+processed,
buflen-processed);
}
}

static void
audio_destroy(void *cls, void *opaque)
{
shairplay_session_t *session = opaque;

ao_close(session->device);
free(session);
}

static void
audio_set_volume(void *cls, void *opaque, float volume)
{
shairplay_session_t *session = opaque;
session->volume = pow(10.0, 0.05*volume);
}

int audio_prepare(char *driver, char *devicename, char *deviceid, raop_callbacks_t *raop_cbs)
{
ao_device *device = NULL;

ao_initialize();

device = audio_open_device(driver, devicename, deviceid, 16, 2, 44100);
if (device == NULL) {
fprintf(stderr, "Error opening audio device %d\n", errno);
fprintf(stderr, "Please check your libao settings and try again\n");
return -1;
} else {
ao_close(device);
device = NULL;
}

ao_driver = driver;
ao_devicename = devicename;
ao_deviceid = deviceid;

memset(raop_cbs, 0, sizeof(*raop_cbs));
raop_cbs->cls = NULL;
raop_cbs->audio_init = audio_init;
raop_cbs->audio_process = audio_process;
raop_cbs->audio_destroy = audio_destroy;
raop_cbs->audio_set_volume = audio_set_volume;

return 0;
}

void audio_shutdown()
{
ao_shutdown();
}
9 changes: 9 additions & 0 deletions src/audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#ifndef __SHAIRPLAY_AUDIO_H__
#define __SHAIRPLAY_AUDIO_H__

#include <ao/ao.h>

int audio_prepare(char *driver, char *devicename, char *deviceid, raop_callbacks_t *raop_cbs);
void audio_shutdown();

#endif
10 changes: 6 additions & 4 deletions src/lib/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
SUBDIRS = crypto alac
SUBDIRS = crypto alac aac_eld

AM_CPPFLAGS = -I$(top_srcdir)/include/shairplay

lib_LTLIBRARIES = libshairplay.la
libshairplay_la_SOURCES = base64.c base64.h digest.c digest.h dnssd.c dnssdint.h http_parser.c http_parser.h http_request.c http_request.h http_response.c http_response.h httpd.c httpd.h logger.c logger.h netutils.c netutils.h raop.c raop_buffer.c raop_buffer.h raop_rtp.c raop_rtp.h rsakey.c rsakey.h rsapem.c rsapem.h sdp.c sdp.h utils.c utils.h compat.h memalign.h sockets.h threads.h
libshairplay_la_SOURCES = base64.c base64.h digest.c digest.h dnssd.c dnssdint.h http_parser.c http_parser.h http_request.c http_request.h http_response.c http_response.h httpd.c httpd.h logger.c logger.h netutils.c netutils.h raop.c raop_buffer.c raop_buffer.h raop_rtp.c raop_rtp.h rsakey.c rsakey.h rsapem.c rsapem.h sdp.c sdp.h utils.c utils.h compat.h memalign.h sockets.h threads.h airplay.c fairplay.h fairplay.c
libshairplay_la_CPPFLAGS = $(AM_CPPFLAGS)

# This library depends on 3rd party libraries
libshairplay_la_LIBADD = crypto/libcrypto.la alac/libalac.la
libshairplay_la_LIBADD = crypto/libcrypto.la alac/libalac.la aac_eld/libaac_eld.la
libshairplay_la_LDFLAGS = -no-undefined -version-info 0:0:0

### Update -version-info above with the following rules
Expand All @@ -26,7 +26,9 @@ libshairplay_la_LDFLAGS = -no-undefined -version-info 0:0:0
###

libshairplay_la_LIBADD += $(LIBADD_DLOPEN)
libshairplay_la_LIBADD += $(LIBM)
libshairplay_la_LIBADD += $(LIBM)
libshairplay_la_LIBADD += -lfdk-aac
libshairplay_la_LIBADD += -lplist

if OS_WIN32
libshairplay_la_CPPFLAGS += -DDLL_EXPORT
Expand Down
2 changes: 2 additions & 0 deletions src/lib/aac_eld/Makefile.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
noinst_LTLIBRARIES = libaac_eld.la
libaac_eld_la_SOURCES = aac_eld.c aac_eld.h
Loading