Радиотелефончик на смартфоне

от автора

image

Немало воды утекло со времени публикации цикла про стриминг видео на Андроид устройствах, но вот ручки добрались и до аудио потоков. Не то, чтобы это была какая-то более заковыристая тема про сравнению с видео, даже наоборот, сложно придумать что-то проще, ибо Audio API не менялось, дай бог памяти, с 2012 года, если не раньше. И не стоило бы, ради этого пилить короткий пост, если бы не зудящая мысля — а на какое расстояние и каким образом можно передать сей аудио поток, если мы будем использовать для этого только два смартфона без всякой мобильной связи и внешних точек доступа.

Если вам интересно узнать, что из этого получилось, то прошу проследовать под кат…

Понятно, что в начале любой здравомыслящий человек посмотрит, собственно, на радиомодуль своего сотового телефона. Действительно, размер соты для уверенного приёма, может составлять несколько километров в поперечнике (у AMPS вообще до 20 км, да и даже LTE ненамного меньше). И если с одной стороны базовая станция может подать весьма мощный сигнал, который легко детектируется, то с другой стороны и сам смартфон обладая, пусть и десятками и сотнями милливатт выходной мощности, но также доносит свой голосок до станции. А если роль базовой станции сыграет другой телефон? Ясно, что расстояние будет в этом случае всё равно меньше, поскольку усилители входных сигналов на станции явно будут помощнее, чем на телефоне, но не в принципиальной же степени (тут мы сразу оговариваемся, что эксперимент проводится в местах, где нет мобильного покрытия, чтобы никому ни в коем разе не мешать).
Другое дело, что API для работы с радиомодулем в современных смартфонах, если и существует (допустим для разработки), то оно наверняка, закопано очень глубоко и совершенно недоступно для пет девелоперов. И если уж делать радиотелефон на базе сотового, то логичнее брать древние модели, по которым уже всплыла внутренняя документация, и на которых ещё можно сыскать какие-нибудь тестовые разъемы и даже к ним подпаяться для оболванивания их электронных мозгов. И таки да, буквально месяц назад на Хабре же вышла прекрасная статья по этой теме, которую я с удовольствием прочитал.

Но как и вы сами можете убедиться, процесс подъёма связи здесь весьма непростой, требует хождения по помойкам, усидчивости и прямых рук даже с исчерпывающим руководством, а результат — коннект в пределах не дальше комнаты, необходимость тащить синхросигнал с чьей-то БС, плюс некоторая вероятность прибытия людей в чёрном. Понятное дело, что какая-то практичность проекта и не подразумевалась, главное было в торжестве человеческого гения, но мне мнилось замахнуться на несколько большие расстояния, чтобы это было больше похоже на настоящую радиосвязь.

Поэтому подивившись силе разума гиков, я обратился к тому, что у меня было уже в руках — двум смартфонам фирмы Сяоми, одному поновее, на Android 12 и второму постарше, на десятке.
Ну, блютус отпал сразу, поскольку на расстоянии на котором он стабильно работает, вы без труда можете переговариваться и без использования телефонов. Работа непосредственно с GSM модулем, тоже сами понимаете, была из разряда ненаучной фантастики. Так что оставался лишь старый добрый Wi-Fi. Идея была такова: поднимаем, значит, на одном смарте локальную сеть (или local Hotspot по английски), а вторым смартфоном к этой сети цепляемся и начинаем гнать звук соответственно от их микрофонов к противоположным динамикам двумя аудио потоками — короче устанавливаем голосовую связь! А потом шаг за шагом проверяем, насколько и на каком расстоянии эта связь устойчиво поддерживается.

Итак, задача понятна, приступаем к ее решению. Начнём естественно с аудио. Как уже упоминалось, API это весьма древнее, и если вам хочется асинхронности и колбэков для записи и приёма данных, то это не сюда. Добро пожаловать в отдельный поток и в цикл while do.

Вообще, самый простой способ записи с микрофона и последующего его воспроизведения через динамик — это использование класса MediaRecorder. Там даже с потоками не надо связываться. Просто учреждаем экземпляр MediaRecorder, определяем куда писать и из какого источника (в нашем случае это микрофон.

       mAudioRecorder = new MediaRecorder();          mAudioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);         mAudioRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);         mAudioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);         mCurrentFile = new      File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)        mAudioRecorder.setOutputFile(mCurrentFile.getPath());        mAudioRecorder.start(); 

А проигрываем соответственно через MediaPlayer():

      mAudioPlayer=new MediaPlayer();       mAudioPlayer.setDataSource(mCurrentFile.getPath());       mAudioPlayer.prepare();       mAudioPlayer.start(); 

Тут настолько всё просто, что даже не требует пояснений. Не забудьте только оформить разрешение на использование микрофона и файлового хранилища.

     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />     <uses-permission android:name="android.permission.RECORD_AUDIO" /> 

Но как легко видеть, для передачи живого аудио потока эти классы подходят мало. Здесь нам пригодится другой класс — AudioRecord, который может работать непосредственно с байтовым потоком (raw data). А чтобы это аудио поток так же потом проигрывать в реальном режиме времени, сгодится класс AudioTrack.

Вообще AudioRecord может так же невозбранно писать аудиопоток в файл как и MediaRecorder, но нам такая его способность не понадобится.

Итак, пишем:

private AudioRecord audioRecord; private int SampleRate = 8000; private int minBufferSize;   protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);  ......   audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, SampleRate,                  AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,                   minBufferSize*2); 

Или можно по модному через билдер, получится то же самое:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {             audioRecord = new AudioRecord.Builder()                     .setAudioSource(MediaRecorder.AudioSource.MIC)                     .setAudioFormat(new AudioFormat.Builder()                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)                             .setSampleRate(SampleRate)                             .setChannelMask(AudioFormat.CHANNEL_IN_MONO)                             .build())                     .setBufferSizeInBytes(2 * minBufferSize)                     .build(); 

Как мы видим, у нас там есть переменные ENCODING_PCM_16BIT, SampleRate и minBufferSize. Рассмотрим их чуть подробнее.

Для перевода аналогового потока с микрофона в цифровой вид здесь используется формат PCM — импульсно кодовая модуляция. То есть с определенной частотой дискретизации (SampleRate) уровень аудио сигнала измеряется и переводится в формат 8 или 16 двоичных разрядов (или даже в формат с плавающей запятой для особых гурманов). Какая же должна быть частота и разрядность в нашем случае? В принципе можно взять формат, хоть как для аудио CD. Там, как известно, частота дискретизации равна 44,1 кГц, а разрядность 16 бит. Но тогда наш аудио поток изрядно распухнет в размерах, даже если мы будем записывать звук в одноканальном (моно) режиме ибо:

44100 х 16 = 705 600 бит в секунду (и это не считая всяких служебных).

И именно с такой скоростью нам придется передавать их по сети. Кажется многовато.
Но опять же, мы передаем не симфоническую музыку, а просто голос. Поэтому, памятуя о ширине обычной телефонной линии в 3 кГц и теореме Котельникова, согласно которой, для восстановления сигнала данной ширины спектра, надо всего лишь удвоить частоту дискретизации, то можно ограничиться SampleRate равным 8000. Это вообще полоса в четыре килогерца — даже шире, чем нам нужно.

Можно использовать разрядность 8 бит и добиться скорости передачи потока всего лишь — 8000 x 8 = 64 000 бит в секунду, что как бы является стандартной скоростью цифровой телефонии. Поток несжатый, но нам городить лишние сущности, пока ни к чему. Правда, при уменьшении разрядности до 8 бит, появляются какие-то раздражающие фоновые шумы (хотя голос вполне разборчив), поэтому можно сильно не экономить и оставить разрядность в 16 бит, тем более, что скорость передачи данных даже 16 кбайт в секунду наш смартфон не обессилит.

Что же касается переменной minBufferSize, то это минимальный размер внутреннего буфера, при котором объект AudioRecord сможет работать.

И вычисляется он по формуле:

minBufferSize = AudioRecord.getMinBufferSize(                 SampleRate, AudioFormat.CHANNEL_IN_MONO,                 AudioFormat.ENCODING_PCM_16BIT); 

Формула не слишком очевидная, поэтому ради интереса, я вывел значение minBufferSize в лог. Он оказался равен 640. Наверное, байт. Во всяком случае, на фоне формирования двухбайтных отсчетов восемь тысяч раз в секунду, цифра невелика и поэтому не зря, видимо, все советуют её (то есть размер внутреннего буфера) увеличивать раз в десять. Ну, как минимум, в два.

После учреждения же всех начальных параметров, можно переходить собственно к запуску инстанса AudioRecord. Запускается он, естественно, в отдельном потоке в цикле while (true или какой-нибудь триггер).

  public void run() {                      try {                  audioRecord.startRecording();                 byte[] outData = new byte[minBufferSize];                 try {                     while (mIsCapturing) {                         // read audio data from internal mic                         audioRecord.read(outData,0, outData.length);                     }                 } finally {                     audioRecord.stop();                 }             } finally {                 audioRecord.release();                      } 

То есть, пока переменная mIsCapturing установлена, audioRecord по готовности аудио данных, кладёт содержимое заполненного внутреннего буфера в байтовый массив outData. И нам остается лишь взять этот массив (пока он не затёрся новыми значениями) и отправить его, куда душа пожелает. Можно в байтовый поток, а потом в файл, но можно и в датаграммный пакет, который потом поедет по сети Wi Fi к нужному адресату.

Обратный процесс (из цифры в ухо), тоже больших проблем не доставляет. Главное там, задать те же параметры дискретизации, что и при записи. Здесь с байтами работает класс AudioTrack.
Правда ему нужны дополнительные параметры в лице AudioAttributes и AudioFormat поэтому писанины получается несколько больше.

AudioAttributes audioAttributes = new AudioAttributes.Builder()                     .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) // defines the type of content being played                     .setUsage(AudioAttributes.USAGE_MEDIA) // defines the purpose of why audio is being played in the app                     .build();              AudioFormat audioFormat = new AudioFormat.Builder()                     .setEncoding(AudioFormat.ENCODING_PCM_16BIT) // we plan on reading byte arrays of data, so use the corresponding encoding                     .setSampleRate(SampleRate)                     .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)                     .build();              AudioTrack audioIn = new AudioTrack(audioAttributes, audioFormat,                     2*minBufferSize, AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE); 

Дальше, так же в отдельном потоке запускаем приём байт (пока мы молчаливо предполагаем, для примера, что они до нас как-то добираются частями, допустим, по 1024 байт).

public void run() {              byte [] inSoundData = new byte[1024];   while (true) {                                      try                 {                     audioIn.write(inSoundData, 0, inSoundData.length);                 }                 catch (Exception e)                 {                     // handle exception                 }                 audioIn.play();             } } 

Тут, главное запускать audioIn.play() после прихода каждой порции данных. Потому как в интернете я сыскал пример кода, где автор шутник расположил его после цикла while. И нескоро я разобрался, почему вместо нормального воспроизведения файла (в начале я писал данные в файл), я слышал что-то вроде короткого ««блурп…» или вообще была полная тишина.

Итак, с записью и воспроизведением мы разобрались. Осталось лишь отправить и принять байты аудио данных по сети. Пока упростим задачу и примем, что сама локальная беспроводная сеть у нас есть, наш смартфон к ней подсоединён и имеет свой постоянный IP адрес. Можно конечно взять для демонстрации адрес 127.0.0.1, но мы не будем проявлять малодушие и действительно заставим звуки путешествовать по сети.

Работа с UDP протоколом (TCP, сами понимаете, здесь излишен) была рассмотрена в цикле статей про видео стриминг на Андроид устройствах, поэтому сильно подробно останавливаться на этой теме не будем.

В коде это выглядит так. Сначала учреждаем два UDP сокета, соответственно для отправки и получения данных. Номера портов пока у них одинаковые, поскольку мы работаем с одним устройство и шлём данные, хоть и по сети, но сами себе. Далее, соответственно запускаются два отдельных потока по приему и передаче аудио. И в этих же потоках начинают работать audioRecord.startRecording() и audioIn.play() , записывая и воспроизводя аудио поток.

Итак:

    private byte inSoundData[];     DatagramSocket udpSocketOut;     DatagramSocket udpSocketIn;      String ip_address = "IP адрес устройства в локальной сети";     InetAddress address;     int portOut = 40000;// выход для пакетов со звуком/40000     int portIn = 40000;// вход для пакетов со звуком// 40000     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);     mIsCapturing = true;                  try {             udpSocketOut = new DatagramSocket();             udpSocketIn = new DatagramSocket(portIn);             Log.i(LOG_TAG, "  создали udp каналы");         } catch (Exception e) {             Log.i(LOG_TAG, "  Не создали udp каналы " + e);         }          new AudioSend();// для отправки звука по UDP         new Udp_recipient();// запускаем прием UDP пакетов          try {             address = InetAddress.getByName(ip_address);             Log.i(LOG_TAG, "  есть адрес");         } catch (Exception e) {              Log.i(LOG_TAG, "  нет адреса " + e);         } }      private class Udp_recipient extends Thread {         Udp_recipient() {             Log.i(LOG_TAG, "запустили прием данных по udp");             start();         }          public void run() {         while (true) {                 try {                     byte buffer[] = new byte[32768];                     DatagramPacket p = new DatagramPacket(buffer, buffer.length);                     udpSocketIn.receive(p);                     byte bBuffer[] = p.getData();                     inSoundData = new byte[p.getLength()];                     synchronized (inSoundData) {                         for (int i = 0; i < inSoundData.length; i++) {                             inSoundData[i] = bBuffer[i];                         }                     }                 }                 catch (Exception e) {                     Log.i(LOG_TAG, e + "hggh ");                 }                  try                 {                     audioIn.write(inSoundData, 0, inSoundData.length);                 }                 catch (Exception e)                 {                     // handle exception                 }                 audioIn.play();             }         }     }      public class AudioSend extends Thread {         AudioSend() {             Log.i(LOG_TAG, "запустили прием данных по udp");             start();         }         @Override         public void run() {            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO)                 try {             Log.i(LOG_TAG, "запустили отправку ");                 audioRecord.startRecording();                 byte[] outData = new byte[minBufferSize];                 try {                     while (mIsCapturing) {                        // read audio data from internal mic                         audioRecord.read(outData,0, outData.length);// получили в байтовый массив порцию звука                         // set audio data to UDP сокет                         try{                             //  Log.i(LOG_TAG, " outDate.length : " + outDate.length);                             DatagramPacket packet = new DatagramPacket(outData, outData.length, address, portOut);                             udpSocketOut.send(packet);                         }                         catch (Exception e)                         {                             Log.i(LOG_TAG, "не отправили UDP пакет");                         }                     }                 } finally {                     audioRecord.stop();                 }              } finally {                 audioRecord.release();                 Log.i(LOG_TAG, "остановили tred");             }         }     } 

Не забудьте только добавить соответствующие разрешения на работу с сетью в манифесте и в самом приложении. И после можете наслаждаться всеми звуковыми эффектами, эхом, реверберацией и положительной обратной связью переходящей в противный свист. Но ежели, подключить наушники, неважно блютус или проводные, то можно более комфортно слушать свою речь. Ещё удивила чувствительность сяомиевского микрофона: стоишь в беспроводных наушниках в 10 метрах от телефона, говоришь еле слышным шепотом, в противоположную сторону, а из наушников всё отчетливо слышно.

В принципе, для телефонизации поселка и походного стана (где нет мобильного покрытия) этого достаточно. Устанавливаем в сельсовете или шатре предводителя мощный роутер с внешней антенной, а вокруг в избах или палатках дружины размещаем маршрутизаторы попроще (как репитеры). Подключаетесь к сетке главного роутера и можете бесплатно разговаривать по смартфонам хоть сутками. Там надо лишь допилить регистрацию абонентов и опцию выбора кому звонить.

Но поскольку я хотел от лишнего звена, роутера избавиться, то пришлось двигаться дальше, дабы сконфигурировать и запустить беспроводную локалку непосредственно у себя на смартфоне.

Для этой цели современный Android, вроде как, предлагает прекрасное современное (на колбэках!) технологическое решение local-only hotspot, чтобы приложения могли соединяться с друг другом по Wi-Fi для обмена данными. И даже заботливо предупреждает, что мол, сеть будет только внутренняя, а в Сеть на ней не выйдешь. Ну, нам в принципе, этого не требуется. Инициализация же этого дела выглядит примерно так:

         wifiManager = (WifiManager) this.getApplicationContext().getSystemService(Context.WIFI_SERVICE);          wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() {             @Override             public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {                 super.onStarted(reservation);                 Log.d(LOG_TAG, "Wifi Hotspot is on now");                 mReservation = reservation;                 currentConfig = mReservation.getWifiConfiguration();                 Log.d(LOG_TAG, "THE PASSWORD IS: "                         + currentConfig.preSharedKey                         + " \n SSID is : "                         + currentConfig.SSID); 

После чего вы получаете сетку с уже предустановленными названием (Android плюс чего-то там) и паролем. Нет, ну ладно сеть можно найти по слову Андроид в названии, а вот как бедному приложению пытающемуся к ней присоединиться, получить пароль??? Может можно сконфигурировать свою сеть с нужными названием и паролем? Смотрим страницу SoftApConfiguration (на официальном сайте!), и находим там ссылку SoftApConfiguration.Builder. И видим: «404 | страница не найдена».

Самое интересное, что в интернете примеры работы с этим билдером есть. Есть он и в доках Андроида. А в Android Studio — нет. Я и студию обновил, после чего текущие проекты жестко забаговались и слетели некоторые настройки (как вам такое: чтобы исправить внезапно возникшие ошибки Gradle, надо кое-где в файле сборки вставить слово version. Stack overflow не даст соврать); и поставил версию SDK API 33 Tiramisu, но все было тщетно, SoftApConfiguration.Builder не воспринимался системой никак.

После этого я стал примерно понимать ощущения безвестного участника Stack Overflow, который задал вопрос, а почему мол, эта новая сеть hidden, то есть скрыта. На что бездушный бот ответил ему, что он такие общие вопросы не понимает, пишите дескать пример кода. А я его понял, LocalOnlyHotspot вроде есть, и всё для его конфигурации имеется, а на практике — его нет.

Чуть позже на этом же форуме я нашел что:

  1. Только системные приложения могут конфигурировать LocalHotspot
  2. setSoftApConfiguration() это защищенное/ скрытое API, которым вы можете пользоваться только, если у вас кастомный SDK или SDK от третьих лиц, но всё равно в приложении оно работать не будет, если приложение не системное.

То есть копеечный роутер на линуксе сеть поднимает, а пет разработчик на Aндроиде видит вместо этого от Андроид разработчиков шиш. Что там такого в этой беспроводной локалке страшного и ужасного, что энтузиастам палки в колеса суют?

Но может быть, я неправ, и они наоборот вместо копания в IP адресах, просто предоставляют гикам продвинутые инструменты более высокого уровня? Вот например есть же Wi-Fi Direct и еще Wi-Fi Aware. Может надо пользоваться именно ими?

Действительно, на официальной странице прямо так и указано: Wi-Fi Direct (P2P) позволяет устройствам с соответсвующим аппаратным обеспечением устанавливать друг с другом связь по Wi Fi без промежуточной точки доступа. А в обсуждениях всё того же Stack Overflow, народ также писал, что Wi-Fi Direct работает вместе с беспроводной сетью (то есть без потери основного Wi Fi соединения) и даже не требует ее отключения. Это, мол, огромное достижение.

Обнадёженный этой информацией, я начал знакомиться Wi-Fi Direct поближе. На первый взгляд всё было прекрасно. Можно сказать, вместо ассемблера работаешь с современным языком высокого уровня: подписчики, чаты, сообщения, группы, публикации и т.д.

В соответствии с руководством я успешно создал экземпляр WifiP2pManager и даже зарегистрировался в Wi-Fi P2P фреймворке вызвав метод initialize(). Но тут, я нечаянно выключил тот самый основной Wi Fi, короче отсоединился от своей домашней сети.

И всё! Тут же пришел интент, что Wi-Fi P2P недоступен!

Как оказалось, мне показалась, что всё это всего лишь обёртка над тривиальным Wi Fi. То есть, в принципе, правильно написали, что Wi-Fi Direct не мешает работе основному Wi Fi соединению. Просто забыли написать, что без основного Wi Fi соединения, Wi-Fi Direct в принципе работать не может.

Но это была ложная тревога. Просто при выходе из домашней локалки я отключил и Wi Fi модуль смартфона. А для Wi-Fi Direct он должен работать, не важно, подключен он при этом к какой-либо сети или нет. Правда, это я уяснил через пару дней, в течении которых я пытался сконфигурировать и поднять беспроводную локалку — дедовскими способами через методы Reflection API.

Это выглядит примерно так, сначала конфигурируем сеть:

WifiConfiguration wcfg = new WifiConfiguration();        wcfg.SSID = " SSID NAME";        wcfg.networkId = Int;        wcfg.preSharedKey = "password";        wcfg.hiddenSSID = false;        wcfg.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);        wcfg.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);        wcfg.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);        wcfg.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);        wcfg.allowedProtocols.set(WifiConfiguration.Protocol.WPA);  

А потом делаем финт ушами:

 Method method = mWifiManager.getClass().getMethod("setWifiApConfiguration",                   wcfg.getClass());                  method.invoke(mWifiManager, wcfg); 

И всё, сеть сконфигурирована. И даже можно получить ее параметры стандартными get-методами

Но современные (Android 12) и относительно современные (Android 10) смартфоны эти reflection методы не видят в упор, и код взлетел лишь на стареньком Самсунге с шестым Андроидом.

Но радовался я недолго, так как сеть мало сконфигурировать, её надо ещё и включить!
На это есть метод:

Method method1 = mWifiManager.getClass().getMethod("setWifiApEnabled",                         WifiConfiguration.class, boolean.class);                  method1.invoke(mWifiManager, wcfg, true);  

Но он выдал исключение, которое привело меня к разрешению CONNECTIVITY_INTERNAL, которое, как оказалось, дается только системным приложениям.

Рутовать для этого даже старый телефон мне было лень и дальнейший поиск, так сказать, на этом остановился.

Я уже собирался вернуться к Wi-Fi Direct, но поскольку сокеты там пишутся немного по другому и вообще другая идеология, то вылетал весь код написаный для обычной Wi Fi сети и надо было писать новый. Поэтому радиотелефон на Wi Fi Direct я отложил для нового поста на Хабре, а затем здраво рассудил, что коль для инициализации своей сетки, надо сделать приложение системным, то почему бы не воспользоваться уже действующим системным приложением, которое работает, наверное, на всех современных телефонах. Это — Точка Доступа. Там, правда, надо на нее нажать пальцем, потом вбить свое название и пароль, что конечно, не комильфо. Но для проверки принципа и такой подход сойдёт.

Далее, со второго смарта надо подсоединиться к этой точке доступа (тоже ручками), и уже в параметрах мы увидим там и IP адрес шлюза (это первый смартофон) и свой IP адрес в этой сети. Теперь, если указать эти адреса в текстах программ, то радиотелефон у нас уже, можно сказать, заработал!

image

Теперь наступила пора полевых исследований. В следующую субботу, поколотив друг друга палками на тренировке по исторической реконструкции, я взял напарника с собой на тихую питерскую улицу и поведал ему свою тайну. Давно не видывал я такого детского восторга в глазах взрослого небритого мужика. Да? Рация на телефоне? Так это ж можно…

Восторги правда поутихли, когда наступили собственно практические испытания. Итог — зона уверенного приема в городе сто метров. На ста десяти/двадцати просто теряется сеть, а с ней, сами понимаете, и возможность живого общения. Это если на частоте 2,4 ГГц. На 5 ГГц ещё меньше, даже уже точно не считали.

На этом, в принципе, исследования завершились. Ради интереса можно ещё попробовать запустить связь на Wi Fi Direct, но скорее всего на дальность это не повлияет, так как всё зависит от мощности и чувствительности Wi Fi модуля, а у нас к нему доступа нет.


ссылка на оригинал статьи https://habr.com/ru/post/715978/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *