1 # audio - zavai audio resource
3 # Copyright (C) 2009 Enrico Zini <enrico@enricozini.org>
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.
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.
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
30 class Recorder(zavai.Resource):
31 def __init__(self, registry):
32 super(Recorder, self).__init__()
33 self.registry = registry
39 def start(self, filename):
40 if self.recorder is not None: return
42 self.registry.resource("audio").connect("audio", self)
44 self.recorder = subprocess.Popen(
45 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", filename])
48 if self.recorder is None: return
50 os.kill(self.recorder.pid, signal.SIGINT)
54 self.registry.resource("audio").disconnect("audio", self)
58 # - hook into the headset plugged/unplugged event
59 # - if unplugged, turn on handset microphone
60 # - if plugged, redo headest mixer settings
61 class Audio(zavai.Service):
62 "Handle mixer settings, audio recording and headset button presses"
63 def __init__(self, registry, name):
64 super(Audio, self).__init__(["audio", "button", "jack"])
66 conf = registry.resource("conf")
67 self.saved_scenario = os.path.join(conf.homedir, "audiomap.state")
70 self.input_watch = None
75 # Set mixer to record from headset and handle headset button
76 self.save_scenario(self.saved_scenario)
77 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
79 # This is a work-around because I have not found a way to query for the
80 # current headset state, I can only know when it changes. So in my
81 # system I configured oeventsd with a rule to touch this file when the
82 # headset is plugged in, and remove the file when it's plugged out.
83 if os.path.exists("/tmp/has_headset"):
84 self.mixer_for_headset(force = True)
86 self.mixer_for_handset(force = True)
88 # Watch the event device
89 self.input_fd = open("/dev/input/event4", "rb")
90 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
91 self.last_button_press = 0
93 # #self.mixer_set("DAPM Handset Mic", "mute")
94 # #self.mixer_set("DAPM Headset Mic", "unmute")
95 # #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
96 # #self.mixer_set("ALC Mixer Mic1", "cap")
97 # #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
99 # self.recorder = None
100 # self.basename = None
103 # Stop watching the event device
104 if self.input_fd is None:
106 gobject.source_remove(self.input_watch)
107 self.input_watch = None
108 self.input_fd.close()
111 # Restore mixer settings
112 self.load_scenario(self.saved_scenario)
114 def on_input_data(self, source, condition):
115 buf = self.input_fd.read(16)
116 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
117 if type == 1 and code == 119:
118 if self.last_button_press + 1 < ts1:
119 self.last_button_press = ts1
123 zavai.info("Headset button release")
124 self.notify("button", False)
126 zavai.info("Headset button press")
127 self.notify("button", True)
128 elif type == 5 and code == 2:
130 zavai.info("Headset plugged in")
131 self.mixer_for_headset()
132 self.notify("jack", True)
134 zavai.info("Headset plugged out")
135 self.mixer_for_handset()
136 self.notify("jack", False)
139 def mixer_for_headset(self, force=False):
140 if not force and self.has_headset: return
141 zavai.info("Setting mixer for headset")
142 # TODO: find out how to disable the handset microphone: this does not
143 # seem to be sufficient
145 ("DAPM Handset Mic", "mute"),
146 ("DAPM Headset Mic", "unmute"),
147 ("Left Mixer Sidetone Playback Sw", "unmute"),
148 ("ALC Mixer Mic1", "cap"),
149 ("Amp Spk", "mute") # We don't need the phone playing what we say
151 self.has_headset = True
153 def mixer_for_handset(self, force=False):
154 if not force and not self.has_headset: return
155 zavai.info("Setting mixer for handset")
157 ("DAPM Handset Mic", "unmute"),
158 ("DAPM Headset Mic", "mute"),
159 ("Left Mixer Sidetone Playback Sw", "mute"),
160 ("ALC Mixer Mic1", "cap"),
161 ("Amp Spk", "mute") # We don't need the phone playing what we say
163 self.has_headset = False
165 # def set_basename(self, basename):
166 # self.basename = basename
168 # def start_levels(self):
169 # self.recorder = subprocess.Popen(
170 # ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
172 def save_scenario(self, name):
173 res = subprocess.call(["alsactl", "store", "-f", name])
175 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
177 def load_scenario(self, name):
178 res = subprocess.call(["alsactl", "restore", "-f", name])
180 raise RuntimeError("Loading audio scenario '%s' failed with error %d" % (name, res))
182 def mixer_set(self, name, *args):
183 args = map(str, args)
184 res = subprocess.call(["amixer", "-q", "set", name] + args)
186 raise RuntimeError("Setting mixer '%s' to %s failed with error %d" % (name, " ".join(args), res))
188 def mixer_set_many(self, *args):
189 """Perform many mixer set operations via amixer --stdin"""
190 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
193 cmd_input.append("sset " + repr(k) + " " + repr(v))
194 (out, err) = proc.communicate(input="\n".join(cmd_input))
197 raise RuntimeError("Setting mixer failed with error %d" % res)
201 # """Hub that manages all the various resources that we use, and initiates
203 # def __init__(self, bus = None):
205 # self.waiting_for_fix = False
206 # self.basename = None
207 # self.recorder = None
210 # self.gps_monitor = None
212 # self.last_pos = None
214 # def shutdown(self):
216 # if self.audio is not None:
219 # # Close waypoints file
220 # if self.gpx is not None:
223 # # Stop the GPS monitor
224 # if self.gps_monitor:
225 # self.gps_monitor.stop()
228 # if self.gps is not None:
232 # self.audio = Audio()
233 # self.audio.start_levels()
236 # self.audio = Audio(self.make_waypoint)
238 # # Get a fix and start recording
239 # if not self.gps.wait_for_fix(self.start_recording):
240 # self.gps_monitor = GPSMonitor(self.gps)
241 # self.gps_monitor.start()
246 # self.gps_monitor = GPSMonitor(self.gps)
247 # self.gps_monitor.start()
249 # def start_recording(self):
250 # if self.gps_monitor:
251 # self.gps_monitor.stop()
252 # self.gps_monitor = None
258 # gpstime = self.gps.gps_time.GetTime()
259 # subprocess.call(["date", "-s", "@%d" % gpstime])
260 # subprocess.call(["hwclock", "--systohc"])
262 # # Compute basename for output files
263 # self.basename = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(gpstime))
264 # self.basename = os.path.join(AUDIODIR, self.basename)
266 # # Start recording the GPX track
267 # self.gpx = GPX(self.basename)
268 # self.gps.track_position(self.on_position_changed)
270 # # Start recording in background forking arecord
271 # self.audio.set_basename(self.basename)
272 # self.audio.start_recording()
274 # def on_position_changed(self, fields, tstamp, lat, lon, alt):
275 # self.last_pos = (fields, tstamp, lat, lon, alt)
277 # self.gpx.trackpoint(tstamp, lat, lon, alt)
279 # def make_waypoint(self):
280 # if self.gpx is None:
282 # if self.last_pos is None:
283 # self.last_pos = self.gps.gps_position.GetPosition()
284 # (fields, tstamp, lat, lon, alt) = self.last_pos
285 # self.gpx.waypoint(tstamp, lat, lon, alt)
286 # info("Making waypoint at %s: %f, %f, %f" % (
287 # time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(tstamp)), lat, lon, alt))