2013-09-24 19:45:40 -03:00
/***
This file is part of PulseAudio .
Copyright 2008 - 2013 João Paulo Rechi Vita
Copyright 2011 - 2013 BMW Car IT GmbH .
PulseAudio is free software ; you can redistribute it and / or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation ; either version 2.1 of the
License , or ( at your option ) any later version .
PulseAudio 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 Lesser General Public
License along with PulseAudio ; if not , write to the Free Software
Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307
USA .
* * */
# ifdef HAVE_CONFIG_H
# include <config.h>
# endif
2013-09-24 19:45:51 -03:00
# include <errno.h>
# include <arpa/inet.h>
2013-09-24 19:45:46 -03:00
# include <sbc/sbc.h>
2013-09-24 19:45:51 -03:00
# include <pulse/rtclock.h>
# include <pulse/timeval.h>
# include <pulsecore/core-error.h>
2014-08-22 11:07:11 +03:00
# include <pulsecore/core-rtclock.h>
2013-09-24 19:45:43 -03:00
# include <pulsecore/core-util.h>
# include <pulsecore/i18n.h>
2013-09-24 19:45:40 -03:00
# include <pulsecore/module.h>
2013-09-24 19:45:41 -03:00
# include <pulsecore/modargs.h>
2013-09-24 19:45:49 -03:00
# include <pulsecore/poll.h>
# include <pulsecore/rtpoll.h>
2013-09-24 19:45:56 -03:00
# include <pulsecore/shared.h>
2013-09-24 19:45:51 -03:00
# include <pulsecore/socket-util.h>
2013-09-24 19:45:49 -03:00
# include <pulsecore/thread.h>
# include <pulsecore/thread-mq.h>
2013-09-24 19:45:51 -03:00
# include <pulsecore/time-smoother.h>
2013-09-24 19:45:40 -03:00
2013-09-24 19:45:46 -03:00
# include "a2dp-codecs.h"
2013-09-24 19:45:40 -03:00
# include "bluez5-util.h"
2013-09-24 19:45:51 -03:00
# include "rtp.h"
2013-09-24 19:45:40 -03:00
# include "module-bluez5-device-symdef.h"
PA_MODULE_AUTHOR ( " João Paulo Rechi Vita " ) ;
PA_MODULE_DESCRIPTION ( " BlueZ 5 Bluetooth audio sink and source " ) ;
PA_MODULE_VERSION ( PACKAGE_VERSION ) ;
PA_MODULE_LOAD_ONCE ( false ) ;
2013-09-24 19:45:41 -03:00
PA_MODULE_USAGE ( " path=<device object path> " ) ;
2013-09-24 19:45:51 -03:00
# define MAX_PLAYBACK_CATCH_UP_USEC (100 * PA_USEC_PER_MSEC)
# define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC)
2014-08-22 11:07:11 +03:00
# define FIXED_LATENCY_PLAYBACK_SCO (125 * PA_USEC_PER_MSEC)
2013-09-24 19:45:51 -03:00
# define FIXED_LATENCY_RECORD_A2DP (25 * PA_USEC_PER_MSEC)
2014-08-22 11:07:11 +03:00
# define FIXED_LATENCY_RECORD_SCO (25 * PA_USEC_PER_MSEC)
2013-09-24 19:45:51 -03:00
# define BITPOOL_DEC_LIMIT 32
# define BITPOOL_DEC_STEP 5
2013-09-24 19:45:41 -03:00
static const char * const valid_modargs [ ] = {
" path " ,
NULL
} ;
2013-09-24 19:45:51 -03:00
enum {
BLUETOOTH_MESSAGE_IO_THREAD_FAILED ,
2014-02-04 19:04:05 -03:00
BLUETOOTH_MESSAGE_STREAM_FD_HUP ,
2013-09-24 19:45:51 -03:00
BLUETOOTH_MESSAGE_MAX
} ;
typedef struct bluetooth_msg {
pa_msgobject parent ;
pa_card * card ;
} bluetooth_msg ;
PA_DEFINE_PRIVATE_CLASS ( bluetooth_msg , pa_msgobject ) ;
# define BLUETOOTH_MSG(o) (bluetooth_msg_cast(o))
2013-09-24 19:45:46 -03:00
typedef struct sbc_info {
sbc_t sbc ; /* Codec data */
bool sbc_initialized ; /* Keep track if the encoder is initialized */
size_t codesize , frame_length ; /* SBC Codesize, frame_length. We simply cache those values here */
uint16_t seq_num ; /* Cumulative packet sequence */
uint8_t min_bitpool ;
uint8_t max_bitpool ;
void * buffer ; /* Codec transfer buffer */
size_t buffer_size ; /* Size of the buffer */
} sbc_info_t ;
2013-09-24 19:45:41 -03:00
struct userdata {
pa_module * module ;
pa_core * core ;
2013-09-24 19:45:42 -03:00
pa_hook_slot * device_connection_changed_slot ;
2013-09-24 19:45:54 -03:00
pa_hook_slot * transport_state_changed_slot ;
2013-09-24 19:45:42 -03:00
2013-09-24 19:45:41 -03:00
pa_bluetooth_discovery * discovery ;
pa_bluetooth_device * device ;
2013-09-24 19:45:46 -03:00
pa_bluetooth_transport * transport ;
bool transport_acquired ;
2013-09-24 19:45:43 -03:00
pa_card * card ;
2013-09-24 19:45:47 -03:00
pa_sink * sink ;
2013-09-24 19:45:48 -03:00
pa_source * source ;
2013-09-24 19:45:43 -03:00
pa_bluetooth_profile_t profile ;
2013-09-24 19:45:44 -03:00
char * output_port_name ;
char * input_port_name ;
2013-09-24 19:45:46 -03:00
2013-09-24 19:45:49 -03:00
pa_thread * thread ;
pa_thread_mq thread_mq ;
pa_rtpoll * rtpoll ;
pa_rtpoll_item * rtpoll_item ;
2013-09-24 19:45:51 -03:00
bluetooth_msg * msg ;
2013-09-24 19:45:49 -03:00
2013-09-24 19:45:46 -03:00
int stream_fd ;
2013-09-24 19:45:51 -03:00
int stream_write_type ;
2013-09-24 19:45:46 -03:00
size_t read_link_mtu ;
size_t write_link_mtu ;
size_t read_block_size ;
size_t write_block_size ;
2013-09-24 19:45:51 -03:00
uint64_t read_index ;
uint64_t write_index ;
pa_usec_t started_at ;
pa_smoother * read_smoother ;
pa_memchunk write_memchunk ;
2013-09-24 19:45:46 -03:00
pa_sample_spec sample_spec ;
struct sbc_info sbc_info ;
2013-09-24 19:45:41 -03:00
} ;
2013-09-24 19:45:40 -03:00
2013-09-24 19:45:43 -03:00
typedef enum pa_bluetooth_form_factor {
PA_BLUETOOTH_FORM_FACTOR_UNKNOWN ,
PA_BLUETOOTH_FORM_FACTOR_HEADSET ,
PA_BLUETOOTH_FORM_FACTOR_HANDSFREE ,
PA_BLUETOOTH_FORM_FACTOR_MICROPHONE ,
PA_BLUETOOTH_FORM_FACTOR_SPEAKER ,
PA_BLUETOOTH_FORM_FACTOR_HEADPHONE ,
PA_BLUETOOTH_FORM_FACTOR_PORTABLE ,
PA_BLUETOOTH_FORM_FACTOR_CAR ,
PA_BLUETOOTH_FORM_FACTOR_HIFI ,
PA_BLUETOOTH_FORM_FACTOR_PHONE ,
} pa_bluetooth_form_factor_t ;
/* Run from main thread */
static pa_bluetooth_form_factor_t form_factor_from_class ( uint32_t class_of_device ) {
unsigned major , minor ;
pa_bluetooth_form_factor_t r ;
static const pa_bluetooth_form_factor_t table [ ] = {
[ 1 ] = PA_BLUETOOTH_FORM_FACTOR_HEADSET ,
[ 2 ] = PA_BLUETOOTH_FORM_FACTOR_HANDSFREE ,
[ 4 ] = PA_BLUETOOTH_FORM_FACTOR_MICROPHONE ,
[ 5 ] = PA_BLUETOOTH_FORM_FACTOR_SPEAKER ,
[ 6 ] = PA_BLUETOOTH_FORM_FACTOR_HEADPHONE ,
[ 7 ] = PA_BLUETOOTH_FORM_FACTOR_PORTABLE ,
[ 8 ] = PA_BLUETOOTH_FORM_FACTOR_CAR ,
[ 10 ] = PA_BLUETOOTH_FORM_FACTOR_HIFI
} ;
/*
* See Bluetooth Assigned Numbers :
* https : //www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
*/
major = ( class_of_device > > 8 ) & 0x1F ;
minor = ( class_of_device > > 2 ) & 0x3F ;
switch ( major ) {
case 2 :
return PA_BLUETOOTH_FORM_FACTOR_PHONE ;
case 4 :
break ;
default :
pa_log_debug ( " Unknown Bluetooth major device class %u " , major ) ;
return PA_BLUETOOTH_FORM_FACTOR_UNKNOWN ;
}
r = minor < PA_ELEMENTSOF ( table ) ? table [ minor ] : PA_BLUETOOTH_FORM_FACTOR_UNKNOWN ;
if ( ! r )
pa_log_debug ( " Unknown Bluetooth minor device class %u " , minor ) ;
return r ;
}
/* Run from main thread */
static const char * form_factor_to_string ( pa_bluetooth_form_factor_t ff ) {
switch ( ff ) {
case PA_BLUETOOTH_FORM_FACTOR_UNKNOWN :
return " unknown " ;
case PA_BLUETOOTH_FORM_FACTOR_HEADSET :
return " headset " ;
case PA_BLUETOOTH_FORM_FACTOR_HANDSFREE :
return " hands-free " ;
case PA_BLUETOOTH_FORM_FACTOR_MICROPHONE :
return " microphone " ;
case PA_BLUETOOTH_FORM_FACTOR_SPEAKER :
return " speaker " ;
case PA_BLUETOOTH_FORM_FACTOR_HEADPHONE :
return " headphone " ;
case PA_BLUETOOTH_FORM_FACTOR_PORTABLE :
return " portable " ;
case PA_BLUETOOTH_FORM_FACTOR_CAR :
return " car " ;
case PA_BLUETOOTH_FORM_FACTOR_HIFI :
return " hifi " ;
case PA_BLUETOOTH_FORM_FACTOR_PHONE :
return " phone " ;
}
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:47 -03:00
/* Run from main thread */
static void connect_ports ( struct userdata * u , void * new_data , pa_direction_t direction ) {
pa_device_port * port ;
if ( direction = = PA_DIRECTION_OUTPUT ) {
pa_sink_new_data * sink_new_data = new_data ;
pa_assert_se ( port = pa_hashmap_get ( u - > card - > ports , u - > output_port_name ) ) ;
pa_assert_se ( pa_hashmap_put ( sink_new_data - > ports , port - > name , port ) > = 0 ) ;
pa_device_port_ref ( port ) ;
} else {
pa_source_new_data * source_new_data = new_data ;
pa_assert_se ( port = pa_hashmap_get ( u - > card - > ports , u - > input_port_name ) ) ;
pa_assert_se ( pa_hashmap_put ( source_new_data - > ports , port - > name , port ) > = 0 ) ;
pa_device_port_ref ( port ) ;
}
}
2014-08-22 11:07:11 +03:00
/* Run from IO thread */
static int sco_process_render ( struct userdata * u ) {
ssize_t l ;
pa_memchunk memchunk ;
pa_assert ( u ) ;
pa_assert ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | |
u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ) ;
pa_assert ( u - > sink ) ;
pa_sink_render_full ( u - > sink , u - > write_block_size , & memchunk ) ;
pa_assert ( memchunk . length = = u - > write_block_size ) ;
for ( ; ; ) {
const void * p ;
/* Now write that data to the socket. The socket is of type
* SEQPACKET , and we generated the data of the MTU size , so this
* should just work . */
p = ( const uint8_t * ) pa_memblock_acquire_chunk ( & memchunk ) ;
l = pa_write ( u - > stream_fd , p , memchunk . length , & u - > stream_write_type ) ;
pa_memblock_release ( memchunk . memblock ) ;
pa_assert ( l ! = 0 ) ;
if ( l > 0 )
break ;
if ( errno = = EINTR )
/* Retry right away if we got interrupted */
continue ;
else if ( errno = = EAGAIN )
/* Hmm, apparently the socket was not writable, give up for now */
return 0 ;
pa_log_error ( " Failed to write data to SCO socket: %s " , pa_cstrerror ( errno ) ) ;
return - 1 ;
}
pa_assert ( ( size_t ) l < = memchunk . length ) ;
if ( ( size_t ) l ! = memchunk . length ) {
pa_log_error ( " Wrote memory block to socket only partially! %llu written, wanted to write %llu. " ,
( unsigned long long ) l ,
( unsigned long long ) memchunk . length ) ;
return - 1 ;
}
u - > write_index + = ( uint64_t ) memchunk . length ;
pa_memblock_unref ( memchunk . memblock ) ;
return 1 ;
}
/* Run from IO thread */
static int sco_process_push ( struct userdata * u ) {
ssize_t l ;
pa_memchunk memchunk ;
struct cmsghdr * cm ;
struct msghdr m ;
bool found_tstamp = false ;
pa_usec_t tstamp = 0 ;
pa_assert ( u ) ;
pa_assert ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | |
u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ) ;
pa_assert ( u - > source ) ;
pa_assert ( u - > read_smoother ) ;
memchunk . memblock = pa_memblock_new ( u - > core - > mempool , u - > read_block_size ) ;
memchunk . index = memchunk . length = 0 ;
for ( ; ; ) {
void * p ;
uint8_t aux [ 1024 ] ;
struct iovec iov ;
pa_zero ( m ) ;
pa_zero ( aux ) ;
pa_zero ( iov ) ;
m . msg_iov = & iov ;
m . msg_iovlen = 1 ;
m . msg_control = aux ;
m . msg_controllen = sizeof ( aux ) ;
p = pa_memblock_acquire ( memchunk . memblock ) ;
iov . iov_base = p ;
iov . iov_len = pa_memblock_get_length ( memchunk . memblock ) ;
l = recvmsg ( u - > stream_fd , & m , 0 ) ;
pa_memblock_release ( memchunk . memblock ) ;
if ( l > 0 )
break ;
if ( l < 0 & & errno = = EINTR )
/* Retry right away if we got interrupted */
continue ;
pa_memblock_unref ( memchunk . memblock ) ;
if ( l < 0 & & errno = = EAGAIN )
/* Hmm, apparently the socket was not readable, give up for now. */
return 0 ;
pa_log_error ( " Failed to read data from SCO socket: %s " , l < 0 ? pa_cstrerror ( errno ) : " EOF " ) ;
return - 1 ;
}
pa_assert ( ( size_t ) l < = pa_memblock_get_length ( memchunk . memblock ) ) ;
/* In some rare occasions, we might receive packets of a very strange
* size . This could potentially be possible if the SCO packet was
* received partially over - the - air , or more probably due to hardware
* issues in our Bluetooth adapter . In these cases , in order to avoid
* an assertion failure due to unaligned data , just discard the whole
* packet */
if ( ! pa_frame_aligned ( l , & u - > sample_spec ) ) {
pa_log_warn ( " SCO packet received of unaligned size: %zu " , l ) ;
pa_memblock_unref ( memchunk . memblock ) ;
return - 1 ;
}
memchunk . length = ( size_t ) l ;
u - > read_index + = ( uint64_t ) l ;
for ( cm = CMSG_FIRSTHDR ( & m ) ; cm ; cm = CMSG_NXTHDR ( & m , cm ) )
if ( cm - > cmsg_level = = SOL_SOCKET & & cm - > cmsg_type = = SO_TIMESTAMP ) {
struct timeval * tv = ( struct timeval * ) CMSG_DATA ( cm ) ;
pa_rtclock_from_wallclock ( tv ) ;
tstamp = pa_timeval_load ( tv ) ;
found_tstamp = true ;
break ;
}
if ( ! found_tstamp ) {
pa_log_warn ( " Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data! " ) ;
tstamp = pa_rtclock_now ( ) ;
}
pa_smoother_put ( u - > read_smoother , tstamp , pa_bytes_to_usec ( u - > read_index , & u - > sample_spec ) ) ;
pa_smoother_resume ( u - > read_smoother , tstamp , true ) ;
pa_source_post ( u - > source , & memchunk ) ;
pa_memblock_unref ( memchunk . memblock ) ;
return l ;
}
2013-09-24 19:45:51 -03:00
/* Run from IO thread */
static void a2dp_prepare_buffer ( struct userdata * u ) {
size_t min_buffer_size = PA_MAX ( u - > read_link_mtu , u - > write_link_mtu ) ;
pa_assert ( u ) ;
if ( u - > sbc_info . buffer_size > = min_buffer_size )
return ;
u - > sbc_info . buffer_size = 2 * min_buffer_size ;
pa_xfree ( u - > sbc_info . buffer ) ;
u - > sbc_info . buffer = pa_xmalloc ( u - > sbc_info . buffer_size ) ;
}
/* Run from IO thread */
static int a2dp_process_render ( struct userdata * u ) {
struct sbc_info * sbc_info ;
struct rtp_header * header ;
struct rtp_payload * payload ;
size_t nbytes ;
void * d ;
const void * p ;
size_t to_write , to_encode ;
unsigned frame_count ;
int ret = 0 ;
pa_assert ( u ) ;
pa_assert ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK ) ;
pa_assert ( u - > sink ) ;
/* First, render some data */
if ( ! u - > write_memchunk . memblock )
pa_sink_render_full ( u - > sink , u - > write_block_size , & u - > write_memchunk ) ;
pa_assert ( u - > write_memchunk . length = = u - > write_block_size ) ;
a2dp_prepare_buffer ( u ) ;
sbc_info = & u - > sbc_info ;
header = sbc_info - > buffer ;
payload = ( struct rtp_payload * ) ( ( uint8_t * ) sbc_info - > buffer + sizeof ( * header ) ) ;
frame_count = 0 ;
/* Try to create a packet of the full MTU */
p = ( const uint8_t * ) pa_memblock_acquire_chunk ( & u - > write_memchunk ) ;
to_encode = u - > write_memchunk . length ;
d = ( uint8_t * ) sbc_info - > buffer + sizeof ( * header ) + sizeof ( * payload ) ;
to_write = sbc_info - > buffer_size - sizeof ( * header ) - sizeof ( * payload ) ;
while ( PA_LIKELY ( to_encode > 0 & & to_write > 0 ) ) {
ssize_t written ;
ssize_t encoded ;
encoded = sbc_encode ( & sbc_info - > sbc ,
p , to_encode ,
d , to_write ,
& written ) ;
if ( PA_UNLIKELY ( encoded < = 0 ) ) {
pa_log_error ( " SBC encoding error (%li) " , ( long ) encoded ) ;
pa_memblock_release ( u - > write_memchunk . memblock ) ;
return - 1 ;
}
pa_assert_fp ( ( size_t ) encoded < = to_encode ) ;
pa_assert_fp ( ( size_t ) encoded = = sbc_info - > codesize ) ;
pa_assert_fp ( ( size_t ) written < = to_write ) ;
pa_assert_fp ( ( size_t ) written = = sbc_info - > frame_length ) ;
p = ( const uint8_t * ) p + encoded ;
to_encode - = encoded ;
d = ( uint8_t * ) d + written ;
to_write - = written ;
frame_count + + ;
}
pa_memblock_release ( u - > write_memchunk . memblock ) ;
pa_assert ( to_encode = = 0 ) ;
PA_ONCE_BEGIN {
pa_log_debug ( " Using SBC encoder implementation: %s " , pa_strnull ( sbc_get_implementation_info ( & sbc_info - > sbc ) ) ) ;
} PA_ONCE_END ;
/* write it to the fifo */
memset ( sbc_info - > buffer , 0 , sizeof ( * header ) + sizeof ( * payload ) ) ;
header - > v = 2 ;
header - > pt = 1 ;
header - > sequence_number = htons ( sbc_info - > seq_num + + ) ;
header - > timestamp = htonl ( u - > write_index / pa_frame_size ( & u - > sample_spec ) ) ;
header - > ssrc = htonl ( 1 ) ;
payload - > frame_count = frame_count ;
nbytes = ( uint8_t * ) d - ( uint8_t * ) sbc_info - > buffer ;
for ( ; ; ) {
ssize_t l ;
l = pa_write ( u - > stream_fd , sbc_info - > buffer , nbytes , & u - > stream_write_type ) ;
pa_assert ( l ! = 0 ) ;
if ( l < 0 ) {
if ( errno = = EINTR )
/* Retry right away if we got interrupted */
continue ;
else if ( errno = = EAGAIN )
/* Hmm, apparently the socket was not writable, give up for now */
break ;
pa_log_error ( " Failed to write data to socket: %s " , pa_cstrerror ( errno ) ) ;
ret = - 1 ;
break ;
}
pa_assert ( ( size_t ) l < = nbytes ) ;
if ( ( size_t ) l ! = nbytes ) {
pa_log_warn ( " Wrote memory block to socket only partially! %llu written, wanted to write %llu. " ,
( unsigned long long ) l ,
( unsigned long long ) nbytes ) ;
ret = - 1 ;
break ;
}
u - > write_index + = ( uint64_t ) u - > write_memchunk . length ;
pa_memblock_unref ( u - > write_memchunk . memblock ) ;
pa_memchunk_reset ( & u - > write_memchunk ) ;
ret = 1 ;
break ;
}
return ret ;
}
/* Run from IO thread */
static int a2dp_process_push ( struct userdata * u ) {
int ret = 0 ;
pa_memchunk memchunk ;
pa_assert ( u ) ;
pa_assert ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SOURCE ) ;
pa_assert ( u - > source ) ;
pa_assert ( u - > read_smoother ) ;
memchunk . memblock = pa_memblock_new ( u - > core - > mempool , u - > read_block_size ) ;
memchunk . index = memchunk . length = 0 ;
for ( ; ; ) {
bool found_tstamp = false ;
pa_usec_t tstamp ;
struct sbc_info * sbc_info ;
struct rtp_header * header ;
struct rtp_payload * payload ;
const void * p ;
void * d ;
ssize_t l ;
size_t to_write , to_decode ;
2014-01-27 01:14:39 +11:00
size_t total_written = 0 ;
2013-09-24 19:45:51 -03:00
a2dp_prepare_buffer ( u ) ;
sbc_info = & u - > sbc_info ;
header = sbc_info - > buffer ;
payload = ( struct rtp_payload * ) ( ( uint8_t * ) sbc_info - > buffer + sizeof ( * header ) ) ;
l = pa_read ( u - > stream_fd , sbc_info - > buffer , sbc_info - > buffer_size , & u - > stream_write_type ) ;
if ( l < = 0 ) {
if ( l < 0 & & errno = = EINTR )
/* Retry right away if we got interrupted */
continue ;
else if ( l < 0 & & errno = = EAGAIN )
/* Hmm, apparently the socket was not readable, give up for now. */
break ;
pa_log_error ( " Failed to read data from socket: %s " , l < 0 ? pa_cstrerror ( errno ) : " EOF " ) ;
ret = - 1 ;
break ;
}
pa_assert ( ( size_t ) l < = sbc_info - > buffer_size ) ;
/* TODO: get timestamp from rtp */
if ( ! found_tstamp ) {
/* pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); */
tstamp = pa_rtclock_now ( ) ;
}
p = ( uint8_t * ) sbc_info - > buffer + sizeof ( * header ) + sizeof ( * payload ) ;
to_decode = l - sizeof ( * header ) - sizeof ( * payload ) ;
d = pa_memblock_acquire ( memchunk . memblock ) ;
to_write = memchunk . length = pa_memblock_get_length ( memchunk . memblock ) ;
while ( PA_LIKELY ( to_decode > 0 ) ) {
size_t written ;
ssize_t decoded ;
decoded = sbc_decode ( & sbc_info - > sbc ,
p , to_decode ,
d , to_write ,
& written ) ;
if ( PA_UNLIKELY ( decoded < = 0 ) ) {
pa_log_error ( " SBC decoding error (%li) " , ( long ) decoded ) ;
pa_memblock_release ( memchunk . memblock ) ;
pa_memblock_unref ( memchunk . memblock ) ;
2014-01-27 01:14:38 +11:00
return 0 ;
2013-09-24 19:45:51 -03:00
}
2014-01-27 01:14:39 +11:00
total_written + = written ;
2013-09-24 19:45:51 -03:00
/* Reset frame length, it can be changed due to bitpool change */
sbc_info - > frame_length = sbc_get_frame_length ( & sbc_info - > sbc ) ;
pa_assert_fp ( ( size_t ) decoded < = to_decode ) ;
pa_assert_fp ( ( size_t ) decoded = = sbc_info - > frame_length ) ;
pa_assert_fp ( ( size_t ) written = = sbc_info - > codesize ) ;
p = ( const uint8_t * ) p + decoded ;
to_decode - = decoded ;
d = ( uint8_t * ) d + written ;
to_write - = written ;
}
2014-01-27 01:14:39 +11:00
u - > read_index + = ( uint64_t ) total_written ;
pa_smoother_put ( u - > read_smoother , tstamp , pa_bytes_to_usec ( u - > read_index , & u - > sample_spec ) ) ;
pa_smoother_resume ( u - > read_smoother , tstamp , true ) ;
2013-09-24 19:45:51 -03:00
memchunk . length - = to_write ;
pa_memblock_release ( memchunk . memblock ) ;
pa_source_post ( u - > source , & memchunk ) ;
ret = l ;
break ;
}
pa_memblock_unref ( memchunk . memblock ) ;
return ret ;
}
/* Run from I/O thread */
static void a2dp_set_bitpool ( struct userdata * u , uint8_t bitpool ) {
struct sbc_info * sbc_info ;
pa_assert ( u ) ;
sbc_info = & u - > sbc_info ;
if ( sbc_info - > sbc . bitpool = = bitpool )
return ;
if ( bitpool > sbc_info - > max_bitpool )
bitpool = sbc_info - > max_bitpool ;
else if ( bitpool < sbc_info - > min_bitpool )
bitpool = sbc_info - > min_bitpool ;
sbc_info - > sbc . bitpool = bitpool ;
sbc_info - > codesize = sbc_get_codesize ( & sbc_info - > sbc ) ;
sbc_info - > frame_length = sbc_get_frame_length ( & sbc_info - > sbc ) ;
pa_log_debug ( " Bitpool has changed to %u " , sbc_info - > sbc . bitpool ) ;
u - > read_block_size =
( u - > read_link_mtu - sizeof ( struct rtp_header ) - sizeof ( struct rtp_payload ) )
/ sbc_info - > frame_length * sbc_info - > codesize ;
u - > write_block_size =
( u - > write_link_mtu - sizeof ( struct rtp_header ) - sizeof ( struct rtp_payload ) )
/ sbc_info - > frame_length * sbc_info - > codesize ;
pa_sink_set_max_request_within_thread ( u - > sink , u - > write_block_size ) ;
pa_sink_set_fixed_latency_within_thread ( u - > sink ,
FIXED_LATENCY_PLAYBACK_A2DP + pa_bytes_to_usec ( u - > write_block_size , & u - > sample_spec ) ) ;
}
/* Run from I/O thread */
static void a2dp_reduce_bitpool ( struct userdata * u ) {
struct sbc_info * sbc_info ;
uint8_t bitpool ;
pa_assert ( u ) ;
sbc_info = & u - > sbc_info ;
/* Check if bitpool is already at its limit */
if ( sbc_info - > sbc . bitpool < = BITPOOL_DEC_LIMIT )
return ;
bitpool = sbc_info - > sbc . bitpool - BITPOOL_DEC_STEP ;
if ( bitpool < BITPOOL_DEC_LIMIT )
bitpool = BITPOOL_DEC_LIMIT ;
a2dp_set_bitpool ( u , bitpool ) ;
}
2013-09-24 19:45:49 -03:00
static void teardown_stream ( struct userdata * u ) {
if ( u - > rtpoll_item ) {
pa_rtpoll_item_free ( u - > rtpoll_item ) ;
u - > rtpoll_item = NULL ;
}
if ( u - > stream_fd > = 0 ) {
pa_close ( u - > stream_fd ) ;
u - > stream_fd = - 1 ;
}
2013-09-24 19:45:51 -03:00
if ( u - > read_smoother ) {
pa_smoother_free ( u - > read_smoother ) ;
u - > read_smoother = NULL ;
}
if ( u - > write_memchunk . memblock ) {
pa_memblock_unref ( u - > write_memchunk . memblock ) ;
pa_memchunk_reset ( & u - > write_memchunk ) ;
}
2013-09-24 19:45:49 -03:00
pa_log_debug ( " Audio stream torn down " ) ;
}
2013-09-24 19:45:46 -03:00
static int transport_acquire ( struct userdata * u , bool optional ) {
pa_assert ( u - > transport ) ;
if ( u - > transport_acquired )
return 0 ;
pa_log_debug ( " Acquiring transport %s " , u - > transport - > path ) ;
u - > stream_fd = u - > transport - > acquire ( u - > transport , optional , & u - > read_link_mtu , & u - > write_link_mtu ) ;
if ( u - > stream_fd < 0 )
return - 1 ;
u - > transport_acquired = true ;
pa_log_info ( " Transport %s acquired: fd %d " , u - > transport - > path , u - > stream_fd ) ;
return 0 ;
}
2013-09-24 19:45:49 -03:00
static void transport_release ( struct userdata * u ) {
pa_assert ( u - > transport ) ;
/* Ignore if already released */
if ( ! u - > transport_acquired )
return ;
pa_log_debug ( " Releasing transport %s " , u - > transport - > path ) ;
u - > transport - > release ( u - > transport ) ;
u - > transport_acquired = false ;
teardown_stream ( u ) ;
}
2013-09-24 19:45:51 -03:00
/* Run from I/O thread */
static void transport_config_mtu ( struct userdata * u ) {
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | | u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ) {
u - > read_block_size = u - > read_link_mtu ;
u - > write_block_size = u - > write_link_mtu ;
} else {
u - > read_block_size =
( u - > read_link_mtu - sizeof ( struct rtp_header ) - sizeof ( struct rtp_payload ) )
/ u - > sbc_info . frame_length * u - > sbc_info . codesize ;
2013-09-24 19:45:51 -03:00
2014-08-22 11:07:11 +03:00
u - > write_block_size =
( u - > write_link_mtu - sizeof ( struct rtp_header ) - sizeof ( struct rtp_payload ) )
/ u - > sbc_info . frame_length * u - > sbc_info . codesize ;
}
2013-09-24 19:45:51 -03:00
if ( u - > sink ) {
pa_sink_set_max_request_within_thread ( u - > sink , u - > write_block_size ) ;
pa_sink_set_fixed_latency_within_thread ( u - > sink ,
2014-08-22 11:07:11 +03:00
( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK ?
FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_SCO ) +
2013-09-24 19:45:51 -03:00
pa_bytes_to_usec ( u - > write_block_size , & u - > sample_spec ) ) ;
}
if ( u - > source )
pa_source_set_fixed_latency_within_thread ( u - > source ,
2014-08-22 11:07:11 +03:00
( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SOURCE ?
FIXED_LATENCY_RECORD_A2DP : FIXED_LATENCY_RECORD_SCO ) +
2013-09-24 19:45:51 -03:00
pa_bytes_to_usec ( u - > read_block_size , & u - > sample_spec ) ) ;
}
/* Run from I/O thread */
static void setup_stream ( struct userdata * u ) {
struct pollfd * pollfd ;
int one ;
pa_log_info ( " Transport %s resuming " , u - > transport - > path ) ;
transport_config_mtu ( u ) ;
pa_make_fd_nonblock ( u - > stream_fd ) ;
pa_make_socket_low_delay ( u - > stream_fd ) ;
one = 1 ;
if ( setsockopt ( u - > stream_fd , SOL_SOCKET , SO_TIMESTAMP , & one , sizeof ( one ) ) < 0 )
pa_log_warn ( " Failed to enable SO_TIMESTAMP: %s " , pa_cstrerror ( errno ) ) ;
pa_log_debug ( " Stream properly set up, we're ready to roll! " ) ;
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK )
a2dp_set_bitpool ( u , u - > sbc_info . max_bitpool ) ;
u - > rtpoll_item = pa_rtpoll_item_new ( u - > rtpoll , PA_RTPOLL_NEVER , 1 ) ;
pollfd = pa_rtpoll_item_get_pollfd ( u - > rtpoll_item , NULL ) ;
pollfd - > fd = u - > stream_fd ;
pollfd - > events = pollfd - > revents = 0 ;
u - > read_index = u - > write_index = 0 ;
u - > started_at = 0 ;
if ( u - > source )
u - > read_smoother = pa_smoother_new ( PA_USEC_PER_SEC , 2 * PA_USEC_PER_SEC , true , true , 10 , pa_rtclock_now ( ) , true ) ;
}
2013-09-24 19:45:53 -03:00
/* Run from IO thread */
static int source_process_msg ( pa_msgobject * o , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
struct userdata * u = PA_SOURCE ( o ) - > userdata ;
bool failed = false ;
int r ;
pa_assert ( u - > source = = PA_SOURCE ( o ) ) ;
pa_assert ( u - > transport ) ;
switch ( code ) {
case PA_SOURCE_MESSAGE_SET_STATE :
switch ( ( pa_source_state_t ) PA_PTR_TO_UINT ( data ) ) {
case PA_SOURCE_SUSPENDED :
/* Ignore if transition is PA_SOURCE_INIT->PA_SOURCE_SUSPENDED */
if ( ! PA_SOURCE_IS_OPENED ( u - > source - > thread_info . state ) )
break ;
/* Stop the device if the sink is suspended as well */
if ( ! u - > sink | | u - > sink - > state = = PA_SINK_SUSPENDED )
transport_release ( u ) ;
if ( u - > read_smoother )
pa_smoother_pause ( u - > read_smoother , pa_rtclock_now ( ) ) ;
break ;
case PA_SOURCE_IDLE :
case PA_SOURCE_RUNNING :
if ( u - > source - > thread_info . state ! = PA_SOURCE_SUSPENDED )
break ;
/* Resume the device if the sink was suspended as well */
if ( ! u - > sink | | ! PA_SINK_IS_OPENED ( u - > sink - > thread_info . state ) ) {
if ( transport_acquire ( u , false ) < 0 )
failed = true ;
else
setup_stream ( u ) ;
}
/* We don't resume the smoother here. Instead we
* wait until the first packet arrives */
break ;
case PA_SOURCE_UNLINKED :
case PA_SOURCE_INIT :
case PA_SOURCE_INVALID_STATE :
break ;
}
break ;
case PA_SOURCE_MESSAGE_GET_LATENCY : {
pa_usec_t wi , ri ;
if ( u - > read_smoother ) {
wi = pa_smoother_get ( u - > read_smoother , pa_rtclock_now ( ) ) ;
ri = pa_bytes_to_usec ( u - > read_index , & u - > sample_spec ) ;
* ( ( pa_usec_t * ) data ) = FIXED_LATENCY_RECORD_A2DP + wi > ri ? FIXED_LATENCY_RECORD_A2DP + wi - ri : 0 ;
} else
* ( ( pa_usec_t * ) data ) = 0 ;
return 0 ;
}
}
r = pa_source_process_msg ( o , code , data , offset , chunk ) ;
return ( r < 0 | | ! failed ) ? r : - 1 ;
}
2013-09-24 19:45:48 -03:00
/* Run from main thread */
static int add_source ( struct userdata * u ) {
pa_source_new_data data ;
pa_assert ( u - > transport ) ;
pa_source_new_data_init ( & data ) ;
data . module = u - > module ;
data . card = u - > card ;
data . driver = __FILE__ ;
data . name = pa_sprintf_malloc ( " bluez_source.%s " , u - > device - > address ) ;
data . namereg_fail = false ;
pa_proplist_sets ( data . proplist , " bluetooth.protocol " , pa_bluetooth_profile_to_string ( u - > profile ) ) ;
pa_source_new_data_set_sample_spec ( & data , & u - > sample_spec ) ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT )
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_INTENDED_ROLES , " phone " ) ;
2013-09-24 19:45:48 -03:00
connect_ports ( u , & data , PA_DIRECTION_INPUT ) ;
if ( ! u - > transport_acquired )
switch ( u - > profile ) {
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE :
2014-08-22 11:07:11 +03:00
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY :
2013-09-24 19:45:48 -03:00
data . suspend_cause = PA_SUSPEND_USER ;
break ;
case PA_BLUETOOTH_PROFILE_A2DP_SINK :
2014-08-22 11:07:11 +03:00
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT :
2013-09-24 19:45:48 -03:00
case PA_BLUETOOTH_PROFILE_OFF :
pa_assert_not_reached ( ) ;
break ;
}
u - > source = pa_source_new ( u - > core , & data , PA_SOURCE_HARDWARE | PA_SOURCE_LATENCY ) ;
pa_source_new_data_done ( & data ) ;
if ( ! u - > source ) {
pa_log_error ( " Failed to create source " ) ;
return - 1 ;
}
u - > source - > userdata = u ;
2013-09-24 19:45:53 -03:00
u - > source - > parent . process_msg = source_process_msg ;
2013-09-24 19:45:48 -03:00
return 0 ;
}
2013-09-24 19:45:52 -03:00
/* Run from IO thread */
static int sink_process_msg ( pa_msgobject * o , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
struct userdata * u = PA_SINK ( o ) - > userdata ;
bool failed = false ;
int r ;
pa_assert ( u - > sink = = PA_SINK ( o ) ) ;
pa_assert ( u - > transport ) ;
switch ( code ) {
case PA_SINK_MESSAGE_SET_STATE :
switch ( ( pa_sink_state_t ) PA_PTR_TO_UINT ( data ) ) {
case PA_SINK_SUSPENDED :
/* Ignore if transition is PA_SINK_INIT->PA_SINK_SUSPENDED */
if ( ! PA_SINK_IS_OPENED ( u - > sink - > thread_info . state ) )
break ;
/* Stop the device if the source is suspended as well */
if ( ! u - > source | | u - > source - > state = = PA_SOURCE_SUSPENDED )
/* We deliberately ignore whether stopping
* actually worked . Since the stream_fd is
* closed it doesn ' t really matter */
transport_release ( u ) ;
break ;
case PA_SINK_IDLE :
case PA_SINK_RUNNING :
if ( u - > sink - > thread_info . state ! = PA_SINK_SUSPENDED )
break ;
/* Resume the device if the source was suspended as well */
if ( ! u - > source | | ! PA_SOURCE_IS_OPENED ( u - > source - > thread_info . state ) ) {
if ( transport_acquire ( u , false ) < 0 )
failed = true ;
else
setup_stream ( u ) ;
}
break ;
case PA_SINK_UNLINKED :
case PA_SINK_INIT :
case PA_SINK_INVALID_STATE :
break ;
}
break ;
case PA_SINK_MESSAGE_GET_LATENCY : {
pa_usec_t wi , ri ;
if ( u - > read_smoother ) {
ri = pa_smoother_get ( u - > read_smoother , pa_rtclock_now ( ) ) ;
wi = pa_bytes_to_usec ( u - > write_index + u - > write_block_size , & u - > sample_spec ) ;
} else {
ri = pa_rtclock_now ( ) - u - > started_at ;
wi = pa_bytes_to_usec ( u - > write_index , & u - > sample_spec ) ;
}
* ( ( pa_usec_t * ) data ) = FIXED_LATENCY_PLAYBACK_A2DP + wi > ri ? FIXED_LATENCY_PLAYBACK_A2DP + wi - ri : 0 ;
return 0 ;
}
}
r = pa_sink_process_msg ( o , code , data , offset , chunk ) ;
return ( r < 0 | | ! failed ) ? r : - 1 ;
}
2013-09-24 19:45:47 -03:00
/* Run from main thread */
static int add_sink ( struct userdata * u ) {
pa_sink_new_data data ;
pa_assert ( u - > transport ) ;
pa_sink_new_data_init ( & data ) ;
data . module = u - > module ;
data . card = u - > card ;
data . driver = __FILE__ ;
data . name = pa_sprintf_malloc ( " bluez_sink.%s " , u - > device - > address ) ;
data . namereg_fail = false ;
pa_proplist_sets ( data . proplist , " bluetooth.protocol " , pa_bluetooth_profile_to_string ( u - > profile ) ) ;
pa_sink_new_data_set_sample_spec ( & data , & u - > sample_spec ) ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT )
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_INTENDED_ROLES , " phone " ) ;
2013-09-24 19:45:47 -03:00
connect_ports ( u , & data , PA_DIRECTION_OUTPUT ) ;
if ( ! u - > transport_acquired )
switch ( u - > profile ) {
2014-08-22 11:07:11 +03:00
case PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY :
data . suspend_cause = PA_SUSPEND_USER ;
break ;
2013-09-24 19:45:47 -03:00
case PA_BLUETOOTH_PROFILE_A2DP_SINK :
/* Profile switch should have failed */
case PA_BLUETOOTH_PROFILE_A2DP_SOURCE :
2014-08-22 11:07:11 +03:00
case PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT :
2013-09-24 19:45:47 -03:00
case PA_BLUETOOTH_PROFILE_OFF :
pa_assert_not_reached ( ) ;
break ;
}
u - > sink = pa_sink_new ( u - > core , & data , PA_SINK_HARDWARE | PA_SINK_LATENCY ) ;
pa_sink_new_data_done ( & data ) ;
if ( ! u - > sink ) {
pa_log_error ( " Failed to create sink " ) ;
return - 1 ;
}
u - > sink - > userdata = u ;
2013-09-24 19:45:52 -03:00
u - > sink - > parent . process_msg = sink_process_msg ;
2013-09-24 19:45:47 -03:00
return 0 ;
}
2013-09-24 19:45:46 -03:00
/* Run from main thread */
static void transport_config ( struct userdata * u ) {
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | | u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ) {
u - > sample_spec . format = PA_SAMPLE_S16LE ;
u - > sample_spec . channels = 1 ;
u - > sample_spec . rate = 8000 ;
} else {
sbc_info_t * sbc_info = & u - > sbc_info ;
a2dp_sbc_t * config ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
pa_assert ( u - > transport ) ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
u - > sample_spec . format = PA_SAMPLE_S16LE ;
config = ( a2dp_sbc_t * ) u - > transport - > config ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
if ( sbc_info - > sbc_initialized )
sbc_reinit ( & sbc_info - > sbc , 0 ) ;
else
sbc_init ( & sbc_info - > sbc , 0 ) ;
sbc_info - > sbc_initialized = true ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
switch ( config - > frequency ) {
case SBC_SAMPLING_FREQ_16000 :
sbc_info - > sbc . frequency = SBC_FREQ_16000 ;
u - > sample_spec . rate = 16000U ;
break ;
case SBC_SAMPLING_FREQ_32000 :
sbc_info - > sbc . frequency = SBC_FREQ_32000 ;
u - > sample_spec . rate = 32000U ;
break ;
case SBC_SAMPLING_FREQ_44100 :
sbc_info - > sbc . frequency = SBC_FREQ_44100 ;
u - > sample_spec . rate = 44100U ;
break ;
case SBC_SAMPLING_FREQ_48000 :
sbc_info - > sbc . frequency = SBC_FREQ_48000 ;
u - > sample_spec . rate = 48000U ;
break ;
default :
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
switch ( config - > channel_mode ) {
case SBC_CHANNEL_MODE_MONO :
sbc_info - > sbc . mode = SBC_MODE_MONO ;
u - > sample_spec . channels = 1 ;
break ;
case SBC_CHANNEL_MODE_DUAL_CHANNEL :
sbc_info - > sbc . mode = SBC_MODE_DUAL_CHANNEL ;
u - > sample_spec . channels = 2 ;
break ;
case SBC_CHANNEL_MODE_STEREO :
sbc_info - > sbc . mode = SBC_MODE_STEREO ;
u - > sample_spec . channels = 2 ;
break ;
case SBC_CHANNEL_MODE_JOINT_STEREO :
sbc_info - > sbc . mode = SBC_MODE_JOINT_STEREO ;
u - > sample_spec . channels = 2 ;
break ;
default :
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
switch ( config - > allocation_method ) {
case SBC_ALLOCATION_SNR :
sbc_info - > sbc . allocation = SBC_AM_SNR ;
break ;
case SBC_ALLOCATION_LOUDNESS :
sbc_info - > sbc . allocation = SBC_AM_LOUDNESS ;
break ;
default :
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
switch ( config - > subbands ) {
case SBC_SUBBANDS_4 :
sbc_info - > sbc . subbands = SBC_SB_4 ;
break ;
case SBC_SUBBANDS_8 :
sbc_info - > sbc . subbands = SBC_SB_8 ;
break ;
default :
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
switch ( config - > block_length ) {
case SBC_BLOCK_LENGTH_4 :
sbc_info - > sbc . blocks = SBC_BLK_4 ;
break ;
case SBC_BLOCK_LENGTH_8 :
sbc_info - > sbc . blocks = SBC_BLK_8 ;
break ;
case SBC_BLOCK_LENGTH_12 :
sbc_info - > sbc . blocks = SBC_BLK_12 ;
break ;
case SBC_BLOCK_LENGTH_16 :
sbc_info - > sbc . blocks = SBC_BLK_16 ;
break ;
default :
pa_assert_not_reached ( ) ;
}
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
sbc_info - > min_bitpool = config - > min_bitpool ;
sbc_info - > max_bitpool = config - > max_bitpool ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
/* Set minimum bitpool for source to get the maximum possible block_size */
sbc_info - > sbc . bitpool = u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK ? sbc_info - > max_bitpool : sbc_info - > min_bitpool ;
sbc_info - > codesize = sbc_get_codesize ( & sbc_info - > sbc ) ;
sbc_info - > frame_length = sbc_get_frame_length ( & sbc_info - > sbc ) ;
2013-09-24 19:45:46 -03:00
2014-08-22 11:07:11 +03:00
pa_log_info ( " SBC parameters: allocation=%u, subbands=%u, blocks=%u, bitpool=%u " ,
sbc_info - > sbc . allocation , sbc_info - > sbc . subbands , sbc_info - > sbc . blocks , sbc_info - > sbc . bitpool ) ;
}
2013-09-24 19:45:46 -03:00
}
/* Run from main thread */
static int setup_transport ( struct userdata * u ) {
pa_bluetooth_transport * t ;
pa_assert ( u ) ;
pa_assert ( ! u - > transport ) ;
pa_assert ( u - > profile ! = PA_BLUETOOTH_PROFILE_OFF ) ;
/* check if profile has a transport */
t = u - > device - > transports [ u - > profile ] ;
if ( ! t | | t - > state < = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED ) {
pa_log_warn ( " Profile has no transport " ) ;
return - 1 ;
}
u - > transport = t ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SOURCE | | u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY )
2013-09-24 19:45:46 -03:00
transport_acquire ( u , true ) ; /* In case of error, the sink/sources will be created suspended */
else if ( transport_acquire ( u , false ) < 0 )
return - 1 ; /* We need to fail here until the interactions with module-suspend-on-idle and alike get improved */
transport_config ( u ) ;
return 0 ;
}
/* Run from main thread */
static int init_profile ( struct userdata * u ) {
int r = 0 ;
pa_assert ( u ) ;
pa_assert ( u - > profile ! = PA_BLUETOOTH_PROFILE_OFF ) ;
if ( setup_transport ( u ) < 0 )
return - 1 ;
pa_assert ( u - > transport ) ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK | | u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | |
u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY )
2013-09-24 19:45:47 -03:00
if ( add_sink ( u ) < 0 )
r = - 1 ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SOURCE | | u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT | |
u - > profile = = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY )
2013-09-24 19:45:48 -03:00
if ( add_source ( u ) < 0 )
r = - 1 ;
2013-09-24 19:45:46 -03:00
return r ;
}
2013-09-24 19:45:49 -03:00
/* I/O thread function */
static void thread_func ( void * userdata ) {
2013-09-24 19:45:51 -03:00
struct userdata * u = userdata ;
unsigned do_write = 0 ;
unsigned pending_read_bytes = 0 ;
bool writable = false ;
pa_assert ( u ) ;
pa_assert ( u - > transport ) ;
pa_log_debug ( " IO Thread starting up " ) ;
if ( u - > core - > realtime_scheduling )
pa_make_realtime ( u - > core - > realtime_priority ) ;
pa_thread_mq_install ( & u - > thread_mq ) ;
/* Setup the stream only if the transport was already acquired */
if ( u - > transport_acquired )
setup_stream ( u ) ;
for ( ; ; ) {
struct pollfd * pollfd ;
int ret ;
bool disable_timer = true ;
pollfd = u - > rtpoll_item ? pa_rtpoll_item_get_pollfd ( u - > rtpoll_item , NULL ) : NULL ;
2014-06-01 14:55:02 +03:00
if ( pollfd & & ( pollfd - > revents & ~ ( POLLOUT | POLLIN ) ) ) {
pa_log_info ( " FD error: %s%s%s%s " ,
pollfd - > revents & POLLERR ? " POLLERR " : " " ,
pollfd - > revents & POLLHUP ? " POLLHUP " : " " ,
pollfd - > revents & POLLPRI ? " POLLPRI " : " " ,
pollfd - > revents & POLLNVAL ? " POLLNVAL " : " " ) ;
if ( pollfd - > revents & POLLHUP ) {
pollfd = NULL ;
teardown_stream ( u ) ;
do_write = 0 ;
pending_read_bytes = 0 ;
writable = false ;
pa_asyncmsgq_post ( pa_thread_mq_get ( ) - > outq , PA_MSGOBJECT ( u - > msg ) , BLUETOOTH_MESSAGE_STREAM_FD_HUP , NULL , 0 , NULL , NULL ) ;
} else
goto fail ;
}
2013-09-24 19:45:51 -03:00
if ( u - > source & & PA_SOURCE_IS_LINKED ( u - > source - > thread_info . state ) ) {
/* We should send two blocks to the device before we expect
* a response . */
if ( u - > write_index = = 0 & & u - > read_index < = 0 )
do_write = 2 ;
if ( pollfd & & ( pollfd - > revents & POLLIN ) ) {
int n_read ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SOURCE )
n_read = a2dp_process_push ( u ) ;
else
n_read = sco_process_push ( u ) ;
2013-09-24 19:45:51 -03:00
if ( n_read < 0 )
2014-06-01 14:55:02 +03:00
goto fail ;
2013-09-24 19:45:51 -03:00
2014-01-27 01:14:38 +11:00
if ( n_read > 0 ) {
/* We just read something, so we are supposed to write something, too */
pending_read_bytes + = n_read ;
do_write + = pending_read_bytes / u - > write_block_size ;
pending_read_bytes = pending_read_bytes % u - > write_block_size ;
}
2013-09-24 19:45:51 -03:00
}
}
if ( u - > sink & & PA_SINK_IS_LINKED ( u - > sink - > thread_info . state ) ) {
if ( PA_UNLIKELY ( u - > sink - > thread_info . rewind_requested ) )
pa_sink_process_rewind ( u - > sink , 0 ) ;
if ( pollfd ) {
if ( pollfd - > revents & POLLOUT )
writable = true ;
if ( ( ! u - > source | | ! PA_SOURCE_IS_LINKED ( u - > source - > thread_info . state ) ) & & do_write < = 0 & & writable ) {
pa_usec_t time_passed ;
pa_usec_t audio_sent ;
/* Hmm, there is no input stream we could synchronize
* to . So let ' s do things by time */
time_passed = pa_rtclock_now ( ) - u - > started_at ;
audio_sent = pa_bytes_to_usec ( u - > write_index , & u - > sample_spec ) ;
if ( audio_sent < = time_passed ) {
pa_usec_t audio_to_send = time_passed - audio_sent ;
/* Never try to catch up for more than 100ms */
if ( u - > write_index > 0 & & audio_to_send > MAX_PLAYBACK_CATCH_UP_USEC ) {
pa_usec_t skip_usec ;
uint64_t skip_bytes ;
skip_usec = audio_to_send - MAX_PLAYBACK_CATCH_UP_USEC ;
skip_bytes = pa_usec_to_bytes ( skip_usec , & u - > sample_spec ) ;
if ( skip_bytes > 0 ) {
pa_memchunk tmp ;
pa_log_warn ( " Skipping %llu us (= %llu bytes) in audio stream " ,
( unsigned long long ) skip_usec ,
( unsigned long long ) skip_bytes ) ;
pa_sink_render_full ( u - > sink , skip_bytes , & tmp ) ;
pa_memblock_unref ( tmp . memblock ) ;
u - > write_index + = skip_bytes ;
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK )
a2dp_reduce_bitpool ( u ) ;
}
}
do_write = 1 ;
pending_read_bytes = 0 ;
}
}
if ( writable & & do_write > 0 ) {
int n_written ;
if ( u - > write_index < = 0 )
u - > started_at = pa_rtclock_now ( ) ;
2014-08-22 11:07:11 +03:00
if ( u - > profile = = PA_BLUETOOTH_PROFILE_A2DP_SINK ) {
if ( ( n_written = a2dp_process_render ( u ) ) < 0 )
goto fail ;
} else {
if ( ( n_written = sco_process_render ( u ) ) < 0 )
goto fail ;
}
2013-09-24 19:45:51 -03:00
if ( n_written = = 0 )
pa_log ( " Broken kernel: we got EAGAIN on write() after POLLOUT! " ) ;
do_write - = n_written ;
writable = false ;
}
if ( ( ! u - > source | | ! PA_SOURCE_IS_LINKED ( u - > source - > thread_info . state ) ) & & do_write < = 0 ) {
pa_usec_t sleep_for ;
pa_usec_t time_passed , next_write_at ;
if ( writable ) {
/* Hmm, there is no input stream we could synchronize
* to . So let ' s estimate when we need to wake up the latest */
time_passed = pa_rtclock_now ( ) - u - > started_at ;
next_write_at = pa_bytes_to_usec ( u - > write_index , & u - > sample_spec ) ;
sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0 ;
/* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */
} else
/* drop stream every 500 ms */
sleep_for = PA_USEC_PER_MSEC * 500 ;
pa_rtpoll_set_timer_relative ( u - > rtpoll , sleep_for ) ;
disable_timer = false ;
}
}
}
if ( disable_timer )
pa_rtpoll_set_timer_disabled ( u - > rtpoll ) ;
/* Hmm, nothing to do. Let's sleep */
if ( pollfd )
pollfd - > events = ( short ) ( ( ( u - > sink & & PA_SINK_IS_LINKED ( u - > sink - > thread_info . state ) & & ! writable ) ? POLLOUT : 0 ) |
( u - > source & & PA_SOURCE_IS_LINKED ( u - > source - > thread_info . state ) ? POLLIN : 0 ) ) ;
if ( ( ret = pa_rtpoll_run ( u - > rtpoll , true ) ) < 0 ) {
pa_log_debug ( " pa_rtpoll_run failed with: %d " , ret ) ;
goto fail ;
}
if ( ret = = 0 ) {
pa_log_debug ( " IO thread shutdown requested, stopping cleanly " ) ;
transport_release ( u ) ;
goto finish ;
}
}
fail :
/* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */
pa_log_debug ( " IO thread failed " ) ;
pa_asyncmsgq_post ( pa_thread_mq_get ( ) - > outq , PA_MSGOBJECT ( u - > msg ) , BLUETOOTH_MESSAGE_IO_THREAD_FAILED , NULL , 0 , NULL , NULL ) ;
pa_asyncmsgq_wait_for ( u - > thread_mq . inq , PA_MESSAGE_SHUTDOWN ) ;
finish :
pa_log_debug ( " IO thread shutting down " ) ;
2013-09-24 19:45:49 -03:00
}
/* Run from main thread */
static int start_thread ( struct userdata * u ) {
pa_assert ( u ) ;
pa_assert ( ! u - > thread ) ;
pa_assert ( ! u - > rtpoll ) ;
pa_assert ( ! u - > rtpoll_item ) ;
u - > rtpoll = pa_rtpoll_new ( ) ;
pa_thread_mq_init ( & u - > thread_mq , u - > core - > mainloop , u - > rtpoll ) ;
if ( ! ( u - > thread = pa_thread_new ( " bluetooth " , thread_func , u ) ) ) {
pa_log_error ( " Failed to create IO thread " ) ;
return - 1 ;
}
if ( u - > sink ) {
pa_sink_set_asyncmsgq ( u - > sink , u - > thread_mq . inq ) ;
pa_sink_set_rtpoll ( u - > sink , u - > rtpoll ) ;
pa_sink_put ( u - > sink ) ;
if ( u - > sink - > set_volume )
u - > sink - > set_volume ( u - > sink ) ;
}
if ( u - > source ) {
pa_source_set_asyncmsgq ( u - > source , u - > thread_mq . inq ) ;
pa_source_set_rtpoll ( u - > source , u - > rtpoll ) ;
pa_source_put ( u - > source ) ;
if ( u - > source - > set_volume )
u - > source - > set_volume ( u - > source ) ;
}
return 0 ;
}
/* Run from main thread */
static void stop_thread ( struct userdata * u ) {
pa_assert ( u ) ;
if ( u - > sink )
pa_sink_unlink ( u - > sink ) ;
if ( u - > source )
pa_source_unlink ( u - > source ) ;
if ( u - > thread ) {
pa_asyncmsgq_send ( u - > thread_mq . inq , NULL , PA_MESSAGE_SHUTDOWN , NULL , 0 , NULL ) ;
pa_thread_free ( u - > thread ) ;
u - > thread = NULL ;
}
if ( u - > rtpoll_item ) {
pa_rtpoll_item_free ( u - > rtpoll_item ) ;
u - > rtpoll_item = NULL ;
}
if ( u - > rtpoll ) {
pa_thread_mq_done ( & u - > thread_mq ) ;
pa_rtpoll_free ( u - > rtpoll ) ;
u - > rtpoll = NULL ;
}
if ( u - > transport ) {
transport_release ( u ) ;
u - > transport = NULL ;
}
if ( u - > sink ) {
pa_sink_unref ( u - > sink ) ;
u - > sink = NULL ;
}
if ( u - > source ) {
pa_source_unref ( u - > source ) ;
u - > source = NULL ;
}
2013-09-24 19:45:51 -03:00
if ( u - > read_smoother ) {
pa_smoother_free ( u - > read_smoother ) ;
u - > read_smoother = NULL ;
}
2013-09-24 19:45:49 -03:00
}
2013-09-24 19:45:43 -03:00
/* Run from main thread */
static char * cleanup_name ( const char * name ) {
char * t , * s , * d ;
bool space = false ;
pa_assert ( name ) ;
while ( ( * name > = 1 & & * name < = 32 ) | | * name > = 127 )
name + + ;
t = pa_xstrdup ( name ) ;
for ( s = d = t ; * s ; s + + ) {
if ( * s < = 32 | | * s > = 127 | | * s = = ' _ ' ) {
space = true ;
continue ;
}
if ( space ) {
* ( d + + ) = ' ' ;
space = false ;
}
* ( d + + ) = * s ;
}
* d = 0 ;
return t ;
}
2013-09-24 19:45:44 -03:00
/* Run from main thread */
static pa_direction_t get_profile_direction ( pa_bluetooth_profile_t p ) {
static const pa_direction_t profile_direction [ ] = {
[ PA_BLUETOOTH_PROFILE_A2DP_SINK ] = PA_DIRECTION_OUTPUT ,
[ PA_BLUETOOTH_PROFILE_A2DP_SOURCE ] = PA_DIRECTION_INPUT ,
2014-08-22 11:07:11 +03:00
[ PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT ,
[ PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ] = PA_DIRECTION_INPUT | PA_DIRECTION_OUTPUT ,
2013-09-24 19:45:44 -03:00
[ PA_BLUETOOTH_PROFILE_OFF ] = 0
} ;
return profile_direction [ p ] ;
}
/* Run from main thread */
static pa_available_t get_port_availability ( struct userdata * u , pa_direction_t direction ) {
pa_available_t result = PA_AVAILABLE_NO ;
unsigned i ;
pa_assert ( u ) ;
pa_assert ( u - > device ) ;
for ( i = 0 ; i < PA_BLUETOOTH_PROFILE_COUNT ; i + + ) {
pa_bluetooth_transport * transport ;
if ( ! ( get_profile_direction ( i ) & direction ) )
continue ;
if ( ! ( transport = u - > device - > transports [ i ] ) )
continue ;
switch ( transport - > state ) {
case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED :
continue ;
case PA_BLUETOOTH_TRANSPORT_STATE_IDLE :
if ( result = = PA_AVAILABLE_NO )
result = PA_AVAILABLE_UNKNOWN ;
break ;
case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING :
return PA_AVAILABLE_YES ;
}
}
return result ;
}
2013-09-24 19:45:45 -03:00
/* Run from main thread */
static pa_available_t transport_state_to_availability ( pa_bluetooth_transport_state_t state ) {
switch ( state ) {
case PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED :
return PA_AVAILABLE_NO ;
case PA_BLUETOOTH_TRANSPORT_STATE_PLAYING :
return PA_AVAILABLE_YES ;
default :
return PA_AVAILABLE_UNKNOWN ;
}
}
2013-09-24 19:45:44 -03:00
/* Run from main thread */
static void create_card_ports ( struct userdata * u , pa_hashmap * ports ) {
pa_device_port * port ;
pa_device_port_new_data port_data ;
const char * name_prefix , * input_description , * output_description ;
pa_assert ( u ) ;
pa_assert ( ports ) ;
pa_assert ( u - > device ) ;
name_prefix = " unknown " ;
input_description = _ ( " Bluetooth Input " ) ;
output_description = _ ( " Bluetooth Output " ) ;
switch ( form_factor_from_class ( u - > device - > class_of_device ) ) {
case PA_BLUETOOTH_FORM_FACTOR_HEADSET :
name_prefix = " headset " ;
input_description = output_description = _ ( " Headset " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_HANDSFREE :
name_prefix = " handsfree " ;
input_description = output_description = _ ( " Handsfree " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_MICROPHONE :
name_prefix = " microphone " ;
input_description = _ ( " Microphone " ) ;
output_description = _ ( " Bluetooth Output " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_SPEAKER :
name_prefix = " speaker " ;
input_description = _ ( " Bluetooth Input " ) ;
output_description = _ ( " Speaker " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_HEADPHONE :
name_prefix = " headphone " ;
input_description = _ ( " Bluetooth Input " ) ;
output_description = _ ( " Headphone " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_PORTABLE :
name_prefix = " portable " ;
input_description = output_description = _ ( " Portable " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_CAR :
name_prefix = " car " ;
input_description = output_description = _ ( " Car " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_HIFI :
name_prefix = " hifi " ;
input_description = output_description = _ ( " HiFi " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_PHONE :
name_prefix = " phone " ;
input_description = output_description = _ ( " Phone " ) ;
break ;
case PA_BLUETOOTH_FORM_FACTOR_UNKNOWN :
name_prefix = " unknown " ;
input_description = _ ( " Bluetooth Input " ) ;
output_description = _ ( " Bluetooth Output " ) ;
break ;
}
u - > output_port_name = pa_sprintf_malloc ( " %s-output " , name_prefix ) ;
pa_device_port_new_data_init ( & port_data ) ;
pa_device_port_new_data_set_name ( & port_data , u - > output_port_name ) ;
pa_device_port_new_data_set_description ( & port_data , output_description ) ;
pa_device_port_new_data_set_direction ( & port_data , PA_DIRECTION_OUTPUT ) ;
pa_device_port_new_data_set_available ( & port_data , get_port_availability ( u , PA_DIRECTION_OUTPUT ) ) ;
pa_assert_se ( port = pa_device_port_new ( u - > core , & port_data , 0 ) ) ;
pa_assert_se ( pa_hashmap_put ( ports , port - > name , port ) > = 0 ) ;
pa_device_port_new_data_done ( & port_data ) ;
u - > input_port_name = pa_sprintf_malloc ( " %s-input " , name_prefix ) ;
pa_device_port_new_data_init ( & port_data ) ;
pa_device_port_new_data_set_name ( & port_data , u - > input_port_name ) ;
pa_device_port_new_data_set_description ( & port_data , input_description ) ;
pa_device_port_new_data_set_direction ( & port_data , PA_DIRECTION_INPUT ) ;
pa_device_port_new_data_set_available ( & port_data , get_port_availability ( u , PA_DIRECTION_INPUT ) ) ;
pa_assert_se ( port = pa_device_port_new ( u - > core , & port_data , 0 ) ) ;
pa_assert_se ( pa_hashmap_put ( ports , port - > name , port ) > = 0 ) ;
pa_device_port_new_data_done ( & port_data ) ;
}
2013-09-24 19:45:45 -03:00
/* Run from main thread */
static pa_card_profile * create_card_profile ( struct userdata * u , const char * uuid , pa_hashmap * ports ) {
pa_device_port * input_port , * output_port ;
pa_card_profile * cp = NULL ;
pa_bluetooth_profile_t * p ;
pa_assert ( u - > input_port_name ) ;
pa_assert ( u - > output_port_name ) ;
pa_assert_se ( input_port = pa_hashmap_get ( ports , u - > input_port_name ) ) ;
pa_assert_se ( output_port = pa_hashmap_get ( ports , u - > output_port_name ) ) ;
if ( pa_streq ( uuid , PA_BLUETOOTH_UUID_A2DP_SINK ) ) {
2014-02-04 19:03:47 -03:00
cp = pa_card_profile_new ( " a2dp_sink " , _ ( " High Fidelity Playback (A2DP Sink) " ) , sizeof ( pa_bluetooth_profile_t ) ) ;
2013-09-24 19:45:45 -03:00
cp - > priority = 10 ;
cp - > n_sinks = 1 ;
cp - > n_sources = 0 ;
cp - > max_sink_channels = 2 ;
cp - > max_source_channels = 0 ;
pa_hashmap_put ( output_port - > profiles , cp - > name , cp ) ;
p = PA_CARD_PROFILE_DATA ( cp ) ;
* p = PA_BLUETOOTH_PROFILE_A2DP_SINK ;
} else if ( pa_streq ( uuid , PA_BLUETOOTH_UUID_A2DP_SOURCE ) ) {
cp = pa_card_profile_new ( " a2dp_source " , _ ( " High Fidelity Capture (A2DP Source) " ) , sizeof ( pa_bluetooth_profile_t ) ) ;
cp - > priority = 10 ;
cp - > n_sinks = 0 ;
cp - > n_sources = 1 ;
cp - > max_sink_channels = 0 ;
cp - > max_source_channels = 2 ;
pa_hashmap_put ( input_port - > profiles , cp - > name , cp ) ;
p = PA_CARD_PROFILE_DATA ( cp ) ;
* p = PA_BLUETOOTH_PROFILE_A2DP_SOURCE ;
2014-08-22 11:07:11 +03:00
} else if ( pa_streq ( uuid , PA_BLUETOOTH_UUID_HSP_HS ) | | pa_streq ( uuid , PA_BLUETOOTH_UUID_HFP_HF ) ) {
cp = pa_card_profile_new ( " headset_head_unit " , _ ( " Headset Head Unit (HSP/HFP) " ) , sizeof ( pa_bluetooth_profile_t ) ) ;
cp - > priority = 20 ;
cp - > n_sinks = 1 ;
cp - > n_sources = 1 ;
cp - > max_sink_channels = 1 ;
cp - > max_source_channels = 1 ;
pa_hashmap_put ( input_port - > profiles , cp - > name , cp ) ;
pa_hashmap_put ( output_port - > profiles , cp - > name , cp ) ;
p = PA_CARD_PROFILE_DATA ( cp ) ;
* p = PA_BLUETOOTH_PROFILE_HEADSET_HEAD_UNIT ;
} else if ( pa_streq ( uuid , PA_BLUETOOTH_UUID_HSP_AG ) | | pa_streq ( uuid , PA_BLUETOOTH_UUID_HFP_AG ) ) {
cp = pa_card_profile_new ( " headset_audio_gateway " , _ ( " Headset Audio Gateway (HSP/HFP) " ) , sizeof ( pa_bluetooth_profile_t ) ) ;
cp - > priority = 20 ;
cp - > n_sinks = 1 ;
cp - > n_sources = 1 ;
cp - > max_sink_channels = 1 ;
cp - > max_source_channels = 1 ;
pa_hashmap_put ( input_port - > profiles , cp - > name , cp ) ;
pa_hashmap_put ( output_port - > profiles , cp - > name , cp ) ;
p = PA_CARD_PROFILE_DATA ( cp ) ;
* p = PA_BLUETOOTH_PROFILE_HEADSET_AUDIO_GATEWAY ;
2013-09-24 19:45:45 -03:00
}
2014-08-11 12:47:46 +03:00
if ( cp ) {
if ( u - > device - > transports [ * p ] )
cp - > available = transport_state_to_availability ( u - > device - > transports [ * p ] - > state ) ;
else
cp - > available = PA_AVAILABLE_NO ;
}
2013-09-24 19:45:45 -03:00
return cp ;
}
2013-09-24 19:45:50 -03:00
/* Run from main thread */
static int set_profile_cb ( pa_card * c , pa_card_profile * new_profile ) {
struct userdata * u ;
pa_bluetooth_profile_t * p ;
pa_assert ( c ) ;
pa_assert ( new_profile ) ;
pa_assert_se ( u = c - > userdata ) ;
p = PA_CARD_PROFILE_DATA ( new_profile ) ;
if ( * p ! = PA_BLUETOOTH_PROFILE_OFF ) {
const pa_bluetooth_device * d = u - > device ;
if ( ! d - > transports [ * p ] | | d - > transports [ * p ] - > state < = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED ) {
pa_log_warn ( " Refused to switch profile to %s: Not connected " , new_profile - > name ) ;
return - PA_ERR_IO ;
}
}
stop_thread ( u ) ;
u - > profile = * p ;
if ( u - > profile ! = PA_BLUETOOTH_PROFILE_OFF )
if ( init_profile ( u ) < 0 )
goto off ;
if ( u - > sink | | u - > source )
if ( start_thread ( u ) < 0 )
goto off ;
return 0 ;
off :
stop_thread ( u ) ;
2013-11-20 15:42:26 +02:00
pa_assert_se ( pa_card_set_profile ( u - > card , pa_hashmap_get ( u - > card - > profiles , " off " ) , false ) > = 0 ) ;
2013-09-24 19:45:50 -03:00
return - PA_ERR_IO ;
}
2013-09-24 19:45:43 -03:00
/* Run from main thread */
static int add_card ( struct userdata * u ) {
const pa_bluetooth_device * d ;
pa_card_new_data data ;
char * alias ;
pa_bluetooth_form_factor_t ff ;
pa_card_profile * cp ;
pa_bluetooth_profile_t * p ;
2013-09-24 19:45:45 -03:00
const char * uuid ;
void * state ;
2013-09-24 19:45:43 -03:00
pa_assert ( u ) ;
pa_assert ( u - > device ) ;
d = u - > device ;
pa_card_new_data_init ( & data ) ;
data . driver = __FILE__ ;
data . module = u - > module ;
alias = cleanup_name ( d - > alias ) ;
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_DESCRIPTION , alias ) ;
pa_xfree ( alias ) ;
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_STRING , d - > address ) ;
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_API , " bluez " ) ;
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_CLASS , " sound " ) ;
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_BUS , " bluetooth " ) ;
if ( ( ff = form_factor_from_class ( d - > class_of_device ) ) ! = PA_BLUETOOTH_FORM_FACTOR_UNKNOWN )
pa_proplist_sets ( data . proplist , PA_PROP_DEVICE_FORM_FACTOR , form_factor_to_string ( ff ) ) ;
pa_proplist_sets ( data . proplist , " bluez.path " , d - > path ) ;
pa_proplist_setf ( data . proplist , " bluez.class " , " 0x%06x " , d - > class_of_device ) ;
pa_proplist_sets ( data . proplist , " bluez.alias " , d - > alias ) ;
data . name = pa_sprintf_malloc ( " bluez_card.%s " , d - > address ) ;
data . namereg_fail = false ;
2013-09-24 19:45:44 -03:00
create_card_ports ( u , data . ports ) ;
2013-09-24 19:45:45 -03:00
PA_HASHMAP_FOREACH ( uuid , d - > uuids , state ) {
cp = create_card_profile ( u , uuid , data . ports ) ;
if ( ! cp )
continue ;
if ( pa_hashmap_get ( data . profiles , cp - > name ) ) {
pa_card_profile_free ( cp ) ;
continue ;
}
pa_hashmap_put ( data . profiles , cp - > name , cp ) ;
}
pa_assert ( ! pa_hashmap_isempty ( data . profiles ) ) ;
2013-09-24 19:45:43 -03:00
cp = pa_card_profile_new ( " off " , _ ( " Off " ) , sizeof ( pa_bluetooth_profile_t ) ) ;
cp - > available = PA_AVAILABLE_YES ;
p = PA_CARD_PROFILE_DATA ( cp ) ;
* p = PA_BLUETOOTH_PROFILE_OFF ;
pa_hashmap_put ( data . profiles , cp - > name , cp ) ;
u - > card = pa_card_new ( u - > core , & data ) ;
pa_card_new_data_done ( & data ) ;
if ( ! u - > card ) {
pa_log ( " Failed to allocate card. " ) ;
return - 1 ;
}
u - > card - > userdata = u ;
2013-09-24 19:45:50 -03:00
u - > card - > set_profile = set_profile_cb ;
2013-09-24 19:45:43 -03:00
p = PA_CARD_PROFILE_DATA ( u - > card - > active_profile ) ;
u - > profile = * p ;
return 0 ;
}
2013-09-24 19:45:54 -03:00
/* Run from main thread */
static void handle_transport_state_change ( struct userdata * u , struct pa_bluetooth_transport * t ) {
bool acquire = false ;
bool release = false ;
pa_card_profile * cp ;
pa_device_port * port ;
pa_assert ( u ) ;
pa_assert ( t ) ;
2014-08-22 11:07:12 +03:00
pa_assert_se ( cp = pa_hashmap_get ( u - > card - > profiles , pa_bluetooth_profile_to_string ( t - > profile ) ) ) ;
2013-09-24 19:45:54 -03:00
pa_card_profile_set_available ( cp , transport_state_to_availability ( t - > state ) ) ;
/* Update port availability */
pa_assert_se ( port = pa_hashmap_get ( u - > card - > ports , u - > output_port_name ) ) ;
pa_device_port_set_available ( port , get_port_availability ( u , PA_DIRECTION_OUTPUT ) ) ;
pa_assert_se ( port = pa_hashmap_get ( u - > card - > ports , u - > input_port_name ) ) ;
pa_device_port_set_available ( port , get_port_availability ( u , PA_DIRECTION_INPUT ) ) ;
/* Acquire or release transport as needed */
acquire = ( t - > state = = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING & & u - > profile = = t - > profile ) ;
release = ( t - > state ! = PA_BLUETOOTH_TRANSPORT_STATE_PLAYING & & u - > profile = = t - > profile ) ;
if ( acquire & & transport_acquire ( u , true ) > = 0 ) {
if ( u - > source ) {
pa_log_debug ( " Resuming source %s because its transport state changed to playing " , u - > source - > name ) ;
2013-09-29 19:47:16 +03:00
/* We remove the IDLE suspend cause, because otherwise
* module - loopback doesn ' t uncork its streams . FIXME : Messing with
* the IDLE suspend cause here is wrong , the correct way to handle
* this would probably be to uncork the loopback streams not only
* when the other end is unsuspended , but also when the other end ' s
* suspend cause changes to IDLE only ( currently there ' s no
* notification mechanism for suspend cause changes , though ) . */
2013-09-24 19:45:54 -03:00
pa_source_suspend ( u - > source , false , PA_SUSPEND_IDLE | PA_SUSPEND_USER ) ;
}
if ( u - > sink ) {
pa_log_debug ( " Resuming sink %s because its transport state changed to playing " , u - > sink - > name ) ;
2013-09-29 19:47:16 +03:00
/* FIXME: See the previous comment. */
2013-09-24 19:45:54 -03:00
pa_sink_suspend ( u - > sink , false , PA_SUSPEND_IDLE | PA_SUSPEND_USER ) ;
}
}
if ( release & & u - > transport_acquired ) {
/* FIXME: this release is racy, since the audio stream might have
* been set up again in the meantime ( but not processed yet by PA ) .
* BlueZ should probably release the transport automatically , and in
* that case we would just mark the transport as released */
/* Remote side closed the stream so we consider it PA_SUSPEND_USER */
if ( u - > source ) {
pa_log_debug ( " Suspending source %s because the remote end closed the stream " , u - > source - > name ) ;
pa_source_suspend ( u - > source , true , PA_SUSPEND_USER ) ;
}
if ( u - > sink ) {
pa_log_debug ( " Suspending sink %s because the remote end closed the stream " , u - > sink - > name ) ;
pa_sink_suspend ( u - > sink , true , PA_SUSPEND_USER ) ;
}
}
}
2013-09-24 19:45:42 -03:00
/* Run from main thread */
static pa_hook_result_t device_connection_changed_cb ( pa_bluetooth_discovery * y , const pa_bluetooth_device * d , struct userdata * u ) {
pa_assert ( d ) ;
pa_assert ( u ) ;
if ( d ! = u - > device | | pa_bluetooth_device_any_transport_connected ( d ) )
return PA_HOOK_OK ;
pa_log_debug ( " Unloading module for device %s " , d - > path ) ;
pa_module_unload ( u - > core , u - > module , true ) ;
return PA_HOOK_OK ;
}
2013-09-24 19:45:54 -03:00
/* Run from main thread */
static pa_hook_result_t transport_state_changed_cb ( pa_bluetooth_discovery * y , pa_bluetooth_transport * t , struct userdata * u ) {
pa_assert ( t ) ;
pa_assert ( u ) ;
if ( t = = u - > transport & & t - > state < = PA_BLUETOOTH_TRANSPORT_STATE_DISCONNECTED )
2013-11-20 15:42:26 +02:00
pa_assert_se ( pa_card_set_profile ( u - > card , pa_hashmap_get ( u - > card - > profiles , " off " ) , false ) > = 0 ) ;
2013-09-24 19:45:54 -03:00
if ( t - > device = = u - > device )
handle_transport_state_change ( u , t ) ;
return PA_HOOK_OK ;
}
2013-09-24 19:45:51 -03:00
/* Run from main thread context */
static int device_process_msg ( pa_msgobject * obj , int code , void * data , int64_t offset , pa_memchunk * chunk ) {
2014-02-04 19:04:04 -03:00
struct bluetooth_msg * m = BLUETOOTH_MSG ( obj ) ;
2013-09-24 19:45:51 -03:00
switch ( code ) {
case BLUETOOTH_MESSAGE_IO_THREAD_FAILED :
2014-02-04 19:04:04 -03:00
if ( m - > card - > module - > unload_requested )
2013-09-24 19:45:51 -03:00
break ;
pa_log_debug ( " Switching the profile to off due to IO thread failure. " ) ;
2014-02-04 19:04:04 -03:00
pa_assert_se ( pa_card_set_profile ( m - > card , pa_hashmap_get ( m - > card - > profiles , " off " ) , false ) > = 0 ) ;
2013-09-24 19:45:51 -03:00
break ;
2014-02-04 19:04:05 -03:00
case BLUETOOTH_MESSAGE_STREAM_FD_HUP :
break ;
2013-09-24 19:45:51 -03:00
}
return 0 ;
}
2013-09-24 19:45:40 -03:00
int pa__init ( pa_module * m ) {
2013-09-24 19:45:41 -03:00
struct userdata * u ;
const char * path ;
pa_modargs * ma ;
pa_assert ( m ) ;
m - > userdata = u = pa_xnew0 ( struct userdata , 1 ) ;
u - > module = m ;
u - > core = m - > core ;
if ( ! ( ma = pa_modargs_new ( m - > argument , valid_modargs ) ) ) {
pa_log_error ( " Failed to parse module arguments " ) ;
goto fail ;
}
if ( ! ( path = pa_modargs_get_value ( ma , " path " , NULL ) ) ) {
pa_log_error ( " Failed to get device path from module arguments " ) ;
goto fail ;
}
2013-09-24 19:45:56 -03:00
if ( ( u - > discovery = pa_shared_get ( u - > core , " bluetooth-discovery " ) ) )
pa_bluetooth_discovery_ref ( u - > discovery ) ;
else {
pa_log_error ( " module-bluez5-discover doesn't seem to be loaded, refusing to load module-bluez5-device " ) ;
2013-09-24 19:45:41 -03:00
goto fail ;
2013-09-24 19:45:56 -03:00
}
2013-09-24 19:45:41 -03:00
if ( ! ( u - > device = pa_bluetooth_discovery_get_device_by_path ( u - > discovery , path ) ) ) {
pa_log_error ( " %s is unknown " , path ) ;
goto fail ;
}
pa_modargs_free ( ma ) ;
2013-09-24 19:45:42 -03:00
u - > device_connection_changed_slot =
pa_hook_connect ( pa_bluetooth_discovery_hook ( u - > discovery , PA_BLUETOOTH_HOOK_DEVICE_CONNECTION_CHANGED ) ,
PA_HOOK_NORMAL , ( pa_hook_cb_t ) device_connection_changed_cb , u ) ;
2013-09-24 19:45:54 -03:00
u - > transport_state_changed_slot =
pa_hook_connect ( pa_bluetooth_discovery_hook ( u - > discovery , PA_BLUETOOTH_HOOK_TRANSPORT_STATE_CHANGED ) ,
PA_HOOK_NORMAL , ( pa_hook_cb_t ) transport_state_changed_cb , u ) ;
2013-09-24 19:45:43 -03:00
if ( add_card ( u ) < 0 )
goto fail ;
2013-09-24 19:45:51 -03:00
if ( ! ( u - > msg = pa_msgobject_new ( bluetooth_msg ) ) )
goto fail ;
u - > msg - > parent . process_msg = device_process_msg ;
u - > msg - > card = u - > card ;
2013-09-24 19:45:46 -03:00
if ( u - > profile ! = PA_BLUETOOTH_PROFILE_OFF )
if ( init_profile ( u ) < 0 )
goto off ;
2013-09-24 19:45:49 -03:00
if ( u - > sink | | u - > source )
if ( start_thread ( u ) < 0 )
goto off ;
2013-09-24 19:45:46 -03:00
return 0 ;
off :
2013-09-24 19:45:49 -03:00
stop_thread ( u ) ;
2013-09-24 19:45:46 -03:00
2013-11-20 15:42:26 +02:00
pa_assert_se ( pa_card_set_profile ( u - > card , pa_hashmap_get ( u - > card - > profiles , " off " ) , false ) > = 0 ) ;
2013-09-24 19:45:46 -03:00
2013-09-24 19:45:40 -03:00
return 0 ;
2013-09-24 19:45:41 -03:00
fail :
if ( ma )
pa_modargs_free ( ma ) ;
pa__done ( m ) ;
return - 1 ;
2013-09-24 19:45:40 -03:00
}
void pa__done ( pa_module * m ) {
2013-09-24 19:45:41 -03:00
struct userdata * u ;
pa_assert ( m ) ;
if ( ! ( u = m - > userdata ) )
return ;
2013-09-24 19:45:49 -03:00
stop_thread ( u ) ;
2013-09-24 19:45:42 -03:00
if ( u - > device_connection_changed_slot )
pa_hook_slot_free ( u - > device_connection_changed_slot ) ;
2013-09-24 19:45:54 -03:00
if ( u - > transport_state_changed_slot )
pa_hook_slot_free ( u - > transport_state_changed_slot ) ;
2013-09-24 19:45:51 -03:00
if ( u - > sbc_info . buffer )
pa_xfree ( u - > sbc_info . buffer ) ;
2013-09-24 19:45:46 -03:00
if ( u - > sbc_info . sbc_initialized )
sbc_finish ( & u - > sbc_info . sbc ) ;
2013-09-24 19:45:51 -03:00
if ( u - > msg )
pa_xfree ( u - > msg ) ;
2013-09-24 19:45:43 -03:00
if ( u - > card )
pa_card_free ( u - > card ) ;
2013-09-24 19:45:41 -03:00
if ( u - > discovery )
pa_bluetooth_discovery_unref ( u - > discovery ) ;
2013-09-24 19:45:44 -03:00
pa_xfree ( u - > output_port_name ) ;
pa_xfree ( u - > input_port_name ) ;
2013-09-24 19:45:41 -03:00
pa_xfree ( u ) ;
2013-09-24 19:45:40 -03:00
}
2013-09-24 19:45:55 -03:00
int pa__get_n_used ( pa_module * m ) {
struct userdata * u ;
pa_assert ( m ) ;
pa_assert_se ( u = m - > userdata ) ;
return ( u - > sink ? pa_sink_linked_by ( u - > sink ) : 0 ) + ( u - > source ? pa_source_linked_by ( u - > source ) : 0 ) ;
}