【问题标题】:Source for a good, simple, soft modem library [closed]一个好的,简单的,软调制解调器库的来源[关闭]
【发布时间】:2012-05-28 10:41:57
【问题描述】:

我正在做一个奇怪的项目,并希望将一些简短的数据报转换为音频 - 通过(物理)无线电发送它们 - 然后在另一台设备上接收和解码它们(想想 - 带有音频输出插孔的嵌入式设备和GSM/GPRS 型无线电)。

(我必须使用物理的、现有的外部无线电)。

有谁知道适合这样一个项目的简单软件调制解调器库?我不太关心数据速率,并且更喜欢简单而不是功能。即使是类似于基本 1200 波特调制解调器的东西也会很棒。

看这更多的是一种学习经验和潜在的构建块,而不是任何非常实用的东西。

【问题讨论】:

  • 似乎从发送方的任何基本音调发生器开始都可以。在接收端,您可能需要一个基于 FFT 的频率检测器。从那里,您可以通过各种自定义协议了解纠错和避免错误的方法。
  • @bbum:如果您知道频率,则不需要 FFT。我认为一个相对较短的滤波器 (FIR) 和一些逻辑就可以了。
  • @Alex 谢谢!我是第一个承认我的信号过滤技能完全缺乏的人!我希望有人能在这个问题上给出一个很好的答案,因为我有兴趣学习(这听起来像是一个很好的项目!)
  • 你说得对,Alex - 几年前我做了一个项目。我记得只是对样本和载波频率的正弦波进行卷积/关联 - 以及另一个 90 度的异相。两者的幅度为我提供了样本波的相对相位,幅度为我提供了对良好载波置信度的衡量标准。 FFT 可以做同样的事情,这种方式要简单得多,占用的 CPU 也少得多。这是一种“自制”算法——FIR 可能会更干净。
  • 我正在查看这个项目中的 FSK 代码 - 看起来很有希望:soft-switch.org/downloads/spandsp

标签: iphone signal-processing modem


【解决方案1】:

如果您仍在寻找软调制解调器,您可以考虑libquietQuiet.js。如果您使用音频电缆,它们提供了功能相当强大的低功耗 GMSK 模式以及更高比特率的模式。 Quiet 使用现有的 SDR 库来执行其调制,因此您将获得功能相当齐全的东西。

【讨论】:

    【解决方案2】:

    作为练习,我使用 FSK 调制实现了一个类似 V.23 的简单调制解调器,支持 1200 位/秒的数据速率(由于开始位和停止位,960 位/秒有效)。

    我很想知道它是否适用于您的收音机。噪声、信号反射和不完善的解调都会影响调制解调器的性能。

    在尝试将此代码集成到您的项目之前,请先查看它是否适用于从您的收音机录制的音频。

    代码:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <limits.h>
    #include <math.h>
    
    #ifndef M_PI
    #define M_PI 3.14159265358979324
    #endif
    
    typedef unsigned char uchar, uint8;
    typedef signed char schar, int8;
    typedef unsigned short ushort, uint16;
    typedef short int16;
    typedef unsigned int uint;
    typedef unsigned long ulong;
    #if UINT_MAX >= 0xFFFFFFFF
    typedef int int32;
    typedef unsigned int uint32;
    #else
    typedef long int32;
    typedef unsigned long uint32;
    #endif
    typedef long long int64;
    typedef unsigned long long uint64;
    
    typedef struct
    {
      double x, y;
    } tComplex;
    
    tComplex complexAdd(const tComplex* a, const tComplex* b)
    {
      tComplex c;
      c.x = a->x + b->x;
      c.y = a->y + b->y;
      return c;
    }
    
    tComplex complexMul(const tComplex* a, const tComplex* b)
    {
      tComplex c;
      c.x = a->x * b->x - a->y * b->y;
      c.y = a->x * b->y + a->y * b->x;
      return c;
    }
    
    void dft(tComplex out[], const tComplex in[], size_t n, int direction)
    {
      size_t k, i;
      for (k = 0; k < n; k++)
      {
        tComplex r = { 0, 0 }, e;
        for (i = 0; i < n; i++)
        {
          e.x = cos(-2 * direction * M_PI / n * ((double)k - n / 2) * ((double)i - n / 2));
          e.y = sin(-2 * direction * M_PI / n * ((double)k - n / 2) * ((double)i - n / 2));
          e = complexMul(&e, &in[i]);
          r = complexAdd(&r, &e);
        }
        out[k] = r;
      }
    }
    
    #define FILTER_LENGTH 64
    
    typedef struct tTx
    {
      enum
      {
        stSendingOnes,
        stSendingData
      } State;
    
      uint SampleRate;
      uint OnesFreq;
      uint ZeroesFreq;
      uint BitRate;
    
      uint32 SampleCnt;
      uint BitSampleCnt;
      uint Data;
      uint DataLeft;
    
      double Phase;
      double PhaseIncrement;
    
      uint (*pTxGetDataCallBack)(struct tTx*, uint8*);
    } tTx;
    
    void TxInit(tTx* pTx,
                uint SampleRate,
                uint (*pTxGetDataCallBack)(tTx*, uint8*))
    {
      memset(pTx, 0, sizeof(*pTx));
      pTx->State = stSendingOnes;
      pTx->SampleRate = SampleRate;
      pTx->OnesFreq = 1300;
      pTx->ZeroesFreq = 2100;
      pTx->BitRate = 1200;
      pTx->pTxGetDataCallBack = pTxGetDataCallBack;
    
      pTx->SampleCnt = 0;
      pTx->BitSampleCnt = pTx->SampleRate;
      pTx->Data = 0;
      pTx->DataLeft = 0;
      pTx->Phase = 0.0;
      pTx->PhaseIncrement = 2 * M_PI * pTx->OnesFreq / pTx->SampleRate;
    }
    
    int16 TxGetSample(tTx* pTx)
    {
      int16 sample;
    
      if (pTx->State == stSendingOnes &&
          pTx->SampleCnt >= pTx->SampleRate)
      {
        // Sent 1 second worth of 1's, can now send data
        pTx->State = stSendingData;
      }
    
      if (pTx->State == stSendingData &&
          pTx->BitSampleCnt >= pTx->SampleRate)
      {
        // Another data bit can now be sent
        uint8 d;
    
        pTx->BitSampleCnt -= pTx->SampleRate;
    
        if (!pTx->DataLeft)
        {
          // Get the next data byte (if any)
          if (pTx->pTxGetDataCallBack(pTx, &d) != 0)
          {
            pTx->Data = d & 0xFF;
            pTx->Data |= 1 << 8; // insert the stop bit
            pTx->Data <<= 1; // insert the start bit
            pTx->DataLeft = 10;
          }
          else
          {
            pTx->Data = 0x3FF; // no data, send 10 1's
            pTx->DataLeft = 10;
          }
        }
    
        // Extract the next data bit to send
        d = pTx->Data & 1;
        pTx->Data >>= 1;
        pTx->DataLeft--;
    
        // Choose the appropriate frequency for 0 and 1
        if (d)
        {
          pTx->PhaseIncrement = 2 * M_PI * pTx->OnesFreq / pTx->SampleRate;
        }
        else
        {
          pTx->PhaseIncrement = 2 * M_PI * pTx->ZeroesFreq / pTx->SampleRate;
        }
      }
    
      // Generate the next sample, advance the generator's phase
      sample = (int16)(16000 * cos(pTx->Phase));
      pTx->Phase += pTx->PhaseIncrement;
      if (pTx->Phase >= 2 * M_PI)
      {
        pTx->Phase -= 2 * M_PI;
      }
    
      if (pTx->State == stSendingData)
      {
        pTx->BitSampleCnt += pTx->BitRate;
      }
    
      pTx->SampleCnt++;
    
      return sample;
    }
    
    typedef struct tRx
    {
      enum
      {
        stCarrierLost,
        stCarrierDetected,
        stReceivingData
      } State;
    
      uint SampleRate;
      uint OnesFreq;
      uint ZeroesFreq;
      uint MidFreq;
      uint BitRate;
    
      uint32 SampleCnt;
      uint BitSampleCnt;
      uint Data;
    
      double Phase;
      double PhaseIncrement;
    
      tComplex Filter[FILTER_LENGTH];
      double Delay[FILTER_LENGTH];
    
      double LastAngle;
      int LastDelta;
      int32 Deltas;
    
      int32 CarrierAngle;
      int32 CarrierCnt;
    
      double LongAvgPower;
      double ShortAvgPower;
    
      void (*pRxGetDataCallBack)(struct tRx*, uint8);
    } tRx;
    
    void RxInit(tRx* pRx,
                uint SampleRate,
                void (*pRxGetDataCallBack)(struct tRx*, uint8))
    {
      tComplex tmp[FILTER_LENGTH];
      uint i;
    
      memset(pRx, 0, sizeof(*pRx));
      pRx->State = stCarrierLost;
      pRx->SampleRate = SampleRate;
      pRx->OnesFreq = 1300;
      pRx->ZeroesFreq = 2100;
      pRx->MidFreq = (pRx->OnesFreq + pRx->ZeroesFreq) / 2;
      pRx->BitRate = 1200;
      pRx->pRxGetDataCallBack = pRxGetDataCallBack;
    
      pRx->SampleCnt = 0;
      pRx->BitSampleCnt = 0;
      pRx->Data = 0x3FF;
      pRx->Phase = 0.0;
      pRx->PhaseIncrement = 2 * M_PI * pRx->MidFreq / pRx->SampleRate;
      pRx->LastAngle = 0.0;
      pRx->LastDelta = 0;
      pRx->Deltas = 0;
      pRx->CarrierAngle = 0;
      pRx->CarrierCnt = 0;
      pRx->LongAvgPower = 0.0;
      pRx->ShortAvgPower = 0.0;
    
      for (i = 0; i < FILTER_LENGTH; i++)
      {
        pRx->Delay[i] = 0.0;
      }
    
      for (i = 0; i < FILTER_LENGTH; i++)
      {
        if (i == 0) // w < 0 (min w)
        {
          pRx->Filter[i].x = 0;
          pRx->Filter[i].y = 0;
        }
        else if (i < FILTER_LENGTH / 2) // w < 0
        {
          pRx->Filter[i].x = 0;
          pRx->Filter[i].y = 0;
        }
        else if (i == FILTER_LENGTH / 2) // w = 0
        {
          pRx->Filter[i].x = 0;
          pRx->Filter[i].y = 0;
        }
        else if (i > FILTER_LENGTH / 2) // w > 0
        {
          pRx->Filter[i].x = 0;
          pRx->Filter[i].y = -1;
    
          // Extra filter to combat channel noise
          if (i - FILTER_LENGTH / 2 < 875UL * FILTER_LENGTH / pRx->SampleRate ||
              i - FILTER_LENGTH / 2 > (2350UL * FILTER_LENGTH + pRx->SampleRate - 1) / pRx->SampleRate)
          {
            pRx->Filter[i].y = 0;
          }
        }
      }
    
      memcpy(tmp, pRx->Filter, sizeof(tmp));
      dft(pRx->Filter, tmp, FILTER_LENGTH, -1);
    }
    
    #define RX_VERBOSE 0
    void RxGetSample(tRx* pRx, int16 Sample)
    {
      tComplex s = { 0.0, 0.0 }, ss;
      double angle;
      uint i;
      int delta;
      double pwr;
    
      // Insert the sample into the delay line
      memmove(&pRx->Delay[0], &pRx->Delay[1], sizeof(pRx->Delay) - sizeof(pRx->Delay[0]));
      pRx->Delay[FILTER_LENGTH - 1] = Sample;
    
      // Get the next analytic signal sample by applying Hilbert transform/filter
      for (i = 0; i < FILTER_LENGTH; i++)
      {
        s.x += pRx->Delay[i] * pRx->Filter[FILTER_LENGTH - 1 - i].x;
        s.y += pRx->Delay[i] * pRx->Filter[FILTER_LENGTH - 1 - i].y;
      }
    
      // Frequency shift by MidFreq down
      ss.x = cos(-pRx->Phase);
      ss.y = sin(-pRx->Phase);
      s = complexMul(&s, &ss);
      pRx->Phase += pRx->PhaseIncrement;
      if (pRx->Phase >= 2 * M_PI)
      {
        pRx->Phase -= 2 * M_PI;
      }
    
      // Calculate signal power
      pwr = (s.x * s.x + s.y * s.y) / 32768 / 32768;
      pRx->LongAvgPower *= 1 - pRx->BitRate / (pRx->SampleRate * 8.0 * 8);
      pRx->LongAvgPower += pwr;
      pRx->ShortAvgPower *= 1 - pRx->BitRate / (pRx->SampleRate * 8.0);
      pRx->ShortAvgPower += pwr;
    
    #if 0
      printf("LongAvgPower:%f ShortAvgPower:%f\n", pRx->LongAvgPower, pRx->ShortAvgPower);
    #endif
    
      // Disconnect if the signal power changes abruptly.
      if (pRx->State != stCarrierLost &&
          pRx->LongAvgPower > pRx->ShortAvgPower * 8 * 8)
      {
        // N.B. The receiver may have received a few extra (garbage) bytes
        // while demodulating the abruptly changed signal.
        // Prefixing data with its size or using a more advanced protocol
        // may be a good solution to this little problem.
        pRx->State = stCarrierLost;
        pRx->BitSampleCnt = 0;
        pRx->Data = 0x3FF;
        pRx->Phase = 0.0;
        pRx->LastAngle = 0.0;
        pRx->LastDelta = 0;
        pRx->Deltas = 0;
        pRx->CarrierAngle = 0;
        pRx->CarrierCnt = 0;
      }
    
      // Get the phase angle from the analytic signal sample
      angle = (fpclassify(s.x) == FP_ZERO && fpclassify(s.y) == FP_ZERO) ?
        0.0 : 180 / M_PI * atan2(s.y, s.x);
      // Calculate the phase angle change and force it to the -PI to +PI range
      delta = (int)(360.5 + angle - pRx->LastAngle) % 360;
      if (delta > 180) delta -= 360;
    
      if (pRx->State == stCarrierLost)
      {
        // Accumulate the phase angle change to see if we're receiving 1's
        pRx->CarrierAngle += delta;
        pRx->CarrierCnt++;
    
        // Check whether or not the phase corresponds to 1's
        if (delta < 0)
        {
          if (pRx->CarrierCnt >= pRx->SampleRate / pRx->OnesFreq * 8)
          {
            double ph = (double)pRx->CarrierAngle / pRx->CarrierCnt;
    #if RX_VERBOSE
            printf("ca:%5ld, cc:%4ld, ca/cc:%4ld\n",
                   (long)pRx->CarrierAngle,
                   (long)pRx->CarrierCnt,
                   (long)(pRx->CarrierAngle / pRx->CarrierCnt));
    #endif
            // Frequency tolerance is +/-16 Hz per the V.23 spec
            if (ph < (pRx->OnesFreq - 17.0 - pRx->MidFreq) * 360.0 / pRx->SampleRate ||
                ph > (pRx->OnesFreq + 17.0 - pRx->MidFreq) * 360.0 / pRx->SampleRate)
            {
              goto BadCarrier;
            }
          }
        }
        else
        {
    BadCarrier:
          // Phase doesn't correspond to 1's
          pRx->CarrierAngle = 0.0;
          pRx->CarrierCnt = 0;
        }
    
        if (pRx->CarrierCnt >= pRx->SampleRate / 2 + pRx->SampleRate / 4)
        {
          // 0.75 seconds worth of 1's have been detected, ready to receive data
    
          // Adjust MidFreq to compensate for the DAC and ADC sample rate difference
          double f1 = (double)pRx->CarrierAngle / pRx->CarrierCnt / 360 * pRx->SampleRate + pRx->MidFreq;
          pRx->MidFreq = (uint)(pRx->MidFreq * f1 / pRx->OnesFreq);
          pRx->PhaseIncrement = 2 * M_PI * pRx->MidFreq / pRx->SampleRate;
    #if RX_VERBOSE
          printf("f1:%u, new MidFreq:%u\n", (uint)f1, pRx->MidFreq);
    #endif
          pRx->State = stCarrierDetected;
        }
      }
      else
      {
        // Detect frequency changes (transitions between 0's and 1's)
        int freqChange = ((int32)pRx->LastDelta * delta < 0 || pRx->LastDelta && !delta);
        int reAddDelta = 0;
    
    #if RX_VERBOSE
        printf("%6lu: delta:%4d freqChange:%d BitSampleCnt:%u\n",
               (ulong)pRx->SampleCnt,
               delta,
               freqChange,
               pRx->BitSampleCnt);
    #endif
    
        // Synchronize with 1<->0 transitions
        if (freqChange)
        {
          if (pRx->BitSampleCnt >= pRx->SampleRate / 2)
          {
            pRx->BitSampleCnt = pRx->SampleRate;
            pRx->Deltas -= delta;
            reAddDelta = 1;
          }
          else
          {
            pRx->BitSampleCnt = 0;
            pRx->Deltas = 0;
          }
        }
    
        // Accumulate analytic signal phase angle changes
        // (positive for 0, negative for 1)
        pRx->Deltas += delta;
    
        if (pRx->BitSampleCnt >= pRx->SampleRate)
        {
          // Another data bit has accumulated
          pRx->BitSampleCnt -= pRx->SampleRate;
    
    #if RX_VERBOSE
          printf("bit: %u\n", pRx->Deltas < 0);
    #endif
    
          pRx->Data >>= 1;
          pRx->Data |= (pRx->Deltas < 0) << 9;
          pRx->Deltas = delta * reAddDelta;
    
          if ((pRx->Data & 0x201) == 0x200)
          {
            // Start and stop bits have been detected
            if (pRx->State == stCarrierDetected)
            {
              pRx->State = stReceivingData;
            }
            pRx->Data = (pRx->Data >> 1) & 0xFF;
            pRx->pRxGetDataCallBack(pRx, (uint8)pRx->Data);
    
    #if RX_VERBOSE
            printf("byte: 0x%02X ('%c')\n",
                   pRx->Data,
                   (pRx->Data >= 0x20 && pRx->Data <= 0x7F) ? pRx->Data : '?');
    #endif
    
            pRx->Data = 0x3FF;
          }
        }
    
        pRx->BitSampleCnt += pRx->BitRate;
      }
    
      pRx->LastAngle = angle;
      pRx->LastDelta = delta;
      pRx->SampleCnt++;
    }
    
    typedef struct
    {
      tTx Tx;
      FILE* DataFile;
      int CountDown;
    } tTxTest;
    
    uint TxGetDataCallBack(tTx* pTx, uint8* pTxData)
    {
      tTxTest* pTxTest = (tTxTest*)pTx;
      uchar c;
    
      if (pTxTest->CountDown)
      {
        pTxTest->CountDown--;
        return 0;
      }
    
      if (fread(&c, 1, 1, pTxTest->DataFile) != 1)
      {
        pTxTest->CountDown = 20;
        return 0;
      }
    
      *pTxData = c;
      return 1;
    }
    
    int testTx(uint SampleRate,
               double NoiseLevel,
               const char* DataFileName,
               const char* AudioFileName)
    {
      FILE *fData = NULL, *fAudio = NULL;
      int err = EXIT_FAILURE;
      tTxTest txTest;
    
      if ((fData = fopen(DataFileName, "rb")) == NULL)
      {
        printf("Can't open file \"%s\"\n", DataFileName);
        goto Exit;
      }
    
      if ((fAudio = fopen(AudioFileName, "wb")) == NULL)
      {
        printf("Can't create file \"%s\"\n", AudioFileName);
        goto Exit;
      }
    
      txTest.DataFile = fData;
      txTest.CountDown = 0;
    
      TxInit(&txTest.Tx,
             SampleRate,
             &TxGetDataCallBack);
    
      do
      {
        int16 sample = TxGetSample(&txTest.Tx);
        if (txTest.CountDown > 1 && txTest.CountDown <= 10)
        {
    #if 0 // Enable this code to test disconnecting.
          // Finish with silence.
          sample = 0;
    #endif
        }
        sample += (rand() - (int)RAND_MAX / 2) * NoiseLevel * 16000 / (RAND_MAX / 2);
        fwrite(&sample, 1, sizeof(sample), fAudio);
      } while (txTest.CountDown != 1); // Drain all data-containing samples
    
      err = EXIT_SUCCESS;
    
    Exit:
    
      if (fData != NULL) fclose(fData);
      if (fAudio != NULL) fclose(fAudio);
    
      return err;
    }
    
    typedef struct
    {
      tRx Rx;
      FILE* DataFile;
    } tRxTest;
    
    void RxGetDataCallBack(tRx* pRx, uint8 RxData)
    {
      tRxTest* pRxTest = (tRxTest*)pRx;
      uchar c = RxData;
      fwrite(&c, 1, 1, pRxTest->DataFile);
    }
    
    int testRx(uint SampleRate,
               const char* AudioFileName,
               const char* DataFileName)
    {
      uint lastState;
      FILE *fAudio = NULL, *fData = NULL;
      int err = EXIT_FAILURE;
      tRxTest rxTest;
    
      if ((fAudio = fopen(AudioFileName, "rb")) == NULL)
      {
        printf("Can't open file \"%s\"\n", AudioFileName);
        goto Exit;
      }
    
      if ((fData = fopen(DataFileName, "wb")) == NULL)
      {
        printf("Can't create file \"%s\"\n", DataFileName);
        goto Exit;
      }
    
      rxTest.DataFile = fData;
    
      RxInit(&rxTest.Rx,
             SampleRate,
             &RxGetDataCallBack);
    
      for (;;)
      {
        int16 sample;
    
        if (fread(&sample, 1, sizeof(sample), fAudio) != sizeof(sample))
        {
          if (rxTest.Rx.State != stCarrierLost) goto NoCarrier;
          break;
        }
    
        lastState = rxTest.Rx.State;
        RxGetSample(&rxTest.Rx, sample);
    
        if (rxTest.Rx.State != lastState && rxTest.Rx.State == stCarrierDetected)
        {
          printf("\nCONNECT %u\n\n", rxTest.Rx.BitRate);
        }
    
        if (rxTest.Rx.State != lastState && rxTest.Rx.State == stCarrierLost)
        {
    NoCarrier:
          printf("\n\nNO CARRIER\n");
          break;
        }
      }
    
      err = EXIT_SUCCESS;
    
    Exit:
    
      if (fAudio != NULL) fclose(fAudio);
      if (fData != NULL) fclose(fData);
    
      return err;
    }
    
    int main(int argc, char* argv[])
    {
      uint sampleRate;
      double noiseLevel;
    
      if (argc < 2 ||
          !stricmp(argv[1], "-help") ||
          !stricmp(argv[1], "/help") ||
          !stricmp(argv[1], "-?") ||
          !stricmp(argv[1], "/?"))
      {
    Usage:
        printf("Usage:\n\n"
               "  %s tx <sample rate> <noise level> <data input file> <PCM output file>\n"
               "  %s rx <sample rate> <PCM input file> <data output file>\n",
               argv[0],
               argv[0]);
        return 0;
      }
    
      if (!stricmp(argv[1], "tx") &&
          argc == 6 &&
          sscanf(argv[2], "%u", &sampleRate) == 1 &&
          sscanf(argv[3], "%lf", &noiseLevel) == 1)
      {
        return testTx(sampleRate, noiseLevel, argv[4], argv[5]);
      }
      else if (!stricmp(argv[1], "rx") &&
               argc == 5 &&
               sscanf(argv[2], "%u", &sampleRate) == 1)
      {
        return testRx(sampleRate, argv[3], argv[4]);
      }
      else
      {
        goto Usage;
      }
    }
    

    典型用法:

    modem.exe tx 8000 0.2 testin.txt test8000.pcm
    modem.exe rx 8000 test8000.pcm testout.txt
    

    生成的 testout.txt 应该与 testin.txt 相同。

    【讨论】:

    • 哇!期待尝试这个! :-)
    • 在尝试将代码导出网页时遇到问题。我可以在某处下载文件吗?
    • 也许在编辑中得到它会更容易一些?开始编辑答案。
    • 使用这个 - 我想我可以通过改变“BitRate”参数来改变比特率。但也有“OnesFreq”和“ZeroesFreq”。这些是什么? (我可以总结一下显而易见的) - 或者更确切地说,它们是否依赖于比特率,或者它们是否可以独立调整?我问是因为 - 我的数据非常简洁。在嘈杂的频道上使用更低的比特率和更明显的(零和一)电平会更好吗?
    • 在 FSK 中,符号(此处为位 0 和 1)由不同频率的音调(此处为 ZeroesFreq 和 OnesFreq)表示。为了使这些符号/音调可检测,它们的持续时间必须超过一个完整的正弦波周期(IOW、1/ZeroesFreq BitRate 和 OnesFreq > BitRate)。这种 FSK 调制信号的带宽从 OnesFreq-BitRate/2(此处为 1300-1200/2=700 Hz)到 ZeroesFreq+BitRate/2(此处为 2100+1200/2=2700 Hz)和上限频率(2700 Hz 这里)必须低于 SampleRate/2(这里 8000/2=4000 Hz)。
    【解决方案3】:

    网络搜索会发现许多业余无线电 BPSK 和 RTTY/FSK 解决方案。大部分代码是为较慢的较旧 CPU 编写的,因此在 iPhone 上应该可以正常运行。您可以使用 Audio Queue API 或 RemoteIO Audio Unit for iOS 音频 IO 到编解码器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-13
      • 2021-11-24
      • 2021-04-14
      • 1970-01-01
      • 2011-07-20
      • 1970-01-01
      相关资源
      最近更新 更多