0

题外话:首先让我说 Java 对我来说是全新的。我已经编程超过 15 年了,除了修改其他人的代码库之外,从来没有需要它,所以请原谅我的无知和可能不恰当的术语。我对射频也不是很熟悉,所以如果我在这里很陌生,请告诉我!

我正在构建一个 SDR(软件定义无线电)无线电发射器,虽然我可以在某个频率上成功传输,但当我发送流(来自设备的麦克风或来自音调发生器的字节)时,我的手持接收器会发出什么听起来像静态的。

我相信这是由于我的接收器设置为接收 NFM(窄带频率调制)和 WFM(宽带频率调制),而来自我的 SDR 的传输正在发送原始的、未调制的数据

我的问题是:如何调制音频字节(即InputStream),以便在FM(频率调制)AM(幅度调制)中调制得到的字节,然后我可以通过 SDR 传输?

尽管有很多开源软件,但我似乎找不到处理调制的类或包(最终我将不得不调制 WFM、FM、AM、SB、LSB、USB、DSB 等) SDR 代码库,但如果你知道我在哪里可以找到它,那基本上就回答了这个问题。到目前为止,我发现的一切都是为了解调。

这是我在 StackOverflow 上围绕Xarph's Answer构建的一个类,它只返回一个包含简单、未调制的音频信号的字节数组,然后可用于通过扬声器播放声音(或通过 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 调制在处理字节数组并将其转换为正确格式时的工作原理可能更有价值,因为我将拥有未来实施更多模式。

4

1 回答 1

0

关于收音机,我有很多不知道的地方。但我想我可以谈谈调制的基础知识和手头的问题,因为我有一点物理知识和编写 FM 合成器的经验。

首先,我认为如果将源信号的 PCM 数据点转换为标准化浮点数(范围从 -1f 到 1f),而不是使用短裤,我认为使用它们可能会更容易。

接收器的目标频率 510-1700 kHz(AM 收音机)明显快于源声音的采样率(大概是 44.1kHz)。假设您有办法输出结果数据,数学运算将涉及从您的信号中获取一个 PCM 值,适当地对其进行缩放(IDK 多少)并将该值与您的载波信号生成的对应于时间的 PCM 数据点相乘间隔。

例如,如果载波信号为 882 kHz,您可以将 20 个载波信号值的序列与源信号值相乘,然后再转到下一个源信号值。再说一次,我的无知:该技术可能有某种平滑算法用于源信号数据点之间的转换。我真的不知道这一点,也不知道它发生在什么阶段。

对于 FM,我们有 MHz 范围内的载波信号,因此我们所说的每个源信号值生成的数据比 AM 多几个数量级。我不知道使用的确切算法,但这是一种简单的概念方法来实现我与 FM 合成器一起使用的正弦频率调制。

假设您有一个包含 1000 个数据点的表,这些数据点代表一个范围在 -1f 到 1f 之间的正弦波。假设您有一个反复遍历表的游标。如果光标以 44100 fps 恰好前进 1 个数据点并以该速率传递值,则生成的音调将为 44.1 Hz,是吗?但您也可以通过大于 1 的间隔(例如 1.5)遍历表。当光标落在两个表值之间时,可以使用线性插值来确定要输出的值。1.5 的光标增量将导致正弦波以 66.2 Hz 的频率倾斜。

我认为 FM 发生的情况是,这个光标增量是不断变化的,它的变化量取决于从源信号转换为增量范围的某种缩放。

我不知道缩放的细节。但是假设一个信号正在以 10MHz 的载波传输并且范围约为 1%(大约从 9.9 MHz 到 10.1 MHz),归一化的源信号将具有某种算法,其中 PCM 值 -1 匹配遍历载波使其产生较低的频率,+1 匹配穿过载波的增量,使其产生较高的频率。因此,如果 +1 的增量提供 10 MHz,则可能 -1 的源波 PCM 信号引发 +0.99 的光标增量,-0.5 的 PCM 值引发 +0.995 的增量,+0.5 的值引发增量+1.005 时,+1 的值会引发 1.01 的光标增量。

这纯粹是我对源 PCM 值之间的关系以及如何使用它来调制载波频率的推测。但也许它有助于给出基本机制的具体形象?

(我使用类似的东西,在AudioCue(一个基于 Java 的用于播放音频数据的类)中使用光标以任意增量迭代 wav PCM 数据点Clip,用于实时频移。代码行 1183 保存迭代的光标在从 wav 文件导入的 PCM 数据上,变量idx保存光标增量。第 1317 行是我们在增加光标后获取音频值的地方。代码行 1372 具有执行线性插值的方法readFractionalFrame() . 还实现了实时音量变化,并且我对从公共输入挂钩提供的值进行了平滑处理。)

同样,IDK 是否在源信号值之间使用任何类型的平滑。根据我的经验,很多技术都涉及过滤和其他各种提高保真度或处理计算的技巧。

于 2020-11-16T18:32:29.490 回答