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
29 #import dbus.mainloop.glib
32 class Recorder(zavai.Resource):
33 def __init__(self, registry):
34 super(Recorder, self).__init__()
35 self.registry = registry
41 def start(self, filename):
42 if self.recorder is not None: return
44 self.registry.resource("audio").connect("audio", self)
46 self.recorder = subprocess.Popen(
47 ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", filename])
50 if self.recorder is None: return
52 os.kill(self.recorder.pid, signal.SIGINT)
56 self.registry.resource("audio").disconnect("audio", self)
60 # - hook into the headset plugged/unplugged event
61 # - if unplugged, turn on handset microphone
62 # - if plugged, redo headest mixer settings
63 class Audio(zavai.Service):
64 "Handle mixer settings, audio recording and headset button presses"
65 def __init__(self, registry):
66 super(Audio, self).__init__(["audio", "button", "jack"])
68 conf = registry.resource("conf")
69 self.saved_scenario = os.path.join(conf.homedir, "audiomap.state")
72 self.input_watch = None
77 # Set mixer to record from headset and handle headset button
78 self.save_scenario(self.saved_scenario)
79 self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
81 # This is a work-around because I have not found a way to query for the
82 # current headset state, I can only know when it changes. So in my
83 # system I configured oeventsd with a rule to touch this file when the
84 # headset is plugged in, and remove the file when it's plugged out.
85 if os.path.exists("/tmp/has_headset"):
86 self.mixer_for_headset(force = True)
88 self.mixer_for_handset(force = True)
90 # Watch the event device
91 self.input_fd = open("/dev/input/event4", "rb")
92 self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
93 self.last_button_press = 0
95 # #self.mixer_set("DAPM Handset Mic", "mute")
96 # #self.mixer_set("DAPM Headset Mic", "unmute")
97 # #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
98 # #self.mixer_set("ALC Mixer Mic1", "cap")
99 # #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
101 # self.recorder = None
102 # self.basename = None
105 # Stop watching the event device
106 if self.input_fd is None:
108 gobject.source_remove(self.input_watch)
109 self.input_watch = None
110 self.input_fd.close()
113 # Restore mixer settings
114 self.load_scenario(self.saved_scenario)
116 def on_input_data(self, source, condition):
117 buf = self.input_fd.read(16)
118 ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
119 if type == 1 and code == 119:
120 if self.last_button_press + 1 < ts1:
121 self.last_button_press = ts1
125 zavai.info("Headset button release")
126 self.notify("button", False)
128 zavai.info("Headset button press")
129 self.notify("button", True)
130 elif type == 5 and code == 2:
132 zavai.info("Headset plugged in")
133 self.mixer_for_headset()
134 self.notify("jack", True)
136 zavai.info("Headset plugged out")
137 self.mixer_for_handset()
138 self.notify("jack", False)
141 def mixer_for_headset(self, force=False):
142 if not force and self.has_headset: return
143 info("Setting mixer for headset")
144 # TODO: find out how to disable the handset microphone: this does not
145 # seem to be sufficient
147 ("DAPM Handset Mic", "mute"),
148 ("DAPM Headset Mic", "unmute"),
149 ("Left Mixer Sidetone Playback Sw", "unmute"),
150 ("ALC Mixer Mic1", "cap"),
151 ("Amp Spk", "mute") # We don't need the phone playing what we say
153 self.has_headset = True
155 def mixer_for_handset(self, force=False):
156 if not force and not self.has_headset: return
157 info("Setting mixer for handset")
159 ("DAPM Handset Mic", "unmute"),
160 ("DAPM Headset Mic", "mute"),
161 ("Left Mixer Sidetone Playback Sw", "mute"),
162 ("ALC Mixer Mic1", "cap"),
163 ("Amp Spk", "mute") # We don't need the phone playing what we say
165 self.has_headset = False
167 # def set_basename(self, basename):
168 # self.basename = basename
170 # def start_levels(self):
171 # self.recorder = subprocess.Popen(
172 # ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
174 def save_scenario(self, name):
175 res = subprocess.call(["alsactl", "store", "-f", name])
177 raise RuntimeError("Saving audio scenario to '%s' failed" % name)
179 def load_scenario(self, name):
180 res = subprocess.call(["alsactl", "restore", "-f", name])
182 raise RuntimeError("Loading audio scenario '%s' failed with error %d" % (name, res))
184 def mixer_set(self, name, *args):
185 args = map(str, args)
186 res = subprocess.call(["amixer", "-q", "set", name] + args)
188 raise RuntimeError("Setting mixer '%s' to %s failed with error %d" % (name, " ".join(args), res))
190 def mixer_set_many(self, *args):
191 """Perform many mixer set operations via amixer --stdin"""
192 proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
195 cmd_input.append("sset " + repr(k) + " " + repr(v))
196 (out, err) = proc.communicate(input="\n".join(cmd_input))
199 raise RuntimeError("Setting mixer failed with error %d" % res)
203 # """Hub that manages all the various resources that we use, and initiates
205 # def __init__(self, bus = None):
207 # self.waiting_for_fix = False
208 # self.basename = None
209 # self.recorder = None
212 # self.gps_monitor = None
214 # self.last_pos = None
216 # def shutdown(self):
218 # if self.audio is not None:
221 # # Close waypoints file
222 # if self.gpx is not None:
225 # # Stop the GPS monitor
226 # if self.gps_monitor:
227 # self.gps_monitor.stop()
230 # if self.gps is not None:
234 # self.audio = Audio()
235 # self.audio.start_levels()
238 # self.audio = Audio(self.make_waypoint)
240 # # Get a fix and start recording
241 # if not self.gps.wait_for_fix(self.start_recording):
242 # self.gps_monitor = GPSMonitor(self.gps)
243 # self.gps_monitor.start()
248 # self.gps_monitor = GPSMonitor(self.gps)
249 # self.gps_monitor.start()
251 # def start_recording(self):
252 # if self.gps_monitor:
253 # self.gps_monitor.stop()
254 # self.gps_monitor = None
260 # gpstime = self.gps.gps_time.GetTime()
261 # subprocess.call(["date", "-s", "@%d" % gpstime])
262 # subprocess.call(["hwclock", "--systohc"])
264 # # Compute basename for output files
265 # self.basename = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(gpstime))
266 # self.basename = os.path.join(AUDIODIR, self.basename)
268 # # Start recording the GPX track
269 # self.gpx = GPX(self.basename)
270 # self.gps.track_position(self.on_position_changed)
272 # # Start recording in background forking arecord
273 # self.audio.set_basename(self.basename)
274 # self.audio.start_recording()
276 # def on_position_changed(self, fields, tstamp, lat, lon, alt):
277 # self.last_pos = (fields, tstamp, lat, lon, alt)
279 # self.gpx.trackpoint(tstamp, lat, lon, alt)
281 # def make_waypoint(self):
282 # if self.gpx is None:
284 # if self.last_pos is None:
285 # self.last_pos = self.gps.gps_position.GetPosition()
286 # (fields, tstamp, lat, lon, alt) = self.last_pos
287 # self.gpx.waypoint(tstamp, lat, lon, alt)
288 # info("Making waypoint at %s: %f, %f, %f" % (
289 # time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(tstamp)), lat, lon, alt))