mirror of
https://github.com/romanz/amodem.git
synced 2026-03-23 02:39:26 +08:00
@@ -1,6 +1,7 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.3"
|
||||
|
||||
install:
|
||||
- pip install .
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
import numpy as np
|
||||
import common
|
||||
import config
|
||||
import sigproc
|
||||
import wave
|
||||
|
||||
from . import common
|
||||
from . import config
|
||||
from . import sigproc
|
||||
from . import wave
|
||||
|
||||
Tsample = 1
|
||||
t = np.arange(int(Tsample * config.Fs)) * config.Ts
|
||||
@@ -22,6 +23,7 @@ def send():
|
||||
except KeyboardInterrupt:
|
||||
p.kill()
|
||||
|
||||
|
||||
def recv():
|
||||
p = wave.record('-', stdout=wave.sp.PIPE)
|
||||
try:
|
||||
@@ -36,13 +38,15 @@ def recv():
|
||||
continue
|
||||
x = x - np.mean(x)
|
||||
|
||||
c = np.abs(np.dot(x, sig)) / (np.sqrt(0.5 * len(x)) * sigproc.norm(x))
|
||||
normalization_factor = np.sqrt(0.5 * len(x)) * sigproc.norm(x)
|
||||
coherence = np.abs(np.dot(x, sig)) / normalization_factor
|
||||
z = np.dot(x, sig.conj()) / (0.5 * len(x))
|
||||
amp = np.abs(z)
|
||||
amplitude = np.abs(z)
|
||||
phase = np.angle(z)
|
||||
peak = np.max(np.abs(x))
|
||||
print('coherence={:.3f} amp={:.3f} phase={:.1f} peak={:.3f}'.format(
|
||||
c, amp, phase * 180 / np.pi, peak))
|
||||
|
||||
fmt = 'coherence={:.3f} amplitude={:.3f} phase={:+.1f} peak={:.3f}'
|
||||
print(fmt.format(coherence, amplitude, phase * 180 / np.pi, peak))
|
||||
except KeyboardInterrupt:
|
||||
p.kill()
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def load(fileobj):
|
||||
|
||||
|
||||
def loads(data):
|
||||
x = np.fromstring(data, dtype='int16')
|
||||
x = np.frombuffer(data, dtype='int16')
|
||||
x = x / scaling
|
||||
return x
|
||||
|
||||
@@ -83,7 +83,7 @@ class Splitter(object):
|
||||
while True:
|
||||
if all(self.read):
|
||||
try:
|
||||
self.last = self.iterable.next()
|
||||
self.last = next(self.iterable)
|
||||
except StopIteration:
|
||||
return
|
||||
|
||||
@@ -108,3 +108,9 @@ def icapture(iterable, result):
|
||||
|
||||
def take(iterable, n):
|
||||
return np.array(list(itertools.islice(iterable, n)))
|
||||
|
||||
|
||||
try:
|
||||
izip = itertools.izip
|
||||
except AttributeError:
|
||||
izip = zip # Python 3
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
''' Reed-Solomon CODEC. '''
|
||||
from reedsolo import rs_encode_msg, rs_correct_msg
|
||||
|
||||
import common
|
||||
from . import common
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -17,7 +17,8 @@ def end_of_stream(size):
|
||||
def encode(data, nsym=DEFAULT_NSYM):
|
||||
chunk_size = BLOCK_SIZE - nsym - 1
|
||||
|
||||
for _, chunk in common.iterate(data, chunk_size, bytearray, truncate=False):
|
||||
for _, chunk in common.iterate(data=data, size=chunk_size,
|
||||
func=bytearray, truncate=False):
|
||||
size = len(chunk)
|
||||
if size < chunk_size:
|
||||
padding = [0] * (chunk_size - size)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import numpy as np
|
||||
import itertools
|
||||
|
||||
import sampling
|
||||
import sigproc
|
||||
from . import sampling
|
||||
from . import sigproc
|
||||
from . import common
|
||||
|
||||
|
||||
class Filter(object):
|
||||
@@ -33,4 +34,4 @@ class FreqLoop(object):
|
||||
self.gens.append(gen)
|
||||
|
||||
def __iter__(self):
|
||||
return itertools.izip(*self.gens)
|
||||
return common.izip(*self.gens)
|
||||
|
||||
@@ -8,21 +8,24 @@ import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
import bitarray
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import stream
|
||||
import sigproc
|
||||
import loop
|
||||
import train
|
||||
import common
|
||||
import config
|
||||
from . import stream
|
||||
from . import sigproc
|
||||
from . import loop
|
||||
from . import train
|
||||
from . import common
|
||||
from . import config
|
||||
from . import ecc
|
||||
|
||||
modem = sigproc.MODEM(config)
|
||||
|
||||
|
||||
if os.environ.get('PYLAB') == '1':
|
||||
import pylab
|
||||
import show
|
||||
from . import pylab
|
||||
from . import show
|
||||
WIDTH = np.floor(np.sqrt(len(modem.freqs)))
|
||||
HEIGHT = np.ceil(len(modem.freqs) / float(WIDTH))
|
||||
else:
|
||||
@@ -190,7 +193,7 @@ def demodulate(symbols, filters, freqs, sampler):
|
||||
stats['rx_start'] = time.time()
|
||||
|
||||
log.info('Demodulation started')
|
||||
for i, block in enumerate(itertools.izip(*streams)): # block per frequency
|
||||
for i, block in enumerate(common.izip(*streams)): # block per frequency
|
||||
for bits in block:
|
||||
stats['rx_bits'] = stats['rx_bits'] + len(bits)
|
||||
yield bits
|
||||
@@ -223,9 +226,6 @@ def receive(signal, freqs, gain=1.0):
|
||||
|
||||
|
||||
def decode(bits_iterator):
|
||||
import bitarray
|
||||
import ecc
|
||||
|
||||
def blocks():
|
||||
while True:
|
||||
bits = itertools.islice(bits_iterator, 8 * ecc.BLOCK_SIZE)
|
||||
@@ -238,11 +238,16 @@ def decode(bits_iterator):
|
||||
return ecc.decode(blocks())
|
||||
|
||||
|
||||
def iread(fd):
|
||||
reader = stream.Reader(fd, data_type=common.loads)
|
||||
return itertools.chain.from_iterable(reader)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
log.info('Running MODEM @ {:.1f} kbps'.format(modem.modem_bps / 1e3))
|
||||
|
||||
signal = stream.iread(args.input)
|
||||
signal = iread(args.input)
|
||||
skipped = common.take(signal, args.skip)
|
||||
log.debug('Skipping first %.3f seconds', len(skipped) / float(modem.baud))
|
||||
|
||||
@@ -282,8 +287,10 @@ if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--skip', type=int, default=100,
|
||||
help='skip initial N samples, due to spurious spikes')
|
||||
p.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin)
|
||||
p.add_argument('-o', '--output', type=argparse.FileType('w'), default=sys.stdout)
|
||||
p.add_argument('-i', '--input', type=argparse.FileType('rb'),
|
||||
default=sys.stdin)
|
||||
p.add_argument('-o', '--output', type=argparse.FileType('wb'),
|
||||
default=sys.stdout)
|
||||
args = p.parse_args()
|
||||
try:
|
||||
main(args)
|
||||
|
||||
@@ -49,6 +49,8 @@ class Sampler(object):
|
||||
def next(self):
|
||||
return self._sample() * self.gain
|
||||
|
||||
__next__ = next
|
||||
|
||||
def _sample(self):
|
||||
offset = self.offset
|
||||
# offset = k + (j / self.resolution)
|
||||
@@ -58,7 +60,7 @@ class Sampler(object):
|
||||
end = k + self.width
|
||||
while self.index < end:
|
||||
self.buff[:-1] = self.buff[1:]
|
||||
self.buff[-1] = self.src.next() # throws StopIteration
|
||||
self.buff[-1] = next(self.src) # throws StopIteration
|
||||
self.index += 1
|
||||
|
||||
self.offset += self.freq
|
||||
|
||||
@@ -7,19 +7,22 @@ import time
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import train
|
||||
import wave
|
||||
from . import train
|
||||
from . import wave
|
||||
|
||||
import common
|
||||
import config
|
||||
import sigproc
|
||||
from . import common
|
||||
from . import config
|
||||
from . import sigproc
|
||||
from . import stream
|
||||
from . import ecc
|
||||
|
||||
modem = sigproc.MODEM(config)
|
||||
|
||||
|
||||
class Symbol(object):
|
||||
t = np.arange(0, config.Nsym) * config.Ts
|
||||
carrier = [np.exp(2j * np.pi * F * t) for F in modem.freqs]
|
||||
def __init__(self):
|
||||
t = np.arange(0, config.Nsym) * config.Ts
|
||||
self.carrier = [np.exp(2j * np.pi * F * t) for F in modem.freqs]
|
||||
|
||||
sym = Symbol()
|
||||
|
||||
@@ -63,26 +66,7 @@ def modulate(fd, bits):
|
||||
break
|
||||
|
||||
|
||||
class Reader(object):
|
||||
def __init__(self, fd, size):
|
||||
self.fd = fd
|
||||
self.size = size
|
||||
self.total = 0
|
||||
|
||||
def next(self):
|
||||
block = self.fd.read(self.size)
|
||||
if block:
|
||||
self.total += len(block)
|
||||
return block
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
|
||||
def main(args):
|
||||
import ecc
|
||||
log.info('Running MODEM @ {:.1f} kbps'.format(modem.modem_bps / 1e3))
|
||||
|
||||
# padding audio with silence
|
||||
@@ -95,7 +79,7 @@ def main(args):
|
||||
log.info('%.3f seconds of training audio',
|
||||
training_size / wave.bytes_per_second)
|
||||
|
||||
reader = Reader(args.input, 64 << 10)
|
||||
reader = stream.Reader(args.input, bufsize=(64 << 10), eof=True)
|
||||
data = itertools.chain.from_iterable(reader)
|
||||
encoded = itertools.chain.from_iterable(ecc.encode(data))
|
||||
modulate(args.output, bits=common.to_bits(encoded))
|
||||
@@ -115,7 +99,9 @@ if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--silence-start', type=float, default=1.0)
|
||||
p.add_argument('--silence-stop', type=float, default=1.0)
|
||||
p.add_argument('-i', '--input', type=argparse.FileType('r'), default=sys.stdin)
|
||||
p.add_argument('-o', '--output', type=argparse.FileType('w'), default=sys.stdout)
|
||||
p.add_argument('-i', '--input', type=argparse.FileType('rb'),
|
||||
default=sys.stdin)
|
||||
p.add_argument('-o', '--output', type=argparse.FileType('wb'),
|
||||
default=sys.stdout)
|
||||
args = p.parse_args()
|
||||
main(args)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import numpy as np
|
||||
from numpy import linalg
|
||||
|
||||
import common
|
||||
from config import Ts, Nsym
|
||||
from . import common
|
||||
from .config import Ts, Nsym
|
||||
|
||||
|
||||
class Filter(object):
|
||||
|
||||
@@ -1,43 +1,57 @@
|
||||
import time
|
||||
import itertools
|
||||
import logging
|
||||
|
||||
import common
|
||||
import wave
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Timeout(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Reader(object):
|
||||
|
||||
SAMPLES = 4096
|
||||
BUFSIZE = int(SAMPLES * wave.bytes_per_sample)
|
||||
WAIT = 0.1
|
||||
TIMEOUT = 2.0
|
||||
|
||||
def __init__(self, fd):
|
||||
def __init__(self, fd, data_type=None, bufsize=4096,
|
||||
eof=False, timeout=2.0, wait=0.2):
|
||||
self.fd = fd
|
||||
self.data_type = data_type if (data_type is not None) else lambda x: x
|
||||
self.bufsize = bufsize
|
||||
self.eof = eof
|
||||
self.timeout = timeout
|
||||
self.wait = wait
|
||||
self.total = 0
|
||||
self.check = None
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.next()
|
||||
|
||||
def next(self):
|
||||
block = bytearray()
|
||||
finish_time = time.time() + self.TIMEOUT
|
||||
if self.eof:
|
||||
data = self.fd.read(self.bufsize)
|
||||
if data:
|
||||
self.total += len(data)
|
||||
block.extend(data)
|
||||
return block
|
||||
else:
|
||||
raise StopIteration()
|
||||
|
||||
finish_time = time.time() + self.timeout
|
||||
while time.time() <= finish_time:
|
||||
left = self.BUFSIZE - len(block)
|
||||
left = self.bufsize - len(block)
|
||||
data = self.fd.read(left)
|
||||
if data:
|
||||
self.total += len(data)
|
||||
block.extend(data)
|
||||
|
||||
if len(block) == self.BUFSIZE:
|
||||
values = common.loads(str(block))
|
||||
if len(block) == self.bufsize:
|
||||
values = self.data_type(block)
|
||||
if self.check:
|
||||
self.check(values)
|
||||
return values
|
||||
|
||||
time.sleep(self.WAIT)
|
||||
time.sleep(self.wait)
|
||||
|
||||
raise IOError('timeout')
|
||||
|
||||
|
||||
def iread(fd):
|
||||
return itertools.chain.from_iterable(Reader(fd))
|
||||
raise Timeout(self.timeout)
|
||||
|
||||
@@ -6,7 +6,7 @@ import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
import config
|
||||
from . import config
|
||||
Fs = int(config.Fs) # sampling rate
|
||||
|
||||
bits_per_sample = 16
|
||||
@@ -27,7 +27,7 @@ def record(fname, **kwargs):
|
||||
|
||||
|
||||
def launch(*args, **kwargs):
|
||||
args = map(str, args)
|
||||
args = list(map(str, args))
|
||||
log.debug('$ %s', ' '.join(args))
|
||||
p = sp.Popen(args=args, **kwargs)
|
||||
p.stop = lambda: os.kill(p.pid, signal.SIGKILL)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
killall -q aplay arecord
|
||||
./calib.py send &
|
||||
python -m amodem.calib send &
|
||||
SENDER_PID=$!
|
||||
./calib.py recv
|
||||
python -m amodem.calib recv
|
||||
|
||||
kill -INT $SENDER_PID
|
||||
@@ -32,20 +32,20 @@ run_src dd if=/dev/urandom of=$TEST_DIR/data.send bs=125kB count=1 status=none
|
||||
SRC_HASH=`run_src sha256sum $TEST_DIR/data.send`
|
||||
|
||||
# modulate data into audio file
|
||||
run_src "./send.py <$TEST_DIR/data.send >$TEST_DIR/audio.send"
|
||||
run_src "python -m amodem.send -i $TEST_DIR/data.send -o $TEST_DIR/audio.send"
|
||||
|
||||
# stop old recording and start a new one
|
||||
run_src killall -q aplay || true
|
||||
run_dst killall -q arecord || true
|
||||
|
||||
run_dst "./wave.py record $TEST_DIR/audio.recv" &
|
||||
run_dst "python -m amodem.wave record $TEST_DIR/audio.recv" &
|
||||
sleep 1 # let audio.recv be filled
|
||||
|
||||
# play the modulated data
|
||||
run_src ./wave.py play $TEST_DIR/audio.send &
|
||||
run_src "python -m amodem.wave play $TEST_DIR/audio.send" &
|
||||
|
||||
# start the receiever
|
||||
run_dst "./recv.py <$TEST_DIR/audio.recv >$TEST_DIR/data.recv"
|
||||
run_dst "python -m amodem.recv -i $TEST_DIR/audio.recv -o $TEST_DIR/data.recv"
|
||||
|
||||
# stop recording after playing is over
|
||||
run_src killall -q aplay || true
|
||||
@@ -25,13 +25,13 @@ def test_iterate():
|
||||
def test_split():
|
||||
L = [(i*2, i*2+1) for i in range(10)]
|
||||
iters = common.split(L, n=2)
|
||||
assert zip(*iters) == L
|
||||
assert list(zip(*iters)) == L
|
||||
|
||||
for i in [0, 1]:
|
||||
iters = common.split(L, n=2)
|
||||
iters[i].next()
|
||||
next(iters[i])
|
||||
try:
|
||||
iters[i].next()
|
||||
next(iters[i])
|
||||
assert False
|
||||
except IndexError as e:
|
||||
assert e.args == (i,)
|
||||
@@ -43,8 +43,8 @@ def test_icapture():
|
||||
z = []
|
||||
for i in common.icapture(x, result=y):
|
||||
z.append(i)
|
||||
assert x == y
|
||||
assert x == z
|
||||
assert list(x) == y
|
||||
assert list(x) == z
|
||||
|
||||
|
||||
def test_dumps_loads():
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import os
|
||||
from cStringIO import StringIO
|
||||
try:
|
||||
from cStringIO import StringIO as BytesIO
|
||||
except ImportError:
|
||||
from io import BytesIO # Python 3
|
||||
|
||||
import numpy as np
|
||||
|
||||
from amodem import send
|
||||
@@ -10,40 +14,48 @@ import logging
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
format='%(asctime)s %(levelname)-12s %(message)s')
|
||||
|
||||
|
||||
class Args(object):
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
||||
def run(size, chan):
|
||||
tx_data = os.urandom(size)
|
||||
tx_audio = StringIO()
|
||||
send.main(Args(silence_start=1, silence_stop=1, input=StringIO(tx_data), output=tx_audio))
|
||||
tx_audio = BytesIO()
|
||||
send.main(Args(silence_start=1, silence_stop=1,
|
||||
input=BytesIO(tx_data), output=tx_audio))
|
||||
|
||||
data = tx_audio.getvalue()
|
||||
data = common.loads(data)
|
||||
data = chan(data)
|
||||
data = common.dumps(data * 1j)
|
||||
rx_audio = StringIO(data)
|
||||
rx_audio = BytesIO(data)
|
||||
|
||||
rx_data = StringIO()
|
||||
rx_data = BytesIO()
|
||||
recv.main(Args(skip=100, input=rx_audio, output=rx_data))
|
||||
rx_data = rx_data.getvalue()
|
||||
|
||||
assert rx_data == tx_data
|
||||
|
||||
|
||||
def test_small():
|
||||
run(1024, lambda x: x)
|
||||
|
||||
|
||||
def test_large():
|
||||
run(54321, lambda x: x)
|
||||
|
||||
|
||||
def test_attenuation():
|
||||
run(5120, lambda x: x * 0.1)
|
||||
|
||||
|
||||
def test_low_noise():
|
||||
r = np.random.RandomState(seed=0)
|
||||
run(5120, lambda x: x + r.normal(size=len(x), scale=0.0001))
|
||||
|
||||
|
||||
def test_medium_noise():
|
||||
r = np.random.RandomState(seed=0)
|
||||
run(5120, lambda x: x + r.normal(size=len(x), scale=0.001))
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from amodem import stream
|
||||
import subprocess as sp
|
||||
|
||||
script = r"""
|
||||
script = br"""
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
sys.stdout.write(b'\x00' * 6400)
|
||||
sys.stdout.write('\x00' * 6400)
|
||||
sys.stderr.write('.')
|
||||
"""
|
||||
|
||||
@@ -19,17 +19,17 @@ def test_read():
|
||||
p.stdin.close()
|
||||
f = stream.Reader(p.stdout)
|
||||
|
||||
result = zip(range(10), f)
|
||||
result = list(zip(range(10), f))
|
||||
p.kill()
|
||||
|
||||
j = 0
|
||||
for i, buf in result:
|
||||
assert i == j
|
||||
assert len(buf) == f.SAMPLES
|
||||
assert len(buf) == f.bufsize
|
||||
j += 1
|
||||
|
||||
try:
|
||||
for buf in f:
|
||||
pass
|
||||
except IOError as e:
|
||||
assert str(e) == 'timeout'
|
||||
except stream.Timeout as e:
|
||||
assert e.args == (f.timeout,)
|
||||
|
||||
Reference in New Issue
Block a user