Started working on the Audio resource
authorEnrico Zini <enrico@enricozini.org>
Mon, 15 Jun 2009 15:18:46 +0000 (23:18 +0800)
committerEnrico Zini <enrico@enricozini.org>
Mon, 15 Jun 2009 15:18:46 +0000 (23:18 +0800)
src/zavai
zavai/__init__.py
zavai/audio.py [new file with mode: 0755]

index 93109994f6de7232288927912a94a15f7dac2891..2be32d4a13b2c16cb141dcd379ec35db1069746c 100755 (executable)
--- a/src/zavai
+++ b/src/zavai
@@ -66,6 +66,7 @@ registry.register(conf, "conf")
 registry.register_factory(zavai.Zavai, "app")
 registry.register_factory(zavai.GPS, "gps")
 registry.register_factory(zavai.GPX, "gpx")
+registry.register_factory(zavai.Audio, "audio")
 
 # Load plugins
 zavai.info("Loading plugins")
index a13a0b594df667b5c33907675e55560721818e47..6c4c82b6cf52d7919bd2eb3fbe875d228e6bcf59 100644 (file)
@@ -23,6 +23,7 @@ from registry import Registry, Resource, Service, get_parent, default_label
 from menu import Menu, MenuButton, LinkButton, ToggleButton
 from app import Zavai, Applet
 from gps import GPS, GPX
+from audio import Audio
 
 def warn(*args):
     import sys
diff --git a/zavai/audio.py b/zavai/audio.py
new file mode 100755 (executable)
index 0000000..37b483f
--- /dev/null
@@ -0,0 +1,273 @@
+# audio - zavai audio resource
+#
+# Copyright (C) 2009  Enrico Zini <enrico@enricozini.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import zavai
+import struct
+import gobject
+#import sys
+#import os
+#import os.path
+#import time
+#import signal
+#import optparse
+#import dbus
+#import dbus.mainloop.glib
+#import subprocess
+
+
+# TODO:
+#  - hook into the headset plugged/unplugged event
+#  - if unplugged, turn on handset microphone
+#  - if plugged, redo headest mixer settings
+class Audio(zavai.Service):
+    "Handle mixer settings, audio recording and headset button presses"
+    def __init__(self, registry):
+        super(Audio, self).__init__(["audio", "button", "jack"])
+
+        conf = registry.resource("conf")
+        self.saved_scenario = os.path.join(conf.homedir, "audiomap.state")
+
+        self.input_fd = None
+        self.input_watch = None
+
+
+    def start(self):
+        # Setup the mixer
+        # Set mixer to record from headset and handle headset button
+        self.save_scenario(self.saved_scenario)
+        self.load_scenario("/usr/share/openmoko/scenarios/voip-handset.state")
+
+        # This is a work-around because I have not found a way to query for the
+        # current headset state, I can only know when it changes. So in my
+        # system I configured oeventsd with a rule to touch this file when the
+        # headset is plugged in, and remove the file when it's plugged out.
+        if os.path.exists("/tmp/has_headset"):
+            self.mixer_for_headset(force = True)
+        else:
+            self.mixer_for_handset(force = True)
+
+        # Watch the event device
+        self.input_fd = open("/dev/input/event4", "rb")
+        self.input_watch = gobject.io_add_watch(self.input_fd.fileno(), gobject.IO_IN, self.on_input_data)
+        self.last_button_press = 0
+
+#        #self.mixer_set("DAPM Handset Mic", "mute")
+#        #self.mixer_set("DAPM Headset Mic", "unmute")
+#        #self.mixer_set("Left Mixer Sidetone Playback Sw", "unmute")
+#        #self.mixer_set("ALC Mixer Mic1", "cap")
+#        #self.mixer_set("Amp Spk", "mute") # We don't need the phone playing what we say
+#
+#        self.recorder = None
+#        self.basename = None
+
+    def stop(self):
+        # Stop watching the event device
+        if self.input_fd is None:
+            return
+        gobject.source_remove(self.input_watch)
+        self.input_watch = None
+        self.input_fd.close()
+        self.input_fd = None
+
+#        if self.recorder is not None:
+#            os.kill(self.recorder.pid, signal.SIGINT)
+#            self.recorder.wait()
+
+        # Restore mixer settings
+        self.load_scenario(self.saved_scenario)
+
+    def on_input_data(self, source, condition):
+        buf = self.input_fd.read(16)
+        ts1, ts2, type, code, value = struct.unpack("LLHHI", buf)
+        if type == 1 and code == 119:
+            if self.last_button_press + 1 < ts1:
+                self.last_button_press = ts1
+            else:
+                return True
+            if value:
+                zavai.info("Headset button release")
+                self.notify("button", False)
+            else:
+                zavai.info("Headset button press")
+                self.notify("button", True)
+        elif type == 5 and code == 2:
+            if value:
+                zavai.info("Headset plugged in")
+                self.mixer_for_headset()
+                self.notify("jack", True)
+            else:
+                zavai.info("Headset plugged out")
+                self.mixer_for_handset()
+                self.notify("jack", False)
+        return True
+
+    def mixer_for_headset(self, force=False):
+        if not force and self.has_headset: return
+        info("Setting mixer for headset")
+        # TODO: find out how to disable the handset microphone: this does not
+        # seem to be sufficient
+        self.mixer_set_many(
+                ("DAPM Handset Mic", "mute"),
+                ("DAPM Headset Mic", "unmute"),
+                ("Left Mixer Sidetone Playback Sw", "unmute"),
+                ("ALC Mixer Mic1", "cap"),
+                ("Amp Spk", "mute") # We don't need the phone playing what we say
+        )
+        self.has_headset = True
+
+    def mixer_for_handset(self, force=False):
+        if not force and not self.has_headset: return
+        info("Setting mixer for handset")
+        self.mixer_set_many(
+                ("DAPM Handset Mic", "unmute"),
+                ("DAPM Headset Mic", "mute"),
+                ("Left Mixer Sidetone Playback Sw", "mute"),
+                ("ALC Mixer Mic1", "cap"),
+                ("Amp Spk", "mute") # We don't need the phone playing what we say
+        )
+        self.has_headset = False
+
+#    def set_basename(self, basename):
+#        self.basename = basename
+#
+#    def start_recording(self):
+#        if self.basename is None:
+#            raise RuntimeError("Recording requested but basename not set")
+#        self.recorder = subprocess.Popen(
+#            ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", self.basename + ".wav"])
+#
+#    def start_levels(self):
+#        self.recorder = subprocess.Popen(
+#            ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", "-V", "stereo", "/dev/null"])
+
+    def save_scenario(self, name):
+        res = subprocess.call(["alsactl", "store", "-f", name])
+        if res != 0:
+            raise RuntimeError("Saving audio scenario to '%s' failed" % name)
+
+    def load_scenario(self, name):
+        res = subprocess.call(["alsactl", "restore", "-f", name])
+        if res != 0:
+            raise RuntimeError("Loading audio scenario '%s' failed with error %d" % (name, res))
+
+    def mixer_set(self, name, *args):
+        args = map(str, args)
+        res = subprocess.call(["amixer", "-q", "set", name] + args)
+        if res != 0:
+            raise RuntimeError("Setting mixer '%s' to %s failed with error %d" % (name, " ".join(args), res))
+
+    def mixer_set_many(self, *args):
+        """Perform many mixer set operations via amixer --stdin"""
+        proc = subprocess.Popen(["amixer", "-q", "--stdin"], stdin=subprocess.PIPE)
+        cmd_input = []
+        for k, v in args:
+            cmd_input.append("sset " + repr(k) + " " + repr(v))
+        (out, err) = proc.communicate(input="\n".join(cmd_input))
+        res = proc.wait()
+        if res != 0:
+            raise RuntimeError("Setting mixer failed with error %d" % res)
+
+
+#class Hub:
+#    """Hub that manages all the various resources that we use, and initiates
+#    operations."""
+#    def __init__(self, bus = None):
+#        self.bus = bus
+#        self.waiting_for_fix = False
+#        self.basename = None
+#        self.recorder = None
+#        self.gpx = None
+#        self.gps = None
+#        self.gps_monitor = None
+#        self.audio = None
+#        self.last_pos = None
+#
+#    def shutdown(self):
+#        # Stop recording
+#        if self.audio is not None:
+#            self.audio.close()
+#
+#        # Close waypoints file
+#        if self.gpx is not None:
+#            self.gpx.close()
+#
+#        # Stop the GPS monitor
+#        if self.gps_monitor:
+#            self.gps_monitor.stop()
+#
+#        # Release the GPS
+#        if self.gps is not None:
+#            self.gps.close()
+#
+#    def levels(self):
+#        self.audio = Audio()
+#        self.audio.start_levels()
+#
+#    def record(self):
+#        self.audio = Audio(self.make_waypoint)
+#        self.gps = GPS()
+#        # Get a fix and start recording
+#        if not self.gps.wait_for_fix(self.start_recording):
+#            self.gps_monitor = GPSMonitor(self.gps)
+#            self.gps_monitor.start()
+#
+#    def monitor(self):
+#        self.audio = None
+#        self.gps = GPS()
+#        self.gps_monitor = GPSMonitor(self.gps)
+#        self.gps_monitor.start()
+#
+#    def start_recording(self):
+#        if self.gps_monitor:
+#            self.gps_monitor.stop()
+#            self.gps_monitor = None
+#
+#        if not self.audio:
+#            return
+#
+#        # Sync system time
+#        gpstime = self.gps.gps_time.GetTime()
+#        subprocess.call(["date", "-s", "@%d" % gpstime])
+#        subprocess.call(["hwclock", "--systohc"])
+#
+#        # Compute basename for output files
+#        self.basename = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(gpstime))
+#        self.basename = os.path.join(AUDIODIR, self.basename)
+#
+#        # Start recording the GPX track
+#        self.gpx = GPX(self.basename)
+#        self.gps.track_position(self.on_position_changed)
+#
+#        # Start recording in background forking arecord
+#        self.audio.set_basename(self.basename)
+#        self.audio.start_recording()
+#
+#    def on_position_changed(self, fields, tstamp, lat, lon, alt):
+#        self.last_pos = (fields, tstamp, lat, lon, alt)
+#        if self.gpx:
+#            self.gpx.trackpoint(tstamp, lat, lon, alt)
+#
+#    def make_waypoint(self):
+#        if self.gpx is None:
+#            return
+#        if self.last_pos is None:
+#            self.last_pos = self.gps.gps_position.GetPosition()
+#        (fields, tstamp, lat, lon, alt) = self.last_pos
+#        self.gpx.waypoint(tstamp, lat, lon, alt)
+#        info("Making waypoint at %s: %f, %f, %f" % (
+#            time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(tstamp)), lat, lon, alt))