Warning: Can't synchronize with repository "(default)" (No changeset 96d22ec3fa3ef6de3ea8dc0d7d398adc9aa071cf in the repository). Look in the Trac log for more information.

source: fsoaudiod/src/plugins/gsmvoice_alsa_cmtspeechdata/cmthandler.vala @ 7c9c12c

Revision 7c9c12c, 17.8 KB checked in by Simon Busch <morphis@…>, 16 months ago (diff)

fsoaudiod: change license from LGPLv2.1 to GPLv2

Signed-off-by: Simon Busch <morphis@…>
Acked-by: Michael 'Mickey' Lauer <mlauer@…>
Acked-by: Denis 'GNUtoo' Carikli <GNUtoo@…>

  • Property mode set to 100644
Line 
1/*
2 * Copyright (C) 2011-2012 Michael 'Mickey' Lauer <mlauer@vanille-media.de>
3 *                         Denis 'GNUtoo' Carikli <GNUtoo@no-log.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 */
19extern int snd_pcm_format_set_silence (Alsa.PcmFormat format,void * data, uint samples );
20
21
22/**
23 * @class RingBuffer
24 *
25 * Provides a ring buffer for alsa frames
26 **/
27errordomain RingError {
28    Overflow,
29    Underflow
30}
31
32class RingBuffer : GLib.Object
33{
34    private uint8[] ring;
35    private int ring_head;
36    private int ring_tail;
37    private int ring_size;
38   
39    public RingBuffer( int size )
40    {
41        this.ring = new uint8[size];
42        this.ring_head = 0;
43        this.ring_tail = 0;
44        this.ring_size = size;
45    }
46
47    public void write( uint8[] x, int count ) throws RingError
48    {
49        int new_ring_head = (ring_head + count) % ring_size;
50        int free = (ring_size + ring_tail - ring_head) % ring_size;
51        if ( free == 0 )
52            free = ring_size;
53        stderr.printf( "RingBuffer.write: ring_head=%d, ring_tail=%d, count=%d, free=%d\n", ring_head, ring_tail, count, free );
54        if ( count > free )
55        {
56            throw new RingError.Overflow( @"Buffer is full (free: $free / wanted to write: $count)" );
57        }               
58
59        stderr.printf( "RingBuffer.write: new ring_head would be %d\n", new_ring_head );
60        /* check wraparound */
61        if ( new_ring_head == 0 || new_ring_head > ring_head )
62        {
63            /* check next line for +-1 errors I made! */
64            stdout.printf( "RingBuffer.write: does not overlap - chunk = %d\n", count );
65            Memory.copy( &ring[ring_head], x, count );
66        }
67        else
68        {
69            stdout.printf( "RingBuffer.write: does overlap - first chunk = %d\n", ring_size - ring_head );
70            /* check next 2 lines for +-1 errors I made! */
71            Memory.copy( &ring[ring_head], x, ring_size - ring_head );
72            stdout.printf( "RingBuffer.write: second chunk = %d\n", ring_size - ring_head );
73            Memory.copy( &ring[0], &x[ring_size - ring_head], count - (ring_size - ring_head) );
74        }
75        ring_head = new_ring_head;
76    }
77
78    /* we pass the pointer to buffer as a parameter, so you can use existing buffers */
79    /* wouldn't want the function to malloc a new buffer each time */
80    public void read( uint8[] x, int count ) throws RingError
81    {
82        int avail = (ring_size + ring_head - ring_tail) % ring_size;
83
84        stderr.printf( "RingBuffer.read: ring_head=%d, ring_tail=%d, count=%d, avail=%d\n", ring_head, ring_tail, count, avail );
85        if ( avail < count )
86        {
87            throw new RingError.Underflow( @"Buffer has only $avail bytes available ($count requested)" );
88        }
89
90        int new_ring_tail = (ring_tail + count) % ring_size;
91        stderr.printf( "RingBuffer.read: new_ring_tail would be %d\n", new_ring_tail );
92
93        if ( new_ring_tail == 0 || new_ring_tail > ring_tail )
94        {
95            stderr.printf( "RingBuffer.read: does not wrap - chunk = %d\n", count );
96            Memory.copy( x, &ring[ring_tail], count );
97        }
98        else
99        {
100            stderr.printf( "RingBuffer.read: does overwrap - first chunk = %d\n", ring_size - ring_tail );
101            Memory.copy( x, &ring[ring_tail], ring_size - ring_tail );
102            stderr.printf( "RingBuffer.read: second chunk = %d\n", count - (ring_size - ring_tail) );
103            Memory.copy( &x[ring_size - ring_tail], ring, count - (ring_size - ring_tail) );
104        }
105        ring_tail = new_ring_tail;
106    }
107
108    public void reset()
109    {
110        ring_tail = ring_head = 0;
111    }
112}
113
114
115
116/**
117 * @class CmtHandler
118 *
119 * Handles Audio via libcmtspeechdata
120 **/
121public class CmtHandler : FsoFramework.AbstractObject
122{
123    private CmtSpeech.Connection connection;
124    private IOChannel channel;
125    private FsoAudio.PcmDevice pcmout = null;
126    private FsoAudio.PcmDevice pcmin = null;
127    private bool status;
128    private const int FCOUNT = 160;
129    private const int FRAMESIZE = 2;
130    private const int BUFSIZE = 3 * FCOUNT * FRAMESIZE;
131
132    /* alsa parameters */
133    private Alsa.PcmFormat format = Alsa.PcmFormat.S16_LE;
134    private Alsa.PcmAccess access = Alsa.PcmAccess.RW_INTERLEAVED;
135
136    /* silence buffer */
137    private uint8[] silence_buffer = null;
138
139    /* playback Thread */
140    private unowned Thread<void *> playbackThread = null;
141    private int runPlaybackThread = 0;
142    private Mutex playbackMutex = new Mutex();
143    private RingBuffer fromModem;
144
145    /* record Thread */
146    private unowned Thread<void *> recordThread = null;
147    private bool runRecordThread = false;
148    private Mutex recordMutex = new Mutex();
149    private RingBuffer toModem;
150    private int timing; // feed UL to the modem (in ms)
151
152    //
153    // Constructor
154    //
155    public CmtHandler()
156    {
157        status = false;
158
159        assert( logger.debug( "Initializing cmtspeech" ) );
160        CmtSpeech.init();
161
162        assert( logger.debug( "Setting up traces" ) );
163        CmtSpeech.trace_toggle( CmtSpeech.TraceType.STATE_CHANGE, true );
164        CmtSpeech.trace_toggle( CmtSpeech.TraceType.IO, true );
165        CmtSpeech.trace_toggle( CmtSpeech.TraceType.DEBUG, true );
166
167        assert( logger.debug( "Instanciating connection" ) );
168        connection = new CmtSpeech.Connection();
169        if ( connection == null )
170        {
171            logger.error( "Can't instanciate connection" );
172            return;
173        }
174
175        var fd = connection.descriptor();
176
177        if ( fd == -1 )
178        {
179            logger.error( "Cmtspeech file descriptor invalid" );
180        }
181
182        timing = 50;
183        fromModem = new RingBuffer( BUFSIZE );
184        toModem = new RingBuffer( BUFSIZE );
185
186        assert( logger.debug( "Hooking up fd with main loop" ) );
187        channel = new IOChannel.unix_new( fd );
188        channel.add_watch( IOCondition.IN | IOCondition.HUP, onInputFromChannel );
189
190        silence_buffer = new uint8[ FCOUNT  * FRAMESIZE ];
191        snd_pcm_format_set_silence( format, silence_buffer, FCOUNT );
192
193        logger.info( "Created" );
194    }
195
196    //
197    // Private API
198    //
199
200    private void play_silence( int count )
201    {
202        Alsa.PcmSignedFrames ret;
203        int retries = 3;
204
205        while ( count > 0 && retries > 0 )
206        {
207            try
208            {
209                ret = pcmout.writei( silence_buffer, FCOUNT );
210                if ( ret == -Posix.EPIPE )
211                {
212                    pcmout.recover( -Posix.EPIPE, 0 );
213                    retries--;
214                }
215                else
216                {
217                    count--;
218                }
219            }
220            catch ( FsoAudio.SoundError e )
221            {
222                logger.error( @"Error: $(e.message)" );
223                return;
224            }
225        }
226    }
227
228    private bool feed_to_modem()
229    {
230        CmtSpeech.FrameBuffer ulbuf = null;
231
232        assert( logger.debug( "feeding from ringbuffer to modem" ) );
233        if ( connection.is_active() )
234        {
235            var ok = connection.ul_buffer_acquire( out ulbuf );
236            if ( ok == 0 )
237            {
238                assert( logger.debug( "protocol state is ACTIVE_DLUL, uploading as well..." ) );
239                try
240                {
241                    recordMutex.lock();
242                    toModem.read( (uint8[])ulbuf.payload, ulbuf.pcount );
243                    recordMutex.unlock();
244                }
245                catch ( RingError e )
246                {
247                    recordMutex.unlock();
248                    Memory.copy( ulbuf.payload, silence_buffer, ulbuf.pcount > FCOUNT * FRAMESIZE ? FCOUNT * FRAMESIZE : ulbuf.pcount );
249                }
250                connection.ul_buffer_release( ulbuf );
251            }
252        }
253        return false;
254    }
255
256    private void * playbackThreadFunc()
257    {
258        while ( runPlaybackThread > 0 )
259        {
260            var buf = new uint8[ FCOUNT * FRAMESIZE ];
261            Alsa.PcmSignedFrames frames;
262
263            try
264            {
265                playbackMutex.lock();
266                fromModem.read( buf, FCOUNT * FRAMESIZE );
267                playbackMutex.unlock();
268                frames = pcmout.writei( (uint8[])buf, FCOUNT );
269                if ( frames != FCOUNT )
270                {
271                    stderr.printf("frames: %ld \n",(long)frames);
272                }
273                else if ( frames == -Posix.EPIPE )
274                {
275                    pcmout.recover( -Posix.EPIPE, 0 );
276                }
277            }
278            catch ( FsoAudio.SoundError e )
279            {
280                logger.error( @"Error: $(e.message)" );
281            }
282            catch ( RingError e )
283            {
284                playbackMutex.unlock();
285                logger.warning( @"RingBuffer error: $(e.message)" );
286                play_silence( 1 );
287            }
288        }
289
290        return null;
291    }
292
293
294    private void * recordThreadFunc()
295    {
296        while ( runRecordThread == true )
297        {
298            var buf = new uint8[ FCOUNT * FRAMESIZE ];
299            Alsa.PcmSignedFrames frames;
300
301            Timeout.add_full( Priority.HIGH, timing, feed_to_modem );
302            try
303            {
304                frames = pcmin.readi( (uint8[])buf, FCOUNT );
305                if ( frames == -Posix.EPIPE )
306                {
307                    pcmin.prepare();
308                }
309                recordMutex.lock();
310                toModem.write( buf, (int)frames * FRAMESIZE );
311                recordMutex.unlock();
312            }
313            catch ( FsoAudio.SoundError e )
314            {
315                logger.error( @"SoundError: $(e.message)" );
316            }
317            catch ( RingError e )
318            {
319                recordMutex.unlock();
320            }
321        }
322
323        return null;
324    }
325
326    private void alsaSinkSetup()
327    {
328        int channels = 1;
329        int rate = 8000;
330
331        pcmout = new FsoAudio.PcmDevice();
332        assert( logger.debug( @"Setup alsa sink for modem audio" ) );
333        try
334        {
335            pcmout.open( "plug:dmix" );
336            pcmout.setFormat( access, format, rate, channels );
337        }
338        catch ( Error e )
339        {
340            logger.error( @"Error: $(e.message)" );
341        }
342
343        /* start the playback thread now
344         */
345        if ( !Thread.supported() )
346        {
347            logger.warning( "Cannot run without thread support!" );
348        }
349        else
350        {
351            if ( playbackThread == null )
352            {
353                try
354                {
355                    playbackThread = Thread.create<void *>( playbackThreadFunc, true );
356                }
357                catch ( ThreadError e )
358                {
359                    stdout.printf( @"Error: $(e.message)" );
360                    return;
361               }
362            }
363            else
364            {
365                stdout.printf( "Thread already launched \n" );
366            }
367            AtomicInt.set(ref runPlaybackThread,1);
368        }
369    }
370
371    private void alsaSrcSetup()
372    {
373        int channels = 1;
374        int rate = 8000;
375
376        pcmin = new FsoAudio.PcmDevice();
377        assert( logger.debug( @"Setup alsa source for modem audio" ) );
378        try
379        {
380            pcmin.open( "plug:dsnoop", Alsa.PcmStream.CAPTURE );
381            pcmin.setFormat( access, format, rate, channels );
382        }
383        catch ( Error e )
384        {
385            logger.error( @"Error: $(e.message)" );
386        }
387
388        /* start the record thread now */
389        if ( !Thread.supported() )
390        {
391            logger.warning( "Cannot run without thread support!" );
392        }
393        else
394        {
395            if ( recordThread == null )
396            {
397                try
398                {
399                    recordThread = Thread.create<void *>( recordThreadFunc, true );
400                }
401                catch ( ThreadError e )
402                {
403                    stdout.printf( @"Error: $(e.message)" );
404                    return;
405               }
406            }
407            else
408            {
409                stdout.printf( "Thread already launched \n" );
410            }
411            runRecordThread = true;
412        }
413
414    }
415
416    private void alsaSinkCleanup()
417    {
418        AtomicInt.set(ref runPlaybackThread,0);
419        playbackThread.join();
420        playbackThread = null;
421        pcmout.close();
422        pcmout = null;
423        fromModem.reset();
424    }
425
426    private void alsaSrcCleanup()
427    {
428        runRecordThread = false;
429        recordThread.join();
430        recordThread = null;
431        pcmin.close();
432        pcmin = null;
433        toModem.reset();
434    }
435
436    private void handleTimingUpdate( CmtSpeech.Event event )
437    {
438        CmtSpeech.EventData* msg = (CmtSpeech.EventData*) (&event.msg);
439        assert( logger.debug( @"modem UL timing update: msec = $(msg.timing_config_ntf.msec) usec = $(msg.timing_config_ntf.usec)" ) );
440        timing = msg.timing_config_ntf.msec;
441    }
442
443    private void handleDataEvent()
444    {
445        assert( logger.debug( @"handleDataEvent during protocol state $(connection.protocol_state())" ) );
446
447        CmtSpeech.FrameBuffer dlbuf = null;
448
449        var ok = connection.dl_buffer_acquire( out dlbuf );
450        if ( ok == 0 )
451        {
452            assert( logger.debug( @"received DL packet w/ $(dlbuf.count) bytes (payload is $(dlbuf.pcount))" ) );
453
454            try
455            {
456                playbackMutex.lock();
457                fromModem.write( (uint8[])dlbuf.payload, dlbuf.pcount );
458                playbackMutex.unlock();
459            }
460            catch ( RingError e )
461            {
462                playbackMutex.unlock();
463                logger.warning( @"RingBuffer error: $(e.message)" );
464            }
465
466            connection.dl_buffer_release( dlbuf );
467        }
468
469    }
470
471    private void handleControlEvent()
472    {
473        assert( logger.debug( @"handleControlEvent during protocol state $(connection.protocol_state())" ) );
474
475        CmtSpeech.Event event = CmtSpeech.Event();
476        CmtSpeech.Transition transition = 0;
477
478        connection.read_event( event );
479
480        assert( logger.debug( @"read event, type is $(event.msg_type)" ) );
481        transition = connection.event_to_state_transition( event );
482
483        switch ( transition )
484        {
485            case CmtSpeech.Transition.INVALID:
486                assert( logger.debug( "ERROR: invalid state transition") );
487                break;
488
489            case CmtSpeech.Transition.6_TIMING_UPDATE:
490            case CmtSpeech.Transition.7_TIMING_UPDATE:
491                handleTimingUpdate( event );
492                break;
493
494            case CmtSpeech.Transition.3_DL_START:
495                alsaSinkSetup();
496                break;
497
498            case CmtSpeech.Transition.12_UL_START:
499                alsaSrcSetup();
500                break;
501
502            case CmtSpeech.Transition.4_DLUL_STOP:
503                alsaSinkCleanup();
504                alsaSrcCleanup();
505                break;
506
507            case CmtSpeech.Transition.1_CONNECTED:
508            case CmtSpeech.Transition.2_DISCONNECTED:
509            case CmtSpeech.Transition.5_PARAM_UPDATE:
510            case CmtSpeech.Transition.10_RESET:
511            case CmtSpeech.Transition.11_UL_STOP:
512                assert( logger.debug( @"State transition ok, new state is $transition" ) );
513                break;
514
515            default:
516                assert_not_reached();
517                break;
518        }
519    }
520
521    private bool onInputFromChannel( IOChannel source, IOCondition condition )
522    {
523        //the following line is commented to work arround a vala 0.12.1 bug
524        //with the use of to_string() on an enum, which results in a segmentation fault
525        //assert( logger.debug( @"onInputFromChannel, condition = $condition" ) );
526
527        assert( condition == IOCondition.HUP || condition == IOCondition.IN );
528
529        if ( condition == IOCondition.HUP )
530        {
531            logger.warning( "HUP! Will no longer handle input from cmtspeechdata" );
532            return false;
533        }
534
535        CmtSpeech.EventType flags = 0;
536        var ok = connection.check_pending( out flags );
537        if ( ok < 0 )
538        {
539            assert( logger.debug( "Error while checking for pending events..." ) );
540        }
541        else if ( ok == 0 )
542        {
543            assert( logger.debug( "D'oh, cmt speech readable, but no events pending..." ) );
544        }
545        else
546        {
547            assert( logger.debug( "Connection reports pending events with flags 0x%0X".printf( flags ) ) );
548
549            if ( ( flags & CmtSpeech.EventType.DL_DATA ) == CmtSpeech.EventType.DL_DATA )
550            {
551                handleDataEvent();
552            }
553            else if ( ( flags & CmtSpeech.EventType.CONTROL ) == CmtSpeech.EventType.CONTROL )
554            {
555                handleControlEvent();
556            }
557            else
558            {
559                assert( logger.debug( "Event no DL_DATA nor CONTROL, ignoring" ) );
560            }
561        }
562
563        return true;
564    }
565
566    //
567    // Public API
568    //
569
570    public override string repr()
571    {
572        CmtSpeech.State state = ( connection != null ) ? connection.protocol_state() : 0;
573        return @"<$state>";
574    }
575
576    public void setAudioStatus( bool enabled )
577    {
578        if ( enabled == status )
579        {
580            assert( logger.debug( @"Status already $status" ) );
581            return;
582        }
583
584        assert( logger.debug( @"Setting call status to $enabled" ) );
585        connection.state_change_call_status( enabled );
586        status = enabled;
587    }
588}
589// vim:ts=4:sw=4:expandtab
Note: See TracBrowser for help on using the repository browser.