diff --git a/modems/m17/M17Demodulator.cpp b/modems/m17/M17Demodulator.cpp index 7a4ce22e4..fce8046ec 100644 --- a/modems/m17/M17Demodulator.cpp +++ b/modems/m17/M17Demodulator.cpp @@ -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()); diff --git a/modems/m17/M17Modulator.h b/modems/m17/M17Modulator.h index 6325b83fa..2947d0717 100644 --- a/modems/m17/M17Modulator.h +++ b/modems/m17/M17Modulator.h @@ -33,7 +33,7 @@ public: using baseband_t = std::array; // One frame of baseband data @ 48ksps using bitstream_t = std::array; // M17 frame of bits (in bytes). using lsf_t = std::array; // Link setup frame bytes. - using lich_segment_t = std::array; // Golay-encoded LICH. + using lich_segment_t = std::array; // Golay-encoded LICH bits. using lich_t = std::array; // All LICH segments. using audio_frame_t = std::array; using codec_frame_t = std::array; @@ -186,6 +186,103 @@ public: return punctured; } + static lich_segment_t make_lich_segment(std::array 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 make_stream_data_frame(uint16_t frame_number, const codec_frame_t& payload) + { + std::array 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 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 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 make_packet_frame( uint8_t packet_number, int packet_size, @@ -271,6 +368,15 @@ public: return punctured; } + static void interleave_and_randomize(std::array& 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)), diff --git a/plugins/channeltx/modm17/CMakeLists.txt b/plugins/channeltx/modm17/CMakeLists.txt index 97e2ad3ff..932ed8359 100644 --- a/plugins/channeltx/modm17/CMakeLists.txt +++ b/plugins/channeltx/modm17/CMakeLists.txt @@ -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 diff --git a/plugins/channeltx/modm17/m17moddecimator.cpp b/plugins/channeltx/modm17/m17moddecimator.cpp new file mode 100644 index 000000000..5aa590871 --- /dev/null +++ b/plugins/channeltx/modm17/m17moddecimator.cpp @@ -0,0 +1,145 @@ +#include +#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; +} diff --git a/plugins/channeltx/modm17/m17moddecimator.h b/plugins/channeltx/modm17/m17moddecimator.h new file mode 100644 index 000000000..e29ea7852 --- /dev/null +++ b/plugins/channeltx/modm17/m17moddecimator.h @@ -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 + +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 diff --git a/plugins/channeltx/modm17/m17modfifo.cpp b/plugins/channeltx/modm17/m17modfifo.cpp index 128baadbe..1df4abb11 100644 --- a/plugins/channeltx/modm17/m17modfifo.cpp +++ b/plugins/channeltx/modm17/m17modfifo.cpp @@ -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; + } +} diff --git a/plugins/channeltx/modm17/m17modfifo.h b/plugins/channeltx/modm17/m17modfifo.h index 84e95fe21..9b59c5d4a 100644 --- a/plugins/channeltx/modm17/m17modfifo.h +++ b/plugins/channeltx/modm17/m17modfifo.h @@ -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); }; diff --git a/plugins/channeltx/modm17/m17modgui.cpp b/plugins/channeltx/modm17/m17modgui.cpp index 556e15c45..bfc2c7662 100644 --- a/plugins/channeltx/modm17/m17modgui.cpp +++ b/plugins/channeltx/modm17/m17modgui.cpp @@ -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(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); diff --git a/plugins/channeltx/modm17/m17modgui.h b/plugins/channeltx/modm17/m17modgui.h index 04b0e1cff..ab47c225c 100644 --- a/plugins/channeltx/modm17/m17modgui.h +++ b/plugins/channeltx/modm17/m17modgui.h @@ -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 m_channelPowerDbAvg; diff --git a/plugins/channeltx/modm17/m17modprocessor.cpp b/plugins/channeltx/modm17/m17modprocessor.cpp index 070ce03e7..8820a23cb 100644 --- a/plugins/channeltx/modm17/m17modprocessor.cpp +++ b/plugins/channeltx/modm17/m17modprocessor.cpp @@ -15,21 +15,36 @@ // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// +#include + #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 lsf; + std::array 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 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& audioBuffer) +{ + int audioBufferRemainder = audioBuffer.size(); + std::vector::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 audioPayload = encodeAudio(m_audioFrame); + std::array audioDataBits = mobilinkd::M17Modulator::make_stream_data_frame(m_audioFrameNumber++, audioPayload); + + if (m_audioFrameNumber == 0x8000) { + m_audioFrameNumber = 0; + } + + std::array& lich = m_lich[m_lichSegmentIndex++]; + + if (m_lichSegmentIndex == 6) { + m_lichSegmentIndex = 0; + } + + std::array 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 M17ModProcessor::encodeAudio(std::array& audioFrame) +{ + std::array audioFrame8k; + m_decimator.decimate(audioFrame.data(), audioFrame8k.data(), 320); + std::array result; + // result.fill(0); // test + codec2_encode(m_codec2, &result[0], const_cast(&audioFrame8k[0])); + codec2_encode(m_codec2, &result[8], const_cast(&audioFrame8k[160])); + return result; +} + void M17ModProcessor::send_eot() { std::array EOT_SYNC = { 0x55, 0x5D }; diff --git a/plugins/channeltx/modm17/m17modprocessor.h b/plugins/channeltx/modm17/m17modprocessor.h index 72331a13f..3c2e23cc3 100644 --- a/plugins/channeltx/modm17/m17modprocessor.h +++ b/plugins/channeltx/modm17/m17modprocessor.h @@ -22,9 +22,11 @@ #include #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& 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 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& 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 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 m_lich; //!< LICH bits + int m_lichSegmentIndex; + std::array 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& audioBuffer); + void processAudioFrame(); + std::array encodeAudio(std::array& audioFrame); void test(const QString& sourceCall, const QString& destCall); void send_preamble(); void send_eot(); diff --git a/plugins/channeltx/modm17/m17modsource.cpp b/plugins/channeltx/modm17/m17modsource.cpp index d87e6ed5a..418c955ed 100644 --- a/plugins/channeltx/modm17/m17modsource.cpp +++ b/plugins/channeltx/modm17/m17modsource.cpp @@ -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& 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& audioFrame = msg->getAudioFrame(); + std::vector fileBuffer; + fileBuffer.resize(1920); + std::fill(fileBuffer.begin(), fileBuffer.end(), 0.0f); + + if (!m_ifstream->eof()) { + m_ifstream->read(reinterpret_cast(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) diff --git a/plugins/channeltx/modm17/m17modsource.h b/plugins/channeltx/modm17/m17modsource.h index 954e5dd34..d753cc38a 100644 --- a/plugins/channeltx/modm17/m17modsource.h +++ b/plugins/channeltx/modm17/m17modsource.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -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;