/*
* Video On Demand Samples
*
* Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* You may also obtain this software under a propriety license from Microchip.
* Please contact Microchip for further information.
*
*/
/*----------------------------------------------------------*/
/*! \file
* \brief This file contains the CMultiplexer class.
*/
/*----------------------------------------------------------*/
#ifndef _MULTIPLEXER_H_
#define _MULTIPLEXER_H_
//#define TS_STUFFING
//#define UDP_STREAM_TARGET_IP "127.0.0.1"
#include
#include
#include
#include
#include
#include
#include
#include "Console.h"
#include "ThreadReadHdd.h"
#include "ThreadWriteNetwork.h"
#ifdef UDP_STREAM_TARGET_IP
#include "udp-stream.h"
static bool udpStreamCreated = false;
#endif
typedef enum WriteResult_tag
{
WriteResult_Success,
WriteResult_Failed,
WriteResult_WouldBlock
} WriteResult_t;
/*----------------------------------------------------------*/
/*! \brief Class multiplexing multiple transport streams into
* a single driver output.
*/
/*----------------------------------------------------------*/
class CMultiplexer : public CStreamList
{
int m_hDriver;
char m_szDriver[64];
uint64_t m_nBytesStuffed;
uint32_t m_nStuffingCount;
uint32_t m_nWriteLen;
uint8_t *m_pBuf;
uint32_t m_nBufPos;
uint8_t m_scratchBuffer[CTsPacket::TS_PACKET_LEN];
uint8_t m_scratchBufPos;
bool m_patSent;
CThreadReadHdd *readThread;
CThreadWriteNetwork *writeThread;
#ifdef UDP_STREAM_TARGET_IP
bool useUdp;
#endif
static const char *GetErrnoString(int err)
{
switch( err )
{
case 0:
return "Nothing stored in errno";
case 1:
return "Operation not permitted";
case 2:
return "No such file or directory";
case 3:
return "No such process";
case 4:
return "Interrupted system call";
case 5:
return "I/O error";
case 6:
return "No such device or address";
case 7:
return "Argument list too long";
case 8:
return "Exec format error";
case 9:
return "Bad file number";
case 10:
return "No child processes";
case 11:
return "Try again";
case 12:
return "Out of memory";
case 13:
return "Permission denied";
case 14:
return "Bad address";
case 15:
return "Block device required";
case 16:
return "Device or resource busy";
case 17:
return "File exists";
case 18:
return "Cross-device link";
case 19:
return "No such device";
case 20:
return "Not a directory";
case 21:
return "Is a directory";
case 22:
return "Invalid argument";
case 23:
return "File table overflow";
case 24:
return "Too many open files";
case 25:
return "Not a typewriter";
case 26:
return "Text file busy";
case 27:
return "File too large";
case 28:
return "No space left on device";
case 29:
return "Illegal seek";
case 30:
return "Read-only file system";
case 31:
return "Too many links";
case 32:
return "Broken pipe";
case 33:
return "Math argument out of domain of func";
case 34:
return "Math result not representable";
default:
break;
}
return "Unknown";
}
public:
/*----------------------------------------------------------*/
/*! \brief Constructs CMultiplexer instance
*
* \param szDriver - The character device name of isochronous
* MOST device channel. (e.g. "/root/myFifo")
* minimal bandwidth.
*/
/*----------------------------------------------------------*/
CMultiplexer( const char *szDriver, uint32_t nWriteLen ) :
m_nStuffingCount( 0 ), m_nWriteLen( nWriteLen ), m_nBufPos( 0 ),
m_scratchBufPos( 0 ), m_patSent( false )
{
#ifdef UDP_STREAM_TARGET_IP
useUdp = !udpStreamCreated;
if (!udpStreamCreated)
{
udpStreamCreated = true;
UdpStream_Init( UDP_STREAM_TARGET_IP, 1234 );
}
#endif
m_pBuf = (uint8_t *)malloc( nWriteLen );
if ( NULL == m_pBuf )
{
ConsolePrintf( PRIO_ERROR, RED"! memory error in CMultiplexer"\
" constructor"RESETCOLOR"\n");
throw( -40 );
}
strncpy(m_szDriver, szDriver, sizeof(m_szDriver));
m_hDriver = open( szDriver, O_WRONLY );
if( -1 == m_hDriver )
{
int err = errno;
ConsolePrintf( PRIO_ERROR, RED"! failed to open driver: '%s', "\
"ERRNO: %d ('%s')"RESETCOLOR"\n",
szDriver, err, GetErrnoString( err ));
throw( -30 );
return;
}
readThread = new CThreadReadHdd();
readThread->AddStreamList( this );
writeThread = new CThreadWriteNetwork();
writeThread->AddMultiplexer( this );
}
/*----------------------------------------------------------*/
/*! \brief Destructs CMultiplexer instance
*/
/*----------------------------------------------------------*/
~CMultiplexer()
{
readThread->RemoveStreamList( this );
delete readThread;
writeThread->RemoveMultiplexer( this );
if( -1 != m_hDriver )
close( m_hDriver );
}
/*----------------------------------------------------------*/
/*! \brief Returns the name of the character device.
* \return The name of the device (e.g. "/dev/mdev0").
*/
/*----------------------------------------------------------*/
const char *GetDriverName( void )
{
return m_szDriver;
}
/*----------------------------------------------------------*/
/*! \brief Starts to add/change a sub-stream in the
* multiplex.
* \param MacAddr - The MAC address of the destination device.
* \param MacAddr - The full path to transport stream file.
*/
/*----------------------------------------------------------*/
void PlayStream( CMacAddr *MacAddr, CSource *pSource )
{
CStream *pStream = GetStream( MacAddr ); // is there already a stream assigned to this MAC?
if( NULL == pStream )
{
pStream = GetStream( NULL ); // find a stream object having an empty MAC
if( NULL == pStream )
{ // all streams in use?
ConsolePrintf( PRIO_ERROR, RED"too many streams open"RESETCOLOR );
return;
}
pStream->SetMacAddr( MacAddr ); // set MAC address of the stream
}
pStream->SetSource( pSource ); // select file to be streamed
pStream->SetPause( false ); // switch off pause
}
/*----------------------------------------------------------*/
/*! \brief Changes play position of a stream
* \param MacAddr - The MAC address of the destination device.
* \param nPos - The new time position of the stream.
*/
/*----------------------------------------------------------*/
void SetPos( CMacAddr *MacAddr, uint16_t nPos )
{
CStream *pStream = GetStream( MacAddr );
if( pStream )
pStream->SetPos( nPos );
}
/*----------------------------------------------------------*/
/*! \brief Pauses a stream
* \param MacAddr - The MAC address of the destination device.
* \param bPause - true, if the stream shall be paused. false,
* if the stream shall be playing.
*/
/*----------------------------------------------------------*/
void SetPause( CMacAddr *MacAddr, bool bPause )
{
CStream *pStream = GetStream( MacAddr );
if( pStream )
pStream->SetPause( bPause );
}
/*----------------------------------------------------------*/
/*! \brief Pauses a stream
* \param MacAddr - The MAC address of the destination device.
* \param bRepetition - true, if the stream shall be
* repeated infinitely, false, if the stream shall be
* stopped after reaching the end.
*/
/*----------------------------------------------------------*/
void SetRepetition( CMacAddr *MacAddr, bool bRepetition )
{
CStream *pStream = GetStream( MacAddr );
if( pStream )
pStream->SetRepetition( bRepetition );
}
/*----------------------------------------------------------*/
/*! \brief Stops a stream and releases resources
* \param MacAddr - The MAC address of the destination device.
*/
/*----------------------------------------------------------*/
void StopStream( CMacAddr *MacAddr )
{
CStream *pStream = GetStream( MacAddr );
if( pStream )
pStream->SetSource( NULL );
}
/*----------------------------------------------------------*/
/*! \brief Structure holding statistics values.
* \note Pass this structure to the GetStatistics method.
*/
/*----------------------------------------------------------*/
typedef struct
{
uint64_t bytesSent; /*! The amount of bytes sent with video and audio
* data are written into the given buffer. */
uint64_t bytesStuffed; /*! The amount of bytes sent with stuffing payload. */
uint64_t bytesRead; /*! The amount of bytes read with video and audio
* data are written into the given buffer. */
uint64_t errPcr; /*! The amount of PCR errors are written into
* the given buffer. */
uint64_t errBufUnderflow; /*! Number of buffer underflows
* occurred since last call. Values higher than zero
* are indicating that the thread reading stream data
* from files, has not enough performance */
uint64_t errBufOverflow; /*! Number of buffer overflows
* occurred since last call. Values higher than zero
* are indicating that the sending thread does not
* get away data fast enough. Reason could be that
* the MOST channel is too small, that the CPU is
* too small, ... */
uint64_t errTs; /*! The amount of TS errors are written into
* the given buffer. */
} MultiplexStats_t;
/*----------------------------------------------------------*/
/*! \brief Retrieves the statistics for the stream
* \param MacAddr - The MAC address of the destination device.
* \param pStats - Pointer to the MultiplexStats_t structure.
* This will be filled with data by this method.
*/
/*----------------------------------------------------------*/
bool GetStatistics( CMacAddr *MacAddr, MultiplexStats_t *pStats )
{
CStream *pStream = GetStream( MacAddr );
if( NULL == pStats )
return false;
memset( ( void * )pStats, 0, sizeof( MultiplexStats_t ) );
if( NULL != pStream )
{
pStats->bytesSent = pStream->GetBytesSent();
pStats->bytesRead = pStream->GetBytesRead();
pStats->errPcr = pStream->GetErrPcr();
pStats->errBufUnderflow = pStream->GetErrBufUnderflow();
pStats->errBufOverflow = pStream->GetErrBufOverflow();
pStats->errTs = pStream->GetErrTs();
}
pStats->bytesStuffed = m_nBytesStuffed;
m_nBytesStuffed = 0;
return true;
}
/*----------------------------------------------------------*/
/*! \brief sends some data if possible
* \return enumeration, holding the result of this operation
*/
/*----------------------------------------------------------*/
WriteResult_t WriteToDriver()
{
uint8_t *pBuf;
if (0 != m_scratchBufPos)
{
uint32_t tail = CTsPacket::TS_PACKET_LEN - m_scratchBufPos;
memcpy(&m_pBuf[m_nBufPos], &m_scratchBuffer[m_scratchBufPos], tail);
m_nBufPos += tail;
}
m_scratchBufPos = 0;
for( ; m_nBufPos < m_nWriteLen; m_nBufPos += CTsPacket::TS_PACKET_LEN )
{
if ( m_nBufPos + CTsPacket::TS_PACKET_LEN <= m_nWriteLen )
{
pBuf = m_pBuf + m_nBufPos; //The buffer has enough space to hold a complete transport stream packet
}
else
{
pBuf = m_scratchBuffer; //The buffer is not full, but there is a little space left, so use scratch buffer
m_scratchBufPos = m_nWriteLen - m_nBufPos;
}
if ( !m_patSent && GetPatPacket( pBuf ) )
{
m_patSent = true;
}
else if( !GetTsPacket( pBuf ) )
{
#ifdef TS_STUFFING
CTsPacket::CreateStuffing( pBuf, m_nStuffingCount++ );
m_nBytesStuffed += CTsPacket::TS_PACKET_LEN;
#else
return WriteResult_WouldBlock;
#endif
}
if (0 != m_scratchBufPos)
{
memcpy(m_pBuf + m_nBufPos, m_scratchBuffer, m_scratchBufPos);
break;
}
}
#ifdef UDP_STREAM_TARGET_IP
if (useUdp)
{
uint32_t udpSent = 0;
const uint32_t maxUdpSend = 7 * CTsPacket::TS_PACKET_LEN;
do
{
uint32_t bytesToSend = m_nWriteLen - udpSent;
if (bytesToSend > maxUdpSend)
bytesToSend = maxUdpSend;
UdpStream_Send( &m_pBuf[udpSent], bytesToSend );
udpSent += bytesToSend;
}
while (udpSent < m_nWriteLen);
}
#endif
m_nBufPos = 0;
m_patSent = false;
return ( m_nWriteLen == ( uint32_t )write( m_hDriver, m_pBuf, m_nWriteLen ) ) ? WriteResult_Success : WriteResult_Failed;
}
};
#endif