/*
 * video.cxx
 *
 * Video conferencing functions for a simple MCU
 *
 * Copyright (c) 2000 Equivalence Pty. Ltd.
 * Copyright (c) 2004 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 Portable Windows Library.
 *
 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
 *
 * Portions of this code were written by Post Increment (http://www.postincrement.com) 
 * with the assistance of funding from Citron Networks (http://www.citron.com.tw)
 *
 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
 * All Rights Reserved.
 *
 * Contributor(s): Derek J Smithies (derek@indranet.co.nz)
 *                 ------------------------------
 *
 * $Log: video.cxx,v $
 * Revision 2.2  2004/03/23 11:40:06  csoutheren
 * Fixed problem where deleting map element in-place causes crash at end of call
 * Fixed problem where referencing map by iterator rather than ID
 * Fixed code formatting problems
 *
 * Revision 2.1  2004/03/11 20:49:44  csoutheren
 * Removed warnings
 *
 * Revision 2.0  2004/03/08 02:06:24  csoutheren
 * Totally rewritten to use new connection locking mecahnism
 * Added ability to monitor conferences
 * Added initial support for H.323 MCU messages
 * Thanks to Citron Networks for supporting this work
 *
 */


#include <ptlib.h>

#ifdef _WIN32
#pragma warning(disable:4786)
#endif

#include "main.h"

#ifndef NO_MCU_VIDEO


VideoBuffer::VideoBuffer()
  : xSize(176), ySize(144)
{
  buffer = NULL;
  SetSize( xSize, ySize );
}

VideoBuffer::~VideoBuffer()
{
  delete[] buffer;
}


void VideoBuffer::WriteAll(BYTE * data, PINDEX amount)
{
  if (amount == 0)
    return;

  PWaitAndSignal mutex(videoBufferMutex);

  BYTE *yFirst, *ySecond, *u, *v, *srcYFirst, *srcYSecond, *srcU, *srcV;

  int  srcFrameSize = (amount<<1)/3;
  int  srcXSize     = (srcFrameSize == (176 * 144) ? 176 : 352 );
  int  srcYSize     = (srcFrameSize == (176 * 144) ? 144 : 288 );

  yFirst  = buffer;
  u= buffer + bufferFrameSize;
  v= buffer + bufferFrameSize + (bufferFrameSize >> 2);

  srcYFirst  = data;
  srcYSecond = srcYFirst + (xSize < srcXSize ?
           srcXSize << 1 :
    	     srcXSize);
  srcU  = data + srcFrameSize;
  srcV  = data + srcFrameSize + (srcFrameSize >> 2);

  ySecond = yFirst + (xSize < srcXSize ?
           xSize :
    	     xSize << 1);    // added down here so that any changes to
   				// yFirst are taken into account - pez
  // Special case, can fit 'in' images perfectly inside 'out' images
  if ( xSize == srcXSize ) {
    // just copy the whole buffer
    memcpy(buffer, data, amount);
  } else if ( xSize == 176 && srcXSize == 352 ) {
     // Copy 1 pixel out of 2
     int src_step   = 3 * srcXSize;
     int step       = xSize;
     int srcuv_step = (srcXSize >> 1);
     int uv_step    = 0;

     for(int i=0; i<srcYSize; i+=4) {
        for(int j=0; j<srcXSize; j+=4) {
           *(yFirst++)  = *srcYFirst;
           srcYFirst += 2;
           *(yFirst++)  = *srcYFirst;
           srcYFirst += 2;
           *(ySecond++) = *srcYSecond;
           srcYSecond += 2;
           *(ySecond++) = *srcYSecond;
           srcYSecond += 2;
           *(u++) = *srcU;
           *(v++) = *srcV;
           srcU+= 2;
           srcV+= 2;
        }
        srcYFirst  += src_step;
        srcYSecond += src_step;
        yFirst     += step;
        ySecond    += step;
        srcU += srcuv_step;
        srcV += srcuv_step;
        u    += uv_step;
        v    += uv_step;
     }
  } else {
     // Copy 2 pixels from 1
     int src_step   = srcXSize;
     int step       = 3 * xSize;
     int srcuv_step = 0;
     int uv_step    = (xSize >> 1);

     for(int i=0; i<srcYSize; i+=2) {
        for(int j=0; j<srcXSize; j++) {
           *yFirst = *srcYFirst;
           *(yFirst + xSize)  = *srcYFirst;
           yFirst++;
           *yFirst = *srcYFirst;
           *(yFirst + xSize)  = *(srcYFirst++);
           yFirst++;

           *ySecond = *srcYSecond;
           *(ySecond + xSize)  = *srcYSecond;
           ySecond++;
           *ySecond = *srcYSecond;
           *(ySecond + xSize)  = *(srcYSecond++);
           ySecond++;

           *u = *srcU;
           *(u + (xSize >> 1)) = *srcU;
           u++;
           if (j%2) srcU++;

           *v = *srcV;
           *(v + (xSize >> 1)) = *srcV;
           v++;
           if (j%2) srcV++;
        }
        srcYFirst  += src_step;
        srcYSecond += src_step;
        yFirst     += step;
        ySecond    += step;
        srcU += srcuv_step;
        srcV += srcuv_step;
        u    += uv_step;
        v    += uv_step;
     }
  }

  return;
}


//Writes data into the specified posn of the buffer.
//0 == top left, 1 == top right
//2 == bot left, 3 == bot right
void VideoBuffer::Write(BYTE * data, PINDEX amount,  PINDEX posn)
{
   // It appears that a full frame is always written.
   // We can determine the size of the frame by how much
   // is written.

   if (amount == 0)
     return;

   PWaitAndSignal mutex(videoBufferMutex);
  
   BYTE *yFirst, *ySecond, *u, *v, *srcYFirst, *srcYSecond, *srcU, *srcV;
   int  srcFrameSize = (amount<<1)/3;
   int  srcXSize     = (srcFrameSize == (176 * 144) ? 176 : 352 );
   int  srcYSize     = (srcFrameSize == (176 * 144) ? 144 : 288 );

   yFirst  = buffer;
   u= buffer + bufferFrameSize;
   v= buffer + bufferFrameSize + (bufferFrameSize >> 2);

   srcYFirst  = data;
   srcYSecond = srcYFirst + (xSize == srcXSize ?
   			     srcXSize << 1 :
			     srcXSize);
   srcU  = data + srcFrameSize;
   srcV  = data + srcFrameSize + (srcFrameSize >> 2);

   switch (posn) {
      case 0:
         break;
      case 1:
         yFirst +=(xSize >> 1);
         u +=(xSize >> 2);
         v +=(xSize >> 2);
         break;
      case 2:
         yFirst += (bufferFrameSize >> 1);
         u += (bufferFrameSize >> 3);
         v += (bufferFrameSize >> 3);
         break;
      case 3:
         yFirst += (bufferFrameSize >> 1) + (xSize >> 1);
         u += (bufferFrameSize >> 3) + (xSize >> 2);
         v += (bufferFrameSize >> 3) + (xSize >> 2);
         break;
      default: 
         return;
   }
    
   ySecond = yFirst + xSize;    // added down here so that any changes to
   				// yFirst are taken into account - pez
   // Special case, can fit 'in' images perfectly inside 'out' images
   if ( xSize == 352 && srcXSize == 176 ) {
      for(int i=0; i < 144; i+=2) {
         memcpy(yFirst, srcYFirst, 176);
	 memcpy(ySecond, srcYSecond, 176);
	 memcpy(u, srcU, 88 );
	 memcpy(v, srcV, 88 );

	 srcYFirst += 352;
	 srcYSecond += 352;
	 yFirst += 704;
	 ySecond += 704;
	 srcU += 88;
	 srcV += 88;
	 u    += 176;
	 v    += 176;
      }
   } else if ( xSize == 176 && srcXSize == 352 ) {
      // Copy 1 pixel out of 4
      int src_step   = 7 * srcXSize;
      int step       = xSize + (xSize >> 1);
      int srcuv_step = (srcXSize >> 1);
      int uv_step    = (xSize >> 2);

      for(int i=0; i<srcYSize; i+=8) {
         for(int j=0; j<srcXSize; j+=8) {
            *(yFirst++) = *srcYFirst;
            srcYFirst += 4;
            *(yFirst++) = *srcYFirst;
            srcYFirst += 4;
            *(ySecond++)= *srcYSecond;
            srcYSecond += 4;
            *(ySecond++) = *srcYSecond;
            srcYSecond += 4;
            *(u++) = *srcU;
            *(v++) = *srcV;
            srcU += 4;
            srcV += 4;
         }
         srcYFirst  += src_step;
         srcYSecond += src_step;
         yFirst     += step;
         ySecond    += step;
         srcU += srcuv_step;
         srcV += srcuv_step;
         u    += uv_step;
         v    += uv_step;
      }
   } else {
      // This code handles the other 2 cases in a generic fashion
      // Copy 1 pixel out of 2
      int src_step   = 3 * srcXSize;
      int step       = xSize + (xSize >> 1);
      int srcuv_step = (srcXSize >> 1);
      int uv_step    = (xSize >> 2);

      for(int i=0; i<srcYSize; i+=4) {
         for(int j=0; j<srcXSize; j+=4) {
            *(yFirst++)  = *srcYFirst;
            srcYFirst += 2;
             *(yFirst++)  = *srcYFirst;
            srcYFirst += 2;
             *(ySecond++) = *srcYSecond;
            srcYSecond += 2;
             *(ySecond++) = *srcYSecond;
            srcYSecond += 2;
             *(u++) = *srcU;
             *(v++) = *srcV;
            srcU+= 2;
            srcV+= 2;
         }
         srcYFirst  += src_step;
         srcYSecond += src_step;
         yFirst     += step;
         ySecond    += step;
         srcU += srcuv_step;
         srcV += srcuv_step;
         u    += uv_step;
         v    += uv_step;
      }
   }

   return;
}

void VideoBuffer::Clear(PINDEX posn)
{
   PWaitAndSignal mutex(videoBufferMutex);
  
   BYTE *yFirst, *ySecond, *u, *v;

   yFirst  = buffer;
   u= buffer + bufferFrameSize;
   v= buffer + bufferFrameSize + (bufferFrameSize/4);

   switch (posn) {
      case 0:
         break;
      case 1:
         yFirst += (xSize >> 1);
         u += (xSize >> 2);
         v += (xSize >> 2);
	 break;
      case 2:
         yFirst += (bufferFrameSize >> 1);
         u += (bufferFrameSize >> 3);
         v += (bufferFrameSize >> 3);
         break;
      case 3:
         yFirst += (bufferFrameSize >>1) + (xSize >> 1);
         u += (bufferFrameSize >> 3) + (xSize >> 2);
	 v += (bufferFrameSize >> 3) + (xSize >> 2);
	 break;
      default:
         return;
   }
   ySecond = yFirst + xSize;

   for(int y=0; y < (ySize>>1); y+=2) {
      memset(yFirst, 0x80, xSize >> 1); // Mid Grey
      memset(ySecond, 0x80, xSize >> 1); // Mid Grey
      memset(u, 0x80, xSize >> 2);
      memset(v, 0x80, xSize >> 2);
      yFirst     += xSize * 2;
      ySecond    += xSize * 2;
      u    += (xSize >> 1);
      v    += (xSize >> 1);
   }

   return;
}

void VideoBuffer::SetSize(int x, int y)
{
  PWaitAndSignal mutex(videoBufferMutex);
  if ( buffer != NULL )
    delete[] buffer;
  xSize = x;
  ySize = y;
  bufferFrameSize = xSize * ySize;
  videoBufferSize = bufferFrameSize
                    + (bufferFrameSize >> 2 )
                    + (bufferFrameSize >> 2); // Y + U + V;
  buffer = new BYTE[ videoBufferSize ];
  memset( buffer, 0x80, videoBufferSize); // Set Y, U and V to 0x80 - Mid Grey.
}


void VideoBuffer::Read(BYTE * data, PINDEX amount)
{
  if (amount == 0)
    return;
  PWaitAndSignal mutex(videoBufferMutex);
  memcpy(data,buffer,amount);
}


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

BOOL VideoDelay::Delay(int frameTime)
{
    if (firstTime)
    {
        firstTime = FALSE;
        previousTime = PTime();
        return TRUE;
    }

    error += frameTime;

    PTime now;
    PTimeInterval delay = now - previousTime;
    error -= (int)delay.GetMilliSeconds();
    previousTime = now;

    if (error > 0) {
#ifdef P_LINUX
        usleep(error * 1000);
#else
        PThread::Current()->Sleep(error);
#endif
    } else if (error <= -frameTime) {
        PThread::Current()->Sleep(frameTime);
        previousTime = PTime();
        error = 0;
    }

    return error <= -frameTime;
}

BOOL Conference::DetectNoise(const void * buffer, PINDEX amount)
{
  short *start = (short *)buffer;
  short *end   = start + (amount/2);
  int sum;
  
  sum=0;
  while (start != end) 
    if(*start<0)
      sum -= *start++;
    else
      sum += *start++;

  return (sum/amount) > 50;
}



BOOL Conference::WriteVideo(const PString & thisToken,
                                const void * buffer,
                                PINDEX amount,
                                const PString & roomID)
{
  PWaitAndSignal mutex(roomListMutex);

  // Check that the room still exists
  if (videoBufferDict.Contains(roomID) == FALSE) {
    cout << "ROOM HAS BEEN REMOVED (WriteVideo)" << endl;
    return FALSE;
  }

  VideoBuffer & videoBuffer = videoBufferDict[roomID];

    if (singleStream) {
    videoBuffer.WriteAll((BYTE *)buffer, amount);
  }
  else {
    // The last four elements of spokenList indicate the last
    // four connections from which audio was received.
    PINDEX keyIndex = FindTokensVideoPosn(thisToken,roomID);
    if (keyIndex != P_MAX_INDEX)
      videoBuffer.Write((BYTE *)buffer, amount, keyIndex);
  }

  return TRUE;
}


BOOL MyH323EndPoint::ReadVideo(const PString & /*thisToken*/,
                               void * buffer,
                               PINDEX amount,
                               const PString & roomID)
{
  PWaitAndSignal mutex(roomListMutex);

  // Check that the room still exists
  if (videoBufferDict.Contains(roomID) == FALSE) {
    cout << "ROOM HAS BEEN REMOVED (ReadVideo)" << endl;
    return FALSE;
  }

  VideoBuffer & videoBuffer = videoBufferDict[roomID];
  videoBuffer.Read((BYTE *)buffer,amount);

  return TRUE;
}


IncomingVideo::IncomingVideo(MyH323EndPoint & _ep, OpenMCUH323Connection & _conn)
  : ep(_ep), conn(_conn), width(0), height(0), frameSize(0)
{
  closed = FALSE;
}

IncomingVideo::~IncomingVideo()
{
   IncomingVideo::Close();
   PVideoChannel::Close();
}
   
BOOL IncomingVideo::Write(const void * buffer, PINDEX amount)
{
  amount = (frameSize*3) >> 1;    // frameSize==width*height
  
  PWaitAndSignal mutex( videoChanMutex );

  if (closed){
    return FALSE;
  }

  conn.OnIncomingVideo(buffer, amount);

  return TRUE;
}

void IncomingVideo::SetRenderFrameSize(int _width, int _height) 
{
   PTRACE(3,"IncomingVideo Set size"); 
   width  = _width;
   height = _height;
   frameSize = width * height;
}
      
BOOL IncomingVideo::Close()
{
   PWaitAndSignal mutex(videoChanMutex);
   closed = TRUE;

   return TRUE;
  
}

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

OutgoingVideo::OutgoingVideo(H323EndPoint & _ep, OpenMCUH323Connection & _conn, int framesPerSec, BOOL _videoLarge)
  : ep(_ep), conn(_conn), videoLarge(_videoLarge)
{
  closed = FALSE;

  if ( ( framesPerSec>0 ) && ( framesPerSec < 31 ) ) {
    msBetweenFrames= 1000/framesPerSec;
  } else {
    cerr << "Invalid video transmit frame rate. Frame rate should be between 1 and 30 frames per second"<<endl;
    msBetweenFrames = 100;
  }
}

OutgoingVideo::~OutgoingVideo()
{
   OutgoingVideo::Close();
}

BOOL OutgoingVideo::Read(void *buffer, PINDEX  amount)
{
   PWaitAndSignal mutex1(videoChanMutex);

   amount = (( videoLarge ? 352*288*3 : 176*144*3 ) >> 1);

   if (!delay.Delay(msBetweenFrames)) 
      conn.OnOutgoingVideo(buffer, amount);
  
   return TRUE;
}


BOOL OutgoingVideo::Close()
{
//  PWaitAndSignal mutex(videoChanMutex);
  closed = TRUE;
  return TRUE;  
}

#endif //NO_MCU_VIDEO



syntax highlighted by Code2HTML, v. 0.9.1