/*
 * silencedetect.cxx
 *
 * Open Phone Abstraction Library (OPAL)
 * Formally known as the Open H323 project.
 *
 * Copyright (c) 2001 Post Increment
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is Open Phone Abstraction Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Contributor(s): ______________________________________.
 *
 * $Revision: 20186 $
 * $Author: rjongbloed $
 * $Date: 2008-05-06 13:13:11 +0200 (mar 06 mai 2008) $
 */

#include <ptlib.h>

#ifdef __GNUC__
#pragma implementation "silencedetect.h"
#endif

#include <codec/silencedetect.h>
#include <opal/patch.h>

#define new PNEW


extern "C" {
  unsigned char linear2ulaw(int pcm_val);
  int ulaw2linear(unsigned char u_val);
};


ostream & operator<<(ostream & strm, OpalSilenceDetector::Mode mode)
{
  static const char * const names[OpalSilenceDetector::NumModes] = {
      "NoSilenceDetection",
      "FixedSilenceDetection",
      "AdaptiveSilenceDetection"
  };

  if (mode >= 0 && mode < OpalSilenceDetector::NumModes && names[mode] != NULL)
    strm << names[mode];
  else
    strm << "OpalSilenceDetector::Modes<" << mode << '>';
  return strm;
}


///////////////////////////////////////////////////////////////////////////////

OpalSilenceDetector::OpalSilenceDetector(const Params & theParam)
#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif
  : receiveHandler(PCREATE_NOTIFIER(ReceivedPacket))
#ifdef _MSC_VER
#pragma warning(default:4355)
#endif
{
  // Initialise the adaptive threshold variables.
  SetParameters(theParam);

  PTRACE(4, "Silence\tHandler created");
}


void OpalSilenceDetector::SetParameters(const Params & newParam)
{
  param = newParam;

  PTRACE(4, "Silence\tParameters set: "
            "mode=" << param.m_mode << ", "
            "threshold=" << param.m_threshold << ", "
            "silencedb=" << param.m_silenceDeadband << ", "
            "signaldb=" << param.m_signalDeadband << ", "
            "period=" << param.m_adaptivePeriod);
  if (param.m_mode != AdaptiveSilenceDetection) {
    levelThreshold = param.m_threshold;
    return;
  }

  // Initials threshold levels
  levelThreshold = 0;

  // Initialise the adaptive threshold variables.
  signalMinimum = UINT_MAX;
  silenceMaximum = 0;
  signalReceivedTime = 0;
  silenceReceivedTime = 0;

  // Restart in silent mode
  inTalkBurst = false;
  lastTimestamp = 0;
  receivedTime = 0;
}


OpalSilenceDetector::Mode OpalSilenceDetector::GetStatus(PBoolean * isInTalkBurst,
                                                       unsigned * currentThreshold) const
{
  if (isInTalkBurst != NULL)
    *isInTalkBurst = inTalkBurst;

  if (currentThreshold != NULL)
    *currentThreshold = ulaw2linear((BYTE)(levelThreshold ^ 0xff));

  return param.m_mode;
}


void OpalSilenceDetector::ReceivedPacket(RTP_DataFrame & frame, INT)
{
  // Already silent
  if (frame.GetPayloadSize() == 0)
    return;

  // Can never have silence if NoSilenceDetection
  if (param.m_mode == NoSilenceDetection)
    return;

  unsigned thisTimestamp = frame.GetTimestamp();
  if (lastTimestamp == 0) {
    lastTimestamp = thisTimestamp;
    return;
  }

  unsigned timeSinceLastFrame = thisTimestamp - lastTimestamp;
  lastTimestamp = thisTimestamp;

  // Can never have average signal level that high, this indicates that the
  // hardware cannot do silence detection.
  unsigned level = GetAverageSignalLevel(frame.GetPayloadPtr(), frame.GetPayloadSize());
  if (level == UINT_MAX)
    return;

  // Convert to a logarithmic scale - use uLaw which is complemented
  level = linear2ulaw(level) ^ 0xff;

  // Now if signal level above threshold we are "talking"
  PBoolean haveSignal = level > levelThreshold;

  // If no change ie still talking or still silent, resent frame counter
  if (inTalkBurst == haveSignal)
    receivedTime = 0;
  else {
    receivedTime += timeSinceLastFrame;
    // If have had enough consecutive frames talking/silent, swap modes.
    if (receivedTime >= (inTalkBurst ? param.m_silenceDeadband : param.m_signalDeadband)) {
      inTalkBurst = !inTalkBurst;
      PTRACE(4, "Silence\tDetector transition: "
             << (inTalkBurst ? "Talk" : "Silent")
             << " level=" << level << " threshold=" << levelThreshold);

      // If we had talk/silence transition restart adaptive threshold measurements
      signalMinimum = UINT_MAX;
      silenceMaximum = 0;
      signalReceivedTime = 0;
      silenceReceivedTime = 0;

      // If we just have moved to sending a talk burst, set the RTP marker
      if (inTalkBurst)
        frame.SetMarker(true);
    }
  }

  if (param.m_mode == FixedSilenceDetection) {
    if (!inTalkBurst)
      frame.SetPayloadSize(0); // Not in talk burst so silence the frame
    return;
  }

  if (levelThreshold == 0) {
    if (level > 1) {
      // Bootstrap condition, use first frame level as silence level
      levelThreshold = level/2;
      PTRACE(4, "Silence\tThreshold initialised to: " << levelThreshold);
    }
    // inTalkBurst always PFalse here, so return silent
    frame.SetPayloadSize(0);
    return;
  }

  // Count the number of silent and signal frames and calculate min/max
  if (haveSignal) {
    if (level < signalMinimum)
      signalMinimum = level;
    signalReceivedTime=signalReceivedTime+timeSinceLastFrame;
  }
  else {
    if (level > silenceMaximum)
      silenceMaximum = level;
    silenceReceivedTime=silenceReceivedTime+timeSinceLastFrame;
  }

  // See if we have had enough frames to look at proportions of silence/signal
  if ((signalReceivedTime + silenceReceivedTime) > param.m_adaptivePeriod) {

    /* Now we have had a period of time to look at some average values we can
       make some adjustments to the threshold. There are four cases:
     */
    if (signalReceivedTime >= param.m_adaptivePeriod) {
      /* If every frame was noisy, move threshold up. Don't want to move too
         fast so only go a quarter of the way to minimum signal value over the
         period. This avoids oscillations, and time will continue to make the
         level go up if there really is a lot of background noise.
       */
      int delta = (signalMinimum - levelThreshold)/4;
      if (delta != 0) {
        levelThreshold += delta;
        PTRACE(4, "Silence\tThreshold increased to: " << levelThreshold);
      }
    }
    else if (silenceReceivedTime >= param.m_adaptivePeriod) {
      /* If every frame was silent, move threshold down. Again do not want to
         move too quickly, but we do want it to move faster down than up, so
         move to halfway to maximum value of the quiet period. As a rule the
         lower the threshold the better as it would improve response time to
         the start of a talk burst.
       */
      unsigned newThreshold = (levelThreshold + silenceMaximum)/2 + 1;
      if (levelThreshold != newThreshold) {
        levelThreshold = newThreshold;
        PTRACE(4, "Silence\tThreshold decreased to: " << levelThreshold);
      }
    }
    else if (signalReceivedTime > silenceReceivedTime) {
      /* We haven't got a definitive silent or signal period, but if we are
         constantly hovering at the threshold and have more signal than
         silence we should creep up a bit.
       */
      levelThreshold++;
      PTRACE(4, "Silence\tThreshold incremented to: " << levelThreshold
             << " signal=" << signalReceivedTime << ' ' << signalMinimum
             << " silence=" << silenceReceivedTime << ' ' << silenceMaximum);
    }

    signalMinimum = UINT_MAX;
    silenceMaximum = 0;
    signalReceivedTime = 0;
    silenceReceivedTime = 0;
  }

  if (!inTalkBurst)
    frame.SetPayloadSize(0); // Not in talk burst so silence the frame
}


/////////////////////////////////////////////////////////////////////////////

unsigned OpalPCM16SilenceDetector::GetAverageSignalLevel(const BYTE * buffer, PINDEX size)
{
  // Calculate the average signal level of this frame
  int sum = 0;
  PINDEX samples = size/2;
  const short * pcm = (const short *)buffer;
  const short * end = pcm + samples;
  while (pcm != end) {
    if (*pcm < 0)
      sum -= *pcm++;
    else
      sum += *pcm++;
  }

  return sum/samples;
}


/////////////////////////////////////////////////////////////////////////////
