Analiza pisków, czyli jak zrobić zdalnie selfie
W moim ulubionym niemieckim sklepie w którym zaopatruję się w życiodajne składniki pokarmowe czasami trafiają się dziwne produkty nieznanych szerzej wewnętrznych marek. Skuszony sekcją z obniżkami cen zakupiłem sobie zdalny wyzwalacz do selfie. Nie, żebym specjalnie zamierzał robić selfie, bo ich nie robię, ale mam w głowie pewien projekt, do którego zdalny wyzwalacz mógłby być użyteczny.
Niestety, zaślepiła mnie przecena. "Selfie Remote Control Release" firmy SilverCrest na opakowaniu miał owszem wspomnianą kompatybilność z Androidem i iOS, którą zignorowałem, bo przecież i tak do mojego Windows w jakiś sposób go podłączę. Z tyłu opakowania miał wspomniane, że działa na zasięgu do 8 metrów, że ma bezpłatną aplikację, że może wyzwolić kilka telefonów z zasięgu.
I to ostatnie zdanie spowodowało, że podniosłem brwi. Ale jak to przycisk udający klawiaturę BT miałby się sparować z kilkoma telefonami naraz? Dziwne. Kiedy w domu rozpakowałem gadżet coś mnie tknęło. Na opakowaniu nie było ani słowa o Bluetooth. Ani w instrukcji. To urządzenie nie działało w oparciu o tę technologię. Zatem więc jak?
Nacisnąłem więc przycisk (jeden z dwóch) dostępnych na tym czymś, a on wydał z siebie pisk. Najpierw dość zwykły, a potem wysoki ćwierk. Dziwaczne. Odpowiednią aplikację pobrałem na leżącego w domu Nexusa 4 z Androidem 5 na którym czasami coś testuję. I nacisnąłem przycisk, a on zrobił zdjęcie. Łał. Nie, serio. Mamy urządzenie, które piszczy, a telefon na to piszczenie reaguje zrobieniem zdjęcia. I to (po testach) wychodzi na to, że "słyszy" ten pisk całkiem nieźle.
Pozostawało pytanie - co to za pisk i jak on działa?
Zacząłem od próby dekompilacji aplikacji dla Androida. Zawiera ona dużo śmieci, w zasadzie jest prostą aplikacją do robienia zdjęć (i wrzucania ich na Facebooka), ale najbardziej interesująca wydawała się klasa o nazwie Detector w pakiecie com.magcom.pitchdetector. Zobaczenie jej zawartości nie przyniosło jednak rozwiązania:
public class Detector { static { System.loadLibrary("detector"); } /* (...) */ private static native int apiDetect(byte[] bArr, int i); private static native void apiInit(Object obj, int[] iArr, int[] iArr2, int i, int i2, int i3, int i4, int i5, int i6, int i7); private static native void apiTerminate(); }
Aha, native. W lib/armeabi-v7a/ znajdowała się za to biblioteka libdetector.so. Czyli całe wykrywanie robi natywna biblioteka w języku C, a dekompilacja czegoś takiego to już trudniejsza sprawa. Ale sama nazwa - pitch detector - sugeruje wykrywanie określonego tonu (a magcom.com sugeruje oryginalnego chińskiego producenta dla tego gadżetu).
Urządzenie ma dwa przyciski - jeden powoduje zrobienie zdjęcia, a drugi - zmianę aparatu używanego w aplikacji. Jeden powoduje jeden pisk i wysoki dźwięk, a drugi dwa piski i wysoki dźwięk. Strzeliłem zatem, że ten wysoki dźwięk zapewne koduje w swojej częstotliwości za jaką funkcję odpowiada.
Sprawa wydawała się prosta. Wkładane mi przez lata studiów całki zaczęły się kłębić w głowie, w której powstała myśl w postaci użycia Dyskretnej Transformaty Fouriera. DFT przerabia sygnał z dziedziny czasu na dziedzinę częstotliwości. Istnieje szansa, że pisk będzie dość jednorodny i będzie wysokim "pikiem" na wykresie. Pozostało nagrać brelok, wykonać transformatę i zobaczyć, co się stanie.
Nagrałem do pliku WAV, znalazłem odpowiedni fragment kodu w Pythonie, wyrysowałem transformatę z użyciem znalezionego w Internecie fragmentu kodu. Wygenerowany wykres wyglądał następująco:
Jak widać, największy "pik" jest przy około 25 kHz. Ale coś z tą wartością jest nie tak. Nagrywałem dźwięk z próbkowaniem 44,1 kHz. Niemożliwe jest w takim przypadku poprawne uzyskanie dźwięku o częstotliwości powyżej 22,05 kHz (częstotliwość Nyquista ). Sięgnąłem więc po inną metodę, uświadamiając sobie, że przecież Audacity, którym nagrywam, również posiada funkcję wyrysowania widma.
Widmo częstotliwościowe dla całego dźwięku było niestety dość "brzydkie", odbiegało od mojej wizji pięknego czystego piku. Spójrzmy jednak na spektrogram sygnału (włączenie spektrogramu w Audacity zajęło mi kilka dłuższych chwil, tak nawiasem):
A zwłaszcza na wyższe częstotliwości:
Wyraźnie widać trwające 0,25 sekundy "coś", a potem drugie "coś", mniej więcej tak samo długie. Analiza częstotliwościowa pierwszego fragmentu wykazuje wyraźny pik na 15876 Hz, a drugiego na 15595 Hz. Czyżby to to?
Spróbujmy zatem zasymulować telefonowi odtworzenie tego dźwięku (i liczmy na to, że głośnik potrafi z siebie wydać taki odgłos).
Chwila pełna napięcia...
Działa! Zrobił zdjęcie (widać odpowiednie ikonki na ekranie aplikacji na telefonie). Kolejne zabawy jednak przyniosły gorszy wniosek - on nie opiera się tylko na wykrywaniu określonych częstotliwości, ale również na ich czasie trwania. I chyba na większej liczbie częstotliwości, bo wycięcie sporej części z nich filtrem górnoprzepustowym spowodowało, że nagranie przestało działać [dopisek: potem okazało się, że aplikacja po prostu przestała działać, a po paru próbach zawiesiła telefon całkowicie - filtr górnoprzepustowy na 10 kHz nie powoduje błędów, działa wykrycie]
Ale dla mnie było to już wystarczające, bo skoro wiem jak piszczy, to mogę napisać program, który te piski rozpozna. Algorytm, który wydawał się pasujący to filtr Goertzela, numerycznie efektywny dla wykrywania obecności określonej częstotliwości w sygnale.
private static double GoertzelFilter(float[] samples, double freq, int start, int end) { double sPrev = 0.0; double sPrev2 = 0.0; int j; double normalizedfreq = freq / SIGNAL_SAMPLE_RATE; double coeff = 2 * Math.Cos(2 * Math.PI * normalizedfreq); for (j = start; j < end; j++) { double s = samples[j] + coeff * sPrev - sPrev2; sPrev2 = sPrev; sPrev = s; } double power = sPrev2 * sPrev2 + sPrev * sPrev - coeff * sPrev * sPrev2; return power; }
Prosty przykład + świetna biblioteka NAudio do robienia wszystkiego z dźwiękiem dla C# zaowocowały prostą aplikacją, najpierw analizującą sygnał zapisany w pliku WAV, a potem "na bieżąco" wykrywającą obecność częstotliwości 15595 Hz.
I już witaliśmy się z gąską, ale trzeba to jeszcze zaprząc do aplikacji dla Windows Phone, a najlepiej dla Windows 10. Taką aplikację popełniłem, ale wymaga jeszcze pewnego dopasowania dla lepszego wykrywania tonów, a obsługa aplikacji UWP w NAudio sprawia jeszcze trochę problemów (lepiej jest dla WinRT) - przede wszystkim wersja dla UWP jest "do samodzielnego skompilowania", jeszcze nie dostępna oficjalnie.
A samo urządzonko? Rozkłada się na dwa kawałki, z których jeden może służyć jako "nóżka" do telefonu, zasilane jest jedną malutką baterią. Kosztowało mnie około 10 zł i trochę zabawy, a planowałem kupić zasadniczo zdalny wyzwalacz na Bluetooth. Zaślepiony ceną jednak kupiłem coś takiego...
Pomyślałem więc, że wykorzystam to w pewnym innym projekcie, tworząc wirtualne urządzenie wejściowe i oprogramuję sobie te piski. W moim laptopie jednak nagranie pisku z urządzonka wywołało inny efekt w Audacity:
Okazuje się, że laptop wyposażony jest w filtr dolnoprzepustowy, który wycina wysokie częstotliwości. I w tym momencie napis na opakowaniu gadżetu o niekompatybilności z pewnymi odmianami Sony Xperia jest jasny - one również wyposażone są w taki filtr.