/** * modem.cpp * This file is part of the YATE Project http://YATE.null.ro * * Yet Another Modem * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2013 Null Team * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include "yatemodem.h" #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif using namespace TelEngine; // Amplitudes for the sine generator (mark and space) used to modulate data #define MARK_AMPLITUDE 6300 #define SPACE_AMPLITUDE 6300 // Pattern length in ms to add after a modulated message #define PATTERN_AFTER 2 // Uncomment this to view the bits decoded by the modem #define YMODEM_BUFFER_BITS // Constant values used by the FSK filter to modulate/demodulate data class FilterConst { public: // Build constants used by this filter FilterConst(FSKModem::Type type); // Release memory ~FilterConst(); // Calculate how many samples do we need to modulate n bits unsigned int bufLen(unsigned int nbits); // Get timing samples and advance the index inline unsigned int timingSamples(unsigned int& index) { unsigned int tmp = bitSamples[index]; if (++index == bitSamplesLen) index = 0; return tmp; } // Signal properties float markFreq; // Mark frequency float spaceFreq; // Space frequency float sampleRate; // Sampling rate float baudRate; // Transmission baud rate (bps) // Modulation/demodulation data double markCoef; // Mark coefficient double spaceCoef; // Space coefficient // Data used to demodulate signals unsigned int spb; // The number of samples per bit (also the length of all buffers) unsigned int halfSpb; // Half of the spb value (used to filter data) float bitLen; // The exact value of bit length in samples float halfBitLen; // Half the bit length float markGain; float spaceGain; float lowbandGain; float* mark; float* space; float* lowband; // Data used to modulate signals double accSin; // Accumulate sine radians during modulation // This value is updated after the header is modulated // and used as start value for each message data unsigned int* bitSamples; // Array of bit samples nedded to maintain the modulation timing unsigned int bitSamplesLen; // The length of the bitSamples array DataBlock header; // Modulated message header // e.g. ETSI: channel seizure pattern + marks }; struct FilterData { ~FilterData() { if (xbuf) delete[] xbuf; if (ybuf) delete[] ybuf; } inline void init(unsigned int len) { xbuf = new float[len]; ybuf = new float[len]; ::memset(xbuf,0,len*sizeof(float)); ::memset(ybuf,0,len*sizeof(float)); } float* xbuf; float* ybuf; }; namespace TelEngine { // BitBuffer class BitBuffer { public: inline BitBuffer() : m_accumulator(8) {} inline const DataBlock& buffer() const { return m_buffer; } inline void reset() { m_buffer.clear(); m_accumulator.reset(); } // Accumulate a bit. Add data bytes to buffer once accumulated inline bool accumulate(bool bit) { #ifdef YMODEM_BUFFER_BITS unsigned int res = m_accumulator.accumulate(bit); if (res > 255) return false; unsigned char c = (unsigned char)res; DataBlock tmp(&c,1,false); m_buffer += tmp; tmp.clear(false); #endif return true; } // Operator used to accumulate a bit inline BitBuffer& operator+=(bool bit) { accumulate(bit); return *this; } // Print bits to output void printBits(DebugEnabler* dbg, unsigned int linelen = 80); private: DataBlock m_buffer; // The data byte buffer BitAccumulator m_accumulator; // The bit accumulator }; // The FSK sample filter class FSKFilter { public: FSKFilter(int type); // Get the constants used by this filter inline FilterConst* constants() { return m_const; } // Check if FSK modulation was already detected inline bool fskStarted() const { return m_fskStarted > 0; } // Process data to demodulate a bit // Return negative if buffer ended, 0/1 if found a bit int getBit(short*& samples, unsigned int& len); // Filter data until a start bit is found (used to wait for FSK modulation to start) // Return true if a start bit is found, false if all buffer was processed with no result bool waitFSK(short*& samples, unsigned int& len); // Add a modulated bit to a destination buffer. Advance the buffer's index void addBit(short* samples, unsigned int& index, bool bit); // Add a modulated data byte to a destination buffer // dataBits must not be 0 or greater the then 8 inline void addByte(short* samples, unsigned int& index, unsigned char value, unsigned char dataBits) { for (unsigned int i = 0; i < dataBits; i++, value >>= 1) addBit(samples,index,(bool)(value & 0x01)); } // Add a complete modulated byte to a destination buffer // The data is enclosed by start/stop bits inline void addByteFull(short* samples, unsigned int& index, unsigned char value, unsigned char dataBits) { addBit(samples,index,false); addByte(samples,index,value,dataBits); addBit(samples,index,true); } // Modulate data to a buffer. Reset the destination's length // dataBits must not be 0 or greater then 8 // Returns the current sine accumulator value double addBuffer(DataBlock& dest, const DataBlock& src, unsigned char dataBits, bool full); private: // Apply mark, space and low band filter float filter(short*& samples, unsigned int& len); int m_fskStarted; // Flag indicating the FSK modulation start float m_lastFiltered; // The last result of the filter float m_processed; // How much of a bit length was processed in (this is used for clock recovery) unsigned int m_index; // Current index in buffer FilterConst* m_const; // Constants used by this filter FilterData m_mark; FilterData m_space; FilterData m_lowband; // Data use to modulate signals double m_accSin; // Accumulate sine radians during modulation unsigned int m_bitSamples; // Current index in the filter constant's bitSamples array }; } /** * Static module data */ static const char* s_libName = "libyatemodem"; FilterConst s_filterConst[FSKModem::TypeCount] = { FilterConst(FSKModem::ETSI) }; /** * FilterConst */ // Build constants used by this filter FilterConst::FilterConst(FSKModem::Type type) { static float m[7] = {-5.6297236492e-02, 4.2915323820e-01, -1.2609358633e+00, 2.2399213250e+00, -2.9928879142e+00, 2.5990173742e+00, 0.0000000000e+00}; static float s[7] = {-5.6297236492e-02, -1.1421579050e-01, -4.8122536483e-01, -4.0121072432e-01, -7.4834487567e-01, -6.9170822332e-01, 0.0000000000e+00}; static float l[7] = {-7.8390522307e-03, 8.5209627801e-02, -4.0804129163e-01, 1.1157139955e+00, -1.8767603680e+00, 1.8916395224e+00, 0.0000000000e+00}; switch (type) { case FSKModem::ETSI: break; default: ::memset(this,0,sizeof(*this)); return; } // ETSI // Signal properties markFreq = 1200.0; spaceFreq = 2200.0; sampleRate = 8000.0; baudRate = 1200.0; // Mark/space coefficients for modulation/demodulation markCoef = 2 * M_PI * markFreq / sampleRate; spaceCoef = 2 * M_PI * spaceFreq / sampleRate; spb = 7; halfSpb = spb / 2; bitLen = sampleRate / baudRate; halfBitLen = bitLen / 2; markGain = 9.8539686961e-02; spaceGain = 9.8531161839e-02; lowbandGain = 3.1262119724e-03; mark = new float[spb+1]; space = new float[spb+1]; lowband = new float[spb+1]; for (unsigned int i = 0; i < spb; i++) { mark[i] = m[i]; space[i] = s[i]; lowband[i] = l[i]; } // Build the array of bit samples nedded to maintain the modulation timing bitSamplesLen = 3; bitSamples = new unsigned int[bitSamplesLen]; bitSamples[0] = bitSamples[2] = 7; bitSamples[1] = 6; accSin = 0; // Build message header // ETSI channel seizure signal + Mark (stop bits) signal // 300 continuous bits of alternating 0 and 1 + 180 of 1 (mark) bits // 480 bits: 60 bytes. Byte 38: 01011111 // This is the data header to be sent with ETSI messages unsigned char* hdr = new unsigned char[60]; ::memset(hdr,0x55,37); hdr[37] = 0xf5; ::memset(&hdr[38],0xff,22); DataBlock src; FSKModem::addRaw(src,hdr,60); FSKFilter filter(type); // Keep the sine accumulator to be used when modulating data accSin = filter.addBuffer(header,src,8,false); Debug(s_libName,DebugInfo,"Initialized filter tables for type '%s' headerlen=%u", lookup(FSKModem::ETSI,FSKModem::s_typeName),header.length()); } // Release memory FilterConst::~FilterConst() { if (!spb) return; delete[] mark; delete[] space; delete[] lowband; delete[] bitSamples; } // Calculate how many samples do we need to modulate n bits unsigned int FilterConst::bufLen(unsigned int n) { if (!bitSamples) return 0; unsigned int count = 0; // Each entry in bitSamples contain the number of samples nedded for current bit for (unsigned int idx = 0; n; n--) count += timingSamples(idx); return count; } /** * BitBuffer */ void BitBuffer::printBits(DebugEnabler* dbg, unsigned int linelen) { #ifdef YMODEM_BUFFER_BITS if ((dbg && !dbg->debugAt(DebugAll)) || (!dbg && !TelEngine::debugAt(DebugAll))) return; ObjList lines; String* s = new String; unsigned char* p = (unsigned char*)m_buffer.data(); for (unsigned int i = 0; i < m_buffer.length(); i++, p++) { for (unsigned char pos = 0; pos < 8; pos++) { char c = (*p & (1 << pos)) ? '1' : '0'; *s += c; } if (s->length() == linelen) { lines.append(s); s = new String; } } if (s->length()) lines.append(s); else TelEngine::destruct(s); String tmp; for (ObjList* o = lines.skipNull(); o; o = o->skipNext()) tmp << "\r\n" << *(static_cast(o->get())); Debug(dbg,DebugAll,"Decoded %u bits:%s",m_buffer.length()*8,tmp.c_str()); #endif } /** * FSKFilter */ FSKFilter::FSKFilter(int type) : m_fskStarted(-1), m_lastFiltered(0), m_processed(0), m_accSin(0), m_bitSamples(0) { switch (type) { case FSKModem::ETSI: break; default: return; } m_index = 0; m_const = s_filterConst + type; // Update the sine accumulator from constants (current value after created the header) m_accSin = m_const->accSin; m_mark.init(1+m_const->spb); m_space.init(1+m_const->spb); m_lowband.init(1+m_const->spb); } // Process data to demodulate a bit // Return negative if buffer ended, 0/1 if found a bit inline int FSKFilter::getBit(short*& samples, unsigned int& len) { float ds = m_const->bitLen / 32.; bool transition = false; while (len) { float filtered = filter(samples,len); // Check if this a bit transition if (filtered * m_lastFiltered < 0 && !transition) { if (m_processed < m_const->halfBitLen) m_processed += ds; else m_processed -= ds; transition = true; } m_lastFiltered = filtered; m_processed += 1.; // Processed a bit: adjust clock (bit length) and return the result if (m_processed > m_const->bitLen) { m_processed -= m_const->bitLen; return filtered > 0 ? 1 : 0;; } } return -1; } // Filter data until a start bit is found (used to wait for FSK modulation to start) // Return true if a start bit is found, false if all buffer was processed with no result inline bool FSKFilter::waitFSK(short*& samples, unsigned int& len) { if (fskStarted()) return true; if (!len) return false; float tmp = 0; if (m_fskStarted == -1) { while (tmp >= -0.5) { if (!len) return false; tmp = filter(samples,len); } m_fskStarted = 0; } // Wait for 0.5 bits before starting the demodulation tmp = 1; while (tmp > 0) { if (len < m_const->halfSpb) return false; for(unsigned int i = m_const->halfSpb; i; i--) tmp = filter(samples,len); } m_fskStarted = 1; return true; } // Add a modulated bit to a destination buffer inline void FSKFilter::addBit(short* samples, unsigned int& index, bool bit) { // Get the number of samples nedded for this bit and advance the index unsigned int n = m_const->timingSamples(m_bitSamples); // Build and store the modulated samples if (bit) for(; n; n--) { m_accSin += m_const->markCoef; samples[index++] = (short)(MARK_AMPLITUDE * sin(m_accSin)); } else for(; n; n--) { m_accSin += m_const->spaceCoef; samples[index++] = (short)(SPACE_AMPLITUDE * sin(m_accSin)); } } // Modulate data to a buffer // dataBits must not be 0 or greater then 8 // full=true: add data bytes (start bit + data + stop bit) double FSKFilter::addBuffer(DataBlock& dest, const DataBlock& src, unsigned char dataBits, bool full) { // Calculate the destination length. Add 2 more bits if full if (m_const) if (full) dest.assign(0,m_const->bufLen(src.length() * (dataBits + 2)) * sizeof(short)); else dest.assign(0,m_const->bufLen(src.length() * dataBits) * sizeof(short)); else dest.clear(); if (!dest.length()) return m_accSin; // Build modulated buffer unsigned char* srcData = (unsigned char*)(src.data()); short* destData = (short*)(dest.data()); unsigned int index = 0; if (full) for (unsigned int i = 0; i < src.length(); i++) addByteFull(destData,index,srcData[i],dataBits); else for (unsigned int i = 0; i < src.length(); i++) addByte(destData,index,srcData[i],dataBits); return m_accSin; } // Apply mark/space and low band filter inline float FSKFilter::filter(short*& samples, unsigned int& len) { #define SPB m_const->spb #define MOD(val) ((val) & SPB) short sample = *samples++; len--; // Mark filter m_mark.xbuf[MOD(m_index+6)] = sample * m_const->markGain; float mark = m_mark.xbuf[MOD(m_index+6)] - m_mark.xbuf[m_index] + 3 * (m_mark.xbuf[MOD(m_index+2)] - m_mark.xbuf[MOD(m_index+4)]); for (unsigned int i = 0; i < 6; i++) mark += m_mark.ybuf[MOD(m_index+i)] * m_const->mark[i]; m_mark.ybuf[MOD(m_index+6)] = mark; // Space filter m_space.xbuf[MOD(m_index+6)] = sample * m_const->spaceGain; float space = m_space.xbuf[MOD(m_index+6)] - m_space.xbuf[m_index] + 3 * (m_space.xbuf[MOD(m_index+2)] - m_space.xbuf[MOD(m_index+4)]); for (unsigned int i = 0; i < 6; i++) space += m_space.ybuf[MOD(m_index+i)] * m_const->space[i]; m_space.ybuf[MOD(m_index+6)] = space; // Low band filter float result = mark * mark - space * space; m_lowband.xbuf[MOD(m_index+6)] = result * m_const->lowbandGain; result = (m_lowband.xbuf[m_index] + m_lowband.xbuf[MOD(m_index+6)]) + 6 * (m_lowband.xbuf[MOD(m_index+1)] + m_lowband.xbuf[MOD(m_index+5)]) + 15 * (m_lowband.xbuf[MOD(m_index+2)] + m_lowband.xbuf[MOD(m_index+4)]) + 20 * m_lowband.xbuf[MOD(m_index+3)]; for (unsigned int i = 0; i < 6; i++) result += m_lowband.ybuf[MOD(m_index+i)] * m_const->lowband[i]; m_lowband.ybuf[MOD(m_index+6)] = result; // Increase index m_index = MOD(m_index+1); #undef SPB #undef MOD return result; } /** * FSKModem */ TokenDict FSKModem::s_typeName[] = { {"etsi", FSKModem::ETSI}, {0,0} }; FSKModem::FSKModem(const NamedList& params, UART* uart) : m_type(ETSI), m_terminated(false), m_filter(0), m_uart(uart), m_bits(0) { if (!m_uart) { Debug(DebugWarn,"Request to create FSK modem without UART"); m_terminated = true; return; } const char* typeName = params.getValue("modemtype"); if (typeName && *typeName) m_type = lookup(typeName,s_typeName); switch (m_type) { case ETSI: break; default: Debug(m_uart,DebugWarn,"Unknown modem type='%s' [%p]",typeName,m_uart); m_terminated = true; return; } #ifdef YMODEM_BUFFER_BITS if (params.getBoolValue("bufferbits",false)) m_bits = new BitBuffer; #endif reset(); XDebug(m_uart,DebugAll,"Modem created type='%s' [%p]",lookup(m_type,s_typeName),this); } FSKModem::~FSKModem() { if (m_filter) delete m_filter; if (m_bits) { m_bits->printBits(m_uart); delete m_bits; } XDebug(m_uart,DebugAll,"Modem destroyed [%p]",this); } // Reset state. Clear buffer void FSKModem::reset() { m_terminated = false; m_buffer.clear(); if (m_filter) delete m_filter; m_filter = 0; if (m_type < TypeCount) m_filter = new FSKFilter(m_type); if (m_bits) m_bits->reset(); } // Data processor. Feed the collector // Return false to stop processing bool FSKModem::demodulate(const DataBlock& data) { if (m_terminated) return false; if (!data.length()) return true; // Prepare buffer to process void* buffer = 0; // Original buffer unsigned int len = 0; // Data length in bytes if (m_buffer.length()) { m_buffer += data; buffer = m_buffer.data(); len = m_buffer.length(); } else { buffer = data.data(); len = data.length(); } short* samples = (short*)buffer; // Data to process unsigned int count = len / sizeof(short); // The number of available samples XDebug(m_uart,DebugAll,"Demodulating %u bytes [%p]",len,m_uart); // Wait at least 6 samples to process while (count > 6) { // Check if FSK modulation was detected if (!m_filter->fskStarted()) { if (!m_filter->waitFSK(samples,count)) break; DDebug(m_uart,DebugInfo,"FSK modulation started [%p]",m_uart); m_terminated = !m_uart->fskStarted(); if (m_terminated) break; #ifdef YMODEM_BUFFER_BITS if (m_bits) m_bits->accumulate(false); #endif m_terminated = !m_uart->recvBit(false); } // FSK started: get bits and send them to the UART for (int bit = 1; bit >= 0 && !m_terminated; ) { bit = m_filter->getBit(samples,count); if (bit >= 0) { #ifdef YMODEM_BUFFER_BITS if (m_bits) m_bits->accumulate(bit != 0); #endif m_terminated = !m_uart->recvBit(bit != 0); } } break; } // Keep the unprocessed bytes unsigned int rest = count * sizeof(short) + len % sizeof(short); if (rest) { DataBlock tmp; if (m_buffer.data()) { tmp.assign(buffer,len,false); m_buffer.clear(false); } m_buffer.assign(samples,rest); } else m_buffer.clear(); return !m_terminated; } // Create a buffer containing the modulated representation of a message void FSKModem::modulate(DataBlock& dest, const DataBlock& data) { #ifdef DEBUG String tmp; tmp.hexify(data.data(),data.length(),' '); Debug(m_uart,DebugAll,"Modulating '%s' [%p]",tmp.safe(),m_uart); #endif if (!(data.length() && m_filter && m_filter->constants())) return; DataBlock tmpData; m_filter->addBuffer(tmpData,data,m_uart->accumulator().dataBits(),true); dest += m_filter->constants()->header; dest += tmpData; // Build pattern after unsigned int nbits = (unsigned int)(m_filter->constants()->baudRate / 1000 * PATTERN_AFTER); DataBlock p(0,(nbits+7)/8); DataBlock tmpAfter; m_filter->addBuffer(tmpAfter,p,m_uart->accumulator().dataBits(),false); dest += tmpAfter; DDebug(m_uart,DebugAll,"Modulated header=%u data=%u pattern=%u [%p]", m_filter->constants()->header.length(), tmpData.length(),tmpAfter.length(),m_uart); } /* vi: set ts=8 sw=4 sts=4 noet: */