2019-10-25 19:51:40 -04:00
# include <cstring>
# include <string>
# include "AudioInput.h"
# include "AudioReframer.h"
# include "../logger.h"
using namespace std ;
using namespace tc ;
using namespace tc : : audio ;
AudioConsumer : : AudioConsumer ( tc : : audio : : AudioInput * handle , size_t channel_count , size_t sample_rate , size_t frame_size ) :
handle ( handle ) ,
channel_count ( channel_count ) ,
sample_rate ( sample_rate ) ,
frame_size ( frame_size ) {
if ( this - > frame_size > 0 ) {
this - > reframer = make_unique < Reframer > ( channel_count , frame_size ) ;
this - > reframer - > on_frame = [ & ] ( const void * buffer ) { this - > handle_framed_data ( buffer , this - > frame_size ) ; } ;
}
}
void AudioConsumer : : handle_framed_data ( const void * buffer , size_t samples ) {
unique_lock read_callback_lock ( this - > on_read_lock ) ;
auto function = this - > on_read ; /* copy */
read_callback_lock . unlock ( ) ;
if ( ! function )
return ;
function ( buffer , samples ) ;
}
void AudioConsumer : : process_data ( const void * buffer , size_t samples ) {
if ( this - > reframer )
this - > reframer - > process ( buffer , samples ) ;
else
this - > handle_framed_data ( buffer , samples ) ;
}
AudioInput : : AudioInput ( size_t channels , size_t rate ) : _channel_count ( channels ) , _sample_rate ( rate ) { }
AudioInput : : ~ AudioInput ( ) {
this - > close_device ( ) ;
lock_guard lock ( this - > consumers_lock ) ;
for ( const auto & consumer : this - > _consumers )
consumer - > handle = nullptr ;
}
bool AudioInput : : open_device ( std : : string & error , PaDeviceIndex index ) {
lock_guard lock ( this - > input_stream_lock ) ;
if ( index = = this - > _current_device_index )
return true ;
this - > close_device ( ) ;
this - > _current_device_index = index ;
if ( index = = paNoDevice )
return true ;
this - > _current_device = Pa_GetDeviceInfo ( index ) ;
if ( ! this - > _current_device ) {
this - > _current_device_index = paNoDevice ;
error = " failed to get device info " ;
return false ;
}
PaStreamParameters parameters { } ;
memset ( & parameters , 0 , sizeof ( parameters ) ) ;
parameters . channelCount = ( int ) this - > _channel_count ;
parameters . device = this - > _current_device_index ;
parameters . sampleFormat = paFloat32 ;
parameters . suggestedLatency = this - > _current_device - > defaultLowOutputLatency ;
2019-11-09 16:16:08 -05:00
auto err = Pa_OpenStream (
& this - > input_stream ,
& parameters ,
nullptr ,
( double ) this - > _sample_rate ,
paFramesPerBufferUnspecified ,
paClipOff ,
& AudioInput : : _audio_callback ,
this ) ;
2019-11-09 17:06:20 -05:00
2019-10-25 19:51:40 -04:00
if ( err ! = paNoError ) {
2019-11-09 17:06:20 -05:00
this - > input_stream = nullptr ;
error = to_string ( err ) + " / " + Pa_GetErrorText ( err ) ;
2019-10-25 19:51:40 -04:00
return false ;
}
return true ;
}
bool AudioInput : : record ( ) {
lock_guard lock ( this - > input_stream_lock ) ;
if ( ! this - > input_stream )
return false ;
2019-11-09 16:16:08 -05:00
2019-10-25 19:51:40 -04:00
if ( Pa_IsStreamActive ( this - > input_stream ) )
return true ;
auto err = Pa_StartStream ( this - > input_stream ) ;
if ( err ! = paNoError & & err ! = paStreamIsNotStopped ) {
log_error ( category : : audio , tr ( " Pa_StartStream returned {} " ) , err ) ;
return false ;
}
return true ;
}
bool AudioInput : : recording ( ) {
lock_guard lock ( this - > input_stream_lock ) ;
return this - > input_stream & & Pa_IsStreamActive ( this - > input_stream ) ;
}
void AudioInput : : stop ( ) {
lock_guard lock ( this - > input_stream_lock ) ;
if ( this - > input_stream ) {
2019-11-09 17:06:20 -05:00
if ( Pa_IsStreamActive ( this - > input_stream ) )
Pa_StopStream ( this - > input_stream ) ;
2019-10-25 19:51:40 -04:00
}
}
void AudioInput : : close_device ( ) {
lock_guard lock ( this - > input_stream_lock ) ;
if ( this - > input_stream ) {
2019-11-09 17:06:20 -05:00
if ( Pa_IsStreamActive ( this - > input_stream ) )
Pa_StopStream ( this - > input_stream ) ;
2019-10-25 19:51:40 -04:00
2019-11-09 17:06:20 -05:00
auto error = Pa_CloseStream ( this - > input_stream ) ;
if ( error ! = paNoError )
log_error ( category : : audio , tr ( " Failed to close PA stream: {} " ) , error ) ;
2019-10-25 19:51:40 -04:00
this - > input_stream = nullptr ;
2019-11-09 17:06:20 -05:00
this_thread : : sleep_for ( chrono : : seconds { 1 } ) ;
2019-10-25 19:51:40 -04:00
}
}
std : : shared_ptr < AudioConsumer > AudioInput : : create_consumer ( size_t frame_length ) {
auto result = shared_ptr < AudioConsumer > ( new AudioConsumer ( this , this - > _channel_count , this - > _sample_rate , frame_length ) ) ;
{
lock_guard lock ( this - > consumers_lock ) ;
this - > _consumers . push_back ( result ) ;
}
return result ;
}
void AudioInput : : delete_consumer ( const std : : shared_ptr < AudioConsumer > & source ) {
{
lock_guard lock ( this - > consumers_lock ) ;
auto it = find ( this - > _consumers . begin ( ) , this - > _consumers . end ( ) , source ) ;
if ( it ! = this - > _consumers . end ( ) )
this - > _consumers . erase ( it ) ;
}
source - > handle = nullptr ;
}
int AudioInput : : _audio_callback ( const void * a , void * b , unsigned long c , const PaStreamCallbackTimeInfo * d , PaStreamCallbackFlags e , void * _ptr_audio_output ) {
return reinterpret_cast < AudioInput * > ( _ptr_audio_output ) - > audio_callback ( a , b , c , d , e ) ;
}
int AudioInput : : audio_callback ( const void * input , void * output , unsigned long frameCount , const PaStreamCallbackTimeInfo * timeInfo , PaStreamCallbackFlags statusFlags ) {
if ( ! input ) /* hmmm.. suspicious */
return 0 ;
if ( this - > _volume ! = 1 ) {
auto ptr = ( float * ) input ;
auto left = frameCount * this - > _channel_count ;
while ( left - - > 0 )
* ( ptr + + ) * = this - > _volume ;
}
auto begin = chrono : : system_clock : : now ( ) ;
for ( const auto & consumer : this - > consumers ( ) ) {
consumer - > process_data ( input , frameCount ) ;
}
auto end = chrono : : system_clock : : now ( ) ;
auto ms = chrono : : duration_cast < chrono : : milliseconds > ( end - begin ) . count ( ) ;
if ( ms > 5 ) {
log_warn ( category : : audio , tr ( " Processing of audio input needed {}ms. This could be an issue! " ) , chrono : : duration_cast < chrono : : milliseconds > ( end - begin ) . count ( ) ) ;
}
return 0 ;
}