1 # agent-audio.tcl --
2 #
3 # FIXME: This file needs a description here.
4 #
5 # Copyright (c) 1996-2002 The Regents of the University of California.
6 # All rights reserved.
7 #
8 # Redistribution and use in source and binary forms, with or without
9 # modification, are permitted provided that the following conditions are met:
10 #
11 # A. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 # B. Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 # C. Neither the names of the copyright holders nor the names of its
17 # contributors may be used to endorse or promote products derived from this
18 # software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
21 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
24 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #
31 # @(#) $Header: /usr/mash/src/repository/mash/mash-1/tcl/net/agent-audio.tcl,v 1.72 2003/06/03 20:06:45 aswan Exp $
32
33
34 import RTPAgent RTP/Audio Configuration \
35 AnnounceListenManager/AS/Client/MeGa/Audio
36
37 Module/AudioEncoder set nb_ 0
38 if [TclObject is-class Audio] {
39 Audio set duplex_ 1
40 }
41 AudioController set echo_thresh_ 0
42 AudioController set echo_suppress_time_ 0
43 AudioController set idle_drop_time_ 0
44
45
46 #------------------------------------------------------------------
47 # Class:
48 # AudioAgent
49 # Description:
50 #
51 # The AudioAgent class provides a coarse-grained interface
52 # that abstracts away all the details of networked audio.
53 # This agent is responsible for creating the underlying RTP
54 # session for the audio channel, for opening and initializing
55 # the audio codec hardware, and for creating the AudioController
56 # that orchestrates the timing and synchronization of all
57 # audio events.
58 # <p>
59 # Since AudioAgent is derived from the RTPAgent base class,
60 # the standard RTP observer API is supported. Any ``interesting''
61 # events (e.g., the arrival of a new RTP flow) within the underlying
62 # RTP sessions are relayed to all the observers attached to this agent.
63 # (Note that the AudioAgent class catches and handles some of the
64 # events on its own.)
65 # <p>
66 # Typically, a mash script will create a separate user interface
67 # object and attach it to the AudioAgent as an Observer.
68 #------------------------------------------------------------------
69 Class AudioAgent -superclass { RTPAgent RTP/Audio } -configuration {
70 megaAudioFormat gsm
71 # multicast is default
72 megaRecvAudioPort 0
73 audioSessionBW 20
74 megaAudioCtrl 224.4.5.24/50000/31
75 audioServiceLocation urn:agw
76 }
77
78 #------------------------------------------------------------------
79 # Method:
80 # AudioAgent init
81 # Description:
82 #
83 # Initialize a new instance of an AudioAgent.
84 # initialize the network (alot of it is done
85 # in the parent classes) and setup the audioStream
86 #
87 AudioAgent public init { app spec {callback {}} } {
88 set ab [SessionAddress parse $spec]
89 if { $ab != "" } {
90 set fmt [$ab fmt]
91 if { $fmt != {} } { $self add_option audioFormat $fmt }
92 }
93
94 $self next $ab $callback
95 if { $ab != "" } {
96 delete $ab
97 }
98
99 # send back-to-back packets spaced out at 128kb/s
100 $self set-bandwidth 1280000
101
102 #FIXME
103 $self site-drop-time [$self get_option siteDropTime]
104
105
106 $self instvar decoders_ audioDevice_ sampRate use16bit useStereo deviceName
107 set decoders_ ""
108
109 #
110 # Set up a table to map lower-case RTP format
111 # names into the suffix of the class name
112 # that encodes or decodes this type
113 #
114 $self instvar classmap_
115 set classmap_(pcm) PCM
116 set classmap_(lpc) LPC
117 set classmap_(gsm) GSM
118 set classmap_(dvi) ADPCM
119 set classmap_(mp3) MP3
120 # added so vat supports linear 16, used with aserver
121 set classmap_(lin16) PCM
122 # $self start_mega
123 $self instvar session_
124 if ![info exists sampRate] {
125 set sampRate 8000
126 }
127 if ![info exists use16bit] {
128 set use16bit 0
129 }
130 if ![info exists useStereo] {
131 set useStereo 0
132 }
133
134 set srcid [$self get_local_srcid]
135 if ![info exists deviceName] {
136 set audioDevice_ [new AudioStream $srcid $session_ $sampRate $use16bit $useStereo]
137 } else {
138 set audioDevice_ [new AudioStream $srcid $session_ $sampRate $use16bit $useStereo $deviceName]
139 }
140 }
141
142 # old code, no longer used
143 AudioAgent public start_mega { } {
144 $self instvar al_
145 if [info exists al_] { delete $al_ }
146 if { [$self get_option megaAudioSession] != "" } {
147 set sname [$self get_option megaAudioSession]
148 set sspec [$self get_option audioSessionSpec]
149 set rportspec [$self get_option megaRecvAudioPort]
150 set ofmt [$self get_option megaAudioFormat]
151 set sbw [$self get_option audioSessionBW]
152 set bw [expr 0.02*$sbw*1000]
153 set megaspec [$self get_option megaAudioCtrl]
154 set loc [$self get_option audioServiceLocation]
155
156 set ab [new AddressBlock $sspec]
157 set sspec [$ab addr]/[$ab sport]:[$ab rport]/[$ab ttl]
158 delete $ab
159 set al_ [new AnnounceListenManager/AS/Client/MeGa/Audio \
160 $self $megaspec $bw [Application name] audio \
161 $sname $sspec $rportspec $ofmt $loc]
162 $al_ start
163 }
164 }
165
166
167 #------------------------------------------------------------------
168 # Method:
169 # AudioAgent destroy
170 # Description:
171 #
172 # Deallocate all the resources associated with
173 # the AudioAgent class. Close the audio device
174 # (and implicitly delete it) and free up the buffer pool.
175 #
176 AudioAgent public destroy {} {
177 $self instvar al_ audioDevice_
178 if [info exists audioDevice_] { delete $audioDevice_ }
179 if [info exists al_] { delete $al_ }
180 $self next
181 }
182
183 # old code, no longer used
184 # Used when we need to point the mega audio gateway we're controlling
185 # to listen to a new source. This is kludgy, and needs a redesign.
186 #
187 AudioAgent public reset_mega {} {
188 $self instvar al_
189 if ![info exists al_] {
190 $self start_mega
191 } else {
192 $al_ reset_spec [$self get_option audioSessionSpec]
193 }
194 }
195
196 #------------------------------------------------------------------
197 # Method:
198 # AudioAgent activate
199 # Description:
200 #
201 # Handle an activate event on source <i>src</i>.
202 # Called from C++ (through Source/RTP) when we start
203 # actively receiving data pkts from the given source.
204 # Create the decoder and dispatch the method to the
205 # observers as normal.
206 # Extends method in RTPAgent.
207 #
208 AudioAgent private activate src {
209 $self instvar decoders_
210 set d [$self create_decoder $src]
211 lappend decoders_ $d
212 $src handler $d
213 $self next $src
214 }
215
216 #------------------------------------------------------------------
217 # Method:
218 # AudioAgent deactivate
219 # Description:
220 #
221 # Handle a deactivate event on source <i>src</i>.
222 # Called from C++ (through Source/RTP) when a source
223 # has left the session (either via an RTCP BYE message
224 # or via an expiration timer). This method can also
225 # get called back if the Source object is explicitly
226 # deleted from the local program.
227 # Extends method in RTPAgent.
228 #
229 AudioAgent private deactivate src {
230 $self instvar decoders_
231 set d [$src handler]
232 set k [lsearch -exact $decoders_ $d]
233 set decoders_ [lreplace $decoders_ $k $k]
234 $self next $src
235 delete $d
236 }
237
238 #
239 #------------------------------------------------------------------
240 # Method:
241 # AudioAgent trigger_media
242 # Description:
243 #
244 # Handle a media-trigger event on source <i>src</i>.
245 # Called from C++ (through Source/RTP) when a data packet
246 # arrives from source <i>src</i> and its media trigger is enabled.
247 # Extends method in RTPAgent.
248 #
249
250 AudioAgent instproc trigger_media src {
251 $self instvar local_chan_
252 if [info exists local_chan_] {
253 set cname [$src sdes cname]
254 if { "$cname" != "" } {
255 $local_chan_ send FOCUS_SPEAKER $cname
256 }
257 }
258 $self next $src
259 }
260
261 #
262 #------------------------------------------------------------------
263 # Method:
264 # AudioAgent attach_local_channel
265 # AudioAgent attach_global_channel
266 # Description:
267 #
268 # Attach a CoordinationBus object to this agent.
269 # When present, the coordination bus enables a number
270 # of inter-agent interactions, e.g., voice-switched-video
271 # windows orchestrated between video and audio agents.
272 #
273
274 AudioAgent instproc attach_local_channel lc {
275 $self set local_chan_ $lc
276 }
277 AudioAgent instproc attach_global_channel gc {
278 $self set glob_chan_ $gc
279 }
280
281 #
282 #------------------------------------------------------------------
283 # Method:
284 # AudioAgent set_maxchannel
285 # Description:
286 #
287 # Called by the underlying network object when the number
288 # of media layers expected across all sources changes.
289 # For example, an underlying network protocol might adjust
290 # the number of multicast channels received to carry out
291 # congestion control. When the level of subscription changes,
292 # the codecs must be informed so that they do not wait
293 # unnecessarily for packets that will never arrive (because
294 # the corresponding layer is not present).
295 # <p>
296 # Extends method in RTPAgent.
297 #
298 AudioAgent private set_maxchannel n {
299 global active
300 foreach s [array names active] {
301 set d [$s handler]
302 $d set maxChannel_ $n
303 }
304 }
305
306 #
307 #------------------------------------------------------------------
308 # Method:
309 # AudioAgent create_decoder
310 # Description:
311 #
312 # Create an audio decoder that can decode the RTP flow
313 # from source <i>src</i>. Assumes this source has received
314 # media packets so we can determine the RTP payload type
315 # and allocate an appropriate decoder. If the system
316 # does not support the media type, a ``null decoder'' is
317 # created and returned.
318 #
319 AudioAgent public create_decoder src {
320 $self instvar classmap_ audioDevice_
321 if ![info exists classmap_([$src format_name])] {
322 # don't support this format
323 set decoder [new Module/AudioDecoder/Null]
324 } else {
325 set decoder [new Module/AudioDecoder/$classmap_([$src format_name])]
326 }
327
328 if { $decoder == "" } {
329 # don't support this format
330 set decoder [new Module/AudioDecoder/Null]
331 }
332 #FIXME
333 set controller [$audioDevice_ get_controller]
334 if { $controller == 0 } {
335 puts stderr "AudioAgent: no audio controller."
336 exit 0
337 }
338 $decoder set agent_ $self
339 $decoder set src_ $src
340 $decoder controller $controller
341 $src handler $decoder
342
343 return $decoder
344 }
345
346 #
347 #------------------------------------------------------------------
348 # Method:
349 # AudioAgent create_session
350 # Description:
351 #
352 # Create a Session object that appropriate for this
353 # type of agent. Called by the RTPAgent class when
354 # initializing the network state.
355 #
356 AudioAgent private create_session {} {
357 return [new Session/RTP/Audio]
358 }
359
360 #------------------------------------------------------------------
361 # Method:
362 # AudioAgent reset
363 # Description:
364 #
365 # Assign a new address (or set of addresses) to the
366 # underlying communication session. The address(es)
367 # must be in the AddressBlock object given by <i>ab</i>.
368 #
369 AudioAgent public reset ab {
370 $self next $ab
371 #
372 # audio shouldn't be looped back (unlike video),
373 # but if we might want to loop it back to other processes
374 # running on the local host (if the option is enabled)
375 #
376 $self app_loopback 0
377 $self net_loopback [$self get_option loopback]
378 $self set-bandwidth 128000
379
380 #$self instvar set_pool_srcid_
381 #if ![info exists set_pool_srcid_] {
382 # not too sure when this is used, kind of an ad hoc way of doing it...
383 $self instvar audioDevice_
384 if [info exists audioDevice_] {
385 set bufferPool [$audioDevice_ set bufferPool_]
386 if ![info exists bufferPool] {
387 set bufferPool [new BufferPool/RTP]
388 }
389 $bufferPool srcid [$self get_local_srcid]
390 }
391 #set set_pool_srcid_ 1
392 #}
393 }
394
395 #------------------------------------------------------------------
396 # Method:
397 # AudioAgent reset_source_offsets
398 # Description:
399 #
400 # Reset the clock offset of each Source object in the underlying
401 # communication session. The clock offset is the skew between
402 # the sender's media timestamp and the local media time.
403 # Rather than run an elaborate synchronization scheme, the offset
404 # is computed and set at the start of each talk spurt.
405 # Since these offsets can drift when the Controller object
406 # is not running off the audio clock, this time base can
407 # shift pretty badly (especially on PC's where gettimeofday
408 # accuracy is low). This hook allows the UI to note when
409 # the device is re-gained so that the offsets can be explicitly reset.
410 # FIXME - this is too much exposure of mechanism. Figure out
411 # how to hide this. The AudioAgent should be able to tell on
412 # its own thtat the device is re-obtained and should do the
413 # reset then... Also, delay adaptation state
414 # should be kept in the source object... (and this method should
415 # live in RtpAgent)
416 #
417 AudioAgent instproc reset_source_offsets {} {
418 $self instvar decoders_
419 foreach d $decoders_ {
420 $d reset-offset
421 }
422 }
423
424 #------------------------------------------------------------------
425 # Method:
426 # AudioAgent bind_transducer
427 # AudioAgent have_audio
428 # AudioAgent set_input_mute
429 # AudioAgent set_output_mute
430 # AudioAgent set_input_gain
431 # AudioAgent set_output_gain
432 # AudioAgent get_input_gain
433 # AudioAgent get_output_gain
434 # AudioAgent set_input_port
435 # AudioAgent set_output_port
436 # AudioAgent get_input_port
437 # AudioAgent get_output_port
438 # AudioAgent get_input_ports
439 # AudioAgent get_output_ports
440 # AudioAgent get_input_portno
441 # AudioAgent get_output_portno
442 # AudioAgent set_speakerphone
443 # AudioAgent audio_test
444 # AudioAgent port_name_to_num
445 # AudioAgent obtain
446 # AudioAgent release
447 # AudioAgent is_active
448 # AudioAgent clear_active
449 # AudioAgent select_format
450 # AudioAgent set_silence_thresh
451 # AudioAgent unix_time
452 # AudioAgent ntp_time
453 # Description:
454 #
455 # A bunch of calls that just forward to the audioDevice_...
456
457 AudioAgent public bind_transducer { which o } {
458 $self instvar audioDevice_
459 $audioDevice_ bind_transducer $which $o
460 }
461
462 AudioAgent public have_audio {} {
463 $self instvar audioDevice_
464 if [info exists audioDevice_] {
465 return [$audioDevice_ have_audio]
466 }
467 return 0
468 }
469
470 AudioAgent public set_input_mute val {
471 $self instvar audioDevice_
472 $audioDevice_ set_input_mute $val
473 }
474
475 AudioAgent public set_output_mute val {
476 $self instvar audioDevice_
477 $audioDevice_ set_output_mute $val
478 }
479
480 AudioAgent public get_input_ports {} {
481 $self instvar audioDevice_
482 return [$audioDevice_ get_input_ports]
483 }
484
485 AudioAgent public get_output_ports {} {
486 $self instvar audioDevice_
487 return [$audioDevice_ get_output_ports]
488 }
489
490 AudioAgent public is_halfduplex {} {
491 $self instvar audioDevice_
492 return [$audioDevice_ is_halfduplex]
493 }
494
495 AudioAgent public get_input_portno { } {
496 $self instvar audioDevice_
497 return [$audioDevice_ get_input_portno]
498 }
499
500 AudioAgent public get_output_portno { } {
501 $self instvar audioDevice_
502 return [$audioDevice_ get_output_portno]
503 }
504
505 AudioAgent public set_speakerphone { port mode } {
506 $self instvar audioDevice_
507 $audioDevice_ set_speakerphone $port $mode
508 }
509
510 AudioAgent public audio_test type {
511 $self instvar audioDevice_
512 $audioDevice_ audio_test $type
513 }
514
515 AudioAgent public port_name_to_num { which name } {
516 $self instvar audioDevice_
517 return [$audioDevice_ port_name_to_num $which $name]
518 }
519
520 AudioAgent public set_input_port port {
521 $self instvar audioDevice_
522 $audioDevice_ set_input_port $port
523 }
524
525 AudioAgent public set_output_port port {
526 $self instvar audioDevice_
527 $audioDevice_ set_output_port $port
528 }
529
530 AudioAgent public set_input_gain gain {
531 $self instvar audioDevice_
532 return [$audioDevice_ set_input_gain $gain]
533 }
534
535 AudioAgent public set_output_gain gain {
536 $self instvar audioDevice_
537 return [$audioDevice_ set_output_gain $gain]
538 }
539
540 AudioAgent public get_input_gain {} {
541 $self instvar audioDevice_
542 return [$audioDevice_ get_input_gain]
543 }
544
545 AudioAgent public get_output_gain {} {
546 $self instvar audioDevice_
547 return [$audioDevice_ get_output_gain]
548 }
549
550 AudioAgent public is_active {} {
551 $self instvar audioDevice_
552 return [$audioDevice_ is_active]
553 }
554
555 AudioAgent public clear_active {} {
556 $self instvar audioDevice_
557 $audioDevice_ clear_active
558 }
559
560 AudioAgent public select_format { fmt BlksPerPkt } {
561 $self instvar audioDevice_
562 $audioDevice_ select_format $fmt $BlksPerPkt
563 }
564
565 AudioAgent public set_silence_thresh thresh {
566 $self instvar audioDevice_
567 $audioDevice_ set_silence_thresh $thresh
568 }
569
570 AudioAgent public release {} {
571 $self instvar audioDevice_
572 $audioDevice_ release
573 }
574
575 AudioAgent public obtain {} {
576 $self instvar audioDevice_
577 $audioDevice_ obtain
578 }
579
580 AudioAgent instproc unix_time {} {
581 $self instvar audioDevice_
582 set controller [$audioDevice_ get_controller]
583 if { $controller == 0 } {
584 return 0
585 }
586 return [$controller unix_time]
587 }
588
589 AudioAgent instproc ntp_time {} {
590 $self instvar audioDevice_
591 set controller [$audioDevice_ get_controller]
592 if { $controller == 0 } {
593 return 0
594 }
595 return [$controller ntp_time]
596 }
597
598
599
600 #
601 # The AudioStream class, basically it is the sound card's abstraction
602 #
603
604 Class AudioStream -configuration {
605 inputGain 32
606 outputGain 180
607 maxPlayout 6
608 mikeAGCLevel 0
609 speakerAGCLevel 0
610 echoSuppressTime 400
611 }
612
613 #------------------------------------------------------------------
614 # Method:
615 # AudioStream init
616 # Description:
617 #
618 # sets up the basic variables are then tries to find
619 # and open the sound card, then give it default settings.
620 #
621 AudioStream public init { srcid session sampRate use16bitPCM useStereo {deviceName {}} } {
622 $self instvar bufferPool_ session_ silenceThresh_ audioTest_ sampleRate_ use16bitPCM_ useStereo_
623
624 set silenceThresh_ 20
625 set audioTest_ none
626 set session_ $session
627 set sampleRate_ $sampRate
628 set use16bitPCM_ $use16bitPCM
629 set useStereo_ $useStereo
630
631 $self compute_controller_defaults
632 #
633 # FIXME current behavior is that agent always opens the underlying
634 # device. The actual physical device may not be present or
635 # might not open, but in any event, the under C++ device object
636 # is created and initialized.
637 #
638
639 # FIXME - shouldn't session and AudioAgent share the same buffer pool?
640 if ![info exists bufferPool_] {
641 set bufferPool_ [new BufferPool/RTP]
642 $bufferPool_ srcid $srcid
643 }
644
645 # try to open the device
646 set devList [$self device_list]
647 if { $devList == "" } {
648 #FIXME
649 $self fatal "no suitable audio device found."
650 }
651
652 if { $deviceName != "" } {
653 set d $deviceName
654 } else {
655 foreach d $devList {
656 if { "$d" != "AF" } {
657 break
658 } elseif [$self yesno useAF] {
659 break
660 }
661 }
662 }
663 $self open_device $d
664 # choose a reasonable default
665 $self select_format PCM 2
666 # initially mute input/mike and unmute output/speaker
667 $self set_input_mute 1
668 $self set_output_mute 0
669 $self set_output_gain 5
670 if ![$self have_audio] {
671 $self obtain
672 }
673 }
674
675 #------------------------------------------------------------------
676 # Method:
677 # AudioStream get_controller
678 # Description:
679 #
680 AudioStream public get_controller { } {
681 $self instvar controller_
682 if [info exists controller_] {
683 return $controller_
684 }
685 return 0
686 }
687
688
689 #------------------------------------------------------------------
690 # Method:
691 # AudioStream destroy
692 # Description:
693 # Close everything
694 #
695 AudioStream public destroy {} {
696 $self instvar bufferPool_
697 if [info exists bufferPool_] {
698 delete $bufferPool_
699 }
700 $self close_device
701 }
702
703 #------------------------------------------------------------------
704 # Method:
705 # AudioStream compute_controller_defaults
706 # Description:
707 #
708 # Compute the max playout delay used by the (C++) audio controller
709 # from the configuration option "maxPlayout". Install the
710 # result in the AudioController class variable (so when we
711 # create the object instance, the C++ max_playout_ variable
712 # is appropriately initialized.
713 #
714 AudioStream private compute_controller_defaults {} {
715 $self instvar sampleRate_ use16bitPCM_ useStereo_
716
717 #FIXME compilation constants
718 set AUDIO_SPS [expr $sampleRate_ * [expr 1 + $useStereo_]]
719 set TALK_LEAD 4
720 set TALK_TAIL 32
721 #AUDIO_FRAMESIZE is the number of samples, not bytes
722 set AUDIO_FRAMESIZE 160
723 set SS_GRANULARITY 1440
724
725 set v [expr [$self get_option maxPlayout] * $AUDIO_SPS]
726 if [expr $v < ($TALK_LEAD + $TALK_TAIL + 2) * $AUDIO_FRAMESIZE] {
727 set w [expr (($TALK_LEAD + $TALK_TAIL + 2) * \
728 $AUDIO_FRAMESIZE + $AUDIO_SPS - 1) / $AUDIO_SPS]
729 puts stderr "max playout delay $v too short - using $w sec"
730 set v [expr ($TALK_LEAD + $TALK_TAIL + 2) * $AUDIO_FRAMESIZE]
731 }
732 set v [expr ($v + ($SS_GRANULARITY - 1)) / $SS_GRANULARITY]
733 set v [expr $v * $SS_GRANULARITY / $AUDIO_FRAMESIZE]
734 AudioController set max_playout_ $v
735 AudioController set echo_suppress_time_ [expr \
736 [$self get_option echoSuppressTime] / 20 * $AUDIO_FRAMESIZE]
737 }
738
739
740
741 #------------------------------------------------------------------
742 # Method:
743 # AudioStream select_format
744 # Description:
745 #
746 # Set the audio format used by the underlying audio compression
747 # algorithm. Note that this only affects outgoing audio.
748 # <i>fmt</i> is a string that represents the coding
749 # scheme and may be any of the values returned by
750 # the get_compression_formats method. (FIXME need to implement)
751 # <p>
752 # <i>blksPerPkt</i> specifies the number of audio blocks
753 # to include in each packet. Typically 2 to 4 audio blocks are
754 # included in each packet. Making this number larger decreases
755 # the per-packet network and system overhead, but increases
756 # latency since the audio sub-system has to wait longer before
757 # sending a packet. Likewise, making it smaller increases interactive
758 # performance at the cost of performance overhead.
759 #
760 AudioStream public select_format { fmt blksPerPkt } {
761 $self instvar encoder_ bufferPool_ session_ \
762 controller_ blksPerPkt_
763 if [info exists encoder_] {
764 delete $encoder_
765 }
766 set encoder_ [new Module/AudioEncoder/$fmt]
767 set blksPerPkt_ $blksPerPkt
768 if [info exists controller_] {
769 $controller_ encoder $encoder_
770 $controller_ blocks-per-packet $blksPerPkt
771 }
772 if { "$encoder_" == "" } {
773 return -1
774 }
775 $encoder_ target $session_
776 $encoder_ buffer-pool $bufferPool_
777
778 return 0
779 }
780
781
782 #------------------------------------------------------------------
783 # Method:
784 # AudioStream device_list
785 # Description:
786 #
787 # Return a list of OTcl class that correspond to devices
788 # that are supported in this version of the mash platform.
789 #
790 AudioStream public device_list {} {
791 if ![TclObject is-class Audio] {
792 return ""
793 }
794 set temp [Audio info subclass]
795 set ix [lsearch -exact $temp Audio/RealAudioVirtualDevice]
796 if {$ix >= 0} {
797 return [lreplace $temp $ix $ix]
798 } else {
799 return $temp
800 }
801 }
802
803 #------------------------------------------------------------------
804 # Method:
805 # AudioStream bind_transducer
806 # Description:
807 #
808 # Arrange for the audio agent to activate the transducer object named
809 # by <i>o</i> whenenter there is input or output, according to
810 # <i>which</i> (either "input" or "output").
811 #
812 AudioStream public bind_transducer { which o } {
813 $self instvar meter_ controller_
814 set meter_($which) $o
815 if [info exists controller_] {
816 $controller_ $which-meter $o
817 }
818 }
819
820
821 #
822 # Create an audio device to manage the underlying hardware codec
823 # and deallocate state that depends on the old device (if it
824 # was open). <i>dev</i> is the OTcl class name of the Audio
825 # device that is created.
826 #
827 AudioStream private open_device dev {
828 $self instvar audio_ meter_ controller_ silenceThresh_ sampleRate_ use16bitPCM_ useStereo_
829 if [info exists audio_] {
830 delete $audio_
831 unset audio_
832 }
833 if [info exists controller_] {
834 delete $controller_
835 unset controller_
836 }
837 set audio_ [new $dev]
838 if { $dev == "Audio/OSS" } {
839 $audio_ set_sample_rate $sampleRate_
840 if { $use16bitPCM_ == 1 } {
841 $audio_ useRaw16PCM
842 }
843 if { $useStereo_ == 1 } {
844 puts "setting card to do stereo sound"
845 $audio_ useStereo
846 }
847 }
848 $self instvar gain_
849 #FIXME
850 set names [$self get_input_ports]
851 foreach port $names {
852 if {[$self get_option $port\Gain]!={}} {
853 set gain_($port) [$self get_option $port\Gain]
854 } else {
855 set gain_($port) [$self get_option inputGain]
856 }
857 }
858 set names [$self get_output_ports]
859 foreach port $names {
860 if {[$self get_option $port\Gain]!={}} {
861 set gain_($port) [$self get_option $port\Gain]
862 } else {
863 set gain_($port) [$self get_option outputGain]
864 }
865 $self set_speakerphone $port [$self get_option $port\Mode]
866 }
867
868 #FIXME
869 $self instvar port_
870 set port_(input) ""
871 set port_(output) ""
872 }
873
874
875 #------------------------------------------------------------------
876 # Method:
877 # AudioStream install_controller
878 # Description:
879 #
880 # Create, install, and initialize the AudioController object. This
881 # operation must be deferred until after the audio device has been
882 # ``obtained'' and opened because we need to determine whether the
883 # underlying hardware operates in full- or half-duplex mode and create
884 # a controller object that is compatible with this mode.
885 #
886 AudioStream private install_controller {} {
887 $self instvar audio_ encoder_ blksPerPkt_ silenceThresh_ meter_ \
888 controller_
889 if [$audio_ set duplex_] {
890 set duplex FullDuplex
891 } else {
892 set duplex HalfDuplex
893 #
894 # If we're half duplex, try obtaining the device
895 # (i.e., actually opening it) to see if it's full
896 # duplex. Or, if an user option says to force
897 # the use of full duplex then do that.
898 #
899 if ![$self have_audio] {
900 $self obtain 0
901 }
902 if { [$self yesno forceFullDuplex] || [$audio_ set duplex_] } {
903 set duplex FullDuplex
904 }
905 }
906 set controller_ [new AudioController/$duplex]
907 $controller_ audio $audio_
908 if [info exists meter_(input)] {
909 $controller_ input-meter $meter_(input)
910 }
911 if [info exists meter_(output)] {
912 $controller_ output-meter $meter_(output)
913 }
914 $controller_ silence-thresh $silenceThresh_
915 if [info exists encoder_] {
916 $controller_ encoder $encoder_
917 $controller_ blocks-per-packet $blksPerPkt_
918 }
919 $controller_ agc-input [$self get_option mikeAGCLevel]
920 $controller_ agc-output [$self get_option speakerAGCLevel]
921 $controller_ silence-thresh [$self get_option silenceThresh]
922 }
923
924 #------------------------------------------------------------------
925 # Method:
926 # AudioStream obtain
927 # Description:
928 #
929 # Attempt to open and intialize the underlying audio hardware.
930 # The creation of an AudioAgent does not immediately mean that
931 # the underlying audio hardware is opened and initialized because
932 # multiple applications might want to simultaneously access the
933 # device but many such devices cannot be shared because of
934 # the operating system's contraints on the API. Thus, we have
935 # explicit methods for obtaining and releasing the underlying
936 # hardware resources. After <i>obtain</i> is called, the <i>haveaudio</i>
937 # method can be queried to see if the device was successfully
938 # opened. Once obtained, the device can be released with the
939 # AudioAgent::release.
940 #
941 AudioStream public obtain { {should_install_controller 1} } {
942 $self instvar audio_ controller_
943 $audio_ obtain
944
945 # check if a controller exists; make sure that the controller
946 # is of the correct "duplex" mode
947 if [info exists controller_] {
948 set cl [$controller_ info class]
949 if [string match *FullDuplex* $cl] {
950 set duplex 1
951 } else {
952 set duplex 0
953 }
954
955 if { [$audio_ set duplex_] != $duplex } {
956 delete $controller_
957 unset controller_
958 }
959 }
960
961 if { ![info exists controller_] && $should_install_controller } {
962 $self install_controller
963 }
964 }
965
966 #------------------------------------------------------------------
967 # Method:
968 # AudioStream release
969 # Description:
970 #
971 # Release the underlying audio hardware without deallocating
972 # the AudioAgent object. This allow
973 # The creation of an AudioAgent does not immediately mean that
974 # the underlying audio hardware is opened and initialized because
975 # multiple applications might want to simultaneously access the
976 # device but many such devices cannot be shared because of
977 # the operating system's contraints on the API. Thus, we have
978 # explicit methods for obtaining and releasing the underlying
979 # hardware resources. After <i>obtain</i> is called, the <i>haveaudio</i>
980 # method can be queried to see if the device was successfully
981 # opened. Once obtained, the device can be released with the
982 # AudioAgent::release.
983 #
984 AudioStream public release {} {
985 # audio_ represents the audio device
986 [$self set audio_] release
987 }
988
989 #------------------------------------------------------------------
990 # Method:
991 # AudioStream set_silence_thresh
992 # Description:
993 #
994 # Set the threshold for the silence suppression algorithm
995 # used by the AudioController to <i>thresh</i>.
996 # FIXME: need to define units. Should be probably be dB.
997 #
998 AudioStream public set_silence_thresh thresh {
999 $self instvar silenceThresh_ controller_
1000 set silenceThresh_ $thresh
1001 if [info exists controller_] {
1002 $controller_ silence-thresh $silenceThresh_
1003 }
1004 }
1005
1006 #------------------------------------------------------------------
1007 # Method:
1008 # AudioStream have_audio
1009 # Description:
1010 # Return true iff the AudioAgent has opened and currently ``owns''
1011 # the underlying hardware audio device.
1012 #
1013 AudioStream public have_audio {} {
1014 $self instvar audio_
1015 if [info exists audio_] {
1016 return [$audio_ have]
1017 }
1018 return 0
1019 }
1020
1021 #------------------------------------------------------------------
1022 # Method:
1023 # AudioStream set_input_mute
1024 # Description:
1025 #
1026 # Set the mute attribute on the input port of the underlying
1027 # audio device (i.e., the mike, linein, etc) as indicated
1028 # by <i>val</i>. When muted, audio samples from the input
1029 # device are ignored and no network traffic is generated.
1030 #
1031 AudioStream public set_input_mute val {
1032 $self instvar audio_
1033 $audio_ set_input_mute $val
1034 }
1035
1036 #------------------------------------------------------------------
1037 # Method:
1038 # AudioStream set_output_mute
1039 # Description:
1040 #
1041 # Set the mute attribute on the output port of the underlying
1042 # audio device (i.e., the speaker, headphones, etc) as indicated
1043 # by <i>val</i>. When muted, audio packets from the network
1044 # are processed but not output to the audio hardware.
1045 #
1046 AudioStream public set_output_mute val {
1047 $self instvar audio_
1048 $audio_ set_output_mute $val
1049 }
1050
1051 #------------------------------------------------------------------
1052 # Method:
1053 # AudioStream get_input_ports
1054 # Description:
1055 #
1056 # Return the list of available input ports supported
1057 # by the underlying audio hardware. Each name in the
1058 # list is unique so names can be used as keys into a table.
1059 # The list consists of a sequence of names,
1060 # where the name is the nick name of the port
1061 # (e.g., "mike").
1062 #
1063 AudioStream public get_input_ports {} {
1064 $self instvar audio_
1065 return [string tolower [$audio_ get_input_ports]]
1066 }
1067
1068 #------------------------------------------------------------------
1069 # Method:
1070 # AudioStream get_output_ports
1071 # Description:
1072 #
1073 # Return the list of available output ports supported
1074 # by the underlying audio hardware. Each name in the
1075 # list is unique so names can be used as keys into a table.
1076 # The list consists of a sequence of names,
1077 # where the name is the nick name of the port
1078 # (e.g., "speaker").
1079 #
1080 AudioStream public get_output_ports {} {
1081 $self instvar audio_
1082 return [string tolower [$audio_ get_output_ports]]
1083 }
1084
1085 #------------------------------------------------------------------
1086 # Method: