Как мне модулировать сигнал для радиопередачи (SDR) в Java?

Не по теме: позвольте мне начать с того, что Java для меня совершенно в новинку. Я занимаюсь программированием более 15 лет, и у меня никогда не было необходимости в этом, кроме модификации кодовой базы других, поэтому, пожалуйста, простите мое невежество и, возможно, неправильную терминологию. Я также не очень хорошо знаком с RF, поэтому, если я ухожу здесь, пожалуйста, дайте мне знать!

Я создаю радиопередатчик SDR (Software Defined Radio), и, хотя я могу успешно передавать на частоте, когда я отправляю поток (либо с микрофона устройства, либо байты из тон-генератор), то, что проходит через мой портативный приемник, звучит как статический.

Я считаю, что это связано с тем, что мой приемник настроен на прием NFM (узкополосная частотная модуляция) и WFM (широкополосная частотная модуляция), в то время как передача, исходящая из моего SDR, отправляет необработанные немодулированные данные.

Мой вопрос: как мне модулировать байты звука (т.е. InputStream), чтобы результирующие байты модулировались в FM (частотная модуляция) или AM (амплитудная модуляция ), что я могу передать через SDR?

Кажется, я не могу найти класс или пакет, который обрабатывает модуляцию (в конечном итоге мне придется модулировать WFM, FM, AM, SB, LSB, USB, DSB и т. Д.), Несмотря на то, что существует довольно много открытых исходных кодов Кодовые базы SDR, но если вы знаете, где я могу это найти, это в основном отвечает на этот вопрос. Все, что я нашел до сих пор, предназначалось для демодуляции.

Это класс, который я построил на основе ответа Xarph здесь, в StackOverflow, он просто возвращает массив байтов, содержащий простой немодулированный аудиосигнал, который затем можно использовать для воспроизведения звука через динамики (или передачи через SDR, но из-за того, что результат не модулируется должным образом, он не проходит должным образом на стороне приемника, что и вызывает у меня проблемы выяснение)

public class ToneGenerator {

    public static byte[] generateTone() {
        return generateTone(60, 1000, 8000);
    }

    public static byte[] generateTone(double duration) {
        return generateTone(duration, 1000, 8000);
    }

    public static byte[] generateTone(double duration, double freqOfTone) {
        return generateTone(duration, freqOfTone, 8000);
    }

    public static byte[] generateTone(double duration, double freqOfTone, int sampleRate) {
        double dnumSamples = duration * sampleRate;
        dnumSamples = Math.ceil(dnumSamples);
        int numSamples = (int) dnumSamples;
        double sample[] = new double[numSamples];
        byte generatedSnd[] = new byte[2 * numSamples];


        for (int i = 0; i < numSamples; ++i) {    // Fill the sample array
            sample[i] = Math.sin(freqOfTone * 2 * Math.PI * i / (sampleRate));
        }

        // convert to 16 bit pcm sound array
        // assumes the sample buffer is normalized.
        // convert to 16 bit pcm sound array
        // assumes the sample buffer is normalised.
        int idx = 0;
        int i = 0 ;

        int ramp = numSamples / 20 ;                                     // Amplitude ramp as a percent of sample count


        for (i = 0; i< ramp; ++i) {                                      // Ramp amplitude up (to avoid clicks)
            double dVal = sample[i];
            // Ramp up to maximum
            final short val = (short) ((dVal * 32767 * i/ramp));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSnd[idx++] = (byte) (val & 0x00ff);
            generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
        }


        for (i = i; i< numSamples - ramp; ++i) {                         // Max amplitude for most of the samples
            double dVal = sample[i];
            // scale to maximum amplitude
            final short val = (short) ((dVal * 32767));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSnd[idx++] = (byte) (val & 0x00ff);
            generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
        }

        for (i = i; i< numSamples; ++i) {                                // Ramp amplitude down
            double dVal = sample[i];
            // Ramp down to zero
            final short val = (short) ((dVal * 32767 * (numSamples-i)/ramp ));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSnd[idx++] = (byte) (val & 0x00ff);
            generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
        }

        return generatedSnd;
    }
}

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


person Ryan M.    schedule 15.11.2020    source источник
comment
Вам, вероятно, понадобится оборудование для модуляции радиочастот.   -  person geocodezip    schedule 16.11.2020
comment
Это хороший момент, я не думал о проверке библиотеки HackRF, чтобы увидеть, обрабатывает ли она аппаратную сторону модуляции (я предполагал, что это программное обеспечение, потому что это делает консоль SDR, но это для приема, с точки зрения передачи доступно не так много, кроме SDR Angel) - собираюсь разобраться в этом, спасибо!   -  person Ryan M.    schedule 16.11.2020


Ответы (1)


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

Во-первых, я думаю, что вам будет проще работать с точками данных PCM исходного сигнала, если вы конвертируете их в нормализованные числа с плавающей запятой (в диапазоне от -1f до 1f), чем работать с шортами.

Целевая частота приемника 510-1700 кГц (AM-радио) значительно выше, чем частота дискретизации исходного звука (предположительно 44,1 кГц). Предполагая, что у вас есть способ вывести результирующие данные, математика будет включать в себя получение значения PCM из вашего сигнала, его соответствующее масштабирование (сколько IDK) и умножение значения на точки данных PCM, сгенерированные вашим сигналом несущей, который соответствует времени интервал.

Например, если сигнал несущей составлял 882 кГц, вы бы умножили последовательность из 20 значений сигнала несущей на значение сигнала источника, прежде чем переходить к следующему значению сигнала источника. Опять же, мое незнание: у технологии может быть какой-то алгоритм сглаживания для перехода между точками данных исходного сигнала. Я действительно не знаю об этом или нет, и на каком этапе это происходит.

Для FM у нас есть сигналы несущей в диапазоне МГц, поэтому мы говорим о том, что для каждого значения сигнала источника генерируется на порядки больше данных, чем для AM. Я не знаю точного используемого алгоритма, но вот простой концептуальный способ реализации частотной модуляции синуса, который я использовал со своим FM-синтезатором.

Допустим, у вас есть таблица с 1000 точками данных, которая представляет одну синусоидальную волну в диапазоне от -1f до 1f. Допустим, у вас есть курсор, который постоянно перемещается по таблице. Если бы курсор продвинулся ровно на 1 точку данных со скоростью 44100 кадров в секунду и выдал значения с такой скоростью, результирующий тон был бы 44,1 Гц, да? Но вы также можете перемещаться по таблице через интервалы больше 1, например 1,5. Когда курсор оказывается между двумя табличными значениями, можно использовать линейную интерполяцию для определения значения для вывода. Увеличение курсора на 1,5 приведет к синусоиде с частотой 66,2 Гц.

Я думаю, что с FM происходит то, что это приращение курсора постоянно изменяется, и величина его изменения зависит от некоторого масштабирования исходного сигнала, переведенного в диапазон приращений.

Специфика масштабирования мне неизвестна. Но предположим, что сигнал передается с несущей 10 МГц и находится в диапазоне ~ 1% (примерно от 9,9 МГц до 10,1 МГц), нормализованный исходный сигнал будет иметь какой-то алгоритм, в котором значение PCM, равное -1, соответствует приращению, которое пересекает несущая волна, заставляющая его производить более медленную частоту, и +1 соответствует приращению, которое пересекает несущую волну, заставляя ее производить более высокую частоту. Таким образом, если приращение +1 дает 10 МГц, возможно, сигнал PCM исходной волны, равный -1, вызывает приращение курсора на +0,99, значение PCM -0,5 вызывает приращение +0,995, значение +0,5 вызывает приращение +1.005, значение +1 вызывает приращение курсора на 1.01.

Это чистое предположение с моей стороны относительно взаимосвязи между исходными значениями ИКМ и того, как они используются для модуляции несущей частоты. Но, может быть, это поможет дать конкретное представление об основном механизме?

(Я использую что-то подобное, используя курсор для перебора точек данных wav PCM с произвольным приращением, в AudioCue (класс для воспроизведения аудиоданных на основе Java Clip) для сдвига частоты в реальном времени. Строка кода 1183 содержит курсор, который перебирает данные PCM, которые были импортированы из файла wav, с переменной idx, содержащей величину приращения курсора. Строка 1317 - это место, где мы извлекаем звуковое значение после приращения курсора. Строки кода 1372 имеют метод readFractionalFrame (), который выполняет линейную интерполяцию. Также реализованы изменения объема в реальном времени, и я использую сглаживание значений, которые предоставляются из общедоступных обработчиков ввода.)

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

person Phil Freihofner    schedule 16.11.2020
comment
Этот ответ оставил мне еще много вопросов (что в данном случае очень хорошо - раньше я действительно не знал, что даже спросить), но также и представление о том, как решить мою проблему. Большое спасибо за исчерпывающий ответ! Я определенно чувствую, что эта информация (и услышав, как вы достигли чего-то подобного, что показало мне, что я думал об этом все неправильно), скорее всего, приведет меня к правильному пониманию этого. Это отлично отвечает на вопрос, потому что я был больше сосредоточен на теории, чем на коде, так что спасибо вам за это! - person Ryan M.; 16.11.2020
comment
Мне очень приятно помочь! Приятно осознавать, что хотя бы часть дня я сделал что-то стоящее. Да, я тоже хотел бы узнать немного больше о радио. Между тем, хорошим (бесплатным) ресурсом об обработке аудиосигналов является dspguide.com Руководство для ученых и инженеров по DSP и вы также можете подумать, можно ли лучше ответить на ваши вопросы на dsp.stackexchange.com. Я ежедневно слежу за тегом javasound, если у вас есть дополнительные вопросы, связанные с java. - person Phil Freihofner; 17.11.2020
comment
Большое спасибо за то, что указал мне на эту книгу и рассказал о другой платформе Stack Exchange! Оба выглядят очень полезными, я не знал, что было что-то подобное. Я заметил, что для HackRF на Android не хватает передатчиков, кроме примера кода, который работает не очень хорошо, поэтому я надеюсь, что смогу сделать что-то вроде PortaPack, но для Android. Теперь, когда я думаю об этом, Windows сильно не хватает TX, кроме SDR Angel. Определенно есть чему поучиться, это все равно что объединить сообщества программистов и радио в одно, что делает вещи интересными. - person Ryan M.; 17.11.2020