mirror of
https://github.com/RangeNetworks/openbts.git
synced 2025-10-23 16:13:52 +00:00
2786 lines
84 KiB
C++
2786 lines
84 KiB
C++
/*
|
|
* Copyright 2008-2010 Free Software Foundation, Inc.
|
|
* Copyright 2010 Kestrel Signal Processing, Inc.
|
|
* Copyright 2012, 2014 Range Networks, Inc.
|
|
*
|
|
* 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.
|
|
|
|
*/
|
|
|
|
#define LOG_GROUP LogGroup::GSM // Can set Log.Level.GSM for debugging
|
|
|
|
#define TESTTCHL1FEC
|
|
|
|
|
|
#include "GSML1FEC.h"
|
|
#include "GSMCommon.h"
|
|
#include "GSMTransfer.h"
|
|
//#include "GSMSAPMux.h"
|
|
#include "GSMConfig.h"
|
|
#include "GSMTDMA.h"
|
|
#include "GSMTAPDump.h"
|
|
#include "GSMLogicalChannel.h"
|
|
#include <ControlCommon.h>
|
|
#include <OpenBTSConfig.h>
|
|
#include <TRXManager.h>
|
|
#include <Logger.h>
|
|
#include <TMSITable.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
|
|
#include "../GPRS/GPRSExport.h"
|
|
|
|
#undef WARNING
|
|
|
|
namespace GSM {
|
|
using namespace std;
|
|
using namespace SIP; // For AudioFrame
|
|
|
|
#undef OBJLOG
|
|
#define OBJLOG(level) LOG(level) <<descriptiveString()<<" "
|
|
#define BLATHER DEBUG // (pat 4-2014) These were formerly INFO but there is one message for each frame, which is too much.
|
|
|
|
|
|
|
|
// (pat) David says this is the initial power level we want to send to handsets.
|
|
// 33 translates to 2 watts, which is the max in the low band.
|
|
// The power control loop will rapidly turn down the power once SACCH is established.
|
|
// This value is in DB. It is converted to an ordered MS power based on tables in GSM 5.05 4.1.
|
|
const float cInitialPower = 33;
|
|
|
|
|
|
// The actual phone settings change every 4 bursts, so average over at least 4.
|
|
static const unsigned cAveragePeriodTiming = 8; // How many we measurement reports over which we average timing, minus 1.
|
|
//static const unsigned cAveragePeriodRSSI = 8; // How many measurement reports over which we average RSSI, minus 1.
|
|
//static const unsigned cAveragePeriodSNR = 8; // How many frames over which we average SNR, minus 1.
|
|
static const unsigned cFERMemory = 208; // How many we frames we average FER, minus 1. For reporting.
|
|
|
|
/*
|
|
|
|
Notes on reading the GSM specifications.
|
|
|
|
Every FEC section in GSM 05.03 uses standard names for the bits at
|
|
different stages of the encoding/decoding process.
|
|
|
|
This is all described formally in GSM 05.03 2.2.
|
|
|
|
"d" -- data bits. The actual payloads from L2 and the vocoders.
|
|
"p" -- parity bits. These are calculated from d.
|
|
"u" -- uncoded bits. A concatenation of d, p and inner tail bits.
|
|
"c" -- coded bits. These are the convolutionally encoded from u.
|
|
"i" -- interleaved bits. These are the output of the interleaver.
|
|
"e" -- "encrypted" bits. These are the channel bits in the radio bursts.
|
|
|
|
The "e" bits are call "encrypted" even when encryption is not used.
|
|
|
|
The encoding process is:
|
|
|
|
L2 -> d -> -> calc p -> u -> c -> i -> e -> radio bursts
|
|
|
|
The decoding process is:
|
|
|
|
radio bursts -> e -> i -> c -> u -> check p -> d -> L2
|
|
|
|
Bit ordering in d is LSB-first in each octet.
|
|
Bit ordering everywhere else in the OpenBTS code is MSB-first
|
|
in every field to give contiguous fields across byte boundaries.
|
|
We use the BitVector2::LSB8MSB() method to translate.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**@name Power control utility functions based on GSM 05.05 4.1.1 */
|
|
//@{
|
|
|
|
/** Power control codes for GSM400, GSM850, EGSM900 from GSM 05.05 4.1.1. */
|
|
static const int powerCommandLowBand[32] =
|
|
{
|
|
39, 39, 39, 37, // 0-3
|
|
35, 33, 31, 29, // 4-7
|
|
27, 25, 23, 21, // 8-11
|
|
19, 17, 15, 13, // 12-15
|
|
11, 9, 7, 5, // 16-19
|
|
5, 5, 5, 5, // 20-23
|
|
5, 5, 5, 5, // 24-27
|
|
5, 5, 5, 5 // 28-31
|
|
};
|
|
|
|
/** Power control codes for DCS1800 from GSM 05.05 4.1.1. */
|
|
static const int powerCommand1800[32] =
|
|
{
|
|
30, 28, 26, 24, // 0-3
|
|
22, 20, 18, 16, // 4-7
|
|
14, 12, 10, 8, // 8-11
|
|
6, 4, 2, 0, // 12-15
|
|
0, 0, 0, 0, // 16-19
|
|
0, 0, 0, 0, // 20-23
|
|
0, 0, 0, 0, // 24-27
|
|
0, 36, 24, 23 // 28-31
|
|
};
|
|
|
|
/** Power control codes for PCS1900 from GSM 05.05 4.1.1. */
|
|
static const int powerCommand1900[32] =
|
|
{
|
|
30, 28, 26, 24, // 0-3
|
|
22, 20, 18, 16, // 4-7
|
|
14, 12, 10, 8, // 8-11
|
|
6, 4, 2, 0, // 12-15
|
|
0, 0, 0, 0, // 16-19
|
|
0, 0, 0, 0, // 20-23
|
|
0, 0, 0, 0, // 24-27
|
|
0, 0, 0, 0, // 28-31
|
|
};
|
|
|
|
|
|
const int* pickTable()
|
|
{
|
|
unsigned band = gBTS.band();
|
|
|
|
|
|
switch (band) {
|
|
case GSM850:
|
|
case EGSM900:
|
|
return powerCommandLowBand;
|
|
break;
|
|
case DCS1800:
|
|
return powerCommand1800;
|
|
break;
|
|
case PCS1900:
|
|
return powerCommand1900;
|
|
break;
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
int decodePower(unsigned code)
|
|
{
|
|
static const int *table = pickTable();
|
|
assert(table);
|
|
return table[code];
|
|
|
|
}
|
|
|
|
|
|
/** Given a power level in dBm, encode the control code. */
|
|
unsigned encodePower(int power)
|
|
{
|
|
static const int *table = pickTable();
|
|
assert(table);
|
|
unsigned minErr = abs(power - table[0]);
|
|
unsigned code = 0;
|
|
for (int i=1; i<32; i++) {
|
|
unsigned thisErr = abs(power - table[i]);
|
|
if (thisErr==0) return i;
|
|
if (thisErr<minErr) {
|
|
minErr = thisErr;
|
|
code = i;
|
|
}
|
|
}
|
|
return code;
|
|
}
|
|
|
|
|
|
//@}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
L1Encoder base class methods.
|
|
*/
|
|
|
|
|
|
L1Encoder::L1Encoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, L1FEC *wParent)
|
|
:mDownstream(NULL),
|
|
mMapping(wMapping),
|
|
mCN(wCN),mTN(wTN),
|
|
mTSC(gBTS.BCC()), // Note that TSC (Training Sequence Code) is hardcoded to the BCC.
|
|
mParent(wParent),
|
|
mTotalFrames(0),
|
|
mPrevWriteTime(gBTS.time().FN(),wTN),
|
|
mNextWriteTime(gBTS.time().FN(),wTN),
|
|
mRunning(false),
|
|
mEncrypted(ENCRYPT_NO),
|
|
mEncryptionAlgorithm(0)
|
|
{
|
|
assert((int)mCN<gConfig.getNum("GSM.Radio.ARFCNs"));
|
|
#ifndef TESTTCHL1FEC
|
|
assert(mMapping.allowedSlot(mTN));
|
|
assert(mMapping.downlink());
|
|
mNextWriteTime.rollForward(mMapping.frameMapping(0),mMapping.repeatLength());
|
|
mPrevWriteTime.rollForward(mMapping.frameMapping(0),mMapping.repeatLength());
|
|
#endif // TESTTCHL1FEC
|
|
// Compatibility with C0 will be checked in the ARFCNManager.
|
|
// Build the descriptive string.
|
|
ostringstream ss;
|
|
ss << wMapping.typeAndOffset();
|
|
mDescriptiveString = format("C%dT%d %s", wCN, wTN, ss.str().c_str());
|
|
}
|
|
|
|
ostream& operator<<(std::ostream& os, const L1Encoder *encp)
|
|
{
|
|
os <<"L1Encoder "<<(encp ? encp->descriptiveString() : "NULL");
|
|
return os;
|
|
}
|
|
|
|
|
|
void L1Encoder::rollForward()
|
|
{
|
|
// Calculate the TDMA parameters for the next transmission.
|
|
// This implements GSM 05.02 Clause 7 for the transmit side.
|
|
mPrevWriteTime = mNextWriteTime;
|
|
mTotalFrames++;
|
|
ScopedLock lock(mWriteTimeLock,__FILE__,__LINE__); // (pat) Protects getNextWriteTime.
|
|
mNextWriteTime.rollForward(mMapping.frameMapping(mTotalFrames),mMapping.repeatLength());
|
|
}
|
|
|
|
|
|
|
|
|
|
TypeAndOffset L1Encoder::typeAndOffset() const
|
|
{
|
|
return mMapping.typeAndOffset();
|
|
}
|
|
|
|
|
|
void L1Encoder::encInit()
|
|
{
|
|
OBJLOG(BLATHER) << "L1Encoder";
|
|
handoverPending(false);
|
|
ScopedLock lock(mEncLock,__FILE__,__LINE__);
|
|
mTotalFrames=0;
|
|
resync(true); // (pat 4-2014) Force mNextWriteTime to be recalculated at channel initiation.
|
|
mPrevWriteTime = gBTS.time(); // (pat) Prevents the first write after opening the channel from blocking in waitToSend called from transmit.
|
|
// (doug) Turning off encryption when the channel closes would be a nightmare
|
|
// (catching all the ways, and performing the handshake under less than
|
|
// ideal conditions), so we leave encryption on to the bitter end,
|
|
// then clear the encryption flag here, when the channel gets reused.
|
|
mEncrypted = ENCRYPT_NO;
|
|
mEncryptionAlgorithm = 0;
|
|
// (pat) On very first initialization, start sending the dummy bursts;
|
|
// this allows us to get rid of the dopey 'starting' of all the channels when the BTS is turned on.
|
|
if (mCN == 0 && !mEncEverActive) { sendDummyFill(); }
|
|
}
|
|
|
|
void L1Encoder::encStart()
|
|
{
|
|
if (!mRunning) { mRunning = true; serviceStart(); }
|
|
mEncActive = true;
|
|
mEncEverActive = true;
|
|
}
|
|
|
|
|
|
// (pat) sendDummyFill does not block, but it advances the mNextWriteTime.
|
|
void L1Encoder::close()
|
|
{
|
|
OBJLOG(BLATHER) << "L1Encoder";
|
|
ScopedLock lock(mEncLock,__FILE__,__LINE__);
|
|
if (mEncActive) { sendDummyFill(); }
|
|
mEncActive = false;
|
|
}
|
|
|
|
|
|
bool L1Encoder::encActive() const
|
|
{
|
|
const L1Decoder *sib = sibling();
|
|
if (sib) {
|
|
return mEncActive && (sib->decActive());
|
|
} else {
|
|
return mEncActive;
|
|
}
|
|
}
|
|
|
|
|
|
L1Decoder* L1Encoder::sibling()
|
|
{
|
|
if (!mParent) return NULL;
|
|
return mParent->decoder();
|
|
}
|
|
|
|
|
|
const L1Decoder* L1Encoder::sibling() const
|
|
{
|
|
if (!mParent) return NULL;
|
|
return mParent->decoder();
|
|
}
|
|
|
|
void L1Encoder::resync(bool force)
|
|
{
|
|
// If the encoder's clock is far from the current BTS clock,
|
|
// get it caught up to something reasonable.
|
|
Time now = gBTS.time();
|
|
int32_t delta = mNextWriteTime-now;
|
|
OBJLOG(DEBUG) << "L1Encoder next=" << mNextWriteTime << " now=" << now << " delta=" << delta;
|
|
if (force || (delta<0) || (delta>(51*26))) {
|
|
mNextWriteTime = now;
|
|
mNextWriteTime.TN(mTN);
|
|
mTotalFrames = 0; // (pat 4-2014) Make sure we start at beginning of mapping.
|
|
{ ScopedLock lock(mWriteTimeLock,__FILE__,__LINE__); // (pat) Protects getNextWriteTime.
|
|
mNextWriteTime.rollForward(mMapping.frameMapping(mTotalFrames),mMapping.repeatLength());
|
|
}
|
|
OBJLOG(DEBUG) <<"L1Encoder RESYNC "<< " next=" << mNextWriteTime << " now=" << now;
|
|
}
|
|
}
|
|
|
|
Time L1Encoder::getNextWriteTime()
|
|
{
|
|
resync();
|
|
ScopedLock lock(mWriteTimeLock,__FILE__,__LINE__);
|
|
return mNextWriteTime;
|
|
}
|
|
|
|
|
|
void L1Encoder::waitToSend() const
|
|
{
|
|
// Block until the BTS clock catches up to the
|
|
// most recently transmitted burst.
|
|
gBTS.clock().wait(mPrevWriteTime);
|
|
}
|
|
|
|
|
|
void L1Encoder::sendDummyFill()
|
|
{
|
|
// Send the L1 idle filling pattern, if any.
|
|
// For C0, that's the dummy burst.
|
|
// For Cn, don't do anything.
|
|
OBJLOG(DEBUG) <<"L1Encoder";
|
|
resync();
|
|
// (pat) FIXME: On other ARFCNs we need to disable the transceiver auto-filling. See wiki ticket 1141.
|
|
// (pat) In the meantime, we must send the dummy burst to inform the MS that this channel is disabled;
|
|
// this is required specifically for SACCH.
|
|
// To preserve the old behavior, we will leave other arfcns non-transmitting until the first time they are used,
|
|
// but ever after we have to send the filler pattern.
|
|
if (mCN==0 || mEncEverActive) {
|
|
for (unsigned i=0; i<mMapping.numFrames(); i++) {
|
|
mFillerBurst.time(mNextWriteTime);
|
|
mDownstream->writeHighSideTx(mFillerBurst,"dummy");
|
|
rollForward();
|
|
}
|
|
mFillerSendTime = gBTS.clock().systime2(mNextWriteTime); // (pat) The time when the last burst of the filler will be delivered.
|
|
}
|
|
}
|
|
|
|
bool L1Encoder::l1IsIdle() const
|
|
{
|
|
return ! mEncActive && mFillerSendTime.passed();
|
|
}
|
|
|
|
unsigned L1Encoder::ARFCN() const
|
|
{
|
|
assert(mDownstream);
|
|
return mDownstream->ARFCN();
|
|
}
|
|
|
|
int unhex(const char c)
|
|
{
|
|
if (c >= '0' && c <= '9') return c - '0';
|
|
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
assert(0);
|
|
}
|
|
|
|
|
|
// Given IMSI, copy Kc. Return true iff there *is* a Kc.
|
|
bool imsi2kc(string wIMSI, unsigned char *wKc)
|
|
{
|
|
string kc = gTMSITable.getKc(wIMSI.c_str());
|
|
if (kc.length() == 0) return false;
|
|
while (kc.length() < 16) {
|
|
kc = '0' + kc;
|
|
}
|
|
assert(kc.length() == 16);
|
|
unsigned char *dst = wKc;
|
|
for (size_t p = 0; p < kc.length(); p += 2) {
|
|
*dst++ = (unhex(kc[p]) << 4) | (unhex(kc[p+1]));
|
|
}
|
|
// Dont leave this message in production code:
|
|
LOG(DEBUG) << format("decrypt maybe imsi=%s Kc=%s",wIMSI.c_str(),kc.c_str());
|
|
return true;
|
|
}
|
|
|
|
|
|
// Turn on encryption phase-in, which is watching for bad frames and
|
|
// retrying them with encryption.
|
|
// Return false and leave encryption off if there's no Kc.
|
|
bool L1Decoder::decrypt_maybe(string wIMSI, int wA5Alg)
|
|
{
|
|
if (!imsi2kc(wIMSI, mKc)) return false;
|
|
mEncrypted = ENCRYPT_MAYBE;
|
|
mEncryptionAlgorithm = wA5Alg;
|
|
LOG(DEBUG) << format("decrypt maybe imsi=%s algorithm=%d",wIMSI.c_str(),mEncryptionAlgorithm);
|
|
return true;
|
|
}
|
|
|
|
|
|
unsigned L1Decoder::ARFCN() const
|
|
{
|
|
assert(mParent);
|
|
return mParent->ARFCN();
|
|
}
|
|
|
|
|
|
TypeAndOffset L1Decoder::typeAndOffset() const
|
|
{
|
|
return mMapping.typeAndOffset();
|
|
}
|
|
|
|
void DecoderStats::decoderStatsInit()
|
|
{
|
|
mAveFER = 0;
|
|
mAveSNR = 0;
|
|
mAveBER = 0;
|
|
mSNRCount = 0;
|
|
mStatTotalFrames = 0;
|
|
mStatStolenFrames = 0;
|
|
mStatBadFrames = 0;
|
|
}
|
|
|
|
ostream& operator<<(std::ostream& os, const L1Decoder *decp)
|
|
{
|
|
os <<"L1Decoder "<<(decp ? decp->descriptiveString() : "NULL");
|
|
return os;
|
|
}
|
|
|
|
string L1Decoder::displayTimers() const
|
|
{
|
|
ostringstream ss;
|
|
// No point in showing T3103 for handover - its too fast.
|
|
//ss <<LOGVARM(mT3101) <<LOGVARM(mT3109) <<LOGVARM(mT3111);
|
|
ss <<LOGVARM(mBadFrameTracker);
|
|
return ss.str();
|
|
}
|
|
|
|
void L1Decoder::decInit()
|
|
{
|
|
handoverPending(false,0);
|
|
ScopedLock lock(mDecLock,__FILE__,__LINE__);
|
|
//if (!mRunning) decStart();
|
|
//mRunning = true;
|
|
mDecoderStats.decoderStatsInit();
|
|
//mFER=0.0F;
|
|
mBadFrameTracker = 0;
|
|
//mT3111.reset();
|
|
//mT3109.reset(gConfig.GSM.Timer.T3109);
|
|
//mT3101.reset();
|
|
// Turning off encryption when the channel closes would be a nightmare
|
|
// (catching all the ways, and performing the handshake under less than
|
|
// ideal conditions), so we leave encryption on to the bitter end,
|
|
// then clear the encryption flag here, when the channel gets reused.
|
|
mEncrypted = ENCRYPT_NO;
|
|
mEncryptionAlgorithm = 0;
|
|
//mActive = true;
|
|
}
|
|
|
|
void L1Decoder::decStart()
|
|
{
|
|
//mT3111.reset();
|
|
// Pat changed initial open state on T3109 from inactive via reset to active via set,
|
|
// so that it is easier to test in GSMLogicalChannel.
|
|
//mT3109.set(); //old: mT3109.reset();
|
|
//mT3101.set();
|
|
mDecActive = true;
|
|
}
|
|
|
|
|
|
void L1Decoder::close()
|
|
{
|
|
mDecActive = false;
|
|
}
|
|
|
|
bool L1Decoder::decActive() const
|
|
{
|
|
return mDecActive;
|
|
}
|
|
|
|
L1Encoder* L1Decoder::sibling()
|
|
{
|
|
if (!mParent) return NULL;
|
|
return mParent->encoder();
|
|
}
|
|
|
|
|
|
const L1Encoder* L1Decoder::sibling() const
|
|
{
|
|
if (!mParent) return NULL;
|
|
return mParent->encoder();
|
|
}
|
|
|
|
|
|
void DecoderStats::countSNR(const RxBurst &burst)
|
|
{
|
|
// setting to 0 disables:
|
|
mLastSNR = burst.getNormalSNR();
|
|
if (int SNRAveragePeriod = gConfig.getNum("GSM.Radio.SNRAveragePeriod")) {
|
|
int count = min((int)mSNRCount,SNRAveragePeriod);
|
|
mAveSNR = (mLastSNR + count * mAveSNR) / (count+1);
|
|
mSNRCount++;
|
|
}
|
|
}
|
|
|
|
|
|
void L1Decoder::countStolenFrame(unsigned nframes)
|
|
{
|
|
// (pat 1-16-2014) Stolen frames should not affect FER reporting.
|
|
mDecoderStats.mStatTotalFrames += nframes;
|
|
mDecoderStats.mStatStolenFrames += nframes;
|
|
}
|
|
|
|
void L1Decoder::countGoodFrame(unsigned nframes) // Number of bursts to count.
|
|
{
|
|
// Subtract 2 for each good frame, but dont go below zero.
|
|
mBadFrameTracker = mBadFrameTracker <= 1 ? 0 : mBadFrameTracker-2;
|
|
static const float a = 1.0F / ((float)cFERMemory);
|
|
static const float b = 1.0F - a;
|
|
mDecoderStats.mAveFER *= b;
|
|
mDecoderStats.mStatTotalFrames += nframes;
|
|
OBJLOG(BLATHER) <<"L1Decoder FER=" << mDecoderStats.mAveFER;
|
|
}
|
|
|
|
void L1Decoder::countBER(unsigned bec, unsigned frameSize)
|
|
{
|
|
static const float a = 1.0F / ((float)cFERMemory);
|
|
static const float b = 1.0F - a;
|
|
float thisBER = (float) bec / frameSize;
|
|
mDecoderStats.mLastBER = thisBER;
|
|
mDecoderStats.mAveBER = b*mDecoderStats.mAveBER + a * thisBER;
|
|
}
|
|
|
|
|
|
void L1Decoder::countBadFrame(unsigned nframes) // Number of bursts to count.
|
|
{
|
|
mBadFrameTracker++;
|
|
static const float a = 1.0F / ((float)cFERMemory);
|
|
static const float b = 1.0F - a;
|
|
mDecoderStats.mAveFER = b*mDecoderStats.mAveFER + a;
|
|
mDecoderStats.mStatTotalFrames += nframes;
|
|
mDecoderStats.mStatBadFrames += nframes;
|
|
OBJLOG(BLATHER) <<"L1Decoder FER=" << mDecoderStats.mAveFER;
|
|
}
|
|
|
|
void SACCHL1Decoder::countBadFrame(unsigned nframes)
|
|
{
|
|
RSSIBumpDown(gConfig.getNum("Control.SACCHTimeout.BumpDown"));
|
|
L1Decoder::countBadFrame(nframes);
|
|
}
|
|
|
|
|
|
void L1Encoder::handoverPending(bool flag)
|
|
{
|
|
if (flag) {
|
|
bool ok = mDownstream->setHandover(mTN);
|
|
if (!ok) OBJLOG(ALERT) << "handover setup failed";
|
|
} else {
|
|
bool ok = mDownstream->clearHandover(mTN);
|
|
if (!ok) OBJLOG(ALERT) << "handover clear failed";
|
|
}
|
|
}
|
|
|
|
|
|
HandoverRecord& L1FEC::handoverPending(bool flag, unsigned handoverRef)
|
|
{
|
|
assert(mEncoder);
|
|
assert(mDecoder);
|
|
mEncoder->handoverPending(flag);
|
|
return mDecoder->handoverPending(flag, handoverRef);
|
|
}
|
|
|
|
|
|
// (pat) This can only be called during initialization, because installDecoder aborts
|
|
// if it is called twice on the same channel.
|
|
// This routine is used for traffic channels.
|
|
void L1FEC::downstream(ARFCNManager* radio)
|
|
{
|
|
if (mEncoder) mEncoder->downstream(radio);
|
|
if (mDecoder) radio->installDecoder(mDecoder);
|
|
}
|
|
|
|
void L1FEC::l1start()
|
|
{
|
|
LOG(DEBUG);
|
|
if (mDecoder) mDecoder->decStart();
|
|
if (mEncoder) mEncoder->encStart();
|
|
}
|
|
|
|
|
|
void L1FEC::l1init()
|
|
{
|
|
LOG(DEBUG);
|
|
if (mDecoder) mDecoder->decInit();
|
|
if (mEncoder) mEncoder->encInit();
|
|
}
|
|
|
|
void L1FEC::l1close()
|
|
{
|
|
LOG(DEBUG) <<descriptiveString();
|
|
if (mEncoder) mEncoder->close(); // Does the sendDummyFill.
|
|
if (mDecoder) mDecoder->close();
|
|
}
|
|
|
|
|
|
// Active means it is currently sending and receiving.
|
|
// recyclable is used to tell when the channel is reusable.
|
|
bool L1FEC::l1active() const
|
|
{
|
|
// non-sacch encode-only channels are always active.
|
|
// Otherwise, the decoder is the better indicator.
|
|
if (mDecoder) {
|
|
return mDecoder->decActive();
|
|
} else {
|
|
return (mEncoder!=NULL); // (pat) The send-only channels are always active.
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// (pat) Note that GPRS has an option to use a PRACH burst identical to RACH bursts.
|
|
// (pat) This routine is fed immediately from the radio in TRXManager;
|
|
// wDemuxTable points to this routine.
|
|
// The RACH is enqueued, and a separate thread runs AccessGrantResponder on the RACH,
|
|
// although I'm not sure why.
|
|
void RACHL1Decoder::writeLowSideRx(const RxBurst& burst)
|
|
{
|
|
// The L1 FEC for the RACH is defined in GSM 05.03 4.6.
|
|
|
|
// Decode the burst.
|
|
const SoftVector e(burst.segment(49,36));
|
|
//e.decode(mVCoder,mU);
|
|
mVCoder.decode(e,mU);
|
|
|
|
// To check validity, we have 4 tail bits and 6 parity bits.
|
|
// False alarm rate for random inputs is 1/1024.
|
|
|
|
// Check the tail bits -- should all the zero.
|
|
if (mU.peekField(14,4)) {
|
|
countBadFrame(1);
|
|
return;
|
|
}
|
|
|
|
// Check the parity.
|
|
// The parity word is XOR'd with the BSIC. (GSM 05.03 4.6.)
|
|
unsigned sentParity = ~mU.peekField(8,6);
|
|
unsigned checkParity = mD.parity(mParity);
|
|
unsigned encodedBSIC = (sentParity ^ checkParity) & 0x03f;
|
|
if (encodedBSIC != gBTS.BSIC()) {
|
|
countBadFrame(1);
|
|
return;
|
|
}
|
|
|
|
// We got a valid RACH burst.
|
|
// The "payload" is an 8-bit field, "RA", defined in GSM 04.08 9.1.8.
|
|
// The channel assignment procedure is in GSM 04.08 3.3.1.1.3.
|
|
// It requires knowledge of the RA value and the burst receive time.
|
|
// The RACH L2 is so thin that we don't even need code for it.
|
|
// Just pass the required information directly to the control layer.
|
|
|
|
countGoodFrame(1);
|
|
countBER(mVCoder.getBEC(),36);
|
|
mD.LSB8MSB();
|
|
unsigned RA = mD.peekField(0,8);
|
|
OBJLOG(INFO) <<"RACHL1Decoder received RA=" << RA << " at time " << burst.time() \
|
|
<< " with RSSI=" << burst.RSSI() << " timingError=" << burst.timingError() <<LOGVAR(TN());
|
|
//gBTS.channelRequest(new ChannelRequestRecord(RA,burst.time(),burst.RSSI(),burst.timingError()));
|
|
AccessGrantResponder(RA,burst.time(),burst.RSSI(),burst.timingError(),TN());
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
XCCHL1Encoder and Decoder methods.
|
|
The "XCCH" L1 components are based on GSM 05.03 4.1.
|
|
These are the most commonly used control channel L1 format
|
|
in GSM and are offer here as examples.
|
|
*/
|
|
|
|
|
|
|
|
XCCHL1Decoder::XCCHL1Decoder(
|
|
unsigned wCN,
|
|
unsigned wTN,
|
|
const TDMAMapping& wMapping,
|
|
L1FEC *wParent)
|
|
:L1Decoder(wCN,wTN,wMapping,wParent)
|
|
{
|
|
}
|
|
|
|
SharedL1Decoder::SharedL1Decoder()
|
|
: mBlockCoder(0x10004820009ULL, 40, 224),
|
|
mC(456),
|
|
mU(228),
|
|
mP(mU.segment(184,40)),mDP(mU.head(224)),mD(mU.head(184)),
|
|
mHParity(0x06f,6,8),mHU(18),mHD(mHU.head(8))
|
|
{
|
|
for (int i=0; i<4; i++) {
|
|
mE[i] = SoftVector(114);
|
|
mI[i] = SoftVector(114);
|
|
// Fill with zeros just to make Valgrind happy.
|
|
mE[i].fill(0);
|
|
mI[i].fill(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void XCCHL1Decoder::writeLowSideRx(const RxBurst& inBurst)
|
|
{
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder " << inBurst;
|
|
// If the channel is closed, ignore the burst.
|
|
if (!decActive()) {
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder not active, ignoring input";
|
|
return;
|
|
}
|
|
mDecoderStats.countSNR(inBurst);
|
|
// save frame number for possible decrypting
|
|
int B = mMapping.reverseMapping(inBurst.time().FN()) % 4;
|
|
mFN[B] = inBurst.time().FN();
|
|
|
|
// Accept the burst into the deinterleaving buffer.
|
|
// Return true if we are ready to interleave.
|
|
if (!processBurst(inBurst)) return;
|
|
if (mEncrypted == ENCRYPT_YES) {
|
|
decrypt();
|
|
}
|
|
if (mEncrypted == ENCRYPT_MAYBE) {
|
|
saveMi();
|
|
}
|
|
deinterleave();
|
|
if (decode()) {
|
|
countGoodFrame(1);
|
|
countBER(mVCoder.getBEC(),mC.size());
|
|
mD.LSB8MSB();
|
|
handleGoodFrame();
|
|
} else {
|
|
if (mEncrypted == ENCRYPT_MAYBE) {
|
|
// We don't want to start decryption until we get the (encrypted) layer 2 acknowledgement
|
|
// of the Ciphering Mode Command, so we start maybe decrypting when we send the command,
|
|
// and when the frame comes along, we'll see that it doesn't pass normal decoding, but
|
|
// when we try again with decryption, it will pass. Unless it's just noise.
|
|
OBJLOG(DEBUG) << "XCCHL1Decoder: try decoding again with decryption";
|
|
restoreMi();
|
|
decrypt();
|
|
deinterleave();
|
|
if (decode()) {
|
|
OBJLOG(DEBUG) << "XCCHL1Decoder: success on 2nd try";
|
|
// We've successfully decoded an encrypted frame. Start decrypting all uplink frames.
|
|
mEncrypted = ENCRYPT_YES;
|
|
// Also start encrypting downlink frames.
|
|
parent()->encoder()->mEncrypted = ENCRYPT_YES;
|
|
parent()->encoder()->mEncryptionAlgorithm = mEncryptionAlgorithm;
|
|
countGoodFrame(1);
|
|
mD.LSB8MSB();
|
|
handleGoodFrame();
|
|
countBER(mVCoder.getBEC(),mC.size());
|
|
} else {
|
|
countBadFrame(1);
|
|
}
|
|
} else {
|
|
countBadFrame(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void XCCHL1Decoder::saveMi()
|
|
{
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int j = 0; j < 114; j++) {
|
|
mE[i].settfb(j, mI[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void XCCHL1Decoder::restoreMi()
|
|
{
|
|
for (int i = 0; i < 4; i++) {
|
|
for (int j = 0; j < 114; j++) {
|
|
mI[i].settfb(j, mE[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void XCCHL1Decoder::decrypt()
|
|
{
|
|
// decrypt y
|
|
for (int i = 0; i < 4; i++) {
|
|
unsigned char block1[15];
|
|
unsigned char block2[15];
|
|
// 03.20 C.1.2
|
|
// 05.02 3.3.2.2.1
|
|
int fn = mFN[i];
|
|
int t1 = fn / (26*51);
|
|
int t2 = fn % 26;
|
|
int t3 = fn % 51;
|
|
int count = (t1<<11) | (t3<<5) | t2;
|
|
LOG(DEBUG) <<LOGVAR(fn) <<LOGVAR(count);
|
|
if (mEncryptionAlgorithm == 1) {
|
|
A51_GSM(mKc, 64, count, block1, block2);
|
|
} else if (mEncryptionAlgorithm == 3) {
|
|
A53_GSM(mKc, 64, count, block1, block2);
|
|
} else {
|
|
devassert(0);
|
|
}
|
|
for (int j = 0; j < 114; j++) {
|
|
if ((block2[j/8] & (0x80 >> (j%8)))) {
|
|
mI[i].settfb(j, 1.0 - mI[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool XCCHL1Decoder::processBurst(const RxBurst& inBurst)
|
|
{
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder " << inBurst;
|
|
// Accept the burst into the deinterleaving buffer.
|
|
// Return true if we are ready to interleave.
|
|
|
|
// TODO -- One quick test of burst validity is to look at the tail bits.
|
|
// We could do that as a double-check against putting garbage into
|
|
// the interleaver or accepting bad parameters.
|
|
// (pat) Wrong! Dont do that. The tail bits are there to help the viterbi decoder by steering
|
|
// it with known final bits, something we dont use at present. But if you discard frames based
|
|
// on non-zero tail bits you could be incorrectly discarding perfectly good frames because
|
|
// of one bad inconsequential bit.
|
|
|
|
// The reverse index runs 0..3 as the bursts arrive.
|
|
// It is the "B" index of GSM 05.03 4.1.4 and 4.1.5.
|
|
int B = mMapping.reverseMapping(inBurst.time().FN()) % 4;
|
|
// A negative value means that the demux is misconfigured.
|
|
assert(B>=0);
|
|
|
|
// Pull the data fields (e-bits) out of the burst and put them into i[B][].
|
|
// GSM 05.03 4.1.5
|
|
inBurst.data1().copyToSegment(mI[B],0);
|
|
inBurst.data2().copyToSegment(mI[B],57);
|
|
|
|
// If the burst index is 0, save the time
|
|
// FIXME -- This should be moved to the deinterleave methods.
|
|
if (B==0)
|
|
mReadTime = inBurst.time();
|
|
|
|
// If the burst index is 3, then this is the last burst in the L2 frame.
|
|
// Return true to indicate that we are ready to deinterleave.
|
|
return B==3;
|
|
|
|
// TODO -- This is sub-optimal because it ignores the case
|
|
// where the B==3 burst is simply missing, even though the soft decoder
|
|
// might actually be able to recover the frame. (pat) Dream on.
|
|
// It also allows for the mixing of bursts from different frames.
|
|
// If we were more clever, we'd be watching for B to roll over as well.
|
|
}
|
|
|
|
|
|
|
|
|
|
void SharedL1Decoder::deinterleave()
|
|
{
|
|
// Deinterleave i[][] to c[].
|
|
// This comes directly from GSM 05.03, 4.1.4.
|
|
for (int k=0; k<456; k++) {
|
|
int B = k%4;
|
|
int j = 2*((49*k) % 57) + ((k%8)/4);
|
|
mC[k] = mI[B][j];
|
|
// Mark this i[][] bit as unknown now.
|
|
// This makes it possible for the soft decoder to work around
|
|
// a missing burst.
|
|
mI[B][j] = 0.5F;
|
|
}
|
|
}
|
|
|
|
|
|
bool SharedL1Decoder::decode()
|
|
{
|
|
// Apply the convolutional decoder and parity check.
|
|
// Return true if we recovered a good L2 frame.
|
|
|
|
// Convolutional decoding c[] to u[].
|
|
// GSM 05.03 4.1.3
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder "<< mC;
|
|
//mC.decode(mVCoder,mU);
|
|
mVCoder.decode(mC,mU);
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder "<< mU;
|
|
|
|
// The GSM L1 u-frame has a 40-bit parity field.
|
|
// False detections are EXTREMELY rare.
|
|
// Parity check of u[].
|
|
// GSM 05.03 4.1.2.
|
|
mP.invert(); // parity is inverted
|
|
// The syndrome should be zero.
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder d[]:p[]=" << mDP;
|
|
unsigned syndrome = mBlockCoder.syndrome(mDP);
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder syndrome=" << hex << syndrome << dec;
|
|
// Simulate high FER for testing?
|
|
if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Uplink")) {
|
|
OBJLOG(NOTICE) << "XCCHL1Decoder simulating dropped uplink frame at " << mReadTime;
|
|
return false;
|
|
}
|
|
return (syndrome==0);
|
|
}
|
|
|
|
|
|
|
|
void XCCHL1Decoder::handleGoodFrame()
|
|
{
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder u[]=" << mU;
|
|
|
|
/*** Moved to L2LogicalChannel.
|
|
{
|
|
ScopedLock lock(mDecLock,__FILE__,__LINE__);
|
|
// Keep T3109 from timing out.
|
|
//mT3109.set();
|
|
// If this is the first good frame of a new transaction,
|
|
// stop T3101 and tell L2 we're alive down here.
|
|
if (mT3101.active()) {
|
|
mT3101.reset();
|
|
// This does not block; goes to a InterthreadQueue L2LAPdm::mL1In
|
|
if (mUpstream!=NULL) mUpstream->writeLowSide(L2Frame(ESTABLISH));
|
|
}
|
|
}
|
|
***/
|
|
|
|
// Get the d[] bits, the actual payload in the radio channel.
|
|
// Undo GSM's LSB-first octet encoding.
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder d[]=" << mD;
|
|
|
|
if (mUpstream) {
|
|
// Are we fuzzing ourselves?
|
|
if (random()%100 < gConfig.getNum("Test.GSM.UplinkFuzzingRate")) {
|
|
size_t i = random() % mD.size();
|
|
mD[i] = 1 - mD[i];
|
|
OBJLOG(NOTICE) << "XCCHL1Decoder fuzzing input frame, flipped bit " << i;
|
|
}
|
|
// Send all bits to GSMTAP
|
|
if (gConfig.getBool("Control.GSMTAP.GSM")) {
|
|
// FIXME -- This repeatLengh>51 is a bit of a hack.
|
|
gWriteGSMTAP(ARFCN(),TN(),mReadTime.FN(),typeAndOffset(),mMapping.repeatLength()>51,true,mD);
|
|
}
|
|
// Build an L2 frame and pass it up.
|
|
const BitVector2 L2Part(mD.tail(headerOffset()));
|
|
OBJLOG(DEBUG) <<"XCCHL1Decoder L2=" << L2Part;
|
|
mUpstream->writeLowSide(L2Frame(L2Part/*,DATA*/));
|
|
} else {
|
|
OBJLOG(ERR) << "XCCHL1Decoder with no uplink connected.";
|
|
}
|
|
}
|
|
|
|
// Get the physical parameters of the burst.
|
|
void MSPhysReportInfo::processPhysInfo(const RxBurst &inBurst)
|
|
{
|
|
// RSSI is dB wrt full scale.
|
|
unsigned count = min((int)mReportCount,(int)gConfig.getNum("GSM.Radio.RSSIAveragePeriod"));
|
|
mRSSI = (inBurst.RSSI() + count * mRSSI) / (count+1);
|
|
|
|
// Timing error is a float in symbol intervals.
|
|
// (pat) It is the timing error of the received bursts which means it is relative to the Timing Advance currently in use.
|
|
count = min(mReportCount,cAveragePeriodTiming);
|
|
mTimingError = (inBurst.timingError() + count * mTimingError) / (count+1);
|
|
|
|
// Timestamp
|
|
mTimestamp = gBTS.clock().systime(inBurst.time());
|
|
|
|
OBJLOG(INFO) << "SACCHL1Decoder " << " RSSI=" <<mRSSI << " burst.RSSI="<<inBurst.RSSI() \
|
|
<< " timestamp=" << mTimestamp \
|
|
<< " timingError=" << inBurst.timingError() << LOGVARM(mReportCount);
|
|
mReportCount++;
|
|
}
|
|
|
|
|
|
bool SACCHL1Decoder::processBurst(const RxBurst& inBurst)
|
|
{
|
|
// TODO -- One quick test of burst validity is to look at the tail bits.
|
|
// We could do that as a double-check against putting garbage into
|
|
// the interleaver or accepting bad parameters.
|
|
|
|
// TODO: We shouldnt save the phys info if the burst is bad.
|
|
processPhysInfo(inBurst);
|
|
return XCCHL1Decoder::processBurst(inBurst);
|
|
}
|
|
|
|
|
|
void SACCHL1Decoder::handleGoodFrame()
|
|
{
|
|
// GSM 04.04 7
|
|
OBJLOG(DEBUG) << "SACCHL1Decoder "<<" phy header " << mU.head(16);
|
|
mActualMSPower = decodePower(mU.peekField(3,5));
|
|
int TAField = mU.peekField(9,7);
|
|
if (TAField<64) mActualMSTiming = TAField;
|
|
OBJLOG(BLATHER) << "actuals pow=" << mActualMSPower << " TA=" << mActualMSTiming;
|
|
XCCHL1Decoder::handleGoodFrame();
|
|
}
|
|
|
|
|
|
// Process the 184 bit frame, starting at offset, add parity, encode.
|
|
// Result is left in mI, representing 4 radio bursts.
|
|
void SharedL1Encoder::encodeFrame41(const BitVector2 &src, int offset, bool copy)
|
|
{
|
|
if (copy) src.copyToSegment(mU,offset);
|
|
OBJLOG(DEBUG) << "before d[]=" << mD;
|
|
mD.LSB8MSB();
|
|
OBJLOG(DEBUG) << "after d[]=" << mD;
|
|
encode41();
|
|
interleave41();
|
|
}
|
|
|
|
|
|
XCCHL1Encoder::XCCHL1Encoder(
|
|
unsigned wCN,
|
|
unsigned wTN,
|
|
const TDMAMapping& wMapping,
|
|
L1FEC* wParent)
|
|
: SharedL1Encoder(),
|
|
L1Encoder(wCN,wTN,wMapping,wParent)
|
|
{
|
|
mFillerBurst = TxBurst(gDummyBurst);
|
|
|
|
// Set up the training sequence and stealing bits
|
|
// since they'll be the same for all bursts.
|
|
|
|
// stealing bits for a control channel, GSM 05.03 4.2.5, 05.02 5.2.3.
|
|
// (pat) For a GPRS channel these bits are used for the encoding, 1,1 implies CS-1.
|
|
// Since we will be sharing the channels between GSM and GPRS, we cannot depend
|
|
// on this preinitialization surviving. This is so minor, I am just going
|
|
// to set these anew inside transmit().
|
|
//mBurst.Hl(1);
|
|
//mBurst.Hu(1);
|
|
|
|
// training sequence, GSM 05.02 5.2.3
|
|
gTrainingSequence[mTSC].copyToSegment(mBurst,61);
|
|
}
|
|
|
|
|
|
// Default initialization is as for XCCH channels (SACCH) or CS-1 encoding.
|
|
// Pat says: From GSM04.03sec5.1 the 40 bit parity is generated by the polynomial:
|
|
// g(D) = (D23 + 1)*(D17 + D3 + 1) = 1 + D3 + D17 + D23 + D26 + D40,
|
|
// which are the bits set in Parity initialization below.
|
|
void SharedL1Encoder::initInterleave(int mIsize)
|
|
{
|
|
// Set up the interleaving buffers.
|
|
for(int k = 0; k<mIsize; k++) {
|
|
mI[k].resize(114);
|
|
mE[k].resize(114);
|
|
// Fill with zeros just to make Valgrind happy.
|
|
mI[k].fill(0);
|
|
mE[k].fill(0);
|
|
}
|
|
}
|
|
|
|
SharedL1Encoder::SharedL1Encoder():
|
|
mBlockCoder(0x10004820009ULL, 40, 224),
|
|
mC(456), mU(228),
|
|
mD(mU.head(184)),
|
|
mP(mU.segment(184,40))
|
|
{
|
|
initInterleave(4);
|
|
mU.zero(); // zeros out the tail bits.
|
|
mC.zero(); // Be safe; only happens once, ever.
|
|
}
|
|
|
|
|
|
void XCCHL1Encoder::writeHighSide(const L2Frame& frame)
|
|
{
|
|
//assert(frame.primitive() == DATA);
|
|
if (!encActive()) { OBJLOG(INFO) << "XCCHL1Encoder::writeHighSide sending on non-active channel "; }
|
|
resync();
|
|
sendFrame(frame);
|
|
}
|
|
|
|
|
|
|
|
void XCCHL1Encoder::sendFrame(const L2Frame& frame)
|
|
{
|
|
OBJLOG(DEBUG) << frame;
|
|
// Make sure there's something down there to take the bursts.
|
|
if (mDownstream==NULL) {
|
|
LOG(WARNING) << "XCCHL1Encoder with no downstream";
|
|
return;
|
|
}
|
|
|
|
// This comes from GSM 05.03 4.1
|
|
|
|
// Send to GSMTAP
|
|
frame.copyToSegment(mU,headerOffset());
|
|
if (gConfig.getBool("Control.GSMTAP.GSM")) {
|
|
gWriteGSMTAP(ARFCN(),TN(),mNextWriteTime.FN(),typeAndOffset(),mMapping.repeatLength()>51,false,mU);
|
|
}
|
|
|
|
|
|
// Copy the L2 frame into u[] for processing.
|
|
// GSM 05.03 4.1.1.
|
|
//LOG(DEBUG) << "mU=" << mU.inspect();
|
|
//LOG(DEBUG) << "mD=" << mD.inspect();
|
|
// Process the 184 bit frame, leave result in mI.
|
|
//mFECEnc.encodeFrame41(frame,headerOffset(),mFECEnc.mVCoder);
|
|
encodeFrame41(frame,headerOffset(), false);
|
|
const int qCS1[8] = { 1,1,1,1,1,1,1,1 }; // magically identifies CS-1.
|
|
transmit(mI,mE,qCS1);
|
|
}
|
|
|
|
|
|
void SharedL1Encoder::encode41()
|
|
{
|
|
// Perform the FEC encoding of GSM 05.03 4.1.2 and 4.1.3
|
|
|
|
// GSM 05.03 4.1.2
|
|
// Generate the parity bits.
|
|
mBlockCoder.writeParityWord(mD,mP);
|
|
OBJLOG(DEBUG) << "u[]=" << mU;
|
|
// GSM 05.03 4.1.3
|
|
// Apply the convolutional encoder.
|
|
//mU.encode(mVCoder,mC);
|
|
mVCoder.encode(mU,mC);
|
|
OBJLOG(DEBUG) << "c[]=" << mC;
|
|
}
|
|
|
|
|
|
|
|
void SharedL1Encoder::interleave41()
|
|
{
|
|
// GSM 05.03, 4.1.4. Verbatim.
|
|
for (int k=0; k<456; k++) {
|
|
int B = k%4;
|
|
int j = 2*((49*k) % 57) + ((k%8)/4);
|
|
mI[B][j] = mC[k];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// (pat) This code is not used for gprs, but part of the L1Encoder is now shared
|
|
// with gprs, and the stealing bits may be modified if the channel is used for
|
|
// gprs, so this function is modified to set the stealing bits properly
|
|
// before each transmission, rather than having them be static.
|
|
// The qbits, also called stealing bits, are defined in GSM05.03.
|
|
// For GPRS they specify the encoding type: CS-1 through CS-4.
|
|
void L1Encoder::transmit(BitVector2 *mI, BitVector2 *mE, const int *qbits)
|
|
{
|
|
// Format the bits into the bursts.
|
|
// GSM 05.03 4.1.5, 05.02 5.2.3
|
|
waitToSend(); // Don't get too far ahead of the clock.
|
|
|
|
if (!mDownstream) {
|
|
// For some testing, we might not have a radio connected.
|
|
// That's OK, as long as we know it.
|
|
LOG(WARNING) << "XCCHL1Encoder with no radio, dumping frames";
|
|
return;
|
|
}
|
|
|
|
// add noise
|
|
// the noise insertion happens below, merged in with the ciphering
|
|
int p = gConfig.getFloat("GSM.Cipher.CCHBER") * (float)0xFFFFFF;
|
|
|
|
for (int qi=0,B=0; B<4; B++) {
|
|
mBurst.time(mNextWriteTime);
|
|
// encrypt y
|
|
if (mEncrypted == ENCRYPT_YES) {
|
|
unsigned char block1[15];
|
|
unsigned char block2[15];
|
|
unsigned char *kc = parent()->decoder()->kc();
|
|
// 03.20 C.1.2
|
|
// 05.02 3.3.2.2.1
|
|
int fn = mNextWriteTime.FN();
|
|
int t1 = fn / (26*51);
|
|
int t2 = fn % 26;
|
|
int t3 = fn % 51;
|
|
int count = (t1<<11) | (t3<<5) | t2;
|
|
if (mEncryptionAlgorithm == 1) {
|
|
A51_GSM(kc, 64, count, block1, block2);
|
|
} else if (mEncryptionAlgorithm == 3) {
|
|
A53_GSM(kc, 64, count, block1, block2);
|
|
} else {
|
|
devassert(0);
|
|
}
|
|
for (int i = 0; i < 114; i++) {
|
|
int b = p ? (random() & 0xFFFFFF) < p : 0;
|
|
b = b ^ (block1[i/8] >> (7-(i%8)));
|
|
mE[B].settfb(i, mI[B].bit(i) ^ (b&1));
|
|
}
|
|
} else {
|
|
if (p) {
|
|
for (int i = 0; i < 114; i++) {
|
|
int b = (random() & 0xFFFFFF) < p;
|
|
mE[B].settfb(i, mI[B].bit(i) ^ b);
|
|
}
|
|
} else {
|
|
// no noise or encryption. use mI below.
|
|
}
|
|
}
|
|
|
|
// Copy in the "encrypted" bits, GSM 05.03 4.1.5, 05.02 5.2.3.
|
|
if (p || mEncrypted == ENCRYPT_YES) {
|
|
OBJLOG(DEBUG) << "transmit mE["<<B<<"]=" << mE[B];
|
|
mE[B].segment(0,57).copyToSegment(mBurst,3);
|
|
mE[B].segment(57,57).copyToSegment(mBurst,88);
|
|
} else {
|
|
// no noise or encryption. use mI.
|
|
OBJLOG(DEBUG) << "transmit mI["<<B<<"]=" << mI[B];
|
|
mI[B].segment(0,57).copyToSegment(mBurst,3);
|
|
mI[B].segment(57,57).copyToSegment(mBurst,88);
|
|
}
|
|
mBurst.Hl(qbits[qi++]);
|
|
mBurst.Hu(qbits[qi++]);
|
|
// Send it to the radio.
|
|
OBJLOG(DEBUG) << "transmit mBurst=" << mBurst;
|
|
mDownstream->writeHighSideTx(mBurst,"Shared");
|
|
rollForward();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void GeneratorL1Encoder::serviceStart()
|
|
{
|
|
//L1Encoder::encStart();
|
|
mSendThread.start((void*(*)(void*))GeneratorL1EncoderServiceLoopAdapter,(void*)this);
|
|
}
|
|
|
|
|
|
|
|
void *GeneratorL1EncoderServiceLoopAdapter(GeneratorL1Encoder* gen)
|
|
{
|
|
gen->serviceLoop();
|
|
// DONTREACH
|
|
return NULL;
|
|
}
|
|
|
|
void GeneratorL1Encoder::serviceLoop()
|
|
{
|
|
while (mRunning && !gBTS.btsShutdown()) {
|
|
resync();
|
|
waitToSend();
|
|
generate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
SCHL1Encoder::SCHL1Encoder(L1FEC* wParent, unsigned wTN)
|
|
:GeneratorL1Encoder(0,wTN,gSCHMapping,wParent),
|
|
mBlockCoder(0x0575,10,25),
|
|
mU(25+10+4), mE(78),
|
|
mD(mU.head(25)),mP(mU.segment(25,10)),
|
|
mE1(mE.segment(0,39)),mE2(mE.segment(39,39))
|
|
{
|
|
// The SCH extended training sequence.
|
|
// GSM 05.02 5.2.5.
|
|
static const BitVector2 xts("1011100101100010000001000000111100101101010001010111011000011011");
|
|
xts.copyToSegment(mBurst,42);
|
|
// Set the tail bits in u[] now, just once.
|
|
mU.fillField(35,0,4);
|
|
}
|
|
|
|
|
|
|
|
void SCHL1Encoder::generate()
|
|
{
|
|
OBJLOG(DEBUG) << "SCHL1Encoder " << mNextWriteTime;
|
|
assert(mDownstream);
|
|
// Data, GSM 04.08 9.1.30
|
|
size_t wp=0;
|
|
mD.writeField(wp,gBTS.BSIC(),6);
|
|
mD.writeField(wp,mNextWriteTime.T1(),11);
|
|
mD.writeField(wp,mNextWriteTime.T2(),5);
|
|
mD.writeField(wp,mNextWriteTime.T3p(),3);
|
|
mD.LSB8MSB();
|
|
// Encoding, GSM 05.03 4.7
|
|
// Parity
|
|
mBlockCoder.writeParityWord(mD,mP);
|
|
// Convolutional encoding
|
|
//mU.encode(mVCoder,mE);
|
|
mVCoder.encode(mU,mE);
|
|
// Mapping onto a burst, GSM 05.02 5.2.5.
|
|
mBurst.time(mNextWriteTime);
|
|
mE1.copyToSegment(mBurst,3);
|
|
mE2.copyToSegment(mBurst,106);
|
|
// Send it already!
|
|
mDownstream->writeHighSideTx(mBurst,"SCH");
|
|
rollForward();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
FCCHL1Encoder::FCCHL1Encoder(L1FEC *wParent, unsigned wTN)
|
|
:GeneratorL1Encoder(0,wTN,gFCCHMapping,wParent)
|
|
{
|
|
mBurst.zero();
|
|
mFillerBurst.zero();
|
|
}
|
|
|
|
|
|
void FCCHL1Encoder::generate()
|
|
{
|
|
OBJLOG(DEBUG) << "FCCHL1Encoder " << mNextWriteTime;
|
|
assert(mDownstream);
|
|
resync();
|
|
for (int i=0; i<5; i++) {
|
|
mBurst.time(mNextWriteTime);
|
|
mDownstream->writeHighSideTx(mBurst,"FCCH");
|
|
rollForward();
|
|
}
|
|
sleep(1);
|
|
}
|
|
|
|
|
|
|
|
|
|
void NDCCHL1Encoder::serviceStart()
|
|
{
|
|
//L1Encoder::encStart();
|
|
mSendThread.start((void*(*)(void*))NDCCHL1EncoderServiceLoopAdapter,(void*)this);
|
|
}
|
|
|
|
|
|
|
|
void *NDCCHL1EncoderServiceLoopAdapter(NDCCHL1Encoder* gen)
|
|
{
|
|
gen->serviceLoop();
|
|
// DONTREACH
|
|
return NULL;
|
|
}
|
|
|
|
void NDCCHL1Encoder::serviceLoop()
|
|
{
|
|
while (mRunning && !gBTS.btsShutdown()) {
|
|
generate();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BCCHL1Encoder::generate()
|
|
{
|
|
OBJLOG(DEBUG) << "BCCHL1Encoder " << mNextWriteTime;
|
|
// BCCH mapping, GSM 05.02 6.3.1.3
|
|
// Since we're not doing GPRS or VGCS, it's just SI1-4 over and over.
|
|
// pat 8-2011: If we are doing GPRS, the SI13 must be in slot 4.
|
|
switch (mNextWriteTime.TC()) {
|
|
// (pat) Maps to: XCCHL1Encoder::writeHighSide.
|
|
case 0: writeHighSide(gBTS.SI1Frame()); return;
|
|
case 1: writeHighSide(gBTS.SI2Frame()); return;
|
|
case 2: writeHighSide(gBTS.SI3Frame()); return;
|
|
case 3: writeHighSide(gBTS.SI4Frame()); return;
|
|
//case 4: writeHighSide(GPRS::GPRSConfig::IsEnabled() ? gBTS.SI13Frame() : gBTS.SI3Frame());
|
|
case 4: writeHighSide(gBTS.SI13() ? gBTS.SI13Frame() : gBTS.SI3Frame());
|
|
return;
|
|
case 5: writeHighSide(gBTS.SI2Frame()); return;
|
|
case 6: writeHighSide(gBTS.SI3Frame()); return;
|
|
case 7: writeHighSide(gBTS.SI4Frame()); return;
|
|
default: assert(0);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// TCH_FS
|
|
TCHFACCHL1Decoder::TCHFACCHL1Decoder(
|
|
unsigned wCN,
|
|
unsigned wTN,
|
|
const TDMAMapping& wMapping,
|
|
L1FEC *wParent)
|
|
:XCCHL1Decoder(wCN,wTN, wMapping, wParent)
|
|
{
|
|
for (int i=0; i<8; i++) {
|
|
mE[i] = SoftVector(114);
|
|
mI[i] = SoftVector(114);
|
|
// Fill with zeros just to make Valgrind happy.
|
|
mI[i].fill(.0);
|
|
mE[i].fill(.0);
|
|
}
|
|
}
|
|
|
|
ViterbiBase *newViterbi(AMRMode mode)
|
|
{
|
|
switch (mode) {
|
|
case TCH_AFS12_2: return new ViterbiTCH_AFS12_2();
|
|
case TCH_AFS10_2: return new ViterbiTCH_AFS10_2();
|
|
case TCH_AFS7_95: return new ViterbiTCH_AFS7_95();
|
|
case TCH_AFS7_4: return new ViterbiTCH_AFS7_4();
|
|
case TCH_AFS6_7: return new ViterbiTCH_AFS6_7();
|
|
case TCH_AFS5_9: return new ViterbiTCH_AFS5_9();
|
|
case TCH_AFS5_15: return new ViterbiTCH_AFS5_15();
|
|
case TCH_AFS4_75: return new ViterbiTCH_AFS4_75();
|
|
case TCH_FS: return new ViterbiR2O4();
|
|
default: assert(0);
|
|
}
|
|
};
|
|
|
|
void TCHFRL1Decoder::setAmrMode(AMRMode wMode)
|
|
{
|
|
mAMRMode = wMode;
|
|
int kd = gAMRKd[wMode]; // decoded payload size.
|
|
mTCHD.resize(kd);
|
|
mPrevGoodFrame.resize(kd);
|
|
mPrevGoodFrame.zero(); // When switching modes the contents of this are garbage, so zero.
|
|
mNumBadFrames = 0;
|
|
setViterbi(wMode);
|
|
if (wMode == TCH_FS) {
|
|
assert(kd == 260);
|
|
mTCHU.resize(189);
|
|
//mClass1_c.dup(mC.head(378)); // no longer used
|
|
mClass1A_d.dup(mTCHD.head(50));
|
|
//mClass2_c.dup(mC.segment(378,78)); // no longer used.
|
|
mTCHParity = Parity(0x0b,3,50);
|
|
} else {
|
|
mTCHU.resize(kd+6); // why +6?
|
|
mClass1A_d.dup(mTCHD.head(mClass1ALth));
|
|
mTCHParity = Parity(0x06f,6,gAMRClass1ALth[wMode]);
|
|
mAMRBitOrder = gAMRBitOrder[wMode];
|
|
mClass1ALth = gAMRClass1ALth[wMode];
|
|
mClass1BLth = gAMRKd[wMode] - gAMRClass1ALth[wMode];
|
|
mTCHUC.resize(gAMRTCHUCLth[wMode]);
|
|
mPuncture = gAMRPuncture[wMode];
|
|
mPunctureLth = gAMRPunctureLth[wMode];
|
|
setViterbi(wMode); //mViterbiSet.getViterbi(wMode);
|
|
}
|
|
}
|
|
|
|
|
|
void TCHFACCHL1Decoder::writeLowSideRx(const RxBurst& inBurst)
|
|
{
|
|
L1FEC *fparent = parent();
|
|
if (fparent->mGprsReserved) { // Channel is reserved for gprs.
|
|
if (parent()->mGPRSFEC) { // If set, bursts are delivered to this FEC in GPRS.
|
|
GPRS::GPRSWriteLowSideRx(inBurst, parent()->mGPRSFEC);
|
|
}
|
|
return; // done
|
|
}
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Decoder " << inBurst <<LOGVAR(mHandoverPending); // <<LOGVAR(mT3101.remaining());
|
|
// If the channel is closed, ignore the burst.
|
|
if (!decActive()) {
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Decoder not active, ignoring input";
|
|
return;
|
|
}
|
|
ScopedLock lock(mDecLock,__FILE__,__LINE__); // this better be redundant.
|
|
if (mHandoverPending && ! mT3103.expired()) {
|
|
// If this channel is waiting for an inbound handover,
|
|
// try to decode a handover access burst.
|
|
// GSM 05.03 4.9, 4.6
|
|
// Based on the RACHL1Decoder.
|
|
|
|
//LOG(DEBUG) << "handover access " << inBurst;
|
|
OBJLOG(NOTICE) << "handover access " << inBurst;
|
|
|
|
// Decode the burst.
|
|
const SoftVector e(inBurst.segment(49,36));
|
|
//e.decode(mVCoder,mHU);
|
|
mVCoder.decode(e,mHU);
|
|
OBJLOG(DEBUG) << "handover access U=" << mHU;
|
|
// Check the tail bits -- should all the zero.
|
|
if (mHU.peekField(14,4)) return;
|
|
// Check the parity.
|
|
unsigned sentParity = ~mHU.peekField(8,6);
|
|
unsigned checkParity = mHD.parity(mHParity);
|
|
unsigned encodedBSIC = (sentParity ^ checkParity) & 0x03f;
|
|
OBJLOG(DEBUG) << "handover access sentParity " << sentParity
|
|
<< " checkParity " << checkParity
|
|
<< " endcodedBSIC " << encodedBSIC;
|
|
if (encodedBSIC != gBTS.BSIC()) return;
|
|
// OK. So we got a burst.
|
|
mHD.LSB8MSB();
|
|
unsigned ref = mHD.peekField(0,8);
|
|
|
|
// l3rewrite validates the handover ref down here in L1 rather than calling up to L3.
|
|
// oops, guess it doesnt.
|
|
// if (!Control::SaveHandoverAccess(ref,inBurst.RSSI(),inBurst.timingError(),inBurst.time())) return;
|
|
// mUpstream->writeLowSide(HANDOVER_ACCESS);
|
|
|
|
if (ref == mHandoverRef) {
|
|
OBJLOG(NOTICE) << "queuing HANDOVER_ACCESS ref=" << ref;
|
|
mT3103.reset();
|
|
double when = gBTS.clock().systime(inBurst.time());
|
|
mHandoverRecord = HandoverRecord(inBurst.RSSI(),inBurst.timingError(),when);
|
|
mUpstream->writeLowSide(L2Message(HANDOVER_ACCESS));
|
|
// (pat) FIXME: We need to set the PHY params from the handover burst so that SACCH will be transmitting the correct TA.
|
|
} else {
|
|
OBJLOG(ERR) << "no inbound handover with reference " << ref;
|
|
}
|
|
|
|
return;
|
|
}
|
|
mDecoderStats.countSNR(inBurst);
|
|
processBurst(inBurst);
|
|
}
|
|
|
|
|
|
|
|
// (pat) How the burst gets here:
|
|
// TRXManager.cpp has a wDemuxTable for each frame+timeslot with a pointer to
|
|
// a virtual L1Decoder::writeLowSideRx() function. For traffic channels, this maps to
|
|
// XCCHL1Decoder::writeLowSideRx(), which checks active(), and if true,
|
|
// then calls this, and if this returns true, goes ahead with decoding.
|
|
bool TCHFACCHL1Decoder::processBurst( const RxBurst& inBurst)
|
|
{
|
|
// Accept the burst into the deinterleaving buffer.
|
|
// Return true if we are ready to interleave.
|
|
|
|
// TODO -- One quick test of burst validity is to look at the tail bits.
|
|
// We could do that as a double-check against putting garbage into
|
|
// the interleaver or accepting bad parameters.
|
|
// (pat) I think the above is a bad idea since there is no error correction on the tail bits.
|
|
// If our viterbi decoder were smarter it would be generating estimated BER during decoding.
|
|
|
|
// The reverse index runs 0..7 as the bursts arrive.
|
|
// It is the "B" index of GSM 05.03 3.1.3 and 3.1.4.
|
|
int B = mMapping.reverseMapping(inBurst.time().FN()) % 8;
|
|
// A negative value means that the demux is misconfigured.
|
|
assert(B>=0);
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Decoder B=" << B << " " << inBurst;
|
|
|
|
// Pull the data fields (e-bits) out of the burst and put them into i[B][].
|
|
// GSM 05.03 3.1.4
|
|
inBurst.data1().copyToSegment(mI[B],0);
|
|
inBurst.data2().copyToSegment(mI[B],57);
|
|
|
|
// save the frame numbers for each burst for possible decryption later
|
|
mFN[B] = inBurst.time().FN();
|
|
stealBitsL[B] = inBurst.Hl();
|
|
stealBitsU[B] = inBurst.Hu();
|
|
|
|
// Every 4th frame is the start of a new block.
|
|
// So if this isn't a "4th" frame, return now.
|
|
if (B%4!=3) return false;
|
|
|
|
if (mEncrypted == ENCRYPT_MAYBE) {
|
|
saveMi();
|
|
}
|
|
|
|
if (mEncrypted == ENCRYPT_YES) {
|
|
decrypt(B);
|
|
}
|
|
|
|
// Deinterleave according to the diagonal "phase" of B.
|
|
// See GSM 05.03 3.1.3.
|
|
// Deinterleaves i[] to c[]
|
|
if (B==3) deinterleaveTCH(4);
|
|
else deinterleaveTCH(0);
|
|
|
|
// See if this was the end of a stolen frame, GSM 05.03 4.2.5.
|
|
// (pat) There are 8 bits to determine if the frame is stolen. If they are all set one
|
|
// way or the other, that is a pretty good indication the frame is stolen or not, but
|
|
// if they are ambiguous, we will try decoding the frame as FACCH to check parity, which is a much stronger condition.
|
|
//bool stolen = inBurst.Hl();
|
|
unsigned stolenbits = 0; // Number of stolen bit markers. In the range 0 .. 8
|
|
if (B == 3) {
|
|
stolenbits = stealBitsU[4] + stealBitsU[5] + stealBitsU[6] + stealBitsU[7] +
|
|
stealBitsL[0] + stealBitsL[1] + stealBitsL[2] + stealBitsL[3];
|
|
} else {
|
|
stolenbits = stealBitsU[0] + stealBitsU[1] + stealBitsU[2] + stealBitsU[3] +
|
|
stealBitsL[4] + stealBitsL[5] + stealBitsL[6] + stealBitsL[7];
|
|
}
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder Hl=" << inBurst.Hl() << " Hu=" << inBurst.Hu();
|
|
bool okFACCH = false;
|
|
if (stolenbits) { // If any of the 8 stolen bits are set, try decoding as FACCH.
|
|
okFACCH = decode(); // Calls SharedL1Decoder::decode() to decode mC into mU
|
|
if (!okFACCH && mEncrypted == ENCRYPT_MAYBE) {
|
|
// (doug) We don't want to start decryption until we get the (encrypted) layer 2 acknowledgement
|
|
// of the Ciphering Mode Command, so we start maybe decrypting when we send the command,
|
|
// and when the frame comes along, we'll see that it doesn't pass normal decoding, but
|
|
// when we try again with decryption, it will pass. Unless it's just noise.
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Decoder: try decoding again with decryption";
|
|
restoreMi();
|
|
decrypt(-1);
|
|
// re-deinterleave
|
|
if (B==3) deinterleaveTCH(4);
|
|
else deinterleaveTCH(0);
|
|
// re-decode
|
|
okFACCH = decode();
|
|
if (okFACCH) {
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Decoder: success on 2nd try";
|
|
// We've successfully decoded an encrypted frame. Start decrypting all uplink frames.
|
|
mEncrypted = ENCRYPT_YES;
|
|
// Also start encrypting downlink frames.
|
|
parent()->encoder()->mEncrypted = ENCRYPT_YES;
|
|
parent()->encoder()->mEncryptionAlgorithm = mEncryptionAlgorithm;
|
|
}
|
|
}
|
|
|
|
if (okFACCH) { // This frame was stolen for sure.
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder good FACCH frame";
|
|
//countGoodFrame();
|
|
mD.LSB8MSB();
|
|
// This also resets T3109.
|
|
handleGoodFrame();
|
|
}
|
|
}
|
|
|
|
// Always feed the traffic channel, even on a stolen frame.
|
|
// decodeTCH will handle the GSM 06.11 bad frame processing.
|
|
// (pat) If the frame was truly stolen but was too corrupt to decrypt we dont want to push it
|
|
// into the audio frame because there are only 3 parity bits on the audio frame so the chance of
|
|
// misinterpreting it is significant. To try to reduce that probability I am adding a totally
|
|
// arbitrary check on the number of stealing bits that were set; I made this number up from thin air.
|
|
bool traffic = decodeTCH(okFACCH || stolenbits > 5,&mC);
|
|
// Now keep statistics...
|
|
if (okFACCH) {
|
|
countStolenFrame(1); // was 4, why? We are counting frames, which occur every 4 bursts (even though they are spread over 8 bursts.)
|
|
countBER(mVCoder.getBEC(),378);
|
|
} else if (traffic) {
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder good TCH frame";
|
|
countGoodFrame(1); // was 4, why?
|
|
countBER(mVCoder.getBEC(),378);
|
|
// Don't let the channel timeout.
|
|
//ScopedLock lock(mDecLock,__FILE__,__LINE__);
|
|
// (pat 4-2014) There are only 3 parity bits on the speech frame so the false-positive detection probability is high,
|
|
// resulting in setting T3109 and preventing the channel from being recycled. Not sure what to do, because
|
|
// there may not be any FACCH frames to set T3109.
|
|
//mT3109.set();
|
|
}
|
|
else countBadFrame(4);
|
|
|
|
return true; // note: result not used by this class.
|
|
}
|
|
|
|
|
|
void TCHFACCHL1Decoder::saveMi()
|
|
{
|
|
for (int i = 0; i < 8; i++) {
|
|
for (int j = 0; j < 114; j++) {
|
|
mE[i].settfb(j, mI[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TCHFACCHL1Decoder::restoreMi()
|
|
{
|
|
for (int i = 0; i < 8; i++) {
|
|
for (int j = 0; j < 114; j++) {
|
|
mI[i].settfb(j, mE[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TCHFACCHL1Decoder::decrypt(int B)
|
|
{
|
|
// decrypt x
|
|
unsigned char block1[15];
|
|
unsigned char block2[15];
|
|
int bb = B==7 ? 4 : 0;
|
|
int be = B<0 ? 8 : bb+4;
|
|
for (int i = bb; i < be; i++) {
|
|
// 03.20 C.1.2
|
|
// 05.02 3.3.2.2.1
|
|
int fn = mFN[i];
|
|
int t1 = fn / (26*51);
|
|
int t2 = fn % 26;
|
|
int t3 = fn % 51;
|
|
int count = (t1<<11) | (t3<<5) | t2;
|
|
if (mEncryptionAlgorithm == 1) {
|
|
A51_GSM(mKc, 64, count, block1, block2);
|
|
} else if (mEncryptionAlgorithm == 3) {
|
|
A53_GSM(mKc, 64, count, block1, block2);
|
|
} else {
|
|
devassert(0);
|
|
}
|
|
for (int j = 0; j < 114; j++) {
|
|
if ((block2[j/8] & (0x80 >> (j%8)))) {
|
|
mI[i].settfb(j, 1.0 - mI[i].softbit(j));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void TCHFACCHL1Decoder::deinterleaveTCH(int blockOffset )
|
|
{
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder blockOffset=" << blockOffset;
|
|
for (int k=0; k<456; k++) {
|
|
int B = ( k + blockOffset ) % 8;
|
|
int j = 2*((49*k) % 57) + ((k%8)/4);
|
|
mC[k] = mI[B][j];
|
|
mI[B][j] = 0.5F;
|
|
}
|
|
}
|
|
|
|
void TCHFACCHL1Decoder::addToSpeechQ(AudioFrame *newFrame) { mSpeechQ.write(newFrame); }
|
|
|
|
|
|
// (pat) See GSM 6.12 5.2 and 5.03 table 2 (in section 5.4)
|
|
// I did not get this to work in that I did not see any silence frames.
|
|
static bool isSIDFrame(BitVector &frame)
|
|
{
|
|
// A SID frame is marked by all zeros in particular positions in the RPE pulse data.
|
|
// If all the above bits are 0, it is a SID frame.
|
|
const char *frameMarker[4] = {
|
|
// There are 13 RPE pulses per frame.
|
|
// In the first three sub-frames, two bits of each RPE pulse are considered.
|
|
// I dont know which direction the bits go within each variable.
|
|
"00x00x00x00x00x00x00x00x00x00x00x00x00x",
|
|
"00x00x00x00x00x00x00x00x00x00x00x00x00x",
|
|
"00x00x00x00x00x00x00x00x00x00x00x00x00x",
|
|
// In the fourth sub-frame bit one is included only for the first 4 RPE pulses (numbers 64-67 inclusive)
|
|
"00x00x00x00x0xx0xx0xx0xx0xx0xx0xx0xx0xx" };
|
|
//"x00x00x00x00x00x00x00x00x00x00x00x00x00",
|
|
//"x00x00x00x00x00x00x00x00x00x00x00x00x00",
|
|
//"x00x00x00x00x00x00x00x00x00x00x00x00x00",
|
|
//"x00x00x00x00xx0xx0xx0xx0xx0xx0xx0xx0xx0"
|
|
|
|
if (0) {
|
|
// Print out the SID bits
|
|
char buf[200], *bp = buf;
|
|
for (unsigned f = 0; f < 4; f++) { // For each voice sub-frame
|
|
const char *fp = frame.begin() + 36 + 17 + f * 56; // RPE params start at bit 17.
|
|
const char *zb = frameMarker[f];
|
|
for (; *zb; zb++) {
|
|
if (*zb == '0') *bp++ = '0' + *fp++;
|
|
}
|
|
}
|
|
*bp = 0;
|
|
printf("SID=%s\n",buf);
|
|
}
|
|
|
|
for (unsigned f = 0; f < 4; f++) { // For each voice sub-frame
|
|
const char *fp = frame.begin() + 36 + 17 + f * 56; // RPE params start at bit 17.
|
|
const char *zb = frameMarker[f];
|
|
for (; *zb; zb++, fp++) {
|
|
if (*zb != 'x') {
|
|
if (*fp != 0) { return false; } // Not a SID frame.
|
|
}
|
|
}
|
|
}
|
|
return true; // All the non-X bits were 0 so it is a SID frame.
|
|
}
|
|
|
|
// GSM Full Rate Speech Frame GSM 6.10 1.7
|
|
struct BitPos { unsigned start; unsigned length; };
|
|
static BitPos GSMFRS_LAR_Positions[8] = { // Log Area Ratio bit positions within full rate speech frame.
|
|
{ 0, 6 },
|
|
{ 6, 6 },
|
|
{ 12, 5 },
|
|
{ 17, 5 },
|
|
{ 22, 4 },
|
|
{ 26, 4 },
|
|
{ 30, 3 },
|
|
{ 33, 3 }
|
|
};
|
|
|
|
struct GSMFRSpeechFrame {
|
|
BitVector &fr;
|
|
static const unsigned frsHeaderSize = 36;
|
|
static const unsigned frsSubFrameSize = 56;
|
|
GSMFRSpeechFrame(BitVector &other) : fr(other) {}
|
|
unsigned getLAR(unsigned n) {
|
|
assert(n >= 1 && n <= 8);
|
|
BitPos lar = GSMFRS_LAR_Positions[n-1];
|
|
return fr.peekField(lar.start,lar.length);
|
|
}
|
|
void setLAR(unsigned n, unsigned value) {
|
|
assert(n >= 1 && n <= 8);
|
|
BitPos lar = GSMFRS_LAR_Positions[n-1];
|
|
fr.fillField(lar.start,value,lar.length);
|
|
}
|
|
void setLTPLag(unsigned subFrame /*0..3*/, unsigned value) {
|
|
fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 0, value, 7);
|
|
}
|
|
void setLTPGain(unsigned subFrame, unsigned value) {
|
|
fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 7, value, 2);
|
|
}
|
|
void setRPEGridPosition(unsigned subFrame, unsigned value) {
|
|
fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 9, value, 2);
|
|
}
|
|
int getBlockAmplitude(unsigned subFrame) {
|
|
return (int) fr.peekField(frsHeaderSize + frsSubFrameSize * subFrame + 11, 6);
|
|
}
|
|
void setBlockAmplitude(unsigned subFrame, unsigned value) {
|
|
fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 11, value, 6);
|
|
}
|
|
void setLPEPulse(unsigned subFrame, unsigned pulseIndex /*1..13*/ , unsigned value) {
|
|
assert(pulseIndex >= 1 && pulseIndex <= 13);
|
|
fr.fillField(frsHeaderSize + frsSubFrameSize * subFrame + 17 + 3*(pulseIndex-1), value, 3);
|
|
}
|
|
};
|
|
|
|
// (pat 1-2014) Make the BitVector into a silence frame.
|
|
// GSM 6.11 section 6 table 1 describes the silence frame, 6.10 1.7 the bit positions.
|
|
static void createSilenceFrame(BitVector &frame)
|
|
{
|
|
GSMFRSpeechFrame sf(frame);
|
|
sf.setLAR(1,42);
|
|
sf.setLAR(2,39);
|
|
sf.setLAR(3,21);
|
|
sf.setLAR(4,10);
|
|
sf.setLAR(5,9);
|
|
sf.setLAR(6,4);
|
|
sf.setLAR(7,3);
|
|
sf.setLAR(8,2);
|
|
for (unsigned f = 0; f <= 3; f++) {
|
|
sf.setLTPGain(f,0);
|
|
sf.setLTPLag(f,40);
|
|
sf.setRPEGridPosition(f,1);
|
|
sf.setBlockAmplitude(f,0);
|
|
sf.setLPEPulse(f,1,3);
|
|
sf.setLPEPulse(f,2,4);
|
|
sf.setLPEPulse(f,3,3);
|
|
sf.setLPEPulse(f,4,4);
|
|
sf.setLPEPulse(f,5,4);
|
|
sf.setLPEPulse(f,6,3);
|
|
sf.setLPEPulse(f,7,3);
|
|
sf.setLPEPulse(f,8,3);
|
|
sf.setLPEPulse(f,9,4);
|
|
sf.setLPEPulse(f,10,4);
|
|
sf.setLPEPulse(f,11,4);
|
|
sf.setLPEPulse(f,12,3);
|
|
sf.setLPEPulse(f,13,3);
|
|
}
|
|
}
|
|
|
|
// The input vector is an argument to make testing easier; we dont have to try to cram it into mC buried in the stack.
|
|
bool TCHFRL1Decoder::decodeTCH_GSM(bool stolen,const SoftVector *wC)
|
|
{
|
|
// GSM 05.02 3.1.2, but backwards
|
|
|
|
// Simulate high FER for testing?
|
|
if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Uplink")) {
|
|
OBJLOG(DEBUG) << "simulating dropped uplink vocoder frame at " << mReadTime;
|
|
stolen = true;
|
|
}
|
|
|
|
// If the frame wasn't stolen, we'll update this with parity later.
|
|
bool good = !stolen;
|
|
|
|
// Good or bad, we will be sending *something* to the speech channel.
|
|
// Allocate it in this scope.
|
|
//unsigned char * newFrame = new unsigned char[33];
|
|
AudioFrame *newFrame = new AudioFrameRtp(TCH_FS);
|
|
|
|
if (!stolen) {
|
|
|
|
// 3.1.2.2
|
|
// decode from c[] to u[]
|
|
//mClass1_c.decode(mVCoder,mTCHU);
|
|
//wC->head(378).decode(mVCoder,mTCHU);
|
|
mVCoder.decode(wC->head(378),mTCHU);
|
|
|
|
// 3.1.2.2
|
|
// copy class 2 bits c[] to d[]
|
|
//mClass2_c.sliced().copyToSegment(mTCHD,182);
|
|
wC->segment(378,78).sliced().copyToSegment(mTCHD,182);
|
|
|
|
// 3.1.2.1
|
|
// copy class 1 bits u[] to d[]
|
|
for (unsigned k=0; k<=90; k++) {
|
|
mTCHD[2*k] = mTCHU[k];
|
|
mTCHD[2*k+1] = mTCHU[184-k];
|
|
}
|
|
|
|
// 3.1.2.1
|
|
// check parity of class 1A
|
|
unsigned sentParity = (~mTCHU.peekField(91,3)) & 0x07;
|
|
unsigned calcParity = mClass1A_d.parity(mTCHParity) & 0x07;
|
|
|
|
// 3.1.2.2
|
|
// Check the tail bits, too.
|
|
// (pat) Update: No we do not want to check the tail bits, because one of these
|
|
// being bad would cause discarding the vector.
|
|
//unsigned tail = mTCHU.peekField(185,4);
|
|
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder c[]=" << mC.begin()<<"="<< mC;
|
|
//OBJLOG(DEBUG) <<"TCHFACCHL1Decoder mclass1_c[]=" << mClass1_c.begin()<< "="<<mClass1_c;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder u[]=" << mTCHU;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder d[]=" << mTCHD;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sentParity=" << sentParity \
|
|
<< " calcParity=" << calcParity; // << " tail=" << tail;
|
|
good = (sentParity==calcParity); // && (tail==0);
|
|
if (good) {
|
|
// Undo Um's importance-sorted bit ordering.
|
|
// See GSM 05.03 3.1 and Table 2.
|
|
#if 0 // pre-pat code:
|
|
BitVector2 payload = mGsmVFrame.payload();
|
|
mTCHD.unmap(g610BitOrder,260,payload);
|
|
mGsmVFrame.pack(newFrame->begin());
|
|
// Save a copy for bad frame processing.
|
|
mGsmPrevGoodFrame.clone(mGsmVFrame);
|
|
#endif
|
|
mTCHD.unmap(g610BitOrder,260,mPrevGoodFrame); // Put the completed decoded data in mPrevGoodFrame.
|
|
newFrame->append(mPrevGoodFrame); // And copy it into the RTP audio frame.
|
|
mNumBadFrames = 0;
|
|
}
|
|
}
|
|
|
|
// We end up here for bad frames.
|
|
// We also jump here directly for stolen frames.
|
|
if (!good) {
|
|
// Bad frame processing, GSM 06.11.
|
|
// Attenuate block amplitudes and randomize grid positions.
|
|
// The spec give the bit-packing format in GSM 06.10 1.7.
|
|
// Note they start counting bits from 1, not 0.
|
|
// (pat) The first 36 bits are LAR filter reflection coefficient parameters applicable to the entire 20ms frame.
|
|
// See GSM 06.11
|
|
// These are followed by 4 sets of 56 bits of parameters for each 5ms sub-frame, consiting of:
|
|
// 7 bits: N1, LTP lag
|
|
// 2 bits: b1, LTP gain
|
|
// 2 bits: M1, RPE grid position
|
|
// 6 bits: Xmax, Block Amplitude
|
|
// 3 bits * 13: x1(0) - x1(12), RPE-pulses
|
|
// Annex 2 is Subjective relevance of speech coder bits.
|
|
// Get xmax of the final sub-frame.
|
|
// (pat) We only modify voice frames, not silence frames.
|
|
// 1-2014 We dont get SID frames because we dont yet support DTX as specified in the beacon in L3CellOptionsBCCH,
|
|
// so this code is zeroed out until we do...
|
|
if (1 || !isSIDFrame(mPrevGoodFrame)) {
|
|
mNumBadFrames++;
|
|
if (mNumBadFrames >= 32) {
|
|
createSilenceFrame(mPrevGoodFrame);
|
|
} else {
|
|
// (pat 1-2014) I changed this a little, but it did not help much.
|
|
GSMFRSpeechFrame sf(mPrevGoodFrame);
|
|
int xmax = sf.getBlockAmplitude(3); // The 'variable name' in 6.10 is 'xmax'.
|
|
// Note: previously code took the average xmax of the prevGoodFrame, not the last xmax.
|
|
// "Age" the frame.
|
|
for (unsigned f=0; f<4; f++) {
|
|
// First bad frame is an extrapolation of previous good frame, which we do by copying
|
|
// the last xmax into all locations. Subsequent bad frames are muted.
|
|
// decrement block amplitude xmax. I am lowering this faster than spec.
|
|
if (mNumBadFrames > 1) { xmax -= 4; }
|
|
if (xmax < 0) xmax = 0;
|
|
sf.setBlockAmplitude(f,xmax);
|
|
// randomize grid positions
|
|
sf.setRPEGridPosition(f,random());
|
|
if (xmax == 0) {
|
|
// Dont kill the LTP gain until xmax is 0, or it sounds cruddy. And it still sounds cruddy anyway.
|
|
// mPrevGoodFrame.fillField(36 + 7 + f*56,0,2);
|
|
createSilenceFrame(mPrevGoodFrame);
|
|
mNumBadFrames = 32; // So we dont have to do this again...
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
printf("found SID frame\n"); fflush(stdout);
|
|
}
|
|
newFrame->append(mPrevGoodFrame);
|
|
} else {
|
|
//printf("good xmax=%u\n", (unsigned) mPrevGoodFrame.peekField(36 + 11,6));
|
|
}
|
|
|
|
// Good or bad, we must feed the speech channel.
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sending" <<LOGVAR(*newFrame);
|
|
addToSpeechQ(newFrame);
|
|
return good;
|
|
}
|
|
|
|
|
|
bool TCHFRL1Decoder::decodeTCH_AFS(bool stolen, const SoftVector *wC)
|
|
{
|
|
// GSM 05.03 3.1.2, but backwards
|
|
// except for full speed AMR, which is 3.9.4
|
|
|
|
// If the frame wasn't stolen, we'll update this with parity later.
|
|
bool good = !stolen;
|
|
|
|
// Good or bad, we will be sending *something* to the speech channel.
|
|
// Allocate it in this scope.
|
|
AudioFrame *newFrame = new AudioFrameRtp(mAMRMode);
|
|
|
|
if (!stolen) {
|
|
|
|
// 3.9.4.4
|
|
// unpuncture from c[] to uc[]
|
|
SoftVector cMinus8 = wC->segment(0, wC->size() - 8); // 8 id bits
|
|
cMinus8.copyUnPunctured(mTCHUC, mPuncture, mPunctureLth);
|
|
|
|
// 3.9.4.4
|
|
// decode from uc[] to u[]
|
|
mViterbi->decode(mTCHUC,mTCHU);
|
|
|
|
// 3.9.4.3 -- class 1a bits in u[] to d[]
|
|
for (unsigned k=0; k < mClass1ALth; k++) {
|
|
mTCHD[k] = mTCHU[k];
|
|
}
|
|
|
|
// 3.9.4.3 -- class 1b bits in u[] to d[]
|
|
for (unsigned k=0; k < mClass1BLth; k++) {
|
|
mTCHD[k+mClass1ALth] = mTCHU[k+mClass1ALth+6];
|
|
}
|
|
|
|
// 3.9.4.3
|
|
// check parity of class 1A
|
|
unsigned sentParity = (~mTCHU.peekField(mClass1ALth,6)) & 0x3f;
|
|
BitVector2 class1A = mTCHU.segment(0, mClass1ALth);
|
|
unsigned calcParity = class1A.parity(mTCHParity) & 0x3f;
|
|
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder c[]=" << *wC; // Does a copy. Gotta love it.
|
|
//OBJLOG(DEBUG) <<"TCHFACCHL1Decoder uc[]=" << mTCHUC;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder u[]=" << mTCHU;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder d[]=" << mTCHD;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sentParity=" << sentParity \
|
|
<< " calcParity=" << calcParity;
|
|
|
|
good = sentParity == calcParity;
|
|
if (good) {
|
|
// Undo Um's importance-sorted bit ordering.
|
|
// See GSM 05.03 3.9.4.2 and Tables 7-14.
|
|
//BitVector2 payload = mAmrVFrame.payload();
|
|
//mTCHD.unmap(mAMRBitOrder,mKd,payload);
|
|
//mAmrVFrame.pack(newFrame->begin());
|
|
//// Save a copy for bad frame processing.
|
|
//mAmrPrevGoodFrame.clone(mAmrVFrame);
|
|
|
|
mTCHD.unmap(mAMRBitOrder,mPrevGoodFrame.size(),mPrevGoodFrame); // Put the completed decoded data in mPrevGoodFrame.
|
|
newFrame->append(mPrevGoodFrame); // And copy it into the RTP audio frame.
|
|
}
|
|
}
|
|
|
|
// We end up here for bad frames.
|
|
// We also jump here directly for stolen frames.
|
|
if (!good) {
|
|
// FIXME: This cannot be correct for AMR.
|
|
#if FIXME
|
|
// Bad frame processing, GSM 06.11.
|
|
// Attenuate block amplitudes and randomize grid positions.
|
|
// The spec give the bit-packing format in GSM 06.10 1.7.
|
|
// Note they start counting bits from 1, not 0.
|
|
int xmax = 0;
|
|
for (unsigned i=0; i<4; i++) {
|
|
xmax += mPrevGoodFrame.peekField(48+i*56-1,6);
|
|
}
|
|
xmax /= 4;
|
|
// "Age" the frame.
|
|
for (unsigned i=0; i<4; i++) {
|
|
// decrement xmax
|
|
if (xmax>0) xmax--;
|
|
mPrevGoodFrame.fillField(48+i*56-1,xmax,6);
|
|
// randomize grid positions
|
|
mPrevGoodFrame.fillField(46+i*56-1,random(),2);
|
|
}
|
|
#endif
|
|
newFrame->append(mPrevGoodFrame);
|
|
}
|
|
|
|
// Good or bad, we must feed the speech channel.
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Decoder sending" <<LOGVAR(*newFrame);
|
|
addToSpeechQ(newFrame);
|
|
return good;
|
|
}
|
|
|
|
bool TCHFRL1Decoder::decodeTCH(bool stolen, const SoftVector *wC) // result goes to sendTCHUp()
|
|
{
|
|
// Simulate high FER for testing?
|
|
if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Uplink")) {
|
|
OBJLOG(DEBUG) << "simulating dropped uplink vocoder frame at " << mReadTime;
|
|
stolen = true;
|
|
}
|
|
|
|
// (pat) Slight weirdness to avoid modifying existing GSM_FR code too much.
|
|
return (mAMRMode == TCH_FS) ? decodeTCH_GSM(stolen,wC) : decodeTCH_AFS(stolen,wC);
|
|
}
|
|
|
|
|
|
void TCHFACCHL1EncoderRoutine( TCHFACCHL1Encoder * encoder )
|
|
{
|
|
while (!gBTS.btsShutdown()) {
|
|
encoder->dispatch();
|
|
}
|
|
}
|
|
|
|
|
|
// (pat) Leaving this here as a comment.
|
|
//GSMFRL1Encoder::GSMFRL1Encoder() :
|
|
// mTCHU(189),mTCHD(260),
|
|
// mClass1_c(mC.head(378)),
|
|
// mClass1A_d(mTCHD.head(50)),
|
|
// mClass2_d(mTCHD.segment(182,78)),
|
|
// mTCHParity(0x0b,3,50)
|
|
//{
|
|
//}
|
|
|
|
|
|
void TCHFRL1Encoder::setAmrMode(AMRMode wMode)
|
|
{
|
|
assert(wMode <= TCH_FS);
|
|
mAMRMode = wMode;
|
|
int kd = gAMRKd[wMode]; // The decoded payload size.
|
|
mTCHRaw.resize(kd);
|
|
mTCHD.resize(kd);
|
|
if (wMode == TCH_FS) {
|
|
mTCHU.resize(189);
|
|
mClass1_c.dup(mC.head(378));
|
|
mClass1A_d.dup(mTCHD.head(50));
|
|
mClass2_d.dup(mTCHD.segment(182,78));
|
|
mTCHParity = Parity(0x0b,3,50);
|
|
} else {
|
|
mTCHU.resize(kd+6);
|
|
mAMRBitOrder = gAMRBitOrder[wMode];
|
|
mClass1ALth = gAMRClass1ALth[wMode];
|
|
mClass1BLth = kd - gAMRClass1ALth[wMode];
|
|
mTCHUC.resize(gAMRTCHUCLth[wMode]);
|
|
mPuncture = gAMRPuncture[wMode];
|
|
mPunctureLth = gAMRPunctureLth[wMode];
|
|
mTCHParity = Parity(0x06f,6,gAMRClass1ALth[wMode]);
|
|
setViterbi(wMode); //mViterbi = mViterbiSet.getViterbi(wMode);
|
|
}
|
|
}
|
|
|
|
|
|
// TCH_FS
|
|
TCHFACCHL1Encoder::TCHFACCHL1Encoder(
|
|
unsigned wCN,
|
|
unsigned wTN,
|
|
const TDMAMapping& wMapping,
|
|
L1FEC *wParent)
|
|
:XCCHL1Encoder(wCN, wTN, wMapping, wParent),
|
|
mPreviousFACCH(true),mOffset(0)
|
|
{
|
|
for(int k = 0; k<8; k++) {
|
|
mI[k].resize(114);
|
|
mE[k].resize(114);
|
|
// Fill with zeros just to make Valgrind happy.
|
|
mI[k].fill(0);
|
|
mE[k].fill(0);
|
|
}
|
|
}
|
|
|
|
|
|
void TCHFACCHL1Encoder::serviceStart()
|
|
{
|
|
//L1Encoder::encStart();
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder";
|
|
mEncoderThread.start((void*(*)(void*))TCHFACCHL1EncoderRoutine,(void*)this);
|
|
}
|
|
|
|
|
|
|
|
|
|
void TCHFACCHL1Encoder::encInit()
|
|
{
|
|
// There was more stuff here at one time to justify overriding the default.
|
|
// But it's gone now.
|
|
XCCHL1Encoder::encInit();
|
|
mPreviousFACCH = true;
|
|
}
|
|
|
|
|
|
void TCHFRL1Encoder::encodeTCH_GSM(const AudioFrame* aFrame)
|
|
{
|
|
assert(mTCHRaw.size() == 260 && mTCHD.size() == 260);
|
|
// GSM 05.03 3.1.2
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder";
|
|
|
|
// The incoming ByteVector is an RTP payload type 3 (GSM), which uses the standard 4 bit RTP header.
|
|
AudioFrameRtp rtpFrame(TCH_FS,aFrame);
|
|
rtpFrame.getPayload(&mTCHRaw); // Get the RTP frame payload into a BitVector2.
|
|
mTCHRaw.map(g610BitOrder,260,mTCHD);
|
|
|
|
// Reorder bits by importance.
|
|
// See GSM 05.03 3.1 and Table 2.
|
|
//mGsmVFrame.payload().map(g610BitOrder,260,mTCHD);
|
|
|
|
// 3.1.2.1 -- parity bits
|
|
BitVector2 p = mTCHU.segment(91,3);
|
|
mTCHParity.writeParityWord(mClass1A_d,p);
|
|
|
|
// 3.1.2.1 -- copy class 1 bits d[] to u[]
|
|
for (unsigned k=0; k<=90; k++) {
|
|
mTCHU[k] = mTCHD[2*k];
|
|
mTCHU[184-k] = mTCHD[2*k+1];
|
|
}
|
|
|
|
// 3.1.2.1 -- tail bits in u[]
|
|
// TODO -- This should only be needed once, in the constructor.
|
|
for (unsigned k=185; k<=188; k++) mTCHU[k]=0;
|
|
|
|
// 3.1.2.2 -- encode u[] to c[] for class 1
|
|
//mTCHU.encode(mVCoder,mClass1_c);
|
|
mVCoder.encode(mTCHU,mClass1_c);
|
|
|
|
// 3.1.2.2 -- copy class 2 d[] to c[]
|
|
mClass2_d.copyToSegment(mC,378);
|
|
|
|
// So the encoded speech frame is now in c[]
|
|
// and ready for the interleaver.
|
|
}
|
|
|
|
void TCHFRL1Encoder::encodeTCH_AFS(const AudioFrame* aFrame)
|
|
{
|
|
// We dont support SID frames.
|
|
// GSM 05.02 3.9
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH_AFS";
|
|
|
|
// Reorder bits by importance while copying speech frame to d[]
|
|
// See GSM 05.03 3.9.4.2 and Tables 7-14.
|
|
//mAmrVFrame.payload().map(mAMRBitOrder, mKd, mTCHD);
|
|
|
|
AudioFrameRtp rtpFrame(mAMRMode,aFrame);
|
|
rtpFrame.getPayload(&mTCHRaw);
|
|
mTCHRaw.map(mAMRBitOrder, mTCHD.size(), mTCHD);
|
|
|
|
// 3.9.4.3 -- class 1a bits in d[] to u[]
|
|
for (unsigned k=0; k < mClass1ALth; k++) {
|
|
mTCHU[k] = mTCHD[k];
|
|
}
|
|
|
|
// 3.9.4.3 -- parity bits from d[] to u[]
|
|
BitVector2 pFrom = mTCHD.segment(0, mClass1ALth);
|
|
BitVector2 pTo = mTCHU.segment(mClass1ALth, 6);
|
|
mTCHParity.writeParityWord(pFrom, pTo);
|
|
|
|
// 3.9.4.3 -- class 1b bits in d[] to u[]
|
|
for (unsigned k=0; k < mClass1BLth; k++) {
|
|
mTCHU[k+mClass1ALth+6] = mTCHD[k+mClass1ALth];
|
|
}
|
|
|
|
// 3.9.4.4 -- encode u[] to uc[]
|
|
mViterbi->encode(mTCHU,mTCHUC);
|
|
|
|
// 3.9.4.4 -- copy uc[] to c[] with puncturing
|
|
BitVector2 cMinus8 = mC.segment(0, mC.size()-8); // 8 id bits
|
|
mTCHUC.copyPunctured(cMinus8, mPuncture, mPunctureLth);
|
|
|
|
// So the encoded speech frame is now in c[]
|
|
// and ready for the interleaver.
|
|
// Puncturing brought the frame size to 448 bits, regardless of mode.
|
|
// TCH_AFS interleaver (3.9.4.5) is same as TCH/FS (3.1.3).
|
|
// TCH_AFS mapper (3.9.4.6) is same as TCH/FS (3.1.4).
|
|
}
|
|
|
|
void TCHFRL1Encoder::encodeTCH(const AudioFrame* aFrame)
|
|
{
|
|
// (pat) Slight weirdness to avoid modifying existing GSM_FR code.
|
|
if (mAMRMode == TCH_FS) { encodeTCH_GSM(aFrame); } else { encodeTCH_AFS(aFrame); }
|
|
}
|
|
|
|
|
|
void TCHFACCHL1Encoder::sendFrame( const L2Frame& frame )
|
|
{
|
|
OBJLOG(DEBUG) << "TCHFACCHL1Encoder " << frame;
|
|
// Simulate high FER for testing.
|
|
if (random()%100 < gConfig.getNum("Test.GSM.SimulatedFER.Downlink")) {
|
|
OBJLOG(NOTICE) << "simulating dropped downlink frame at " << mNextWriteTime;
|
|
return;
|
|
}
|
|
mL2Q.write(new L2Frame(frame));
|
|
}
|
|
|
|
|
|
|
|
void TCHFACCHL1Encoder::dispatch()
|
|
{
|
|
|
|
// No downstream? That's a problem.
|
|
assert(mDownstream);
|
|
|
|
// Get right with the system clock.
|
|
resync();
|
|
|
|
// If the channel is not active, wait for a multiframe and return.
|
|
// Most channels do not need this, becuase they are entirely data-driven
|
|
// from above. TCH/FACCH, however, must feed the interleaver on time.
|
|
if (!encActive()) {
|
|
{ ScopedLock lock(mWriteTimeLock,__FILE__,__LINE__); // (pat) Protects getNextWriteTime.
|
|
mNextWriteTime += 26;
|
|
}
|
|
gBTS.clock().wait(mNextWriteTime);
|
|
return;
|
|
}
|
|
|
|
// Let previous data get transmitted.
|
|
resync();
|
|
waitToSend();
|
|
|
|
// flag to control stealing bits
|
|
bool currentFACCH = false;
|
|
|
|
// Speech latency control.
|
|
// Since Asterisk is local, latency should be small.
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder speechQ.size=" << mSpeechQ.size();
|
|
int maxQ = gConfig.getNum("GSM.MaxSpeechLatency");
|
|
while ((int)mSpeechQ.size() > maxQ) delete mSpeechQ.read();
|
|
|
|
// Send, by priority: (1) FACCH, (2) TCH, (3) filler.
|
|
if (L2Frame *fFrame = mL2Q.readNoBlock()) {
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder FACCH " << *fFrame;
|
|
currentFACCH = true;
|
|
// Send to GSMTAP
|
|
if (gConfig.getBool("Control.GSMTAP.GSM")) {
|
|
gWriteGSMTAP(ARFCN(),TN(),mNextWriteTime.FN(),typeAndOffset(),mMapping.repeatLength()>51,false,*fFrame);
|
|
}
|
|
// Copy the L2 frame into u[] for processing.
|
|
// GSM 05.03 4.1.1.
|
|
fFrame->LSB8MSB();
|
|
fFrame->copyTo(mU);
|
|
// Encode u[] to c[], GSM 05.03 4.1.2 and 4.1.3.
|
|
encode41();
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder FACCH c[]=" << mC;
|
|
delete fFrame;
|
|
// Flush the vocoder FIFO to limit latency.
|
|
while (mSpeechQ.size()>0) delete mSpeechQ.read();
|
|
} else if (AudioFrame *tFrame = mSpeechQ.readNoBlock()) {
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH " << *tFrame;
|
|
// Encode the speech frame into c[] as per GSM 05.03 3.1.2.
|
|
encodeTCH(tFrame);
|
|
delete tFrame;
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder TCH c[]=" << mC;
|
|
} else {
|
|
// We have no ready data but must send SOMETHING.
|
|
if (!mPreviousFACCH) {
|
|
// This filler pattern was captured from a Nokia 3310, BTW.
|
|
static const BitVector2 fillerC("110100001000111100000000111001111101011100111101001111000000000000110111101111111110100110101010101010101010101010101010101010101010010000110000000000000000000000000000000000000000001101001111000000000000000000000000000000000000000000000000111010011010101010101010101010101010101010101010101001000011000000000000000000110100111100000000111001111101101000001100001101001111000000000000000000011001100000000000000000000000000000000000000000000000000000000001");
|
|
fillerC.copyTo(mC);
|
|
} else {
|
|
// FIXME -- This could be a lot more efficient.
|
|
currentFACCH = true;
|
|
L2Frame frame(L2IdleFrame());
|
|
frame.LSB8MSB();
|
|
frame.copyTo(mU);
|
|
encode41();
|
|
}
|
|
OBJLOG(DEBUG) <<"TCHFACCHL1Encoder filler FACCH=" << currentFACCH << " c[]=" << mC;
|
|
}
|
|
|
|
// Interleave c[] to i[].
|
|
interleave31(mOffset);
|
|
|
|
// randomly toggle bits in control channel bursts
|
|
// the toggle happens below, merged in with the ciphering
|
|
int p = currentFACCH ? gConfig.getFloat("GSM.Cipher.CCHBER") * (float)0xFFFFFF : 0;
|
|
|
|
// "mapping on a burst"
|
|
// Map c[] into outgoing normal bursts, marking stealing flags as needed.
|
|
// GMS 05.03 3.1.4.
|
|
for (int B=0; B<4; B++) {
|
|
// set TDMA position
|
|
mBurst.time(mNextWriteTime);
|
|
// encrypt x
|
|
if (mEncrypted == ENCRYPT_YES) {
|
|
unsigned char block1[15];
|
|
unsigned char block2[15];
|
|
unsigned char *kc = parent()->decoder()->kc();
|
|
// 03.20 C.1.2
|
|
// 05.02 3.3.2.2.1
|
|
int fn = mNextWriteTime.FN();
|
|
int t1 = fn / (26*51);
|
|
int t2 = fn % 26;
|
|
int t3 = fn % 51;
|
|
int count = (t1<<11) | (t3<<5) | t2;
|
|
if (mEncryptionAlgorithm == 1) {
|
|
A51_GSM(kc, 64, count, block1, block2);
|
|
} else if (mEncryptionAlgorithm == 3) {
|
|
A53_GSM(kc, 64, count, block1, block2);
|
|
} else {
|
|
devassert(0);
|
|
}
|
|
for (int i = 0; i < 114; i++) {
|
|
int b = p ? (random() & 0xFFFFFF) < p : 0;
|
|
b = b ^ (block1[i/8] >> (7-(i%8)));
|
|
mE[B+mOffset].settfb(i, mI[B+mOffset].bit(i) ^ (b&1));
|
|
}
|
|
} else {
|
|
if (p) {
|
|
for (int i = 0; i < 114; i++) {
|
|
int b = (random() & 0xFFFFFF) < p;
|
|
mE[B+mOffset].settfb(i, mI[B+mOffset].bit(i) ^ b);
|
|
}
|
|
} else {
|
|
// no noise and no encryption - use mI below
|
|
}
|
|
}
|
|
// copy in the bits
|
|
if (p || mEncrypted == ENCRYPT_YES) {
|
|
mE[B+mOffset].segment(0,57).copyToSegment(mBurst,3);
|
|
mE[B+mOffset].segment(57,57).copyToSegment(mBurst,88);
|
|
} else {
|
|
// no noise and no encryption - use mI
|
|
mI[B+mOffset].segment(0,57).copyToSegment(mBurst,3);
|
|
mI[B+mOffset].segment(57,57).copyToSegment(mBurst,88);
|
|
}
|
|
// stealing bits
|
|
mBurst.Hu(currentFACCH);
|
|
mBurst.Hl(mPreviousFACCH);
|
|
// send
|
|
OBJLOG(DEBUG) <<"TCHFACCHEncoder sending burst=" << mBurst;
|
|
mDownstream->writeHighSideTx(mBurst,"FACCH");
|
|
rollForward();
|
|
}
|
|
|
|
// Update the offset for the next transmission.
|
|
if (mOffset==0) mOffset=4;
|
|
else mOffset=0;
|
|
|
|
// Save the stealing flag.
|
|
mPreviousFACCH = currentFACCH;
|
|
}
|
|
|
|
|
|
|
|
void TCHFACCHL1Encoder::interleave31(int blockOffset)
|
|
{
|
|
// GSM 05.03, 3.1.3
|
|
for (int k=0; k<456; k++) {
|
|
int B = ( k + blockOffset ) % 8;
|
|
int j = 2*((49*k) % 57) + ((k%8)/4);
|
|
mI[B][j] = mC[k];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SACCHL1FEC::setPhy(const SACCHL1FEC& other)
|
|
{
|
|
mSACCHDecoder->setPhy(*other.mSACCHDecoder);
|
|
mSACCHEncoder->setPhy(*other.mSACCHEncoder);
|
|
}
|
|
|
|
void SACCHL1FEC::l1InitPhy(float RSSI, float timingError, double wTimestamp)
|
|
{
|
|
mSACCHDecoder->initPhy(RSSI,timingError,wTimestamp);
|
|
mSACCHEncoder->initPhy(RSSI,timingError);
|
|
}
|
|
|
|
void MSPhysReportInfo::sacchInit1()
|
|
{
|
|
mActualMSTiming = 0.0F;
|
|
// (pat) 6-2013: Bug fix: RSSI must be inited each time SACCH is opened, and to RSSITarget, not to 0.
|
|
mRSSI = gConfig.getNum("GSM.Radio.RSSITarget");
|
|
// The RACH was sent at full power, but full power probably depends on the MS power class. We just use a constant.
|
|
mActualMSPower = cInitialPower;
|
|
mReportCount = 0;
|
|
mTimingError = 0;
|
|
mTimestamp = 0;
|
|
}
|
|
|
|
//void MSPhysReportInfo::sacchInit2(float wRSSI, float wTimingError, double wTimestamp)
|
|
//{
|
|
// // Used to initialize L1 phy parameters.
|
|
// // This is similar to the code for the closed loop tracking,
|
|
// // except that there's no damping.
|
|
// sacchInit1(); // first make sure everything is inited.
|
|
// // Do NOT set mReportCount - we use that to tell when the first measurement report arrives.
|
|
// // FIXME: We may want to set the initial power based on the RACH power instead of just using a constant cInitialPower.
|
|
// mTimestamp=wTimestamp;
|
|
// mTimingError = wTimingError;
|
|
// mRSSI = wRSSI;
|
|
// OBJLOG(BLATHER) << "SACCHL1Encoder init" <<LOGVARM(wRSSI) <<LOGVARM(wTimingError) <<LOGVARM(wTimestamp);
|
|
//}
|
|
|
|
|
|
void SACCHL1Decoder::decInit()
|
|
{
|
|
OBJLOG(DEBUG) << "SACCHL1Decoder";
|
|
// Set initial defaults for power and timing advance.
|
|
// We know the handset sent the RACH burst at max power and 0 timing advance.
|
|
// (pat) But what is the max power? Does it depend on the MS class?
|
|
// Measured values should be set after opening with setPhy.
|
|
sacchInit1();
|
|
XCCHL1Decoder::decInit(); // (pat) maps to L1Decoder::decInit()
|
|
}
|
|
|
|
//void SACCHL1Decoder::open2(float wRSSI, float wTimingError, double wTimestamp)
|
|
//{
|
|
// OBJLOG(DEBUG) << "SACCHL1Decoder";
|
|
// // Set initial defaults for power and timing advance.
|
|
// // We know the handset sent the RACH burst at max power and 0 timing advance.
|
|
// // (pat) But what is the max power? Does it depend on the MS class?
|
|
// // Measured values should be set after opening with setPhy.
|
|
// sacchInit2(wRSSI,wTimingError,wTimestamp);
|
|
// XCCHL1Decoder::open(); // (pat) This appears to map to L1Decoder::open()
|
|
//}
|
|
|
|
|
|
|
|
void MSPhysReportInfo::initPhy(float wRSSI, float wTimingError, double wTimestamp)
|
|
{
|
|
// Used to initialize L1 phy parameters.
|
|
mRSSI=wRSSI;
|
|
mTimingError=wTimingError;
|
|
mTimestamp=wTimestamp;
|
|
OBJLOG(INFO) << "SACCHL1Decoder RSSI=" << wRSSI << " timingError=" << wTimingError << " timestamp=" << wTimestamp;
|
|
}
|
|
|
|
void MSPhysReportInfo::setPhy(const SACCHL1Decoder& other)
|
|
{
|
|
// Used to initialize a new SACCH L1 phy parameters
|
|
// from those of a preexisting established channel.
|
|
mActualMSPower = other.mActualMSPower;
|
|
mActualMSTiming = other.mActualMSTiming;
|
|
mRSSI=other.mRSSI;
|
|
mReportCount = other.mReportCount;
|
|
mTimingError=other.mTimingError;
|
|
mTimestamp=other.mTimestamp;
|
|
OBJLOG(INFO) << "SACCHL1Decoder actuals RSSI=" << mRSSI << " timingError=" << mTimingError \
|
|
<< " timestamp=" << mTimestamp \
|
|
<< " MSPower=" << mActualMSPower << " MSTiming=" << mActualMSTiming;
|
|
}
|
|
|
|
|
|
static float boundMSPower(float orderedMSPower)
|
|
{
|
|
float maxPower = gConfig.getNum("GSM.MS.Power.Max");
|
|
float minPower = gConfig.getNum("GSM.MS.Power.Min");
|
|
if (orderedMSPower>maxPower) orderedMSPower=maxPower;
|
|
else if (orderedMSPower<minPower) orderedMSPower=minPower;
|
|
return orderedMSPower;
|
|
}
|
|
|
|
void SACCHL1Encoder::setMSPower(float orderedPower)
|
|
{
|
|
mOrderedMSPower = boundMSPower(orderedPower);
|
|
}
|
|
|
|
void SACCHL1Encoder::setMSTiming(float orderedTiming)
|
|
{
|
|
mOrderedMSTiming = orderedTiming;
|
|
float maxTiming = gConfig.getNum("GSM.MS.TA.Max");
|
|
if (mOrderedMSTiming<0.0F) mOrderedMSTiming=0.0F;
|
|
else if (mOrderedMSTiming>maxTiming) mOrderedMSTiming=maxTiming;
|
|
}
|
|
|
|
|
|
void SACCHL1Encoder::initPhy(float wRSSI, float wTimingError)
|
|
{
|
|
// Used to initialize L1 phy parameters.
|
|
// This is similar to the code for the closed loop tracking, except that there's no damping.
|
|
//SACCHL1Decoder &sib = *SACCHSibling();
|
|
// RSSI
|
|
// (pat 4-2014) This is used only on channel initialization. The RSSI comes from the RACH burst which is
|
|
// always delivered at full power. So we want to ignore the actual MS power, which is not known yet.
|
|
// Arbitrarily goose the initial power up a little (10) by adjusting RSSITarget, just to make sure we get an ok initialization,
|
|
// in case the RSSI measured power was inaccurate, or there is noise in the channel.
|
|
//float RSSI = sib.getRSSI();
|
|
float RSSITarget = gConfig.getNum("GSM.Radio.RSSITarget") + 10;
|
|
float deltaP = wRSSI - RSSITarget;
|
|
//float actualPower = sib.actualMSPower(); // This is just set to cInitialPower.
|
|
//setMSPower(actualPower - deltaP);
|
|
setMSPower(cInitialPower - deltaP);
|
|
//OBJLOG(INFO) <<"SACCHL1Encoder RSSI=" << wRSSI << " target=" << RSSITarget
|
|
// << " deltaP=" << deltaP << " actual=" << actualPower << " order=" << mOrderedMSPower;
|
|
// Timing Advance
|
|
// (pat) This is called at channel init so we have not received any measurement reports from the MS yet,
|
|
// so the actualMSTiming must be the initialized value, that is, 0, unless some stray measurement report
|
|
// comes in on the channel, in which case we should ignore it.
|
|
// float timingError = sib.timingError();
|
|
// float actualTiming = sib.actualMSTiming();
|
|
// mOrderedMSTiming = actualTiming + timingError;
|
|
setMSTiming(wTimingError);
|
|
//OBJLOG(INFO) << "SACCHL1Encoder init timingError=" << sib.timingError() << " actual=" << sib.actualMSTiming() << " ordered=" << mOrderedMSTiming;
|
|
OBJLOG(INFO) << "SACCHL1Encoder init" <<LOGVARM(wTimingError) <<LOGVARM(wRSSI) <<LOGVAR(deltaP) <<LOGVARM(mOrderedMSPower);
|
|
}
|
|
|
|
const char* SACCHL1Encoder::descriptiveString() const
|
|
{
|
|
// It is a const wannabe.
|
|
if (mSacchDescriptiveString.length() == 0) {
|
|
Unconst(this)->mSacchDescriptiveString = string(L1Encoder::descriptiveString()) + "-SACCH";
|
|
}
|
|
return mSacchDescriptiveString.c_str();
|
|
}
|
|
|
|
|
|
void SACCHL1Encoder::setPhy(const SACCHL1Encoder& other)
|
|
{
|
|
// Used to initialize a new SACCH L1 phy parameters
|
|
// from those of a preexisting established channel.
|
|
mOrderedMSPower = other.mOrderedMSPower;
|
|
mOrderedMSTiming = other.mOrderedMSTiming;
|
|
OBJLOG(BLATHER) << "SACCHL1Encoder orders MSPower=" << mOrderedMSPower << " MSTiming=" << mOrderedMSTiming;
|
|
}
|
|
|
|
|
|
|
|
SACCHL1Encoder::SACCHL1Encoder(unsigned wCN, unsigned wTN, const TDMAMapping& wMapping, SACCHL1FEC *wParent)
|
|
:XCCHL1Encoder(wCN,wTN,wMapping,(L1FEC*)wParent),
|
|
mSACCHParent(wParent),
|
|
mOrderedMSPower(cInitialPower),mOrderedMSTiming(0)
|
|
{ }
|
|
|
|
|
|
void SACCHL1Encoder::encInit()
|
|
{
|
|
OBJLOG(BLATHER) <<"SACCHL1Encoder";
|
|
setMSPower(cInitialPower);
|
|
mOrderedMSTiming = 0;
|
|
XCCHL1Encoder::encInit(); // (pat 4-2014) goes to L1Encoder::encInit
|
|
}
|
|
|
|
|
|
|
|
SACCHL1Encoder* SACCHL1Decoder::SACCHSibling()
|
|
{
|
|
return mSACCHParent->encoder();
|
|
}
|
|
|
|
SACCHL1Decoder* SACCHL1Encoder::SACCHSibling()
|
|
{
|
|
return mSACCHParent->decoder();
|
|
}
|
|
|
|
|
|
|
|
void SACCHL1Encoder::sendFrame(const L2Frame& frame)
|
|
{
|
|
OBJLOG(BLATHER) << "SACCHL1Encoder " << frame;
|
|
|
|
// Physical header, GSM 04.04 6, 7.1
|
|
// Power and timing control, GSM 05.08 4, GSM 05.10 5, 6.
|
|
|
|
SACCHL1Decoder &sib = *SACCHSibling();
|
|
|
|
if (sib.isValid()) {
|
|
// Power. GSM 05.08 4.
|
|
// Power expressed in dBm, RSSI in dB wrt max.
|
|
float RSSI = sib.getRSSI();
|
|
float RSSITarget = gConfig.getNum("GSM.Radio.RSSITarget");
|
|
// (pat) RSSI and RSSITarget are both negative, so deltaP is positive if power is too high.
|
|
float deltaP = RSSI - RSSITarget;
|
|
// SNRTarget == 0 disables:
|
|
if (float SNRTarget = gConfig.getNum("GSM.Radio.SNRTarget")) {
|
|
float SNR = sib.getAveSNR();
|
|
if (deltaP > 0 && SNR < SNRTarget) { // If RSSITarget is met but SNR looks bad...
|
|
// How do we decide what the target power should be from SNR? And I dont want to call log().
|
|
// We only change upward based on SNR - we rely on RSSITarget to keep the power down.
|
|
deltaP = SNR - SNRTarget; // So how about something simple like this? eg: SNR == 10 is ok, SNR==6 would be bad, so add 4dB?
|
|
}
|
|
}
|
|
float actualPower = sib.actualMSPower();
|
|
int configPowerDamping = gConfig.getNum("GSM.MS.Power.Damping");
|
|
// Use the power damping algorithm.
|
|
float targetMSPower = actualPower - deltaP;
|
|
float powerDamping = configPowerDamping*0.01F;
|
|
if (configPowerDamping < 90 && deltaP < 4) {
|
|
// (pat 2-2014) Adjust the power in the upward direction faster than in the downward direction
|
|
// if we are in danger of losing the signal.
|
|
// This is intended to lessen signal degradation from, eg, just turning your head.
|
|
// But this may not be worth the effort. If the signal has dropped much lower than this
|
|
// we will already have lost communication with the MS so we will never get here,
|
|
// instead RSSIBumpDown will be used, although that could indirectly induce this greater jump here.
|
|
powerDamping /= 2; // This should probably be log response.
|
|
}
|
|
// (pat) Now, how fast does RSSI as seen in OpenBTS respond to changes ordered in the MS by mOrderedPower?
|
|
// Do we need the damping factor to take that into account, or should we instead wait a bit after ordering
|
|
// a power change before ordering another? I guess we rely on the powerDamping factor to handle it.
|
|
setMSPower(powerDamping*mOrderedMSPower + (1.0F-powerDamping)*targetMSPower);
|
|
OBJLOG(DEBUG) <<"SACCHL1Encoder RSSI=" << RSSI << " target=" << RSSITarget
|
|
<< " deltaP=" << deltaP << " actual=" << actualPower << " order=" << mOrderedMSPower;
|
|
// Timing. GSM 05.10 5, 6.
|
|
// Time expressed in symbol periods.
|
|
float timingError = sib.timingError();
|
|
float actualTiming = sib.actualMSTiming();
|
|
float targetMSTiming = actualTiming + timingError;
|
|
float TADamping = gConfig.getNum("GSM.MS.TA.Damping")*0.01F;
|
|
setMSTiming(TADamping*mOrderedMSTiming + (1.0F-TADamping)*targetMSTiming);
|
|
OBJLOG(DEBUG) << "SACCHL1Encoder timingError=" << timingError
|
|
<< " actualTA=" << actualTiming << " orderedTA=" << mOrderedMSTiming
|
|
<< " targetTA=" << targetMSTiming;
|
|
|
|
}
|
|
// Write physical header into mU and then call base class.
|
|
|
|
// SACCH physical header, GSM 04.04 6.1, 7.1.
|
|
mU.fillField(0,encodePower(mOrderedMSPower),8);
|
|
mU.fillField(8,(int)(mOrderedMSTiming+0.5F),8); // timing (GSM 04.04 6.1)
|
|
OBJLOG(INFO) <<"SACCHL1Encoder orders pow=" << mOrderedMSPower << " TA=" << mOrderedMSTiming << " with header " << mU.head(16);
|
|
|
|
// Encode the rest of the frame.
|
|
XCCHL1Encoder::sendFrame(frame);
|
|
}
|
|
|
|
|
|
void CBCHL1Encoder::sendFrame(const L2Frame& frame)
|
|
{
|
|
OBJLOG(DEBUG);
|
|
// Sync to (FN/51)%8==0 at the start of a new block.
|
|
if (frame.peekField(4,4)==0) {
|
|
mNextWriteTime.rollForward(mMapping.frameMapping(0),51*8);
|
|
}
|
|
// Transmit.
|
|
XCCHL1Encoder::sendFrame(frame);
|
|
}
|
|
|
|
#ifdef TESTTCHL1FEC
|
|
|
|
|
|
BitVector2 randomBitVector(int n)
|
|
{
|
|
BitVector2 t(n);
|
|
for (int i = 0; i < n; i++) t[i] = random()%2;
|
|
return t;
|
|
}
|
|
|
|
void TestTCHL1FEC()
|
|
{
|
|
const TDMAMapping hack((TypeAndOffset)0,0,0,0,0,0,0,0);
|
|
TCHFACCHL1Encoder encoder(0, 0, hack, 0);
|
|
TCHFACCHL1Decoder decoder(0, 0, hack, 0);
|
|
for (unsigned modei = 0; modei <= TCH_FS; modei++) {
|
|
int modeii = modei == 0 ? (int)TCH_FS : modei-1; // test full rate GSM first.
|
|
AMRMode mode = (AMRMode)modeii;
|
|
unsigned inSize = gAMRKd[mode];
|
|
bool ok = true;
|
|
cout <<LOGVAR(mode) <<LOGVAR(inSize) <<" ";
|
|
encoder.setAmrMode(mode);
|
|
decoder.setAmrMode(mode);
|
|
assert(encoder.getTCHPayloadSize() == inSize);
|
|
for (unsigned trial = 0; trial < 10; trial++) {
|
|
bool ok1 = true;
|
|
BitVector2 r(inSize);
|
|
if (trial == 0) { r.zero(); }
|
|
else { r = randomBitVector(inSize); }
|
|
BitVector2 pay1, pay2;
|
|
AudioFrame *aFrame = new AudioFrameRtp(mode);
|
|
aFrame->append(r);
|
|
encoder.encodeTCH(aFrame); // Leaves the result in mC
|
|
LOG(BLATHER) <<LOGVAR(encoder.mC);
|
|
SoftVector softC = encoder.mC; // Convert mC to a SoftVector.
|
|
ok1 = decoder.decodeTCH(false,&softC); // Decoder leaves result in the mSpeechQ.
|
|
if (!ok1) {
|
|
cout << LOGVAR(trial) <<" decode fail";
|
|
ok = false;
|
|
}
|
|
AudioFrame * aframe = decoder.recvTCH(); // Pulls it out of mSpeechQ.
|
|
LOG(BLATHER)<<LOGVAR(*aframe);
|
|
|
|
pay1 = r;
|
|
// Unmap the RTP frame:
|
|
AudioFrameRtp gsmFrame(mode,aframe);
|
|
pay2 = BitVector2(inSize);
|
|
gsmFrame.getPayload(&pay2);
|
|
|
|
if (pay1.size() != pay2.size()) {
|
|
cout <<LOGVAR(trial) <<" diff sizes" << endl;
|
|
ok = ok1 = false;
|
|
}
|
|
|
|
if (ok1) for (unsigned i = 0; i < pay1.size() && ok; i++) {
|
|
if (pay1.bit(i) == pay2.bit(i)) continue;
|
|
cout <<LOGVAR(trial) <<" values differ at " << i << endl;
|
|
ok = ok1 = false;
|
|
break;
|
|
}
|
|
//cout <<LOGVAR(pay1) << endl;
|
|
//cout <<LOGVAR(pay2) << endl;
|
|
if (!ok1) { ok = false; } // redundant, but make sure
|
|
if (ok1) { cout <<LOGVAR(trial) << " OK"; }
|
|
}
|
|
cout << (ok ? " OK" : " FAIL") << endl;
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
#endif // TESTTCHL1FEC
|
|
|
|
}; // namespace GSM
|
|
|
|
|
|
// vim: ts=4 sw=4
|