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:
parent
1fc688c904
commit
8f48d7d135
@ -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());
|
||||
|
@ -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)),
|
||||
|
@ -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
|
||||
|
145
plugins/channeltx/modm17/m17moddecimator.cpp
Normal file
145
plugins/channeltx/modm17/m17moddecimator.cpp
Normal 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;
|
||||
}
|
49
plugins/channeltx/modm17/m17moddecimator.h
Normal file
49
plugins/channeltx/modm17/m17moddecimator.h
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user