MidiAlsaSeq: protect concurrent sequencer accesses with mutex

The ALSA sequencer interface is not reentrant and thus we can't queue
MIDI events for output from different threads (which happens with multicore
rendering as processOutEvent() is being called by individual note play
handles). We therefore have to care that we don't call the ALSA sequencer
functions concurrently.

Thanks to Benjamin Kusch for providing a testcase.

Closes #531.
This commit is contained in:
Tobias Doerffel
2013-11-20 23:49:13 +01:00
parent e4d18e76d5
commit 87cf991c32
2 changed files with 49 additions and 2 deletions

View File

@@ -1,7 +1,7 @@
/*
* MidiAlsaSeq.h - ALSA-sequencer-client
*
* Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2005-2013 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
@@ -31,6 +31,7 @@
#include <alsa/asoundlib.h>
#endif
#include <QtCore/QMutex>
#include <QtCore/QThread>
#include <QtCore/QTimer>
@@ -130,6 +131,7 @@ private:
virtual void run();
#ifdef LMMS_HAVE_ALSA
QMutex m_seqMutex;
snd_seq_t * m_seqHandle;
struct Ports
{

View File

@@ -1,7 +1,7 @@
/*
* MidiAlsaSeq.cpp - ALSA sequencer client
*
* Copyright (c) 2005-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
* Copyright (c) 2005-2013 Tobias Doerffel <tobydox/at/users.sourceforge.net>
*
* This file is part of Linux MultiMedia Studio - http://lmms.sourceforge.net
*
@@ -73,6 +73,7 @@ static QString __portName( snd_seq_t * _seq, const snd_seq_addr_t * _addr )
MidiAlsaSeq::MidiAlsaSeq() :
MidiClient(),
m_seqMutex(),
m_seqHandle( NULL ),
m_queueID( -1 ),
m_quit( false ),
@@ -131,9 +132,11 @@ MidiAlsaSeq::~MidiAlsaSeq()
m_quit = true;
wait( EventPollTimeOut*2 );
m_seqMutex.lock();
snd_seq_stop_queue( m_seqHandle, m_queueID, NULL );
snd_seq_free_queue( m_seqHandle, m_queueID );
snd_seq_close( m_seqHandle );
m_seqMutex.unlock();
}
}
@@ -228,8 +231,10 @@ void MidiAlsaSeq::processOutEvent( const midiEvent & _me,
return;
}
m_seqMutex.lock();
snd_seq_event_output( m_seqHandle, &ev );
snd_seq_drain_output( m_seqHandle );
m_seqMutex.unlock();
}
@@ -238,6 +243,8 @@ void MidiAlsaSeq::processOutEvent( const midiEvent & _me,
void MidiAlsaSeq::applyPortMode( MidiPort * _port )
{
m_seqMutex.lock();
// determine port-capabilities
unsigned int caps[2] = { 0, 0 };
@@ -297,6 +304,7 @@ void MidiAlsaSeq::applyPortMode( MidiPort * _port )
}
}
m_seqMutex.unlock();
}
@@ -304,6 +312,8 @@ void MidiAlsaSeq::applyPortMode( MidiPort * _port )
void MidiAlsaSeq::applyPortName( MidiPort * _port )
{
m_seqMutex.lock();
for( int i = 0; i < 2; ++i )
{
if( m_portIDs[_port][i] == -1 )
@@ -320,6 +330,8 @@ void MidiAlsaSeq::applyPortName( MidiPort * _port )
port_info );
snd_seq_port_info_free( port_info );
}
m_seqMutex.unlock();
}
@@ -329,8 +341,11 @@ void MidiAlsaSeq::removePort( MidiPort * _port )
{
if( m_portIDs.contains( _port ) )
{
m_seqMutex.lock();
snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] );
snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] );
m_seqMutex.unlock();
m_portIDs.remove( _port );
}
MidiClient::removePort( _port );
@@ -361,11 +376,16 @@ void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port,
{
return;
}
m_seqMutex.lock();
snd_seq_addr_t sender;
if( snd_seq_parse_address( m_seqHandle, &sender,
_dest.section( ' ', 0, 0 ).toAscii().constData() ) )
{
fprintf( stderr, "error parsing sender-address!\n" );
m_seqMutex.unlock();
return;
}
snd_seq_port_info_t * port_info;
@@ -386,6 +406,8 @@ void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port,
}
snd_seq_port_subscribe_free( subs );
snd_seq_port_info_free( port_info );
m_seqMutex.unlock();
}
@@ -406,11 +428,14 @@ void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port,
return;
}
m_seqMutex.lock();
snd_seq_addr_t dest;
if( snd_seq_parse_address( m_seqHandle, &dest,
_dest.section( ' ', 0, 0 ).toAscii().constData() ) )
{
fprintf( stderr, "error parsing dest-address!\n" );
m_seqMutex.unlock();
return;
}
snd_seq_port_info_t * port_info;
@@ -431,6 +456,7 @@ void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port,
}
snd_seq_port_subscribe_free( subs );
snd_seq_port_info_free( port_info );
m_seqMutex.unlock();
}
@@ -471,15 +497,20 @@ void MidiAlsaSeq::run()
break;
}
m_seqMutex.lock();
// while event queue is not empty
while( snd_seq_event_input_pending( m_seqHandle, true ) > 0 )
{
snd_seq_event_t * ev;
if( snd_seq_event_input( m_seqHandle, &ev ) < 0 )
{
m_seqMutex.unlock();
qCritical( "error while fetching MIDI event from sequencer" );
break;
}
m_seqMutex.unlock();
snd_seq_addr_t * source = NULL;
MidiPort * dest = NULL;
@@ -582,8 +613,12 @@ void MidiAlsaSeq::run()
break;
} // end switch
m_seqMutex.lock();
} // end while
m_seqMutex.unlock();
}
delete[] pollfd_set;
@@ -594,9 +629,13 @@ void MidiAlsaSeq::run()
void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm )
{
m_seqMutex.lock();
snd_seq_change_queue_tempo( m_seqHandle, m_queueID,
60000000 / (int) _bpm, NULL );
snd_seq_drain_output( m_seqHandle );
m_seqMutex.unlock();
}
@@ -615,6 +654,9 @@ void MidiAlsaSeq::updatePortList()
snd_seq_port_info_malloc( &pinfo );
snd_seq_client_info_set_client( cinfo, -1 );
m_seqMutex.lock();
while( snd_seq_query_next_client( m_seqHandle, cinfo ) >= 0 )
{
int client = snd_seq_client_info_get_client( cinfo );
@@ -643,6 +685,9 @@ void MidiAlsaSeq::updatePortList()
}
}
m_seqMutex.unlock();
snd_seq_client_info_free( cinfo );
snd_seq_port_info_free( pinfo );