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

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();


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

     
    


Но как легко видеть, для передачи живого аудио потока эти классы подходят мало. Здесь нам пригодится другой класс — 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×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 модуля, а у нас к нему доступа нет.

© Habrahabr.ru