/* * 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();
        }
    }
}
