├── README.md └── src ├── CJOCh264bitstream.cpp ├── CJOCh264bitstream.h ├── CJOCh264encoder.cpp ├── CJOCh264encoder.h ├── h264simpleCoder.cpp └── makefile /README.md: -------------------------------------------------------------------------------- 1 | h264simpleCoder 2 | =============== 3 | 4 | With the aim to improve my understanding of h264 bitstream and its techniques I have created the following code in C++ that generates a compliant h264 file from a YUV420p file. 5 | 6 | First of all I have to say that the following source code is NOT a h264 encoder but it generates a compliant (playable) h264 stream, this means that the size of output file will be slightly bigger than the size of the input file. This it is because I have used only I_PCM macroblock codification (non compressed format) to describe the images in h264 stream. 7 | 8 | I think that to read and understand this code could be a good starting point to start flirting with the h264 standard, better than dive into the standard (more than 700 pages of dense text). 9 | 10 | Usage example: 11 | 12 | > h264simpleCoder AVsyncTest.yuv OutTest.h264 128 96 25 16 9 13 | 14 | More information on: http://jordicenzano.name/2014/08/31/the-source-code-of-a-minimal-h264-encoder-c/ 15 | 16 | 17 | Generating a YUV file 18 | A 10 second yuv file (SQCIF 128x96) with a test image can be made with this ffmpeg command 19 | > ffmpeg -f lavfi -i testsrc -t 10 -s sqcif -pix_fmt yuv420p AVsyncTest.yuv 20 | 21 | -------------------------------------------------------------------------------- /src/CJOCh264bitstream.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CJOCh264bitstream.cpp 3 | * 4 | * Created on: Aug 23, 2014 5 | * Author: Jordi Cenzano (www.jordicenzano.name) 6 | */ 7 | 8 | #include "CJOCh264bitstream.h" 9 | 10 | CJOCh264bitstream::CJOCh264bitstream(FILE *pOutBinaryFile) 11 | { 12 | clearbuffer(); 13 | 14 | m_pOutFile = pOutBinaryFile; 15 | } 16 | 17 | CJOCh264bitstream::~CJOCh264bitstream() 18 | { 19 | close(); 20 | } 21 | 22 | void CJOCh264bitstream::clearbuffer() 23 | { 24 | memset (&m_buffer,0,sizeof (unsigned char)* BUFFER_SIZE_BITS); 25 | m_nLastbitinbuffer = 0; 26 | m_nStartingbyte = 0; 27 | } 28 | 29 | int CJOCh264bitstream::getbitnum (unsigned long lval, int nNumbit) 30 | { 31 | int lrc = 0; 32 | 33 | unsigned long lmask = (unsigned long) pow((unsigned long)2,(unsigned long)nNumbit); 34 | if ((lval & lmask) > 0) 35 | lrc = 1; 36 | 37 | return lrc; 38 | } 39 | 40 | void CJOCh264bitstream::addbittostream (int nVal) 41 | { 42 | if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS) 43 | { 44 | //Must be aligned, no need to do dobytealign(); 45 | savebufferbyte(); 46 | } 47 | 48 | //Use circular buffer of BUFFER_SIZE_BYTES 49 | int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % BUFFER_SIZE_BYTES; 50 | //The first bit to add is on the left 51 | int nBitPosInByte = 7 - m_nLastbitinbuffer % 8; 52 | 53 | //Get the byte value from buffer 54 | int nValTmp = m_buffer[nBytePos]; 55 | 56 | //Change the bit 57 | if (nVal > 0) 58 | nValTmp = (nValTmp | (int) pow(2,nBitPosInByte)); 59 | else 60 | nValTmp = (nValTmp & ~((int) pow(2,nBitPosInByte))); 61 | 62 | //Save the new byte value to the buffer 63 | m_buffer[nBytePos] = (unsigned char) nValTmp; 64 | 65 | m_nLastbitinbuffer++; 66 | } 67 | 68 | void CJOCh264bitstream::addbytetostream (int nVal) 69 | { 70 | if (m_nLastbitinbuffer >= BUFFER_SIZE_BITS) 71 | { 72 | //Must be aligned, no need to do dobytealign(); 73 | savebufferbyte(); 74 | } 75 | 76 | //Used circular buffer of BUFFER_SIZE_BYTES 77 | int nBytePos = (m_nStartingbyte + (m_nLastbitinbuffer / 8)) % BUFFER_SIZE_BYTES; 78 | //The first bit to add is on the left 79 | int nBitPosInByte = 7 - m_nLastbitinbuffer % 8; 80 | 81 | //Check if it is byte aligned 82 | if (nBitPosInByte != 7) 83 | throw "Error: inserting not aligment byte"; 84 | 85 | //Add all byte to buffer 86 | m_buffer[nBytePos] = (unsigned char) nVal; 87 | 88 | m_nLastbitinbuffer = m_nLastbitinbuffer + 8; 89 | } 90 | 91 | void CJOCh264bitstream::dobytealign() 92 | { 93 | //Check if the last bit in buffer is multiple of 8 94 | int nr = m_nLastbitinbuffer % 8; 95 | if ((nr % 8) != 0) 96 | m_nLastbitinbuffer = m_nLastbitinbuffer + (8 - nr); 97 | } 98 | 99 | void CJOCh264bitstream::savebufferbyte(bool bemulationprevention) 100 | { 101 | bool bemulationpreventionexecuted = false; 102 | 103 | if (m_pOutFile == NULL) 104 | throw "Error: out file is NULL"; 105 | 106 | //Check if the last bit in buffer is multiple of 8 107 | if ((m_nLastbitinbuffer % 8) != 0) 108 | throw "Error: Save to file must be byte aligned"; 109 | 110 | if ((m_nLastbitinbuffer / 8) <= 0) 111 | throw "Error: NO bytes to save"; 112 | 113 | if (bemulationprevention == true) 114 | { 115 | //Emulation prevention will be used: 116 | /*As per h.264 spec, 117 | rbsp_data shouldn't contain 118 | - 0x 00 00 00 119 | - 0x 00 00 01 120 | - 0x 00 00 02 121 | - 0x 00 00 03 122 | 123 | rbsp_data shall be in the following way 124 | - 0x 00 00 03 00 125 | - 0x 00 00 03 01 126 | - 0x 00 00 03 02 127 | - 0x 00 00 03 03 128 | */ 129 | 130 | //Check if emulation prevention is needed (emulation prevention is byte align defined) 131 | if ((m_buffer[((m_nStartingbyte + 0) % BUFFER_SIZE_BYTES)] == 0x00)&&(m_buffer[((m_nStartingbyte + 1) % BUFFER_SIZE_BYTES)] == 0x00)&&((m_buffer[((m_nStartingbyte + 2) % BUFFER_SIZE_BYTES)] == 0x00)||(m_buffer[((m_nStartingbyte + 2) % BUFFER_SIZE_BYTES)] == 0x01)||(m_buffer[((m_nStartingbyte + 2) % BUFFER_SIZE_BYTES)] == 0x02)||(m_buffer[((m_nStartingbyte + 2) % BUFFER_SIZE_BYTES)] == 0x03))) 132 | { 133 | int nbuffersaved = 0; 134 | unsigned char cEmulationPreventionByte = H264_EMULATION_PREVENTION_BYTE; 135 | 136 | //Save 1st byte 137 | fwrite(&m_buffer[((m_nStartingbyte + nbuffersaved) % BUFFER_SIZE_BYTES)], 1, 1, m_pOutFile); 138 | nbuffersaved ++; 139 | 140 | //Save 2st byte 141 | fwrite(&m_buffer[((m_nStartingbyte + nbuffersaved) % BUFFER_SIZE_BYTES)], 1, 1, m_pOutFile); 142 | nbuffersaved ++; 143 | 144 | //Save emulation prevention byte 145 | fwrite(&cEmulationPreventionByte, 1, 1, m_pOutFile); 146 | 147 | //Save the rest of bytes (usually 1) 148 | while (nbuffersaved < BUFFER_SIZE_BYTES) 149 | { 150 | fwrite(&m_buffer[((m_nStartingbyte + nbuffersaved) % BUFFER_SIZE_BYTES)], 1, 1, m_pOutFile); 151 | nbuffersaved++; 152 | } 153 | 154 | //All bytes in buffer are saved, so clear the buffer 155 | clearbuffer(); 156 | 157 | bemulationpreventionexecuted = true; 158 | } 159 | } 160 | 161 | if (bemulationpreventionexecuted == false) 162 | { 163 | //No emulation prevention was used 164 | 165 | //Save the oldest byte in buffer 166 | fwrite(&m_buffer[m_nStartingbyte], 1, 1, m_pOutFile); 167 | 168 | //Move the index 169 | m_buffer[m_nStartingbyte] = 0; 170 | m_nStartingbyte++; 171 | m_nStartingbyte = m_nStartingbyte % BUFFER_SIZE_BYTES; 172 | m_nLastbitinbuffer = m_nLastbitinbuffer - 8; 173 | } 174 | } 175 | 176 | //Public functions 177 | 178 | void CJOCh264bitstream::addbits (unsigned long lval, int nNumbits) 179 | { 180 | if ((nNumbits <= 0 )||(nNumbits > 64)) 181 | throw "Error: numbits must be between 1 ... 64"; 182 | 183 | int nBit = 0; 184 | int n = nNumbits-1; 185 | while (n >= 0) 186 | { 187 | nBit = getbitnum (lval,n); 188 | n--; 189 | 190 | addbittostream (nBit); 191 | } 192 | } 193 | 194 | void CJOCh264bitstream::addbyte (unsigned char cByte) 195 | { 196 | //Byte alignment optimization 197 | if ((m_nLastbitinbuffer % 8) == 0) 198 | { 199 | addbytetostream (cByte); 200 | } 201 | else 202 | { 203 | addbits (cByte, 8); 204 | } 205 | } 206 | 207 | void CJOCh264bitstream::addexpgolombunsigned (unsigned long lval) 208 | { 209 | //it implements unsigned exp golomb coding 210 | 211 | unsigned long lvalint = lval + 1; 212 | int nnumbits = log2 (lvalint) + 1; 213 | 214 | for (int n = 0; n < (nnumbits-1); n++) 215 | addbits(0,1); 216 | 217 | addbits(lvalint,nnumbits); 218 | } 219 | 220 | void CJOCh264bitstream::addexpgolombsigned (long lval) 221 | { 222 | //it implements a signed exp golomb coding 223 | 224 | unsigned long lvalint = abs(lval) * 2 - 1; 225 | if (lval <= 0) 226 | lvalint = 2 * abs(lval); 227 | 228 | addexpgolombunsigned(lvalint); 229 | } 230 | 231 | void CJOCh264bitstream::add4bytesnoemulationprevention (unsigned int nVal, bool bDoAlign) 232 | { 233 | //Used to add NAL header stream 234 | //Remember: NAL header is byte oriented 235 | 236 | if (bDoAlign == true) 237 | dobytealign(); 238 | 239 | if ((m_nLastbitinbuffer % 8) != 0) 240 | throw "Error: Save to file must be byte aligned"; 241 | 242 | while (m_nLastbitinbuffer != 0) 243 | savebufferbyte(); 244 | 245 | unsigned char cbyte = (nVal & 0xFF000000)>>24; 246 | fwrite(&cbyte, 1, 1, m_pOutFile); 247 | 248 | cbyte = (nVal & 0x00FF0000)>>16; 249 | fwrite(&cbyte, 1, 1, m_pOutFile); 250 | 251 | cbyte = (nVal & 0x0000FF00)>>8; 252 | fwrite(&cbyte, 1, 1, m_pOutFile); 253 | 254 | cbyte = (nVal & 0x000000FF); 255 | fwrite(&cbyte, 1, 1, m_pOutFile); 256 | } 257 | 258 | void CJOCh264bitstream::close() 259 | { 260 | //Flush the data in stream buffer 261 | 262 | dobytealign(); 263 | 264 | while (m_nLastbitinbuffer != 0) 265 | savebufferbyte(); 266 | } 267 | -------------------------------------------------------------------------------- /src/CJOCh264bitstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CJOCh264bitstream.h 3 | * 4 | * Created on: Aug 23, 2014 5 | * Author: Jordi Cenzano (www.jordicenzano.name) 6 | */ 7 | 8 | #ifndef CJOCH264BITSTREAM_H_ 9 | #define CJOCH264BITSTREAM_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | //! h264 bitstream class 17 | /*! 18 | 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) 19 | */ 20 | class CJOCh264bitstream 21 | { 22 | #define BUFFER_SIZE_BITS 24 /*! Buffer size in bits used for emulation prevention */ 23 | #define BUFFER_SIZE_BYTES (24/8) /*! Buffer size in bytes used for emulation prevention */ 24 | 25 | #define H264_EMULATION_PREVENTION_BYTE 0x03 /*! Emulation prevention byte */ 26 | 27 | private: 28 | 29 | /*! Buffer */ 30 | unsigned char m_buffer[BUFFER_SIZE_BITS]; 31 | 32 | /*! Bit buffer index */ 33 | unsigned int m_nLastbitinbuffer; 34 | 35 | /*! Starting byte indicator */ 36 | unsigned int m_nStartingbyte; 37 | 38 | /*! Pointer to output file */ 39 | FILE *m_pOutFile; 40 | 41 | //! Clears the buffer 42 | void clearbuffer(); 43 | 44 | //! Returns the nNumbit value (1 or 0) of lval 45 | /*! 46 | \param lval number to extract the nNumbit value 47 | \param nNumbit Bit position that we want to know if its 1 or 0 (from 0 to 63) 48 | \return bit value (1 or 0) 49 | */ 50 | static int getbitnum (unsigned long lval, int nNumbit); 51 | 52 | //! Adds 1 bit to the end of h264 bitstream 53 | /*! 54 | \param nVal bit to add at the end of h264 bitstream 55 | */ 56 | void addbittostream (int nVal); 57 | 58 | //! Adds 8 bit to the end of h264 bitstream (it is optimized for byte aligned situations) 59 | /*! 60 | \param nVal byte to add at the end of h264 bitstream (from 0 to 255) 61 | */ 62 | void addbytetostream (int nVal); 63 | 64 | //! Save all buffer to file 65 | /*! 66 | \param bemulationprevention Indicates if it will insert the emulation prevention byte or not (when it is needed) 67 | */ 68 | void savebufferbyte(bool bemulationprevention = true); 69 | 70 | public: 71 | //! Constructor 72 | /*! 73 | \param pOutBinaryFile The output file pointer 74 | */ 75 | CJOCh264bitstream(FILE *pOutBinaryFile); 76 | 77 | //! Destructor 78 | virtual ~CJOCh264bitstream(); 79 | 80 | //! Add 4 bytes to h264 bistream without taking into acount the emulation prevention. Used to add the NAL header to the h264 bistream 81 | /*! 82 | \param nVal The 32b value to add 83 | \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 84 | */ 85 | void add4bytesnoemulationprevention (unsigned int nVal, bool bDoAlign = false); 86 | 87 | //! Adds nNumbits of lval to the end of h264 bitstream 88 | /*! 89 | \param nVal value to add at the end of the h264 stream (only the LAST nNumbits will be added) 90 | \param nNumbits number of bits of lval that will be added to h264 stream (counting from left) 91 | */ 92 | void addbits (unsigned long lval, int nNumbits); 93 | 94 | //! Adds lval to the end of h264 bitstream using exp golomb coding for unsigned values 95 | /*! 96 | \param nVal value to add at the end of the h264 stream 97 | */ 98 | void addexpgolombunsigned (unsigned long lval); 99 | 100 | //! Adds lval to the end of h264 bitstream using exp golomb coding for signed values 101 | /*! 102 | \param nVal value to add at the end of the h264 stream 103 | */ 104 | void addexpgolombsigned (long lval); 105 | 106 | //! Adds 0 to the end of h264 bistream in order to leave a byte aligned stream (It will insert seven 0 maximum) 107 | void dobytealign(); 108 | 109 | //! Adds cByte (8 bits) to the end of h264 bitstream. This function it is optimized in byte aligned streams. 110 | /*! 111 | \param cByte value to add at the end of the h264 stream (from 0 to 255) 112 | */ 113 | void addbyte (unsigned char cByte); 114 | 115 | //! Close the h264 stream saving to disk the last remaing bits in buffer 116 | void close(); 117 | }; 118 | 119 | #endif /* CJOCH264BITSTREAM_H_ */ 120 | -------------------------------------------------------------------------------- /src/CJOCh264encoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CJOCh264encoder.cpp 3 | * 4 | * Created on: Aug 17, 2014 5 | * Author: Jordi Cenzano (www.jordicenzano.name) 6 | */ 7 | 8 | #include "CJOCh264encoder.h" 9 | 10 | //Private functions 11 | 12 | //Contructor 13 | CJOCh264encoder::CJOCh264encoder(FILE *pOutFile):CJOCh264bitstream(pOutFile) 14 | { 15 | m_lNumFramesAdded = 0; 16 | 17 | memset (&m_frame, 0, sizeof (frame_t)); 18 | m_nFps = 25; 19 | } 20 | 21 | //Destructor 22 | CJOCh264encoder::~CJOCh264encoder() 23 | { 24 | free_video_src_frame (); 25 | } 26 | 27 | //Free the allocated video frame mem 28 | void CJOCh264encoder::free_video_src_frame () 29 | { 30 | if (m_frame.yuv420pframe.pYCbCr != NULL) 31 | free (m_frame.yuv420pframe.pYCbCr); 32 | 33 | memset (&m_frame, 0, sizeof (frame_t)); 34 | } 35 | 36 | //Alloc mem to store a video frame 37 | void CJOCh264encoder::alloc_video_src_frame () 38 | { 39 | if (m_frame.yuv420pframe.pYCbCr != NULL) 40 | throw "Error: null values in frame"; 41 | 42 | int nYsize = m_frame.nYwidth * m_frame.nYheight; 43 | int nCsize = m_frame.nCwidth * m_frame.nCheight; 44 | m_frame.nyuv420pframesize = nYsize + nCsize + nCsize; 45 | 46 | m_frame.yuv420pframe.pYCbCr = (unsigned char*) malloc (sizeof (unsigned char) * m_frame.nyuv420pframesize); 47 | 48 | if (m_frame.yuv420pframe.pYCbCr == NULL) 49 | throw "Error: memory alloc"; 50 | } 51 | 52 | //Creates and saves the NAL SPS (including VUI) (one per file) 53 | void CJOCh264encoder::create_sps (int nImW, int nImH, int nMbW, int nMbH, int nFps, int nSARw, int nSARh) 54 | { 55 | add4bytesnoemulationprevention (0x000001); // NAL header 56 | addbits (0x0,1); // forbidden_bit 57 | addbits (0x3,2); // nal_ref_idc 58 | addbits (0x7,5); // nal_unit_type : 7 ( SPS ) 59 | addbits (0x42,8); // profile_idc = baseline ( 0x42 ) 60 | addbits (0x0,1); // constraint_set0_flag 61 | addbits (0x0,1); // constraint_set1_flag 62 | addbits (0x0,1); // constraint_set2_flag 63 | addbits (0x0,1); // constraint_set3_flag 64 | addbits (0x0,1); // constraint_set4_flag 65 | addbits (0x0,1); // constraint_set5_flag 66 | addbits (0x0,2); // reserved_zero_2bits /* equal to 0 */ 67 | addbits (0x0a,8); // level_idc: 3.1 (0x0a) 68 | addexpgolombunsigned(0); // seq_parameter_set_id 69 | addexpgolombunsigned(0); // log2_max_frame_num_minus4 70 | addexpgolombunsigned(0); // pic_order_cnt_type 71 | addexpgolombunsigned(0); // log2_max_pic_order_cnt_lsb_minus4 72 | addexpgolombunsigned(0); // max_num_refs_frames 73 | addbits(0x0,1); // gaps_in_frame_num_value_allowed_flag 74 | 75 | int nWinMbs = nImW / nMbW; 76 | addexpgolombunsigned(nWinMbs-1); // pic_width_in_mbs_minus_1 77 | int nHinMbs = nImH / nMbH; 78 | addexpgolombunsigned(nHinMbs-1); // pic_height_in_map_units_minus_1 79 | 80 | addbits(0x1,1); // frame_mbs_only_flag 81 | addbits(0x0,1); // direct_8x8_interfernce 82 | addbits(0x0,1); // frame_cropping_flag 83 | addbits(0x1,1); // vui_parameter_present 84 | 85 | //VUI parameters (AR, timming) 86 | addbits(0x1,1); //aspect_ratio_info_present_flag 87 | addbits(0xFF,8); //aspect_ratio_idc = Extended_SAR 88 | 89 | //AR 90 | addbits(nSARw, 16); //sar_width 91 | addbits(nSARh, 16); //sar_height 92 | 93 | addbits(0x0,1); //overscan_info_present_flag 94 | addbits(0x0,1); //video_signal_type_present_flag 95 | addbits(0x0,1); //chroma_loc_info_present_flag 96 | addbits(0x1,1); //timing_info_present_flag 97 | 98 | unsigned int nnum_units_in_tick = TIME_SCALE_IN_HZ / (2*nFps); 99 | addbits(nnum_units_in_tick,32); //num_units_in_tick 100 | addbits(TIME_SCALE_IN_HZ,32); //time_scale 101 | addbits(0x1,1); //fixed_frame_rate_flag 102 | 103 | addbits(0x0,1); //nal_hrd_parameters_present_flag 104 | addbits(0x0,1); //vcl_hrd_parameters_present_flag 105 | addbits(0x0,1); //pic_struct_present_flag 106 | addbits(0x0,1); //bitstream_restriction_flag 107 | //END VUI 108 | 109 | addbits(0x1,1); // rbsp stop bit 110 | 111 | dobytealign(); 112 | } 113 | 114 | //Creates and saves the NAL PPS (one per file) 115 | void CJOCh264encoder::create_pps () 116 | { 117 | add4bytesnoemulationprevention (0x000001); // NAL header 118 | addbits (0x0,1); // forbidden_bit 119 | addbits (0x3,2); // nal_ref_idc 120 | addbits (0x8,5); // nal_unit_type : 8 ( PPS ) 121 | addexpgolombunsigned(0); // pic_parameter_set_id 122 | addexpgolombunsigned(0); // seq_parameter_set_id 123 | addbits (0x0,1); // entropy_coding_mode_flag 124 | addbits (0x0,1); // bottom_field_pic_order_in frame_present_flag 125 | addexpgolombunsigned(0); // nun_slices_groups_minus1 126 | addexpgolombunsigned(0); // num_ref_idx10_default_active_minus 127 | addexpgolombunsigned(0); // num_ref_idx11_default_active_minus 128 | addbits (0x0,1); // weighted_pred_flag 129 | addbits (0x0,2); // weighted_bipred_idc 130 | addexpgolombsigned(0); // pic_init_qp_minus26 131 | addexpgolombsigned(0); // pic_init_qs_minus26 132 | addexpgolombsigned(0); // chroma_qp_index_offset 133 | addbits (0x0,1); //deblocking_filter_present_flag 134 | addbits (0x0,1); // constrained_intra_pred_flag 135 | addbits (0x0,1); //redundant_pic_ent_present_flag 136 | addbits(0x1,1); // rbsp stop bit 137 | 138 | dobytealign(); 139 | } 140 | 141 | //Creates and saves the NAL SLICE (one per frame) 142 | void CJOCh264encoder::create_slice_header(unsigned long lFrameNum) 143 | { 144 | add4bytesnoemulationprevention (0x000001); // NAL header 145 | addbits (0x0,1); // forbidden_bit 146 | addbits (0x3,2); // nal_ref_idc 147 | addbits (0x5,5); // nal_unit_type : 5 ( Coded slice of an IDR picture ) 148 | addexpgolombunsigned(0); // first_mb_in_slice 149 | addexpgolombunsigned(7); // slice_type 150 | addexpgolombunsigned(0); // pic_param_set_id 151 | 152 | unsigned char cFrameNum = 0; // FrameNum=0 for IDR frames [otherwise lFrameNum % 16; //(2⁴)] 153 | addbits (cFrameNum,4); // frame_num ( numbits = v = log2_max_frame_num_minus4 + 4) 154 | 155 | unsigned long lidr_pic_id = lFrameNum % 512; // could be % 65536 as valid range 0..65536 156 | //idr_pic_flag = 1 157 | addexpgolombunsigned(lidr_pic_id); // idr_pic_id 158 | addbits(0x0,4); // pic_order_cnt_lsb (numbits = v = log2_max_fpic_order_cnt_lsb_minus4 + 4) 159 | addbits(0x0,1); //no_output_of_prior_pics_flag 160 | addbits(0x0,1); //long_term_reference_flag 161 | addexpgolombsigned(0); //slice_qp_delta 162 | 163 | //Probably NOT byte aligned!!! 164 | } 165 | 166 | //Creates and saves the SLICE footer (one per SLICE) 167 | void CJOCh264encoder::create_slice_footer() 168 | { 169 | addbits(0x1,1); // rbsp stop bit 170 | } 171 | 172 | //Creates and saves the macroblock header(one per macroblock) 173 | void CJOCh264encoder::create_macroblock_header () 174 | { 175 | addexpgolombunsigned(25); // mb_type (I_PCM) 176 | } 177 | 178 | //Creates & saves a macroblock (coded INTRA 16x16) 179 | void CJOCh264encoder::create_macroblock(unsigned int nYpos, unsigned int nXpos) 180 | { 181 | unsigned int x,y; 182 | 183 | create_macroblock_header(); 184 | 185 | dobytealign(); 186 | 187 | //Y 188 | unsigned int nYsize = m_frame.nYwidth * m_frame.nYheight; 189 | for(y = nYpos * m_frame.nYmbheight; y < (nYpos+1) * m_frame.nYmbheight; y++) 190 | { 191 | for (x = nXpos * m_frame.nYmbwidth; x < (nXpos+1) * m_frame.nYmbwidth; x++) 192 | { 193 | addbyte (m_frame.yuv420pframe.pYCbCr[(y * m_frame.nYwidth + x)]); 194 | } 195 | } 196 | 197 | //Cb 198 | unsigned int nCsize = m_frame.nCwidth * m_frame.nCheight; 199 | for(y = nYpos * m_frame.nCmbheight; y < (nYpos+1) * m_frame.nCmbheight; y++) 200 | { 201 | for (x = nXpos * m_frame.nCmbwidth; x < (nXpos+1) * m_frame.nCmbwidth; x++) 202 | { 203 | addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + (y * m_frame.nCwidth + x)]); 204 | } 205 | } 206 | 207 | //Cr 208 | for(y = nYpos * m_frame.nCmbheight; y < (nYpos+1) * m_frame.nCmbheight; y++) 209 | { 210 | for (x = nXpos * m_frame.nCmbwidth; x < (nXpos+1) * m_frame.nCmbwidth; x++) 211 | { 212 | addbyte(m_frame.yuv420pframe.pYCbCr[nYsize + nCsize + (y * m_frame.nCwidth + x)]); 213 | } 214 | } 215 | } 216 | 217 | 218 | //public functions 219 | 220 | //Initilizes the h264 coder (mini-coder) 221 | void CJOCh264encoder::IniCoder (int nImW, int nImH, int nImFps, CJOCh264encoder::enSampleFormat SampleFormat, int nSARw, int nSARh) 222 | { 223 | m_lNumFramesAdded = 0; 224 | 225 | if (SampleFormat != SAMPLE_FORMAT_YUV420p) 226 | throw "Error: SAMPLE FORMAT not allowed. Only yuv420p is allowed in this version"; 227 | 228 | free_video_src_frame (); 229 | 230 | //Ini vars 231 | m_frame.sampleformat = SampleFormat; 232 | m_frame.nYwidth = nImW; 233 | m_frame.nYheight = nImH; 234 | if (SampleFormat == SAMPLE_FORMAT_YUV420p) 235 | { 236 | //Set macroblock Y size 237 | m_frame.nYmbwidth = MACROBLOCK_Y_WIDTH; 238 | m_frame.nYmbheight = MACROBLOCK_Y_HEIGHT; 239 | 240 | //Set macroblock C size (in YUV420 is 1/2 of Y) 241 | m_frame.nCmbwidth = MACROBLOCK_Y_WIDTH/2; 242 | m_frame.nCmbheight = MACROBLOCK_Y_HEIGHT/2; 243 | 244 | //Set C size 245 | m_frame.nCwidth = m_frame.nYwidth / 2; 246 | m_frame.nCheight = m_frame.nYheight / 2; 247 | 248 | //In this implementation only picture sizes multiples of macroblock size (16x16) are allowed 249 | if (((nImW % MACROBLOCK_Y_WIDTH) != 0)||((nImH % MACROBLOCK_Y_HEIGHT) != 0)) 250 | throw "Error: size not allowed. Only multiples of macroblock are allowed (macroblock size is: 16x16)"; 251 | } 252 | m_nFps = nImFps; 253 | 254 | //Alloc mem for 1 frame 255 | alloc_video_src_frame (); 256 | 257 | //Create h264 SPS & PPS 258 | create_sps (m_frame.nYwidth , m_frame.nYheight, m_frame.nYmbwidth, m_frame.nYmbheight, nImFps , nSARw, nSARh); 259 | create_pps (); 260 | } 261 | 262 | //Returns the frame pointer to load the video frame 263 | void* CJOCh264encoder::GetFramePtr() 264 | { 265 | if (m_frame.yuv420pframe.pYCbCr == NULL) 266 | throw "Error: video frame is null (not initialized)"; 267 | 268 | return (void*) m_frame.yuv420pframe.pYCbCr; 269 | } 270 | 271 | //Returns the the allocated size for video frame 272 | unsigned int CJOCh264encoder::GetFrameSize() 273 | { 274 | return m_frame.nyuv420pframesize; 275 | } 276 | 277 | //Codifies & save the video frame (it only uses 16x16 intra PCM -> NO COMPRESSION!) 278 | void CJOCh264encoder::CodeAndSaveFrame() 279 | { 280 | //The slice header is not byte aligned, so the first macroblock header is not byte aligned 281 | create_slice_header (m_lNumFramesAdded); 282 | 283 | //Loop over macroblock size 284 | unsigned int y,x; 285 | for (y = 0; y < m_frame.nYheight / m_frame.nYmbheight; y++) 286 | { 287 | for (x = 0; x < m_frame.nYwidth / m_frame.nYmbwidth; x++) 288 | { 289 | create_macroblock(y, x); 290 | } 291 | } 292 | 293 | create_slice_footer(); 294 | dobytealign(); 295 | 296 | m_lNumFramesAdded++; 297 | } 298 | 299 | //Returns the number of codified frames 300 | unsigned long CJOCh264encoder::GetSavedFrames() 301 | { 302 | return m_lNumFramesAdded; 303 | } 304 | 305 | //Closes the h264 coder saving the last bits in the buffer 306 | void CJOCh264encoder::CloseCoder () 307 | { 308 | close(); 309 | } 310 | -------------------------------------------------------------------------------- /src/CJOCh264encoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CJOCh264encoder.h 3 | * 4 | * Created on: Aug 17, 2014 5 | * Author: Jordi Cenzano (www.jordicenzano.name) 6 | */ 7 | 8 | #ifndef CJOCH264ENCODER_H_ 9 | #define CJOCH264ENCODER_H_ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "CJOCh264bitstream.h" 16 | 17 | //! h264 encoder class 18 | /*! 19 | It is used to create the h264 compliant stream 20 | */ 21 | class CJOCh264encoder : CJOCh264bitstream 22 | { 23 | public: 24 | 25 | /** 26 | * Allowed sample formats 27 | */ 28 | enum enSampleFormat 29 | { 30 | SAMPLE_FORMAT_YUV420p//!< SAMPLE_FORMAT_YUV420p 31 | }; 32 | 33 | private: 34 | /*!Set the used Y macroblock size for I PCM in YUV420p */ 35 | #define MACROBLOCK_Y_WIDTH 16 36 | #define MACROBLOCK_Y_HEIGHT 16 37 | 38 | /*!Set time base in Hz */ 39 | #define TIME_SCALE_IN_HZ 27000000 40 | 41 | /*!Pointer to pixels */ 42 | typedef struct 43 | { 44 | unsigned char *pYCbCr; 45 | }YUV420p_frame_t; 46 | 47 | /*! Frame */ 48 | typedef struct 49 | { 50 | enSampleFormat sampleformat; /*!< Sample format */ 51 | unsigned int nYwidth; /*!< Y (luminance) block width in pixels */ 52 | unsigned int nYheight; /*!< Y (luminance) block height in pixels */ 53 | unsigned int nCwidth; /*!< C (Crominance) block width in pixels */ 54 | unsigned int nCheight; /*!< C (Crominance) block height in pixels */ 55 | 56 | unsigned int nYmbwidth; /*!< Y (luminance) macroblock width in pixels */ 57 | unsigned int nYmbheight; /*!< Y (luminance) macroblock height in pixels */ 58 | unsigned int nCmbwidth; /*!< Y (Crominance) macroblock width in pixels */ 59 | unsigned int nCmbheight; /*!< Y (Crominance) macroblock height in pixels */ 60 | 61 | YUV420p_frame_t yuv420pframe; /*!< Pointer to current frame data */ 62 | unsigned int nyuv420pframesize; /*!< Size in bytes of yuv420pframe */ 63 | }frame_t; 64 | 65 | /*! The frame var*/ 66 | frame_t m_frame; 67 | 68 | /*! The frames per second var*/ 69 | int m_nFps; 70 | 71 | /*! Number of frames sent to the output */ 72 | unsigned long m_lNumFramesAdded; 73 | 74 | //! Frees the frame yuv420pframe allocated memory 75 | void free_video_src_frame (); 76 | 77 | //! Allocs the frame yuv420pframe memory according to the frame properties 78 | void alloc_video_src_frame (); 79 | 80 | //! Creates SPS NAL and add it to the output 81 | /*! 82 | \param nImW Frame width in pixels 83 | \param nImH Frame height in pixels 84 | \param nMbW macroblock width in pixels 85 | \param nMbH macroblock height in pixels 86 | \param nFps frames x second (tipical values are: 25, 30, 50, etc) 87 | \param nSARw Indicates the horizontal size of the sample aspect ratio (tipical values are:1, 4, 16, etc) 88 | \param nSARh Indicates the vertical size of the sample aspect ratio (tipical values are:1, 3, 9, etc) 89 | */ 90 | void create_sps (int nImW, int nImH, int nMbW, int nMbH, int nFps, int nSARw, int nSARh); 91 | 92 | //! Creates PPS NAL and add it to the output 93 | void create_pps (); 94 | 95 | //! Creates Slice NAL and add it to the output 96 | /*! 97 | \param lFrameNum number of frame 98 | */ 99 | void create_slice_header(unsigned long lFrameNum); 100 | 101 | //! Creates macroblock header and add it to the output 102 | void create_macroblock_header (); 103 | 104 | //! Creates the slice footer and add it to the output 105 | void create_slice_footer(); 106 | 107 | //! Creates SPS NAL and add it to the output 108 | /*! 109 | \param nYpos First vertical macroblock pixel inside the frame 110 | \param nYpos nXpos horizontal macroblock pixel inside the frame 111 | */ 112 | void create_macroblock(unsigned int nYpos, unsigned int nXpos); 113 | 114 | public: 115 | //! Constructor 116 | /*! 117 | \param pOutFile The output file pointer 118 | */ 119 | CJOCh264encoder(FILE *pOutFile); 120 | 121 | //! Destructor 122 | virtual ~CJOCh264encoder(); 123 | 124 | //! Initializes the coder 125 | /*! 126 | \param nImW Frame width in pixels 127 | \param nImH Frame height in pixels 128 | \param nFps Desired frames per second of the output file (typical values are: 25, 30, 50, etc) 129 | \param SampleFormat Sample format if the input file. In this implementation only SAMPLE_FORMAT_YUV420p is allowed 130 | \param nSARw Indicates the horizontal size of the sample aspect ratio (typical values are:1, 4, 16, etc) 131 | \param nSARh Indicates the vertical size of the sample aspect ratio (typical values are:1, 3, 9, etc) 132 | */ 133 | void IniCoder (int nImW, int nImH, int nImFps, CJOCh264encoder::enSampleFormat SampleFormat, int nSARw = 1, int nSARh = 1); 134 | 135 | //! Returns the frame pointer 136 | /*! 137 | \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 138 | */ 139 | void* GetFramePtr(); 140 | 141 | //! Returns the allocated frame memory in bytes 142 | /*! 143 | \return The allocated memory to store the frame data 144 | */ 145 | unsigned int GetFrameSize(); 146 | 147 | //! It codes the frame that is in frame memory a it saves the coded data to disc 148 | void CodeAndSaveFrame(); 149 | 150 | //! Returns number of coded frames 151 | /*! 152 | \return The number of coded frames 153 | */ 154 | unsigned long GetSavedFrames(); 155 | 156 | //! Flush all data and save the trailing bits 157 | void CloseCoder (); 158 | }; 159 | 160 | #endif /* CJOCH264ENCODER_H_ */ 161 | -------------------------------------------------------------------------------- /src/h264simpleCoder.cpp: -------------------------------------------------------------------------------- 1 | //============================================================================ 2 | // Name : h264simpleCoder.cpp 3 | // Author : Jordi Cenzano (www.jordicenzano.name) 4 | // Version : 1.0 5 | // Copyright : Copyright Jordi Cenzano 2014 6 | // Description : Simple h264 encoder 7 | //============================================================================ 8 | 9 | #include 10 | #include "CJOCh264encoder.h" 11 | 12 | using namespace std; 13 | 14 | int main(int argc, char **argv) 15 | { 16 | int nRc = 1; 17 | 18 | puts("Simple h264 coder by Jordi Cenzano (www.jordicenzano.name)"); 19 | puts("This is NOT a video compressor, only uses I_PCM macroblocks (intra without compression)"); 20 | puts("It is made only for learning purposes"); 21 | puts("**********************************************************"); 22 | 23 | if (argc < 3) 24 | { 25 | puts("------------------------------------------------------------------------"); 26 | puts("Usage: h264mincoder input.yuv output.h264 [image width] [image height] [fps] [AR SARw] [AR SARh]"); 27 | puts("Default parameters: Image width=128 Image height=96 Fps=25 SARw=1 SARh=1"); 28 | puts("Assumptions: Input file is yuv420p"); 29 | puts("------------------------------------------------------------------------"); 30 | 31 | return nRc; 32 | } 33 | 34 | char szInputFile[512]; 35 | char szOutputFile[512]; 36 | int nImWidth = 128; 37 | int nImHeight = 96; 38 | int nFps = 25; 39 | int nSARw = 1; 40 | int nSARh = 1; 41 | 42 | //Get input file 43 | strncpy (szInputFile,argv[1],512); 44 | 45 | //Get output file 46 | strncpy (szOutputFile,argv[2],512); 47 | 48 | //Get image width 49 | if (argc > 3) 50 | { 51 | nImWidth = (int) atol (argv[3]); 52 | if (nImWidth == 0) 53 | puts ("Error reading image width input parameter"); 54 | } 55 | 56 | //Get image height 57 | if (argc > 4) 58 | { 59 | nImHeight = (int) atol (argv[4]); 60 | if (nImHeight == 0) 61 | puts ("Error reading image height input parameter"); 62 | } 63 | 64 | //Get fps 65 | if (argc > 5) 66 | { 67 | nFps = (int) atol (argv[5]); 68 | if (nFps == 0) 69 | puts ("Error reading fps input parameter"); 70 | } 71 | 72 | //Get SARw 73 | if (argc > 6) 74 | { 75 | nSARw = (int) atol (argv[6]); 76 | if (nSARw == 0) 77 | puts ("Error reading AR SARw input parameter"); 78 | } 79 | 80 | //Get SARh 81 | if (argc > 7) 82 | { 83 | nSARh = (int) atol (argv[7]); 84 | if (nSARh == 0) 85 | puts ("Error reading AR SARh input parameter"); 86 | } 87 | 88 | FILE *pfsrc = NULL; 89 | FILE *pfdst = NULL; 90 | 91 | pfsrc = fopen (szInputFile,"rb"); 92 | if (pfsrc == NULL) 93 | { 94 | puts ("Error opening source file"); 95 | return nRc; 96 | } 97 | 98 | pfdst = fopen (szOutputFile,"wb"); 99 | if (pfdst == NULL) 100 | { 101 | puts ("Error opening destination file"); 102 | return nRc; 103 | } 104 | 105 | try 106 | { 107 | //Instantiate the h264 coder 108 | CJOCh264encoder *ph264encoder = new CJOCh264encoder(pfdst); 109 | 110 | //Initialize the h264 coder with frame parameters 111 | ph264encoder->IniCoder(nImWidth,nImHeight,nFps,CJOCh264encoder::SAMPLE_FORMAT_YUV420p, nSARw, nSARh); 112 | 113 | int nSavedFrames = 0; 114 | char szLog[256]; 115 | 116 | //Iterate through all frames 117 | while (! feof(pfsrc)) 118 | { 119 | //Get frame pointer to fill 120 | void *pFrame = ph264encoder->GetFramePtr (); 121 | 122 | //Get the size allocated in pFrame 123 | unsigned int nFrameSize = ph264encoder->GetFrameSize(); 124 | 125 | //Load frame from disk and load it into pFrame 126 | size_t nreaded = fread (pFrame,1, nFrameSize, pfsrc); 127 | if (nreaded != nFrameSize) 128 | { 129 | if (! feof(pfsrc)) 130 | throw "Error: Reading frame"; 131 | } 132 | else 133 | { 134 | //Encode & save frame 135 | ph264encoder->CodeAndSaveFrame(); 136 | 137 | //Get the number of saved frames 138 | nSavedFrames = ph264encoder->GetSavedFrames(); 139 | 140 | //Show the number of saved / encoded frames iin console 141 | sprintf(szLog,"Saved frame num: %d", nSavedFrames - 1); 142 | puts (szLog); 143 | } 144 | } 145 | 146 | //Close encoder 147 | ph264encoder->CloseCoder(); 148 | 149 | //Set return code to 0 150 | nRc = 0; 151 | } 152 | catch (const char *szErrorDesc) 153 | { 154 | //Show the error description on console 155 | puts (szErrorDesc); 156 | } 157 | 158 | //Close previously opened files 159 | if (pfsrc != NULL) 160 | fclose (pfsrc); 161 | 162 | if (pfdst != NULL) 163 | fclose (pfdst); 164 | 165 | return nRc; 166 | } 167 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | # Makefile for h264simpleCoder 2 | # By Jordi Cenzano (www.jordicenzano.name) 3 | CC=g++ 4 | CFLAGS=-c -Wall 5 | HEADERS = CJOCh264bitstream.h CJOCh264encoder.h 6 | 7 | all: h264simpleCoder 8 | 9 | h264simpleCoder: h264simpleCoder.o CJOCh264encoder.o CJOCh264bitstream.o 10 | $(CC) h264simpleCoder.o CJOCh264encoder.o CJOCh264bitstream.o -o h264simpleCoder 11 | 12 | h264simpleCoder.o: h264simpleCoder.cpp $(HEADERS) 13 | $(CC) $(CFLAGS) h264simpleCoder.cpp 14 | 15 | CJOCh264encoder.o: CJOCh264encoder.cpp $(HEADERS) 16 | $(CC) $(CFLAGS) CJOCh264encoder.cpp 17 | 18 | CJOCh264bitstream.o: CJOCh264bitstream.cpp $(HEADERS) 19 | $(CC) $(CFLAGS) CJOCh264bitstream.cpp 20 | 21 | clean: 22 | rm -rf *o h264simpleCoder 23 | --------------------------------------------------------------------------------