1 /*****************************************************************************
2 * raop.c: Remote Audio Output Protocol streaming support
3 *****************************************************************************
4 * Copyright (C) 2008 VLC authors and VideoLAN
7 * Author: Michael Hanselmann
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
38 #include <vlc_block.h>
39 #include <vlc_network.h>
40 #include <vlc_strings.h>
41 #include <vlc_charset.h>
43 #include <vlc_gcrypt.h>
46 #include <vlc_memory.h>
48 #define RAOP_PORT 5000
49 #define RAOP_USER_AGENT "VLC " VERSION
52 static const char ps_raop_rsa_pubkey
[] =
53 "\xe7\xd7\x44\xf2\xa2\xe2\x78\x8b\x6c\x1f\x55\xa0\x8e\xb7\x05\x44"
54 "\xa8\xfa\x79\x45\xaa\x8b\xe6\xc6\x2c\xe5\xf5\x1c\xbd\xd4\xdc\x68"
55 "\x42\xfe\x3d\x10\x83\xdd\x2e\xde\xc1\xbf\xd4\x25\x2d\xc0\x2e\x6f"
56 "\x39\x8b\xdf\x0e\x61\x48\xea\x84\x85\x5e\x2e\x44\x2d\xa6\xd6\x26"
57 "\x64\xf6\x74\xa1\xf3\x04\x92\x9a\xde\x4f\x68\x93\xef\x2d\xf6\xe7"
58 "\x11\xa8\xc7\x7a\x0d\x91\xc9\xd9\x80\x82\x2e\x50\xd1\x29\x22\xaf"
59 "\xea\x40\xea\x9f\x0e\x14\xc0\xf7\x69\x38\xc5\xf3\x88\x2f\xc0\x32"
60 "\x3d\xd9\xfe\x55\x15\x5f\x51\xbb\x59\x21\xc2\x01\x62\x9f\xd7\x33"
61 "\x52\xd5\xe2\xef\xaa\xbf\x9b\xa0\x48\xd7\xb8\x13\xa2\xb6\x76\x7f"
62 "\x6c\x3c\xcf\x1e\xb4\xce\x67\x3d\x03\x7b\x0d\x2e\xa3\x0c\x5f\xff"
63 "\xeb\x06\xf8\xd0\x8a\xdd\xe4\x09\x57\x1a\x9c\x68\x9f\xef\x10\x72"
64 "\x88\x55\xdd\x8c\xfb\x9a\x8b\xef\x5c\x89\x43\xef\x3b\x5f\xaa\x15"
65 "\xdd\xe6\x98\xbe\xdd\xf3\x59\x96\x03\xeb\x3e\x6f\x61\x37\x2b\xb6"
66 "\x28\xf6\x55\x9f\x59\x9a\x78\xbf\x50\x06\x87\xaa\x7f\x49\x76\xc0"
67 "\x56\x2d\x41\x29\x56\xf8\x98\x9e\x18\xa6\x35\x5b\xd8\x15\x97\x82"
68 "\x5e\x0f\xc8\x75\x34\x3e\xc7\x82\x11\x76\x25\xcd\xbf\x98\x44\x7b";
70 static const char ps_raop_rsa_exp
[] = "\x01\x00\x01";
72 static const char psz_delim_space
[] = " ";
73 static const char psz_delim_colon
[] = ":";
74 static const char psz_delim_equal
[] = "=";
75 static const char psz_delim_semicolon
[] = ";";
78 /*****************************************************************************
80 *****************************************************************************/
81 static int Open( vlc_object_t
* );
82 static void Close( vlc_object_t
* );
84 static sout_stream_id_sys_t
*Add( sout_stream_t
*, es_format_t
* );
85 static int Del( sout_stream_t
*, sout_stream_id_sys_t
* );
86 static int Send( sout_stream_t
*, sout_stream_id_sys_t
*, block_t
* );
88 static int VolumeCallback( vlc_object_t
*p_this
, char const *psz_cmd
,
89 vlc_value_t oldval
, vlc_value_t newval
,
99 struct sout_stream_sys_t
101 /* Input parameters */
107 sout_stream_id_sys_t
*p_audio_stream
;
109 bool b_volume_callback
;
111 /* Connection state */
115 uint8_t ps_aes_key
[16];
116 uint8_t ps_aes_iv
[16];
117 gcry_cipher_hd_t aes_ctx
;
120 char *psz_client_instance
;
122 char *psz_last_status_line
;
132 size_t i_sendbuf_len
;
136 struct sout_stream_id_sys_t
142 /*****************************************************************************
144 *****************************************************************************/
145 #define SOUT_CFG_PREFIX "sout-raop-"
147 #define HOST_TEXT N_("Host")
148 #define HOST_LONGTEXT N_("Hostname or IP address of target device")
150 #define VOLUME_TEXT N_("Volume")
151 #define VOLUME_LONGTEXT N_("Output volume for analog output: 0 for silence, " \
152 "1..255 from almost silent to very loud.")
154 #define PASSWORD_TEXT N_("Password")
155 #define PASSWORD_LONGTEXT N_("Password for target device.")
157 #define PASSWORD_FILE_TEXT N_("Password file")
158 #define PASSWORD_FILE_LONGTEXT N_("Read password for target device from file.")
161 set_shortname( N_("RAOP") )
162 set_description( N_("Remote Audio Output Protocol stream output") )
163 set_capability( "sout stream", 0 )
164 add_shortcut( "raop" )
165 set_category( CAT_SOUT
)
166 set_subcategory( SUBCAT_SOUT_STREAM
)
167 add_string( SOUT_CFG_PREFIX
"host", "",
168 HOST_TEXT
, HOST_LONGTEXT
, false )
169 add_password( SOUT_CFG_PREFIX
"password", NULL
,
170 PASSWORD_TEXT
, PASSWORD_LONGTEXT
, false )
171 add_loadfile( SOUT_CFG_PREFIX
"password-file", NULL
,
172 PASSWORD_FILE_TEXT
, PASSWORD_FILE_LONGTEXT
, false )
173 add_integer_with_range( SOUT_CFG_PREFIX
"volume", 100, 0, 255,
174 VOLUME_TEXT
, VOLUME_LONGTEXT
, false )
175 set_callbacks( Open
, Close
)
178 static const char *const ppsz_sout_options
[] = {
187 /*****************************************************************************
189 *****************************************************************************/
190 static void FreeSys( vlc_object_t
*p_this
, sout_stream_sys_t
*p_sys
)
192 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
194 if ( p_sys
->i_control_fd
>= 0 )
195 net_Close( p_sys
->i_control_fd
);
196 if ( p_sys
->i_stream_fd
>= 0 )
197 net_Close( p_sys
->i_stream_fd
);
198 if ( p_sys
->b_volume_callback
)
199 var_DelCallback( p_stream
, SOUT_CFG_PREFIX
"volume",
200 VolumeCallback
, NULL
);
202 gcry_cipher_close( p_sys
->aes_ctx
);
204 free( p_sys
->p_sendbuf
);
205 free( p_sys
->psz_host
);
206 free( p_sys
->psz_password
);
207 free( p_sys
->psz_url
);
208 free( p_sys
->psz_session
);
209 free( p_sys
->psz_client_instance
);
210 free( p_sys
->psz_last_status_line
);
214 static void FreeId( sout_stream_id_sys_t
*id
)
219 static void RemoveBase64Padding( char *str
)
221 char *ps_pos
= strchr( str
, '=' );
222 if ( ps_pos
!= NULL
)
226 static int CheckForGcryptErrorWithLine( sout_stream_t
*p_stream
,
227 gcry_error_t i_gcrypt_err
,
228 unsigned int i_line
)
230 if ( i_gcrypt_err
!= GPG_ERR_NO_ERROR
)
232 msg_Err( p_stream
, "gcrypt error (line %d): %s", i_line
,
233 gpg_strerror( i_gcrypt_err
) );
240 /* Wrapper to pass line number for easier debugging */
241 #define CheckForGcryptError( p_this, i_gcrypt_err ) \
242 CheckForGcryptErrorWithLine( p_this, i_gcrypt_err, __LINE__ )
244 /* MGF1 is specified in RFC2437, section 10.2.1. Variables are named after the
247 static int MGF1( vlc_object_t
*p_this
,
248 unsigned char *mask
, size_t l
,
249 const unsigned char *Z
, const size_t zLen
,
252 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
253 gcry_error_t i_gcrypt_err
;
254 gcry_md_hd_t md_handle
= NULL
;
256 unsigned char *ps_md
;
257 uint32_t counter
= 0;
260 int i_err
= VLC_SUCCESS
;
262 assert( mask
!= NULL
);
265 hLen
= gcry_md_get_algo_dlen( Hash
);
267 i_gcrypt_err
= gcry_md_open( &md_handle
, Hash
, 0 );
268 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
270 i_err
= VLC_EGENERIC
;
276 /* 3. For counter from 0 to \lceil{l / hLen}\rceil-1, do the following:
277 * a. Convert counter to an octet string C of length 4 with the
278 * primitive I2OSP: C = I2OSP (counter, 4)
280 C
[0] = (counter
>> 24) & 0xff;
281 C
[1] = (counter
>> 16) & 0xff;
282 C
[2] = (counter
>> 8) & 0xff;
283 C
[3] = counter
& 0xff;
286 /* b. Concatenate the hash of the seed Z and C to the octet string T:
287 * T = T || Hash (Z || C)
289 gcry_md_reset( md_handle
);
290 gcry_md_write( md_handle
, Z
, zLen
);
291 gcry_md_write( md_handle
, C
, 4 );
292 ps_md
= gcry_md_read( md_handle
, Hash
);
294 /* 4. Output the leading l octets of T as the octet string mask. */
295 i_copylen
= __MIN( l
, hLen
);
296 memcpy( mask
, ps_md
, i_copylen
);
302 gcry_md_close( md_handle
);
307 /* EME-OAEP-ENCODE is specified in RFC2437, section 9.1.1.1. Variables are
308 * named after the specification.
310 static int AddOaepPadding( vlc_object_t
*p_this
,
311 unsigned char *EM
, const size_t emLenWithPrefix
,
312 const unsigned char *M
, const size_t mLen
,
313 const unsigned char *P
, const size_t pLen
)
315 const int Hash
= GCRY_MD_SHA1
;
316 const unsigned int hLen
= gcry_md_get_algo_dlen( Hash
);
317 unsigned char *seed
= NULL
;
318 unsigned char *DB
= NULL
;
319 unsigned char *dbMask
= NULL
;
320 unsigned char *seedMask
= NULL
;
324 int i_err
= VLC_SUCCESS
;
326 /* Space for 0x00 prefix in EM. */
327 emLen
= emLenWithPrefix
- 1;
330 * If ||M|| > emLen-2hLen-1 then output "message too long" and stop.
332 if ( mLen
> (emLen
- (2 * hLen
) - 1) )
334 msg_Err( p_this
, "Message too long" );
339 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
340 * octets. The length of PS may be 0.
342 psLen
= emLen
- mLen
- (2 * hLen
) - 1;
346 * Concatenate pHash, PS, the message M, and other padding to form a data
347 * block DB as: DB = pHash || PS || 01 || M
349 DB
= calloc( 1, hLen
+ psLen
+ 1 + mLen
);
350 dbMask
= calloc( 1, emLen
- hLen
);
351 seedMask
= calloc( 1, hLen
);
353 if ( DB
== NULL
|| dbMask
== NULL
|| seedMask
== NULL
)
360 * Let pHash = Hash(P), an octet string of length hLen.
362 gcry_md_hash_buffer( Hash
, DB
, P
, pLen
);
365 * Generate an octet string PS consisting of emLen-||M||-2hLen-1 zero
366 * octets. The length of PS may be 0.
368 memset( DB
+ hLen
, 0, psLen
);
371 * Concatenate pHash, PS, the message M, and other padding to form a data
372 * block DB as: DB = pHash || PS || 01 || M
374 DB
[hLen
+ psLen
] = 0x01;
375 memcpy( DB
+ hLen
+ psLen
+ 1, M
, mLen
);
378 * Generate a random octet string seed of length hLen
380 seed
= gcry_random_bytes( hLen
, GCRY_STRONG_RANDOM
);
388 * Let dbMask = MGF(seed, emLen-hLen).
390 i_err
= MGF1( p_this
, dbMask
, emLen
- hLen
, seed
, hLen
, Hash
);
391 if ( i_err
!= VLC_SUCCESS
)
395 * Let maskedDB = DB \xor dbMask.
397 for ( i
= 0; i
< (emLen
- hLen
); ++i
)
401 * Let seedMask = MGF(maskedDB, hLen).
403 i_err
= MGF1( p_this
, seedMask
, hLen
, DB
, emLen
- hLen
, Hash
);
404 if ( i_err
!= VLC_SUCCESS
)
408 * Let maskedSeed = seed \xor seedMask.
410 for ( i
= 0; i
< hLen
; ++i
)
411 seed
[i
] ^= seedMask
[i
];
414 * Let EM = maskedSeed || maskedDB.
416 assert( (1 + hLen
+ (hLen
+ psLen
+ 1 + mLen
)) == emLenWithPrefix
);
418 memcpy( EM
+ 1, seed
, hLen
);
419 memcpy( EM
+ 1 + hLen
, DB
, hLen
+ psLen
+ 1 + mLen
);
434 static int EncryptAesKeyBase64( vlc_object_t
*p_this
, char **result
)
436 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
437 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
438 gcry_error_t i_gcrypt_err
;
439 gcry_sexp_t sexp_rsa_params
= NULL
;
440 gcry_sexp_t sexp_input
= NULL
;
441 gcry_sexp_t sexp_encrypted
= NULL
;
442 gcry_sexp_t sexp_token_a
= NULL
;
443 gcry_mpi_t mpi_pubkey
= NULL
;
444 gcry_mpi_t mpi_exp
= NULL
;
445 gcry_mpi_t mpi_input
= NULL
;
446 gcry_mpi_t mpi_output
= NULL
;
447 unsigned char ps_padded_key
[256];
448 unsigned char *ps_value
;
452 /* Add RSA-OAES-SHA1 padding */
453 i_err
= AddOaepPadding( p_this
,
454 ps_padded_key
, sizeof( ps_padded_key
),
455 p_sys
->ps_aes_key
, sizeof( p_sys
->ps_aes_key
),
457 if ( i_err
!= VLC_SUCCESS
)
459 i_err
= VLC_EGENERIC
;
461 /* Read public key */
462 i_gcrypt_err
= gcry_mpi_scan( &mpi_pubkey
, GCRYMPI_FMT_USG
,
464 sizeof( ps_raop_rsa_pubkey
) - 1, NULL
);
465 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
469 i_gcrypt_err
= gcry_mpi_scan( &mpi_exp
, GCRYMPI_FMT_USG
, ps_raop_rsa_exp
,
470 sizeof( ps_raop_rsa_exp
) - 1, NULL
);
471 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
474 /* If the input data starts with a set bit (0x80), gcrypt thinks it's a
475 * signed integer and complains. Prefixing it with a zero byte (\0)
476 * works, but involves more work. Converting it to an MPI in our code is
479 i_gcrypt_err
= gcry_mpi_scan( &mpi_input
, GCRYMPI_FMT_USG
,
480 ps_padded_key
, sizeof( ps_padded_key
),
482 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
485 /* Build S-expression with RSA parameters */
486 i_gcrypt_err
= gcry_sexp_build( &sexp_rsa_params
, NULL
,
487 "(public-key(rsa(n %m)(e %m)))",
488 mpi_pubkey
, mpi_exp
);
489 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
492 /* Build S-expression for data */
493 i_gcrypt_err
= gcry_sexp_build( &sexp_input
, NULL
, "(data(value %m))",
495 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
499 i_gcrypt_err
= gcry_pk_encrypt( &sexp_encrypted
, sexp_input
,
501 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
504 /* Extract encrypted data */
505 sexp_token_a
= gcry_sexp_find_token( sexp_encrypted
, "a", 0 );
508 msg_Err( p_this
, "Token 'a' not found in result S-expression" );
512 mpi_output
= gcry_sexp_nth_mpi( sexp_token_a
, 1, GCRYMPI_FMT_USG
);
515 msg_Err( p_this
, "Unable to extract MPI from result" );
519 /* Copy encrypted data into char array */
520 i_gcrypt_err
= gcry_mpi_aprint( GCRYMPI_FMT_USG
, &ps_value
, &i_value_size
,
522 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
527 /* Encode in Base64 */
528 *result
= vlc_b64_encode_binary( ps_value
, i_value_size
);
532 gcry_sexp_release( sexp_rsa_params
);
533 gcry_sexp_release( sexp_input
);
534 gcry_sexp_release( sexp_encrypted
);
535 gcry_sexp_release( sexp_token_a
);
536 gcry_mpi_release( mpi_pubkey
);
537 gcry_mpi_release( mpi_exp
);
538 gcry_mpi_release( mpi_input
);
539 gcry_mpi_release( mpi_output
);
544 static char *ReadPasswordFile( vlc_object_t
*p_this
, const char *psz_path
)
547 char *psz_password
= NULL
;
551 p_file
= vlc_fopen( psz_path
, "rt" );
552 if ( p_file
== NULL
)
554 msg_Err( p_this
, "Unable to open password file '%s': %s", psz_path
,
555 vlc_strerror_c(errno
) );
559 /* Read one line only */
560 if ( fgets( ps_buffer
, sizeof( ps_buffer
), p_file
) == NULL
)
562 if ( ferror( p_file
) )
564 msg_Err( p_this
, "Error reading '%s': %s", psz_path
,
565 vlc_strerror_c(errno
) );
569 /* Nothing was read, but there was no error either. Maybe the file is
570 * empty. Not all implementations of fgets(3) write \0 to the output
571 * buffer in this case.
576 /* Replace first newline with '\0' */
577 psz_newline
= strchr( ps_buffer
, '\n' );
578 if ( psz_newline
!= NULL
)
582 if ( *ps_buffer
== '\0' ) {
583 msg_Err( p_this
, "No password could be read from '%s'", psz_path
);
587 psz_password
= strdup( ps_buffer
);
590 if ( p_file
!= NULL
)
596 /* Splits the value of a received header.
598 * Example: "Transport: RTP/AVP/TCP;unicast;mode=record;server_port=6000"
600 static int SplitHeader( char **ppsz_next
, char **ppsz_name
,
603 /* Find semicolon (separator between assignments) */
604 *ppsz_name
= strsep( ppsz_next
, psz_delim_semicolon
);
608 *ppsz_name
+= strspn( *ppsz_name
, psz_delim_space
);
611 *ppsz_value
= *ppsz_name
;
612 strsep( ppsz_value
, psz_delim_equal
);
620 static void FreeHeader( void *p_value
, void *p_data
)
622 VLC_UNUSED( p_data
);
626 static int ReadStatusLine( vlc_object_t
*p_this
)
628 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
629 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
630 char *psz_line
= NULL
;
633 int i_result
= VLC_EGENERIC
;
635 p_sys
->psz_last_status_line
= net_Gets( p_this
, p_sys
->i_control_fd
,
637 if ( !p_sys
->psz_last_status_line
)
640 /* Create working copy */
641 psz_line
= strdup( p_sys
->psz_last_status_line
);
645 psz_token
= strsep( &psz_next
, psz_delim_space
);
646 if ( !psz_token
|| strncmp( psz_token
, "RTSP/1.", 7 ) != 0 )
648 msg_Err( p_this
, "Unknown protocol (%s)",
649 p_sys
->psz_last_status_line
);
654 psz_token
= strsep( &psz_next
, psz_delim_space
);
657 msg_Err( p_this
, "Request failed (%s)",
658 p_sys
->psz_last_status_line
);
662 i_result
= atoi( psz_token
);
670 static int ReadHeader( vlc_object_t
*p_this
,
671 vlc_dictionary_t
*p_resp_headers
,
674 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
675 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
676 char *psz_original
= NULL
;
677 char *psz_line
= NULL
;
682 int i_err
= VLC_SUCCESS
;
684 psz_line
= net_Gets( p_this
, p_sys
->i_control_fd
, NULL
);
687 i_err
= VLC_EGENERIC
;
691 /* Empty line for response end */
692 if ( psz_line
[0] == '\0' )
696 psz_original
= strdup( psz_line
);
699 psz_token
= strsep( &psz_next
, psz_delim_colon
);
700 if ( !psz_token
|| psz_next
[0] != ' ' )
702 msg_Err( p_this
, "Invalid header format (%s)", psz_original
);
703 i_err
= VLC_EGENERIC
;
707 psz_name
= psz_token
;
708 psz_value
= psz_next
+ 1;
710 vlc_dictionary_insert( p_resp_headers
, psz_name
, strdup( psz_value
) );
714 free( psz_original
);
720 static int WriteAuxHeaders( vlc_object_t
*p_this
,
721 vlc_dictionary_t
*p_req_headers
)
723 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
724 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
725 char **ppsz_keys
= NULL
;
728 int i_err
= VLC_SUCCESS
;
732 ppsz_keys
= vlc_dictionary_all_keys( p_req_headers
);
733 for ( i
= 0; ppsz_keys
[i
]; ++i
)
735 psz_key
= ppsz_keys
[i
];
736 psz_value
= vlc_dictionary_value_for_key( p_req_headers
, psz_key
);
738 i_rc
= net_Printf( p_this
, p_sys
->i_control_fd
, NULL
,
739 "%s: %s\r\n", psz_key
, psz_value
);
742 i_err
= VLC_EGENERIC
;
748 for ( i
= 0; ppsz_keys
[i
]; ++i
)
749 free( ppsz_keys
[i
] );
755 static int SendRequest( vlc_object_t
*p_this
, const char *psz_method
,
756 const char *psz_content_type
, const char *psz_body
,
757 vlc_dictionary_t
*p_req_headers
)
759 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
760 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
761 const unsigned char psz_headers_end
[] = "\r\n";
762 size_t i_body_length
= 0;
763 int i_err
= VLC_SUCCESS
;
766 i_rc
= net_Printf( p_this
, p_sys
->i_control_fd
, NULL
,
768 "User-Agent: " RAOP_USER_AGENT
"\r\n"
769 "Client-Instance: %s\r\n"
771 psz_method
, p_sys
->psz_url
,
772 p_sys
->psz_client_instance
,
776 i_err
= VLC_EGENERIC
;
780 if ( psz_content_type
)
782 i_rc
= net_Printf( p_this
, p_sys
->i_control_fd
, NULL
,
783 "Content-Type: %s\r\n", psz_content_type
);
793 i_body_length
= strlen( psz_body
);
795 i_rc
= net_Printf( p_this
, p_sys
->i_control_fd
, NULL
,
796 "Content-Length: %u\r\n",
797 (unsigned int)i_body_length
);
805 i_err
= WriteAuxHeaders( p_this
, p_req_headers
);
806 if ( i_err
!= VLC_SUCCESS
)
809 i_rc
= net_Write( p_this
, p_sys
->i_control_fd
, NULL
,
810 psz_headers_end
, sizeof( psz_headers_end
) - 1 );
818 net_Write( p_this
, p_sys
->i_control_fd
, NULL
,
819 psz_body
, i_body_length
);
825 static int ParseAuthenticateHeader( vlc_object_t
*p_this
,
826 vlc_dictionary_t
*p_resp_headers
)
828 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
829 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
831 int i_err
= VLC_SUCCESS
;
833 psz_auth
= vlc_dictionary_value_for_key( p_resp_headers
,
834 "WWW-Authenticate" );
835 if ( psz_auth
== NULL
)
837 msg_Err( p_this
, "HTTP 401 response missing "
838 "WWW-Authenticate header" );
839 i_err
= VLC_EGENERIC
;
843 http_auth_ParseWwwAuthenticateHeader( p_this
, &p_sys
->auth
, psz_auth
);
849 static int ExecRequest( vlc_object_t
*p_this
, const char *psz_method
,
850 const char *psz_content_type
, const char *psz_body
,
851 vlc_dictionary_t
*p_req_headers
,
852 vlc_dictionary_t
*p_resp_headers
)
854 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
855 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
856 char *psz_authorization
= NULL
;
858 int i_err
= VLC_SUCCESS
;
862 if ( p_sys
->i_control_fd
< 0 )
864 msg_Err( p_this
, "Control connection not open" );
865 i_err
= VLC_EGENERIC
;
872 /* Send header only when Digest authentication is used */
873 if ( p_sys
->psz_password
!= NULL
&& p_sys
->auth
.psz_nonce
!= NULL
)
875 FREENULL( psz_authorization
);
878 http_auth_FormatAuthorizationHeader( p_this
, &p_sys
->auth
,
881 p_sys
->psz_password
);
882 if ( psz_authorization
== NULL
)
884 i_err
= VLC_EGENERIC
;
888 vlc_dictionary_insert( p_req_headers
, "Authorization",
893 i_err
= SendRequest( p_this
, psz_method
, psz_content_type
, psz_body
,
895 if ( i_err
!= VLC_SUCCESS
)
898 /* Read status line */
899 i_status
= ReadStatusLine( p_this
);
906 vlc_dictionary_clear( p_resp_headers
, FreeHeader
, NULL
);
910 while ( !headers_done
)
912 i_err
= ReadHeader( p_this
, p_resp_headers
, &headers_done
);
913 if ( i_err
!= VLC_SUCCESS
)
917 if ( i_status
== 200 )
918 /* Request successful */
920 else if ( i_status
== 401 )
922 /* Authorization required */
923 if ( i_auth_state
== 1 || p_sys
->psz_password
== NULL
)
925 msg_Err( p_this
, "Access denied, password invalid" );
926 i_err
= VLC_EGENERIC
;
930 i_err
= ParseAuthenticateHeader( p_this
, p_resp_headers
);
931 if ( i_err
!= VLC_SUCCESS
)
938 msg_Err( p_this
, "Request failed (%s), status is %d",
939 p_sys
->psz_last_status_line
, i_status
);
940 i_err
= VLC_EGENERIC
;
946 FREENULL( p_sys
->psz_last_status_line
);
947 free( psz_authorization
);
952 static int AnnounceSDP( vlc_object_t
*p_this
, char *psz_local
,
953 uint32_t i_session_id
)
955 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
956 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
957 vlc_dictionary_t req_headers
;
958 vlc_dictionary_t resp_headers
;
959 unsigned char ps_sac
[16];
960 char *psz_sdp
= NULL
;
961 char *psz_sac_base64
= NULL
;
962 char *psz_aes_key_base64
= NULL
;
963 char *psz_aes_iv_base64
= NULL
;
964 int i_err
= VLC_SUCCESS
;
967 vlc_dictionary_init( &req_headers
, 0 );
968 vlc_dictionary_init( &resp_headers
, 0 );
970 /* Encrypt AES key and encode it in Base64 */
971 i_rc
= EncryptAesKeyBase64( p_this
, &psz_aes_key_base64
);
972 if ( i_rc
!= VLC_SUCCESS
|| psz_aes_key_base64
== NULL
)
974 i_err
= VLC_EGENERIC
;
977 RemoveBase64Padding( psz_aes_key_base64
);
979 /* Encode AES IV in Base64 */
980 psz_aes_iv_base64
= vlc_b64_encode_binary( p_sys
->ps_aes_iv
,
981 sizeof( p_sys
->ps_aes_iv
) );
982 if ( psz_aes_iv_base64
== NULL
)
984 i_err
= VLC_EGENERIC
;
987 RemoveBase64Padding( psz_aes_iv_base64
);
989 /* Random bytes for Apple-Challenge header */
990 gcry_randomize( ps_sac
, sizeof( ps_sac
), GCRY_STRONG_RANDOM
);
992 psz_sac_base64
= vlc_b64_encode_binary( ps_sac
, sizeof( ps_sac
) );
993 if ( psz_sac_base64
== NULL
)
995 i_err
= VLC_EGENERIC
;
998 RemoveBase64Padding( psz_sac_base64
);
1001 * Note: IPv6 addresses also use "IP4". Make sure not to include the
1004 i_rc
= asprintf( &psz_sdp
,
1006 "o=iTunes %u 0 IN IP4 %s\r\n"
1010 "m=audio 0 RTP/AVP 96\r\n"
1011 "a=rtpmap:96 AppleLossless\r\n"
1012 "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n"
1013 "a=rsaaeskey:%s\r\n"
1015 i_session_id
, psz_local
, p_sys
->psz_host
,
1016 psz_aes_key_base64
, psz_aes_iv_base64
);
1024 /* Build and send request */
1025 vlc_dictionary_insert( &req_headers
, "Apple-Challenge", psz_sac_base64
);
1027 i_err
= ExecRequest( p_this
, "ANNOUNCE", "application/sdp", psz_sdp
,
1028 &req_headers
, &resp_headers
);
1029 if ( i_err
!= VLC_SUCCESS
)
1033 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1034 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1037 free( psz_sac_base64
);
1038 free( psz_aes_key_base64
);
1039 free( psz_aes_iv_base64
);
1044 static int SendSetup( vlc_object_t
*p_this
)
1046 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1047 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1048 vlc_dictionary_t req_headers
;
1049 vlc_dictionary_t resp_headers
;
1050 int i_err
= VLC_SUCCESS
;
1056 vlc_dictionary_init( &req_headers
, 0 );
1057 vlc_dictionary_init( &resp_headers
, 0 );
1059 vlc_dictionary_insert( &req_headers
, "Transport",
1060 ((void*)"RTP/AVP/TCP;unicast;interleaved=0-1;"
1063 i_err
= ExecRequest( p_this
, "SETUP", NULL
, NULL
,
1064 &req_headers
, &resp_headers
);
1065 if ( i_err
!= VLC_SUCCESS
)
1068 psz_tmp
= vlc_dictionary_value_for_key( &resp_headers
, "Session" );
1071 msg_Err( p_this
, "Missing 'Session' header during setup" );
1072 i_err
= VLC_EGENERIC
;
1076 free( p_sys
->psz_session
);
1077 p_sys
->psz_session
= strdup( psz_tmp
);
1079 /* Get server_port */
1080 psz_next
= vlc_dictionary_value_for_key( &resp_headers
, "Transport" );
1081 while ( SplitHeader( &psz_next
, &psz_name
, &psz_value
) )
1083 if ( psz_value
&& strcmp( psz_name
, "server_port" ) == 0 )
1085 p_sys
->i_server_port
= atoi( psz_value
);
1090 if ( !p_sys
->i_server_port
)
1092 msg_Err( p_this
, "Missing 'server_port' during setup" );
1093 i_err
= VLC_EGENERIC
;
1098 psz_next
= vlc_dictionary_value_for_key( &resp_headers
,
1099 "Audio-Jack-Status" );
1100 while ( SplitHeader( &psz_next
, &psz_name
, &psz_value
) )
1102 if ( strcmp( psz_name
, "type" ) != 0 )
1105 if ( strcmp( psz_value
, "analog" ) == 0 )
1106 p_sys
->i_jack_type
= JACK_TYPE_ANALOG
;
1108 else if ( strcmp( psz_value
, "digital" ) == 0 )
1109 p_sys
->i_jack_type
= JACK_TYPE_DIGITAL
;
1115 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1116 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1121 static int SendRecord( vlc_object_t
*p_this
)
1123 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1124 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1125 vlc_dictionary_t req_headers
;
1126 vlc_dictionary_t resp_headers
;
1127 int i_err
= VLC_SUCCESS
;
1130 vlc_dictionary_init( &req_headers
, 0 );
1131 vlc_dictionary_init( &resp_headers
, 0 );
1133 vlc_dictionary_insert( &req_headers
, "Range", (void *)"npt=0-" );
1134 vlc_dictionary_insert( &req_headers
, "RTP-Info",
1135 (void *)"seq=0;rtptime=0" );
1136 vlc_dictionary_insert( &req_headers
, "Session",
1137 (void *)p_sys
->psz_session
);
1139 i_err
= ExecRequest( p_this
, "RECORD", NULL
, NULL
,
1140 &req_headers
, &resp_headers
);
1141 if ( i_err
!= VLC_SUCCESS
)
1144 psz_value
= vlc_dictionary_value_for_key( &resp_headers
, "Audio-Latency" );
1146 p_sys
->i_audio_latency
= atoi( psz_value
);
1148 p_sys
->i_audio_latency
= 0;
1151 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1152 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1157 static int SendFlush( vlc_object_t
*p_this
)
1159 VLC_UNUSED( p_this
);
1160 vlc_dictionary_t resp_headers
;
1161 vlc_dictionary_t req_headers
;
1162 int i_err
= VLC_SUCCESS
;
1164 vlc_dictionary_init( &req_headers
, 0 );
1165 vlc_dictionary_init( &resp_headers
, 0 );
1167 vlc_dictionary_insert( &req_headers
, "RTP-Info",
1168 (void *)"seq=0;rtptime=0" );
1170 i_err
= ExecRequest( p_this
, "FLUSH", NULL
, NULL
,
1171 &req_headers
, &resp_headers
);
1172 if ( i_err
!= VLC_SUCCESS
)
1176 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1177 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1182 static int SendTeardown( vlc_object_t
*p_this
)
1184 vlc_dictionary_t resp_headers
;
1185 vlc_dictionary_t req_headers
;
1186 int i_err
= VLC_SUCCESS
;
1188 vlc_dictionary_init( &req_headers
, 0 );
1189 vlc_dictionary_init( &resp_headers
, 0 );
1191 i_err
= ExecRequest( p_this
, "TEARDOWN", NULL
, NULL
,
1192 &req_headers
, &resp_headers
);
1193 if ( i_err
!= VLC_SUCCESS
)
1197 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1198 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1203 static int UpdateVolume( vlc_object_t
*p_this
)
1205 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1206 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1207 vlc_dictionary_t req_headers
;
1208 vlc_dictionary_t resp_headers
;
1209 char *psz_parameters
= NULL
;
1211 int i_err
= VLC_SUCCESS
;
1214 vlc_dictionary_init( &req_headers
, 0 );
1215 vlc_dictionary_init( &resp_headers
, 0 );
1217 /* Our volume is 0..255, RAOP is -144..0 (-144 off, -30..0 on) */
1220 p_sys
->i_volume
= VLC_CLIP( p_sys
->i_volume
, 0, 255 );
1222 if ( p_sys
->i_volume
== 0 )
1225 d_volume
= -30 + ( ( (double)p_sys
->i_volume
) * 30.0 / 255.0 );
1227 /* Format without using locales */
1228 i_rc
= us_asprintf( &psz_parameters
, "volume: %0.6f\r\n", d_volume
);
1235 vlc_dictionary_insert( &req_headers
, "Session",
1236 (void *)p_sys
->psz_session
);
1238 i_err
= ExecRequest( p_this
, "SET_PARAMETER",
1239 "text/parameters", psz_parameters
,
1240 &req_headers
, &resp_headers
);
1241 if ( i_err
!= VLC_SUCCESS
)
1245 vlc_dictionary_clear( &req_headers
, NULL
, NULL
);
1246 vlc_dictionary_clear( &resp_headers
, FreeHeader
, NULL
);
1247 free( psz_parameters
);
1252 static void LogInfo( vlc_object_t
*p_this
)
1254 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1255 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1256 const char *psz_jack_name
;
1258 msg_Info( p_this
, "Audio latency: %d", p_sys
->i_audio_latency
);
1260 switch ( p_sys
->i_jack_type
)
1262 case JACK_TYPE_ANALOG
:
1263 psz_jack_name
= "analog";
1266 case JACK_TYPE_DIGITAL
:
1267 psz_jack_name
= "digital";
1270 case JACK_TYPE_NONE
:
1272 psz_jack_name
= "none";
1276 msg_Info( p_this
, "Jack type: %s", psz_jack_name
);
1279 static void SendAudio( sout_stream_t
*p_stream
, block_t
*p_buffer
)
1281 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1282 gcry_error_t i_gcrypt_err
;
1285 size_t i_payload_len
;
1286 size_t i_realloc_len
;
1289 const uint8_t header
[16] = {
1290 0x24, 0x00, 0x00, 0x00,
1291 0xf0, 0xff, 0x00, 0x00,
1292 0x00, 0x00, 0x00, 0x00,
1293 0x00, 0x00, 0x00, 0x00,
1298 i_len
= sizeof( header
) + p_buffer
->i_buffer
;
1300 /* Buffer resize needed? */
1301 if ( i_len
> p_sys
->i_sendbuf_len
|| p_sys
->p_sendbuf
== NULL
)
1303 /* Grow in blocks of 4K */
1304 i_realloc_len
= (1 + (i_len
/ 4096)) * 4096;
1306 p_sys
->p_sendbuf
= realloc_or_free( p_sys
->p_sendbuf
, i_realloc_len
);
1307 if ( p_sys
->p_sendbuf
== NULL
)
1310 p_sys
->i_sendbuf_len
= i_realloc_len
;
1314 memcpy( p_sys
->p_sendbuf
, header
, sizeof( header
) );
1315 memcpy( p_sys
->p_sendbuf
+ sizeof( header
),
1316 p_buffer
->p_buffer
, p_buffer
->i_buffer
);
1318 /* Calculate payload length and update header */
1319 i_payload_len
= i_len
- 4;
1320 if ( i_payload_len
> 0xffff )
1322 msg_Err( p_stream
, "Buffer is too long (%u bytes)",
1323 (unsigned int)i_payload_len
);
1327 p_sys
->p_sendbuf
[2] = ( i_payload_len
>> 8 ) & 0xff;
1328 p_sys
->p_sendbuf
[3] = i_payload_len
& 0xff;
1331 i_gcrypt_err
= gcry_cipher_reset( p_sys
->aes_ctx
);
1332 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
1336 i_gcrypt_err
= gcry_cipher_setiv( p_sys
->aes_ctx
, p_sys
->ps_aes_iv
,
1337 sizeof( p_sys
->ps_aes_iv
) );
1338 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
1341 /* Encrypt in place. Only full blocks of 16 bytes are encrypted,
1342 * the rest (0-15 bytes) is left unencrypted.
1345 gcry_cipher_encrypt( p_sys
->aes_ctx
,
1346 p_sys
->p_sendbuf
+ sizeof( header
),
1347 ( p_buffer
->i_buffer
/ 16 ) * 16,
1349 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
1353 rc
= net_Write( p_stream
, p_sys
->i_stream_fd
, NULL
,
1354 p_sys
->p_sendbuf
, i_len
);
1358 p_next
= p_buffer
->p_next
;
1359 block_Release( p_buffer
);
1364 block_ChainRelease( p_buffer
);
1369 /*****************************************************************************
1371 *****************************************************************************/
1372 static int Open( vlc_object_t
*p_this
)
1374 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1375 sout_stream_sys_t
*p_sys
;
1376 char psz_local
[NI_MAXNUMERICHOST
];
1377 char *psz_pwfile
= NULL
;
1378 gcry_error_t i_gcrypt_err
;
1379 int i_err
= VLC_SUCCESS
;
1380 uint32_t i_session_id
;
1381 uint64_t i_client_instance
;
1385 config_ChainParse( p_stream
, SOUT_CFG_PREFIX
, ppsz_sout_options
,
1388 p_sys
= calloc( 1, sizeof( *p_sys
) );
1389 if ( p_sys
== NULL
)
1395 p_stream
->pf_add
= Add
;
1396 p_stream
->pf_del
= Del
;
1397 p_stream
->pf_send
= Send
;
1398 p_stream
->p_sys
= p_sys
;
1399 p_stream
->pace_nocontrol
= true;
1401 p_sys
->i_control_fd
= -1;
1402 p_sys
->i_stream_fd
= -1;
1403 p_sys
->i_volume
= var_GetInteger( p_stream
, SOUT_CFG_PREFIX
"volume");
1404 p_sys
->i_jack_type
= JACK_TYPE_NONE
;
1406 http_auth_Init( &p_sys
->auth
);
1408 p_sys
->psz_host
= var_GetNonEmptyString( p_stream
,
1409 SOUT_CFG_PREFIX
"host" );
1410 if ( p_sys
->psz_host
== NULL
)
1412 msg_Err( p_this
, "Missing host" );
1413 i_err
= VLC_EGENERIC
;
1417 p_sys
->psz_password
= var_GetNonEmptyString( p_stream
,
1418 SOUT_CFG_PREFIX
"password" );
1419 if ( p_sys
->psz_password
== NULL
)
1421 /* Try password file instead */
1422 psz_pwfile
= var_GetNonEmptyString( p_stream
,
1423 SOUT_CFG_PREFIX
"password-file" );
1424 if ( psz_pwfile
!= NULL
)
1426 p_sys
->psz_password
= ReadPasswordFile( p_this
, psz_pwfile
);
1427 if ( p_sys
->psz_password
== NULL
)
1429 i_err
= VLC_EGENERIC
;
1435 if ( p_sys
->psz_password
!= NULL
)
1436 msg_Info( p_this
, "Using password authentication" );
1438 var_AddCallback( p_stream
, SOUT_CFG_PREFIX
"volume",
1439 VolumeCallback
, NULL
);
1440 p_sys
->b_volume_callback
= true;
1442 /* Open control connection */
1443 p_sys
->i_control_fd
= net_ConnectTCP( p_stream
, p_sys
->psz_host
,
1445 if ( p_sys
->i_control_fd
< 0 )
1447 msg_Err( p_this
, "Cannot establish control connection to %s:%d (%s)",
1448 p_sys
->psz_host
, RAOP_PORT
, vlc_strerror_c(errno
) );
1449 i_err
= VLC_EGENERIC
;
1453 /* Get local IP address */
1454 if ( net_GetSockAddress( p_sys
->i_control_fd
, psz_local
, NULL
) )
1456 msg_Err( p_this
, "cannot get local IP address" );
1457 i_err
= VLC_EGENERIC
;
1461 /* Random session ID */
1462 gcry_randomize( &i_session_id
, sizeof( i_session_id
),
1463 GCRY_STRONG_RANDOM
);
1465 /* Random client instance */
1466 gcry_randomize( &i_client_instance
, sizeof( i_client_instance
),
1467 GCRY_STRONG_RANDOM
);
1468 if ( asprintf( &p_sys
->psz_client_instance
, "%016"PRIX64
,
1469 i_client_instance
) < 0 )
1475 /* Build session URL */
1476 if ( asprintf( &p_sys
->psz_url
, "rtsp://%s/%u",
1477 psz_local
, i_session_id
) < 0 )
1483 /* Generate AES key and IV */
1484 gcry_randomize( p_sys
->ps_aes_key
, sizeof( p_sys
->ps_aes_key
),
1485 GCRY_STRONG_RANDOM
);
1486 gcry_randomize( p_sys
->ps_aes_iv
, sizeof( p_sys
->ps_aes_iv
),
1487 GCRY_STRONG_RANDOM
);
1490 i_gcrypt_err
= gcry_cipher_open( &p_sys
->aes_ctx
, GCRY_CIPHER_AES
,
1491 GCRY_CIPHER_MODE_CBC
, 0 );
1492 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
1494 i_err
= VLC_EGENERIC
;
1499 i_gcrypt_err
= gcry_cipher_setkey( p_sys
->aes_ctx
, p_sys
->ps_aes_key
,
1500 sizeof( p_sys
->ps_aes_key
) );
1501 if ( CheckForGcryptError( p_stream
, i_gcrypt_err
) )
1503 i_err
= VLC_EGENERIC
;
1507 /* Protocol handshake */
1508 i_err
= AnnounceSDP( p_this
, psz_local
, i_session_id
);
1509 if ( i_err
!= VLC_SUCCESS
)
1512 i_err
= SendSetup( p_this
);
1513 if ( i_err
!= VLC_SUCCESS
)
1516 i_err
= SendRecord( p_this
);
1517 if ( i_err
!= VLC_SUCCESS
)
1520 i_err
= UpdateVolume( p_this
);
1521 if ( i_err
!= VLC_SUCCESS
)
1526 /* Open stream connection */
1527 p_sys
->i_stream_fd
= net_ConnectTCP( p_stream
, p_sys
->psz_host
,
1528 p_sys
->i_server_port
);
1529 if ( p_sys
->i_stream_fd
< 0 )
1531 msg_Err( p_this
, "Cannot establish stream connection to %s:%d (%s)",
1532 p_sys
->psz_host
, p_sys
->i_server_port
,
1533 vlc_strerror_c(errno
) );
1534 i_err
= VLC_EGENERIC
;
1541 if ( i_err
!= VLC_SUCCESS
)
1542 FreeSys( p_this
, p_sys
);
1548 /*****************************************************************************
1550 *****************************************************************************/
1551 static void Close( vlc_object_t
*p_this
)
1553 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1554 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1556 SendFlush( p_this
);
1557 SendTeardown( p_this
);
1559 FreeSys( p_this
, p_sys
);
1563 /*****************************************************************************
1565 *****************************************************************************/
1566 static sout_stream_id_sys_t
*Add( sout_stream_t
*p_stream
, es_format_t
*p_fmt
)
1568 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1569 sout_stream_id_sys_t
*id
= NULL
;
1571 id
= calloc( 1, sizeof( *id
) );
1575 es_format_Copy( &id
->fmt
, p_fmt
);
1577 switch ( id
->fmt
.i_cat
)
1580 if ( id
->fmt
.i_codec
== VLC_CODEC_ALAC
)
1582 if ( p_sys
->p_audio_stream
)
1584 msg_Warn( p_stream
, "Only the first Apple Lossless audio "
1587 else if ( id
->fmt
.audio
.i_rate
!= 44100 ||
1588 id
->fmt
.audio
.i_channels
!= 2 )
1590 msg_Err( p_stream
, "The Apple Lossless audio stream must be "
1591 "encoded with 44100 Hz and 2 channels" );
1595 /* Use this stream */
1596 p_sys
->p_audio_stream
= id
;
1599 else if ( !p_sys
->b_alac_warning
)
1601 msg_Err( p_stream
, "Apple Lossless is the only codec supported. "
1602 "Use the \"transcode\" module for conversion "
1603 "(e.g. \"transcode{acodec=alac,"
1604 "channels=2}\")." );
1605 p_sys
->b_alac_warning
= true;
1611 /* Leave other stream types alone */
1624 /*****************************************************************************
1626 *****************************************************************************/
1627 static int Del( sout_stream_t
*p_stream
, sout_stream_id_sys_t
*id
)
1629 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1630 int i_err
= VLC_SUCCESS
;
1632 if ( p_sys
->p_audio_stream
== id
)
1633 p_sys
->p_audio_stream
= NULL
;
1641 /*****************************************************************************
1643 *****************************************************************************/
1644 static int Send( sout_stream_t
*p_stream
, sout_stream_id_sys_t
*id
,
1647 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1649 if ( id
->fmt
.i_cat
== AUDIO_ES
&& id
== p_sys
->p_audio_stream
)
1651 /* SendAudio takes care of releasing the buffers */
1652 SendAudio( p_stream
, p_buffer
);
1656 block_ChainRelease( p_buffer
);
1663 /*****************************************************************************
1664 * VolumeCallback: called when the volume is changed on the fly.
1665 *****************************************************************************/
1666 static int VolumeCallback( vlc_object_t
*p_this
, char const *psz_cmd
,
1667 vlc_value_t oldval
, vlc_value_t newval
,
1670 VLC_UNUSED(psz_cmd
);
1674 sout_stream_t
*p_stream
= (sout_stream_t
*)p_this
;
1675 sout_stream_sys_t
*p_sys
= p_stream
->p_sys
;
1677 /* TODO: Implement volume change */