Montag, 31. Dezember 2012

Raspberry Audio ohne Störungen

Der Rapberry PI ist mit 30-40 € sicher eines der günstigsten LINUX basierten Einplatinensysteme die im Augenblick auf dem Markt verfügbar sind.
Die Verwendung als Low-Cost Audiowiedergabestation drängt sich förmlich auf.
Leider haben die aktuellen Versionen einen Bug im Audiosystem, der dazu führt, dass am Ende der Wiedergabe einer MP3 Datei ein häßliches PLOP oder POP Geräusch zu hören ist.
Dieser Artikel beschreibt eine einfach Möglichkeit diese Störungen weitgehend zu unterdrücken.

Die Störung scheint zu entstehen, wenn der Raspberry das Audiodevice bzw. den entsprechenden Treiber öffnet oder schließt. Wird mit mplayer, vlc oder gst123 eine Playliste  wiedergegeben ertönt das Knacken nach und for jedem einzelnen MP3.

Ein Blick in die Suchmaschine ergab, dass das Problem bekannt, eine Behebung aber im Augenblick nicht verfügbar und unter Umständen nur durch einen Hardwareupgrade zu beheben ist.

Hier mein Vorschlag für einen Workaround:

mit  

     apt-get install pulseaudio

den pulseaudio Server installieren.
Nach erfolgreicher Installation in der Datei /etc/default/pulseaudio die Zeile:

    PULSEAUDIO_SYSTEM_START=0

in

    PULSEAUDIO_SYSTEM_START=1

ändern.

Diese Änderung sorgt dafür, dass der pulseaudio Server direkt beim Systemstart ausgeführt wird. Der pulseaudio Server öffnet das Audiodevice und das Störgeräusch wird einmal beim Booten wiedergegeben.

Damit alle Programme, die ALSA verwenden auf den pulseaudio Server umgelenkt werden muss die Datei /etc/asound.conf angepasst werden.
Die original asound.conf sichern. Anschließend die folgenden Zeilen in die Datei /etc/asound.conf eintragen:

    pcm.pulse {
         type pulse
    }
    ctl.pulse {
        type pulse
    }
    pcm.!default {
        type pulse
    }
    ctl.!default {
        type pulse
    }


Der pulseaudio Server schließt nicht verwendete Audiogeräte nach einer gewissen Zeit. Da dies ebenfalls das Störgeräusch erzeugt findet, man gelegentlich den Hinweis die folgende Änderung in der Datei /etc/pulse/system.pa durchzuführen. Änderung von:

    load-module module-suspend-on-idle

in

    #load-module module-suspend-on-idle

Mein Raspberry gibt nach dieser Änderung keinen Ton mehr von sich. Ich rate also davon ab diese Änderung durchzuführen.

Wer die Wiedergabelautstärke seines Raspberry remote steuern möchte kann die folgende Zeile an die Datei /etc/pulse/system.pa anhängen.

    load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1;192.168.1.0/24 listen="0.0.0.0"

Bei dieser Änderung bitte die berechtigten Subnetze anpassen. Also für ein typisches fritzbox Netz wurde statt:  192.168.1.0/24 in 192.168.178.0/24 geändert werden.

Durch diese Änderung wird der pulseaudio Server über die Netzwerkschnittstelle erreichbar. Soll die Lautstärke von einem anderen Rechner aus geändert werden, muss die Umgebungsvariable PULSE_SERVER die IP Adresse des Raspberry enthalten. Nach dem Setzen der Variable kann man GUI Tools wie pavucontrol oder pacmd verwenden, um die Lautstärke zu ändern.
Bei pactl muss der Server direkt über die -s Option angegeben werden.


Beispiele:

    $ pactl -s <raspberry IP> set-sink-volume 0 50000
  $ PULSE_SERVER=<raspberrypi IP> pavucontrol 

Als letzter Schritt müssen alle Benutzer, die Audio wiedergeben möchten den Gruppen pulse und pulse-access zugefügt werden, andernfalls erscheint die folgende Fehlermeldung:

[AO_ALSA] alsa-lib: pulse.c:243:(pulse_connect) PulseAudio: Unable to connect: Access denied

Also für jeden Benutzer der Audio wiedergeben können soll, müssen die beiden Befehle als root oder mit sudo ausgeführt werden:
  
    # addgroup pi pulse
    # addgroup pi pulse-access

Sind alle Änderungen durchgeführt, den Raspberry neu starten. Das Problem mit dem Plop sollte jetzt nur noch beim Starten oder Stoppen auftreten, bzw. wenn der pulseaudio Server das Device öffnet oder schließt. Zwischen den MP3s sollte es zumindest deutlich leiser, wenn nicht ganz verschwunden sein.

EDIT: 
Eine weitere Quelle für verbrummtes oder anderweitig gestörtes Audio kann auch das Netzteil und das verwendete Kabel sein. Sitzt die Masse des Steckers locker im Netzteil kann das zu erheblichen Störungen führen. Auch preiswerte USB-Netzteil oder billige Kabel können zu Störungen bei der Audiowiedergabe führen.
Bei Problemen einfach mal Kabel und/Oder Netzteil tauschen.

Donnerstag, 11. Oktober 2012

Grails WAR mit Maven auf einen entfernten Tomcat deployen

Zum kompilieren einer Grails Applikation wird das Grails Plugin verwendet. Das produzierte Artefakt kann dann durch Verwendung des Cargo Maven Plugins auf einen entfernten Tomcat Server, in meinem Fall einem Tomcat 6, deployed werden.

Tomcat Einstellungen

Ich verwende Tomcat aus dem stable Repository von Debian Squeeze. Um später das Remote-Deployment durchführen zu können muss zusätzlich zum Package tomcat6 das Package tomcat6-admin installiert sein.

Mit root Berechtigung reicht die Ausführung des Kommandos:

    apt-get install tomcat6 tomcat6-admin

um  alles notwendige zu installieren.

Anschließend muss ein Tomcat-Benutzer  eingerichtet werden. Die Datei /var/lib/tomcat6/conf/tomcat-users.xml muss um die folgenden Einträge erweitert werden:

    <role rolename="manager-gui"/>
    <role rolename="manager-script"/>
    <role rolename="manager-jmx"/>
    <role rolename="manager-status"/>
    <user username="me" password="secret

          roles="manager-gui,manager-script,manager-jmx,manager-status"/>
Diese Erweiterung sorgt dafür, dass der Benutzer "me" mit dem Passwort "secret" Zugriff auf die Managementfunktionen des Tomcat bekommt.

Beim Deployment von Grails Applikationen kommt es häufig zu einem "out of PermGenSpace" Fehler. Um diesen zu vermeiden sollte die JAVA_OPTS Zeile in der Datei  /etc/default/tomcat6 um die Option -XX:MaxPermSize=256m erweitert werden. Dadurch wird der PermGenSpace auf 256 MByte vergrößert und reicht in den meisten Fällen aus.

Die JAVA_OPTS Zeile in /etc/default/tomcat6 sollte dann so aussehen:


    ...
    JAVA_OPTS="-Djava.awt.headless=true -Xmx128m -XX:+UseConcMarkSweepGC
             -XX:MaxPermSize=256m"
    ...

Damit sind alle für Tomcat notwendigen Einstellungen vorgenommen und Tomcat muss mit:

    /etc/init.d/tomcat6 restart

neu gestartet werden, damit alle durchgeführten Änderungen wirksam werden.

MAVEN Einstellungen

In der pom.xml der Grails-Applikation wird das Cargo Plugin hinzugefügt. In der Plugin Konfiguration werden alle relevanten Daten wie der Server auf dem Deployed werden soll, sowie die oben festgelegten Zugangsdaten konfiguriert.
Beispiel:

        <plugin>
        <groupId>org.codehaus.cargo</groupId>
        <artifactId>cargo-maven2-plugin</artifactId>
        <version>1.0.6</version>
        <configuration>
            <container>
            <containerId>tomcat6x</containerId>
            <type>remote</type>
            </container>
            <configuration>
            <type>runtime</type>
            <properties>
                <cargo.tomcat.manager.url>

                    http://myserver:8080/manager
                </cargo.tomcat.manager.url>
                <cargo.remote.username>me</cargo.remote.username>
                <cargo.remote.password>secret</cargo.remote.password>
            </properties>
            </configuration>
            <deployer>
            <type>remote</type>
            <deployables>
                <deployable>
                <location>target/${artifactId}-${version}.war</location>
                <groupId>${groupId}</groupId>
                <artifactId>${artifactId}</artifactId>
                <type>war</type>
                <properties>
                    <context>${artifactId}</context>
                </properties>
                </deployable>
            </deployables>
            </deployer>
        </configuration>
        </plugin>


Das <location> Tag sorgt dafür, dass das gerade erstellte Artefakt über das Filesystem und nicht aus dem Build selbst heraus deployed wird.
Da das Grails Plugin den Packaging-Type grails-app benötigt, das Cargo Plugin aber nur Standard Packaging Types wie war, ear usw. verarbeitet, würde es sonst zu folgendem Fehler kommen:

    [INFO] [cargo:redeploy {execution: default-cli}]
    [WARNING] The defined deployable has the same groupId and artifactId 

    as your project's main artifact but the type is different. You've 
    defined a [war] type whereas the project's packaging is 
    [grails-app]. This is possibly an error and as a consequence the 
    plugin will try to find this deployable in the project's 
    dependencies.
    [INFO] ----------------------------------------------------------------------
    [ERROR] BUILD ERROR


Wenn der POM angepasst wurde und alles korrekt konfiguriert worden ist, kann mit dem Kommando:

    mvn -Dmaven.test.skip=true clean install cargo:redeploy

das Artefakt gebaut und deployed werden.

Dienstag, 4. September 2012

Zotac Box mit HDMI Audio

HDMI Audio ist unter Linux und speziell unter Debian immer ein wenig kompliziert in Betrieb zu nehmen.
Ich beschreibe hier kurz wie ich meine Zotac Box, die mit einem NVIDIA Ion Chipset ausgerüstet ist eingerichtet habe. Auf der Box läuft ein Debian Squeeze (6.0.3) und MythTV 0.24.
Das alsa Package und das alsa-utils Package müssen installiert sein.
Mit aplay -l werden alle Audiodevices aufgelistet. Das sieht bei mir so aus:
root@twinpeaks:~# aplay -l
**** Liste der Hardware-Geräte (PLAYBACK) ****
Karte 0: Intel [HDA Intel], Gerät 0: ALC888 Analog [ALC888 Analog]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0
Karte 0: Intel [HDA Intel], Gerät 1: ALC888 Digital [ALC888 Digital]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0
Karte 1: NVidia [HDA NVidia], Gerät 3: HDMI 0 [HDMI 0]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0
Karte 1: NVidia [HDA NVidia], Gerät 7: HDMI 0 [HDMI 0]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0
Karte 1: NVidia [HDA NVidia], Gerät 8: HDMI 0 [HDMI 0]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0
Karte 1: NVidia [HDA NVidia], Gerät 9: HDMI 0 [HDMI 0]
  Sub-Geräte: 1/1
  Sub-Gerät #0: subdevice #0

Welches der vielen Audiodevices das Richtige ist habe ich durch probieren ermittelt. Hierzu eigenet sich das in den alsa-utils enthaltene speaker-test Programm.
HDMI Kabel an den Fernseher anschließen. Lautstärke des Fernsehers beachten, also nicht zu niedrig einstellen.
Anschließend für jedes der aufgelisteten Geräte das Kommando:
speaker-test -Dplughw:<Karte>,<Gerät>
ausführen. Wobei <Karte> durch die Nummer der Karte und <Gerät> durch die Nummer des Gerätes ersetzt werden müssen. Also das Kommando um das erste Gerät der Liste zu testen wäre dann:
speaker-test -Dplughw:0,0
Bei mir war es plughw:1,7. Hat man das Device identifiziert kann man es als Alsa Default Device vereinbaren, in dem man die Datei /etc/asound.conf wir folgt anlegt:

pcm.!default {
    type plug
    slave.pcm {
        type hw
        card 1
        device 7
    }
}

Ich würde das Alsa System jetzt neu starten. 
Für alle MythTV Anwender: In der Audiokonfiguration wird das Device mit ALSA:plughw:1,7, natürlich entsprechend eurer Karte/Gerät, angegeben.

Einfacher WMA nach MP3 Encoder

Da einige der Internetradiosender ihre Sendungen nur als  WMA-Stream ausstrahlen, mein N770 Webtablet aber nur MP3 wiedergeben lann habe ich, natürlich nur zu Bildungszwecken, das folgende kleine Skript geschrieben.
Es ist ein kleiner Webserver in Python der auf Port 8080 lauscht. Bei einem eingehenden Request, wobei es sich um einen beliebigen GET Request handeln muss, verbindet sich der Webserver mit der im run  Kommando angegebenen mms Adresse und lädt den WMA-Stream in einen Transcoder, der ihn dann in einen MP3 Stream umwandelt. Der aus dem Transcoder gewonnene Datenstrom wird mit dem notwendigen Contentheader versehen und als Antwort auf den GET Request zurücksendet.
Das Skript ist in Python 2.5 geschrieben und benötigt die libmimms bindings für Python.
Source radio.py:
from os import curdir, sep
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from libmimms.core import run

__author__="barmeier"
__date__ ="$04.01.2010 16:48:20$"


class MmsTranscoder(BaseHTTPRequestHandler):

    def do_GET(self):
        try:
            self.send_response(200)
            self.send_header('Content-type', 'audio/mpeg')
            self.end_headers()
            run("mms://radiotstation/livestream.wma",self.wfile)
        except IOError:
            self.send_error(404,'File Not Found: %s' % self.path)

def main():
    try:
        server = HTTPServer(('', 8080), MmsTranscoder)
        print 'Welcome to the machine...'
        server.serve_forever()
    except KeyboardInterrupt:
        print '^C received, shutting down server'
        server.socket.close()

if __name__ == '__main__':
    main()
Damit die automatische MP3 Kodierung funktioniert habe ich die download Methode in der Datei libmms/core.py gepatched. Das kann man sicher viel eleganter machen, aber ich wollte endlich Radio hören.
download Methode für libmms/core.py:
def download(url, outStream):
    "Using the given options, download the stream to a file."
    stream = libmms.Stream(url, 1e6)
    bitrate= 128000
    dec= enc= mx= None
    #sys.stdout.flush()
    dm = muxer.Demuxer("wma")
    type="mp3"
    stopped = False
    for data in stream:
        #f.write(data)
        frames = dm.parse(data)
        if frames:
            for fr in frames:
                # Assume for now only audio streams
                if dec == None:
                    # Open decoder
                    dec = acodec.Decoder(dm.streams[fr[0]])
                    print 'Decoder params:', dm.streams[fr[0]]
                # Decode audio frame
                r = dec.decode(fr[1])
                try:
                    if r:
                        if bitrate == None:
                            bitrate = r.bitrate
                        # Open muxer and encoder
                        if enc == None:
                            params = {'id': acodec.getCodecID(type),
                                'bitrate': bitrate,
                                'sample_rate': r.sample_rate,
                                'channels': r.channels}
                            print 'Encoder params:', params
                            mx = muxer.Muxer(type)
                            stId = mx.addStream(muxer.CODEC_TYPE_AUDIO, params)
                            enc = acodec.Encoder(params)
                            ss = mx.start()
                            outStream.write(ss)
                            outStream.flush()
                        enc_frames = enc.encode(r.data)
                        if enc_frames:
                            for efr in enc_frames:
                                ss = mx.write(stId, efr)
                                if ss:
                                    outStream.write(ss)
                                    outStream.flush()
                except:
                    print "Client lost. Waiting for next ..."
                    stopped=True
                    break
        if stopped:
            break
    stream.close()

Auf der Kommandozeile startet man das Programm mit:
sbamamoto/home$ python raodio.py
Einfach im Browser http://<rechnername>:8080 aufrufen und dann sollte es funktionieren.