From ff2a5768dd4d199cb8bbbe2af4ea3fbb68a489f3 Mon Sep 17 00:00:00 2001 From: mio Date: Sun, 27 Oct 2024 11:26:22 +1000 Subject: [PATCH] Update the xineScope to remove global variables. This patch updates xineScope.c to better align with Amarok's code. As a result, the analyzer is more accurate. For instance, when playing an audio file and there is a silent spot, the blocks will correctly "drop" to the bottom, leaving an empty analyzer. The previous behaviour would leave some blocks "stuck" in their position See: TDE/codeine#23 Signed-off-by: mio --- src/app/xineEngine.cpp | 85 +++++++++++++++++++++++------------- src/app/xineEngine.h | 3 +- src/app/xineScope.c | 98 ++++++++++++++++++++++++++++++++---------- src/app/xineScope.h | 11 +++-- 4 files changed, 141 insertions(+), 56 deletions(-) diff --git a/src/app/xineEngine.cpp b/src/app/xineEngine.cpp index 2cb9cd3..7f73e0d 100644 --- a/src/app/xineEngine.cpp +++ b/src/app/xineEngine.cpp @@ -36,8 +36,9 @@ VideoWindow::VideoWindow( TQWidget *parent ) , m_eventQueue( nullptr ) , m_videoPort( nullptr ) , m_audioPort( nullptr ) - , m_scope( nullptr ) + , m_post( nullptr ) , m_xine( nullptr ) + , m_scope( Analyzer::SCOPE_SIZE * 2 ) // Multiply by two to account for interleaved PCM. , m_current_vpts( 0 ) { DEBUG_BLOCK @@ -51,10 +52,6 @@ VideoWindow::VideoWindow( TQWidget *parent ) setPaletteBackgroundColor( TQt::black ); setFocusPolicy( ClickFocus ); - //TODO sucks - //TODO namespace this? - myList->next = myList; //init the buffer list - // Detect xine version, this is used for volume adjustment. // Xine versions prior to 1.2.13 use linear volume, so the engine uses logarithmic volume. // Xine versions starting from 1.2.13 use logarithmic volume, so the engine uses linear volume. @@ -101,7 +98,7 @@ VideoWindow::~VideoWindow() if( m_stream ) xine_dispose( m_stream ); if( m_audioPort ) xine_close_audio_driver( m_xine, m_audioPort ); if( m_videoPort ) xine_close_video_driver( m_xine, m_videoPort ); - if( m_scope ) xine_post_dispose( m_xine, m_scope ); + if( m_post ) xine_post_dispose( m_xine, m_post ); if( m_xine ) xine_exit( m_xine ); cleanUpVideo(); @@ -119,7 +116,7 @@ VideoWindow::init() if( !m_xine ) return false; - xine_engine_set_param( m_xine, XINE_ENGINE_PARAM_VERBOSITY, 99 ); + xine_engine_set_param(m_xine, XINE_ENGINE_PARAM_VERBOSITY, XINE_VERBOSITY_DEBUG); debug() << "xine_config_load()\n"; xine_config_load( m_xine, TQFile::encodeName( TQDir::homeDirPath() + "/.xine/config" ) ); @@ -161,9 +158,9 @@ VideoWindow::init() debug() << "scope_plugin_new()\n"; #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) - m_scope = xine_post_init( m_xine, "codeine-scope", 1, &m_audioPort, nullptr ); + m_post = xine_post_init( m_xine, "codeine-scope", 1, &m_audioPort, nullptr ); #else - m_scope = scope_plugin_new( m_xine, m_audioPort ); + m_post = scope_plugin_new( m_xine, m_audioPort ); //FIXME this one seems to make seeking unstable for Codeine, perhaps xine_set_param( m_stream, XINE_PARAM_METRONOM_PREBUFFER, 6000 ); //less buffering, faster seeking.. @@ -281,9 +278,9 @@ VideoWindow::load( const KURL &url ) // FIXME leaves one erroneous buffer timerEvent( nullptr ); - if( m_scope ) { + if( m_post ) { xine_post_out_t *source = xine_get_audio_source( m_stream ); - xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_scope, const_cast("audio in") ); + xine_post_in_t *target = (xine_post_in_t*)xine_post_input( m_post, const_cast("audio in") ); xine_post_wire( source, target ); } @@ -373,6 +370,8 @@ VideoWindow::stop() { xine_stop( m_stream ); + std::fill(m_scope.begin(), m_scope.end(), 0); + announceStateChange(); } @@ -590,7 +589,7 @@ VideoWindow::setStreamParameter( int value ) else if( sender == "volume" ) { parameter = XINE_PARAM_AUDIO_AMP_LEVEL; - value = 100 - value; // TQt sliders are wrong way round when vertical + value = 100 - value; // TQt sliders are the wrong way round when vertical if (s_logarithmicVolume) { value = makeVolumeLogarithmic(value); @@ -607,16 +606,25 @@ VideoWindow::scope() { using Analyzer::SCOPE_SIZE; - static Engine::Scope scope( SCOPE_SIZE ); + if (!m_post || !m_stream || xine_get_status(m_stream) != XINE_STATUS_PLAY) + { + return m_scope; + } - if( xine_get_status( m_stream ) != XINE_STATUS_PLAY ) - return scope; + MyNode *const myList = scope_plugin_list(m_post); + const int64_t pts_per_smpls = scope_plugin_pts_per_smpls(m_post); + const int channels = scope_plugin_channels(m_post); + int scopeIdx = 0; + + if (channels > 2) + { + return m_scope; + } //prune the buffer list and update the m_current_vpts timestamp timerEvent( nullptr ); - const int64_t pts_per_smpls = scope_plugin_pts_per_smpls(m_scope); - for( int channels = xine_get_stream_info( m_stream, XINE_STREAM_INFO_AUDIO_CHANNELS ), frame = 0; frame < SCOPE_SIZE; ) + for (int n, frame = 0; frame < SCOPE_SIZE; /* no-op */) { MyNode *best_node = nullptr; @@ -637,10 +645,10 @@ VideoWindow::scope() data16 = best_node->mem; data16 += diff; - diff += diff % channels; //important correction to ensure we don't overflow the buffer - diff /= channels; + diff += diff % channels; // important correction to ensure we don't overflow the buffer. + diff /= channels; // use units of frames, not samples. - int + // calculate the number of available samples in this buffer. n = best_node->num_frames; n -= diff; n += frame; //clipping for # of frames we need @@ -650,33 +658,52 @@ VideoWindow::scope() for( int a, c; frame < n; ++frame, data16 += channels ) { for( a = c = 0; c < channels; ++c ) - a += data16[c]; - - a /= channels; - scope[frame] = a; + { + // now we give interleaved PCM to the scope. + m_scope[scopeIdx++] = data16[c]; + if (channels == 1) + { + // Duplicate mono samples. + m_scope[scopeIdx++] = data16[c]; + } + } } m_current_vpts = best_node->vpts_end; m_current_vpts++; //FIXME needs to be done for some reason, or you get situations where it uses same buffer again and again } - return scope; + return m_scope; } void VideoWindow::timerEvent( TQTimerEvent* ) { + if (!m_stream) + { + return; + } + /// here we prune the buffer list regularly - MyNode * const first_node = myList->next; - MyNode const * const list_end = myList; + MyNode *myList = scope_plugin_list(m_post); + + if (!myList) + { + return; + } + + // We operate on a subset of the list for thread-safety. + MyNode *const firstNode = myList->next; + const MyNode *const listEnd = myList; + // If we're not playing or paused, empty the list. m_current_vpts = (xine_get_status( m_stream ) == XINE_STATUS_PLAY) ? xine_get_current_vpts( m_stream ) : std::numeric_limits::max(); - for( MyNode *prev = first_node, *node = first_node->next; node != list_end; node = node->next ) + for( MyNode *prev = firstNode, *node = firstNode->next; node != listEnd; node = node->next ) { - // we never delete first_node + // we never delete firstNode // this maintains thread-safety if( node->vpts_end < m_current_vpts ) { prev->next = node->next; diff --git a/src/app/xineEngine.h b/src/app/xineEngine.h index 6f8170a..168080d 100644 --- a/src/app/xineEngine.h +++ b/src/app/xineEngine.h @@ -104,9 +104,10 @@ namespace Codeine xine_event_queue_t *m_eventQueue; xine_video_port_t *m_videoPort; xine_audio_port_t *m_audioPort; - xine_post_t *m_scope; + xine_post_t *m_post; xine_t *m_xine; + Engine::Scope m_scope; int64_t m_current_vpts; KURL m_url; diff --git a/src/app/xineScope.c b/src/app/xineScope.c index 3e7cb69..55cf026 100644 --- a/src/app/xineScope.c +++ b/src/app/xineScope.c @@ -4,16 +4,38 @@ /* need access to port_ticket */ #define XINE_ENGINE_INTERNAL +#define LOG_MODULE "codine-scope" +#define LOG_LEVEL LOG_LEVEL_DEBUG +// #define LOG + #include "xineScope.h" #include #include +typedef struct scope_plugin_s { + post_plugin_t super; + int channels; + int64_t pts_per_smpls; + MyNode *list; +} scope_plugin_t; -static MyNode theList; -static int myChannels = 0; -static int64_t pts_per_smpls; +int scope_plugin_channels(void *post) +{ + scope_plugin_t *self = post; + return self->channels; +} -MyNode* const myList = &theList; +MyNode *scope_plugin_list(void *post) +{ + scope_plugin_t *self = post; + return self->list; +} + +int64_t scope_plugin_pts_per_smpls(void *post) +{ + scope_plugin_t *self = post; + return self->pts_per_smpls; +} /************************* * post plugin functions * @@ -22,7 +44,10 @@ MyNode* const myList = &theList; static int scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bits, uint32_t rate, int mode ) { + lprintf("scope_port_open()\n"); + post_audio_port_t *port = (post_audio_port_t *)port_gen; + scope_plugin_t *self = (scope_plugin_t *)port->post; _x_post_rewire( (post_plugin_t*)port->post ); _x_post_inc_usage( port ); @@ -32,14 +57,14 @@ scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bi port->rate = rate; port->mode = mode; - myChannels = _x_ao_mode2channels( mode ); + self->channels = _x_ao_mode2channels(mode); int ret = port->original_port->open( port->original_port, stream, bits, rate, mode ); #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) - pts_per_smpls = ((uint32_t)90000 * (uint32_t)32768) / rate; + self->pts_per_smpls = ((uint32_t)90000 * (uint32_t)32768) / rate; #else - pts_per_smpls = stream->metronom->pts_per_smpls; + self->pts_per_smpls = stream->metronom->pts_per_smpls; #endif return ret; } @@ -47,7 +72,17 @@ scope_port_open( xine_audio_port_t *port_gen, xine_stream_t *stream, uint32_t bi static void scope_port_close( xine_audio_port_t *port_gen, xine_stream_t *stream ) { + lprintf("scope_port_close()\n"); + post_audio_port_t *port = (post_audio_port_t *)port_gen; + scope_plugin_t *self = (scope_plugin_t *)port->post; + + /* ensure the buffers are deleted during the next VideoWindow::timerEvent */ + MyNode *node; + for (node = self->list->next; node != self->list; node = node->next) + { + node->vpts = node->vpts_end - 1; + } port->stream = NULL; port->original_port->close( port->original_port, stream ); @@ -59,6 +94,7 @@ static void scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream ) { post_audio_port_t *port = (post_audio_port_t *)port_gen; + scope_plugin_t *self = (scope_plugin_t *)port->post; /* we are too simple to handle 8bit */ /* what does it mean when stream == NULL? */ @@ -66,7 +102,7 @@ scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_st port->original_port->put_buffer( port->original_port, buf, stream ); return; } MyNode *new_node; - const int num_samples = buf->num_frames * myChannels; + const int num_samples = buf->num_frames * self->channels; new_node = malloc( sizeof(MyNode) ); #ifdef METRONOM_VPTS @@ -80,7 +116,7 @@ scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_st { int64_t - K = pts_per_smpls; /*smpls = 1<<16 samples*/ + K = self->pts_per_smpls; /*smpls = 1<<16 samples*/ K *= num_samples; K /= (1<<16); K += new_node->vpts; @@ -93,14 +129,29 @@ scope_port_put_buffer( xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_st /* finally we should append the current buffer to the list * NOTE this is thread-safe due to the way we handle the list in the GUI thread */ - new_node->next = myList->next; - myList->next = new_node; + new_node->next = self->list->next; + self->list->next = new_node; } static void scope_dispose( post_plugin_t *this ) { - free( this ); + MyNode *list = ((scope_plugin_t *)this)->list; + MyNode *prev; + MyNode *node = list; + + /* Free all elements of the list (a ring buffer) */ + do + { + prev = node->next; + + free(node->mem); + free(node); + + node = prev; + } while(node != list); + + free(this); } @@ -118,15 +169,15 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) if( audio_target == NULL ) return NULL; - post_plugin_t *post_plugin = calloc( 1, sizeof(post_plugin_t) ); + scope_plugin_t *scope_plugin = calloc(1, sizeof(scope_plugin_t)); + post_plugin_t *post_plugin = (post_plugin_t *)scope_plugin; { - post_plugin_t *this = post_plugin; post_in_t *input; post_out_t *output; post_audio_port_t *port; - _x_post_init( this, 1, 0 ); + _x_post_init(post_plugin, 1, 0); #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) @@ -138,10 +189,10 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) port->new_port.close = scope_port_close; port->new_port.put_buffer = scope_port_put_buffer; - this->xine_post.audio_input[0] = &port->new_port; - this->xine_post.type = PLUGIN_POST; + post_plugin->xine_post.audio_input[0] = &port->new_port; + post_plugin->xine_post.type = PLUGIN_POST; - this->dispose = scope_dispose; + post_plugin->dispose = scope_dispose; } #if XINE_MAJOR_VERSION < 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION < 2) || \ @@ -153,6 +204,12 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) post_plugin->xine = xine; #endif + /* scope_plugin_t init */ + scope_plugin->channels = 0; + scope_plugin->pts_per_smpls = 0; + scope_plugin->list = calloc(1, sizeof(MyNode)); + scope_plugin->list->next = scope_plugin->list; + #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) return post_plugin; @@ -161,11 +218,6 @@ xine_post_t* scope_plugin_new( xine_t *xine, xine_audio_port_t *audio_target ) #endif } -int64_t scope_plugin_pts_per_smpls( void *post ) -{ - return pts_per_smpls; -} - #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) static void *scope_init_plugin(xine_t *xine, const void *data) diff --git a/src/app/xineScope.h b/src/app/xineScope.h index 623d56d..99d7215 100644 --- a/src/app/xineScope.h +++ b/src/app/xineScope.h @@ -33,18 +33,23 @@ struct my_node_s int64_t vpts_end; }; -extern MyNode* const myList; - #ifdef __cplusplus extern "C" { +#endif + #if XINE_MAJOR_VERSION > 1 || (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION > 2) || \ (XINE_MAJOR_VERSION == 1 && XINE_MINOR_VERSION == 2 && XINE_SUB_VERSION >= 10) extern const plugin_info_t scope_plugin_info[]; #else xine_post_t* scope_plugin_new( xine_t*, xine_audio_port_t* ); #endif - int64_t scope_plugin_pts_per_smpls( void* ); + + int scope_plugin_channels(void *); + MyNode *scope_plugin_list(void *); + int64_t scope_plugin_pts_per_smpls(void *); + +#ifdef __cplusplus } #endif