raop.c
资源名称:vlc-1.0.5.zip [点击查看]
上传用户:kjfoods
上传日期:2020-07-06
资源大小:29949k
文件大小:43k
源码类别:
midi
开发平台:
Unix_Linux
- /*****************************************************************************
- * raop.c: Remote Audio Output Protocol streaming support
- *****************************************************************************
- * Copyright (C) 2008 the VideoLAN team
- * $Id: c0ee052eeb1396bc4cf66c07f80229fa8eaaa730 $
- *
- * Author: Michael Hanselmann
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
- *****************************************************************************/
- /*****************************************************************************
- * Preamble
- *****************************************************************************/
- #ifdef HAVE_CONFIG_H
- # include "config.h"
- #endif
- #include <assert.h>
- #include <gcrypt.h>
- #include <vlc_common.h>
- #include <vlc_plugin.h>
- #include <vlc_sout.h>
- #include <vlc_block.h>
- #include <vlc_network.h>
- #include <vlc_strings.h>
- #include <vlc_charset.h>
- #include <vlc_gcrypt.h>
- #include <vlc_es.h>
- #define RAOP_PORT 5000
- #define RAOP_USER_AGENT "VLC " VERSION
- static const char ps_raop_rsa_pubkey[] =
- "xe7xd7x44xf2xa2xe2x78x8bx6cx1fx55xa0x8exb7x05x44"
- "xa8xfax79x45xaax8bxe6xc6x2cxe5xf5x1cxbdxd4xdcx68"
- "x42xfex3dx10x83xddx2exdexc1xbfxd4x25x2dxc0x2ex6f"
- "x39x8bxdfx0ex61x48xeax84x85x5ex2ex44x2dxa6xd6x26"
- "x64xf6x74xa1xf3x04x92x9axdex4fx68x93xefx2dxf6xe7"
- "x11xa8xc7x7ax0dx91xc9xd9x80x82x2ex50xd1x29x22xaf"
- "xeax40xeax9fx0ex14xc0xf7x69x38xc5xf3x88x2fxc0x32"
- "x3dxd9xfex55x15x5fx51xbbx59x21xc2x01x62x9fxd7x33"
- "x52xd5xe2xefxaaxbfx9bxa0x48xd7xb8x13xa2xb6x76x7f"
- "x6cx3cxcfx1exb4xcex67x3dx03x7bx0dx2exa3x0cx5fxff"
- "xebx06xf8xd0x8axddxe4x09x57x1ax9cx68x9fxefx10x72"
- "x88x55xddx8cxfbx9ax8bxefx5cx89x43xefx3bx5fxaax15"
- "xddxe6x98xbexddxf3x59x96x03xebx3ex6fx61x37x2bxb6"
- "x28xf6x55x9fx59x9ax78xbfx50x06x87xaax7fx49x76xc0"
- "x56x2dx41x29x56xf8x98x9ex18xa6x35x5bxd8x15x97x82"
- "x5ex0fxc8x75x34x3exc7x82x11x76x25xcdxbfx98x44x7b";
- static const char ps_raop_rsa_exp[] = "x01x00x01";
- static const char psz_delim_space[] = " ";
- static const char psz_delim_colon[] = ":";
- static const char psz_delim_equal[] = "=";
- static const char psz_delim_semicolon[] = ";";
- /*****************************************************************************
- * Prototypes
- *****************************************************************************/
- static int Open( vlc_object_t * );
- static void Close( vlc_object_t * );
- static sout_stream_id_t *Add( sout_stream_t *, es_format_t * );
- static int Del( sout_stream_t *, sout_stream_id_t * );
- static int Send( sout_stream_t *, sout_stream_id_t *, block_t* );
- static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
- vlc_value_t oldval, vlc_value_t newval,
- void *p_data );
- typedef enum
- {
- JACK_TYPE_NONE = 0,
- JACK_TYPE_ANALOG,
- JACK_TYPE_DIGITAL,
- } jack_type_t;
- struct sout_stream_sys_t
- {
- /* Input parameters */
- char *psz_host;
- int i_volume;
- /* Plugin status */
- sout_stream_id_t *p_audio_stream;
- bool b_alac_warning;
- bool b_volume_callback;
- /* Connection state */
- int i_control_fd;
- int i_stream_fd;
- uint8_t ps_aes_key[16];
- uint8_t ps_aes_iv[16];
- gcry_cipher_hd_t aes_ctx;
- char *psz_url;
- char *psz_client_instance;
- char *psz_session;
- int i_cseq;
- int i_server_port;
- int i_audio_latency;
- int i_jack_type;
- /* Send buffer */
- size_t i_sendbuf_len;
- uint8_t *p_sendbuf;
- };
- struct sout_stream_id_t
- {
- es_format_t fmt;
- };
- /*****************************************************************************
- * Module descriptor
- *****************************************************************************/
- #define SOUT_CFG_PREFIX "sout-raop-"
- #define HOST_TEXT N_("Host")
- #define HOST_LONGTEXT N_("Hostname or IP address of target device")
- #define VOLUME_TEXT N_("Volume")
- #define VOLUME_LONGTEXT N_("Output volume for analog output: 0 for silence, "
- "1..255 from almost silent to very loud.")
- vlc_module_begin()
- set_shortname( N_("RAOP") )
- set_description( N_("Remote Audio Output Protocol stream output") )
- set_capability( "sout stream", 0 )
- add_shortcut( "raop" )
- set_category( CAT_SOUT )
- set_subcategory( SUBCAT_SOUT_STREAM )
- add_string( SOUT_CFG_PREFIX "host", "", NULL,
- HOST_TEXT, HOST_LONGTEXT, false )
- add_integer_with_range( SOUT_CFG_PREFIX "volume", 100, 0, 255, NULL,
- VOLUME_TEXT, VOLUME_LONGTEXT, false )
- set_callbacks( Open, Close )
- vlc_module_end()
- static const char *const ppsz_sout_options[] = {
- "host",
- "volume",
- NULL
- };
- /*****************************************************************************
- * Utilities:
- *****************************************************************************/
- 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 );
- free( p_sys->p_sendbuf );
- free( p_sys->psz_host );
- free( p_sys->psz_url );
- free( p_sys->psz_session );
- free( p_sys->psz_client_instance );
- free( p_sys );
- }
- static void FreeId( sout_stream_id_t *id )
- {
- free( id );
- }
- static void RemoveBase64Padding( char *str )
- {
- char *ps_pos = strchr( str, '=' );
- if ( ps_pos != NULL )
- *ps_pos = ' ';
- }
- 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 )
- {
- msg_Err( p_stream, "gcrypt error (line %d): %s", i_line,
- gpg_strerror( i_gcrypt_err ) );
- return 1;
- }
- return 0;
- }
- /* Wrapper to pass line number for easier debugging */
- #define CheckForGcryptError( p_this, i_gcrypt_err )
- CheckForGcryptErrorWithLine( p_this, i_gcrypt_err, __LINE__ )
- /* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
- * specification.
- */
- static int MGF1( vlc_object_t *p_this,
- unsigned char *mask, size_t l,
- const unsigned char *Z, const size_t zLen,
- const int Hash )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- gcry_error_t i_gcrypt_err;
- gcry_md_hd_t md_handle = NULL;
- unsigned int hLen;
- unsigned char *ps_md;
- uint32_t counter = 0;
- uint8_t C[4];
- size_t i_copylen;
- int i_err = VLC_SUCCESS;
- assert( mask != NULL );
- assert( Z != NULL );
- hLen = gcry_md_get_algo_dlen( Hash );
- i_gcrypt_err = gcry_md_open( &md_handle, Hash, 0 );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- while ( l > 0 )
- {
- /* 3. For counter from 0 to lceil{l / hLen}rceil-1, do the following:
- * a. Convert counter to an octet string C of length 4 with the
- * primitive I2OSP: C = I2OSP (counter, 4)
- */
- C[0] = (counter >> 24) & 0xff;
- C[1] = (counter >> 16) & 0xff;
- C[2] = (counter >> 8) & 0xff;
- C[3] = counter & 0xff;
- ++counter;
- /* b. Concatenate the hash of the seed Z and C to the octet string T:
- * T = T || Hash (Z || C)
- */
- gcry_md_reset( md_handle );
- gcry_md_write( md_handle, Z, zLen );
- gcry_md_write( md_handle, C, 4 );
- ps_md = gcry_md_read( md_handle, Hash );
- /* 4. Output the leading l octets of T as the octet string mask. */
- i_copylen = __MIN( l, hLen );
- memcpy( mask, ps_md, i_copylen );
- mask += i_copylen;
- l -= i_copylen;
- }
- error:
- gcry_md_close( md_handle );
- return i_err;
- }
- /* EME-OAEP-ENCODE is specified in RFC2437, section 9.1.1.1. Variables are
- * named after the specification.
- */
- static int AddOaepPadding( vlc_object_t *p_this,
- unsigned char *EM, const size_t emLenWithPrefix,
- const unsigned char *M, const size_t mLen,
- const unsigned char *P, const size_t pLen )
- {
- const int Hash = GCRY_MD_SHA1;
- const unsigned int hLen = gcry_md_get_algo_dlen( Hash );
- unsigned char *seed = NULL;
- unsigned char *DB = NULL;
- unsigned char *dbMask = NULL;
- unsigned char *seedMask = NULL;
- size_t emLen;
- size_t psLen;
- size_t i;
- int i_err = VLC_SUCCESS;
- /* Space for 0x00 prefix in EM. */
- emLen = emLenWithPrefix - 1;
- /* Step 2:
- * If ||M|| > emLen-2hLen-1 then output "message too long" and stop.
- */
- if ( mLen > (emLen - (2 * hLen) - 1) )
- {
- msg_Err( p_this , "Message too long" );
- goto error;
- }
- /* Step 3:
- * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
- * octets. The length of PS may be 0.
- */
- psLen = emLen - mLen - (2 * hLen) - 1;
- /*
- * Step 5:
- * Concatenate pHash, PS, the message M, and other padding to form a data
- * block DB as: DB = pHash || PS || 01 || M
- */
- DB = calloc( 1, hLen + psLen + 1 + mLen );
- dbMask = calloc( 1, emLen - hLen );
- seedMask = calloc( 1, hLen );
- if ( DB == NULL || dbMask == NULL || seedMask == NULL )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- /* Step 4:
- * Let pHash = Hash(P), an octet string of length hLen.
- */
- gcry_md_hash_buffer( Hash, DB, P, pLen );
- /* Step 3:
- * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
- * octets. The length of PS may be 0.
- */
- memset( DB + hLen, 0, psLen );
- /* Step 5:
- * Concatenate pHash, PS, the message M, and other padding to form a data
- * block DB as: DB = pHash || PS || 01 || M
- */
- DB[hLen + psLen] = 0x01;
- memcpy( DB + hLen + psLen + 1, M, mLen );
- /* Step 6:
- * Generate a random octet string seed of length hLen
- */
- seed = gcry_random_bytes( hLen, GCRY_STRONG_RANDOM );
- if ( seed == NULL )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- /* Step 7:
- * Let dbMask = MGF(seed, emLen-hLen).
- */
- i_err = MGF1( p_this, dbMask, emLen - hLen, seed, hLen, Hash );
- if ( i_err != VLC_SUCCESS )
- goto error;
- /* Step 8:
- * Let maskedDB = DB xor dbMask.
- */
- for ( i = 0; i < (emLen - hLen); ++i )
- DB[i] ^= dbMask[i];
- /* Step 9:
- * Let seedMask = MGF(maskedDB, hLen).
- */
- i_err = MGF1( p_this, seedMask, hLen, DB, emLen - hLen, Hash );
- if ( i_err != VLC_SUCCESS )
- goto error;
- /* Step 10:
- * Let maskedSeed = seed xor seedMask.
- */
- for ( i = 0; i < hLen; ++i )
- seed[i] ^= seedMask[i];
- /* Step 11:
- * Let EM = maskedSeed || maskedDB.
- */
- assert( (1 + hLen + (hLen + psLen + 1 + mLen)) == emLenWithPrefix );
- EM[0] = 0x00;
- memcpy( EM + 1, seed, hLen );
- memcpy( EM + 1 + hLen, DB, hLen + psLen + 1 + mLen );
- /* Step 12:
- * Output EM.
- */
- error:
- free( DB );
- free( dbMask );
- free( seedMask );
- free( seed );
- return i_err;
- }
- static int EncryptAesKeyBase64( vlc_object_t *p_this, char **result )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- gcry_error_t i_gcrypt_err;
- gcry_sexp_t sexp_rsa_params = NULL;
- gcry_sexp_t sexp_input = NULL;
- gcry_sexp_t sexp_encrypted = NULL;
- gcry_sexp_t sexp_token_a = NULL;
- gcry_mpi_t mpi_pubkey = NULL;
- gcry_mpi_t mpi_exp = NULL;
- gcry_mpi_t mpi_input = NULL;
- gcry_mpi_t mpi_output = NULL;
- unsigned char ps_padded_key[256];
- unsigned char *ps_value;
- size_t i_value_size;
- int i_err = VLC_SUCCESS;
- /* Add RSA-OAES-SHA1 padding */
- i_err = AddOaepPadding( p_this,
- ps_padded_key, sizeof( ps_padded_key ),
- p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
- NULL, 0 );
- if ( i_err != VLC_SUCCESS )
- goto error;
- /* Read public key */
- i_gcrypt_err = gcry_mpi_scan( &mpi_pubkey, GCRYMPI_FMT_USG,
- ps_raop_rsa_pubkey,
- sizeof( ps_raop_rsa_pubkey ) - 1, NULL );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Read exponent */
- i_gcrypt_err = gcry_mpi_scan( &mpi_exp, GCRYMPI_FMT_USG, ps_raop_rsa_exp,
- sizeof( ps_raop_rsa_exp ) - 1, NULL );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* If the input data starts with a set bit (0x80), gcrypt thinks it's a
- * signed integer and complains. Prefixing it with a zero byte ( )
- * works, but involves more work. Converting it to an MPI in our code is
- * cleaner.
- */
- i_gcrypt_err = gcry_mpi_scan( &mpi_input, GCRYMPI_FMT_USG,
- ps_padded_key, sizeof( ps_padded_key ),
- NULL);
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Build S-expression with RSA parameters */
- i_gcrypt_err = gcry_sexp_build( &sexp_rsa_params, NULL,
- "(public-key(rsa(n %m)(e %m)))",
- mpi_pubkey, mpi_exp );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Build S-expression for data */
- i_gcrypt_err = gcry_sexp_build( &sexp_input, NULL, "(data(value %m))",
- mpi_input );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Encrypt data */
- i_gcrypt_err = gcry_pk_encrypt( &sexp_encrypted, sexp_input,
- sexp_rsa_params );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Extract encrypted data */
- sexp_token_a = gcry_sexp_find_token( sexp_encrypted, "a", 0 );
- if ( !sexp_token_a )
- {
- msg_Err( p_this , "Token 'a' not found in result S-expression" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- mpi_output = gcry_sexp_nth_mpi( sexp_token_a, 1, GCRYMPI_FMT_USG );
- if ( !mpi_output )
- {
- msg_Err( p_this, "Unable to extract MPI from result" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Copy encrypted data into char array */
- i_gcrypt_err = gcry_mpi_aprint( GCRYMPI_FMT_USG, &ps_value, &i_value_size,
- mpi_output );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Encode in Base64 */
- *result = vlc_b64_encode_binary( ps_value, i_value_size );
- error:
- gcry_sexp_release( sexp_rsa_params );
- gcry_sexp_release( sexp_input );
- gcry_sexp_release( sexp_encrypted );
- gcry_sexp_release( sexp_token_a );
- gcry_mpi_release( mpi_pubkey );
- gcry_mpi_release( mpi_exp );
- gcry_mpi_release( mpi_input );
- gcry_mpi_release( mpi_output );
- return i_err;
- }
- /* Splits the value of a received header.
- *
- * Example: "Transport: RTP/AVP/TCP;unicast;mode=record;server_port=6000"
- */
- static int SplitHeader( char **ppsz_next, char **ppsz_name,
- char **ppsz_value )
- {
- /* Find semicolon (separator between assignments) */
- *ppsz_name = strsep( ppsz_next, psz_delim_semicolon );
- if ( *ppsz_name )
- {
- /* Skip spaces */
- *ppsz_name += strspn( *ppsz_name, psz_delim_space );
- /* Get value */
- *ppsz_value = *ppsz_name;
- strsep( ppsz_value, psz_delim_equal );
- }
- else
- *ppsz_value = NULL;
- return !!*ppsz_name;
- }
- static void FreeHeader( void *p_value, void *p_data )
- {
- VLC_UNUSED( p_data );
- free( p_value );
- }
- static int ReadStatusLine( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- char *psz_original = NULL;
- char *psz_line = NULL;
- char *psz_token;
- char *psz_next;
- int i_err = VLC_SUCCESS;
- psz_line = net_Gets( p_this, p_sys->i_control_fd, NULL );
- if ( !psz_line )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- psz_original = strdup( psz_line );
- psz_next = psz_line;
- /* Protocol field */
- psz_token = strsep( &psz_next, psz_delim_space );
- if ( !psz_token || strncmp( psz_token, "RTSP/1.", 7 ) != 0 )
- {
- msg_Err( p_this, "Unknown protocol (%s)", psz_original );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Status field */
- psz_token = strsep( &psz_next, psz_delim_space );
- if ( !psz_token || strcmp( psz_token, "200" ) != 0 )
- {
- msg_Err( p_this, "Request failed (%s)", psz_original );
- i_err = VLC_EGENERIC;
- goto error;
- }
- error:
- free( psz_original );
- free( psz_line );
- return i_err;
- }
- static int ReadHeader( vlc_object_t *p_this,
- vlc_dictionary_t *p_resp_headers,
- int *done )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- char *psz_original = NULL;
- char *psz_line = NULL;
- char *psz_token;
- char *psz_next;
- char *psz_name;
- char *psz_value;
- int i_err = VLC_SUCCESS;
- psz_line = net_Gets( p_this, p_sys->i_control_fd, NULL );
- if ( !psz_line )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Empty line for response end */
- if ( psz_line[0] == ' ' )
- *done = 1;
- else if ( p_resp_headers )
- {
- psz_original = strdup( psz_line );
- psz_next = psz_line;
- psz_token = strsep( &psz_next, psz_delim_colon );
- if ( !psz_token || psz_next[0] != ' ' )
- {
- msg_Err( p_this, "Invalid header format (%s)", psz_original );
- i_err = VLC_EGENERIC;
- goto error;
- }
- psz_name = psz_token;
- psz_value = psz_next + 1;
- vlc_dictionary_insert( p_resp_headers, psz_name, strdup( psz_value ) );
- }
- error:
- free( psz_original );
- free( psz_line );
- return i_err;
- }
- static int WriteAuxHeaders( vlc_object_t *p_this,
- vlc_dictionary_t *p_req_headers )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- char **ppsz_keys = NULL;
- char *psz_key;
- char *psz_value;
- int i_err = VLC_SUCCESS;
- int i_rc;
- size_t i;
- ppsz_keys = vlc_dictionary_all_keys( p_req_headers );
- for ( i = 0; ppsz_keys[i]; ++i )
- {
- psz_key = ppsz_keys[i];
- psz_value = vlc_dictionary_value_for_key( p_req_headers, psz_key );
- i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
- "%s: %srn", psz_key, psz_value );
- if ( i_rc < 0 )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- }
- error:
- for ( i = 0; ppsz_keys[i]; ++i )
- free( ppsz_keys[i] );
- free( ppsz_keys );
- return i_err;
- }
- static int SendRequest( vlc_object_t *p_this, const char *psz_method,
- const char *psz_content_type, const char *psz_body,
- vlc_dictionary_t *p_req_headers )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- const unsigned char psz_headers_end[] = "rn";
- size_t i_body_length = 0;
- int i_err = VLC_SUCCESS;
- int i_rc;
- i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
- "%s %s RTSP/1.0rn"
- "User-Agent: " RAOP_USER_AGENT "rn"
- "Client-Instance: %srn"
- "CSeq: %drn",
- psz_method, p_sys->psz_url,
- p_sys->psz_client_instance,
- ++p_sys->i_cseq );
- if ( i_rc < 0 )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- if ( psz_content_type )
- {
- i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
- "Content-Type: %srn", psz_content_type );
- if ( i_rc < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- }
- if ( psz_body )
- {
- i_body_length = strlen( psz_body );
- i_rc = net_Printf( p_this, p_sys->i_control_fd, NULL,
- "Content-Length: %urn",
- (unsigned int)i_body_length );
- if ( i_rc < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- }
- if ( p_req_headers )
- {
- i_err = WriteAuxHeaders( p_this, p_req_headers );
- if ( i_err != VLC_SUCCESS )
- goto error;
- }
- i_rc = net_Write( p_this, p_sys->i_control_fd, NULL,
- psz_headers_end, sizeof( psz_headers_end ) - 1 );
- if ( i_rc < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- if ( psz_body )
- net_Write( p_this, p_sys->i_control_fd, NULL,
- psz_body, i_body_length );
- error:
- return i_err;
- }
- static int ExecRequest( vlc_object_t *p_this, const char *psz_method,
- const char *psz_content_type, const char *psz_body,
- vlc_dictionary_t *p_req_headers,
- vlc_dictionary_t *p_resp_headers )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- int headers_done;
- int i_err = VLC_SUCCESS;
- if ( p_sys->i_control_fd < 0 )
- {
- msg_Err( p_this, "Control connection not open" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Send request */
- i_err = SendRequest( p_this, psz_method, psz_content_type, psz_body,
- p_req_headers);
- if ( i_err != VLC_SUCCESS )
- goto error;
- /* Read status line */
- i_err = ReadStatusLine( p_this );
- if ( i_err != VLC_SUCCESS )
- goto error;
- if ( p_resp_headers )
- vlc_dictionary_clear( p_resp_headers, FreeHeader, NULL );
- /* Read headers */
- headers_done = 0;
- while ( !headers_done )
- {
- i_err = ReadHeader( p_this, p_resp_headers, &headers_done );
- if ( i_err != VLC_SUCCESS )
- goto error;
- }
- error:
- return i_err;
- }
- static int AnnounceSDP( vlc_object_t *p_this, char *psz_local,
- uint32_t i_session_id )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- vlc_dictionary_t req_headers;
- vlc_dictionary_t resp_headers;
- unsigned char ps_sac[16];
- char *psz_sdp = NULL;
- char *psz_sac_base64 = NULL;
- char *psz_aes_key_base64 = NULL;
- char *psz_aes_iv_base64 = NULL;
- int i_err = VLC_SUCCESS;
- int i_rc;
- vlc_dictionary_init( &req_headers, 0 );
- vlc_dictionary_init( &resp_headers, 0 );
- /* Encrypt AES key and encode it in Base64 */
- i_rc = EncryptAesKeyBase64( p_this, &psz_aes_key_base64 );
- if ( i_rc != VLC_SUCCESS || psz_aes_key_base64 == NULL )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- RemoveBase64Padding( psz_aes_key_base64 );
- /* Encode AES IV in Base64 */
- psz_aes_iv_base64 = vlc_b64_encode_binary( p_sys->ps_aes_iv,
- sizeof( p_sys->ps_aes_iv ) );
- if ( psz_aes_iv_base64 == NULL )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- RemoveBase64Padding( psz_aes_iv_base64 );
- /* Random bytes for Apple-Challenge header */
- gcry_randomize( ps_sac, sizeof( ps_sac ), GCRY_STRONG_RANDOM );
- psz_sac_base64 = vlc_b64_encode_binary( ps_sac, sizeof( ps_sac ) );
- if ( psz_sac_base64 == NULL )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- RemoveBase64Padding( psz_sac_base64 );
- /* Build SDP
- * Note: IPv6 addresses also use "IP4". Make sure not to include the
- * scope ID.
- */
- i_rc = asprintf( &psz_sdp,
- "v=0rn"
- "o=iTunes %u 0 IN IP4 %srn"
- "s=iTunesrn"
- "c=IN IP4 %srn"
- "t=0 0rn"
- "m=audio 0 RTP/AVP 96rn"
- "a=rtpmap:96 AppleLosslessrn"
- "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100rn"
- "a=rsaaeskey:%srn"
- "a=aesiv:%srn",
- i_session_id, psz_local, p_sys->psz_host,
- psz_aes_key_base64, psz_aes_iv_base64 );
- if ( i_rc < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- /* Build and send request */
- vlc_dictionary_insert( &req_headers, "Apple-Challenge", psz_sac_base64 );
- i_err = ExecRequest( p_this, "ANNOUNCE", "application/sdp", psz_sdp,
- &req_headers, &resp_headers);
- if ( i_err != VLC_SUCCESS )
- goto error;
- error:
- vlc_dictionary_clear( &req_headers, NULL, NULL );
- vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
- free( psz_sdp );
- free( psz_sac_base64 );
- free( psz_aes_key_base64 );
- free( psz_aes_iv_base64 );
- return i_err;
- }
- static int SendSetup( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- vlc_dictionary_t req_headers;
- vlc_dictionary_t resp_headers;
- int i_err = VLC_SUCCESS;
- char *psz_tmp;
- char *psz_next;
- char *psz_name;
- char *psz_value;
- vlc_dictionary_init( &req_headers, 0 );
- vlc_dictionary_init( &resp_headers, 0 );
- vlc_dictionary_insert( &req_headers, "Transport",
- ((void*)"RTP/AVP/TCP;unicast;interleaved=0-1;"
- "mode=record") );
- i_err = ExecRequest( p_this, "SETUP", NULL, NULL,
- &req_headers, &resp_headers );
- if ( i_err != VLC_SUCCESS )
- goto error;
- psz_tmp = vlc_dictionary_value_for_key( &resp_headers, "Session" );
- if ( !psz_tmp )
- {
- msg_Err( p_this, "Missing 'Session' header during setup" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- free( p_sys->psz_session );
- p_sys->psz_session = strdup( psz_tmp );
- /* Get server_port */
- psz_next = vlc_dictionary_value_for_key( &resp_headers, "Transport" );
- while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
- {
- if ( psz_value && strcmp( psz_name, "server_port" ) == 0 )
- {
- p_sys->i_server_port = atoi( psz_value );
- break;
- }
- }
- if ( !p_sys->i_server_port )
- {
- msg_Err( p_this, "Missing 'server_port' during setup" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Get jack type */
- psz_next = vlc_dictionary_value_for_key( &resp_headers,
- "Audio-Jack-Status" );
- while ( SplitHeader( &psz_next, &psz_name, &psz_value ) )
- {
- if ( strcmp( psz_name, "type" ) != 0 )
- continue;
- if ( strcmp( psz_value, "analog" ) == 0 )
- p_sys->i_jack_type = JACK_TYPE_ANALOG;
- else if ( strcmp( psz_value, "digital" ) == 0 )
- p_sys->i_jack_type = JACK_TYPE_DIGITAL;
- break;
- }
- error:
- vlc_dictionary_clear( &req_headers, NULL, NULL );
- vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
- return i_err;
- }
- static int SendRecord( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- vlc_dictionary_t req_headers;
- vlc_dictionary_t resp_headers;
- int i_err = VLC_SUCCESS;
- char *psz_value;
- vlc_dictionary_init( &req_headers, 0 );
- vlc_dictionary_init( &resp_headers, 0 );
- vlc_dictionary_insert( &req_headers, "Range", (void *)"npt=0-" );
- vlc_dictionary_insert( &req_headers, "RTP-Info",
- (void *)"seq=0;rtptime=0" );
- vlc_dictionary_insert( &req_headers, "Session",
- (void *)p_sys->psz_session );
- i_err = ExecRequest( p_this, "RECORD", NULL, NULL,
- &req_headers, &resp_headers );
- if ( i_err != VLC_SUCCESS )
- goto error;
- psz_value = vlc_dictionary_value_for_key( &resp_headers, "Audio-Latency" );
- if ( psz_value )
- p_sys->i_audio_latency = atoi( psz_value );
- else
- p_sys->i_audio_latency = 0;
- error:
- vlc_dictionary_clear( &req_headers, NULL, NULL );
- vlc_dictionary_clear( &resp_headers, FreeHeader, NULL );
- return i_err;
- }
- static int SendFlush( vlc_object_t *p_this )
- {
- VLC_UNUSED( p_this );
- vlc_dictionary_t req_headers;
- int i_err = VLC_SUCCESS;
- vlc_dictionary_init( &req_headers, 0 );
- vlc_dictionary_insert( &req_headers, "RTP-Info",
- (void *)"seq=0;rtptime=0" );
- i_err = ExecRequest( p_this, "FLUSH", NULL, NULL, &req_headers, NULL );
- if ( i_err != VLC_SUCCESS )
- goto error;
- error:
- vlc_dictionary_clear( &req_headers, NULL, NULL );
- return i_err;
- }
- static int SendTeardown( vlc_object_t *p_this )
- {
- int i_err = VLC_SUCCESS;
- i_err = ExecRequest( p_this, "TEARDOWN", NULL, NULL, NULL, NULL );
- if ( i_err != VLC_SUCCESS )
- goto error;
- error:
- return i_err;
- }
- static int UpdateVolume( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- vlc_dictionary_t req_headers;
- char *psz_parameters = NULL;
- double d_volume;
- int i_err = VLC_SUCCESS;
- int i_rc;
- vlc_dictionary_init( &req_headers, 0 );
- /* Our volume is 0..255, RAOP is -144..0 (-144 off, -30..0 on) */
- /* Limit range */
- p_sys->i_volume = __MAX( 0, __MIN( p_sys->i_volume, 255 ) );
- if ( p_sys->i_volume == 0 )
- d_volume = -144.0;
- else
- d_volume = -30 + ( ( (double)p_sys->i_volume ) * 30.0 / 255.0 );
- /* Format without using locales */
- i_rc = us_asprintf( &psz_parameters, "volume: %0.6frn", d_volume );
- if ( i_rc < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- vlc_dictionary_insert( &req_headers, "Session",
- (void *)p_sys->psz_session );
- i_err = ExecRequest( p_this, "SET_PARAMETER",
- "text/parameters", psz_parameters,
- &req_headers, NULL );
- if ( i_err != VLC_SUCCESS )
- goto error;
- error:
- vlc_dictionary_clear( &req_headers, NULL, NULL );
- free( psz_parameters );
- return i_err;
- }
- static void LogInfo( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- const char *psz_jack_name;
- msg_Info( p_this, "Audio latency: %d", p_sys->i_audio_latency );
- switch ( p_sys->i_jack_type )
- {
- case JACK_TYPE_ANALOG:
- psz_jack_name = "analog";
- break;
- case JACK_TYPE_DIGITAL:
- psz_jack_name = "digital";
- break;
- case JACK_TYPE_NONE:
- default:
- psz_jack_name = "none";
- break;
- }
- msg_Info( p_this, "Jack type: %s", psz_jack_name );
- }
- static void SendAudio( sout_stream_t *p_stream, block_t *p_buffer )
- {
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- gcry_error_t i_gcrypt_err;
- block_t *p_next;
- size_t i_len;
- size_t i_payload_len;
- size_t i_realloc_len;
- int rc;
- const uint8_t header[16] = {
- 0x24, 0x00, 0x00, 0x00,
- 0xf0, 0xff, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- };
- while ( p_buffer )
- {
- i_len = sizeof( header ) + p_buffer->i_buffer;
- /* Buffer resize needed? */
- if ( i_len > p_sys->i_sendbuf_len || p_sys->p_sendbuf == NULL )
- {
- /* Grow in blocks of 4K */
- i_realloc_len = (1 + (i_len / 4096)) * 4096;
- p_sys->p_sendbuf = realloc( p_sys->p_sendbuf, i_realloc_len );
- if ( p_sys->p_sendbuf == NULL )
- goto error;
- p_sys->i_sendbuf_len = i_realloc_len;
- }
- /* 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 );
- /* Calculate payload length and update header */
- i_payload_len = i_len - 4;
- if ( i_payload_len > 0xffff )
- {
- msg_Err( p_stream, "Buffer is too long (%u bytes)",
- (unsigned int)i_payload_len );
- goto error;
- }
- p_sys->p_sendbuf[2] = ( i_payload_len >> 8 ) & 0xff;
- p_sys->p_sendbuf[3] = i_payload_len & 0xff;
- /* Reset cipher */
- i_gcrypt_err = gcry_cipher_reset( p_sys->aes_ctx );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- goto error;
- /* Set IV */
- i_gcrypt_err = gcry_cipher_setiv( p_sys->aes_ctx, p_sys->ps_aes_iv,
- sizeof( p_sys->ps_aes_iv ) );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- goto error;
- /* Encrypt in place. Only full blocks of 16 bytes are encrypted,
- * the rest (0-15 bytes) is left unencrypted.
- */
- i_gcrypt_err =
- gcry_cipher_encrypt( p_sys->aes_ctx,
- p_sys->p_sendbuf + sizeof( header ),
- ( p_buffer->i_buffer / 16 ) * 16,
- NULL, 0 );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- goto error;
- /* Send data */
- rc = net_Write( p_stream, p_sys->i_stream_fd, NULL,
- p_sys->p_sendbuf, i_len );
- if ( rc < 0 )
- goto error;
- p_next = p_buffer->p_next;
- block_Release( p_buffer );
- p_buffer = p_next;
- }
- error:
- block_ChainRelease( p_buffer );
- return;
- }
- /*****************************************************************************
- * Open:
- *****************************************************************************/
- static int Open( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys;
- char psz_local[NI_MAXNUMERICHOST];
- gcry_error_t i_gcrypt_err;
- int i_err = VLC_SUCCESS;
- uint32_t i_session_id;
- uint64_t i_client_instance;
- vlc_gcrypt_init();
- config_ChainParse( p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
- p_stream->p_cfg );
- p_sys = calloc( 1, sizeof( *p_sys ) );
- if ( p_sys == NULL )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- p_stream->p_sys = p_sys;
- p_stream->pf_add = Add;
- p_stream->pf_del = Del;
- p_stream->pf_send = Send;
- p_stream->p_sout->i_out_pace_nocontrol++;
- p_sys->i_control_fd = -1;
- p_sys->i_stream_fd = -1;
- p_sys->i_volume = var_GetInteger( p_stream, SOUT_CFG_PREFIX "volume");
- p_sys->i_jack_type = JACK_TYPE_NONE;
- p_sys->psz_host = var_GetNonEmptyString( p_stream,
- SOUT_CFG_PREFIX "host" );
- if ( p_sys->psz_host == NULL )
- {
- msg_Err( p_this, "Missing host" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- var_AddCallback( p_stream, SOUT_CFG_PREFIX "volume",
- VolumeCallback, NULL );
- p_sys->b_volume_callback = true;
- /* Open control connection */
- p_sys->i_control_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
- RAOP_PORT );
- if ( p_sys->i_control_fd < 0 )
- {
- msg_Err( p_this, "Cannot establish control connection to %s:%d (%m)",
- p_sys->psz_host, RAOP_PORT );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Get local IP address */
- if ( net_GetSockAddress( p_sys->i_control_fd, psz_local, NULL ) )
- {
- msg_Err( p_this, "cannot get local IP address" );
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Random session ID */
- gcry_randomize( &i_session_id, sizeof( i_session_id ),
- GCRY_STRONG_RANDOM );
- /* Random client instance */
- gcry_randomize( &i_client_instance, sizeof( i_client_instance ),
- GCRY_STRONG_RANDOM );
- if ( asprintf( &p_sys->psz_client_instance, "%016llX",
- i_client_instance ) < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- /* Build session URL */
- if ( asprintf( &p_sys->psz_url, "rtsp://%s/%u",
- psz_local, i_session_id ) < 0 )
- {
- i_err = VLC_ENOMEM;
- goto error;
- }
- /* Generate AES key and IV */
- gcry_randomize( p_sys->ps_aes_key, sizeof( p_sys->ps_aes_key ),
- GCRY_STRONG_RANDOM );
- gcry_randomize( p_sys->ps_aes_iv, sizeof( p_sys->ps_aes_iv ),
- GCRY_STRONG_RANDOM );
- /* Setup AES */
- i_gcrypt_err = gcry_cipher_open( &p_sys->aes_ctx, GCRY_CIPHER_AES,
- GCRY_CIPHER_MODE_CBC, 0 );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Set key */
- i_gcrypt_err = gcry_cipher_setkey( p_sys->aes_ctx, p_sys->ps_aes_key,
- sizeof( p_sys->ps_aes_key ) );
- if ( CheckForGcryptError( p_stream, i_gcrypt_err ) )
- {
- i_err = VLC_EGENERIC;
- goto error;
- }
- /* Protocol handshake */
- i_err = AnnounceSDP( p_this, psz_local, i_session_id );
- if ( i_err != VLC_SUCCESS )
- goto error;
- i_err = SendSetup( p_this );
- if ( i_err != VLC_SUCCESS )
- goto error;
- i_err = SendRecord( p_this );
- if ( i_err != VLC_SUCCESS )
- goto error;
- i_err = UpdateVolume( p_this );
- if ( i_err != VLC_SUCCESS )
- goto error;
- LogInfo( p_this );
- /* Open stream connection */
- p_sys->i_stream_fd = net_ConnectTCP( p_stream, p_sys->psz_host,
- p_sys->i_server_port );
- if ( p_sys->i_stream_fd < 0 )
- {
- msg_Err( p_this, "Cannot establish stream connection to %s:%d (%m)",
- p_sys->psz_host, p_sys->i_server_port );
- i_err = VLC_EGENERIC;
- goto error;
- }
- error:
- if ( i_err != VLC_SUCCESS )
- FreeSys( p_this, p_sys );
- return i_err;
- }
- /*****************************************************************************
- * Close:
- *****************************************************************************/
- static void Close( vlc_object_t *p_this )
- {
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- SendFlush( p_this );
- SendTeardown( p_this );
- FreeSys( p_this, p_sys );
- p_stream->p_sout->i_out_pace_nocontrol--;
- }
- /*****************************************************************************
- * Add:
- *****************************************************************************/
- static sout_stream_id_t *Add( sout_stream_t *p_stream, es_format_t *p_fmt )
- {
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- sout_stream_id_t *id = NULL;
- id = calloc( 1, sizeof( *id ) );
- if ( id == NULL )
- goto error;
- es_format_Copy( &id->fmt, 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" );
- }
- else if ( id->fmt.audio.i_rate != 44100 ||
- id->fmt.audio.i_channels != 2 )
- {
- msg_Err( p_stream, "The Apple Lossless audio stream must be "
- "encoded with 44100 Hz and 2 channels" );
- }
- else
- {
- /* Use this stream */
- p_sys->p_audio_stream = id;
- }
- }
- else if ( !p_sys->b_alac_warning )
- {
- msg_Err( p_stream, "Apple Lossless is the only codec supported. "
- "Use the "transcode" module for conversion "
- "(e.g. "transcode{acodec=alac,"
- "channels=2}")." );
- p_sys->b_alac_warning = true;
- }
- break;
- default:
- /* Leave other stream types alone */
- break;
- }
- return id;
- error:
- FreeId( id );
- return NULL;
- }
- /*****************************************************************************
- * Del:
- *****************************************************************************/
- static int Del( sout_stream_t *p_stream, sout_stream_id_t *id )
- {
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- int i_err = VLC_SUCCESS;
- if ( p_sys->p_audio_stream == id )
- p_sys->p_audio_stream = NULL;
- FreeId( id );
- return i_err;
- }
- /*****************************************************************************
- * Send:
- *****************************************************************************/
- static int Send( sout_stream_t *p_stream, sout_stream_id_t *id,
- block_t *p_buffer )
- {
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- if ( id->fmt.i_cat == AUDIO_ES && id == p_sys->p_audio_stream )
- {
- /* SendAudio takes care of releasing the buffers */
- SendAudio( p_stream, p_buffer );
- }
- else
- {
- block_ChainRelease( p_buffer );
- }
- return VLC_SUCCESS;
- }
- /*****************************************************************************
- * VolumeCallback: called when the volume is changed on the fly.
- *****************************************************************************/
- static int VolumeCallback( vlc_object_t *p_this, char const *psz_cmd,
- vlc_value_t oldval, vlc_value_t newval,
- void *p_data )
- {
- VLC_UNUSED(psz_cmd);
- VLC_UNUSED(oldval);
- VLC_UNUSED(p_data);
- VLC_UNUSED(newval);
- sout_stream_t *p_stream = (sout_stream_t*)p_this;
- sout_stream_sys_t *p_sys = p_stream->p_sys;
- /* TODO: Implement volume change */
- VLC_UNUSED(p_sys);
- return VLC_SUCCESS;
- }