如何在 Java 中调制用于无线电传输 (SDR) 的信号?
How do I modulate a signal for radio transmission (SDR) in Java?
题外话:让我先说 Java 对我来说是全新的。我已经编程超过 15 年了,除了修改别人的代码库之外从来没有需要它,所以请原谅我的无知和可能不正确的术语。我对 RF 也不是很熟悉,所以如果我在这里离开领域,请告诉我!
我正在构建一个 SDR(软件定义无线电)无线电发射器,虽然我可以在某个频率上成功传输,但当我发送流时(来自设备的麦克风或来自 tone generator 的字节) , 通过我的手持接收器传来的声音听起来像是静电干扰。
我认为这是因为我的接收器被设置为接收 NFM(窄带调频)和 WFM(宽带调频),而 来自我的 SDR 的传输正在发送原始的、未调制的数据.
我的问题是:如何调制音频字节(即 InputStream),以便在 FM(频率调制) 或 AM(调幅),然后我可以通过 SDR 传输?
我似乎找不到 class 或处理调制的软件包(最终我将不得不调制 WFM、FM、AM、SB、LSB、USB、DSB 等)尽管有相当多的开源 SDR 代码库,但如果你知道我在哪里可以找到它,那基本上可以回答这个问题。到目前为止我发现的所有内容都是为了解调。
这是我在 Whosebug 上围绕 Xarph's Answer 构建的 class,它只是 returns 一个字节数组,包含一个简单的、未调制的音频信号,然后可以使用通过扬声器播放声音(或通过 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 调制在处理字节数组并将其转换为正确格式时的工作原理可能更有价值因为我以后必须实现更多的模式。
关于无线电,我有很多不了解的地方。但我想我可以说几句关于调制的基础知识和手头的问题,因为我有一点物理学知识和编写 FM 合成器的经验。
首先,我认为如果将源信号的 PCM 数据点转换为归一化浮点数(范围从 -1f 到 1f),而不是使用短裤,您可能会发现使用它们更容易。
接收器的目标频率 510-1700 kHz(AM 收音机)明显快于源声音的采样率(大概是 44.1kHz)。假设你有办法输出结果数据,数学将涉及从你的信号中获取一个 PCM 值,适当地缩放它(IDK 多少)并将该值乘以你的载波信号生成的与时间相对应的 PCM 数据点区间.
例如,如果载波信号为 882 kHz,则在移动到下一个源信号值之前,您可以将 20 个载波信号值的序列与源信号值相乘。再一次,我的无知:技术可能有某种平滑算法来处理源信号数据点之间的转换。我真的不知道这件事与否,也不知道它发生在什么阶段。
对于 FM,我们有 MHz 范围内的载波信号,因此我们说每个源信号值生成的数据比 AM 多几个数量级。我不知道使用的确切算法,但这里有一个简单的概念方法来实现我在 FM 合成器中使用的正弦波的频率调制。
假设您有一个包含 1000 个数据点的 table,代表一个介于 -1f 到 1f 之间的正弦波。假设您有一个反复遍历 table 的游标。如果光标以 44100 fps 精确前进 1 个数据点并以该速率传送值,则生成的音调将是 44.1 Hz,是吗?但是您也可以通过大于 1 的间隔遍历 table,例如 1.5。当光标落在两个 table 值之间时,可以使用线性插值来确定要输出的值。光标增量 1.5 将导致正弦波的音高为 66.2 Hz。
我认为 FM 正在发生的事情是这个光标增量是连续变化的,并且它变化的量取决于从源信号转换为一系列增量的某种缩放。
我不知道缩放的细节。但是假设信号以 10MHz 的载波传输并且范围为 ~1%(大约从 9.9 MHz 到 10.1 MHz),归一化源信号将具有某种算法,其中 -1 的 PCM 值与遍历载波导致它产生较慢的频率,+1 匹配穿过载波的增量,导致它产生较高的频率。因此,如果 +1 的增量提供 10 MHz,可能 -1 的源波 PCM 信号会引发 +0.99 的光标增量,-0.5 的 PCM 值会引发 +0.995 的增量,+0.5 的值会引发光标增量+1.005 的值,+1 的值引发 1.01 的游标增量。
这纯粹是我对源 PCM 值与如何使用它来调制载波频率之间关系的推测。但也许它有助于给出基本机制的具体形象?
(我使用类似的东西,使用游标以任意增量迭代 wav PCM 数据点,在 AudioCue(class 用于基于 [=40= 播放音频数据] Clip
),用于实时频移。代码行 1183 保存光标,该光标迭代从 wav 文件导入的 PCM 数据,变量 idx 保存光标增量。第 1317 行是我们在增加光标后获取音频值的地方。代码行 1372 具有执行线性插值的方法 readFractionalFrame()。还实现了实时音量变化,我对 public 输入挂钩提供的值使用平滑。)
同样,IDK 是否在源信号值之间使用了任何类型的平滑。根据我的经验,很多技术都涉及过滤和其他 技巧 各种提高保真度或处理计算的技巧。
题外话:让我先说 Java 对我来说是全新的。我已经编程超过 15 年了,除了修改别人的代码库之外从来没有需要它,所以请原谅我的无知和可能不正确的术语。我对 RF 也不是很熟悉,所以如果我在这里离开领域,请告诉我!
我正在构建一个 SDR(软件定义无线电)无线电发射器,虽然我可以在某个频率上成功传输,但当我发送流时(来自设备的麦克风或来自 tone generator 的字节) , 通过我的手持接收器传来的声音听起来像是静电干扰。
我认为这是因为我的接收器被设置为接收 NFM(窄带调频)和 WFM(宽带调频),而 来自我的 SDR 的传输正在发送原始的、未调制的数据.
我的问题是:如何调制音频字节(即 InputStream),以便在 FM(频率调制) 或 AM(调幅),然后我可以通过 SDR 传输?
我似乎找不到 class 或处理调制的软件包(最终我将不得不调制 WFM、FM、AM、SB、LSB、USB、DSB 等)尽管有相当多的开源 SDR 代码库,但如果你知道我在哪里可以找到它,那基本上可以回答这个问题。到目前为止我发现的所有内容都是为了解调。
这是我在 Whosebug 上围绕 Xarph's Answer 构建的 class,它只是 returns 一个字节数组,包含一个简单的、未调制的音频信号,然后可以使用通过扬声器播放声音(或通过 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 调制在处理字节数组并将其转换为正确格式时的工作原理可能更有价值因为我以后必须实现更多的模式。
关于无线电,我有很多不了解的地方。但我想我可以说几句关于调制的基础知识和手头的问题,因为我有一点物理学知识和编写 FM 合成器的经验。
首先,我认为如果将源信号的 PCM 数据点转换为归一化浮点数(范围从 -1f 到 1f),而不是使用短裤,您可能会发现使用它们更容易。
接收器的目标频率 510-1700 kHz(AM 收音机)明显快于源声音的采样率(大概是 44.1kHz)。假设你有办法输出结果数据,数学将涉及从你的信号中获取一个 PCM 值,适当地缩放它(IDK 多少)并将该值乘以你的载波信号生成的与时间相对应的 PCM 数据点区间.
例如,如果载波信号为 882 kHz,则在移动到下一个源信号值之前,您可以将 20 个载波信号值的序列与源信号值相乘。再一次,我的无知:技术可能有某种平滑算法来处理源信号数据点之间的转换。我真的不知道这件事与否,也不知道它发生在什么阶段。
对于 FM,我们有 MHz 范围内的载波信号,因此我们说每个源信号值生成的数据比 AM 多几个数量级。我不知道使用的确切算法,但这里有一个简单的概念方法来实现我在 FM 合成器中使用的正弦波的频率调制。
假设您有一个包含 1000 个数据点的 table,代表一个介于 -1f 到 1f 之间的正弦波。假设您有一个反复遍历 table 的游标。如果光标以 44100 fps 精确前进 1 个数据点并以该速率传送值,则生成的音调将是 44.1 Hz,是吗?但是您也可以通过大于 1 的间隔遍历 table,例如 1.5。当光标落在两个 table 值之间时,可以使用线性插值来确定要输出的值。光标增量 1.5 将导致正弦波的音高为 66.2 Hz。
我认为 FM 正在发生的事情是这个光标增量是连续变化的,并且它变化的量取决于从源信号转换为一系列增量的某种缩放。
我不知道缩放的细节。但是假设信号以 10MHz 的载波传输并且范围为 ~1%(大约从 9.9 MHz 到 10.1 MHz),归一化源信号将具有某种算法,其中 -1 的 PCM 值与遍历载波导致它产生较慢的频率,+1 匹配穿过载波的增量,导致它产生较高的频率。因此,如果 +1 的增量提供 10 MHz,可能 -1 的源波 PCM 信号会引发 +0.99 的光标增量,-0.5 的 PCM 值会引发 +0.995 的增量,+0.5 的值会引发光标增量+1.005 的值,+1 的值引发 1.01 的游标增量。
这纯粹是我对源 PCM 值与如何使用它来调制载波频率之间关系的推测。但也许它有助于给出基本机制的具体形象?
(我使用类似的东西,使用游标以任意增量迭代 wav PCM 数据点,在 AudioCue(class 用于基于 [=40= 播放音频数据] Clip
),用于实时频移。代码行 1183 保存光标,该光标迭代从 wav 文件导入的 PCM 数据,变量 idx 保存光标增量。第 1317 行是我们在增加光标后获取音频值的地方。代码行 1372 具有执行线性插值的方法 readFractionalFrame()。还实现了实时音量变化,我对 public 输入挂钩提供的值使用平滑。)
同样,IDK 是否在源信号值之间使用了任何类型的平滑。根据我的经验,很多技术都涉及过滤和其他 技巧 各种提高保真度或处理计算的技巧。