1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-10-31 15:07:12 -04:00

M17 mod audio with file input

This commit is contained in:
f4exb 2022-06-29 08:45:44 +02:00
parent 1fc688c904
commit 8f48d7d135
13 changed files with 713 additions and 27 deletions

View File

@ -403,7 +403,7 @@ void M17Demodulator::do_frame(float filtered_sample)
if (len != 0)
{
std::cerr << "M17Demodulator::do_frame: sync_word_type:" << (int) sync_word_type << " len:" << len << std::endl;
// std::cerr << "M17Demodulator::do_frame: sync_word_type:" << (int) sync_word_type << " len:" << len << std::endl;
need_clock_update_ = true;
M17FrameDecoder::input_buffer_t buffer;
std::copy(tmp, tmp + len, buffer.begin());

View File

@ -33,7 +33,7 @@ public:
using baseband_t = std::array<int16_t, 1920>; // One frame of baseband data @ 48ksps
using bitstream_t = std::array<uint8_t, 48>; // M17 frame of bits (in bytes).
using lsf_t = std::array<uint8_t, 30>; // Link setup frame bytes.
using lich_segment_t = std::array<uint8_t, 12>; // Golay-encoded LICH.
using lich_segment_t = std::array<uint8_t, 96>; // Golay-encoded LICH bits.
using lich_t = std::array<lich_segment_t, 6>; // All LICH segments.
using audio_frame_t = std::array<int16_t, 320>;
using codec_frame_t = std::array<uint8_t, 16>;
@ -186,6 +186,103 @@ public:
return punctured;
}
static lich_segment_t make_lich_segment(std::array<uint8_t, 5> segment, uint8_t segment_number)
{
lich_segment_t result;
uint16_t tmp;
uint32_t encoded;
tmp = segment[0];
tmp <<= 4;
tmp |= ((segment[1] >> 4) & 0x0F);
// tmp = segment[0] << 4 | ((segment[1] >> 4) & 0x0F);
encoded = mobilinkd::Golay24::encode24(tmp);
for (size_t i = 0; i != 24; ++i)
{
result[i] = (encoded & (1 << 23)) != 0 ? 1 : 0;
encoded <<= 1;
}
tmp = segment[1] & 0x0F;
tmp <<= 8;
tmp |= segment[2];
// tmp = ((segment[1] & 0x0F) << 8) | segment[2];
encoded = mobilinkd::Golay24::encode24(tmp);
for (size_t i = 24; i != 48; ++i)
{
result[i] = (encoded & (1 << 23)) != 0 ? 1 : 0;
encoded <<= 1;
}
tmp = segment[3];
tmp <<= 4;
tmp |= ((segment[4] >> 4) & 0x0F);
// tmp = segment[3] << 4 | ((segment[4] >> 4) & 0x0F);
encoded = mobilinkd::Golay24::encode24(tmp);
for (size_t i = 48; i != 72; ++i)
{
result[i] = (encoded & (1 << 23)) != 0 ? 1 : 0;
encoded <<= 1;
}
tmp = segment[4] & 0x0F;
tmp <<= 8;
tmp |= (segment_number << 5);
// tmp = ((segment[4] & 0x0F) << 8) | (segment_number << 5);
encoded = mobilinkd::Golay24::encode24(tmp);
for (size_t i = 72; i != 96; ++i)
{
result[i] = (encoded & (1 << 23)) != 0 ? 1 : 0;
encoded <<= 1;
}
return result;
}
static std::array<int8_t, 272> make_stream_data_frame(uint16_t frame_number, const codec_frame_t& payload)
{
std::array<uint8_t, 18> data; // FN, payload = 2 + 16;
data[0] = uint8_t((frame_number >> 8) & 0xFF);
data[1] = uint8_t(frame_number & 0xFF);
std::copy(payload.begin(), payload.end(), data.begin() + 2);
std::array<uint8_t, 296> encoded;
size_t index = 0;
uint32_t memory = 0;
for (auto b : data)
{
for (size_t i = 0; i != 8; ++i)
{
uint32_t x = (b & 0x80) >> 7;
b <<= 1;
memory = mobilinkd::update_memory<4>(memory, x);
encoded[index++] = mobilinkd::convolve_bit(031, memory);
encoded[index++] = mobilinkd::convolve_bit(027, memory);
}
}
// Flush the encoder.
for (size_t i = 0; i != 4; ++i)
{
memory = mobilinkd::update_memory<4>(memory, 0);
encoded[index++] = mobilinkd::convolve_bit(031, memory);
encoded[index++] = mobilinkd::convolve_bit(027, memory);
}
std::array<int8_t, 272> punctured;
auto size = mobilinkd::puncture(encoded, punctured, mobilinkd::P2);
if (size != 272) {
std::cerr << "mobilinkd::M17Modulator::make_stream_data_frame: incorrect size (not 272)" << size;
}
return punctured;
}
std::array<int8_t, 368> make_packet_frame(
uint8_t packet_number,
int packet_size,
@ -271,6 +368,15 @@ public:
return punctured;
}
static void interleave_and_randomize(std::array<int8_t, 368>& punctured)
{
M17Randomizer<368> randomizer;
PolynomialInterleaver<45, 92, 368> interleaver;
interleaver.interleave(punctured);
randomizer.randomize(punctured);
}
M17Modulator(const std::string& source, const std::string& dest = "") :
source_(encode_callsign(source)),
dest_(encode_callsign(dest)),

View File

@ -6,6 +6,7 @@ set(modm17_SOURCES
m17modsource.cpp
m17modprocessor.cpp
m17modfifo.cpp
m17moddecimator.cpp
m17modplugin.cpp
m17modsettings.cpp
m17modwebapiadapter.cpp
@ -17,6 +18,7 @@ set(modm17_HEADERS
m17modsource.h
m17modprocessor.h
m17modfifo.h
m17moddecimator.h
m17modplugin.h
m17modsettings.h
m17modwebapiadapter.h

View File

@ -0,0 +1,145 @@
#include <cmath>
#include "m17moddecimator.h"
M17ModDecimator::M17ModDecimator() :
mKernel(nullptr),
mShift(nullptr)
{}
M17ModDecimator::~M17ModDecimator()
{
delete [] mKernel;
delete [] mShift;
}
void M17ModDecimator::initialize(
double decimatedSampleRate,
double passFrequency,
unsigned oversampleRatio
)
{
mDecimatedSampleRate = decimatedSampleRate;
mRatio = oversampleRatio;
mOversampleRate = decimatedSampleRate * oversampleRatio;
double NyquistFreq = decimatedSampleRate / 2;
// See DSP Guide.
double Fc = (NyquistFreq + passFrequency) / 2 / mOversampleRate;
double BW = (NyquistFreq - passFrequency) / mOversampleRate;
int M = std::ceil(4 / BW);
if (M % 2) {
M++;
}
unsigned int activeKernelSize = M + 1;
unsigned int inactiveSize = mRatio - activeKernelSize % mRatio;
mKernelSize = activeKernelSize + inactiveSize;
// DSP Guide uses approx. values. Got these from Wikipedia.
double a0 = 7938. / 18608., a1 = 9240. / 18608., a2 = 1430. / 18608.;
// Allocate and initialize the FIR filter kernel.
delete [] mKernel;
mKernel = new float[mKernelSize];
double gain = 0;
for (unsigned int i = 0; i < inactiveSize; i++) {
mKernel[i] = 0;
}
for (int i = 0; i < activeKernelSize; i++)
{
double y;
if (i == M/2) {
y = 2 * M_PI * Fc;
} else {
y = (sin(2 * M_PI * Fc * (i - M / 2)) / (i - M / 2) *
(a0 - a1 * cos(2 * M_PI * i/ M) + a2 * cos(4 * M_PI / M)));
}
gain += y;
mKernel[inactiveSize + i] = y;
}
// Adjust the kernel for unity gain.
float inv_gain = 1 / gain;
for (unsigned int i = inactiveSize; i < mKernelSize; i++) {
mKernel[i] *= inv_gain;
}
// Allocate and clear the shift register.
delete [] mShift;
mShift = new float[mKernelSize];
for (unsigned int i = 0; i < mKernelSize; i++) {
mShift[i] = 0;
}
mCursor = 0;
}
// The filter kernel is linear. Coefficients for oldest samples
// are on the left; newest on the right.
//
// The shift register is circular. Oldest samples are at cursor;
// newest are just left of cursor.
//
// We have to do the multiply-accumulate in two pieces.
//
// Kernel
// +------------+----------------+
// | 0 .. n-c-1 | n-c .. n-1 |
// +------------+----------------+
// ^ ^ ^
// 0 n-c n
//
// Shift Register
// +----------------+------------+
// | n-c .. n-1 | 0 .. n-c-1 |
// +----------------+------------+
// ^ ^ ^
// mShift shiftp n
void M17ModDecimator::decimate(int16_t *in, int16_t *out, unsigned int outCount)
{
// assert(!(mCursor % mRatio));
// assert(mCursor < mKernelSize);
unsigned int cursor = mCursor;
int16_t *inp = in;
float *shiftp = mShift + cursor;
for (unsigned int i = 0; i < outCount; i++)
{
// Insert mRatio input samples at cursor.
for (unsigned int j = 0; j < mRatio; j++) {
*shiftp++ = *inp++;
}
if ((cursor += mRatio) == mKernelSize)
{
cursor = 0;
shiftp = mShift;
}
// Calculate one output sample.
double acc = 0;
unsigned int size0 = mKernelSize - cursor;
unsigned int size1 = cursor;
const float *kernel1 = mKernel + size0;
for (unsigned int j = 0; j < size0; j++) {
acc += shiftp[j] * mKernel[j];
}
for (unsigned int j = 0; j < size1; j++) {
acc += mShift[j] * kernel1[j];
}
out[i] = acc;
}
mCursor = cursor;
}

View File

@ -0,0 +1,49 @@
// Polyphase decimation filter.
//
// Convert an oversampled audio stream to non-oversampled. Uses a
// windowed sinc FIR filter w/ Blackman window to control aliasing.
// Christian Floisand's 'blog explains it very well.
//
// This version has a very simple main processing loop (the decimate
// method) which vectorizes easily.
//
// Refs:
// https://christianfloisand.wordpress.com/2012/12/05/audio-resampling-part-1/
// https://christianfloisand.wordpress.com/2013/01/28/audio-resampling-part-2/
// http://www.dspguide.com/ch16.htm
// http://en.wikipedia.org/wiki/Window_function#Blackman_windows
#ifndef INCLUDE_M17MODDECIMATOR_H
#define INCLUDE_M17MODDECIMATOR_H
#include <cstdint>
class M17ModDecimator {
public:
M17ModDecimator();
~M17ModDecimator();
void initialize(
double decimatedSampleRate,
double passFrequency,
unsigned oversampleRatio
);
double oversampleRate() const { return mOversampleRate; }
int oversampleRatio() const { return mRatio; }
void decimate(int16_t *in, int16_t *out, unsigned int outCount);
// N.B., input must have (ratio * outCount) samples.
private:
double mDecimatedSampleRate;
double mOversampleRate;
int mRatio; // oversample ratio
float *mKernel;
unsigned int mKernelSize;
float *mShift; // shift register
unsigned int mCursor;
};
#endif // INCLUDE_M17MODDECIMATOR_H

View File

@ -18,7 +18,8 @@
#include "m17modfifo.h"
M17ModFIFO::M17ModFIFO() :
m_fifo(nullptr)
m_fifo(nullptr),
m_fifoEmpty(true)
{
m_size = 0;
m_writeIndex = 0;
@ -103,12 +104,29 @@ uint32_t M17ModFIFO::readOne(int16_t* data)
if (m_readIndex == m_writeIndex)
{
if (!m_fifoEmpty) {
qDebug("M17ModFIFO::readOne: FIFO gets empty");
}
m_fifoEmpty = true;
*data = 0;
return 0;
}
else
{
m_fifoEmpty = false;
}
*data = m_fifo[m_readIndex++];
m_readIndex = m_readIndex == (int) m_size ? 0 : m_readIndex;
return 1;
}
int M17ModFIFO::getFill() const
{
if (m_readIndex > m_writeIndex) {
return m_size - (m_readIndex - m_writeIndex);
} else {
return m_writeIndex - m_readIndex;
}
}

View File

@ -30,8 +30,10 @@ public:
~M17ModFIFO();
void setSize(uint32_t numSamples);
uint32_t getSize() const { return m_size; }
uint32_t write(const int16_t* data, uint32_t numSamples);
uint32_t readOne(int16_t* data);
int getFill() const;
private:
QMutex m_mutex;
@ -39,6 +41,7 @@ private:
uint32_t m_size;
int m_writeIndex;
int m_readIndex;
bool m_fifoEmpty;
void create(uint32_t numSamples);
};

View File

@ -29,6 +29,7 @@
#include "util/db.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/scopevisxy.h"
#include "gui/crightclickenabler.h"
#include "gui/audioselectdialog.h"
#include "gui/basicchannelsettingsdialog.h"
@ -435,9 +436,27 @@ M17ModGUI::M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
ui->screenTV->setColor(true);
ui->screenTV->resizeTVScreen(100,100);
m_scopeVisXY = new ScopeVisXY(ui->screenTV);
m_scopeVisXY->setScale(2.0);
m_scopeVisXY->setPixelsPerFrame(4001);
m_scopeVisXY->setPlotRGB(qRgb(0, 220, 250));
m_scopeVisXY->setGridRGB(qRgb(255, 255, 128));
for (float x = -0.84; x < 1.0; x += 0.56)
{
for (float y = -0.84; y < 1.0; y += 0.56)
{
m_scopeVisXY->addGraticulePoint(std::complex<float>(x, y));
}
}
m_scopeVisXY->calculateGraticule(100,100);
m_m17Mod = (M17Mod*) channelTx;
m_m17Mod->setMessageQueueToGUI(getInputMessageQueue());
@ -484,6 +503,8 @@ M17ModGUI::M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
M17ModGUI::~M17ModGUI()
{
delete m_scopeVisXY;
ui->screenTV->setParent(nullptr); // Prefer memory leak to core dump... ~TVScreen() is buggy
delete ui;
}
@ -557,6 +578,10 @@ void M17ModGUI::displaySettings()
ui->aprsTo->lineEdit()->setText(m_settings.m_aprsTo);
ui->aprsVia->lineEdit()->setText(m_settings.m_aprsVia);
m_scopeVisXY->setPixelsPerFrame(400*960); // 48000 / 50. Chunks of 50 ms.
m_scopeVisXY->setStroke(220);
m_scopeVisXY->setDecay(200);
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
blockApplySettings(false);

View File

@ -32,6 +32,7 @@
class PluginAPI;
class DeviceUISet;
class BasebandSampleSource;
class ScopeVisXY;
namespace Ui {
class M17ModGUI;
@ -77,6 +78,7 @@ private:
int m_basebandSampleRate;
bool m_doApplySettings;
bool m_fmAudioMode;
ScopeVisXY* m_scopeVisXY;
M17Mod* m_m17Mod;
MovingAverageUtil<double, double, 20> m_channelPowerDbAvg;

View File

@ -15,21 +15,36 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <codec2/codec2.h>
#include "m17/M17Modulator.h"
#include "m17modprocessor.h"
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendSMS, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendAudio, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendAudioFrame, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgStartAudio, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgStopAudio, Message)
M17ModProcessor::M17ModProcessor() :
m_m17Modulator("MYCALL", "")
m_m17Modulator("MYCALL", ""),
m_lichSegmentIndex(0),
m_audioFrameIndex(0),
m_audioFrameNumber(0)
{
m_basebandFifo.setSize(96000);
m_basebandFifoLow = 4096;
m_basebandFifoHigh = 96000 - m_basebandFifoLow;
m_decimator.initialize(8000.0, 3000.0, 6);
m_codec2 = ::codec2_create(CODEC2_MODE_3200);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
M17ModProcessor::~M17ModProcessor()
{}
{
codec2_destroy(m_codec2);
}
bool M17ModProcessor::handleMessage(const Message& cmd)
{
@ -43,6 +58,34 @@ bool M17ModProcessor::handleMessage(const Message& cmd)
// test(notif.getSourceCall(), notif.getDestCall());
return true;
}
else if (MsgSendAudio::match(cmd))
{
MsgSendAudio& notif = (MsgSendAudio&) cmd;
// qDebug("M17ModProcessor::handleMessage: MsgSendAudio: %lu samples", notif.getAudioBuffer().size());
processAudio(notif.getAudioBuffer());
return true;
}
else if (MsgSendAudioFrame::match(cmd))
{
MsgSendAudioFrame& notif = (MsgSendAudioFrame&) cmd;
m_audioFrame = notif.getAudioFrame();
processAudioFrame();
return true;
}
else if (MsgStartAudio::match(cmd))
{
MsgStartAudio& notif = (MsgStartAudio&) cmd;
qDebug("M17ModProcessor::handleMessage: MsgStartAudio: %s to %s",
qPrintable(notif.getSourceCall()), qPrintable(notif.getDestCall()));
audioStart(notif.getSourceCall(), notif.getDestCall());
return true;
}
else if (MsgStopAudio::match(cmd))
{
qDebug("M17ModProcessor::handleMessage: MsgStopAudio");
audioStop();
return true;
}
return false;
}
@ -107,6 +150,43 @@ void M17ModProcessor::processPacket(const QString& sourceCall, const QString& de
send_eot(); // EOT
}
void M17ModProcessor::audioStart(const QString& sourceCall, const QString& destCall)
{
qDebug("M17ModProcessor::audioStart");
m_m17Modulator.source(sourceCall.toStdString());
m_m17Modulator.dest(destCall.toStdString());
m_audioFrameNumber = 0;
send_preamble(); // preamble
// LSF
std::array<uint8_t, 30> lsf;
std::array<int8_t, 368> lsf_frame = mobilinkd::M17Modulator::make_lsf(lsf, sourceCall.toStdString(), destCall.toStdString());
output_baseband(mobilinkd::M17Modulator::LSF_SYNC_WORD, lsf_frame);
// Prepare LICH
for (size_t i = 0; i < m_lich.size(); ++i)
{
std::array<uint8_t, 5> segment;
std::copy(lsf.begin() + i*5, lsf.begin() + (i + 1)*5, segment.begin());
mobilinkd::M17Modulator::lich_segment_t lich_segment = mobilinkd::M17Modulator::make_lich_segment(segment, i);
std::copy(lich_segment.begin(), lich_segment.end(), m_lich[i].begin());
}
}
void M17ModProcessor::audioStop()
{
qDebug("M17ModProcessor::audioStop");
if (m_audioFrameIndex > 0) // send remainder audio + null samples
{
std::fill(m_audioFrame.begin() + m_audioFrameIndex, m_audioFrame.end(), 0);
processAudioFrame();
m_audioFrameIndex = 0;
}
send_eot(); // EOT
}
void M17ModProcessor::send_preamble()
{
// Preamble is simple... bytes -> symbols -> baseband.
@ -117,6 +197,80 @@ void M17ModProcessor::send_preamble()
m_basebandFifo.write(preamble_baseband.data(), 1920);
}
void M17ModProcessor::processAudio(const std::vector<int16_t>& audioBuffer)
{
int audioBufferRemainder = audioBuffer.size();
std::vector<int16_t>::const_iterator audioBufferIt = audioBuffer.begin();
int audioFrameRemainder = m_audioFrame.size() - m_audioFrameIndex;
if (audioBufferRemainder < audioFrameRemainder) {
qDebug("M17ModProcessor::processAudio: too few samples: %d/%d", audioBufferRemainder, audioFrameRemainder);
}
while (audioBufferRemainder >= (int) m_audioFrame.size())
{
m_audioFrame.fill(0);
std::copy(
audioBufferIt,
audioBufferIt + audioFrameRemainder,
m_audioFrame.begin() + m_audioFrameIndex);
if (m_basebandFifo.getFill() < m_basebandFifoHigh) {
processAudioFrame();
}
if (m_basebandFifo.getFill() < m_basebandFifoLow)
{
qDebug("M17ModProcessor::processAudio: repeat frame: %d", m_basebandFifo.getFill());
// m_audioFrame.fill(0);
processAudioFrame();
}
audioBufferRemainder -= audioFrameRemainder;
audioBufferIt += audioFrameRemainder;
m_audioFrameIndex = 0;
audioFrameRemainder = m_audioFrame.size();
}
std::copy(audioBufferIt, audioBuffer.end(), m_audioFrame.begin());
m_audioFrameIndex = audioBufferRemainder;
}
void M17ModProcessor::processAudioFrame()
{
std::array<uint8_t, 16> audioPayload = encodeAudio(m_audioFrame);
std::array<int8_t, 272> audioDataBits = mobilinkd::M17Modulator::make_stream_data_frame(m_audioFrameNumber++, audioPayload);
if (m_audioFrameNumber == 0x8000) {
m_audioFrameNumber = 0;
}
std::array<uint8_t, 96>& lich = m_lich[m_lichSegmentIndex++];
if (m_lichSegmentIndex == 6) {
m_lichSegmentIndex = 0;
}
std::array<int8_t, 368> temp;
auto it = std::copy(lich.begin(), lich.end(), temp.begin());
std::copy(audioDataBits.begin(), audioDataBits.end(), it);
mobilinkd::M17Modulator::interleave_and_randomize(temp);
output_baseband(mobilinkd::M17Modulator::STREAM_SYNC_WORD, temp);
}
std::array<uint8_t, 16> M17ModProcessor::encodeAudio(std::array<int16_t, 320*6>& audioFrame)
{
std::array<int16_t, 320> audioFrame8k;
m_decimator.decimate(audioFrame.data(), audioFrame8k.data(), 320);
std::array<uint8_t, 16> result;
// result.fill(0); // test
codec2_encode(m_codec2, &result[0], const_cast<int16_t*>(&audioFrame8k[0]));
codec2_encode(m_codec2, &result[8], const_cast<int16_t*>(&audioFrame8k[160]));
return result;
}
void M17ModProcessor::send_eot()
{
std::array<uint8_t, 2> EOT_SYNC = { 0x55, 0x5D };

View File

@ -22,9 +22,11 @@
#include <QByteArray>
#include "m17/M17Modulator.h"
#include "dsp/dsptypes.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "m17modfifo.h"
#include "m17moddecimator.h"
class M17ModProcessor : public QObject
{
@ -55,6 +57,91 @@ public:
{ }
};
class MsgSendAudio : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getSourceCall() const { return m_sourceCall; }
const QString& getDestCall() const { return m_destCall; }
std::vector<int16_t>& getAudioBuffer() { return m_audioBuffer; }
static MsgSendAudio* create(const QString& sourceCall, const QString& destCall) {
return new MsgSendAudio(sourceCall, destCall);
}
private:
QString m_sourceCall;
QString m_destCall;
std::vector<int16_t> m_audioBuffer;
MsgSendAudio(const QString& sourceCall, const QString& destCall) :
Message(),
m_sourceCall(sourceCall),
m_destCall(destCall)
{ }
};
class MsgSendAudioFrame : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getSourceCall() const { return m_sourceCall; }
const QString& getDestCall() const { return m_destCall; }
std::array<int16_t, 1920>& getAudioFrame() { return m_audioFrame; }
static MsgSendAudioFrame* create(const QString& sourceCall, const QString& destCall) {
return new MsgSendAudioFrame(sourceCall, destCall);
}
private:
QString m_sourceCall;
QString m_destCall;
std::array<int16_t, 1920> m_audioFrame;
MsgSendAudioFrame(const QString& sourceCall, const QString& destCall) :
Message(),
m_sourceCall(sourceCall),
m_destCall(destCall)
{ }
};
class MsgStartAudio : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getSourceCall() const { return m_sourceCall; }
const QString& getDestCall() const { return m_destCall; }
static MsgStartAudio* create(const QString& sourceCall, const QString& destCall) {
return new MsgStartAudio(sourceCall, destCall);
}
private:
QString m_sourceCall;
QString m_destCall;
MsgStartAudio(const QString& sourceCall, const QString& destCall) :
Message(),
m_sourceCall(sourceCall),
m_destCall(destCall)
{ }
};
class MsgStopAudio : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStopAudio* create() {
return new MsgStopAudio();
}
private:
MsgStopAudio() :
Message()
{ }
};
M17ModProcessor();
~M17ModProcessor();
@ -64,10 +151,24 @@ public:
private:
MessageQueue m_inputMessageQueue;
M17ModFIFO m_basebandFifo; //!< Samples are 16 bit integer baseband 48 kS/s samples
int m_basebandFifoHigh;
int m_basebandFifoLow;
M17ModDecimator m_decimator; //!< 48k -> 8k decimator
mobilinkd::M17Modulator m_m17Modulator;
std::array<mobilinkd::M17Modulator::lich_segment_t, 6> m_lich; //!< LICH bits
int m_lichSegmentIndex;
std::array<int16_t, 320*6> m_audioFrame;
int m_audioFrameIndex;
uint16_t m_audioFrameNumber;
struct CODEC2 *m_codec2;
bool handleMessage(const Message& cmd);
void processPacket(const QString& sourceCall, const QString& destCall, const QByteArray& packetBytes);
void audioStart(const QString& sourceCall, const QString& destCall);
void audioStop();
void processAudio(const std::vector<int16_t>& audioBuffer);
void processAudioFrame();
std::array<uint8_t, 16> encodeAudio(std::array<int16_t, 320*6>& audioFrame);
void test(const QString& sourceCall, const QString& destCall);
void send_preamble();
void send_eot();

View File

@ -47,6 +47,8 @@ M17ModSource::M17ModSource() :
m_audioBufferFill = 0;
m_audioReadBuffer.resize(24000);
m_audioReadBufferFill = 0;
m_m17PullAudio = false;
m_m17PullCount = 0;
m_feedbackAudioBuffer.resize(1<<14);
m_feedbackAudioBufferFill = 0;
@ -55,10 +57,10 @@ M17ModSource::M17ModSource() :
m_demodBufferFill = 0;
m_magsq = 0.0;
m_basebandMax = 0;
m_basebandMin = 0;
m_processor = new M17ModProcessor();
m_processor->moveToThread(&m_processorThread);
m_processorThread.start();
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
@ -66,6 +68,8 @@ M17ModSource::M17ModSource() :
M17ModSource::~M17ModSource()
{
m_processorThread.exit();
m_processorThread.wait();
delete m_processor;
}
@ -123,8 +127,9 @@ void M17ModSource::pullOne(Sample& sample)
void M17ModSource::prefetch(unsigned int nbSamples)
{
unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
pullAudio(nbSamplesAudio);
(void) nbSamples;
// unsigned int nbSamplesAudio = nbSamples * ((Real) m_audioSampleRate / (Real) m_channelSampleRate);
// pullAudio(nbSamplesAudio);
}
void M17ModSource::pullAudio(unsigned int nbSamplesAudio)
@ -136,6 +141,41 @@ void M17ModSource::pullAudio(unsigned int nbSamplesAudio)
}
std::copy(&m_audioReadBuffer[0], &m_audioReadBuffer[nbSamplesAudio], &m_audioBuffer[0]);
if (m_settings.m_m17Mode == M17ModSettings::M17ModeM17Audio)
{
if (!m_m17PullAudio)
{
M17ModProcessor::MsgStartAudio *msg = M17ModProcessor::MsgStartAudio::create(m_settings.m_sourceCall, m_settings.m_destCall);
m_processor->getInputMessageQueue()->push(msg);
m_m17PullAudio = true;
}
M17ModProcessor::MsgSendAudio *msg = M17ModProcessor::MsgSendAudio::create(m_settings.m_sourceCall, m_settings.m_destCall);
std::vector<int16_t>& audioBuffer = msg->getAudioBuffer();
audioBuffer.resize(nbSamplesAudio);
std::transform(
&m_audioReadBuffer[0],
&m_audioReadBuffer[nbSamplesAudio],
audioBuffer.begin(),
[](const AudioSample& s) -> int16_t {
return (s.l + s.r) / 2;
}
);
m_processor->getInputMessageQueue()->push(msg);
}
else
{
if (m_m17PullAudio)
{
M17ModProcessor::MsgStopAudio *msg = M17ModProcessor::MsgStopAudio::create();
m_processor->getInputMessageQueue()->push(msg);
m_m17PullAudio = false;
}
}
m_audioBufferFill = 0;
if (m_audioReadBufferFill > nbSamplesAudio) // copy back remaining samples at the start of the read buffer
@ -152,10 +192,8 @@ void M17ModSource::modulateSample()
if ((m_settings.m_m17Mode == M17ModSettings::M17ModeFMTone) || (m_settings.m_m17Mode == M17ModSettings::M17ModeFMAudio)) {
pullAF(t, carrier);
} else if (m_settings.m_m17Mode != M17ModSettings::M17ModeNone) {
pullM17(t, carrier);
} else {
t = 0;
pullM17(t, carrier);
}
if (m_settings.m_feedbackAudioEnable) {
@ -272,29 +310,70 @@ void M17ModSource::pullAF(Real& sample, bool& carrier)
void M17ModSource::pullM17(Real& sample, bool& carrier)
{
int16_t basbandSample;
carrier = m_processor->getBasebandFifo()->readOne(&basbandSample) != 0;
if (carrier)
// Prefetch file data if buffer is low
if (m_settings.m_m17Mode == M17ModSettings::M17ModeM17Audio)
{
if (basbandSample > m_basebandMax)
if (!m_m17PullAudio)
{
qDebug("M17ModSource::pullM17: max: %d", basbandSample);
m_basebandMax = basbandSample;
M17ModProcessor::MsgStartAudio *msg = M17ModProcessor::MsgStartAudio::create(m_settings.m_sourceCall, m_settings.m_destCall);
m_processor->getInputMessageQueue()->push(msg);
m_m17PullAudio = true;
}
if (basbandSample < m_basebandMin)
if ((m_settings.m_audioType == M17ModSettings::AudioFile)
&& m_ifstream
&& (m_ifstream->is_open())
&& (m_processor->getBasebandFifo()->getFill() < 1920) && (m_m17PullCount > 192))
{
qDebug("M17ModSource::pullM17: min: %d", basbandSample);
m_basebandMin = basbandSample;
}
if (m_ifstream->eof() && m_settings.m_playLoop)
{
m_ifstream->clear();
m_ifstream->seekg(0, std::ios::beg);
}
sample = basbandSample / 32768.0f;
M17ModProcessor::MsgSendAudioFrame *msg = M17ModProcessor::MsgSendAudioFrame::create(m_settings.m_sourceCall, m_settings.m_destCall);
std::array<int16_t, 1920>& audioFrame = msg->getAudioFrame();
std::vector<float> fileBuffer;
fileBuffer.resize(1920);
std::fill(fileBuffer.begin(), fileBuffer.end(), 0.0f);
if (!m_ifstream->eof()) {
m_ifstream->read(reinterpret_cast<char*>(fileBuffer.data()), sizeof(float)*1920);
}
std::transform(
fileBuffer.begin(),
fileBuffer.end(),
audioFrame.begin(),
[this](const float& fs) -> int16_t {
return fs * 32768.0f * m_settings.m_volumeFactor;
}
);
m_processor->getInputMessageQueue()->push(msg);
m_m17PullCount = 0;
}
}
else
{
if (m_m17PullAudio)
{
M17ModProcessor::MsgStopAudio *msg = M17ModProcessor::MsgStopAudio::create();
m_processor->getInputMessageQueue()->push(msg);
m_m17PullAudio = false;
}
}
int16_t basbandSample;
carrier = m_processor->getBasebandFifo()->readOne(&basbandSample) != 0;
if (carrier) {
sample = basbandSample / 32768.0f;
} else {
sample = 0.0f;
}
m_m17PullCount++;
}
void M17ModSource::pushFeedback(Real sample)

View File

@ -21,6 +21,7 @@
#include <QObject>
#include <QMutex>
#include <QVector>
#include <QThread>
#include <iostream>
#include <fstream>
@ -105,6 +106,8 @@ private:
AudioVector m_audioReadBuffer;
unsigned int m_audioReadBufferFill;
AudioFifo m_audioFifo;
bool m_m17PullAudio;
int m_m17PullCount;
int m_feedbackAudioSampleRate;
AudioVector m_feedbackAudioBuffer;
@ -119,11 +122,10 @@ private:
std::ifstream *m_ifstream;
M17ModProcessor *m_processor;
QThread m_processorThread;
HighPassFilterRC m_preemphasisFilter;
QMutex m_mutex;
int16_t m_basebandMin;
int16_t m_basebandMax;
static const int m_levelNbSamples;
static const float m_preemphasis;