Parsing hevc bitstream

11,921

If all you want to do is take some nal unit packets (e.g. depending on layer id and temporal id), and you don't need to modify the VPS, SPS, PPS, Slice Header etc., then you can also easily implement this yourself:

The corresponding syntax is stated in the Annex B "Byte Stream Format" of the HEVC standard.

In short:

  1. Search the bitstream file for the pattern 0x000001, which separates all the nal units. Additionally, there can be a 0x00 byte before this pattern, if the next nal unit is the first nal unit of an access unit (access unit = all nal units for decoding a whole frame).

  2. Read the nal unit header according to section 7.3.1.2 of the HEVC standard and keep/delete the nal units based on whatever criteria you want. Make sure you keep the parameter sets (nal unit types 32, 33 and 34 according to Table 7-1 of the HEVC standard).

  3. Assemble all the nal units in a new file and make sure you always have the 0x000001 sequence inbetween.

I once did something similar using Python, which worked pretty well. If you want to make reading the nal unit headers easier, use the bitstring module. If you want to do this and have more detailed questions, you can pm me for help if you want to.

Edit: Regarding the code you posted: Why do you put "-8" when assigning the position in the BitStream object (s.pos =pcks[0]-8 and s.pos = pcks[1]-8)? This should be +24 (24 bits = 3 bytes = length of the nal unit separator 0x000001), to start reading after the separator to get the nal unit. However, you have to take this into account when reading data: no_p = pcks[1]-pcks[0] should be no_p = pcks[1]-pcks[0]-24, because you start reading after the nal unit separator.

If you got confused that the first found position (pcks[0]) is 8, not 0: Before each nal unit separator, there can be an arbitrary number of zero-bytes, according to the annex B of the HEVC standard. Usually, there is always one zero-byte before each access unit.

Share:
11,921
zinon
Author by

zinon

Ph.D. on adaptive video delivery for real-time applications and mHealth systems.

Updated on June 11, 2022

Comments

  • zinon
    zinon about 2 years

    Is there a way to parse an HEVC bitstream file?

    I want to be able to create a new bitstream file having selected nal unit packets selected from the original bitstream file.

    Edit: I inserted my code. Please find my bitstream file here.

    #library for searching in a string
    import re
    
    #library to keep dictionary order
    import collections
    import bitstring
    from bitstring import BitStream, BitArray, ConstBitStream, pack
    from bitstring import ByteStore, offsetcopy
    
    #read bitstream file
    s = BitStream(filename='11LTCCA_560x416_50Hz_8b_P420_GOP8_IP48_200frms_QP28.HEVC.str')
    
    #find no of packets
    pcks = list(s.findall('0x000001', bytealigned=True))
    
    print len(pcks)
    
    #set the current position, in the beginning of the nal unit.
    s.pos =pcks[0]-8
    print s.pos
    
    #find the number of bits of first nal packet
    no_p = pcks[1]-pcks[0]
    
    
    forbidden_zero_bit = s.read(1)
    nal_unit_type = s.read('uint:6')
    
    # go to the beginning of the second nal unit
    s.read(no_p)
    # print nal unit type of the 1st packet
    print nal_unit_type
    
    no_p = pcks[2]-pcks[1]
    s.pos = pcks[1]-8
    print s.pos
    forbidden_zero_bit = s.read(1)
    nal_unit_type = s.read('uint:6')
    s.read(no_p)
    print nal_unit_type
    
  • zinon
    zinon almost 10 years
    Thank you very much! I'll develop it using Python too. I will try it soon and if I' ll have any question I'll pm you!
  • zinon
    zinon almost 10 years
    Can you please help me with the parsing? I need some more help to read the headers. The find() method does not really work in bitstreams... This site does not support pm...
  • Bastian35022
    Bastian35022 almost 10 years
    I assume you opened the bytestream and it's in a variable called "data", and you have the position "pos" of the beginning of a nal unit. Then "data[pos]" would be the first byte of the nal unit header, and "data[pos+1]" the second byte of the nal unit header. You can then get the nal unit type using "ord(data[pos]) & int(0b01111110)", and the temporal id using "ord(data[pos+1]) & int(0b00000111)". the layer id would be a little more difficult, as it has bits in both bytes.
  • zinon
    zinon almost 10 years
    Thank you! Using & you mean the'+' character? Is there a way to iterate through the bytestream and read the sequential nal type units?
  • Bastian35022
    Bastian35022 almost 10 years
    Using & i mean the '&' character ;) this is a bitwise operator. Let me explain in more detail: The first bit of the NAL unit header must always be zero and carries no information for you. the next 6 bits make up the nal unit type in the form of an unsigned integer, so if the first byte of the nal unit header is e.g. 0b01000011, you nal unit type is 0b100001 = 33 (2^5+2^0), and you get this number using the bitwise operation described above. One thing i forgot: you have to bitshift the whole thing to get the correct results. So just do "(ord(data[pos]) & int(0b01111110))>>1".
  • zinon
    zinon almost 10 years
    OK Thank you! I parse the bitstream file using s = BitStream(filename= ... ) Is there a way to iterate through this bitstream to get all the nal unit types of the included packets?
  • Bastian35022
    Bastian35022 almost 10 years
    You are using the bitstring module? Then there should be a find method, of the base class of "BitStream", "Bits": pythonhosted.org/bitstring/constbitarray.html#bitstring.Bits . Make sure that you do a byte aligned search for the pattern nal unit seperator ('0x000001').
  • Bastian35022
    Bastian35022 almost 10 years
    And if you use the bitstring module anyway, you can make your life easier by using the read methods of 'BitStream's base class 'ConstBitStream': pythonhosted.org/bitstring/…
  • zinon
    zinon almost 10 years
  • zinon
    zinon almost 10 years
    Please join the chat room please!
  • zinon
    zinon almost 10 years
    Unfortunately I still haven't managed to read the nal unit type correctly. I edited my previous post above, including my code. Can you please help me more? Thank you in advance!!
  • zinon
    zinon almost 10 years
    Great!! Thanks a lot!! Is there a way to find the size of the packet, the POC number and the offset?
  • Bastian35022
    Bastian35022 almost 10 years
    Size of the packet (in bits) would be pcks[1]-pcks[0]-24, as described above, or do you mean something different?
  • Bastian35022
    Bastian35022 almost 10 years
    What do you mean by offset? As for the POC number, this information can be extracted from the slice header, but is not too simple. First, you have to parse the slice segment nal unit according to section 7.3.6.1 of the HEVC standard to get the value for slice_pic_order_cnt_lsb, and then you can calculate the POC according to section 8.3.1 of the HEVC standard. I didn't do this before, so i don't have detailed knowledge about this decoding process :(
  • zinon
    zinon almost 10 years
    OK for packet size! Thanks! Offset is a hex number for example 0x00000000 for first packet, 0x00000021 for 2nd, 0x00000093 for 3rd etc. Go to lengaur.com/parser and try my bitstream file. OK about slice segment. I'll try it. Thank you once again!
  • Bastian35022
    Bastian35022 almost 10 years
    You get the offsets of the nal units in this format by converting the byte positions of the nal unit separators to hex strings: hex(pcks[i]/8). The positions on the webside are the positions of access units. You can usually get these by searching for 0x00000001 instead of 0x000001, but i am not too sure myself, if this is completely standard conforming.
  • zinon
    zinon almost 10 years
    Thanks once again! Do you know if there is a way to find the arrival time of each frame or packet?
  • Bastian35022
    Bastian35022 almost 10 years
    If you mean the time when the packet or frame arrived over the network etc., then this information is not included in the HEVC bitstream. It is only dealing with video compression, not transport. For this, you'd need to use tools like tcpdump or tshark i guess, but i'm not too familiar with those, unfortunately :(
  • zinon
    zinon almost 10 years
    No, I mean time of encoding for each frame. If a decode an str stream, I get decoded time for each frame. Is there a corresponding field for encoding time?
  • Bastian35022
    Bastian35022 almost 10 years
    Ah okay. Not that i know of. If there exists something in the Bitstream, i am pretty sure it would in an SEI message (Annex E of the standard), and thus not mandatory, so usually not part of the bitstream.
  • zinon
    zinon almost 10 years
    Yes, I think is a computation using pic_dpb_output_delay. OK, thank you!