hawkeye camera support(not tested)
This commit is contained in:
401
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264bitstream.cs
Normal file
401
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264bitstream.cs
Normal file
@@ -0,0 +1,401 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/*
|
||||
* CJOCh264bitstream.cpp
|
||||
*
|
||||
* Created on: Aug 23, 2014
|
||||
* Author: Jordi Cenzano (www.jordicenzano.name)
|
||||
*/
|
||||
|
||||
/*
|
||||
* CJOCh264bitstream.h
|
||||
*
|
||||
* Created on: Aug 23, 2014
|
||||
* Author: Jordi Cenzano (www.jordicenzano.name)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
//! h264 bitstream class
|
||||
/*!
|
||||
It is used to create the h264 bit oriented stream, it contains different functions that helps you to create the h264 compliant stream (bit oriented, exp golomb coder)
|
||||
*/
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class CJOCh264bitstream : System.IDisposable
|
||||
{
|
||||
private const int BUFFER_SIZE_BITS = 24; //! Buffer size in bits used for emulation prevention
|
||||
//C++ TO C# CONVERTER NOTE: The following #define macro was replaced in-line:
|
||||
//ORIGINAL LINE: #define BUFFER_SIZE_BYTES (24/8)
|
||||
|
||||
private const int H264_EMULATION_PREVENTION_BYTE = 0x03; //! Emulation prevention byte
|
||||
|
||||
|
||||
/*! Buffer */
|
||||
private byte[] m_buffer = new byte[BUFFER_SIZE_BITS];
|
||||
|
||||
/*! Bit buffer index */
|
||||
private int m_nLastbitinbuffer;
|
||||
|
||||
/*! Starting byte indicator */
|
||||
private int m_nStartingbyte;
|
||||
|
||||
/*! Pointer to output file */
|
||||
//private FILE m_pOutFile;
|
||||
//Byte Array used for output
|
||||
private List<byte> m_pOutFile;
|
||||
|
||||
//! Clears the buffer
|
||||
private void clearbuffer()
|
||||
{
|
||||
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
|
||||
//memset(m_buffer, 0, sizeof(byte) * BUFFER_SIZE_BITS);
|
||||
System.Array.Clear(m_buffer, 0, BUFFER_SIZE_BITS);
|
||||
m_nLastbitinbuffer = 0;
|
||||
m_nStartingbyte = 0;
|
||||
}
|
||||
|
||||
//! Returns the nNumbit value (1 or 0) of lval
|
||||
/*!
|
||||
\param lval number to extract the nNumbit value
|
||||
\param nNumbit Bit position that we want to know if its 1 or 0 (from 0 to 63)
|
||||
\return bit value (1 or 0)
|
||||
*/
|
||||
private static int getbitnum(uint lval, int nNumbit)
|
||||
{
|
||||
int lrc = 0;
|
||||
|
||||
uint lmask = (uint) Math.Pow((uint)2,(uint)nNumbit);
|
||||
if ((lval & lmask) > 0)
|
||||
{
|
||||
lrc = 1;
|
||||
}
|
||||
|
||||
return lrc;
|
||||
}
|
||||
|
||||
//! Adds 1 bit to the end of h264 bitstream
|
||||
/*!
|
||||
\param nVal bit to add at the end of h264 bitstream
|
||||
*/
|
||||
private void addbittostream(int nVal)
|
||||
{
|
||||
if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS)
|
||||
{
|
||||
//Must be aligned, no need to do dobytealign();
|
||||
savebufferbyte();
|
||||
}
|
||||
|
||||
//Use circular buffer of BUFFER_SIZE_BYTES
|
||||
int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % (24 / 8);
|
||||
//The first bit to add is on the left
|
||||
int nBitPosInByte = 7 - m_nLastbitinbuffer % 8;
|
||||
|
||||
//Get the byte value from buffer
|
||||
int nValTmp = m_buffer[nBytePos];
|
||||
|
||||
//Change the bit
|
||||
if (nVal > 0)
|
||||
{
|
||||
nValTmp = (nValTmp | (int) Math.Pow(2,nBitPosInByte));
|
||||
}
|
||||
else
|
||||
{
|
||||
nValTmp = (nValTmp & ~((int) Math.Pow(2,nBitPosInByte)));
|
||||
}
|
||||
|
||||
//Save the new byte value to the buffer
|
||||
m_buffer[nBytePos] = (byte) nValTmp;
|
||||
|
||||
m_nLastbitinbuffer++;
|
||||
}
|
||||
|
||||
//! Adds 8 bit to the end of h264 bitstream (it is optimized for byte aligned situations)
|
||||
/*!
|
||||
\param nVal byte to add at the end of h264 bitstream (from 0 to 255)
|
||||
*/
|
||||
private void addbytetostream(int nVal)
|
||||
{
|
||||
if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS)
|
||||
{
|
||||
//Must be aligned, no need to do dobytealign();
|
||||
savebufferbyte();
|
||||
}
|
||||
|
||||
//Used circular buffer of BUFFER_SIZE_BYTES
|
||||
int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % (24 / 8);
|
||||
//The first bit to add is on the left
|
||||
int nBitPosInByte = 7 - m_nLastbitinbuffer % 8;
|
||||
|
||||
//Check if it is byte aligned
|
||||
if (nBitPosInByte != 7)
|
||||
{
|
||||
throw new System.Exception("Error: inserting not aligment byte");
|
||||
}
|
||||
|
||||
//Add all byte to buffer
|
||||
m_buffer[nBytePos] = (byte) nVal;
|
||||
|
||||
m_nLastbitinbuffer = m_nLastbitinbuffer + 8;
|
||||
}
|
||||
|
||||
//! Save all buffer to file
|
||||
/*!
|
||||
\param bemulationprevention Indicates if it will insert the emulation prevention byte or not (when it is needed)
|
||||
*/
|
||||
private void savebufferbyte(bool bemulationprevention = true)
|
||||
{
|
||||
bool bemulationpreventionexecuted = false;
|
||||
|
||||
if (m_pOutFile == null)
|
||||
{
|
||||
throw new System.Exception("Error: out file is NULL");
|
||||
}
|
||||
|
||||
//Check if the last bit in buffer is multiple of 8
|
||||
if ((m_nLastbitinbuffer % 8) != 0)
|
||||
{
|
||||
throw new System.Exception("Error: Save to file must be byte aligned");
|
||||
}
|
||||
|
||||
if ((m_nLastbitinbuffer / 8) <= 0)
|
||||
{
|
||||
throw new System.Exception("Error: NO bytes to save");
|
||||
}
|
||||
|
||||
if (bemulationprevention == true)
|
||||
{
|
||||
//Emulation prevention will be used:
|
||||
/*As per h.264 spec,
|
||||
rbsp_data shouldn't contain
|
||||
- 0x 00 00 00
|
||||
- 0x 00 00 01
|
||||
- 0x 00 00 02
|
||||
- 0x 00 00 03
|
||||
|
||||
rbsp_data shall be in the following way
|
||||
- 0x 00 00 03 00
|
||||
- 0x 00 00 03 01
|
||||
- 0x 00 00 03 02
|
||||
- 0x 00 00 03 03
|
||||
*/
|
||||
|
||||
//Check if emulation prevention is needed (emulation prevention is byte align defined)
|
||||
if ( (m_buffer[((m_nStartingbyte + 0) % (24 / 8))] == 0x00)
|
||||
&& (m_buffer[((m_nStartingbyte + 1) % (24 / 8))] == 0x00)
|
||||
&& ((m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x00)
|
||||
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x01)
|
||||
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x02)
|
||||
|| (m_buffer[((m_nStartingbyte + 2) % (24 / 8))] == 0x03)))
|
||||
{
|
||||
int nbuffersaved = 0;
|
||||
byte cEmulationPreventionByte = H264_EMULATION_PREVENTION_BYTE;
|
||||
|
||||
//Save 1st byte
|
||||
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
|
||||
nbuffersaved++;
|
||||
|
||||
//Save 2st byte
|
||||
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
|
||||
nbuffersaved++;
|
||||
|
||||
//Save emulation prevention byte
|
||||
fwrite(cEmulationPreventionByte, 1, 1, m_pOutFile);
|
||||
|
||||
//Save the rest of bytes (usually 1)
|
||||
while (nbuffersaved < (24 / 8))
|
||||
{
|
||||
fwrite(m_buffer[((m_nStartingbyte + nbuffersaved) % (24 / 8))], 1, 1, m_pOutFile);
|
||||
nbuffersaved++;
|
||||
}
|
||||
|
||||
//All bytes in buffer are saved, so clear the buffer
|
||||
clearbuffer();
|
||||
|
||||
bemulationpreventionexecuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bemulationpreventionexecuted == false)
|
||||
{
|
||||
//No emulation prevention was used
|
||||
|
||||
//Save the oldest byte in buffer
|
||||
fwrite(m_buffer[m_nStartingbyte], 1, 1, m_pOutFile);
|
||||
|
||||
//Move the index
|
||||
m_buffer[m_nStartingbyte] = 0;
|
||||
m_nStartingbyte++;
|
||||
m_nStartingbyte = m_nStartingbyte % (24 / 8);
|
||||
m_nLastbitinbuffer = m_nLastbitinbuffer - 8;
|
||||
}
|
||||
}
|
||||
|
||||
//! Constructor
|
||||
/*!
|
||||
\param pOutBinaryFile The output file pointer
|
||||
*/
|
||||
public CJOCh264bitstream(List<byte> pOutBinaryFile)
|
||||
{
|
||||
clearbuffer();
|
||||
|
||||
//C++ TO C# CONVERTER TODO TASK: C# does not have an equivalent to pointers to variables (in C#, the variable no longer points to the original when the original variable is re-assigned):
|
||||
//ORIGINAL LINE: m_pOutFile = pOutBinaryFile;
|
||||
m_pOutFile = pOutBinaryFile;
|
||||
}
|
||||
|
||||
//! Destructor
|
||||
public virtual void Dispose()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
//! Add 4 bytes to h264 bistream without taking into acount the emulation prevention. Used to add the NAL header to the h264 bistream
|
||||
/*!
|
||||
\param nVal The 32b value to add
|
||||
\param bDoAlign Indicates if the function will insert 0 in order to create a byte aligned stream before adding nVal 4 bytes to stream. If you try to call this function and the stream is not byte aligned an exception will be thrown
|
||||
*/
|
||||
public void add4bytesnoemulationprevention(uint nVal, bool bDoAlign = false)
|
||||
{
|
||||
//Used to add NAL header stream
|
||||
//Remember: NAL header is byte oriented
|
||||
|
||||
if (bDoAlign == true)
|
||||
{
|
||||
dobytealign();
|
||||
}
|
||||
|
||||
if ((m_nLastbitinbuffer % 8) != 0)
|
||||
{
|
||||
throw new System.Exception("Error: Save to file must be byte aligned");
|
||||
}
|
||||
|
||||
while (m_nLastbitinbuffer != 0)
|
||||
{
|
||||
savebufferbyte();
|
||||
}
|
||||
|
||||
byte cbyte = (byte)((nVal & 0xFF000000) >> 24);
|
||||
fwrite(cbyte, 1, 1, m_pOutFile);
|
||||
|
||||
cbyte = (byte)((nVal & 0x00FF0000) >> 16);
|
||||
fwrite(cbyte, 1, 1, m_pOutFile);
|
||||
|
||||
cbyte = (byte)((nVal & 0x0000FF00) >> 8);
|
||||
fwrite(cbyte, 1, 1, m_pOutFile);
|
||||
|
||||
cbyte = (byte)(nVal & 0x000000FF);
|
||||
fwrite(cbyte, 1, 1, m_pOutFile);
|
||||
}
|
||||
|
||||
//! Adds nNumbits of lval to the end of h264 bitstream
|
||||
/*!
|
||||
\param nVal value to add at the end of the h264 stream (only the LAST nNumbits will be added)
|
||||
\param nNumbits number of bits of lval that will be added to h264 stream (counting from left)
|
||||
*/
|
||||
|
||||
//Public functions
|
||||
|
||||
public void addbits(uint lval, int nNumbits)
|
||||
{
|
||||
if ((nNumbits <= 0) || (nNumbits > 64))
|
||||
{
|
||||
throw new System.Exception("Error: numbits must be between 1 ... 64");
|
||||
}
|
||||
|
||||
int nBit = 0;
|
||||
int n = nNumbits - 1;
|
||||
while (n >= 0)
|
||||
{
|
||||
nBit = getbitnum(lval, n);
|
||||
n--;
|
||||
|
||||
addbittostream(nBit);
|
||||
}
|
||||
}
|
||||
|
||||
//! Adds lval to the end of h264 bitstream using exp golomb coding for unsigned values
|
||||
/*!
|
||||
\param nVal value to add at the end of the h264 stream
|
||||
*/
|
||||
public void addexpgolombunsigned(uint lval)
|
||||
{
|
||||
//it implements unsigned exp golomb coding
|
||||
|
||||
uint lvalint = lval + 1;
|
||||
int nnumbits = (int)(Math.Log(lvalint,2) + 1);
|
||||
|
||||
for (int n = 0; n < (nnumbits - 1); n++)
|
||||
{
|
||||
addbits(0, 1);
|
||||
}
|
||||
|
||||
addbits(lvalint, nnumbits);
|
||||
}
|
||||
|
||||
//! Adds lval to the end of h264 bitstream using exp golomb coding for signed values
|
||||
/*!
|
||||
\param nVal value to add at the end of the h264 stream
|
||||
*/
|
||||
public void addexpgolombsigned(int lval)
|
||||
{
|
||||
//it implements a signed exp golomb coding
|
||||
|
||||
uint lvalint = (uint)(Math.Abs(lval) * 2 - 1);
|
||||
if (lval <= 0)
|
||||
{
|
||||
lvalint = (uint)(2 * Math.Abs(lval));
|
||||
}
|
||||
|
||||
addexpgolombunsigned(lvalint);
|
||||
}
|
||||
|
||||
//! Adds 0 to the end of h264 bistream in order to leave a byte aligned stream (It will insert seven 0 maximum)
|
||||
public void dobytealign()
|
||||
{
|
||||
//Check if the last bit in buffer is multiple of 8
|
||||
int nr = m_nLastbitinbuffer % 8;
|
||||
if ((nr % 8) != 0)
|
||||
{
|
||||
m_nLastbitinbuffer = m_nLastbitinbuffer + (8 - nr);
|
||||
}
|
||||
}
|
||||
|
||||
//! Adds cByte (8 bits) to the end of h264 bitstream. This function it is optimized in byte aligned streams.
|
||||
/*!
|
||||
\param cByte value to add at the end of the h264 stream (from 0 to 255)
|
||||
*/
|
||||
public void addbyte(byte cByte)
|
||||
{
|
||||
//Byte alignment optimization
|
||||
if ((m_nLastbitinbuffer % 8) == 0)
|
||||
{
|
||||
addbytetostream(cByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
addbits(cByte, 8);
|
||||
}
|
||||
}
|
||||
|
||||
//! Close the h264 stream saving to disk the last remaing bits in buffer
|
||||
public void close()
|
||||
{
|
||||
//Flush the data in stream buffer
|
||||
|
||||
dobytealign();
|
||||
|
||||
while (m_nLastbitinbuffer != 0)
|
||||
{
|
||||
savebufferbyte();
|
||||
}
|
||||
}
|
||||
|
||||
// 'writing' to memory
|
||||
private void fwrite(byte b, int x, int y, List<byte>data)
|
||||
{
|
||||
data.Add(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
498
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264encoder.cs
Normal file
498
framework/Inspectron.HawkEye/RTSP/Server/CJOCh264encoder.cs
Normal file
@@ -0,0 +1,498 @@
|
||||
/* * CJOCh264encoder.cpp
|
||||
*
|
||||
* Created on: Aug 17, 2014
|
||||
* Author: Jordi Cenzano (www.jordicenzano.name)
|
||||
*/
|
||||
|
||||
/*
|
||||
* CJOCh264encoder.h
|
||||
*
|
||||
* Created on: Aug 17, 2014
|
||||
* Author: Jordi Cenzano (www.jordicenzano.name)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
//C++ TO C# CONVERTER NOTE: The following #define macro was replaced in-line:
|
||||
//ORIGINAL LINE: #define BUFFER_SIZE_BYTES (24/8)
|
||||
|
||||
//! h264 encoder class
|
||||
/*!
|
||||
It is used to create the h264 compliant stream
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class CJOCh264encoder : CJOCh264bitstream
|
||||
{
|
||||
|
||||
/**
|
||||
* Allowed sample formats
|
||||
*/
|
||||
public enum enSampleFormat
|
||||
{
|
||||
SAMPLE_FORMAT_YUV420p //!< SAMPLE_FORMAT_YUV420p
|
||||
}
|
||||
|
||||
|
||||
public List<byte> m_pOutFile = null;
|
||||
public byte[] sps = null;
|
||||
public byte[] pps = null;
|
||||
public byte[] nal = null;
|
||||
|
||||
/*!Set the used Y macroblock size for I PCM in YUV420p */
|
||||
private const int MACROBLOCK_Y_WIDTH = 16;
|
||||
private const int MACROBLOCK_Y_HEIGHT = 16;
|
||||
|
||||
/*!Set time base in Hz */
|
||||
private const int TIME_SCALE_IN_HZ = 27000000;
|
||||
|
||||
/*!Pointer to pixels */
|
||||
private class YUV420p_frame_t
|
||||
{
|
||||
public byte[] pYCbCr;
|
||||
}
|
||||
|
||||
/*! Frame */
|
||||
private class frame_t
|
||||
{
|
||||
public enSampleFormat sampleformat; //!< Sample format
|
||||
public uint nYwidth; //!< Y (luminance) block width in pixels
|
||||
public uint nYheight; //!< Y (luminance) block height in pixels
|
||||
public uint nCwidth; //!< C (Crominance) block width in pixels
|
||||
public uint nCheight; //!< C (Crominance) block height in pixels
|
||||
|
||||
public uint nYmbwidth; //!< Y (luminance) macroblock width in pixels
|
||||
public uint nYmbheight; //!< Y (luminance) macroblock height in pixels
|
||||
public uint nCmbwidth; //!< Y (Crominance) macroblock width in pixels
|
||||
public uint nCmbheight; //!< Y (Crominance) macroblock height in pixels
|
||||
|
||||
public YUV420p_frame_t yuv420pframe = new YUV420p_frame_t(); //!< Pointer to current frame data
|
||||
public uint nyuv420pframesize; //!< Size in bytes of yuv420pframe
|
||||
}
|
||||
|
||||
/*! The frame var*/
|
||||
private frame_t m_frame = new frame_t();
|
||||
|
||||
/*! The frames per second var*/
|
||||
private uint m_nFps;
|
||||
|
||||
/*! Number of frames sent to the output */
|
||||
private uint m_lNumFramesAdded;
|
||||
|
||||
|
||||
|
||||
//! Frees the frame yuv420pframe allocated memory
|
||||
|
||||
//Free the allocated video frame mem
|
||||
private void free_video_src_frame()
|
||||
{
|
||||
if (m_frame.yuv420pframe.pYCbCr != null)
|
||||
{
|
||||
//C++ TO C# CONVERTER TODO TASK: The memory management function 'free' has no equivalent in C#:
|
||||
// free(m_frame.yuv420pframe.pYCbCr);
|
||||
}
|
||||
|
||||
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
|
||||
// memset(m_frame, 0, sizeof(frame_t));
|
||||
}
|
||||
|
||||
//! Allocs the frame yuv420pframe memory according to the frame properties
|
||||
|
||||
//Alloc mem to store a video frame
|
||||
private void alloc_video_src_frame()
|
||||
{
|
||||
if (m_frame.yuv420pframe.pYCbCr != null)
|
||||
{
|
||||
throw new System.Exception("Error: null values in frame");
|
||||
}
|
||||
|
||||
uint nYsize = m_frame.nYwidth * m_frame.nYheight;
|
||||
uint nCsize = m_frame.nCwidth * m_frame.nCheight;
|
||||
m_frame.nyuv420pframesize = nYsize + nCsize + nCsize;
|
||||
|
||||
m_frame.yuv420pframe.pYCbCr = new byte[m_frame.nyuv420pframesize];
|
||||
|
||||
if (m_frame.yuv420pframe.pYCbCr == null)
|
||||
{
|
||||
throw new System.Exception("Error: memory alloc");
|
||||
}
|
||||
}
|
||||
|
||||
//! Creates SPS NAL and add it to the output
|
||||
/*!
|
||||
\param nImW Frame width in pixels
|
||||
\param nImH Frame height in pixels
|
||||
\param nMbW macroblock width in pixels
|
||||
\param nMbH macroblock height in pixels
|
||||
\param nFps frames x second (tipical values are: 25, 30, 50, etc)
|
||||
\param nSARw Indicates the horizontal size of the sample aspect ratio (tipical values are:1, 4, 16, etc)
|
||||
\param nSARh Indicates the vertical size of the sample aspect ratio (tipical values are:1, 3, 9, etc)
|
||||
*/
|
||||
|
||||
//Creates and saves the NAL SPS (including VUI) (one per file)
|
||||
private void create_sps(uint nImW, uint nImH, uint nMbW, uint nMbH, uint nFps, uint nSARw, uint nSARh)
|
||||
{
|
||||
add4bytesnoemulationprevention(0x000001); // NAL header
|
||||
addbits(0x0, 1); // forbidden_bit
|
||||
addbits(0x3, 2); // nal_ref_idc
|
||||
addbits(0x7, 5); // nal_unit_type : 7 ( SPS )
|
||||
addbits(0x42, 8); // profile_idc = baseline ( 0x42 )
|
||||
addbits(0x0, 1); // constraint_set0_flag
|
||||
addbits(0x0, 1); // constraint_set1_flag
|
||||
addbits(0x0, 1); // constraint_set2_flag
|
||||
addbits(0x0, 1); // constraint_set3_flag
|
||||
addbits(0x0, 1); // constraint_set4_flag
|
||||
addbits(0x0, 1); // constraint_set5_flag
|
||||
addbits(0x0, 2); // reserved_zero_2bits /* equal to 0 */
|
||||
addbits(0x0a, 8); // level_idc: 3.1 (0x0a)
|
||||
addexpgolombunsigned(0); // seq_parameter_set_id
|
||||
addexpgolombunsigned(0); // log2_max_frame_num_minus4
|
||||
addexpgolombunsigned(0); // pic_order_cnt_type
|
||||
addexpgolombunsigned(0); // log2_max_pic_order_cnt_lsb_minus4
|
||||
addexpgolombunsigned(0); // max_num_refs_frames
|
||||
addbits(0x0, 1); // gaps_in_frame_num_value_allowed_flag
|
||||
|
||||
uint nWinMbs = nImW / nMbW;
|
||||
addexpgolombunsigned(nWinMbs - 1); // pic_width_in_mbs_minus_1
|
||||
uint nHinMbs = nImH / nMbH;
|
||||
addexpgolombunsigned(nHinMbs - 1); // pic_height_in_map_units_minus_1
|
||||
|
||||
addbits(0x1, 1); // frame_mbs_only_flag
|
||||
addbits(0x0, 1); // direct_8x8_interfernce
|
||||
addbits(0x0, 1); // frame_cropping_flag
|
||||
// addbits(0x1, 1); // vui_parameter_present
|
||||
addbits(0x0, 1); // vui_parameter_present
|
||||
|
||||
//VUI parameters (AR, timming)
|
||||
// addbits(0x1, 1); //aspect_ratio_info_present_flag
|
||||
// addbits(0xFF, 8); //aspect_ratio_idc = Extended_SAR
|
||||
|
||||
//AR
|
||||
// addbits(nSARw, 16); //sar_width
|
||||
// addbits(nSARh, 16); //sar_height
|
||||
|
||||
// addbits(0x0, 1); //overscan_info_present_flag
|
||||
// addbits(0x0, 1); //video_signal_type_present_flag
|
||||
// addbits(0x0, 1); //chroma_loc_info_present_flag
|
||||
// addbits(0x1, 1); //timing_info_present_flag
|
||||
|
||||
// uint nnum_units_in_tick = TIME_SCALE_IN_HZ / (2 * nFps);
|
||||
// addbits(nnum_units_in_tick, 32); //num_units_in_tick
|
||||
// addbits(TIME_SCALE_IN_HZ, 32); //time_scale
|
||||
// addbits(0x1, 1); //fixed_frame_rate_flag
|
||||
|
||||
// addbits(0x0, 1); //nal_hrd_parameters_present_flag
|
||||
// addbits(0x0, 1); //vcl_hrd_parameters_present_flag
|
||||
// addbits(0x0, 1); //pic_struct_present_flag
|
||||
// addbits(0x0, 1); //bitstream_restriction_flag
|
||||
//END VUI
|
||||
|
||||
//BUG? addbits(0x0, 1); // frame_mbs_only_flag
|
||||
addbits(0x1, 1); // rbsp stop bit
|
||||
|
||||
dobytealign();
|
||||
}
|
||||
|
||||
//! Creates PPS NAL and add it to the output
|
||||
|
||||
//Creates and saves the NAL PPS (one per file)
|
||||
private void create_pps()
|
||||
{
|
||||
add4bytesnoemulationprevention(0x000001); // NAL header
|
||||
addbits(0x0, 1); // forbidden_bit
|
||||
addbits(0x3, 2); // nal_ref_idc
|
||||
addbits(0x8, 5); // nal_unit_type : 8 ( PPS )
|
||||
addexpgolombunsigned(0); // pic_parameter_set_id
|
||||
addexpgolombunsigned(0); // seq_parameter_set_id
|
||||
addbits(0x0, 1); // entropy_coding_mode_flag
|
||||
addbits(0x0, 1); // bottom_field_pic_order_in frame_present_flag
|
||||
addexpgolombunsigned(0); // nun_slices_groups_minus1
|
||||
addexpgolombunsigned(0); // num_ref_idx10_default_active_minus
|
||||
addexpgolombunsigned(0); // num_ref_idx11_default_active_minus
|
||||
addbits(0x0, 1); // weighted_pred_flag
|
||||
addbits(0x0, 2); // weighted_bipred_idc
|
||||
addexpgolombsigned(0); // pic_init_qp_minus26
|
||||
addexpgolombsigned(0); // pic_init_qs_minus26
|
||||
addexpgolombsigned(0); // chroma_qp_index_offset
|
||||
addbits(0x0, 1); //deblocking_filter_present_flag
|
||||
addbits(0x0, 1); // constrained_intra_pred_flag
|
||||
addbits(0x0, 1); //redundant_pic_ent_present_flag
|
||||
addbits(0x1, 1); // rbsp stop bit
|
||||
|
||||
dobytealign();
|
||||
}
|
||||
|
||||
//! Creates Slice NAL and add it to the output
|
||||
/*!
|
||||
\param lFrameNum number of frame
|
||||
*/
|
||||
|
||||
//Creates and saves the NAL SLICE (one per frame)
|
||||
//H264 Spec Section 7.3.3 Slice Header Syntax
|
||||
private void create_slice_header(uint lFrameNum)
|
||||
{
|
||||
add4bytesnoemulationprevention(0x000001); // NAL header
|
||||
addbits(0x0, 1); // forbidden_bit
|
||||
addbits(0x3, 2); // nal_ref_idc
|
||||
addbits(0x5, 5); // nal_unit_type : 5 ( Coded slice of an IDR picture )
|
||||
addexpgolombunsigned(0); // first_mb_in_slice
|
||||
addexpgolombunsigned(7); // slice_type
|
||||
addexpgolombunsigned(0); // pic_param_set_id
|
||||
|
||||
byte cFrameNum = 0; // (byte)(lFrameNum % 16); // H264 Spec says "If the current picture is an IDR picture, frame_num shall be equal to 0. "
|
||||
// Also any maths here must relate to the value of log2_max_frame_num_minus4 in the SPS
|
||||
|
||||
addbits(cFrameNum, 4); // frame_num ( numbits = v = log2_max_frame_num_minus4 + 4)
|
||||
|
||||
// idr_pic_id range is 0..65535. All slices in the same IDR must have the same pic_id. Spec says if there are two
|
||||
// IDRs back to back they must have different idr_pic_id values
|
||||
uint lidr_pic_id = lFrameNum % 65536;
|
||||
|
||||
addexpgolombunsigned(lidr_pic_id); // idr_pic_id
|
||||
|
||||
addbits(0x0, 4); // pic_order_cnt_lsb (numbits = v = log2_max_fpic_order_cnt_lsb_minus4 + 4)
|
||||
// nal_ref_idc != 0. Insert dec_ref_pic_marking
|
||||
addbits(0x0, 1); // no_output_of_prior_pics_flag
|
||||
addbits(0x0, 1); // long_term_reference_flag
|
||||
|
||||
addexpgolombsigned(0); //slice_qp_delta
|
||||
|
||||
//Probably NOT byte aligned!!!
|
||||
}
|
||||
|
||||
//! Creates macroblock header and add it to the output
|
||||
|
||||
//Creates and saves the macroblock header(one per macroblock)
|
||||
private void create_macroblock_header()
|
||||
{
|
||||
addexpgolombunsigned(25); // mb_type (I_PCM)
|
||||
}
|
||||
|
||||
//! Creates the slice footer and add it to the output
|
||||
|
||||
//Creates and saves the SLICE footer (one per SLICE)
|
||||
private void create_slice_footer()
|
||||
{
|
||||
addbits(0x1, 1); // rbsp stop bit
|
||||
}
|
||||
|
||||
//! Creates SPS NAL and add it to the output
|
||||
/*!
|
||||
\param nYpos First vertical macroblock pixel inside the frame
|
||||
\param nYpos nXpos horizontal macroblock pixel inside the frame
|
||||
*/
|
||||
|
||||
//Creates & saves a macroblock (coded INTRA 16x16)
|
||||
private void create_macroblock(uint nYpos, uint nXpos)
|
||||
{
|
||||
uint x;
|
||||
uint y;
|
||||
|
||||
create_macroblock_header();
|
||||
|
||||
dobytealign();
|
||||
|
||||
//Y
|
||||
uint nYsize = m_frame.nYwidth * m_frame.nYheight;
|
||||
for (y = nYpos * m_frame.nYmbheight; y < (nYpos + 1) * m_frame.nYmbheight; y++)
|
||||
{
|
||||
for (x = nXpos * m_frame.nYmbwidth; x < (nXpos + 1) * m_frame.nYmbwidth; x++)
|
||||
{
|
||||
addbyte(m_frame.yuv420pframe.pYCbCr[(y * m_frame.nYwidth + x)]);
|
||||
}
|
||||
}
|
||||
|
||||
//Cb
|
||||
uint nCsize = m_frame.nCwidth * m_frame.nCheight;
|
||||
for (y = nYpos * m_frame.nCmbheight; y < (nYpos + 1) * m_frame.nCmbheight; y++)
|
||||
{
|
||||
for (x = nXpos * m_frame.nCmbwidth; x < (nXpos + 1) * m_frame.nCmbwidth; x++)
|
||||
{
|
||||
addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + (y * m_frame.nCwidth + x)]);
|
||||
}
|
||||
}
|
||||
|
||||
//Cr
|
||||
for (y = nYpos * m_frame.nCmbheight; y < (nYpos + 1) * m_frame.nCmbheight; y++)
|
||||
{
|
||||
for (x = nXpos * m_frame.nCmbwidth; x < (nXpos + 1) * m_frame.nCmbwidth; x++)
|
||||
{
|
||||
addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + nCsize + (y * m_frame.nCwidth + x)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Constructor
|
||||
/*!
|
||||
\param pOutFile The output file pointer
|
||||
*/
|
||||
|
||||
//Private functions
|
||||
|
||||
//Contructor
|
||||
public CJOCh264encoder(List<byte> pOutFile) : base(pOutFile)
|
||||
{
|
||||
m_lNumFramesAdded = 0;
|
||||
|
||||
//C++ TO C# CONVERTER TODO TASK: The memory management function 'memset' has no equivalent in C#:
|
||||
//memset(m_frame, 0, sizeof(frame_t));
|
||||
m_nFps = 25;
|
||||
|
||||
m_pOutFile = pOutFile;
|
||||
}
|
||||
|
||||
//! Destructor
|
||||
|
||||
//Destructor
|
||||
public override void Dispose()
|
||||
{
|
||||
free_video_src_frame();
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
//! Initializes the coder
|
||||
/*!
|
||||
\param nImW Frame width in pixels
|
||||
\param nImH Frame height in pixels
|
||||
\param nFps Desired frames per second of the output file (typical values are: 25, 30, 50, etc)
|
||||
\param SampleFormat Sample format if the input file. In this implementation only SAMPLE_FORMAT_YUV420p is allowed
|
||||
\param nSARw Indicates the horizontal size of the sample aspect ratio (typical values are:1, 4, 16, etc)
|
||||
\param nSARh Indicates the vertical size of the sample aspect ratio (typical values are:1, 3, 9, etc)
|
||||
*/
|
||||
|
||||
//public functions
|
||||
|
||||
//Initilizes the h264 coder (mini-coder)
|
||||
public void IniCoder(uint nImW, uint nImH, uint nImFps, CJOCh264encoder.enSampleFormat SampleFormat, uint nSARw = 1, uint nSARh = 1)
|
||||
{
|
||||
m_lNumFramesAdded = 0;
|
||||
|
||||
if (SampleFormat != enSampleFormat.SAMPLE_FORMAT_YUV420p)
|
||||
{
|
||||
throw new System.Exception("Error: SAMPLE FORMAT not allowed. Only yuv420p is allowed in this version");
|
||||
}
|
||||
|
||||
free_video_src_frame();
|
||||
|
||||
//Ini vars
|
||||
m_frame.sampleformat = SampleFormat;
|
||||
m_frame.nYwidth = nImW;
|
||||
m_frame.nYheight = nImH;
|
||||
if (SampleFormat == enSampleFormat.SAMPLE_FORMAT_YUV420p)
|
||||
{
|
||||
//Set macroblock Y size
|
||||
m_frame.nYmbwidth = MACROBLOCK_Y_WIDTH;
|
||||
m_frame.nYmbheight = MACROBLOCK_Y_HEIGHT;
|
||||
|
||||
//Set macroblock C size (in YUV420 is 1/2 of Y)
|
||||
m_frame.nCmbwidth = MACROBLOCK_Y_WIDTH / 2;
|
||||
m_frame.nCmbheight = MACROBLOCK_Y_HEIGHT / 2;
|
||||
|
||||
//Set C size
|
||||
m_frame.nCwidth = m_frame.nYwidth / 2;
|
||||
m_frame.nCheight = m_frame.nYheight / 2;
|
||||
|
||||
//In this implementation only picture sizes multiples of macroblock size (16x16) are allowed
|
||||
if (((nImW % MACROBLOCK_Y_WIDTH) != 0) || ((nImH % MACROBLOCK_Y_HEIGHT) != 0))
|
||||
{
|
||||
throw new System.Exception("Error: size not allowed. Only multiples of macroblock are allowed (macroblock size is: 16x16)");
|
||||
}
|
||||
}
|
||||
m_nFps = nImFps;
|
||||
|
||||
//Alloc mem for 1 frame
|
||||
alloc_video_src_frame();
|
||||
|
||||
//Create h264 SPS & PPS
|
||||
create_sps(m_frame.nYwidth, m_frame.nYheight, m_frame.nYmbwidth, m_frame.nYmbheight, nImFps, nSARw, nSARh);
|
||||
close(); // Flush data to the List<byte>
|
||||
sps = m_pOutFile.ToArray();
|
||||
m_pOutFile.Clear();
|
||||
|
||||
create_pps();
|
||||
close(); // Flush data to the List<byte>
|
||||
pps = m_pOutFile.ToArray();
|
||||
m_pOutFile.Clear();
|
||||
}
|
||||
|
||||
//! Returns the frame pointer
|
||||
/*!
|
||||
\return Frame pointer ready to fill with frame pixels data (the format to fill the data is indicated by SampleFormat parameter when the coder is initialized
|
||||
*/
|
||||
|
||||
//Returns the frame pointer to load the video frame
|
||||
public byte[] GetFramePtr()
|
||||
{
|
||||
if (m_frame.yuv420pframe.pYCbCr == null)
|
||||
{
|
||||
throw new System.Exception("Error: video frame is null (not initialized)");
|
||||
}
|
||||
|
||||
return m_frame.yuv420pframe.pYCbCr;
|
||||
}
|
||||
|
||||
//! Returns the allocated frame memory in bytes
|
||||
/*!
|
||||
\return The allocated memory to store the frame data
|
||||
*/
|
||||
|
||||
//Returns the the allocated size for video frame
|
||||
public uint GetFrameSize()
|
||||
{
|
||||
return m_frame.nyuv420pframesize;
|
||||
}
|
||||
|
||||
//! It codes the frame that is in frame memory a it saves the coded data to disc
|
||||
|
||||
//Codifies & save the video frame (it only uses 16x16 intra PCM -> NO COMPRESSION!)
|
||||
public void CodeAndSaveFrame()
|
||||
{
|
||||
m_pOutFile.Clear();
|
||||
|
||||
//The slice header is not byte aligned, so the first macroblock header is not byte aligned
|
||||
create_slice_header(m_lNumFramesAdded);
|
||||
|
||||
//Loop over macroblock size
|
||||
uint y;
|
||||
uint x;
|
||||
for (y = 0; y < m_frame.nYheight / m_frame.nYmbheight; y++)
|
||||
{
|
||||
for (x = 0; x < m_frame.nYwidth / m_frame.nYmbwidth; x++)
|
||||
{
|
||||
create_macroblock(y, x);
|
||||
}
|
||||
}
|
||||
|
||||
create_slice_footer();
|
||||
dobytealign();
|
||||
|
||||
m_lNumFramesAdded++;
|
||||
|
||||
// flush
|
||||
close();
|
||||
nal = m_pOutFile.ToArray();
|
||||
}
|
||||
|
||||
//! Returns number of coded frames
|
||||
/*!
|
||||
\return The number of coded frames
|
||||
*/
|
||||
|
||||
//Returns the number of codified frames
|
||||
public uint GetSavedFrames()
|
||||
{
|
||||
return m_lNumFramesAdded;
|
||||
}
|
||||
|
||||
//! Flush all data and save the trailing bits
|
||||
|
||||
//Closes the h264 coder saving the last bits in the buffer
|
||||
public void CloseCoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
34
framework/Inspectron.HawkEye/RTSP/Server/RTPPacketUtil.cs
Normal file
34
framework/Inspectron.HawkEye/RTSP/Server/RTPPacketUtil.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public static class RTPPacketUtil
|
||||
{
|
||||
|
||||
public static void WriteHeader(byte[] rtp_packet, int rtp_version, int rtp_padding, int rtp_extension, int rtp_csrc_count, int rtp_marker, int rtp_payload_type)
|
||||
{
|
||||
rtp_packet[0] = (byte)((rtp_version << 6) | (rtp_padding << 5) | (rtp_extension << 4) | rtp_csrc_count);
|
||||
rtp_packet[1] = (byte)((rtp_marker << 7) | (rtp_payload_type & 0x7F));
|
||||
}
|
||||
|
||||
public static void WriteSequenceNumber(byte[] rtp_packet, uint empty_sequence_id)
|
||||
{
|
||||
rtp_packet[2] = ((byte)((empty_sequence_id >> 8) & 0xFF));
|
||||
rtp_packet[3] = ((byte)((empty_sequence_id >> 0) & 0xFF));
|
||||
}
|
||||
|
||||
public static void WriteTS(byte[] rtp_packet, uint ts)
|
||||
{
|
||||
rtp_packet[4] = ((byte)((ts >> 24) & 0xFF));
|
||||
rtp_packet[5] = ((byte)((ts >> 16) & 0xFF));
|
||||
rtp_packet[6] = ((byte)((ts >> 8) & 0xFF));
|
||||
rtp_packet[7] = ((byte)((ts >> 0) & 0xFF));
|
||||
}
|
||||
|
||||
public static void WriteSSRC(byte[] rtp_packet, uint ssrc)
|
||||
{
|
||||
rtp_packet[8] = ((byte)((ssrc >> 24) & 0xFF));
|
||||
rtp_packet[9] = ((byte)((ssrc >> 16) & 0xFF));
|
||||
rtp_packet[10] = ((byte)((ssrc >> 8) & 0xFF));
|
||||
rtp_packet[11] = ((byte)((ssrc >> 0) & 0xFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal file
800
framework/Inspectron.HawkEye/RTSP/Server/RtspServer.cs
Normal file
@@ -0,0 +1,800 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Inspectron.HawkEye.RTSP.Messages;
|
||||
|
||||
// RTSP Server Example (c) Roger Hardiman, 2016, 2018
|
||||
// Released uder the MIT Open Source Licence
|
||||
//
|
||||
// Re-uses some code from the Multiplexer example of SharpRTSP
|
||||
//
|
||||
// This example simulates a live RTSP video stream, for example a CCTV Camera
|
||||
// It creates a Video Source (a test card) that creates a YUV Image
|
||||
// The image is then encoded as H264 data using a very basic H264 Encoder
|
||||
// The H264 data (the NALs) are sent to the RTSP clients
|
||||
// Video is sent in UDP Mode or TCP Mode (ie RTP over RTSP mode)
|
||||
|
||||
// The Tiny H264 Encoder is a 100% .NET encoder which is lossless and creates large bitstreams as
|
||||
// there is no compression. It is limited to 128x96 resolution. However it makes it easy to write a quick
|
||||
// demo without needing native APIs or cross compiled C libraries for H264
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class RtspServer : IDisposable
|
||||
{
|
||||
const int h264_width = 192; // Tiny needs 128x96
|
||||
const int h264_height = 128;
|
||||
const int h264_fps = 25;
|
||||
|
||||
const uint global_ssrc = 0x4321FADE; // 8 hex digits
|
||||
|
||||
private TcpListener _RTSPServerListener;
|
||||
private ManualResetEvent _Stopping;
|
||||
private Thread _ListenTread;
|
||||
|
||||
private TestCard video_source = null;
|
||||
private SimpleH264Encoder h264_encoder = null;
|
||||
//private TinyH264Encoder h264_encoder = null;
|
||||
|
||||
byte[] raw_sps = null;
|
||||
byte[] raw_pps = null;
|
||||
|
||||
List<RTSPConnection> rtsp_list = new List<RTSPConnection>(); // list of RTSP Listeners
|
||||
|
||||
Random rnd = new Random();
|
||||
int session_handle = 1;
|
||||
|
||||
Authentication auth = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RTSPServer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="aPortNumber">A numero port.</param>
|
||||
/// <param name="username">username.</param>
|
||||
/// <param name="password">password.</param>
|
||||
public RtspServer(int portNumber, String username, String password)
|
||||
{
|
||||
if (portNumber < System.Net.IPEndPoint.MinPort || portNumber > System.Net.IPEndPoint.MaxPort)
|
||||
throw new ArgumentOutOfRangeException("aPortNumber", portNumber, "Port number must be between System.Net.IPEndPoint.MinPort and System.Net.IPEndPoint.MaxPort");
|
||||
Contract.EndContractBlock();
|
||||
|
||||
if (String.IsNullOrEmpty(username) == false
|
||||
&& String.IsNullOrEmpty(password) == false) {
|
||||
String realm = "SharpRTSPServer";
|
||||
auth = new Authentication(username,password,realm,Authentication.Type.Digest);
|
||||
} else {
|
||||
auth = null;
|
||||
}
|
||||
|
||||
RtspUtils.RegisterUri();
|
||||
_RTSPServerListener = new TcpListener(IPAddress.Any, portNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the listen.
|
||||
/// </summary>
|
||||
public void StartListen()
|
||||
{
|
||||
_RTSPServerListener.Start();
|
||||
|
||||
_Stopping = new ManualResetEvent(false);
|
||||
_ListenTread = new Thread(new ThreadStart(AcceptConnection));
|
||||
_ListenTread.Start();
|
||||
|
||||
// Initialise the H264 encoder
|
||||
h264_encoder = new SimpleH264Encoder(h264_width, h264_height, h264_fps);
|
||||
//h264_encoder = new TinyH264Encoder(); // hard coded to 192x128
|
||||
|
||||
// Start the VideoSource
|
||||
video_source = new TestCard(h264_width, h264_height, h264_fps);
|
||||
video_source.ReceivedYUVFrame += video_source_ReceivedYUVFrame;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Accepts the connection.
|
||||
/// </summary>
|
||||
private void AcceptConnection()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!_Stopping.WaitOne(0))
|
||||
{
|
||||
// Wait for an incoming TCP Connection
|
||||
TcpClient oneClient = _RTSPServerListener.AcceptTcpClient();
|
||||
Console.WriteLine("Connection from " + oneClient.Client.RemoteEndPoint.ToString());
|
||||
|
||||
// Hand the incoming TCP connection over to the RTSP classes
|
||||
var rtsp_socket = new RtspTcpTransport(oneClient);
|
||||
RtspListener newListener = new RtspListener(rtsp_socket);
|
||||
newListener.MessageReceived += RTSP_Message_Received;
|
||||
//RTSPDispatcher.Instance.AddListener(newListener);
|
||||
|
||||
// Add the RtspListener to the RTSPConnections List
|
||||
lock (rtsp_list) {
|
||||
RTSPConnection new_connection = new RTSPConnection();
|
||||
new_connection.listener = newListener;
|
||||
new_connection.client_hostname = newListener.RemoteAdress.Split(':')[0];
|
||||
new_connection.ssrc = global_ssrc;
|
||||
|
||||
new_connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
|
||||
new_connection.video_time_since_last_rtcp_keepalive = DateTime.UtcNow;
|
||||
|
||||
rtsp_list.Add(new_connection);
|
||||
}
|
||||
|
||||
newListener.Start();
|
||||
}
|
||||
}
|
||||
catch (SocketException error)
|
||||
{
|
||||
// _logger.Warn("Got an error listening, I have to handle the stopping which also throw an error", error);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
// _logger.Error("Got an error listening...", error);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void StopListen()
|
||||
{
|
||||
_RTSPServerListener.Stop();
|
||||
_Stopping.Set();
|
||||
_ListenTread.Join();
|
||||
}
|
||||
|
||||
#region IDisposable Membres
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
StopListen();
|
||||
_Stopping.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Process each RTSP message that is received
|
||||
private void RTSP_Message_Received(object sender, RtspChunkEventArgs e)
|
||||
{
|
||||
// Cast the 'sender' and 'e' into the RTSP Listener (the Socket) and the RTSP Message
|
||||
RtspListener listener = sender as RtspListener;
|
||||
RtspMessage message = e.Message as RtspMessage;
|
||||
|
||||
Console.WriteLine("RTSP message received " + message);
|
||||
|
||||
|
||||
// Check if the RTSP Message has valid authentication (validating against username,password,realm and nonce)
|
||||
if (auth != null) {
|
||||
bool authorized = false;
|
||||
if (message.Headers.ContainsKey("Authorization") == true ) {
|
||||
// The Header contained Authorization
|
||||
// Check the message has the correct Authorization
|
||||
// If it does not have the correct Authorization then close the RTSP connection
|
||||
authorized = auth.IsValid(message);
|
||||
|
||||
if (authorized == false) {
|
||||
// Send a 401 Authentication Failed reply, then close the RTSP Socket
|
||||
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
|
||||
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader());
|
||||
authorization_response.ReturnCode = 401;
|
||||
listener.SendMessage(authorization_response);
|
||||
|
||||
lock (rtsp_list) {
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()){
|
||||
if (connection.listener == listener) {
|
||||
rtsp_list.Remove(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.Dispose();
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
if ((message.Headers.ContainsKey("Authorization") == false)){
|
||||
// Send a 401 Authentication Failed with extra info in WWW-Authenticate
|
||||
// to tell the Client if we are using Basic or Digest Authentication
|
||||
RtspResponse authorization_response = (e.Message as RtspRequest).CreateResponse();
|
||||
authorization_response.AddHeader("WWW-Authenticate: " + auth.GetHeader()); // 'Basic' or 'Digest'
|
||||
authorization_response.ReturnCode = 401;
|
||||
listener.SendMessage(authorization_response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the RTSP Keepalive Timeout
|
||||
// We could check that the message is GET_PARAMETER or OPTIONS for a keepalive but instead we will update the timer on any message
|
||||
lock (rtsp_list)
|
||||
{
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress))
|
||||
{
|
||||
// found the connection
|
||||
connection.time_since_last_rtsp_keepalive = DateTime.UtcNow;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle OPTIONS message
|
||||
if (message is RtspRequestOptions)
|
||||
{
|
||||
// Create the reponse to OPTIONS
|
||||
RtspResponse options_response = (e.Message as RtspRequestOptions).CreateResponse();
|
||||
listener.SendMessage(options_response);
|
||||
}
|
||||
|
||||
// Handle DESCRIBE message
|
||||
if (message is RtspRequestDescribe)
|
||||
{
|
||||
String requested_url = (message as RtspRequestDescribe).RtspUri.ToString();
|
||||
Console.WriteLine("Request for " + requested_url);
|
||||
|
||||
// TODO. Check the requsted_url is valid. In this example we accept any RTSP URL
|
||||
|
||||
// Make the Base64 SPS and PPS
|
||||
raw_sps = h264_encoder.GetRawSPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
|
||||
raw_pps = h264_encoder.GetRawPPS(); // no 0x00 0x00 0x00 0x01 or 32 bit size header
|
||||
String sps_str = Convert.ToBase64String(raw_sps);
|
||||
String pps_str = Convert.ToBase64String(raw_pps);
|
||||
|
||||
StringBuilder sdp = new StringBuilder();
|
||||
|
||||
// Generate the SDP
|
||||
// The sprop-parameter-sets provide the SPS and PPS for H264 video
|
||||
// The packetization-mode defines the H264 over RTP payloads used but is Optional
|
||||
sdp.Append("v=0\n");
|
||||
sdp.Append("o=user 123 0 IN IP4 0.0.0.0\n");
|
||||
sdp.Append("s=SharpRTSP Test Camera\n");
|
||||
sdp.Append("m=video 0 RTP/AVP 96\n");
|
||||
sdp.Append("c=IN IP4 0.0.0.0\n");
|
||||
sdp.Append("a=control:trackID=0\n");
|
||||
sdp.Append("a=rtpmap:96 H264/90000\n");
|
||||
sdp.Append("a=fmtp:96 profile-level-id=42A01E; sprop-parameter-sets=" + sps_str + "," + pps_str + ";\n");
|
||||
|
||||
byte[] sdp_bytes = Encoding.ASCII.GetBytes(sdp.ToString());
|
||||
|
||||
// Create the reponse to DESCRIBE
|
||||
// This must include the Session Description Protocol (SDP)
|
||||
RtspResponse describe_response = (e.Message as RtspRequestDescribe).CreateResponse();
|
||||
|
||||
describe_response.AddHeader("Content-Base: " + requested_url);
|
||||
describe_response.AddHeader("Content-Type: application/sdp");
|
||||
describe_response.Data = sdp_bytes;
|
||||
describe_response.AdjustContentLength();
|
||||
listener.SendMessage(describe_response);
|
||||
}
|
||||
|
||||
// Handle SETUP message
|
||||
if (message is RtspRequestSetup)
|
||||
{
|
||||
|
||||
//
|
||||
var setupMessage = message as RtspRequestSetup;
|
||||
|
||||
// Check the RTSP transport
|
||||
// If it is UDP or Multicast, create the sockets
|
||||
// If it is RTP over RTSP we send data via the RTSP Listener
|
||||
|
||||
// FIXME client may send more than one possible transport.
|
||||
// very rare
|
||||
RtspTransport transport = setupMessage.GetTransports()[0];
|
||||
|
||||
|
||||
// Construct the Transport: reply from the Server to the client
|
||||
RtspTransport transport_reply = new RtspTransport();
|
||||
transport_reply.SSrc = global_ssrc.ToString("X8"); // Convert to Hex, padded to 8 characters
|
||||
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.TCP)
|
||||
{
|
||||
// RTP over RTSP mode}
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.TCP;
|
||||
transport_reply.Interleaved = new PortCouple(transport.Interleaved.First, transport.Interleaved.Second);
|
||||
}
|
||||
|
||||
UDPSocket udp_pair = null;
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& transport.IsMulticast == false)
|
||||
{
|
||||
Boolean udp_supported = true;
|
||||
if (udp_supported) {
|
||||
// RTP over UDP mode
|
||||
// Create a pair of UDP sockets - One is for the Video, one is for the RTCP
|
||||
udp_pair = new UDPSocket(50000, 51000); // give a range of 500 pairs (1000 addresses) to try incase some address are in use
|
||||
udp_pair.DataReceived += (object local_sender, RtspChunkEventArgs local_e) => {
|
||||
// RTCP data received
|
||||
Console.WriteLine("RTCP data received " + local_sender.ToString() + " " + local_e.ToString());
|
||||
};
|
||||
udp_pair.Start(); // start listening for data on the UDP ports
|
||||
|
||||
// Pass the Port of the two sockets back in the reply
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
|
||||
transport_reply.IsMulticast = false;
|
||||
transport_reply.ClientPort = new PortCouple(udp_pair.data_port,udp_pair.control_port);
|
||||
} else {
|
||||
transport_reply = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& transport.IsMulticast == true)
|
||||
{
|
||||
// RTP over Multicast UDP mode}
|
||||
// Create a pair of UDP sockets in Multicast Mode
|
||||
// Pass the Ports of the two sockets back in the reply
|
||||
transport_reply.LowerTransport = RtspTransport.LowerTransportType.UDP;
|
||||
transport_reply.IsMulticast = true;
|
||||
transport_reply.Port = new PortCouple(7000, 7001); // FIX
|
||||
|
||||
// for now until implemented
|
||||
transport_reply = null;
|
||||
}
|
||||
|
||||
|
||||
if (transport_reply != null)
|
||||
{
|
||||
|
||||
// Update the session with transport information
|
||||
String copy_of_session_id = "";
|
||||
lock (rtsp_list)
|
||||
{
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (connection.listener.RemoteAdress.Equals(listener.RemoteAdress)) {
|
||||
// ToDo - Check the Track ID to determine if this is a SETUP for the Video Stream
|
||||
// or a SETUP for an Audio Stream.
|
||||
// In the SDP the H264 video track is TrackID 0
|
||||
|
||||
|
||||
// found the connection
|
||||
// Add the transports to the connection
|
||||
connection.video_client_transport = transport;
|
||||
connection.video_transport_reply = transport_reply;
|
||||
|
||||
// If we are sending in UDP mode, add the UDP Socket pair and the Client Hostname
|
||||
connection.video_udp_pair = udp_pair;
|
||||
|
||||
|
||||
connection.video_session_id = session_handle.ToString();
|
||||
session_handle++;
|
||||
|
||||
|
||||
// Copy the Session ID
|
||||
copy_of_session_id = connection.video_session_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RtspResponse setup_response = setupMessage.CreateResponse();
|
||||
setup_response.Headers[RtspHeaderNames.Transport] = transport_reply.ToString();
|
||||
setup_response.Session = copy_of_session_id;
|
||||
listener.SendMessage(setup_response);
|
||||
}
|
||||
else
|
||||
{
|
||||
RtspResponse setup_response = setupMessage.CreateResponse();
|
||||
// unsuported transport
|
||||
setup_response.ReturnCode = 461;
|
||||
listener.SendMessage(setup_response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle PLAY message (Sent with a Session ID)
|
||||
if (message is RtspRequestPlay)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List. Change the state to "PLAY"
|
||||
bool session_found = false;
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (message.Session == connection.video_session_id) /* OR AUDIO_SESSION_ID */
|
||||
{
|
||||
// found the session
|
||||
session_found = true;
|
||||
connection.play = true; // ACTUALLY YOU COULD PAUSE JUST THE VIDEO (or JUST THE AUDIO)
|
||||
|
||||
string range = "npt=0-"; // Playing the 'video' from 0 seconds until the end
|
||||
string rtp_info = "url="+((RtspRequestPlay)message).RtspUri+";seq=" + connection.video_sequence_number; // TODO Add rtptime +";rtptime="+session.rtp_initial_timestamp;
|
||||
|
||||
// Send the reply
|
||||
RtspResponse play_response = (e.Message as RtspRequestPlay).CreateResponse();
|
||||
play_response.AddHeader("Range: " + range);
|
||||
play_response.AddHeader("RTP-Info: " + rtp_info);
|
||||
listener.SendMessage(play_response);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (session_found == false) {
|
||||
// Session ID was not found in the list of Sessions. Send a 454 error
|
||||
RtspResponse play_failed_response = (e.Message as RtspRequestPlay).CreateResponse();
|
||||
play_failed_response.ReturnCode = 454; // Session Not Found
|
||||
listener.SendMessage(play_failed_response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle PAUSE message (Sent with a Session ID)
|
||||
if (message is RtspRequestPause)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List. Change the state of "PLAY"
|
||||
foreach (RTSPConnection connection in rtsp_list)
|
||||
{
|
||||
if (message.Session == connection.video_session_id /* OR AUDIO SESSION ID */)
|
||||
{
|
||||
// found the session
|
||||
connection.play = false; // COULD HAVE PLAY/PAUSE FOR VIDEO AND AUDIO
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToDo - only send back the OK response if the Session in the RTSP message was found
|
||||
RtspResponse pause_response = (e.Message as RtspRequestPause).CreateResponse();
|
||||
listener.SendMessage(pause_response);
|
||||
}
|
||||
|
||||
|
||||
// Handle GET_PARAMETER message, often used as a Keep Alive
|
||||
if (message is RtspRequestGetParameter)
|
||||
{
|
||||
// Create the reponse to GET_PARAMETER
|
||||
RtspResponse getparameter_response = (e.Message as RtspRequestGetParameter).CreateResponse();
|
||||
listener.SendMessage(getparameter_response);
|
||||
}
|
||||
|
||||
|
||||
// Handle TEARDOWN (sent with a Session ID)
|
||||
if (message is RtspRequestTeardown)
|
||||
{
|
||||
lock (rtsp_list)
|
||||
{
|
||||
// Search for the Session in the Sessions List.
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()) // Convert to ToArray so we can delete from the rtp_list
|
||||
{
|
||||
if (message.Session == connection.video_session_id) // SHOULD HAVE AN AUDIO TEARDOWN AS WELL
|
||||
{
|
||||
// If this is UDP, close the transport
|
||||
// For TCP there is no transport to close (as RTP packets were interleaved into the RTSP connection)
|
||||
if (connection.video_udp_pair != null) {
|
||||
connection.video_udp_pair.Stop();
|
||||
connection.video_udp_pair = null;
|
||||
}
|
||||
|
||||
rtsp_list.Remove(connection);
|
||||
|
||||
// Close the RTSP socket
|
||||
listener.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// The 'Camera' (YUV TestCard) has generated a YUV image.
|
||||
// If there are RTSP clients connected then Compress the Video Frame (with H264) and send it to the client
|
||||
void video_source_ReceivedYUVFrame(uint timestamp_ms, int width, int height, byte[] yuv_data)
|
||||
{
|
||||
DateTime now = DateTime.UtcNow;
|
||||
int current_rtp_play_count = 0;
|
||||
int current_rtp_count = 0;
|
||||
int timeout_in_seconds = 70; // must have a RTSP message every 70 seconds or we will close the connection
|
||||
lock (rtsp_list) {
|
||||
current_rtp_count = rtsp_list.Count;
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()) { // Convert to Array to allow us to delete from rtsp_list
|
||||
// RTSP Timeout (clients receiving RTP video over the RTSP session
|
||||
// do not need to send a keepalive (so we check for Socket write errors)
|
||||
Boolean sending_rtp_via_tcp = false;
|
||||
if ((connection.video_client_transport != null) &&
|
||||
(connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP))
|
||||
{
|
||||
sending_rtp_via_tcp = true;
|
||||
}
|
||||
|
||||
if (sending_rtp_via_tcp == false && ((now - connection.time_since_last_rtsp_keepalive).TotalSeconds > timeout_in_seconds)) {
|
||||
|
||||
Console.WriteLine("Removing session " + connection.video_session_id + " due to TIMEOUT");
|
||||
connection.play = false; // stop sending data
|
||||
if (connection.video_udp_pair != null)
|
||||
{
|
||||
connection.video_udp_pair.Stop();
|
||||
connection.video_udp_pair = null;
|
||||
}
|
||||
connection.listener.Dispose();
|
||||
|
||||
rtsp_list.Remove(connection);
|
||||
continue;
|
||||
}
|
||||
else if (connection.play) current_rtp_play_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Take the YUV image and encode it into a H264 NAL
|
||||
// This returns a NAL with no headers (no 00 00 00 01 header and no 32 bit sizes)
|
||||
Console.WriteLine(current_rtp_count + " RTSP clients connected. " + current_rtp_play_count + " RTSP clients in PLAY mode");
|
||||
|
||||
if (current_rtp_play_count == 0) return;
|
||||
|
||||
// Compress the video (YUV to H264)
|
||||
byte[] raw_video_nal = h264_encoder.CompressFrame(yuv_data);
|
||||
Boolean isKeyframe = true; // SimpleH264encoder and TinyH24encoder only emit keyframes
|
||||
|
||||
|
||||
List<byte[]> nal_array = new List<byte[]>();
|
||||
|
||||
// We may want to add the SPS and PPS to the H264 stream as in-band data.
|
||||
// This may be of use if the client did not parse the SPS/PPS in the SDP
|
||||
// or if the H264 encoder changes properties (eg a new resolution or framerate which
|
||||
// gives a new SPS or PPS).
|
||||
// Also looking towards H265, the VPS/SPS/PPS do not need to be in the SDP so would be added here.
|
||||
|
||||
Boolean add_sps_pps_to_keyframe = true;
|
||||
|
||||
if (add_sps_pps_to_keyframe && isKeyframe) {
|
||||
nal_array.Add(raw_sps);
|
||||
nal_array.Add(raw_pps);
|
||||
}
|
||||
|
||||
// add the rest of the NALs
|
||||
nal_array.Add(raw_video_nal);
|
||||
|
||||
|
||||
|
||||
UInt32 rtp_timestamp = timestamp_ms * 90; // 90kHz clock
|
||||
|
||||
// Build a list of 1 or more RTP packets
|
||||
// The last packet will have the M bit set to '1'
|
||||
List<byte[]> rtp_packets = new List<byte[]>();
|
||||
|
||||
for(int x = 0; x < nal_array.Count; x++) {
|
||||
|
||||
byte[] raw_nal = nal_array[x];
|
||||
Boolean last_nal = false;
|
||||
if (x == nal_array.Count - 1) {
|
||||
last_nal = true; // last NAL in our nal_array
|
||||
}
|
||||
|
||||
// The H264 Payload could be sent as one large RTP packet (assuming the receiver can handle it)
|
||||
// or as a Fragmented Data, split over several RTP packets with the same Timestamp.
|
||||
bool fragmenting = false;
|
||||
int packetMTU = 65500;
|
||||
if (raw_nal.Length > packetMTU) fragmenting = true;
|
||||
|
||||
|
||||
if (fragmenting == false)
|
||||
{
|
||||
// Put the whole NAL into one RTP packet.
|
||||
// Note some receivers will have maximum buffers and be unable to handle large RTP packets.
|
||||
// Also with RTP over RTSP there is a limit of 65535 bytes for the RTP packet.
|
||||
|
||||
byte[] rtp_packet = new byte[12 + raw_nal.Length]; // 12 is header size when there are no CSRCs or extensions
|
||||
// Create an single RTP fragment
|
||||
|
||||
// RTP Packet Header
|
||||
// 0 - Version, P, X, CC, M, PT and Sequence Number
|
||||
//32 - Timestamp. H264 uses a 90kHz clock
|
||||
//64 - SSRC
|
||||
//96 - CSRCs (optional)
|
||||
//nn - Extension ID and Length
|
||||
//nn - Extension header
|
||||
|
||||
int rtp_version = 2;
|
||||
int rtp_padding = 0;
|
||||
int rtp_extension = 0;
|
||||
int rtp_csrc_count = 0;
|
||||
int rtp_marker = (last_nal == true ? 1 : 0); // set to 1 if the last NAL in the array
|
||||
int rtp_payload_type = 96;
|
||||
|
||||
RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);
|
||||
|
||||
UInt32 empty_sequence_id = 0;
|
||||
RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);
|
||||
|
||||
RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);
|
||||
|
||||
UInt32 empty_ssrc = 0;
|
||||
RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);
|
||||
|
||||
// Now append the raw NAL
|
||||
System.Array.Copy(raw_nal, 0, rtp_packet, 12, raw_nal.Length);
|
||||
|
||||
rtp_packets.Add(rtp_packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
int data_remaining = raw_nal.Length;
|
||||
int nal_pointer = 0;
|
||||
int start_bit = 1;
|
||||
int end_bit = 0;
|
||||
|
||||
// consume first byte of the raw_nal. It is used in the FU header
|
||||
byte first_byte = raw_nal[0];
|
||||
nal_pointer++;
|
||||
data_remaining--;
|
||||
|
||||
while (data_remaining > 0)
|
||||
{
|
||||
int payload_size = Math.Min(packetMTU, data_remaining);
|
||||
if (data_remaining - payload_size == 0) end_bit = 1;
|
||||
|
||||
byte[] rtp_packet = new byte[12 + 2 + payload_size]; // 12 is header size. 2 bytes for FU-A header. Then payload
|
||||
|
||||
// RTP Packet Header
|
||||
// 0 - Version, P, X, CC, M, PT and Sequence Number
|
||||
//32 - Timestamp. H264 uses a 90kHz clock
|
||||
//64 - SSRC
|
||||
//96 - CSRCs (optional)
|
||||
//nn - Extension ID and Length
|
||||
//nn - Extension header
|
||||
|
||||
int rtp_version = 2;
|
||||
int rtp_padding = 0;
|
||||
int rtp_extension = 0;
|
||||
int rtp_csrc_count = 0;
|
||||
int rtp_marker = (last_nal == true ? 1 : 0); // Marker set to 1 on last packet
|
||||
int rtp_payload_type = 96;
|
||||
|
||||
RTPPacketUtil.WriteHeader(rtp_packet, rtp_version, rtp_padding, rtp_extension, rtp_csrc_count, rtp_marker, rtp_payload_type);
|
||||
|
||||
UInt32 empty_sequence_id = 0;
|
||||
RTPPacketUtil.WriteSequenceNumber(rtp_packet, empty_sequence_id);
|
||||
|
||||
RTPPacketUtil.WriteTS(rtp_packet, rtp_timestamp);
|
||||
|
||||
UInt32 empty_ssrc = 0;
|
||||
RTPPacketUtil.WriteSSRC(rtp_packet, empty_ssrc);
|
||||
|
||||
// Now append the Fragmentation Header (with Start and End marker) and part of the raw_nal
|
||||
byte f_bit = 0;
|
||||
byte nri = (byte)((first_byte >> 5) & 0x03); // Part of the 1st byte of the Raw NAL (NAL Reference ID)
|
||||
byte type = 28; // FU-A Fragmentation
|
||||
|
||||
rtp_packet[12] = (byte)((f_bit << 7) + (nri << 5) + type);
|
||||
rtp_packet[13] = (byte)((start_bit << 7) + (end_bit << 6) + (0 << 5) + (first_byte & 0x1F));
|
||||
|
||||
System.Array.Copy(raw_nal, nal_pointer, rtp_packet, 14, payload_size);
|
||||
nal_pointer = nal_pointer + payload_size;
|
||||
data_remaining = data_remaining - payload_size;
|
||||
|
||||
rtp_packets.Add(rtp_packet);
|
||||
|
||||
start_bit = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lock (rtsp_list)
|
||||
{
|
||||
|
||||
// Go through each RTSP connection and output the NAL on the Video Session
|
||||
foreach (RTSPConnection connection in rtsp_list.ToArray()) // ToArray makes a temp copy of the list.
|
||||
// This lets us delete items in the foreach
|
||||
// eg when there is Write Error
|
||||
{
|
||||
// Only process Sessions in Play Mode
|
||||
if (connection.play == false) continue;
|
||||
|
||||
String connection_type = "";
|
||||
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.TCP) connection_type = "TCP";
|
||||
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& connection.video_client_transport.IsMulticast == false) connection_type = "UDP";
|
||||
if (connection.video_client_transport.LowerTransport == RtspTransport.LowerTransportType.UDP
|
||||
&& connection.video_client_transport.IsMulticast == true) connection_type = "Multicast";
|
||||
Console.WriteLine("Sending video session " + connection.video_session_id + " " + connection_type + " Timestamp(ms)=" + timestamp_ms + ". RTP timestamp=" + rtp_timestamp + ". Sequence="+ connection.video_sequence_number);
|
||||
|
||||
// There could be more than 1 RTP packet (if the data is fragmented)
|
||||
Boolean write_error = false;
|
||||
foreach (byte[] rtp_packet in rtp_packets)
|
||||
{
|
||||
// Add the specific data for each transmission
|
||||
RTPPacketUtil.WriteSequenceNumber(rtp_packet, connection.video_sequence_number);
|
||||
connection.video_sequence_number++;
|
||||
|
||||
// Add the specific SSRC for each transmission
|
||||
RTPPacketUtil.WriteSSRC(rtp_packet, connection.ssrc);
|
||||
|
||||
|
||||
// Send as RTP over RTSP (Interleaved)
|
||||
if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.TCP)
|
||||
{
|
||||
int video_channel = connection.video_transport_reply.Interleaved.First; // second is for RTCP status messages)
|
||||
object state = new object();
|
||||
try
|
||||
{
|
||||
// send the whole NAL. With RTP over RTSP we do not need to Fragment the NAL (as we do with UDP packets or Multicast)
|
||||
//session.listener.BeginSendData(video_channel, rtp_packet, new AsyncCallback(session.listener.EndSendData), state);
|
||||
connection.listener.SendData(video_channel, rtp_packet);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
|
||||
write_error = true;
|
||||
break; // exit out of foreach loop
|
||||
}
|
||||
}
|
||||
|
||||
// Send as RTP over UDP
|
||||
if (connection.video_transport_reply.LowerTransport == RtspTransport.LowerTransportType.UDP && connection.video_transport_reply.IsMulticast == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// send the whole NAL. ** We could fragment the RTP packet into smaller chuncks that fit within the MTU
|
||||
// Send to the IP address of the Client
|
||||
// Send to the UDP Port the Client gave us in the SETUP command
|
||||
connection.video_udp_pair.Write_To_Data_Port(rtp_packet,connection.client_hostname,connection.video_client_transport.ClientPort.First);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("UDP Write Exception " + e.ToString());
|
||||
Console.WriteLine("Error writing to listener " + connection.listener.RemoteAdress);
|
||||
write_error = true;
|
||||
break; // exit out of foreach loop
|
||||
}
|
||||
}
|
||||
|
||||
// TODO. Add Multicast
|
||||
}
|
||||
if (write_error)
|
||||
{
|
||||
Console.WriteLine("Removing session " + connection.video_session_id + " due to write error");
|
||||
connection.play = false; // stop sending data
|
||||
if (connection.video_udp_pair != null) {
|
||||
connection.video_udp_pair.Stop();
|
||||
connection.video_udp_pair = null;
|
||||
}
|
||||
connection.listener.Dispose();
|
||||
rtsp_list.Remove(connection); // remove the session. It is dead
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class RTSPConnection
|
||||
{
|
||||
public RtspListener listener = null; // The RTSP client connection
|
||||
public bool play = false; // set to true when Session is in Play mode
|
||||
public DateTime time_since_last_rtsp_keepalive = DateTime.UtcNow; // Time since last RTSP message received - used to spot dead UDP clients
|
||||
public UInt32 ssrc = 0x12345678; // SSRC value used with this client connection
|
||||
public String client_hostname = ""; // Client Hostname/IP Address
|
||||
|
||||
public String video_session_id = ""; // RTSP Session ID used with this client connection
|
||||
public UInt16 video_sequence_number = 1; // 16 bit RTP packet sequence number used with this client connection
|
||||
public RtspTransport video_client_transport; // Transport: string from the client to the server
|
||||
public RtspTransport video_transport_reply; // Transport: reply from the server to the client
|
||||
public UDPSocket video_udp_pair = null; // Pair of UDP sockets (data and control) used when sending via UDP
|
||||
public DateTime video_time_since_last_rtcp_keepalive = DateTime.UtcNow; // Time since last RTCP message received - used to spot dead UDP clients
|
||||
|
||||
// TODO - Add Audio
|
||||
}
|
||||
}
|
||||
}
|
||||
109
framework/Inspectron.HawkEye/RTSP/Server/SimpleH264Encoder.cs
Normal file
109
framework/Inspectron.HawkEye/RTSP/Server/SimpleH264Encoder.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
// Simple H264 Encoder
|
||||
// Written by Jordi Cenzano (www.jordicenzano.name)
|
||||
//
|
||||
// Ported to C# by Roger Hardiman www.rjh.org.uk
|
||||
|
||||
// This is a very simple lossless H264 encoder. No compression is used and so the output NAL data is as
|
||||
// large as the input YUV data.
|
||||
// It is used for a quick example of H264 encoding in pure .Net without needing OS specific APIs
|
||||
// or cross compiled C libraries.
|
||||
//
|
||||
// SimpleH264Encoder can use any image Width or Height
|
||||
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class SimpleH264Encoder
|
||||
{
|
||||
CJOCh264encoder h264encoder = null;
|
||||
|
||||
uint width = 0;
|
||||
uint height = 0;
|
||||
|
||||
List<byte> nal = new List<byte>();
|
||||
|
||||
// Constuctor
|
||||
public SimpleH264Encoder(uint width, uint height, uint fps)
|
||||
{
|
||||
// We have the ability to set the aspect ratio (SAR).
|
||||
// For now we set to 1:1
|
||||
uint SARw = 1;
|
||||
uint SARh = 1;
|
||||
|
||||
// Initialise H264 encoder. The original C++ code writes to a file. In this port it writes to a List<byte>
|
||||
h264encoder = new CJOCh264encoder(nal);
|
||||
h264encoder.IniCoder(width, height, fps, CJOCh264encoder.enSampleFormat.SAMPLE_FORMAT_YUV420p, SARw, SARh);
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
// NAL array will contain SPS and PPS
|
||||
|
||||
}
|
||||
|
||||
// Raw SPS with no Size Header and no 00 00 00 01 headers
|
||||
public byte[] GetRawSPS()
|
||||
{
|
||||
byte[] sps_with_header = h264encoder.sps;
|
||||
byte[] sps = new byte[sps_with_header.Length - 4];
|
||||
System.Array.Copy(sps_with_header, 4, sps, 0, sps.Length);
|
||||
return sps;
|
||||
}
|
||||
|
||||
public byte[] GetRawPPS()
|
||||
{
|
||||
byte[] pps_with_header = h264encoder.pps;
|
||||
byte[] pps = new byte[pps_with_header.Length - 4];
|
||||
System.Array.Copy(pps_with_header, 4, pps, 0, pps.Length);
|
||||
return pps;
|
||||
}
|
||||
|
||||
public byte[] CompressFrame(byte[] yuv_data)
|
||||
{
|
||||
byte[] image = h264encoder.GetFramePtr();
|
||||
// copy over the YUV image
|
||||
System.Array.Copy(yuv_data, image, image.Length);
|
||||
|
||||
// // HACK. Set the YUV pixels all to 127
|
||||
// for (int hack = 0; hack < image.Length; hack++) image[hack] = 127;
|
||||
|
||||
h264encoder.CodeAndSaveFrame();
|
||||
|
||||
// Get the NAL (which has the 00 00 00 01 header)
|
||||
byte[] nal_with_header = h264encoder.nal;
|
||||
byte[] nal = new byte[nal_with_header.Length - 4];
|
||||
System.Array.Copy(nal_with_header, 4, nal, 0, nal.Length);
|
||||
return nal;
|
||||
}
|
||||
|
||||
|
||||
public void ChangeAnnexBto32BitSize(byte[] data)
|
||||
{
|
||||
|
||||
if (data.Length < 4) return;
|
||||
|
||||
// change data from 0x00 0x00 0x00 0x01 format to 32 bit size
|
||||
int len = data.Length - 4;// subtract Annex B header size
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
data[0] = (byte)((len >> 24) & 0xFF);
|
||||
data[1] = (byte)((len >> 16) & 0xFF);
|
||||
data[2] = (byte)((len >> 8) & 0xFF);
|
||||
data[3] = (byte)((len << 0) & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
data[0] = (byte)((len >> 0) & 0xFF);
|
||||
data[1] = (byte)((len >> 8) & 0xFF);
|
||||
data[2] = (byte)((len >> 16) & 0xFF);
|
||||
data[3] = (byte)((len >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
209
framework/Inspectron.HawkEye/RTSP/Server/TestCard.cs
Normal file
209
framework/Inspectron.HawkEye/RTSP/Server/TestCard.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
// (c) Roger Hardiman 2016
|
||||
|
||||
// This class uses a System Timer to generate a YUV image at regular intervals
|
||||
// The ReceivedYUVFrame event is fired for each new YUV image
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class TestCard
|
||||
{
|
||||
|
||||
// Events that applications can receive
|
||||
public event ReceivedYUVFrameHandler ReceivedYUVFrame;
|
||||
|
||||
// Delegated functions (essentially the function prototype)
|
||||
public delegate void ReceivedYUVFrameHandler(uint timestamp, int width, int height, byte[] data);
|
||||
|
||||
|
||||
// Local variables
|
||||
private System.Timers.Timer frame_timer;
|
||||
private int fps = 0;
|
||||
private Stopwatch stopwatch;
|
||||
private byte[] yuv_frame = null;
|
||||
private int x_position = 0;
|
||||
private int y_position = 0;
|
||||
private int width = 0;
|
||||
private int height = 0;
|
||||
private Object generate_lock = new Object();
|
||||
private long count = 0;
|
||||
|
||||
// ASCII Font
|
||||
// Created by Roger Hardiman using an online generation tool
|
||||
// http://www.riyas.org/2013/12/online-led-matrix-font-generator-with.html
|
||||
|
||||
byte[] ascii_0 = { 0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c };
|
||||
byte[] ascii_1 = { 0x00, 0x08, 0x18, 0x28, 0x08, 0x08, 0x08, 0x3e };
|
||||
byte[] ascii_2 = { 0x00, 0x3e, 0x42, 0x02, 0x0c, 0x30, 0x40, 0x7e };
|
||||
byte[] ascii_3 = { 0x00, 0x7c, 0x02, 0x02, 0x3c, 0x02, 0x02, 0x7c };
|
||||
byte[] ascii_4 = { 0x00, 0x0c, 0x14, 0x24, 0x44, 0x7e, 0x04, 0x04 };
|
||||
byte[] ascii_5 = { 0x00, 0x7e, 0x40, 0x40, 0x7c, 0x02, 0x02, 0x7c };
|
||||
byte[] ascii_6 = { 0x00, 0x3e, 0x40, 0x40, 0x7c, 0x42, 0x42, 0x3c };
|
||||
byte[] ascii_7 = { 0x00, 0x7e, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20 };
|
||||
byte[] ascii_8 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x42, 0x42, 0x3c };
|
||||
byte[] ascii_9 = { 0x00, 0x3c, 0x42, 0x42, 0x3c, 0x02, 0x02, 0x3e };
|
||||
byte[] ascii_colon = { 0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00 };
|
||||
byte[] ascii_space = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
byte[] ascii_dot = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00 };
|
||||
|
||||
// Constructor
|
||||
public TestCard(int width, int height, int fps)
|
||||
{
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.fps = fps;
|
||||
|
||||
// YUV size
|
||||
int y_size = width * height;
|
||||
int u_size = (width >> 1) * (height >> 1);
|
||||
int v_size = (width >> 1) * (height >> 1);
|
||||
yuv_frame = new byte[y_size + u_size + v_size];
|
||||
|
||||
// Set all values to 127
|
||||
for (int x = 0; x < yuv_frame.Length; x++)
|
||||
{
|
||||
yuv_frame[x] = 127;
|
||||
}
|
||||
|
||||
stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
// Start timer. The Timer will generate each YUV frame
|
||||
frame_timer = new System.Timers.Timer();
|
||||
frame_timer.Interval = 1; // on first pass timer will fire straight away (cannot have zero interval)
|
||||
frame_timer.AutoReset = false; // do not restart timer after the time has elapsed
|
||||
frame_timer.Elapsed += (object sender, System.Timers.ElapsedEventArgs e) =>
|
||||
{
|
||||
// send a frame
|
||||
Send_YUV_Frame();
|
||||
count++;
|
||||
|
||||
// Some CPU cycles will have been used in Sending the YUV Frame.
|
||||
// Compute the delay required (the Timer Interval) before sending the next YUV frame
|
||||
long time_for_next_tick_ms = (count * 1000) / fps;
|
||||
long time_to_wait = time_for_next_tick_ms - stopwatch.ElapsedMilliseconds;
|
||||
if (time_to_wait <= 0) time_to_wait = 1; // cannot have negative or zero intervals
|
||||
frame_timer.Interval = time_to_wait;
|
||||
frame_timer.Start();
|
||||
};
|
||||
frame_timer.Start();
|
||||
|
||||
}
|
||||
|
||||
// Dispose
|
||||
public void Disconnect()
|
||||
{
|
||||
// Stop the frame timer
|
||||
frame_timer.Stop();
|
||||
frame_timer.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private void Send_YUV_Frame()
|
||||
{
|
||||
lock (generate_lock)
|
||||
{
|
||||
// Get the current time
|
||||
DateTime now_utc = DateTime.UtcNow;
|
||||
DateTime now_local = now_utc.ToLocalTime();
|
||||
|
||||
|
||||
long timestamp_ms = ((long)(now_utc.Ticks / TimeSpan.TicksPerMillisecond));
|
||||
|
||||
// Generate the String to write
|
||||
char[] overlay = null;
|
||||
|
||||
if (width >= 96)
|
||||
{
|
||||
// Need 12 characters of 8x8 pixels. 12*8 = 96
|
||||
// HH:MM:SS.mmm
|
||||
String overlay_str = now_local.ToString("HH:mm:ss.fff", CultureInfo.InvariantCulture); // do not replace : or . by local formats
|
||||
overlay = overlay_str.ToCharArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Min for most video formats is 16x16, enough for 2 characters
|
||||
String overlay_str = now_local.ToString("ss", CultureInfo.InvariantCulture); // do not replace : or . by local formats
|
||||
overlay = overlay_str.ToCharArray();
|
||||
}
|
||||
|
||||
// process each character
|
||||
int start_row = ((height / 2) - 4); // start 4 pixels above the centre row (4 is half the font height)
|
||||
for (int c = 0; c < overlay.Length; c++)
|
||||
{
|
||||
byte[] font = ascii_space;
|
||||
if (overlay[c] == '0') font = ascii_0;
|
||||
if (overlay[c] == '1') font = ascii_1;
|
||||
if (overlay[c] == '2') font = ascii_2;
|
||||
if (overlay[c] == '3') font = ascii_3;
|
||||
if (overlay[c] == '4') font = ascii_4;
|
||||
if (overlay[c] == '5') font = ascii_5;
|
||||
if (overlay[c] == '6') font = ascii_6;
|
||||
if (overlay[c] == '7') font = ascii_7;
|
||||
if (overlay[c] == '8') font = ascii_8;
|
||||
if (overlay[c] == '9') font = ascii_9;
|
||||
if (overlay[c] == ' ') font = ascii_space;
|
||||
if (overlay[c] == ':') font = ascii_colon;
|
||||
if (overlay[c] == '.') font = ascii_dot;
|
||||
|
||||
// process the font character
|
||||
for (int rows = 0; rows < 8; rows++)
|
||||
{
|
||||
int y_plane_pos = (start_row * width) + (rows * width) + (c * 8);
|
||||
byte row_byte = font[rows];
|
||||
// bit shift the row byte into individual pixels where the font On/Off maps to Y intensity 50 or 200
|
||||
for (int bits = 0; bits < 8; bits++)
|
||||
{
|
||||
if ((row_byte & 0x80) == 0x80)
|
||||
{
|
||||
// Pixel On
|
||||
yuv_frame[y_plane_pos] = 200;
|
||||
}
|
||||
else
|
||||
{
|
||||
yuv_frame[y_plane_pos] = 50;
|
||||
}
|
||||
y_plane_pos++;
|
||||
row_byte = (byte)(row_byte << 1); // shift up so the next 'bit' to process is the most significant bit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Toggle the pixel value
|
||||
byte pixel_value = yuv_frame[(y_position * width) + x_position];
|
||||
|
||||
// change brightness of pixel
|
||||
if (pixel_value > 128) pixel_value = 30;
|
||||
else pixel_value = 230;
|
||||
|
||||
yuv_frame[(y_position * width) + x_position] = pixel_value;
|
||||
|
||||
// move the x and y position
|
||||
x_position = x_position + 5;
|
||||
if (x_position >= width)
|
||||
{
|
||||
x_position = 0;
|
||||
y_position = y_position + 1;
|
||||
}
|
||||
|
||||
if (y_position >= height)
|
||||
{
|
||||
y_position = 0;
|
||||
}
|
||||
|
||||
// fire the Event
|
||||
if (ReceivedYUVFrame != null)
|
||||
{
|
||||
ReceivedYUVFrame((uint)stopwatch.ElapsedMilliseconds, width, height, yuv_frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
166
framework/Inspectron.HawkEye/RTSP/Server/TinyH264Encoder.cs
Normal file
166
framework/Inspectron.HawkEye/RTSP/Server/TinyH264Encoder.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
||||
// Tiny H264 Encoder
|
||||
// World's Smallest h.264 Encoder, by Ben Mesander.
|
||||
// https://cardinalpeak.com/blog/worlds-smallest-h-264-encoder/
|
||||
//
|
||||
// Ported to C# by Roger Hardiman www.rjh.org.uk
|
||||
|
||||
// Input: YUV image that must be 128x96
|
||||
// Output: H264 NAL
|
||||
//
|
||||
// This is a very simple lossless H264 encoder. No compression is used and so the output NAL data is as
|
||||
// large as the input YUV data.
|
||||
// It is used for a quick example of H264 encoding in pure .Net without needing OS specific APIs
|
||||
// or cross compiled C libraries.
|
||||
//
|
||||
// The H264 SPS/PPS data includes the image size. As the SPS/PPS is hard coded in this example the YUV
|
||||
// image size must be 128 x 96
|
||||
|
||||
namespace Inspectron.HawkEye.RTSP.Server
|
||||
{
|
||||
public class TinyH264Encoder
|
||||
{
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int uv_width = 0;
|
||||
int uv_height = 0;
|
||||
int y_size = 0;
|
||||
int u_size = 0;
|
||||
int v_size = 0;
|
||||
|
||||
byte[] sps = { 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 };
|
||||
//byte[] sps_b = { 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // Annex B
|
||||
//byte[] sps32 = { 0x00, 0x00, 0x00, 0x07, 0x67, 0x42, 0x00, 0x0a, 0xf8, 0x41, 0xa2 }; // 32 bit size
|
||||
|
||||
byte[] pps = { 0x68, 0xce, 0x38, 0x80 };
|
||||
//byte[] pps_b = { 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80 }; // Annex B
|
||||
//byte[] pps32 = { 0x00, 0x00, 0x00, 0x04, 0x68, 0xce, 0x38, 0x80 }; // 32 bit size
|
||||
|
||||
byte[] slice_header = { 0x05, 0x88, 0x84, 0x21, 0xa0 };
|
||||
//byte[] slice_header_b = { 0x00, 0x00, 0x00, 0x01, 0x05, 0x88, 0x84, 0x21, 0xa0 };
|
||||
//byte[] slice_header_32 = { 0x00, 0x00, 0x00, 0x00, 0x05, 0x88, 0x84, 0x21, 0xa0 }; // must replace size bytes
|
||||
byte[] slice_end = { 0x80 };
|
||||
byte[] macroblock_header = { 0x0d, 0x00 };
|
||||
|
||||
List<byte> nal = new List<byte>();
|
||||
|
||||
// Constuctor
|
||||
public TinyH264Encoder()
|
||||
{
|
||||
this.width = 128; // Hard coded size that is embedded in the SPS/PPS data
|
||||
this.height = 96; // Hard coded size that is embedded in the SPS/PPS data
|
||||
this.uv_width = width >> 1;
|
||||
this.uv_height = height >> 1;
|
||||
this.y_size = width * height;
|
||||
this.u_size = (width >> 1) * (height >> 1);
|
||||
this.v_size = (width >> 1) * (height >> 1);
|
||||
}
|
||||
|
||||
public byte[] GetRawSPS()
|
||||
{
|
||||
return sps.ToArray();
|
||||
}
|
||||
|
||||
public byte[] GetRawPPS()
|
||||
{
|
||||
return pps.ToArray();
|
||||
}
|
||||
|
||||
public byte[] CompressFrame(byte[] yuv_data)
|
||||
{
|
||||
// we can only do 128 x 96
|
||||
if (width != 128) return null;
|
||||
if (height != 96) return null;
|
||||
|
||||
// check size
|
||||
if (yuv_data.Length < (y_size + u_size + v_size))
|
||||
{
|
||||
// the yuv image is too small.
|
||||
return null;
|
||||
}
|
||||
|
||||
nal.Clear();
|
||||
|
||||
// Slice Header
|
||||
foreach (byte b in slice_header) nal.Add(b);
|
||||
|
||||
// Add each macro block
|
||||
for (int i = 0; i < (height / 16); i++) {
|
||||
for (int j = 0; j < (width / 16); j++) {
|
||||
macroblock(i, j, yuv_data);
|
||||
}
|
||||
}
|
||||
|
||||
// Add slice end
|
||||
foreach (byte b in slice_end) nal.Add(b);
|
||||
|
||||
byte[] nal_array = nal.ToArray();
|
||||
|
||||
return nal_array;
|
||||
}
|
||||
|
||||
/* Write a macroblock's worth of YUV data in I_PCM mode */
|
||||
private void macroblock(int i, int j, byte[] frame)
|
||||
{
|
||||
int x, y;
|
||||
|
||||
if (!((i == 0) && (j == 0)))
|
||||
{
|
||||
foreach (byte b in macroblock_header) nal.Add(b);
|
||||
}
|
||||
|
||||
for (x = i * 16; x < ((i + 1) * 16); x++)
|
||||
{
|
||||
for (y = j * 16; y < ((j + 1) * 16); y++)
|
||||
{
|
||||
nal.Add(frame[(x * width) + y]);
|
||||
}
|
||||
}
|
||||
for (x = i * 8; x < (i + 1) * 8; x++)
|
||||
{
|
||||
for (y = j * 8; y < (j + 1) * 8; y++)
|
||||
{
|
||||
nal.Add(frame[y_size + (x * uv_width) + y]);
|
||||
}
|
||||
}
|
||||
for (x = i * 8; x < (i + 1) * 8; x++)
|
||||
{
|
||||
for (y = j * 8; y < (j + 1) * 8; y++)
|
||||
{
|
||||
nal.Add(frame[y_size + u_size + (x * uv_width) + y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void ChangeAnnexBto32BitSize(byte[] data)
|
||||
{
|
||||
|
||||
if (data.Length < 4) return;
|
||||
|
||||
// change data from 0x00 0x00 0x00 0x01 format to 32 bit size
|
||||
int len = data.Length - 4;// subtract Annex B header size
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
data[0] = (byte)((len >> 24) & 0xFF);
|
||||
data[1] = (byte)((len >> 16) & 0xFF);
|
||||
data[2] = (byte)((len >> 8) & 0xFF);
|
||||
data[3] = (byte)((len << 0) & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
data[0] = (byte)((len >> 0) & 0xFF);
|
||||
data[1] = (byte)((len >> 8) & 0xFF);
|
||||
data[2] = (byte)((len >> 16) & 0xFF);
|
||||
data[3] = (byte)((len >> 24) & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user