/* * 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