Skip to content

Instantly share code, notes, and snippets.

@ericbolo
Last active April 29, 2024 18:41
Show Gist options
  • Save ericbolo/386940d2c5488d07571fd56018ce1af1 to your computer and use it in GitHub Desktop.
Save ericbolo/386940d2c5488d07571fd56018ce1af1 to your computer and use it in GitHub Desktop.
Bridging audio input (mic) to audio output (headphones) with JACK on Raspberry PI (Raspbian Jessie)

Introduction

This walks you through installing JACK, connecting a USB sound card, and copying the audio on the input port onto the output port. The result: you will hear what you say played back immediately from the headphones.

Installation

sudo pip install setuptools
sudo apt-get install python-dev
sudo apt-get install libffi-dev
sudo pip install cffi
sudo pip install JACK-Client

Configure sound card

This assumes you have a USB sound card with one input jack and one output jack. I tested with the "C-Media USB Headphone Set".

In /boot/config.txt, comment out this line:

dtparam=audio-on

In /lib/modprobe.d/aliases.conf, comment out:

snd-usb-audio index=-2

Run script

I used this script taken directly from the JACK audio docs (https://jackclient-python.readthedocs.io/en/0.4.1/)

    #!/usr/bin/env python3
    
    """Create a JACK client that copies input audio directly to the outputs.
    
    This is somewhat modeled after the "thru_client.c" example of JACK 2:
    http://github.com/jackaudio/jack2/blob/master/example-clients/thru_client.c
    
    If you have a microphone and loudspeakers connected, this might cause an
    acoustical feedback!
    
    """
    import sys
    import signal
    import os
    import jack
    import threading
    
    if sys.version_info < (3, 0):
        # In Python 2.x, event.wait() cannot be interrupted with Ctrl+C.
        # Therefore, we disable the whole KeyboardInterrupt mechanism.
        # This will not close the JACK client properly, but at least we can
        # use Ctrl+C.
        signal.signal(signal.SIGINT, signal.SIG_DFL)
    else:
        # If you use Python 3.x, everything is fine.
        pass
    
    argv = iter(sys.argv)
    # By default, use script name without extension as client name:
    defaultclientname = os.path.splitext(os.path.basename(next(argv)))[0]
    clientname = next(argv, defaultclientname)
    servername = next(argv, None)
    
    print "server name";
    print servername;
    
    client = jack.Client(clientname, servername=servername)
    
    if client.status.server_started:
        print("JACK server started")
    if client.status.name_not_unique:
        print("unique name {0!r} assigned".format(client.name))
    
    event = threading.Event()
    
    
    @client.set_process_callback
    def process(frames):
        print "processing frames"
        assert len(client.inports) == len(client.outports)
        assert frames == client.blocksize
        for i, o in zip(client.inports, client.outports):
            o.get_buffer()[:] = i.get_buffer()
    
    
    @client.set_shutdown_callback
    def shutdown(status, reason):
        print("JACK shutdown!")
        print("status:", status)
        print("reason:", reason)
        event.set()
    
    
    # create two port pairs (Eric says: stereo sound?)
    for number in 1, 2:
        client.inports.register("input_{0}".format(number))
        client.outports.register("output_{0}".format(number))
    
    # Eric says: trying with a single input and a single output
    # This results in only one side of the audio being heard
    #client.inports.register("input_1")
    #client.outports.register("output_1")
    
    
    with client:
        # When entering this with-statement, client.activate() is called.
        # This tells the JACK server that we are ready to roll.
        # Our process() callback will start running now.
    
        # Connect the ports.  You can't do this before the client is activated,
        # because we can't make connections to clients that aren't running.
        # Note the confusing (but necessary) orientation of the driver backend
        # ports: playback ports are "input" to the backend, and capture ports
        # are "output" from it.
    
        capture = client.get_ports(is_physical=True, is_output=True)
        if not capture:
            raise RuntimeError("No physical capture ports")
    
        for src, dest in zip(capture, client.inports):
            client.connect(src, dest)
    
        playback = client.get_ports(is_physical=True, is_input=True)
        if not playback:
            raise RuntimeError("No physical playback ports")
    
        for src, dest in zip(client.outports, playback):
            client.connect(src, dest)
    
        print("Press Ctrl+C to stop")
        try:
            event.wait()
        except KeyboardInterrupt:
            print("\nInterrupted by user")
    
    # When the above with-statement is left (either because the end of the
    # code block is reached, or because an exception was raised inside),
    # client.deactivate() and client.close() are called automatically.

Run the script, and what you say will be played back in the headphones. Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment