From 2e1c7ce8a22febef43c38772eca3beadd9919ffa Mon Sep 17 00:00:00 2001 From: Tobias Doerffel Date: Tue, 7 Apr 2009 00:07:19 +0200 Subject: [PATCH] AudioJack: fixed lockup when getting zombified This commit fixes lockup of the whole audio engine as soon as LMMS' JACK backend got zombified due to longer delays (e.g. when audio engine is blocked while loading a project). Restart JACK backend in such cases and show an according message to the user. Furthermore this commit includes various cleanups and improvements. (cherry picked from commit b4e38e39789b3b0cc7fde7315a2bc49e13d875a7) --- include/audio_jack.h | 32 ++-- src/core/audio/audio_jack.cpp | 286 ++++++++++++++++++---------------- 2 files changed, 177 insertions(+), 141 deletions(-) diff --git a/include/audio_jack.h b/include/audio_jack.h index 3fcf54ed1c..92fe57ddd4 100644 --- a/include/audio_jack.h +++ b/include/audio_jack.h @@ -1,7 +1,7 @@ /* * audio_jack.h - support for JACK-transport * - * Copyright (c) 2005-2008 Tobias Doerffel + * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -29,9 +29,8 @@ #include "lmmsconfig.h" #ifdef LMMS_HAVE_JACK - #include - +#endif #include #include @@ -45,16 +44,17 @@ class QLineEdit; class lcdSpinBox; -class audioJACK : public audioDevice +class audioJACK : public QObject, public audioDevice { + Q_OBJECT public: audioJACK( bool & _success_ful, mixer * _mixer ); virtual ~audioJACK(); inline static QString name( void ) { - return( QT_TRANSLATE_NOOP( "setupWidget", - "JACK (JACK Audio Connection Kit)" ) ); + return QT_TRANSLATE_NOOP( "setupWidget", + "JACK (JACK Audio Connection Kit)" ); } @@ -73,7 +73,14 @@ public: } ; +private slots: + void restartAfterZombified( void ); + + +#ifdef LMMS_HAVE_JACK private: + bool initJackClient( void ); + virtual void startProcessing( void ); virtual void stopProcessing( void ); virtual void applyQualitySettings( void ); @@ -82,7 +89,10 @@ private: virtual void unregisterPort( audioPort * _port ); virtual void renamePort( audioPort * _port ); - static int processCallback( jack_nframes_t _nframes, void * _udata ); + int processCallback( jack_nframes_t _nframes, void * _udata ); + + static int staticProcessCallback( jack_nframes_t _nframes, + void * _udata ); static void shutdownCallback( void * _udata ); @@ -91,7 +101,7 @@ private: bool m_active; bool m_stopped; - QSemaphore m_stop_semaphore; + QSemaphore m_stopSemaphore; QVector m_outputPorts; surroundSampleFrame * m_outBuf; @@ -108,9 +118,11 @@ private: typedef QMap jackPortMap; jackPortMap m_portMap; +#endif + +signals: + void zombified( void ); } ; #endif - -#endif diff --git a/src/core/audio/audio_jack.cpp b/src/core/audio/audio_jack.cpp index 8f7b643072..d5f9681fb9 100644 --- a/src/core/audio/audio_jack.cpp +++ b/src/core/audio/audio_jack.cpp @@ -1,9 +1,7 @@ -#ifndef SINGLE_SOURCE_COMPILE - /* * audio_jack.cpp - support for JACK-transport * - * Copyright (c) 2005-2008 Tobias Doerffel + * Copyright (c) 2005-2009 Tobias Doerffel * * This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net * @@ -45,6 +43,7 @@ #include "config_mgr.h" #include "lcd_spinbox.h" #include "audio_port.h" +#include "main_window.h" @@ -55,87 +54,21 @@ audioJACK::audioJACK( bool & _success_ful, mixer * _mixer ) : DEFAULT_CHANNELS, SURROUND_CHANNELS ), _mixer ), m_client( NULL ), - m_active( FALSE ), - m_stop_semaphore( 1 ), + m_active( false ), + m_stopSemaphore( 1 ), m_outBuf( new surroundSampleFrame[getMixer()->framesPerPeriod()] ), m_framesDoneInCurBuf( 0 ), m_framesToDoInCurBuf( 0 ) { - _success_ful = FALSE; - - QString client_name = configManager::inst()->value( "audiojack", - "clientname" ); - if( client_name == "" ) + _success_ful = initJackClient(); + if( _success_ful ) { - client_name = "lmms"; + m_stopSemaphore.acquire(); + + connect( this, SIGNAL( zombified() ), + this, SLOT( restartAfterZombified() ), + Qt::QueuedConnection ); } - -#ifndef OLD_JACK - const char * server_name = NULL; - jack_status_t status; - m_client = jack_client_open( client_name.toAscii().constData(), - JackNullOption, &status, - server_name ); - if( m_client == NULL ) - { - printf( "jack_client_open() failed, status 0x%2.0x\n", status ); - if( status & JackServerFailed ) - { - printf( "Could not connect to JACK server.\n" ); - } - return; - } - if( status & JackNameNotUnique ) - { - printf( "there's already a client with name '%s', so unique " - "name '%s' was assigned\n", client_name. - toAscii().constData(), - jack_get_client_name( m_client ) ); - } - -#else /* OLD_JACK */ - - m_client = jack_client_new( client_name.toAscii().constData() ); - if( m_client == NULL ) - { - printf( "jack_client_new() failed\n" ); - return; - } - -#endif - - // set process-callback - jack_set_process_callback( m_client, processCallback, this ); - - // set shutdown-callback - jack_on_shutdown( m_client, shutdownCallback, this ); - - - - if( jack_get_sample_rate( m_client ) != sampleRate() ) - { - setSampleRate( jack_get_sample_rate( m_client ) ); - } - - for( Uint8 ch = 0; ch < channels(); ++ch ) - { - QString name = QString( "master out " ) + - ( ( ch % 2 ) ? "R" : "L" ) + - QString::number( ch / 2 + 1 ); - m_outputPorts.push_back( jack_port_register( m_client, - name.toAscii().constData(), - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0 ) ); - if( m_outputPorts.back() == NULL ) - { - printf( "no more JACK-ports available!\n" ); - return; - } - } - - m_stop_semaphore.acquire(); - - _success_ful = TRUE; } @@ -143,7 +76,7 @@ audioJACK::audioJACK( bool & _success_ful, mixer * _mixer ) : audioJACK::~audioJACK() { - m_stop_semaphore.release(); + m_stopSemaphore.release(); while( m_portMap.size() ) { @@ -160,7 +93,101 @@ audioJACK::~audioJACK() } delete[] m_outBuf; +} + + + +void audioJACK::restartAfterZombified( void ) +{ + if( initJackClient() ) + { + m_active = false; + startProcessing(); + QMessageBox::information( engine::getMainWindow(), + tr( "JACK client restarted" ), + tr( "LMMS was kicked by JACK for some reason. " + "Therefore the JACK backend of LMMS has been " + "restarted. You will have to make manual " + "connections again." ) ); + } + else + { + QMessageBox::information( engine::getMainWindow(), + tr( "JACK server down" ), + tr( "The JACK server seems to have been shutdown " + "and starting a new instance failed. " + "Therefore LMMS is unable to proceed. " + "You should save your project and restart " + "JACK and LMMS." ) ); + } +} + + + + + +bool audioJACK::initJackClient( void ) +{ + QString clientName = configManager::inst()->value( "audiojack", + "clientname" ); + if( clientName.isEmpty() ) + { + clientName = "lmms"; + } + + const char * serverName = NULL; + jack_status_t status; + m_client = jack_client_open( clientName.toAscii().constData(), + JackNullOption, &status, + serverName ); + if( m_client == NULL ) + { + printf( "jack_client_open() failed, status 0x%2.0x\n", status ); + if( status & JackServerFailed ) + { + printf( "Could not connect to JACK server.\n" ); + } + return false; + } + if( status & JackNameNotUnique ) + { + printf( "there's already a client with name '%s', so unique " + "name '%s' was assigned\n", clientName. + toAscii().constData(), + jack_get_client_name( m_client ) ); + } + + // set process-callback + jack_set_process_callback( m_client, staticProcessCallback, this ); + + // set shutdown-callback + jack_on_shutdown( m_client, shutdownCallback, this ); + + + + if( jack_get_sample_rate( m_client ) != sampleRate() ) + { + setSampleRate( jack_get_sample_rate( m_client ) ); + } + + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) + { + QString name = QString( "master out " ) + + ( ( ch % 2 ) ? "R" : "L" ) + + QString::number( ch / 2 + 1 ); + m_outputPorts.push_back( jack_port_register( m_client, + name.toAscii().constData(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0 ) ); + if( m_outputPorts.back() == NULL ) + { + printf( "no more JACK-ports available!\n" ); + return false; + } + } + + return true; } @@ -168,9 +195,9 @@ audioJACK::~audioJACK() void audioJACK::startProcessing( void ) { - m_stopped = FALSE; + m_stopped = false; - if( m_active ) + if( m_active || m_client == NULL ) { return; } @@ -181,7 +208,7 @@ void audioJACK::startProcessing( void ) return; } - m_active = TRUE; + m_active = true; // make sure, JACK transport is rolling @@ -192,7 +219,7 @@ void audioJACK::startProcessing( void ) // try to sync JACK's and LMMS's buffer-size - jack_set_buffer_size( m_client, getMixer()->framesPerPeriod() ); +// jack_set_buffer_size( m_client, getMixer()->framesPerPeriod() ); @@ -206,7 +233,7 @@ void audioJACK::startProcessing( void ) } else { - for( Uint8 ch = 0; ch < channels(); ++ch ) + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { if( jack_connect( m_client, jack_port_name( m_outputPorts[ch] ), @@ -227,7 +254,7 @@ void audioJACK::startProcessing( void ) void audioJACK::stopProcessing( void ) { - m_stop_semaphore.acquire(); + m_stopSemaphore.acquire(); } @@ -259,7 +286,7 @@ void audioJACK::registerPort( audioPort * _port ) const QString name[2] = { _port->name() + " L", _port->name() + " R" } ; - for( Uint8 ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { m_portMap[_port].ports[ch] = jack_port_register( m_client, name[ch].toAscii().constData(), @@ -277,7 +304,7 @@ void audioJACK::unregisterPort( audioPort * _port ) #ifdef AUDIO_PORT_SUPPORT if( m_portMap.contains( _port ) ) { - for( Uint8 ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { if( m_portMap[_port].ports[ch] != NULL ) { @@ -300,7 +327,7 @@ void audioJACK::renamePort( audioPort * _port ) { const QString name[2] = { _port->name() + " L", _port->name() + " R" }; - for( Uint8 ch = 0; ch < DEFAULT_CHANNELS; ++ch ) + for( ch_cnt_t ch = 0; ch < DEFAULT_CHANNELS; ++ch ) { jack_port_set_name( m_portMap[_port].ports[ch], name[ch].toAscii().constData() ); @@ -314,33 +341,24 @@ void audioJACK::renamePort( audioPort * _port ) int audioJACK::processCallback( jack_nframes_t _nframes, void * _udata ) { - audioJACK * _this = static_cast( _udata ); + jack_transport_state_t ts = jack_transport_query( m_client, NULL ); -/* printf( "%f\n", jack_cpu_load( _this->m_client ) );*/ - -#ifdef LMMS_DEBUG - assert( _this != NULL ); -#endif - jack_transport_state_t ts = jack_transport_query( _this->m_client, - NULL ); - - QVector outbufs( _this->channels(), - NULL ); - Uint8 chnl = 0; + QVector outbufs( channels(), NULL ); + ch_cnt_t chnl = 0; for( QVector::iterator it = outbufs.begin(); it != outbufs.end(); ++it, ++chnl ) { *it = (jack_default_audio_sample_t *) jack_port_get_buffer( - _this->m_outputPorts[chnl], _nframes ); + m_outputPorts[chnl], _nframes ); } #ifdef AUDIO_PORT_SUPPORT const Uint32 frames = qMin( _nframes, - _this->getMixer()->framesPerPeriod() ); - for( jackPortMap::iterator it = _this->m_portMap.begin(); - it != _this->m_portMap.end(); ++it ) + getMixer()->framesPerPeriod() ); + for( jackPortMap::iterator it = m_portMap.begin(); + it != m_portMap.end(); ++it ) { - for( Uint8 ch = 0; ch < _this->channels(); ++ch ) + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { if( it.data().ports[ch] == NULL ) { @@ -359,50 +377,60 @@ int audioJACK::processCallback( jack_nframes_t _nframes, void * _udata ) #endif jack_nframes_t done = 0; - while( done < _nframes && _this->m_stopped == FALSE ) + while( done < _nframes && m_stopped == false ) { jack_nframes_t todo = qMin( _nframes, - _this->m_framesToDoInCurBuf - - _this->m_framesDoneInCurBuf ); + m_framesToDoInCurBuf - + m_framesDoneInCurBuf ); if( ts == JackTransportRolling ) { - for( Uint8 chnl = 0; chnl < _this->channels(); ++chnl ) + const float gain = getMixer()->masterGain(); + for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl ) { + jack_default_audio_sample_t * o = outbufs[chnl]; for( jack_nframes_t frame = 0; frame < todo; ++frame ) { - outbufs[chnl][done+frame] = - _this->m_outBuf[_this->m_framesDoneInCurBuf+frame][chnl] * - _this->getMixer()->masterGain(); + o[done+frame] = + m_outBuf[m_framesDoneInCurBuf+ + frame][chnl] * gain; } } } done += todo; - _this->m_framesDoneInCurBuf += todo; - if( _this->m_framesDoneInCurBuf == _this->m_framesToDoInCurBuf ) + m_framesDoneInCurBuf += todo; + if( m_framesDoneInCurBuf == m_framesToDoInCurBuf ) { - _this->m_framesToDoInCurBuf = _this->getNextBuffer( - _this->m_outBuf ); - if( !_this->m_framesToDoInCurBuf ) + m_framesToDoInCurBuf = getNextBuffer( m_outBuf ); + if( !m_framesToDoInCurBuf ) { - _this->m_stopped = TRUE; - _this->m_stop_semaphore.release(); + m_stopped = true; + m_stopSemaphore.release(); } - _this->m_framesDoneInCurBuf = 0; + m_framesDoneInCurBuf = 0; } } - if( ts != JackTransportRolling || _this->m_stopped == TRUE ) + if( ts != JackTransportRolling || m_stopped == true ) { - for( Uint8 ch = 0; ch < _this->channels(); ++ch ) + for( ch_cnt_t ch = 0; ch < channels(); ++ch ) { jack_default_audio_sample_t * b = outbufs[ch] + done; memset( b, 0, sizeof( *b ) * ( _nframes - done ) ); } } - return( 0 ); + return 0; +} + + + + +int audioJACK::staticProcessCallback( jack_nframes_t _nframes, void * _udata ) +{ + return static_cast( _udata )-> + processCallback( _nframes, _udata ); } @@ -412,13 +440,7 @@ void audioJACK::shutdownCallback( void * _udata ) { audioJACK * _this = static_cast( _udata ); _this->m_client = NULL; -/* QMessageBox::information( 0, setupWidget::tr( "JACK-server down" ), - setupWidget::tr( "You seem to have " - "shutdown JACK-server, so " - "LMMS is unable to proceed. " - "You should save your project " - "and restart LMMS!" ), - QMessageBox::Ok );*/ + _this->zombified(); } @@ -472,6 +494,8 @@ void audioJACK::setupWidget::saveSettings( void ) } -#endif + +#include "moc_audio_jack.cxx" #endif +