From 3d30089cc5105424bba3a93f5ba8f5b59a0e2f2c Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Fri, 4 May 2018 19:46:18 +0800 Subject: [PATCH 01/19] without fftw3 and portaudio, generate shared library from static libraries --- scripts/libs.sh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/libs.sh b/scripts/libs.sh index 6f58f38..84d3c54 100755 --- a/scripts/libs.sh +++ b/scripts/libs.sh @@ -23,19 +23,19 @@ rm -f "$SYSROOT/usr/lib/libfec.dylib" mkdir -p "$BUILDPATH/liquid-dsp" cd "$BUILDPATH/liquid-dsp" -cmake -DCMAKE_BUILD_TYPE=Release "$SRCPATH/liquid-dsp" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" -DCMAKE_SHARED_LINKER_FLAGS="-L$SYSROOT/usr/lib" -DLIQUID_BUILD_EXAMPLES="off" -DLIQUID_BUILD_SANDBOX="off" && make liquid-static && make install +cmake -DCMAKE_BUILD_TYPE=Release "$SRCPATH/liquid-dsp" -DCMAKE_C_FLAGS="-fPIC" -DLIQUID_FFTOVERRIDE=ON -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" -DCMAKE_SHARED_LINKER_FLAGS="-L$SYSROOT/usr/lib" -DLIQUID_BUILD_EXAMPLES="off" -DLIQUID_BUILD_SANDBOX="off" && make liquid-static && make install mkdir -p "$BUILDPATH/jansson" cd "$BUILDPATH/jansson" cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DJANSSON_BUILD_SHARED_LIBS=off -DJANSSON_WITHOUT_TESTS=on -DJANSSON_EXAMPLES=off -DJANSSON_BUILD_DOCS=off "$SRCPATH/jansson" && make && make install -mkdir -p "$BUILDPATH/portaudio" -cd "$BUILDPATH/portaudio" -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" +# mkdir -p "$BUILDPATH/portaudio" +# cd "$BUILDPATH/portaudio" +# cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" mkdir -p "$BUILDPATH/libquiet" cd "$BUILDPATH/libquiet" -cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/libquiet" && make +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/libquiet" && make CTEST_OUTPUT_ON_FAILURE=1 make check make install @@ -44,15 +44,19 @@ mkdir -p "$INCLUDEPATH" cp "$SYSROOTPATH/usr/lib/libfec.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libliquid.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libjansson.a" "$LIBPATH" -cp "$SYSROOTPATH/usr/lib/libportaudio.a" "$LIBPATH" +# cp "$SYSROOTPATH/usr/lib/libportaudio.a" "$LIBPATH" cp "$SYSROOTPATH/usr/lib/libquiet.a" "$LIBPATH" cp "$SYSROOTPATH/usr/include/fec.h" "$INCLUDEPATH" cp -R "$SYSROOTPATH/usr/include/liquid" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/jansson.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/jansson_config.h" "$INCLUDEPATH" -cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" +# cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/quiet.h" "$INCLUDEPATH" +gcc -shared -o $LIBPATH/libquiet.so \ +-Wl,--whole-archive $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ +$LIBPATH/libjansson.a -Wl,--no-whole-archive + echo echo "Build complete. Built libraries are in $LIBPATH" echo "and includes in $INCLUDEPATH." From 059d6acc8be0fb124b7251a5600dfd6ae20c754d Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Fri, 4 May 2018 19:48:26 +0800 Subject: [PATCH 02/19] add a ctypes wrapper of quiet library --- quiet/__init__.py | 0 quiet/quiet.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 quiet/__init__.py create mode 100755 quiet/quiet.py diff --git a/quiet/__init__.py b/quiet/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/quiet/quiet.py b/quiet/quiet.py new file mode 100755 index 0000000..041ed73 --- /dev/null +++ b/quiet/quiet.py @@ -0,0 +1,196 @@ + + +import time +import os +import json +from ctypes import * +from numpy.ctypeslib import ndpointer +import numpy + + +c_float_p = POINTER(c_float) + + +class Quiet(object): + def __init__(self, sample_rate=44100.0, profiles="/usr/local/share/quiet/quiet-profiles.json", profile_name='ultrasonic-experimental'): + self._load_lib() + + self._encoder_options = self._lib.quiet_encoder_profile_filename(profiles, profile_name) + self._encoder = self._lib.quiet_encoder_create(self._encoder_options, sample_rate) + + self._decoder_options = self._lib.quiet_decoder_profile_filename(profiles, profile_name) + self._decoder = self._lib.quiet_decoder_create(self._decoder_options, sample_rate) + # self._lib.quiet_decoder_set_nonblocking(self._decoder) + + def __del__(self): + self._lib.quiet_encoder_destroy(self._encoder) + self._lib.quiet_decoder_destroy(self._decoder) + + def decode(self, data, flush=False): + self._lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) + + if flush: + self._lib.quiet_decoder_flush(self._decoder) + + buf = numpy.empty(128, dtype='uint8') + got = self._lib.quiet_decoder_recv(self._decoder, buf, len(buf)) + + if got > 0: + return buf[:got] + + def flush(self): + self._lib.quiet_decoder_flush(self._decoder) + + code = [] + while True: + buf = numpy.empty(128, dtype='uint8') + got = self._lib.quiet_decoder_recv(self._decoder, buf, len(buf)) + + if got <= 0: + break + + code.append(buf[:got].tostring()) + + def encode(self, buf): + pass + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + def _load_lib(self): + lib_name = 'libquiet.so' + lib_path = os.path.join(os.path.dirname(__file__), lib_name) + + self._lib = cdll.LoadLibrary(lib_path) + + # quiet_encoder_options *quiet_encoder_profile_filename(const char *fname, const char *profilename) + self._lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] + self._lib.quiet_encoder_profile_filename.restype = c_void_p + + # quiet_decoder_options *quiet_decoder_profile_filename(const char *fname, const char *profilename) + self._lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] + self._lib.quiet_decoder_profile_filename.restype = c_void_p + + # quiet_encoder *quiet_encoder_create(const quiet_encoder_options *opt, float sample_rate) + self._lib.quiet_encoder_create.argtypes = [c_void_p, c_float] + self._lib.quiet_encoder_create.restype = c_void_p + + # ssize_t quiet_encoder_send(quiet_encoder *e, const void *buf, size_t len) + self._lib.quiet_encoder_send.argtypes = [c_void_p, c_void_p, c_int] + self._lib.quiet_encoder_send.restype = c_int + + # void quiet_encoder_set_blocking(quiet_encoder *e, time_t sec, long nano) + # self._lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] + # self._lib.quiet_encoder_set_blocking.restype = c_int + + # void quiet_encoder_set_nonblocking(quiet_encoder *e) + + # size_t quiet_encoder_clamp_frame_len(quiet_encoder *e, size_t sample_len) + self._lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_uint] + self._lib.quiet_encoder_clamp_frame_len.restype = c_uint + + # size_t quiet_encoder_get_frame_len(const quiet_encoder *e) + self._lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] + self._lib.quiet_encoder_get_frame_len.restype = c_uint + + # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) + self._lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] + self._lib.quiet_encoder_emit.restype = c_ssize_t + + # void quiet_encoder_close(quiet_encoder *e) + self._lib.quiet_encoder_close.argtypes = [c_void_p] + self._lib.quiet_encoder_close.restype = None + + # void quiet_encoder_destroy(quiet_encoder *e) + self._lib.quiet_encoder_destroy.argtypes = [c_void_p] + self._lib.quiet_encoder_destroy.restype = None + + + # quiet_decoder *quiet_decoder_create(const quiet_decoder_options *opt, float sample_rate) + self._lib.quiet_decoder_create.argtypes = [c_void_p, c_float] + self._lib.quiet_decoder_create.restype = c_void_p + + # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) + self._lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] + self._lib.quiet_decoder_recv.restype = c_ssize_t + + # void quiet_decoder_set_nonblocking(quiet_decoder *d) + self._lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] + self._lib.quiet_decoder_set_nonblocking.restype = None + + # void quiet_decoder_consume(quiet_decoder *d, const quiet_sample_t *samplebuf, size_t sample_len) + self._lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] + self._lib.quiet_decoder_consume.restype = None + + # bool quiet_decoder_frame_in_progress(quiet_decoder *d) + + # void quiet_decoder_flush(quiet_decoder *d) + self._lib.quiet_decoder_flush.argtypes = [c_void_p] + self._lib.quiet_decoder_flush.restype = None + + # void quiet_decoder_close(quiet_decoder *d) + self._lib.quiet_decoder_close.argtypes = [c_void_p] + self._lib.quiet_decoder_close.restype = None + + # unsigned int quiet_decoder_checksum_fails(const quiet_decoder *d) + self._lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] + self._lib.quiet_decoder_checksum_fails.restype = c_uint + + # void quiet_decoder_enable_stats(quiet_decoder *d) + + # void quiet_decoder_disable_stats(quiet_decoder *d) + + # void quiet_decoder_set_stats_blocking(quiet_decoder *d, time_t sec, long nano) + + # void quiet_decoder_set_stats_nonblocking(quiet_decoder *d) + + # void quiet_decoder_destroy(quiet_decoder *d) + self._lib.quiet_decoder_destroy.argtypes = [c_void_p] + self._lib.quiet_decoder_destroy.restype = None + + +def main(): + import pyaudio + import audioop + import Queue + + FORMAT = pyaudio.paFloat32 + CHANNELS = 1 + RATE = 44100 + CHUNK = 16384 # int(RATE / 100) + + t = Quiet() + + p = pyaudio.PyAudio() + q = Queue.Queue() + + def callback(in_data, frame_count, time_info, status): + q.put(in_data) + return (None, pyaudio.paContinue) + + stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=callback) + + count = 0 + with Quiet(profile_name='ultrasonic-experimental') as quiet: + while True: + try: + audio = q.get() + audio = numpy.fromstring(audio, dtype='float32') + # audio = audio[::CHANNELS] + code = quiet.decode(audio) + if code is not None: + count += 1 + print(count, code.tostring()) + except KeyboardInterrupt: + break + +if __name__ == '__main__': + main() From bf6f3f99dbd2cdfd728021a54524b4bdd2675794 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Fri, 4 May 2018 19:50:32 +0800 Subject: [PATCH 03/19] change libquiet.so location --- scripts/libs.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/libs.sh b/scripts/libs.sh index 84d3c54..7940a2c 100755 --- a/scripts/libs.sh +++ b/scripts/libs.sh @@ -53,7 +53,7 @@ cp "$SYSROOTPATH/usr/include/jansson_config.h" "$INCLUDEPATH" # cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/quiet.h" "$INCLUDEPATH" -gcc -shared -o $LIBPATH/libquiet.so \ +gcc -shared -o $ABSPATH/quiet/libquiet.so \ -Wl,--whole-archive $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ $LIBPATH/libjansson.a -Wl,--no-whole-archive From 0b85e803e35fc30fd075778af5602166754f1c76 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Tue, 8 May 2018 17:47:32 +0800 Subject: [PATCH 04/19] divide into Decoder and Encoder --- quiet/quiet.py | 231 +++++++++++++++++++++++++++++-------------------- 1 file changed, 135 insertions(+), 96 deletions(-) diff --git a/quiet/quiet.py b/quiet/quiet.py index 041ed73..9487e26 100755 --- a/quiet/quiet.py +++ b/quiet/quiet.py @@ -3,141 +3,102 @@ import time import os import json -from ctypes import * +from ctypes import c_char_p, c_void_p, c_float, c_size_t, c_ssize_t, c_uint, c_uint8, POINTER, cdll from numpy.ctypeslib import ndpointer import numpy -c_float_p = POINTER(c_float) - - -class Quiet(object): - def __init__(self, sample_rate=44100.0, profiles="/usr/local/share/quiet/quiet-profiles.json", profile_name='ultrasonic-experimental'): - self._load_lib() - - self._encoder_options = self._lib.quiet_encoder_profile_filename(profiles, profile_name) - self._encoder = self._lib.quiet_encoder_create(self._encoder_options, sample_rate) - - self._decoder_options = self._lib.quiet_decoder_profile_filename(profiles, profile_name) - self._decoder = self._lib.quiet_decoder_create(self._decoder_options, sample_rate) - # self._lib.quiet_decoder_set_nonblocking(self._decoder) - - def __del__(self): - self._lib.quiet_encoder_destroy(self._encoder) - self._lib.quiet_decoder_destroy(self._decoder) - - def decode(self, data, flush=False): - self._lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) - - if flush: - self._lib.quiet_decoder_flush(self._decoder) - - buf = numpy.empty(128, dtype='uint8') - got = self._lib.quiet_decoder_recv(self._decoder, buf, len(buf)) - - if got > 0: - return buf[:got] - - def flush(self): - self._lib.quiet_decoder_flush(self._decoder) +PROFILES = os.path.join(os.path.dirname(__file__), 'quiet-profiles.json') - code = [] - while True: - buf = numpy.empty(128, dtype='uint8') - got = self._lib.quiet_decoder_recv(self._decoder, buf, len(buf)) - - if got <= 0: - break +c_float_p = POINTER(c_float) - code.append(buf[:got].tostring()) - def encode(self, buf): - pass - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - pass - def _load_lib(self): +class Quiet(object): + # for lazy loading + lib = None + + @staticmethod + def load_lib(): lib_name = 'libquiet.so' lib_path = os.path.join(os.path.dirname(__file__), lib_name) - self._lib = cdll.LoadLibrary(lib_path) + if os.path.isfile(lib_path): + lib = cdll.LoadLibrary(lib_path) + else: + lib = cdll.LoadLibrary(lib_name) # quiet_encoder_options *quiet_encoder_profile_filename(const char *fname, const char *profilename) - self._lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] - self._lib.quiet_encoder_profile_filename.restype = c_void_p + lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_encoder_profile_filename.restype = c_void_p # quiet_decoder_options *quiet_decoder_profile_filename(const char *fname, const char *profilename) - self._lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] - self._lib.quiet_decoder_profile_filename.restype = c_void_p + lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_decoder_profile_filename.restype = c_void_p # quiet_encoder *quiet_encoder_create(const quiet_encoder_options *opt, float sample_rate) - self._lib.quiet_encoder_create.argtypes = [c_void_p, c_float] - self._lib.quiet_encoder_create.restype = c_void_p + lib.quiet_encoder_create.argtypes = [c_void_p, c_float] + lib.quiet_encoder_create.restype = c_void_p # ssize_t quiet_encoder_send(quiet_encoder *e, const void *buf, size_t len) - self._lib.quiet_encoder_send.argtypes = [c_void_p, c_void_p, c_int] - self._lib.quiet_encoder_send.restype = c_int + lib.quiet_encoder_send.argtypes = [c_void_p, c_char_p, c_size_t] + lib.quiet_encoder_send.restype = c_ssize_t # void quiet_encoder_set_blocking(quiet_encoder *e, time_t sec, long nano) - # self._lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] - # self._lib.quiet_encoder_set_blocking.restype = c_int + # lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] + # lib.quiet_encoder_set_blocking.restype = None # void quiet_encoder_set_nonblocking(quiet_encoder *e) # size_t quiet_encoder_clamp_frame_len(quiet_encoder *e, size_t sample_len) - self._lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_uint] - self._lib.quiet_encoder_clamp_frame_len.restype = c_uint + lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_size_t] + lib.quiet_encoder_clamp_frame_len.restype = c_size_t # size_t quiet_encoder_get_frame_len(const quiet_encoder *e) - self._lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] - self._lib.quiet_encoder_get_frame_len.restype = c_uint + lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] + lib.quiet_encoder_get_frame_len.restype = c_size_t # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) - self._lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] - self._lib.quiet_encoder_emit.restype = c_ssize_t + lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_encoder_emit.restype = c_ssize_t # void quiet_encoder_close(quiet_encoder *e) - self._lib.quiet_encoder_close.argtypes = [c_void_p] - self._lib.quiet_encoder_close.restype = None + lib.quiet_encoder_close.argtypes = [c_void_p] + lib.quiet_encoder_close.restype = None # void quiet_encoder_destroy(quiet_encoder *e) - self._lib.quiet_encoder_destroy.argtypes = [c_void_p] - self._lib.quiet_encoder_destroy.restype = None - + lib.quiet_encoder_destroy.argtypes = [c_void_p] + lib.quiet_encoder_destroy.restype = None # quiet_decoder *quiet_decoder_create(const quiet_decoder_options *opt, float sample_rate) - self._lib.quiet_decoder_create.argtypes = [c_void_p, c_float] - self._lib.quiet_decoder_create.restype = c_void_p + lib.quiet_decoder_create.argtypes = [c_void_p, c_float] + lib.quiet_decoder_create.restype = c_void_p # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) - self._lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] - self._lib.quiet_decoder_recv.restype = c_ssize_t + lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_decoder_recv.restype = c_ssize_t # void quiet_decoder_set_nonblocking(quiet_decoder *d) - self._lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] - self._lib.quiet_decoder_set_nonblocking.restype = None + # lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] + # lib.quiet_decoder_set_nonblocking.restype = None # void quiet_decoder_consume(quiet_decoder *d, const quiet_sample_t *samplebuf, size_t sample_len) - self._lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] - self._lib.quiet_decoder_consume.restype = None + lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] + lib.quiet_decoder_consume.restype = None # bool quiet_decoder_frame_in_progress(quiet_decoder *d) # void quiet_decoder_flush(quiet_decoder *d) - self._lib.quiet_decoder_flush.argtypes = [c_void_p] - self._lib.quiet_decoder_flush.restype = None + lib.quiet_decoder_flush.argtypes = [c_void_p] + lib.quiet_decoder_flush.restype = None # void quiet_decoder_close(quiet_decoder *d) - self._lib.quiet_decoder_close.argtypes = [c_void_p] - self._lib.quiet_decoder_close.restype = None + lib.quiet_decoder_close.argtypes = [c_void_p] + lib.quiet_decoder_close.restype = None # unsigned int quiet_decoder_checksum_fails(const quiet_decoder *d) - self._lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] - self._lib.quiet_decoder_checksum_fails.restype = c_uint + lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] + lib.quiet_decoder_checksum_fails.restype = c_uint # void quiet_decoder_enable_stats(quiet_decoder *d) @@ -148,13 +109,82 @@ def _load_lib(self): # void quiet_decoder_set_stats_nonblocking(quiet_decoder *d) # void quiet_decoder_destroy(quiet_decoder *d) - self._lib.quiet_decoder_destroy.argtypes = [c_void_p] - self._lib.quiet_decoder_destroy.restype = None + lib.quiet_decoder_destroy.argtypes = [c_void_p] + lib.quiet_decoder_destroy.restype = None + + return lib + + +class Decoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES, max_frame=128): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles, profile_name) + self._decoder = Quiet.lib.quiet_decoder_create(self._decoder_options, sample_rate) + + self.max_frame = max_frame + + def __del__(self): + Quiet.lib.quiet_decoder_destroy(self._decoder) + def decode(self, data, flush=False): + Quiet.lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) + + if flush: + Quiet.lib.quiet_decoder_flush(self._decoder) + + buf = numpy.empty(self.max_frame, dtype='uint8') + got = Quiet.lib.quiet_decoder_recv(self._decoder, buf, len(buf)) + + if got > 0: + return buf[:got] + + def flush(self): + Quiet.lib.quiet_decoder_flush(self._decoder) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +class Encoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles, profile_name) + self._encoder = Quiet.lib.quiet_encoder_create(self._encoder_options, sample_rate) -def main(): + def __del__(self): + Quiet.lib.quiet_encoder_destroy(self._encoder) + + def encode(self, data, chunk_size=1024): + Quiet.lib.quiet_encoder_send(self._encoder, c_char_p(data), len(data)) + + buf = numpy.empty(chunk_size, dtype='float32') + while True: + got = Quiet.lib.quiet_encoder_emit(self._encoder, buf, len(buf)) + + if got < 0: + return + elif got < chunk_size: + yield buf + return + else: + yield buf + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +def decode(): import pyaudio - import audioop import Queue FORMAT = pyaudio.paFloat32 @@ -162,8 +192,6 @@ def main(): RATE = 44100 CHUNK = 16384 # int(RATE / 100) - t = Quiet() - p = pyaudio.PyAudio() q = Queue.Queue() @@ -179,18 +207,29 @@ def callback(in_data, frame_count, time_info, status): stream_callback=callback) count = 0 - with Quiet(profile_name='ultrasonic-experimental') as quiet: + with Decoder(profile_name='ultrasonic-experimental') as decoder: while True: try: audio = q.get() audio = numpy.fromstring(audio, dtype='float32') # audio = audio[::CHANNELS] - code = quiet.decode(audio) + code = decoder.decode(audio) if code is not None: count += 1 - print(count, code.tostring()) + print(code.tostring().decode('utf-8', 'ignore')) except KeyboardInterrupt: break + +def test(): + encoder = Encoder() + decoder = Decoder() + + for chunk in encoder.encode('hello, world'): + message = decoder.decode(chunk) + if message: + print(message) + + if __name__ == '__main__': - main() + decode() From 4f3bd7f53ff7fe99b1bee646848ac4ddd71ba828 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Tue, 8 May 2018 17:47:57 +0800 Subject: [PATCH 05/19] add profiles --- quiet/quiet-profiles.json | 270 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 quiet/quiet-profiles.json diff --git a/quiet/quiet-profiles.json b/quiet/quiet-profiles.json new file mode 100644 index 0000000..03948c2 --- /dev/null +++ b/quiet/quiet-profiles.json @@ -0,0 +1,270 @@ +{ + "audible": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 100, + "modulation": { + "center_frequency": 4200, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 10, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "audible-7k-channel-0": { + "mod_scheme": "arb16opt", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 600, + "modulation": { + "center_frequency": 9200, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 6, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 48, + "cyclic_prefix_length": 8, + "taper_length": 4, + "left_band": 0, + "right_band": 0 + } + }, + "audible-7k-channel-1": { + "mod_scheme": "arb16opt", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 600, + "modulation": { + "center_frequency": 15500, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 6, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 48, + "cyclic_prefix_length": 8, + "taper_length": 4, + "left_band": 0, + "right_band": 0 + } + }, + "cable-64k": { + "mod_scheme": "qam1024", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27p23", + "outer_fec_scheme": "rs8", + "frame_length": 7500, + "modulation": { + "center_frequency": 10200, + "gain": 0.09 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 2, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.03 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "ofdm": { + "num_subcarriers": 128, + "cyclic_prefix_length": 16, + "taper_length": 8, + "left_band": 6, + "right_band": 12 + } + }, + "hello-world": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 25, + "modulation": { + "center_frequency": 4400, + "gain": 0.08 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 20, + "symbol_delay": 4, + "excess_bandwidth": 0.38 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 75, + "modulation": { + "center_frequency": 19000, + "gain": 0.1 + }, + "interpolation": { + "shape": "rrcos", + "samples_per_symbol": 14, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-3600": { + "ofdm": { + "num_subcarriers": 64, + "cyclic_prefix_length": 20, + "taper_length": 8, + "left_band": 4, + "right_band": 13 + }, + "mod_scheme": "V29", + "checksum_scheme": "crc8", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 550, + "modulation": { + "center_frequency": 18500, + "gain": 0.1 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 7, + "symbol_delay": 4, + "excess_bandwidth": 0.33 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-whisper": { + "mod_scheme": "gmsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "v27", + "outer_fec_scheme": "none", + "frame_length": 16, + "modulation": { + "center_frequency": 19500, + "gain": 0.1 + }, + "interpolation": { + "shape": "rrcos", + "samples_per_symbol": 30, + "symbol_delay": 4, + "excess_bandwidth": 0.35 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + } + }, + "ultrasonic-experimental": { + "mod_scheme": "bpsk", + "checksum_scheme": "crc32", + "inner_fec_scheme": "rs8", + "outer_fec_scheme": "v29", + "frame_length": 100, + "modulation": { + "center_frequency": 19000, + "gain": 0.2 + }, + "interpolation": { + "shape": "kaiser", + "samples_per_symbol": 10, + "symbol_delay": 4, + "excess_bandwidth": 0.31 + }, + "encoder_filters": { + "dc_filter_alpha": 0.01 + }, + "resampler": { + "delay": 13, + "bandwidth": 0.45, + "attenuation": 60, + "filter_bank_size": 64 + }, + "header": { + "checksum_scheme": "crc32", + "inner_fec_scheme": "secded7264", + "outer_fec_scheme": "v29", + "mod_scheme": "bpsk" + } + } +} From 9b89a635a4d8d39ff42894ee83d319087f94db77 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Tue, 8 May 2018 18:12:16 +0800 Subject: [PATCH 06/19] use setuptools and customize build command --- setup.py | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/setup.py b/setup.py index aed2ff5..2bb9cb5 100644 --- a/setup.py +++ b/setup.py @@ -1,21 +1,35 @@ -from distutils.core import setup, Extension -# if osx + +from setuptools import setup +from setuptools.command.build_py import build_py +import ctypes.util +import subprocess import os -os.environ['LDFLAGS'] = '-framework Carbon -framework AudioUnit -framework AudioToolbox -framework CoreAudio' -module = Extension('cquiet', - sources=['quietmodule.c'], - extra_compile_args=['-std=c99', '-Iinclude'], - extra_link_args=['-Llib', 'libquiet.a', 'libportaudio.a', 'libjansson.a', 'libliquid.a', 'libfec.a'], - ) + +class BuildPyCommand(build_py): + """Custom build command.""" + + def run(self): + # check if libquiet.so is at system lib paths + if not ctypes.util.find_library('quiet'): + libquiet = os.path.join(os.path.dirname( + __file__), 'quiet', 'libquiet.so') + if not os.path.isfile(libquiet): + # build libquiet.so + subprocess.check_call(['bash', 'scripts/libs.sh']) + + build_py.run(self) + setup(name='quiet', version='0.1', description='Quiet Modem', - author='Brian Armstrong', + author='Brian Armstrong, Yihui Xiong', author_email='brian.armstrong.ece+pypi@gmail.com', - url='https://github.com/quiet', - ext_modules=[module], + url='https://github.com/quiet/quiet.py', + cmdclass={ + 'build_py': BuildPyCommand, + }, packages=['quiet'], -) + zip_safe=False) From 32b383558e0c6f31a6943384d47255677791aca0 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Tue, 8 May 2018 18:13:04 +0800 Subject: [PATCH 07/19] add MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..7620721 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include quiet/* \ No newline at end of file From 97d1d34b74e1d8f621c377c70fccf285ec95b4fc Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Wed, 9 May 2018 10:24:14 +0800 Subject: [PATCH 08/19] CRLF to LF --- quiet/quiet.py | 470 ++++++++++++++++++++++++------------------------- 1 file changed, 235 insertions(+), 235 deletions(-) diff --git a/quiet/quiet.py b/quiet/quiet.py index 9487e26..e43c3a2 100755 --- a/quiet/quiet.py +++ b/quiet/quiet.py @@ -1,235 +1,235 @@ - - -import time -import os -import json -from ctypes import c_char_p, c_void_p, c_float, c_size_t, c_ssize_t, c_uint, c_uint8, POINTER, cdll -from numpy.ctypeslib import ndpointer -import numpy - - -PROFILES = os.path.join(os.path.dirname(__file__), 'quiet-profiles.json') - -c_float_p = POINTER(c_float) - - - -class Quiet(object): - # for lazy loading - lib = None - - @staticmethod - def load_lib(): - lib_name = 'libquiet.so' - lib_path = os.path.join(os.path.dirname(__file__), lib_name) - - if os.path.isfile(lib_path): - lib = cdll.LoadLibrary(lib_path) - else: - lib = cdll.LoadLibrary(lib_name) - - # quiet_encoder_options *quiet_encoder_profile_filename(const char *fname, const char *profilename) - lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] - lib.quiet_encoder_profile_filename.restype = c_void_p - - # quiet_decoder_options *quiet_decoder_profile_filename(const char *fname, const char *profilename) - lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] - lib.quiet_decoder_profile_filename.restype = c_void_p - - # quiet_encoder *quiet_encoder_create(const quiet_encoder_options *opt, float sample_rate) - lib.quiet_encoder_create.argtypes = [c_void_p, c_float] - lib.quiet_encoder_create.restype = c_void_p - - # ssize_t quiet_encoder_send(quiet_encoder *e, const void *buf, size_t len) - lib.quiet_encoder_send.argtypes = [c_void_p, c_char_p, c_size_t] - lib.quiet_encoder_send.restype = c_ssize_t - - # void quiet_encoder_set_blocking(quiet_encoder *e, time_t sec, long nano) - # lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] - # lib.quiet_encoder_set_blocking.restype = None - - # void quiet_encoder_set_nonblocking(quiet_encoder *e) - - # size_t quiet_encoder_clamp_frame_len(quiet_encoder *e, size_t sample_len) - lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_size_t] - lib.quiet_encoder_clamp_frame_len.restype = c_size_t - - # size_t quiet_encoder_get_frame_len(const quiet_encoder *e) - lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] - lib.quiet_encoder_get_frame_len.restype = c_size_t - - # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) - lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] - lib.quiet_encoder_emit.restype = c_ssize_t - - # void quiet_encoder_close(quiet_encoder *e) - lib.quiet_encoder_close.argtypes = [c_void_p] - lib.quiet_encoder_close.restype = None - - # void quiet_encoder_destroy(quiet_encoder *e) - lib.quiet_encoder_destroy.argtypes = [c_void_p] - lib.quiet_encoder_destroy.restype = None - - # quiet_decoder *quiet_decoder_create(const quiet_decoder_options *opt, float sample_rate) - lib.quiet_decoder_create.argtypes = [c_void_p, c_float] - lib.quiet_decoder_create.restype = c_void_p - - # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) - lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] - lib.quiet_decoder_recv.restype = c_ssize_t - - # void quiet_decoder_set_nonblocking(quiet_decoder *d) - # lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] - # lib.quiet_decoder_set_nonblocking.restype = None - - # void quiet_decoder_consume(quiet_decoder *d, const quiet_sample_t *samplebuf, size_t sample_len) - lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] - lib.quiet_decoder_consume.restype = None - - # bool quiet_decoder_frame_in_progress(quiet_decoder *d) - - # void quiet_decoder_flush(quiet_decoder *d) - lib.quiet_decoder_flush.argtypes = [c_void_p] - lib.quiet_decoder_flush.restype = None - - # void quiet_decoder_close(quiet_decoder *d) - lib.quiet_decoder_close.argtypes = [c_void_p] - lib.quiet_decoder_close.restype = None - - # unsigned int quiet_decoder_checksum_fails(const quiet_decoder *d) - lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] - lib.quiet_decoder_checksum_fails.restype = c_uint - - # void quiet_decoder_enable_stats(quiet_decoder *d) - - # void quiet_decoder_disable_stats(quiet_decoder *d) - - # void quiet_decoder_set_stats_blocking(quiet_decoder *d, time_t sec, long nano) - - # void quiet_decoder_set_stats_nonblocking(quiet_decoder *d) - - # void quiet_decoder_destroy(quiet_decoder *d) - lib.quiet_decoder_destroy.argtypes = [c_void_p] - lib.quiet_decoder_destroy.restype = None - - return lib - - -class Decoder(object): - def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES, max_frame=128): - if not Quiet.lib: - Quiet.lib = Quiet.load_lib() - - self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles, profile_name) - self._decoder = Quiet.lib.quiet_decoder_create(self._decoder_options, sample_rate) - - self.max_frame = max_frame - - def __del__(self): - Quiet.lib.quiet_decoder_destroy(self._decoder) - - def decode(self, data, flush=False): - Quiet.lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) - - if flush: - Quiet.lib.quiet_decoder_flush(self._decoder) - - buf = numpy.empty(self.max_frame, dtype='uint8') - got = Quiet.lib.quiet_decoder_recv(self._decoder, buf, len(buf)) - - if got > 0: - return buf[:got] - - def flush(self): - Quiet.lib.quiet_decoder_flush(self._decoder) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - pass - - -class Encoder(object): - def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES): - if not Quiet.lib: - Quiet.lib = Quiet.load_lib() - - self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles, profile_name) - self._encoder = Quiet.lib.quiet_encoder_create(self._encoder_options, sample_rate) - - def __del__(self): - Quiet.lib.quiet_encoder_destroy(self._encoder) - - def encode(self, data, chunk_size=1024): - Quiet.lib.quiet_encoder_send(self._encoder, c_char_p(data), len(data)) - - buf = numpy.empty(chunk_size, dtype='float32') - while True: - got = Quiet.lib.quiet_encoder_emit(self._encoder, buf, len(buf)) - - if got < 0: - return - elif got < chunk_size: - yield buf - return - else: - yield buf - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - pass - - -def decode(): - import pyaudio - import Queue - - FORMAT = pyaudio.paFloat32 - CHANNELS = 1 - RATE = 44100 - CHUNK = 16384 # int(RATE / 100) - - p = pyaudio.PyAudio() - q = Queue.Queue() - - def callback(in_data, frame_count, time_info, status): - q.put(in_data) - return (None, pyaudio.paContinue) - - stream = p.open(format=FORMAT, - channels=CHANNELS, - rate=RATE, - input=True, - frames_per_buffer=CHUNK, - stream_callback=callback) - - count = 0 - with Decoder(profile_name='ultrasonic-experimental') as decoder: - while True: - try: - audio = q.get() - audio = numpy.fromstring(audio, dtype='float32') - # audio = audio[::CHANNELS] - code = decoder.decode(audio) - if code is not None: - count += 1 - print(code.tostring().decode('utf-8', 'ignore')) - except KeyboardInterrupt: - break - - -def test(): - encoder = Encoder() - decoder = Decoder() - - for chunk in encoder.encode('hello, world'): - message = decoder.decode(chunk) - if message: - print(message) - - -if __name__ == '__main__': - decode() + + +import time +import os +import json +from ctypes import c_char_p, c_void_p, c_float, c_size_t, c_ssize_t, c_uint, c_uint8, POINTER, cdll +from numpy.ctypeslib import ndpointer +import numpy + + +PROFILES = os.path.join(os.path.dirname(__file__), 'quiet-profiles.json') + +c_float_p = POINTER(c_float) + + + +class Quiet(object): + # for lazy loading + lib = None + + @staticmethod + def load_lib(): + lib_name = 'libquiet.so' + lib_path = os.path.join(os.path.dirname(__file__), lib_name) + + if os.path.isfile(lib_path): + lib = cdll.LoadLibrary(lib_path) + else: + lib = cdll.LoadLibrary(lib_name) + + # quiet_encoder_options *quiet_encoder_profile_filename(const char *fname, const char *profilename) + lib.quiet_encoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_encoder_profile_filename.restype = c_void_p + + # quiet_decoder_options *quiet_decoder_profile_filename(const char *fname, const char *profilename) + lib.quiet_decoder_profile_filename.argtypes = [c_char_p, c_char_p] + lib.quiet_decoder_profile_filename.restype = c_void_p + + # quiet_encoder *quiet_encoder_create(const quiet_encoder_options *opt, float sample_rate) + lib.quiet_encoder_create.argtypes = [c_void_p, c_float] + lib.quiet_encoder_create.restype = c_void_p + + # ssize_t quiet_encoder_send(quiet_encoder *e, const void *buf, size_t len) + lib.quiet_encoder_send.argtypes = [c_void_p, c_char_p, c_size_t] + lib.quiet_encoder_send.restype = c_ssize_t + + # void quiet_encoder_set_blocking(quiet_encoder *e, time_t sec, long nano) + # lib.quiet_encoder_set_blocking.argtypes = [c_void_p, c_uint, c_long] + # lib.quiet_encoder_set_blocking.restype = None + + # void quiet_encoder_set_nonblocking(quiet_encoder *e) + + # size_t quiet_encoder_clamp_frame_len(quiet_encoder *e, size_t sample_len) + lib.quiet_encoder_clamp_frame_len.argtypes = [c_void_p, c_size_t] + lib.quiet_encoder_clamp_frame_len.restype = c_size_t + + # size_t quiet_encoder_get_frame_len(const quiet_encoder *e) + lib.quiet_encoder_get_frame_len.argtypes = [c_void_p] + lib.quiet_encoder_get_frame_len.restype = c_size_t + + # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) + lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_encoder_emit.restype = c_ssize_t + + # void quiet_encoder_close(quiet_encoder *e) + lib.quiet_encoder_close.argtypes = [c_void_p] + lib.quiet_encoder_close.restype = None + + # void quiet_encoder_destroy(quiet_encoder *e) + lib.quiet_encoder_destroy.argtypes = [c_void_p] + lib.quiet_encoder_destroy.restype = None + + # quiet_decoder *quiet_decoder_create(const quiet_decoder_options *opt, float sample_rate) + lib.quiet_decoder_create.argtypes = [c_void_p, c_float] + lib.quiet_decoder_create.restype = c_void_p + + # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) + lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_decoder_recv.restype = c_ssize_t + + # void quiet_decoder_set_nonblocking(quiet_decoder *d) + # lib.quiet_decoder_set_nonblocking.argtypes = [c_void_p] + # lib.quiet_decoder_set_nonblocking.restype = None + + # void quiet_decoder_consume(quiet_decoder *d, const quiet_sample_t *samplebuf, size_t sample_len) + lib.quiet_decoder_consume.argtypes = [c_void_p, c_void_p, c_size_t] + lib.quiet_decoder_consume.restype = None + + # bool quiet_decoder_frame_in_progress(quiet_decoder *d) + + # void quiet_decoder_flush(quiet_decoder *d) + lib.quiet_decoder_flush.argtypes = [c_void_p] + lib.quiet_decoder_flush.restype = None + + # void quiet_decoder_close(quiet_decoder *d) + lib.quiet_decoder_close.argtypes = [c_void_p] + lib.quiet_decoder_close.restype = None + + # unsigned int quiet_decoder_checksum_fails(const quiet_decoder *d) + lib.quiet_decoder_checksum_fails.argtypes = [c_void_p] + lib.quiet_decoder_checksum_fails.restype = c_uint + + # void quiet_decoder_enable_stats(quiet_decoder *d) + + # void quiet_decoder_disable_stats(quiet_decoder *d) + + # void quiet_decoder_set_stats_blocking(quiet_decoder *d, time_t sec, long nano) + + # void quiet_decoder_set_stats_nonblocking(quiet_decoder *d) + + # void quiet_decoder_destroy(quiet_decoder *d) + lib.quiet_decoder_destroy.argtypes = [c_void_p] + lib.quiet_decoder_destroy.restype = None + + return lib + + +class Decoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES, max_frame=128): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles, profile_name) + self._decoder = Quiet.lib.quiet_decoder_create(self._decoder_options, sample_rate) + + self.max_frame = max_frame + + def __del__(self): + Quiet.lib.quiet_decoder_destroy(self._decoder) + + def decode(self, data, flush=False): + Quiet.lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) + + if flush: + Quiet.lib.quiet_decoder_flush(self._decoder) + + buf = numpy.empty(self.max_frame, dtype='uint8') + got = Quiet.lib.quiet_decoder_recv(self._decoder, buf, len(buf)) + + if got > 0: + return buf[:got] + + def flush(self): + Quiet.lib.quiet_decoder_flush(self._decoder) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +class Encoder(object): + def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES): + if not Quiet.lib: + Quiet.lib = Quiet.load_lib() + + self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles, profile_name) + self._encoder = Quiet.lib.quiet_encoder_create(self._encoder_options, sample_rate) + + def __del__(self): + Quiet.lib.quiet_encoder_destroy(self._encoder) + + def encode(self, data, chunk_size=1024): + Quiet.lib.quiet_encoder_send(self._encoder, c_char_p(data), len(data)) + + buf = numpy.empty(chunk_size, dtype='float32') + while True: + got = Quiet.lib.quiet_encoder_emit(self._encoder, buf, len(buf)) + + if got < 0: + return + elif got < chunk_size: + yield buf + return + else: + yield buf + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + pass + + +def decode(): + import pyaudio + import Queue + + FORMAT = pyaudio.paFloat32 + CHANNELS = 1 + RATE = 44100 + CHUNK = 16384 # int(RATE / 100) + + p = pyaudio.PyAudio() + q = Queue.Queue() + + def callback(in_data, frame_count, time_info, status): + q.put(in_data) + return (None, pyaudio.paContinue) + + stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=callback) + + count = 0 + with Decoder(profile_name='ultrasonic-experimental') as decoder: + while True: + try: + audio = q.get() + audio = numpy.fromstring(audio, dtype='float32') + # audio = audio[::CHANNELS] + code = decoder.decode(audio) + if code is not None: + count += 1 + print(code.tostring().decode('utf-8', 'ignore')) + except KeyboardInterrupt: + break + + +def test(): + encoder = Encoder() + decoder = Decoder() + + for chunk in encoder.encode('hello, world'): + message = decoder.decode(chunk) + if message is not None: + print(message) + + +if __name__ == '__main__': + decode() From 0e2963d161fbc3dc3b6aeaca7212cfbdb854a8d7 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Wed, 9 May 2018 10:50:11 +0800 Subject: [PATCH 09/19] link flag uses -all_load instead --whole-archive on macOS --- scripts/libs.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/libs.sh b/scripts/libs.sh index 7940a2c..063f9f3 100755 --- a/scripts/libs.sh +++ b/scripts/libs.sh @@ -31,7 +31,7 @@ cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DJANSSON # mkdir -p "$BUILDPATH/portaudio" # cd "$BUILDPATH/portaudio" -# cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" +# cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-fPIC" -DCMAKE_INSTALL_PREFIX="$SYSROOT/usr" -DCMAKE_PREFIX_PATH="$SYSROOT" "$SRCPATH/portaudio" && make && make install && cp libportaudio_static.a "$SYSROOT/usr/lib/libportaudio.a" mkdir -p "$BUILDPATH/libquiet" cd "$BUILDPATH/libquiet" @@ -53,9 +53,16 @@ cp "$SYSROOTPATH/usr/include/jansson_config.h" "$INCLUDEPATH" # cp "$SYSROOTPATH/usr/include/portaudio.h" "$INCLUDEPATH" cp "$SYSROOTPATH/usr/include/quiet.h" "$INCLUDEPATH" +if [ "$(uname)" == "Darwin" ]; then +gcc -shared -o $ABSPATH/quiet/libquiet.so \ +-Wl,-all_load $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ +$LIBPATH/libjansson.a -Wl,-noall_load +else gcc -shared -o $ABSPATH/quiet/libquiet.so \ -Wl,--whole-archive $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ $LIBPATH/libjansson.a -Wl,--no-whole-archive +fi + echo echo "Build complete. Built libraries are in $LIBPATH" From 77c75509bfce70b9cce812eb331b8d24d1da2103 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Wed, 9 May 2018 11:34:08 +0800 Subject: [PATCH 10/19] compatible with python 3 --- quiet/quiet.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/quiet/quiet.py b/quiet/quiet.py index e43c3a2..04b8dc3 100755 --- a/quiet/quiet.py +++ b/quiet/quiet.py @@ -120,7 +120,7 @@ def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES if not Quiet.lib: Quiet.lib = Quiet.load_lib() - self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles, profile_name) + self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles.encode('utf-8'), profile_name.encode('utf-8')) self._decoder = Quiet.lib.quiet_decoder_create(self._decoder_options, sample_rate) self.max_frame = max_frame @@ -155,14 +155,14 @@ def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES if not Quiet.lib: Quiet.lib = Quiet.load_lib() - self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles, profile_name) + self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles.encode('utf-8'), profile_name.encode('utf-8')) self._encoder = Quiet.lib.quiet_encoder_create(self._encoder_options, sample_rate) def __del__(self): Quiet.lib.quiet_encoder_destroy(self._encoder) def encode(self, data, chunk_size=1024): - Quiet.lib.quiet_encoder_send(self._encoder, c_char_p(data), len(data)) + Quiet.lib.quiet_encoder_send(self._encoder, data.encode('utf-8'), len(data)) buf = numpy.empty(chunk_size, dtype='float32') while True: @@ -185,7 +185,11 @@ def __exit__(self, type, value, traceback): def decode(): import pyaudio - import Queue + import sys + if sys.version_info[0] < 3: + import Queue as queue + else: + import queue FORMAT = pyaudio.paFloat32 CHANNELS = 1 @@ -193,7 +197,7 @@ def decode(): CHUNK = 16384 # int(RATE / 100) p = pyaudio.PyAudio() - q = Queue.Queue() + q = queue.Queue() def callback(in_data, frame_count, time_info, status): q.put(in_data) @@ -232,4 +236,4 @@ def test(): if __name__ == '__main__': - decode() + test() From 07a29daf5e42465072335b59bb8368bfab78f09d Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Wed, 9 May 2018 11:35:32 +0800 Subject: [PATCH 11/19] format --- quiet/quiet.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/quiet/quiet.py b/quiet/quiet.py index 04b8dc3..2809462 100755 --- a/quiet/quiet.py +++ b/quiet/quiet.py @@ -13,11 +13,10 @@ c_float_p = POINTER(c_float) - class Quiet(object): # for lazy loading lib = None - + @staticmethod def load_lib(): lib_name = 'libquiet.so' @@ -59,7 +58,8 @@ def load_lib(): lib.quiet_encoder_get_frame_len.restype = c_size_t # ssize_t quiet_encoder_emit(quiet_encoder *e, quiet_sample_t *samplebuf, size_t samplebuf_len) - lib.quiet_encoder_emit.argtypes = [c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_encoder_emit.argtypes = [ + c_void_p, ndpointer(c_float, flags="C_CONTIGUOUS"), c_size_t] lib.quiet_encoder_emit.restype = c_ssize_t # void quiet_encoder_close(quiet_encoder *e) @@ -75,7 +75,8 @@ def load_lib(): lib.quiet_decoder_create.restype = c_void_p # ssize_t quiet_decoder_recv(quiet_decoder *d, uint8_t *data, size_t len) - lib.quiet_decoder_recv.argtypes = [c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] + lib.quiet_decoder_recv.argtypes = [ + c_void_p, ndpointer(c_uint8, flags="C_CONTIGUOUS"), c_size_t] lib.quiet_decoder_recv.restype = c_ssize_t # void quiet_decoder_set_nonblocking(quiet_decoder *d) @@ -119,9 +120,11 @@ class Decoder(object): def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES, max_frame=128): if not Quiet.lib: Quiet.lib = Quiet.load_lib() - - self._decoder_options = Quiet.lib.quiet_decoder_profile_filename(profiles.encode('utf-8'), profile_name.encode('utf-8')) - self._decoder = Quiet.lib.quiet_decoder_create(self._decoder_options, sample_rate) + + self._decoder_options = Quiet.lib.quiet_decoder_profile_filename( + profiles.encode('utf-8'), profile_name.encode('utf-8')) + self._decoder = Quiet.lib.quiet_decoder_create( + self._decoder_options, sample_rate) self.max_frame = max_frame @@ -129,14 +132,15 @@ def __del__(self): Quiet.lib.quiet_decoder_destroy(self._decoder) def decode(self, data, flush=False): - Quiet.lib.quiet_decoder_consume(self._decoder, data.ctypes.data_as(c_void_p), len(data)) + Quiet.lib.quiet_decoder_consume( + self._decoder, data.ctypes.data_as(c_void_p), len(data)) if flush: Quiet.lib.quiet_decoder_flush(self._decoder) buf = numpy.empty(self.max_frame, dtype='uint8') got = Quiet.lib.quiet_decoder_recv(self._decoder, buf, len(buf)) - + if got > 0: return buf[:got] @@ -154,15 +158,18 @@ class Encoder(object): def __init__(self, sample_rate=44100., profile_name='audible', profiles=PROFILES): if not Quiet.lib: Quiet.lib = Quiet.load_lib() - - self._encoder_options = Quiet.lib.quiet_encoder_profile_filename(profiles.encode('utf-8'), profile_name.encode('utf-8')) - self._encoder = Quiet.lib.quiet_encoder_create(self._encoder_options, sample_rate) + + self._encoder_options = Quiet.lib.quiet_encoder_profile_filename( + profiles.encode('utf-8'), profile_name.encode('utf-8')) + self._encoder = Quiet.lib.quiet_encoder_create( + self._encoder_options, sample_rate) def __del__(self): Quiet.lib.quiet_encoder_destroy(self._encoder) def encode(self, data, chunk_size=1024): - Quiet.lib.quiet_encoder_send(self._encoder, data.encode('utf-8'), len(data)) + Quiet.lib.quiet_encoder_send( + self._encoder, data.encode('utf-8'), len(data)) buf = numpy.empty(chunk_size, dtype='float32') while True: From 977cc7a5e5e46e8621a511847701fdea13a39ccd Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Thu, 10 May 2018 16:18:52 +0800 Subject: [PATCH 12/19] use abspath --- quiet/quiet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quiet/quiet.py b/quiet/quiet.py index 2809462..dbba94e 100755 --- a/quiet/quiet.py +++ b/quiet/quiet.py @@ -8,7 +8,7 @@ import numpy -PROFILES = os.path.join(os.path.dirname(__file__), 'quiet-profiles.json') +PROFILES = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'quiet-profiles.json') c_float_p = POINTER(c_float) @@ -20,7 +20,7 @@ class Quiet(object): @staticmethod def load_lib(): lib_name = 'libquiet.so' - lib_path = os.path.join(os.path.dirname(__file__), lib_name) + lib_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), lib_name) if os.path.isfile(lib_path): lib = cdll.LoadLibrary(lib_path) @@ -243,4 +243,4 @@ def test(): if __name__ == '__main__': - test() + decode() From cf0459bd2650b069fb63233af8b338f0064c0927 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Thu, 10 May 2018 16:19:21 +0800 Subject: [PATCH 13/19] include package data --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 2bb9cb5..1b11708 100644 --- a/setup.py +++ b/setup.py @@ -32,4 +32,5 @@ def run(self): 'build_py': BuildPyCommand, }, packages=['quiet'], + include_package_data=True, zip_safe=False) From 3d0eb25727022f7786c003677f9c279ca1b2d967 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Fri, 1 Jun 2018 10:38:55 +0800 Subject: [PATCH 14/19] reduce .so file size --- scripts/libs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/libs.sh b/scripts/libs.sh index 063f9f3..5af719f 100755 --- a/scripts/libs.sh +++ b/scripts/libs.sh @@ -59,8 +59,8 @@ gcc -shared -o $ABSPATH/quiet/libquiet.so \ $LIBPATH/libjansson.a -Wl,-noall_load else gcc -shared -o $ABSPATH/quiet/libquiet.so \ --Wl,--whole-archive $LIBPATH/libquiet.a $LIBPATH/libliquid.a $LIBPATH/libfec.a \ -$LIBPATH/libjansson.a -Wl,--no-whole-archive +-Wl,--whole-archive $LIBPATH/libquiet.a -Wl,--no-whole-archive $LIBPATH/libliquid.a $LIBPATH/libfec.a \ +$LIBPATH/libjansson.a fi From fa210b9e639e35b3a86d2bbc6795018ffe4e125f Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Mon, 27 May 2019 12:21:40 +0100 Subject: [PATCH 15/19] import Decode and Encoder at __init__.py --- quiet/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quiet/__init__.py b/quiet/__init__.py index e69de29..6f361dc 100644 --- a/quiet/__init__.py +++ b/quiet/__init__.py @@ -0,0 +1,4 @@ +# -*- coding:utf-8 -*- + +from .quiet import Decoder, Encoder + From 8a309fd8df094a3f915762cfe8ef5f641ec18d27 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Mon, 27 May 2019 12:22:02 +0100 Subject: [PATCH 16/19] update README.md --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7cf55b2..f8b70ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,90 @@ -# quiet.py -Python bindings for libquiet +quiet.py +======== + +Python ctypes bindings for libquiet to transmit data with sound. + +## Install +Just run: +``` +pip install quiet.py +``` + +For ARM platform such as Raspbian on Raspberry Pi, Armbian on Nanopi, +to speed up the installation process, we can install `numpy` separately, +as installing `numpy` via pip is slow. + +``` +sudo pt install python-numpy +pip install --no-deps quiet.py +``` + + +## Usage +1. Encode a message, and then decode it +``` +from quiet import Encode, Decoder + +def test(): + encoder = Encoder() + decoder = Decoder() + + for chunk in encoder.encode('hello, world'): + message = decoder.decode(chunk) + if message is not None: + print(message) + + +test() +``` + +2. decode messages from recording in realtime + +``` +import sys +import pyaudio +from quiet import Encode, Decoder + +def decode(): + if sys.version_info[0] < 3: + import Queue as queue + else: + import queue + + FORMAT = pyaudio.paFloat32 + CHANNELS = 1 + RATE = 44100 + CHUNK = 16384 # int(RATE / 100) + + p = pyaudio.PyAudio() + q = queue.Queue() + + def callback(in_data, frame_count, time_info, status): + q.put(in_data) + return (None, pyaudio.paContinue) + + stream = p.open(format=FORMAT, + channels=CHANNELS, + rate=RATE, + input=True, + frames_per_buffer=CHUNK, + stream_callback=callback) + + count = 0 + with Decoder(profile_name='ultrasonic-experimental') as decoder: + while True: + try: + audio = q.get() + audio = numpy.fromstring(audio, dtype='float32') + # audio = audio[::CHANNELS] + code = decoder.decode(audio) + if code is not None: + count += 1 + print(code.tostring().decode('utf-8', 'ignore')) + except KeyboardInterrupt: + break + + +decode() +``` + -This repo is not yet ready. It is a work in progress. From d924bedc4ad635ac91a6707142ca071f0761f7e3 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Mon, 27 May 2019 12:24:07 +0100 Subject: [PATCH 17/19] change package name to quiet.py --- setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1b11708..b16832a 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,10 @@ import os +with open('README.md') as f: + long_description = f.read() + + class BuildPyCommand(build_py): """Custom build command.""" @@ -22,9 +26,11 @@ def run(self): build_py.run(self) -setup(name='quiet', +setup(name='quiet.py', version='0.1', - description='Quiet Modem', + description='Quiet Modem, to transmit data with sound', + long_description=long_description, + long_description_content_type='text/markdown', author='Brian Armstrong, Yihui Xiong', author_email='brian.armstrong.ece+pypi@gmail.com', url='https://github.com/quiet/quiet.py', @@ -33,4 +39,5 @@ def run(self): }, packages=['quiet'], include_package_data=True, + install_requires=['numpy'], zip_safe=False) From ba6f8480302371f7163e32b10ec99e8fb0aee20e Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Mon, 27 May 2019 21:06:30 +0800 Subject: [PATCH 18/19] fix typo and add pypi badge --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f8b70ce..29ba215 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ quiet.py ======== +[![](https://img.shields.io/pypi/v/quiet.py.svg)](https://pypi.org/project/quiet.py/) + + Python ctypes bindings for libquiet to transmit data with sound. ## Install @@ -14,7 +17,7 @@ to speed up the installation process, we can install `numpy` separately, as installing `numpy` via pip is slow. ``` -sudo pt install python-numpy +sudo apt install python-numpy pip install --no-deps quiet.py ``` From 529471c6c886abe70cff7a1ac6e46e6b40be4ce9 Mon Sep 17 00:00:00 2001 From: Yihui Xiong Date: Mon, 27 May 2019 21:15:53 +0800 Subject: [PATCH 19/19] Update README.md --- README.md | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 29ba215..45d44da 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,28 @@ quiet.py Python ctypes bindings for libquiet to transmit data with sound. +## Requirements ++ numpy + ## Install -Just run: -``` -pip install quiet.py -``` -For ARM platform such as Raspbian on Raspberry Pi, Armbian on Nanopi, -to speed up the installation process, we can install `numpy` separately, -as installing `numpy` via pip is slow. ++ For ARM platform, binary package is available on pypi, just use `pip` to install it: -``` -sudo apt install python-numpy -pip install --no-deps quiet.py -``` + ``` + sudo apt install python-numpy + pip install --no-deps quiet.py + ``` + + We install `numpy` separately, as installing `numpy` via pip requires compiling numpy from source. + ++ For x86/amd64 + + ``` + sudo apt install cmake + git clone https://github.com/xiongyihui/quiet.py && cd quiet.py + ./scripts/libs.sh + pip install . + ``` ## Usage @@ -44,6 +52,7 @@ test() ``` import sys +import numpy import pyaudio from quiet import Encode, Decoder