# audio - zavai audio resource # # Copyright (C) 2009 Enrico Zini # # 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 os.path import subprocess #import sys #import os #import time #import signal #import optparse #import dbus #import dbus.mainloop.glib class Recorder(zavai.Resource): def __init__(self, registry): super(Recorder, self).__init__() self.registry = registry self.recorder = None def shutdown(self): self.stop() def start(self, filename): if self.recorder is not None: return self.registry.resource("audio").connect("audio", self) self.recorder = subprocess.Popen( ["arecord", "-D", "hw", "-f", "cd", "-r", "8000", "-t", "wav", filename]) def stop(self): if self.recorder is None: return os.kill(self.recorder.pid, signal.SIGINT) self.recorder.wait() self.recorder = None self.registry.resource("audio").disconnect("audio", self) # 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, name): 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 # 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 zavai.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 zavai.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_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))