Zrób modem (programowy)!

14

Cel

Zaprojektować MO dulator / dem odulator parę precyzyjnie przekazywać dane tak szybko, jak to możliwe na symulowane Plain Old Telephone Service (POTS) .

Kroki

  1. Wygeneruj losowe ( /dev/randomlub tym podobne) dane, których przesłanie zajmie 3-4 sekundy
  2. Zmoduluj dane za pomocą modulatora, aby utworzyć plik audio
  3. Prześlij plik audio przez symulator POTS . Jeśli nie masz języka Python / Scipy, możesz przesłać plik za pomocą formularza lub wykonać żądanie interfejsu API JSON.
  4. Demoduluj plik audio z powrotem do danych binarnych
  5. Sprawdź, czy dane wejściowe i wyjściowe są równe * (limit 1 na 1000 bitów może zostać uszkodzony)
  6. Wynik to liczba przesłanych bitów podzielona przez długość pliku audio (bitów / sekundę)

Zasady

  • Plik wejściowy musi wynosić 3-4 sekundy, 44,1 kHz, mono.
  • Uruchom symulator z SNR 30 dB (jest to ustawienie domyślne)
  • Demodulator musi zrekonstruować przesyłane dane z bitowym współczynnikiem błędów nie większym niż 10-3 (1 na tysiąc bitów).
  • Kompresja cyfrowa nie jest dozwolona (tzn. Kompresowanie danych. Jest to poza zakresem wyzwania).
  • Bez próby wpychania danych na częstotliwości powyżej 4 kHz. (Moje filtry nie są idealne, ale są względnie podobne do POTS przy stosunkowo niewielkiej liczbie dotknięć).
  • Jeśli twój protokół modemu wymaga krótkiej preambuły (nie dłuższej niż 1 sekunda) do synchronizacji / kalibracji odbiornika, nie jest karany.
  • Jeśli to możliwe, umieść plik audio w dostępnym miejscu, abyśmy mogli posłuchać kakofonii dźwięków i dźwięków.

Przykład

Oto przykładowy notatnik, który demonstruje modulację / demodulację za pomocą prostego „kluczowania on-off” (dołączone próbki audio!).

Dałoby to 100 (bitów / sekundę). Zauważ, że transmituje ze znacznie gorszym SNR 5 dB.

Nick T.
źródło
2
Czy różni się to od zwykłego wyzwania „kompresuj te dane binarne”? Jeśli tak, czy możesz wyjaśnić, jak dokładnie się różni?
Klamka
1
Tutaj modulujesz dane (zamieniając je w coś analogowego), a potem odwrotnie. Można to nazwać „kompresją analogową”
Nick T
Niestety nie jestem pewien, czy rozumiem, jak działa to wyzwanie. Słowo „modulować” nie pojawia się nawet w linkowanym artykule w Wikipedii. Czy możesz podać więcej podstawowych informacji lub wyjaśnić specyfikację?
Klamka
4
wget wikipedia.org/Special:Random | grep title | texttospeech audio.wav speechtotext POTSaudio.wav | wget wikipedia/wiki/$text
TessellatingHeckler
1
To niesamowite wyzwanie, postaram się znaleźć czas na przesłanie odpowiedzi!
GoatInTheMachine

Odpowiedzi:

7

MATLAB, 1960 bps

Oto moja zaktualizowana próba:

fs = 44100; %44.1kHz audio rate
fc = 2450;  %2.45kHz carrier - nice fraction of fs!
fsym = fc/5; %symbol rate

tmax = 4; %about 4 seconds worth

preamblesyms = 6;

t = 1/fs:1/fs:(tmax+preamblesyms/fsym);

symbols = preamblesyms+fsym*tmax;
symbollength = length(t)/symbols;
bits = symbols*3;
bitstream = [zeros(1,preamblesyms*3),rand(1,bits-preamblesyms*3)>0.5]; %Add a little preamble of 18 bits
data = bin2dec(char(reshape(bitstream,3,symbols)'+'0'))';

greycode = [0 1 3 2 6 7 5 4];

%Encode the symbols using QAM8 - we use effectively grey code so that
%adjacent symbols in the constellation have only one bit difference
%(minimises error rate)
encoded = zeros(2,symbols);
encoded(1,data==1) = 1/sqrt(2);
encoded(1,data==3) = 1;
encoded(1,data==2) = 1/sqrt(2);
encoded(1,data==7) = -1/sqrt(2);
encoded(1,data==5) = -1;
encoded(1,data==4) = -1/sqrt(2);
encoded(2,data==0) = 1;
encoded(2,data==1) = 1/sqrt(2);
encoded(2,data==2) = -1/sqrt(2);
encoded(2,data==6) = -1;
encoded(2,data==7) = -1/sqrt(2);
encoded(2,data==4) = 1/sqrt(2);

%Modulate onto carrier
carrier = [sin(2*pi*fc*t);cos(2*pi*fc*t)];
signal = reshape(repmat(encoded(1,:)',1,symbollength)',1,[]);
signal(2,:) = reshape(repmat(encoded(2,:)',1,symbollength)',1,[]);
modulated = sum(signal.*carrier)';

%Write out an audio file
audiowrite('audio.wav',modulated,fs);

%Wait for the user to run through the POTS simulator
input('');

%Read in the filtered data
filtered=audioread('audio.pots-filtered.wav')';

%Recover the two carrier signals
preamblecos = filtered(symbollength+1:symbollength*2);
preamblesin = filtered(symbollength+1+round(symbollength*3/4):symbollength*2+round(symbollength*3/4));

%Replicated the recovered carriers for all symbols
carrierfiltered = [repmat(preamblesin,1,symbols);repmat(preamblecos,1,symbols)];

%Generate a demodulation filter (pass up to 0.66*fc, stop at 1.33*fc
%(really we just need to kill everything around 2*fc where the alias ends up)
d=fdesign.lowpass('Fp,Fst,Ap,Ast',0.05,0.1,0.5,60);
Hd = design(d,'equiripple');

%Demodulate the incoming stream
demodulated = carrierfiltered .* [filtered;filtered];
demodulated(1,:)=filtfilt(Hd.Numerator,1,demodulated(1,:));
demodulated(2,:)=filtfilt(Hd.Numerator,1,demodulated(2,:));

%Split signal up into bit periods
recovereddemodulated=[];
recovereddemodulated(1,:,:) = reshape(demodulated(1,:),symbollength,symbols);
recovereddemodulated(2,:,:) = reshape(demodulated(2,:),symbollength,symbols);

%Extract the average level for each bit period. Only look at the second
%half to account for slow rise times in the signal due to filtering
recoveredsignal=mean(recovereddemodulated(1,round(symbollength/2):symbollength,:));
recoveredsignal(2,:)=mean(recovereddemodulated(2,round(symbollength/2):symbollength,:));

%Convert the recovered signal into a complex number.
recoveredsignal=recoveredsignal(2,:) + 1j*recoveredsignal(1,:);

%Determine the magnitude and angle of the symbol. The phase is normalised
%to pi/4 as that is the angle between the symbols. Rounding this to the
%nearest integer will tell us which of the 8 phases it is closest to
recoveredphase = round(angle(recoveredsignal)/(pi/4));
recoveredphase = mod(recoveredphase+8,8)+1; %Remap to an index in the grey code vector.

%Determine the symbol in the QAM8 constellation
recoveredencoded=greycode(recoveredphase);
recoveredencoded(1:preamblesyms)=0; %Assume the preamble is correct for comparison

%Turn it back in to a bit stream
bitstreamRecovered = reshape(dec2bin(recoveredencoded)'-'0',1,[]);

%And check if they are all correct...
if(all(bitstream==bitstreamRecovered))
    disp(['Woop, ' num2str(fsym*4) 'bps']);
else
    error('Its corrupt Jim.');
end

Od mojej pierwszej próby grałem trochę. Na początku jest teraz mała preambuła (18 bitów, ale może być krótsza), która zawiera tylko falę cosinus. Wyodrębniłem to i zreplikowałem, aby utworzyć poprawnie fazowane nośniki sinusoidalne i cosinusowe do demodulacji - ponieważ jest to bardzo krótka preambuła, nie policzyłem jej w przepływności zgodnie z twoimi instrukcjami.

Również od pierwszej próby używam teraz konstelacji QAM8, aby osiągnąć 3 bity na symbol zamiast 2. To skutecznie podwaja szybkość transferu. Tak więc z nośnikiem ~ 2,4 kHz osiągam teraz 1960bps.

Poprawiłem także wykrywanie symboli, aby na uśrednianie nie wpływały powolne czasy narastania spowodowane przez filtrowanie - w zasadzie tylko druga połowa każdego okresu bitowego jest uśredniana, aby usunąć wpływ czasów narastania.

Nadal nigdzie w pobliżu teoretycznej przepustowości kanału 40 kb / s według teorii Shannona-Hartleya (przy założeniu 30 dB SNR)

Dla tych, którzy lubią okropne dźwięki, oto nowy wpis:


A jeśli ktoś jest zainteresowany, jest to poprzedni wpis 960bps

Tom Carpenter
źródło
Punktacja to tylko szybkość transferu, więc zachowaj swój kod w czystości. Dodałem sugestię, aby gdzieś przechowywać plik audio, jeśli jest to łatwe dla fanów: D
Nick T
Prześlę dźwięk do mojej witryny. Brzmi dość dziwnie!
Tom Carpenter
Przesłano plik audio @NickT - patrz link na dole wpisu.
Tom Carpenter
Jeśli masz konto SoundCloud, możesz przesłać plik audio i opublikować link, który będzie można odtworzyć w swoim poście. ( Przykład )
Calvin's Hobbies
@NickT dzięki. Utworzyłem konto SoundCloud i przesłałem je. Zrobiłem również zaktualizowaną wersję z podwójną szybkością przesyłania danych :)
Tom Carpenter