[vlc-devel] [PATCH] Add Apple AirTunes stream output plugin

Michael Hanselmann public at hansmi.ch
Sat Dec 13 00:12:28 CET 2008


Hi Rémi

Thank you for the review. My comments are below.

2008/12/11 Rémi Denis-Courmont <rem at videolan.org>:
> Le jeudi 11 décembre 2008 02:46:48 Michael Hanselmann, vous avez écrit :
>> A variant of HTTP is used to negotiate details before sending music
>> data over an AES encrypted TCP connection. RSA is used to encrypt the
>> AES key before transfering it via an HTTP header. To this day, only
>> the public part of the RSA key has been made public while the private
>> part remains unknown.
>
> This "variant of HTTP" is called interleaved RTSP record mode :)

For the sake of even more correctness, according to Wikipedia[1],
"AirPort Express' streaming media capabilities use Apple's Remote
Audio Output Protocol (RAOP), a proprietary variant of RTSP/RTP".

>> The "airtunes" plugin can be used like this:
>> --sout='#transcode{acodec=alac,channels=2}:airtunes{host=hostname}'
>
> Is AirTunes a trademark or anything?

Yes, unfortunately it is[2]. I didn't even think of this before. Would
"raop" (for Remote Audio Output Protocol[3]) be a better name for the
plugin?

>> +AC_ARG_ENABLE(airtunes,
>> +  [  --enable-airtunes       AirTunes stream plugin (default enabled)])
>[…]
>> +fi
>
> Hmm. Do we really need yet another configure option? It seems to only depend
> on libgcrypt.

The "remoteosd" plugin also has its own configure option and has the
same dependencies. What do you prefer?

>> +static const char psz_airtunes_rsa_pubkey[] =
> […]
>> +    "562d412956f8989e18a6355bd81597825e0fc875343ec782117625cdbf98447b";
>
> Does MPI accept binary data? That would take half as much space in the binary.
> Not a big deal though.

I tried using binary data, but didn't get it to work. That was before
I fully grasped what the actual problem was, though. Should I try
again with binary data or can we just leave it as it is?

>> +static int _CheckForGcryptError( sout_stream_t *p_stream,
>> +                                 gcry_error_t i_gcrypt_err,
>> +                                 unsigned int i_line )
>
> I know VLC is abusing them quite much already, but symbols starting with
> underscore are supposed to be reserved for the C run-time.

Renamed it to CheckForGcryptErrorWithLine:
--- a/modules/stream_out/airtunes.c
+++ b/modules/stream_out/airtunes.c
@@ -189,7 +189,7 @@ static void RemoveBase64Padding( char *str )
 }

-static int _CheckForGcryptError( sout_stream_t *p_stream,
-                                 gcry_error_t i_gcrypt_err,
-                                 unsigned int i_line )
+static int CheckForGcryptErrorWithLine( sout_stream_t *p_stream,
+                                        gcry_error_t i_gcrypt_err,
+                                        unsigned int i_line )
 {
     if ( i_gcrypt_err != GPG_ERR_NO_ERROR )
@@ -205,5 +205,5 @@ static int _CheckForGcryptError( sout_stream_t *p_stream,
 /* Wrapper to pass line number for easier debugging */
 #define CheckForGcryptError( p_this, i_gcrypt_err ) \
-    _CheckForGcryptError( p_this, i_gcrypt_err, __LINE__ )
+    CheckForGcryptErrorWithLine( p_this, i_gcrypt_err, __LINE__ )

 /* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the


>> +    /* Build SDP */
>> +    i_rc = asprintf( &psz_sdp,
>> +                     "v=0\r\n"
>> +                     "o=iTunes %u 0 IN IP4 %s\r\n"
>> +                     "s=iTunes\r\n"
>> +                     "c=IN IP4 %s\r\n"
>> +                     "t=0 0\r\n"
>[…]
>> +                     i_session_id, psz_local, p_sys->psz_host,
>> +                     psz_aes_key_base64, psz_aes_iv_base64 );
>
> I wonder if IP6 would ever make any sense here?
> I also guess this is not really RTP but we cannot fix that :(

Another program, rtunes[4], claims to support IPv6 and uses "IP4" in
the SDP data. Unfortunately I can't verify whether it actually works —
the program fails to compile on both Linux and Mac OS X.

>> +        /* Fill buffer */
>> +        memcpy( p_sys->p_sendbuf, header, sizeof( header ) );
>> +        memcpy( p_sys->p_sendbuf + sizeof( header ),
>> +                p_buffer->p_buffer, p_buffer->i_buffer );
>
> What about using block_Realloc() instead?

Do mean to use a block_t instead of a manually malloc'ed buffer? Or to
do the encryption directly in the buffer passed to Send()?

>> +    var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume",
>> +                     VolumeCallback, NULL );
>
> Currently VolumeCallback does nothing, so that's OK. But after that, you need
> to ensure proper thread synchronization, and call var_DelCallback() early
> enough in Close().

I know about the thread synchronization. For correct error handling
(e.g. Open() failing), I added var_DelCallback to FreeSys:
--- a/modules/stream_out/airtunes.c
+++ b/modules/stream_out/airtunes.c
@@ -95,4 +95,5 @@ struct sout_stream_sys_t
     sout_stream_id_t *p_audio_stream;
     bool b_alac_warning;
+    bool b_volume_callback;

     /* Connection state */
@@ -160,10 +161,15 @@ static const char *const ppsz_sout_options[] = {
  * Utilities:
  *****************************************************************************/
-static void FreeSys( sout_stream_sys_t *p_sys )
+static void FreeSys( vlc_object_t *p_this, sout_stream_sys_t *p_sys )
 {
+    sout_stream_t *p_stream = (sout_stream_t*)p_this;
+
     if ( p_sys->i_control_fd >= 0 )
         net_Close( p_sys->i_control_fd );
     if ( p_sys->i_stream_fd >= 0 )
         net_Close( p_sys->i_stream_fd );
+    if ( p_sys->b_volume_callback )
+        var_DelCallback( p_stream, SOUT_CFG_PREFIX "volume",
+                         VolumeCallback, NULL );

     gcry_cipher_close( p_sys->aes_ctx );
@@ -1250,4 +1256,5 @@ static int Open( vlc_object_t *p_this )
     var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume",
                      VolumeCallback, NULL );
+    p_sys->b_volume_callback = true;

     /* Open control connection */
@@ -1348,5 +1355,5 @@ static int Open( vlc_object_t *p_this )
 error:
     if ( i_err != VLC_SUCCESS )
-        FreeSys( p_sys );
+        FreeSys( p_this, p_sys );

     return i_err;
@@ -1365,5 +1372,5 @@ static void Close( vlc_object_t *p_this )
     SendTeardown( p_this );

-    FreeSys( p_sys );
+    FreeSys( p_this, p_sys );

     p_stream->p_sout->i_out_pace_nocontrol--;


>> +    default:
>> +        /* Leave other stream types alone */
>> +        break;
>
> Perhaps, you need to check there is no more than one lossless ES at a time.

It is already being checked:

static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
[…]
    switch ( id->fmt.i_cat )
    {
    case AUDIO_ES:
        if ( id->fmt.i_codec == VLC_FOURCC('a', 'l', 'a', 'c') )
        {
            if ( p_sys->p_audio_stream )
            {
                msg_Warn( p_stream, "Only the first Apple Lossless audio "
                                    "stream is used" );
            }
[…]

p_sys->p_audio_stream points to the "id" (what does it stand for?) of
the first ALAC stream.

[1] http://en.wikipedia.org/wiki/AirPort#AirTunes
[2] http://www.apple.com/legal/trademark/appletmlist.html
[3] http://en.wikipedia.org/wiki/Remote_Audio_Output_Protocol
[4] http://www.nazgul.ch/dev_rtunes.html

Regards,
Michael

-- 
http://hansmi.ch/



More information about the vlc-devel mailing list