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 модуля, а у нас к нему доступа нет.

Комментарии (65)


  1. YegorP
    00.00.0000 00:00

    Какова задержка от микрофона одного телефона до динамика другого?


    1. Tarson Автор
      00.00.0000 00:00

      Сотни миллисекунд. Копейки не считал, но когда говоришь "привет" слышно без наложения


    1. event1
      00.00.0000 00:00
      +1

      Зависит от minBufferSize, который автор довольно легкомысленно увеличил в 10 раз. Хорошая задержка должна быть, до 50 мс. 16 кбайт/с = 16 байт/мс. Соответственно, делать буфер больше 800 байт — опрометчиво.


      1. Tarson Автор
        00.00.0000 00:00
        +2

        Не, там предлагали для записи так делать, чтоб не дёргать часто буфер. А у меня он в 2 раза больше минимального.



  1. shpon
    00.00.0000 00:00
    +5

    В моём древнем смартфоне есть радиомодуль на 400+/-МГц. Можно с таксистами общаться. Вообще, интересная штука.


    1. iShrimp
      00.00.0000 00:00

      Китаец типа Blackview? Как обстоят дела с качеством и дальностью связи?


      1. shpon
        00.00.0000 00:00
        +3

        Китаец Runbo X5. По городу в прямой видимости около 4км на связи с мини мотороллой и около 400 метров в городской застройке с baofeng uv5r.

        А еще там чат есть, общался с приятелем у которого тоже runbo, но в формате планшета.


    1. R820T2
      00.00.0000 00:00

      Что за смарт?


      1. shpon
        00.00.0000 00:00

        Древний, морально устаревший runbo x5


  1. okhsunrog
    00.00.0000 00:00
    +1

    Думаю, будет интересно взглянуть https://meshtastic.org
    Правда, там обмен текстовыми сообщениями, но дальность гораздо больше


    1. Tarson Автор
      00.00.0000 00:00
      +1

      Лору на смарте не видел, и там расстояние за счёт ретрансляции в пределах группы.


  1. Renatsun
    00.00.0000 00:00
    +1

    Вот зря наверное Bluetooth отбросили. Интересно было бы посмотреть, как далеко работает. Есть же спецификация Long Range обещает 400 метров и больше. Но не все телефоны поддерживают. List of smartphones that support Bluetooth 5 Long Range - Tracmo Help Center (mytracmo.com)


    1. Tarson Автор
      00.00.0000 00:00
      +3

      Так я в наушниках блютус в другую комнату зашёл и всё, дальность кончилась... Поэтому пришлось отбросить.


      1. Stanislavvv
        00.00.0000 00:00

        Оба девайса должны уметь long range, насколько я понимаю. Наушниками LR нах не сдался, там bt чисто чтоб проводами не привязываться.


        1. Tarson Автор
          00.00.0000 00:00

          Это надо спонсора найтить, чтобы обеспечил парой таких смартов. А то без них то никак не проверить:(


      1. Stalker_RED
        00.00.0000 00:00

        А я в блютус-наушниках без проблем отхожу от смартфона метров на 25-30, если стен не много.


        1. elfukado
          00.00.0000 00:00

          Какая модель смартфона, наушников и окружающая обстановка?

          У меня есть poco f3 и jbl 115. Может они и больше 10 метров потянут, но когда мне нужна связь или музыка - я не буду наворачивать круги вокруг смартфона и считать стены - это уже не нормальное использование, а эксперимент.


          1. strvv
            00.00.0000 00:00

            Самые дешёвые из закрытых sg8, классические наушники с амбюшурами.

            В 80+ кв.м. квартире по диагонали, через 3-4 стены работают, хотя вблизи бывают сильные затыки с взаимным расположением. А это метров 25 (на две стороны дома квартира, телефон на балконе и я по всей квартире.


            1. Tarson Автор
              00.00.0000 00:00

              У меня BOSE, но поворот за угол и привет...


    1. dmitryrf
      00.00.0000 00:00

      Пропускной способности LR не хватит на голос.


      1. saege5b
        00.00.0000 00:00

        Как жать... В принципе, сейчас для голоса канала в 10кбс с запасом хватает.
        Но, не каждый современный смарт сможет такое пожать и расжать.


      1. event1
        00.00.0000 00:00

        В интернете обещают 125-500 килобит. Можно 4 голоса уместить


        1. vorphalack
          00.00.0000 00:00

          в цифровой радиосвязи используются кодеки с полосой 2400-4400бит/с до FEC, с такой полосой там каналов 20 можно запихнуть вместе с сигналлингом.


          1. event1
            00.00.0000 00:00
            +2

            Ну ладно вам. Если уж есть широкая полоса, давайте какой-нибудь телефонный кодек туда пихнём хороший с блекджеком и шлюхами. Зачем людей мучить.


            1. vorphalack
              00.00.0000 00:00

              ну можно и телефонный — там всё равно полоса копеечная, но смысл в том, что чем уже кодек — тем проще насыпать поверх него FEC, и если в этом LRBT есть режим работы «йа UDPшко» — то это будет иметь бОльшую данность, чем если там нужно поддерживать полноценный линк с обработкой ошибок. ну или банально слать по 3-4 пакета подряд хотя бы


              1. event1
                00.00.0000 00:00

                если в этом LRBT есть режим работы «йа UDPшко»

                Судя по описанию, указанный режим — это вариант PHY. Через это, видимо, там работают стандартные профили БТ. А среди них совершенно случайно уже есть стандартный профиль именно для таких целей. Правда, он похоже не поддерживается ни в андроиде, ни в блюзе. Что навевает грустные мысли.


                1. vorphalack
                  00.00.0000 00:00
                  +2

                  судя по гуглу — БЫЛ, на bluetooth.com спека значится как отозванная, к сожалению.

                  EDIT: наткнулся таки на описание, оно звезда, а не p2p


      1. aectaan
        00.00.0000 00:00

        Опусом пожать и вперёд. Стримил стереопоток с микроконтроллера на ноут. Правда не на LR, а на мегабите. Хватало метров на 50 + 2 стены


    1. andry67
      00.00.0000 00:00

      Long Range ...

      Немного погуглил - так и не нашел ни одной програмки под андроид, которая задействует эту фичу.


    1. Tarson Автор
      00.00.0000 00:00

      Сейчас настраиваю WI FI direct, зацепили строчки из официала андроида

      Using these APIs, you can discover and connect to other devices when each device supports Wi-Fi P2P, then communicate over a speedy connection across distances much longer than a Bluetooth connection

      Чего это они вдруг сами решили вдруг, что дальше?


  1. Sagittarius67
    00.00.0000 00:00
    +5

    поскольку усилители входных сигналов на станции явно будут помощнее, чем на телефоне, но не в принципиальной же степени

    Как известно, самый лучший усилитель - это антенна. На базовой станции стоят антенны с усилением в районе 17 dBi. А если учесть ещё то, что приём ведётся на две антенны (receive diversity), что даёт ещё +3 dB, то общее усиление по приёму на базовой станции будет не менее 20 dBi. Эффект в мощности сами можете посчитать.


  1. Javian
    00.00.0000 00:00
    +1

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

    Интересно бы проверить дальность при сети на точке доступа TP-Link WA5210 перешитой в аналог Ubiquiti NanoStation 2.


  1. zuek
    00.00.0000 00:00
    +1

    Странно, что не посетила мысль поднять "точку доступа" на ноутбуке/малинке/смартфоне с запущенным Asterisk, а на смартфонах установить какой-нибудь softphone - сразу и вопрос "адресной телефонизации палаточного городка" решается... правда, и собственных изысканий становится совсем скромнёхонько...


    1. Tarson Автор
      00.00.0000 00:00
      +3

      Только напрямую байты и udp протоколы, только хардкор!


  1. MaFrance351
    00.00.0000 00:00
    +3

    Интересная статья!
    К слову говоря, встречал и промышленную реализацию данной идеи, в ТСД Symbol (позже Motorola, ещё позже Zebra), была функция PTT, позволявшая переговариваться между двумя ТСД в одной сети.

    (И огромное спасибо, что мою статью упомянули!)


    1. Tarson Автор
      00.00.0000 00:00
      +1

      Пожалуйста! Я ваш пост раза три перечитал, вещь!


  1. Tomasina
    00.00.0000 00:00

    А вне города не замеряли зону приёма? Ибо решение для вылазок на природу небольшими группами - самое то.


    1. Dr_Faksov
      00.00.0000 00:00
      +1

      О том же подумал. У WiFi в отсутствие помех заявленная дальность вроде метров 500.

      А еще интересно текст переслать. Пусть медленно, но максимально далеко.

      Статья классная, поставил плюсы везде, где можно!


      1. Tarson Автор
        00.00.0000 00:00
        +1

        спасибо, нет, за город пока холодно... к теплу, наверное, с Wi Fi Direct доделаю, тоже проверю.


    1. inkelyad
      00.00.0000 00:00

      Что-то мне кажется, что, увы, частоты радио интерфейсов, что есть в смартфоне - не подходят ни для какой природы, кроме 'голая степь'. Интереснее было бы понять, какое дополнительное оборудование из доступного и удобного в использовании можно к смартфону прицепить, чтобы было 'со всеми удобствами'.


      Я подозреваю - что тупо рацию из ближайшего магазина через разъем для гарнитуры.


  1. mikelavr
    00.00.0000 00:00
    +5

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

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

    Например, для 802.11g: Data rates with varying modulation types: 6, 9, 12, 18, 24, 36, 48 and 54 Mbps; can revert to 1, 2, 5.5, and 11 Mbps using DSSS and CCK.

    802.11n: Data rates with varying modulation types: 1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54 Mbps

    Очевидно, что в ваших целях 1 мегабит предпочтительнее 54.


  1. Didimus
    00.00.0000 00:00
    +1

    Раньше в телефонах производители пытались занести функцию уоки-токи, но, видимо, под давлением опсосов эту функцию убили


    1. iamkisly
      00.00.0000 00:00
      +1

      Речь о нокийной Push-to-Talk? Там не все так просто.. емнип технология работала через базовую станцию, но оба абонента должны быть в зоне обслуживания одной и той же bts (может быть даже одной cell, не помню) чтобы базовая станция выступала только ретранслятором. И вот тут начинались сложности, потому что PTT фича не стандартизованная, и кажется работало это только на базовых станциях nsn, к тому же по пакетке (условно через gprs).. а пакетная связь в gsm по умолчанию всегда выделяласm по остаточному принципу. Тоесть, большинство таймслотов всегда заняты голосом, а поставить в сектор еще один trx и зарезервировать слоты под пакетку это дорого. Не знаю можно ли было прикрутить туда билинг, возможно да.. но вероятно проблема была еще и в качестве связи. Тоесть попробуй обьясни абоненту почему у него булькает все в трубке, он скорее уйдет к другому оператору.


      1. Didimus
        00.00.0000 00:00

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


        1. Dr_Faksov
          00.00.0000 00:00
          +1

          Ну как-бы железо приличной радиостанции занимает приличное место. И прилично кушает батарею. Ну и нужна приличная антенна. Это физика.

          А еще есть законы. В США нужна лицензия даже на даже на бытовую радиостанцию. Формальность, заплати и получи, но нужна. В Канаде не нужна. Но и там и там если у вас нет позывного, то вы можете использовать только радиостанции попадающие под описание "бытовые". Один из параметров такой станции - отсутствие клавиатуры.


          1. Didimus
            00.00.0000 00:00
            +1

            А вы видели китайские телефонги? Там и антенна, и телевизор, и аккумулятор как в авто


        1. Sagittarius67
          00.00.0000 00:00

          У Моторолы были телефоны с кнопкой PTT и соответствующим софтом. Была и PTT платформа. Считалось, что это будет киллер фича для GPRS. Эта штука, в частности, тестировалась для таксистов в Москве на базе МТС.


          1. Didimus
            00.00.0000 00:00

            И чем закончилось?


            1. Sagittarius67
              00.00.0000 00:00

              Это было миллиард лет тому назад, не помню уже точно. Работало, вроде, нормально, но была довольно большая задержка после нажатия кнопки PTT. Ну и телефон нужно было специальный покупать. Тогда же МТС закупил платформу для i-mode у японцев и похорнил потом всё это.

              https://www.cnews.ru/news/top/mts_zakryvaet_imode


  1. Devadas
    00.00.0000 00:00

    Вот что-то никак у меня не сходится, не могу понять... и даже в этой статье - цифры в начале статьи с результатом в ее конце... искренне хочу разобраться.

    размер соты для уверенного приёма, может составлять несколько километров в поперечнике (у AMPS вообще до 20 км, да и даже LTE ненамного меньше). ... и сам смартфон обладая, пусть и десятками и сотнями милливатт выходной мощности, но также доносит свой голосок до станции.

    Как смартфон со своей крошечной мощностью добивает до БС через городскую застройку на 20-30 км и всего лишь сто метров до своего сородича (как в этом опыте)?


    1. Tarson Автор
      00.00.0000 00:00

      Так там вроде написали, антенны, антенны на БС. А у Fi Wi они махонькие... А так да: Согласно требованиям к системе LTE, при радиусе соты в 5 км, все требования к спектральной эффективности, пропускной способности и работы с мобильными абонентами должны поддерживаться. При радиусе соты в 30 км допускается ухудшение в показателях производительности.


    1. Tomasina
      00.00.0000 00:00
      +3

      4-10 км в поле, 1-3 км в городе - это пределы для телефончика.

      А красивые цифры в спецификациях - традиционно для идеальных условий (отсутсвие помех, радионепрозрачных преград и т.п.).


      1. Tarson Автор
        00.00.0000 00:00

        Не, ну понятно, что как расход бензина на 100 км от автопроизводителя.


    1. phosfin
      00.00.0000 00:00

      так gsm мобилы до 5 Вт может мощность поднять, + относительная открытость высокой антенны.


    1. moviq
      00.00.0000 00:00

      через городскую застройку на 20-30 км

      наверное это приводилось для открытой местности. Поправьте, у 2G же тоже по стандарту порядка20...30км


    1. begin_end
      00.00.0000 00:00
      +4

      Дело даже не в размерах антенн непосредственно: узкая направленность антенн специальной конструкции и даваемое при этом усиление (улучшение соотношения сигнал/шум) — вот это работает и достигается еще и размещением антенн. Если обратить внимание, антенны сотовых операторов обычно почти всегда оптически видимы, прикрыты максимум 1-2 препятствиями. Даже если где-то получилась сильная тень для ближайшей соты — соседняя сота помогает.

      Смартфон же имеет принципиально всенаправленные антенны, чтобы работать в любом положении. Но мало того, при локальном использовании смартфон-смартфон, как в статье, они оба находятся на «уровне земли», в руках. А еще Wi-Fi сильно зашумлен в городе.
      Ну и как выше сказали, надо Data rate минимальный ставить и в 802.11b стандарте работать.

      Я проводил ряд экспериментов коннектов со смартфона к точке доступа (старая-добрая DWL-G2100, настроенная в 150мВт, rate 0.25Mbps, b):
      • точка доступа с дипольной антенной на высоте 30м, телефон на уровне земли — пока есть прямая видимость, связь доступна на расстоянии 1..1,5км;
      без прямой видимости (1 препятствие в виде дерева или человека) — 100..200м;
      • точка доступа с рупорной антенной (укороченный рупор с вертикальной поляризацией) на высоте 30м, телефон на уровне земли (на линии от рупора) — пока есть прямая видимость, связь доступна на расстоянии до 3км; для максимально дальней связи нужно учитывать отсутствие препятствий не только на оптическом маршруте, но и по бокам, в зоне Френеля;
      без прямой видимости (1 препятствие в виде дерева или человека) — 100..500м;
      • то же самое, когда и точка доступа на уровне земли: стабильная связь 20..50м (1 и более препятствие в виде дерева или человека), далее как повезет, фрагментарно до 200..300м максимум; рупорная антенна незначительно увеличивает вероятность успешной связи и кстати, добавочный 5W усилитель на точку доступа тоже неэффективен (весьма логично).

      Еще одно существенное замечание: тесты были на старом смартфоне GT-I9300. На более современном Galaxy A70 дальность связи заметно ниже — чувствительность радиотракта Wi-Fi по программам-измерялкам на 6..9db хуже, чем у старой модели. Думаю, с учетом перенасыщенности Wi-Fi производители специально загрубляют чувствительность, оптимизируя исключительно под комнатное использование.

      Резюмируя: высота размещения, открытость и направленность антенн — главные факторы для дальности связи.


      1. Tarson Автор
        00.00.0000 00:00

        Весьма интересная информация, буду иметь в виду...


  1. ciuafm
    00.00.0000 00:00

    Вопрос к автору - почему бы не сбилдить и не выложить аппликуху? Вдруг кто-то захочет потестировать, хотя компилировать не умеет?


    1. Tarson Автор
      00.00.0000 00:00

      Это надо gui делать, да ещё тестить, пока нет возможности.


  1. penetration
    00.00.0000 00:00
    -1

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


    1. Didimus
      00.00.0000 00:00

      Зачем оператор? Один телефон как базовая станция работает


      1. penetration
        00.00.0000 00:00

        Да хоть так, главное чтобы расстояние было приличная и была реализация на андроиде, но как я сказал, таких нет


  1. LevKoshkin
    00.00.0000 00:00

    А можно готовый .apk? Давно искал подобное Р2Р приложение для связи во время велопокатушек, готовые приложения из маркета с избыточным функционалом и регистрацией.


    1. Tarson Автор
      00.00.0000 00:00

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