Don't call stop uselessly at shutdown
[gregoa/zavai.git] / zavai / audio.py
1 # audio - zavai audio resource
2 #
3 # Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19 import zavai
20 import struct
21 import gobject
22 #import sys
23 #import os
24 #import os.path
25 #import time
26 #import signal
27 #import optparse
28 #import dbus
29 #import dbus.mainloop.glib
30 #import subprocess
31
32
33 # TODO:
34 #  - hook into the headset plugged/unplugged event
35 #  - if unplugged, turn on handset microphone
36 #  - if plugged, redo headest mixer settings
37 class Audio(zavai.Service):
38     "Handle mixer settings, audio recording and headset button presses"
39     def __init__(self, registry):
40         super(Audio, self).__init__(["audio", "button", "jack"])
41
42         conf = registry.resource("conf")
43         self.saved_scenario = os.path.join(conf.homedir, "audiomap.state")
44
45         self.input_fd = None
46         self.input_watch = None
47
48
49     def start(self):
50         # Setup the mixer
51         # Set mixer to record from headset and handle headset button
52         self.save_scenario(self.saved_scenario)
53         self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
54
55         # This is a work-around because I have not found a way to query for the
56         # current headset state, I can only know when it changes. So in my
57         # system I configured oeventsd with a rule to touch this file when the
58         # headset is plugged in, and remove the file when it's plugged out.
59         if os.path.exists("/tmp/has_headset"):
60             self.mixer_for_headset(force = True)
61         else:
62             self.mixer_for_handset(force = True)
63
64         # Watch the event device
65         self.input_fd = open("/dev/input/event4", "rb")
66         self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
67         self.last_button_press = 0
68
69 #        #self.mixer_set("DAPM Handset Mic", "mute")
70 #        #self.mixer_set("DAPM Headset Mic", "unmute")
71 #        #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
72 #        #self.mixer_set("ALC Mixer Mic1", "cap")
73 #        #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
74 #
75 #        self.recorder = None
76 #        self.basename = None
77
78     def stop(self):
79         # Stop watching the event device
80         if self.input_fd is None:
81             return
82         gobject.source_remove(self.input_watch)
83         self.input_watch = None
84         self.input_fd.close()
85         self.input_fd = None
86
87 #        if self.recorder is not None:
88 #            os.kill(self.recorder.pid, signal.SIGINT)
89 #            self.recorder.wait()
90
91         # Restore mixer settings
92         self.load_scenario(self.saved_scenario)
93
94     def on_input_data(self, source, condition):
95         buf = self.input_fd.read(16)
96         ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
97         if type == 1 and code == 119:
98             if self.last_button_press + 1 < ts1:
99                 self.last_button_press = ts1
100             else:
101                 return True
102             if value:
103                 zavai.info("Headset button release")
104                 self.notify("button", False)
105             else:
106                 zavai.info("Headset button press")
107                 self.notify("button", True)
108         elif type == 5 and code == 2:
109             if value:
110                 zavai.info("Headset plugged in")
111                 self.mixer_for_headset()
112                 self.notify("jack", True)
113             else:
114                 zavai.info("Headset plugged out")
115                 self.mixer_for_handset()
116                 self.notify("jack", False)
117         return True
118
119     def mixer_for_headset(self, force=False):
120         if not force and self.has_headset: return
121         info("Setting mixer for headset")
122         # TODO: find out how to disable the handset microphone: this does not
123         # seem to be sufficient
124         self.mixer_set_many(
125                 ("DAPM Handset Mic", "mute"),
126                 ("DAPM Headset Mic", "unmute"),
127                 ("Left Mixer Sidetone Playback Sw", "unmute"),
128                 ("ALC Mixer Mic1", "cap"),
129                 ("Amp Spk", "mute") # We don't need the phone playing what we say
130         )
131         self.has_headset = True
132
133     def mixer_for_handset(self, force=False):
134         if not force and not self.has_headset: return
135         info("Setting mixer for handset")
136         self.mixer_set_many(
137                 ("DAPM Handset Mic", "unmute"),
138                 ("DAPM Headset Mic", "mute"),
139                 ("Left Mixer Sidetone Playback Sw", "mute"),
140                 ("ALC Mixer Mic1", "cap"),
141                 ("Amp Spk", "mute") # We don't need the phone playing what we say
142         )
143         self.has_headset = False
144
145 #    def set_basename(self, basename):
146 #        self.basename = basename
147 #
148 #    def start_recording(self):
149 #        if self.basename is None:
150 #            raise RuntimeError("Recording requested but basename not set")
151 #        self.recorder = subprocess.Popen(
152 #            ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
153 #
154 #    def start_levels(self):
155 #        self.recorder = subprocess.Popen(
156 #            ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
157
158     def save_scenario(self, name):
159         res = subprocess.call(["alsactl", "store", "-f", name])
160         if res != 0:
161             raise RuntimeError("Saving audio scenario to '%s' failed" % name)
162
163     def load_scenario(self, name):
164         res = subprocess.call(["alsactl", "restore", "-f", name])
165         if res != 0:
166             raise RuntimeError("Loading audio scenario '%s' failed with error %d" % (name, res))
167
168     def mixer_set(self, name, *args):
169         args = map(str, args)
170         res = subprocess.call(["amixer", "-q", "set", name] + args)
171         if res != 0:
172             raise RuntimeError("Setting mixer '%s' to %s failed with error %d" % (name, " ".join(args), res))
173
174     def mixer_set_many(self, *args):
175         """Perform many mixer set operations via amixer --stdin"""
176         proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
177         cmd_input = []
178         for k, v in args:
179             cmd_input.append("sset " + repr(k) + " " + repr(v))
180         (out, err) = proc.communicate(input="\n".join(cmd_input))
181         res = proc.wait()
182         if res != 0:
183             raise RuntimeError("Setting mixer failed with error %d" % res)
184
185
186 #class Hub:
187 #    """Hub that manages all the various resources that we use, and initiates
188 #    operations."""
189 #    def __init__(self, bus = None):
190 #        self.bus = bus
191 #        self.waiting_for_fix = False
192 #        self.basename = None
193 #        self.recorder = None
194 #        self.gpx = None
195 #        self.gps = None
196 #        self.gps_monitor = None
197 #        self.audio = None
198 #        self.last_pos = None
199 #
200 #    def shutdown(self):
201 #        # Stop recording
202 #        if self.audio is not None:
203 #            self.audio.close()
204 #
205 #        # Close waypoints file
206 #        if self.gpx is not None:
207 #            self.gpx.close()
208 #
209 #        # Stop the GPS monitor
210 #        if self.gps_monitor:
211 #            self.gps_monitor.stop()
212 #
213 #        # Release the GPS
214 #        if self.gps is not None:
215 #            self.gps.close()
216 #
217 #    def levels(self):
218 #        self.audio = Audio()
219 #        self.audio.start_levels()
220 #
221 #    def record(self):
222 #        self.audio = Audio(self.make_waypoint)
223 #        self.gps = GPS()
224 #        # Get a fix and start recording
225 #        if not self.gps.wait_for_fix(self.start_recording):
226 #            self.gps_monitor = GPSMonitor(self.gps)
227 #            self.gps_monitor.start()
228 #
229 #    def monitor(self):
230 #        self.audio = None
231 #        self.gps = GPS()
232 #        self.gps_monitor = GPSMonitor(self.gps)
233 #        self.gps_monitor.start()
234 #
235 #    def start_recording(self):
236 #        if self.gps_monitor:
237 #            self.gps_monitor.stop()
238 #            self.gps_monitor = None
239 #
240 #        if not self.audio:
241 #            return
242 #
243 #        # Sync system time
244 #        gpstime = self.gps.gps_time.GetTime()
245 #        subprocess.call(["date", "-s", "@%d" % gpstime])
246 #        subprocess.call(["hwclock", "--systohc"])
247 #
248 #        # Compute basename for output files
249 #        self.basename = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(gpstime))
250 #        self.basename = os.path.join(AUDIODIR, self.basename)
251 #
252 #        # Start recording the GPX track
253 #        self.gpx = GPX(self.basename)
254 #        self.gps.track_position(self.on_position_changed)
255 #
256 #        # Start recording in background forking arecord
257 #        self.audio.set_basename(self.basename)
258 #        self.audio.start_recording()
259 #
260 #    def on_position_changed(self, fields, tstamp, lat, lon, alt):
261 #        self.last_pos = (fields, tstamp, lat, lon, alt)
262 #        if self.gpx:
263 #            self.gpx.trackpoint(tstamp, lat, lon, alt)
264 #
265 #    def make_waypoint(self):
266 #        if self.gpx is None:
267 #            return
268 #        if self.last_pos is None:
269 #            self.last_pos = self.gps.gps_position.GetPosition()
270 #        (fields, tstamp, lat, lon, alt) = self.last_pos
271 #        self.gpx.waypoint(tstamp, lat, lon, alt)
272 #        info("Making waypoint at %s: %f, %f, %f" % (
273 #            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(tstamp)), lat, lon, alt))