Skip to content

Commit

Permalink
Fixed AlsaInput, bugfix for AlsaOutput
Browse files Browse the repository at this point in the history
  • Loading branch information
wedesoft committed Nov 16, 2012
1 parent acbd4f1 commit 08a0d5e
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 92 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -7,7 +7,7 @@ require 'rake/loaders/makefile'
require 'rbconfig' require 'rbconfig'


PKG_NAME = 'hornetseye-alsa' PKG_NAME = 'hornetseye-alsa'
PKG_VERSION = '1.1.2' PKG_VERSION = '1.2.0'
CFG = RbConfig::CONFIG CFG = RbConfig::CONFIG
CXX = ENV[ 'CXX' ] || 'g++' CXX = ENV[ 'CXX' ] || 'g++'
RB_FILES = FileList[ 'lib/**/*.rb' ] RB_FILES = FileList[ 'lib/**/*.rb' ]
Expand Down
193 changes: 136 additions & 57 deletions ext/alsainput.cc
@@ -1,5 +1,5 @@
/* HornetsEye - Computer Vision with Ruby /* HornetsEye - Computer Vision with Ruby
Copyright (C) 2006, 2007, 2008, 2009, 2010 Jan Wedekind Copyright (C) 2012 Jan Wedekind
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
Expand All @@ -19,16 +19,17 @@ using namespace std;


VALUE AlsaInput::cRubyClass = Qnil; VALUE AlsaInput::cRubyClass = Qnil;


AlsaInput::AlsaInput( const string &pcmName, unsigned int rate, AlsaInput::AlsaInput(const string &pcmName, unsigned int rate,
unsigned int channels, int periods, unsigned int channels) throw (Error):
snd_pcm_uframes_t frames ) throw (Error): m_pcmHandle(NULL), m_pcmName( pcmName ), m_rate( rate ), m_channels( channels ),
m_pcmHandle(NULL), m_pcmName( pcmName ), m_rate( rate ), m_channels( channels ) m_periodSize(1024), m_threadInitialised(false), m_start(0), m_count(0),
m_size(rate)
{ {
try { try {
snd_pcm_hw_params_t *hwParams; snd_pcm_hw_params_t *hwParams;
snd_pcm_hw_params_alloca( &hwParams ); snd_pcm_hw_params_alloca(&hwParams);
int err = snd_pcm_open( &m_pcmHandle, m_pcmName.c_str(), SND_PCM_STREAM_CAPTURE, int err = snd_pcm_open(&m_pcmHandle, m_pcmName.c_str(), SND_PCM_STREAM_CAPTURE,
0 ); 0);
ERRORMACRO( err >= 0, Error, , "Error opening PCM device \"" << m_pcmName ERRORMACRO( err >= 0, Error, , "Error opening PCM device \"" << m_pcmName
<< "\": " << snd_strerror( err ) ); << "\": " << snd_strerror( err ) );
err = snd_pcm_hw_params_any( m_pcmHandle, hwParams ); err = snd_pcm_hw_params_any( m_pcmHandle, hwParams );
Expand All @@ -48,16 +49,22 @@ AlsaInput::AlsaInput( const string &pcmName, unsigned int rate,
err = snd_pcm_hw_params_set_channels( m_pcmHandle, hwParams, channels ); err = snd_pcm_hw_params_set_channels( m_pcmHandle, hwParams, channels );
ERRORMACRO( err >= 0, Error, , "Error setting number of channels of PCM device \"" ERRORMACRO( err >= 0, Error, , "Error setting number of channels of PCM device \""
<< m_pcmName << "\" to " << channels << ": " << snd_strerror( err ) ); << m_pcmName << "\" to " << channels << ": " << snd_strerror( err ) );
err = snd_pcm_hw_params_set_periods( m_pcmHandle, hwParams, periods, 0 ); unsigned int bufferTime = 500000;
ERRORMACRO( err >= 0, Error, , "Error setting number of periods of PCM device \"" err = snd_pcm_hw_params_set_buffer_time_near(m_pcmHandle, hwParams, &bufferTime, NULL);
<< m_pcmName << "\" to " << periods << ": " << snd_strerror( err ) ); ERRORMACRO(err >= 0, Error, , "Error setting buffer time of PCM device \""
err = snd_pcm_hw_params_set_buffer_size_near( m_pcmHandle, hwParams, &frames ); << m_pcmName << "\" to " << bufferTime << " us: " << snd_strerror(err));
ERRORMACRO( err >= 0, Error, , "Error setting buffer size of PCM device \"" unsigned int periods = 16;
<< m_pcmName << "\" to " << frames << " frames: " err = snd_pcm_hw_params_set_periods_near(m_pcmHandle, hwParams, &periods, NULL);
<< snd_strerror( err ) ); ERRORMACRO(err >= 0, Error, , "Error setting periods of PCM device \""
<< m_pcmName << "\" to " << periods << ": " << snd_strerror(err));
err = snd_pcm_hw_params( m_pcmHandle, hwParams ); err = snd_pcm_hw_params( m_pcmHandle, hwParams );
ERRORMACRO( err >= 0, Error, , "Error setting parameters of PCM device \"" ERRORMACRO( err >= 0, Error, , "Error setting parameters of PCM device \""
<< m_pcmName << "\": " << snd_strerror( err ) ); << m_pcmName << "\": " << snd_strerror( err ) );
err = snd_pcm_hw_params_get_period_size(hwParams, &m_periodSize, NULL);
ERRORMACRO( err >= 0, Error, , "Error getting period size of PCM device \""
<< m_pcmName << "\": " << snd_strerror( err ) );
err = pthread_mutex_init(&m_mutex, NULL);
ERRORMACRO(err == 0, Error, , "Error initialising mutex: " << strerror(err));
} catch ( Error &e ) { } catch ( Error &e ) {
close(); close();
throw e; throw e;
Expand All @@ -72,29 +79,59 @@ AlsaInput::~AlsaInput(void)
void AlsaInput::close(void) void AlsaInput::close(void)
{ {
if ( m_pcmHandle != NULL ) { if ( m_pcmHandle != NULL ) {
// drop(); drop();
pthread_mutex_destroy(&m_mutex);
snd_pcm_close( m_pcmHandle ); snd_pcm_close( m_pcmHandle );
m_pcmHandle = NULL; m_pcmHandle = NULL;
}; };
} }


SequencePtr AlsaInput::read( int samples ) throw (Error) SequencePtr AlsaInput::read(int samples) throw (Error)
{ {
ERRORMACRO( m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName ERRORMACRO(m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName
<< "\" is not open. Did you call \"close\" before?" ); << "\" is not open. Did you call \"close\" before?");
SequencePtr frame( new Sequence( (int)( samples * 2 * m_channels ) ) ); SequencePtr frame(new Sequence((int)(samples * 2 * m_channels)));
int err; lock();
while ( ( err = snd_pcm_readi( m_pcmHandle, (short int *)frame->data(), if (!m_data.get()) {
samples ) ) < 0 ) { if (m_threadInitialised) pthread_join(m_thread, NULL);
err = snd_pcm_recover( m_pcmHandle, err, 1 ); m_data = boost::shared_array<short int>(new short int[m_size * m_channels]);
ERRORMACRO( err >= 0, Error, , "Error reading audio frames from PCM device \"" m_start = 0;
<< m_pcmName << "\": " << snd_strerror( err ) ); m_count = 0;
pthread_create(&m_thread, NULL, staticThreadFunc, this);
}; };
ERRORMACRO( samples == err, Error, , "Only managed to read " << err << " of " int n = samples;
<< samples << " frames from PCM device \"" << m_pcmName << "\"" ); if (n > m_count) n = m_count;
if (m_start + n > m_size) {
memcpy(frame->data(), m_data.get() + m_start * m_channels, (m_size - m_start) * 2 * m_channels);
memcpy(frame->data() + (m_size - m_start) * 2 * m_channels, m_data.get(), (n + m_start - m_size) * 2 * m_channels);
} else
memcpy(frame->data(), m_data.get() + m_start * m_channels, n * 2 * m_channels);
m_start += n;
if (m_start >= m_size) m_start -= m_size;
m_count -= n;
if (n < samples) {
try {
readi((short int *)frame->data() + n * m_channels * 2, samples - n);
} catch (Error &e) {
unlock();
throw e;
}
}
unlock();
return frame; return frame;
} }


void AlsaInput::drop(void) throw (Error)
{
ERRORMACRO( m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName
<< "\" is not open. Did you call \"close\" before?" );
lock();
m_data.reset();
m_count = 0;
snd_pcm_drop(m_pcmHandle);
unlock();
}

unsigned int AlsaInput::rate(void) unsigned int AlsaInput::rate(void)
{ {
return m_rate; return m_rate;
Expand All @@ -109,6 +146,7 @@ int AlsaInput::avail(void) throw (Error)
{ {
ERRORMACRO( m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName ERRORMACRO( m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName
<< "\" is not open. Did you call \"close\" before?" ); << "\" is not open. Did you call \"close\" before?" );
lock();
snd_pcm_sframes_t frames; snd_pcm_sframes_t frames;
int err = 0; int err = 0;
while ( ( frames = snd_pcm_avail( m_pcmHandle ) ) < 0 ) { while ( ( frames = snd_pcm_avail( m_pcmHandle ) ) < 0 ) {
Expand All @@ -117,22 +155,34 @@ int AlsaInput::avail(void) throw (Error)
"retrieval from PCM device \"" << m_pcmName << "\": " "retrieval from PCM device \"" << m_pcmName << "\": "
<< snd_strerror( err ) ); << snd_strerror( err ) );
}; };
frames += m_count;
unlock();
return frames; return frames;
} }


int AlsaInput::delay(void) throw (Error) void AlsaInput::readi(short int *data, int count)
{ {
ERRORMACRO( m_pcmHandle != NULL, Error, , "PCM device \"" << m_pcmName
<< "\" is not open. Did you call \"close\" before?" );
snd_pcm_sframes_t frames;
int err; int err;
while ( ( err = snd_pcm_delay( m_pcmHandle, &frames ) ) < 0 ) { while ((err = snd_pcm_readi(m_pcmHandle, data, count)) < 0) {
err = snd_pcm_recover( m_pcmHandle, err, 1 ); if (err == -EBADFD)
ERRORMACRO( err >= 0, Error, , "Error querying number of available frames for " err = snd_pcm_prepare(m_pcmHandle);
"capture on PCM device \"" << m_pcmName << "\": " else
<< snd_strerror( err ) ); err = snd_pcm_recover(m_pcmHandle, err, 1);
ERRORMACRO(err >= 0, Error, , "Error reading audio frames from PCM device \""
<< m_pcmName << "\": " << snd_strerror(err));
}; };
return frames; ERRORMACRO(count == err, Error, , "Only managed to read " << err << " of "
<< count << " frames from PCM device \"" << m_pcmName << "\"" );
}

void AlsaInput::lock(void)
{
pthread_mutex_lock( &m_mutex );
}

void AlsaInput::unlock(void)
{
pthread_mutex_unlock( &m_mutex );
} }


void AlsaInput::prepare(void) throw (Error) void AlsaInput::prepare(void) throw (Error)
Expand All @@ -144,17 +194,59 @@ void AlsaInput::prepare(void) throw (Error)
<< "\": " << snd_strerror( err ) ); << "\": " << snd_strerror( err ) );
} }


void AlsaInput::threadFunc(void)
{
bool quit = false;
while (!quit) {
snd_pcm_wait(m_pcmHandle, 1000);
try {
lock();
if (m_data.get()) {
int n = m_periodSize;
if (m_count + n > m_size) {
int m_size_new = m_size;
while(m_size_new < m_count + n) m_size_new = 2 * m_size_new;
boost::shared_array<short int> data(new short int[m_size_new * m_channels]);
if (m_start + m_count > m_size) {
memcpy(data.get(), m_data.get() + m_start * m_channels, (m_size - m_start) * 2 * m_channels);
memcpy(data.get() + (m_size - m_start) * m_channels, m_data.get(), (m_start + m_count - m_size) * 2 * m_channels);
} else
memcpy(data.get(), m_data.get() + m_start * m_channels, m_count * 2 * m_channels);
m_data = data;
m_start = 0;
m_size = m_size_new;
};
int offset = m_start + m_count;
if (offset > m_size) offset -= m_size;
if (offset + n > m_size) n = m_size - offset;
readi(m_data.get() + offset * m_channels, n);
m_count += n;
} else
quit = true;
unlock();
} catch (Error &e) {
quit = true;
m_data.reset();
unlock();
}
};
}

void *AlsaInput::staticThreadFunc( void *self )
{
((AlsaInput *)self)->threadFunc();
return self;
}
VALUE AlsaInput::registerRubyClass( VALUE rbModule ) VALUE AlsaInput::registerRubyClass( VALUE rbModule )
{ {
cRubyClass = rb_define_class_under( rbModule, "AlsaInput", rb_cObject ); cRubyClass = rb_define_class_under( rbModule, "AlsaInput", rb_cObject );
rb_define_singleton_method( cRubyClass, "new", rb_define_singleton_method(cRubyClass, "new",
RUBY_METHOD_FUNC( wrapNew ), 5 ); RUBY_METHOD_FUNC(wrapNew), 3);
rb_define_method( cRubyClass, "close", RUBY_METHOD_FUNC( wrapClose ), 0 ); rb_define_method( cRubyClass, "close", RUBY_METHOD_FUNC( wrapClose ), 0 );
rb_define_method( cRubyClass, "read", RUBY_METHOD_FUNC( wrapRead ), 1 ); rb_define_method( cRubyClass, "read", RUBY_METHOD_FUNC( wrapRead ), 1 );
rb_define_method( cRubyClass, "rate", RUBY_METHOD_FUNC( wrapRate ), 0 ); rb_define_method( cRubyClass, "rate", RUBY_METHOD_FUNC( wrapRate ), 0 );
rb_define_method( cRubyClass, "channels", RUBY_METHOD_FUNC( wrapChannels ), 0 ); rb_define_method( cRubyClass, "channels", RUBY_METHOD_FUNC( wrapChannels ), 0 );
rb_define_method( cRubyClass, "avail", RUBY_METHOD_FUNC( wrapAvail ), 0 ); rb_define_method( cRubyClass, "avail", RUBY_METHOD_FUNC( wrapAvail ), 0 );
rb_define_method( cRubyClass, "delay", RUBY_METHOD_FUNC( wrapDelay ), 0 );
rb_define_method( cRubyClass, "prepare", RUBY_METHOD_FUNC( wrapPrepare ), 0 ); rb_define_method( cRubyClass, "prepare", RUBY_METHOD_FUNC( wrapPrepare ), 0 );
} }


Expand All @@ -164,14 +256,13 @@ void AlsaInput::deleteRubyObject( void *ptr )
} }


VALUE AlsaInput::wrapNew( VALUE rbClass, VALUE rbPCMName, VALUE rbRate, VALUE AlsaInput::wrapNew( VALUE rbClass, VALUE rbPCMName, VALUE rbRate,
VALUE rbChannels, VALUE rbPeriods, VALUE rbFrames ) VALUE rbChannels)
{ {
VALUE retVal = Qnil; VALUE retVal = Qnil;
try { try {
rb_check_type( rbPCMName, T_STRING ); rb_check_type( rbPCMName, T_STRING );
AlsaInputPtr ptr( new AlsaInput( StringValuePtr( rbPCMName ), AlsaInputPtr ptr(new AlsaInput(StringValuePtr(rbPCMName),
NUM2UINT( rbRate ), NUM2UINT( rbChannels ), NUM2UINT(rbRate), NUM2UINT(rbChannels)));
NUM2INT( rbPeriods ), NUM2INT( rbFrames ) ) );
retVal = Data_Wrap_Struct( rbClass, 0, deleteRubyObject, retVal = Data_Wrap_Struct( rbClass, 0, deleteRubyObject,
new AlsaInputPtr( ptr ) ); new AlsaInputPtr( ptr ) );
} catch ( exception &e ) { } catch ( exception &e ) {
Expand Down Expand Up @@ -224,18 +315,6 @@ VALUE AlsaInput::wrapAvail( VALUE rbSelf )
return rbRetVal; return rbRetVal;
} }


VALUE AlsaInput::wrapDelay( VALUE rbSelf )
{
VALUE rbRetVal = Qnil;
try {
AlsaInputPtr *self; Data_Get_Struct( rbSelf, AlsaInputPtr, self );
rbRetVal = INT2NUM( (*self)->delay() );
} catch ( exception &e ) {
rb_raise( rb_eRuntimeError, "%s", e.what() );
};
return rbRetVal;
}

VALUE AlsaInput::wrapPrepare( VALUE rbSelf ) VALUE AlsaInput::wrapPrepare( VALUE rbSelf )
{ {
try { try {
Expand Down
25 changes: 18 additions & 7 deletions ext/alsainput.hh
@@ -1,5 +1,5 @@
/* HornetsEye - Computer Vision with Ruby /* HornetsEye - Computer Vision with Ruby
Copyright (C) 2006, 2007, 2008, 2009, 2010 Jan Wedekind Copyright (C) 2012 Jan Wedekind
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
Expand All @@ -26,33 +26,44 @@ class AlsaInput
{ {
public: public:
AlsaInput( const std::string &pcmName = "default:0", AlsaInput( const std::string &pcmName = "default:0",
unsigned int rate = 48000, unsigned int channels = 2, unsigned int rate = 48000, unsigned int channels = 2) throw (Error);
int periods = 16, snd_pcm_uframes_t frames = 1024 ) throw (Error);
virtual ~AlsaInput(void); virtual ~AlsaInput(void);
void close(void); void close(void);
SequencePtr read( int samples ) throw (Error); SequencePtr read( int samples ) throw (Error);
void drop(void) throw (Error);
unsigned int rate(void); unsigned int rate(void);
unsigned int channels(void); unsigned int channels(void);
int avail(void) throw (Error); int avail(void) throw (Error);
int delay(void) throw (Error); void lock(void);
void unlock(void);
void prepare(void) throw (Error); void prepare(void) throw (Error);
static VALUE cRubyClass; static VALUE cRubyClass;
static VALUE registerRubyClass( VALUE rbModule ); static VALUE registerRubyClass( VALUE rbModule );
static void deleteRubyObject( void *ptr ); static void deleteRubyObject( void *ptr );
static VALUE wrapNew( VALUE rbClass, VALUE rbPCMName, VALUE rbRate, static VALUE wrapNew(VALUE rbClass, VALUE rbPCMName, VALUE rbRate,
VALUE rbChannels, VALUE rbPeriods, VALUE rbFrames ); VALUE rbChannels);
static VALUE wrapClose( VALUE rbSelf ); static VALUE wrapClose( VALUE rbSelf );
static VALUE wrapRead( VALUE rbSelf, VALUE rbSamples ); static VALUE wrapRead( VALUE rbSelf, VALUE rbSamples );
static VALUE wrapRate( VALUE rbSelf ); static VALUE wrapRate( VALUE rbSelf );
static VALUE wrapChannels( VALUE rbSelf ); static VALUE wrapChannels( VALUE rbSelf );
static VALUE wrapAvail( VALUE rbSelf ); static VALUE wrapAvail( VALUE rbSelf );
static VALUE wrapDelay( VALUE rbSelf );
static VALUE wrapPrepare( VALUE rbSelf ); static VALUE wrapPrepare( VALUE rbSelf );
protected: protected:
void readi(short int *data, int count);
void threadFunc(void);
static void *staticThreadFunc( void *self );
snd_pcm_t *m_pcmHandle; snd_pcm_t *m_pcmHandle;
std::string m_pcmName; std::string m_pcmName;
unsigned int m_rate; unsigned int m_rate;
unsigned int m_channels; unsigned int m_channels;
snd_pcm_uframes_t m_periodSize;
bool m_threadInitialised;
boost::shared_array<short int> m_data;
int m_start;
int m_count;
int m_size;
pthread_t m_thread;
pthread_mutex_t m_mutex;
}; };


typedef boost::shared_ptr< AlsaInput > AlsaInputPtr; typedef boost::shared_ptr< AlsaInput > AlsaInputPtr;
Expand Down

0 comments on commit 08a0d5e

Please sign in to comment.