changeset 12437:e50f803e26fb draft

Add support for Dolby Vision RPU muxing
author Aruna Matheswaran <aruna@multicorewareinc.com>
date Thu, 27 Sep 2018 14:16:15 +0530
parents b748ee9f4465
children 8eb57eb225d5
files doc/reST/cli.rst source/CMakeLists.txt source/common/frame.cpp source/common/frame.h source/encoder/api.cpp source/encoder/encoder.cpp source/encoder/frameencoder.cpp source/encoder/nal.cpp source/encoder/sei.cpp source/x265.cpp source/x265.h source/x265cli.h
diffstat 12 files changed, 148 insertions(+-), 5 deletions(-) [+]
line wrap: on
line diff
--- a/doc/reST/cli.rst	Fri Sep 28 10:45:23 2018 +0530
+++ b/doc/reST/cli.rst	Thu Sep 27 14:16:15 2018 +0530
@@ -2208,6 +2208,15 @@ Bitstream options
     
     Currently only profile 5 enabled, Default 0 (disabled)
 
+.. option:: --dolby-vision-rpu
+
+    File containing Dolby Vision RPU metadata. If given, x265's Dolby Vision 
+    metadata parser will fill the RPU field of input pictures with the metadata
+    read from the file. The library will interleave access units with RPUs in the 
+    bitstream. Default NULL (disabled).
+   
+    **CLI ONLY**
+
 .. option:: --info, --no-info
 
 	Emit an informational SEI with the stream headers which describes
--- a/source/CMakeLists.txt	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/CMakeLists.txt	Thu Sep 27 14:16:15 2018 +0530
@@ -29,7 +29,7 @@ option(NATIVE_BUILD "Target the build CP
 option(STATIC_LINK_CRT "Statically link C runtime for release builds" OFF)
 mark_as_advanced(FPROFILE_USE FPROFILE_GENERATE NATIVE_BUILD)
 # X265_BUILD must be incremented each time the public API is changed
-set(X265_BUILD 166)
+set(X265_BUILD 167)
 configure_file("${PROJECT_SOURCE_DIR}/x265.def.in"
                "${PROJECT_BINARY_DIR}/x265.def")
 configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in"
--- a/source/common/frame.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/common/frame.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -44,6 +44,8 @@ Frame::Frame()
     m_param = NULL;
     m_userSEI.numPayloads = 0;
     m_userSEI.payloads = NULL;
+    m_rpu.payloadSize = 0;
+    m_rpu.payload = NULL;
     memset(&m_lowres, 0, sizeof(m_lowres));
     m_rcData = NULL;
     m_encodeStartTime = 0;
--- a/source/common/frame.h	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/common/frame.h	Thu Sep 27 14:16:15 2018 +0530
@@ -98,6 +98,7 @@ public:
 
     float*                 m_quantOffsets;       // points to quantOffsets in x265_picture
     x265_sei               m_userSEI;
+    x265_dolby_vision_rpu            m_rpu;
 
     /* Frame Parallelism - notification between FrameEncoders of available motion reference rows */
     ThreadSafeInteger*     m_reconRowFlag;       // flag of CTU rows completely reconstructed and extended for motion reference
--- a/source/encoder/api.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/encoder/api.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -598,6 +598,8 @@ void x265_picture_init(x265_param *param
     pic->quantOffsets = NULL;
     pic->userSEI.payloads = NULL;
     pic->userSEI.numPayloads = 0;
+    pic->rpu.payloadSize = 0;
+    pic->rpu.payload = NULL;
 
     if ((param->analysisSave || param->analysisLoad) || (param->bMVType == AVC_INFO))
     {
@@ -612,6 +614,8 @@ void x265_picture_init(x265_param *param
 
 void x265_picture_free(x265_picture *p)
 {
+    if (p->rpu.payload)
+        X265_FREE(p->rpu.payload);
     return x265_free(p);
 }
 
--- a/source/encoder/encoder.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/encoder/encoder.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -1075,6 +1075,14 @@ int Encoder::encode(const x265_picture* 
 
         copyUserSEIMessages(inFrame, pic_in);
 
+        /*Copy Dolby Vision RPU from pic_in to frame*/
+        if (pic_in->rpu.payloadSize)
+        {
+            inFrame->m_rpu.payloadSize = pic_in->rpu.payloadSize;
+            inFrame->m_rpu.payload = new uint8_t[pic_in->rpu.payloadSize];
+            memcpy(inFrame->m_rpu.payload, pic_in->rpu.payload, pic_in->rpu.payloadSize);
+        }
+
         if (pic_in->quantOffsets != NULL)
         {
             int cuCount;
@@ -2362,6 +2370,13 @@ void Encoder::getStreamHeaders(NALList& 
 {
     sbacCoder.setBitstream(&bs);
 
+    if (m_param->dolbyProfile && !m_param->bRepeatHeaders)
+    {
+        bs.resetBits();
+        bs.write(0x10, 8);
+        list.serialize(NAL_UNIT_ACCESS_UNIT_DELIMITER, bs);
+    }
+    
     /* headers for start of bitstream */
     bs.resetBits();
     sbacCoder.codeVPS(m_vps);
--- a/source/encoder/frameencoder.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/encoder/frameencoder.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -1063,6 +1063,14 @@ void FrameEncoder::compressFrame()
         m_accessUnitBits = bytes << 3;
     }
 
+    if (m_frame->m_rpu.payloadSize)
+    {
+        m_bs.resetBits();
+        for (int i = 0; i < m_frame->m_rpu.payloadSize; i++)
+            m_bs.write(m_frame->m_rpu.payload[i], 8);
+        m_nalList.serialize(NAL_UNIT_UNSPECIFIED, m_bs);
+    }
+
     m_endCompressTime = x265_mdate();
 
     /* Decrement referenced frame reference counts, allow them to be recycled */
--- a/source/encoder/nal.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/encoder/nal.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -97,7 +97,7 @@ void NALList::serialize(NalUnitType nalU
         /* Will write size later */
         bytes += 4;
     }
-    else if (!m_numNal || nalUnitType == NAL_UNIT_VPS || nalUnitType == NAL_UNIT_SPS || nalUnitType == NAL_UNIT_PPS)
+    else if (!m_numNal || nalUnitType == NAL_UNIT_VPS || nalUnitType == NAL_UNIT_SPS || nalUnitType == NAL_UNIT_PPS || nalUnitType == NAL_UNIT_UNSPECIFIED)
     {
         memcpy(out, startCodePrefix, 4);
         bytes += 4;
@@ -124,7 +124,7 @@ void NALList::serialize(NalUnitType nalU
      *  - 0x000002 */
     for (uint32_t i = 0; i < payloadSize; i++)
     {
-        if (i > 2 && !out[bytes - 2] && !out[bytes - 3] && out[bytes - 1] <= 0x03)
+        if (i > 2 && !out[bytes - 2] && !out[bytes - 3] && out[bytes - 1] <= 0x03 && nalUnitType != NAL_UNIT_UNSPECIFIED)
         {
             /* inject 0x03 to prevent emulating a start code */
             out[bytes] = out[bytes - 1];
--- a/source/encoder/sei.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/encoder/sei.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -66,7 +66,8 @@ void SEI::writeSEImessages(Bitstream& bs
 
     if (!isNested)
     {
-        bs.writeByteAlignment();
+        if (nalUnitType != NAL_UNIT_UNSPECIFIED)
+            bs.writeByteAlignment();
         list.serialize(nalUnitType, bs);
     }
 }
--- a/source/x265.cpp	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/x265.cpp	Thu Sep 27 14:16:15 2018 +0530
@@ -65,6 +65,8 @@ static void sigint_handler(int)
 {
     b_ctrl_c = 1;
 }
+#define START_CODE 0x00000001
+#define START_CODE_BYTES 4
 
 struct CLIOptions
 {
@@ -72,6 +74,7 @@ struct CLIOptions
     ReconFile* recon;
     OutputFile* output;
     FILE*       qpfile;
+    FILE*    dolbyVisionRpu;    /* File containing Dolby Vision BL RPU metadata */
     const char* reconPlayCmd;
     const x265_api* api;
     x265_param* param;
@@ -94,6 +97,7 @@ struct CLIOptions
         recon = NULL;
         output = NULL;
         qpfile = NULL;
+        dolbyVisionRpu = NULL;
         reconPlayCmd = NULL;
         api = NULL;
         param = NULL;
@@ -124,6 +128,9 @@ void CLIOptions::destroy()
     if (qpfile)
         fclose(qpfile);
     qpfile = NULL;
+    if (dolbyVisionRpu)
+        fclose(dolbyVisionRpu);
+    dolbyVisionRpu = NULL;
     if (output)
         output->release();
     output = NULL;
@@ -311,6 +318,15 @@ bool CLIOptions::parse(int argc, char **
                 if (!this->qpfile)
                     x265_log_file(param, X265_LOG_ERROR, "%s qpfile not found or error in opening qp file\n", optarg);
             }
+            OPT("dolby-vision-rpu")
+            {
+                this->dolbyVisionRpu = x265_fopen(optarg, "rb");
+                if (!this->dolbyVisionRpu)
+                {
+                    x265_log_file(param, X265_LOG_ERROR, "Dolby Vision RPU metadata file %s not found or error in opening file\n", optarg);
+                    return true;
+                }
+            }
             OPT("fullhelp")
             {
                 param->logLevel = X265_LOG_FULL;
@@ -552,6 +568,59 @@ static int get_argv_utf8(int *argc_ptr, 
 }
 #endif
 
+/* Parse the RPU file and extract the RPU corresponding to the current picture 
+ * and fill the rpu field of the input picture */
+static int rpuParser(x265_picture * pic, FILE * ptr)
+{
+    uint8_t byte;
+    uint32_t code = 0;
+    int bytesRead = 0;
+    pic->rpu.payloadSize = 0;
+
+    if (!pic->pts)
+    {
+        while (bytesRead++ < 4 && fread(&byte, sizeof(uint8_t), 1, ptr))
+            code = (code << 8) | byte;
+      
+        if (code != START_CODE)
+        {
+            x265_log(NULL, X265_LOG_ERROR, "Invalid Dolby Vision RPU startcode in POC %d\n", pic->pts);
+            return 1;
+        }
+    } 
+
+    bytesRead = 0;
+    while (fread(&byte, sizeof(uint8_t), 1, ptr))
+    {
+        code = (code << 8) | byte;
+        if (bytesRead++ < 3)
+            continue;
+        if (bytesRead >= 1024)
+        {
+            x265_log(NULL, X265_LOG_ERROR, "Invalid Dolby Vision RPU size in POC %d\n", pic->pts);
+            return 1;
+        }
+        
+        if (code != START_CODE)
+            pic->rpu.payload[pic->rpu.payloadSize++] = (code >> (3 * 8)) & 0xFF;
+        else
+            return 0;       
+    }
+
+    int ShiftBytes = START_CODE_BYTES - (bytesRead - pic->rpu.payloadSize);
+    int bytesLeft = bytesRead - pic->rpu.payloadSize;
+    code = (code << ShiftBytes * 8);
+    for (int i = 0; i < bytesLeft; i++)
+    {
+        pic->rpu.payload[pic->rpu.payloadSize++] = (code >> (3 * 8)) & 0xFF;
+        code = (code << 8);
+    }
+    if (!pic->rpu.payloadSize)
+        x265_log(NULL, X265_LOG_WARNING, "Dolby Vision RPU not found for POC %d\n", pic->pts);
+    return 0;
+}
+
+
 /* CLI return codes:
  *
  * 0 - encode successful
@@ -630,8 +699,10 @@ int main(int argc, char **argv)
     x265_stats stats;
     uint32_t nal;
     int16_t *errorBuf = NULL;
+    bool bDolbyVisionRPU = false;
     int ret = 0;
 
+
     if (!param->bRepeatHeaders)
     {
         if (api->encoder_headers(encoder, &p_nal, &nal) < 0)
@@ -646,6 +717,13 @@ int main(int argc, char **argv)
 
     api->picture_init(param, pic_in);
 
+    if (param->dolbyProfile && cliopt.dolbyVisionRpu)
+    {
+        pic_in->rpu.payload = X265_MALLOC(uint8_t, 1024);
+        if (pic_in->rpu.payload)
+            bDolbyVisionRPU = true;
+    }
+    
     if (cliopt.bDither)
     {
         errorBuf = X265_MALLOC(int16_t, param->sourceWidth + 1);
@@ -685,8 +763,13 @@ int main(int argc, char **argv)
             }
             /* Overwrite PTS */
             pic_in->pts = pic_in->poc;
+
+            if (bDolbyVisionRPU)
+            {
+                if (rpuParser(pic_in, cliopt.dolbyVisionRpu) > 0)
+                    goto fail;
+            }
         }
-
         int numEncoded = api->encoder_encode(encoder, &p_nal, &nal, pic_in, pic_recon);
         if (numEncoded < 0)
         {
@@ -749,6 +832,13 @@ int main(int argc, char **argv)
             break;
     }
   
+    if (bDolbyVisionRPU)
+    {
+        if(fgetc(cliopt.dolbyVisionRpu) != EOF)
+            x265_log(NULL, X265_LOG_WARNING, "Dolby Vision RPU count is greater than frame count\n");
+        x265_log(NULL, X265_LOG_INFO, "VES muxing with Dolby Vision RPU file successful\n");
+    }
+
     /* clear progress report */
     if (cliopt.bProgress)
         fprintf(stderr, "%*s\r", 80, " ");
--- a/source/x265.h	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/x265.h	Thu Sep 27 14:16:15 2018 +0530
@@ -81,6 +81,7 @@ typedef enum
     NAL_UNIT_FILLER_DATA,
     NAL_UNIT_PREFIX_SEI,
     NAL_UNIT_SUFFIX_SEI,
+    NAL_UNIT_UNSPECIFIED = 62,
     NAL_UNIT_INVALID = 64,
 } NalUnitType;
 
@@ -360,6 +361,12 @@ typedef struct x265_sei
     x265_sei_payload *payloads;
 } x265_sei;
 
+typedef struct x265_dolby_vision_rpu
+{
+    int payloadSize;
+    uint8_t* payload;
+}x265_dolby_vision_rpu;
+
 /* Used to pass pictures into the encoder, and to get picture data back out of
  * the encoder.  The input and output semantics are different */
 typedef struct x265_picture
@@ -445,6 +452,9 @@ typedef struct x265_picture
 
     // pts is reordered in the order of encoding.
     int64_t reorderedPts;
+
+    //Dolby Vision RPU metadata
+    x265_dolby_vision_rpu rpu;
 } x265_picture;
 
 typedef enum
--- a/source/x265cli.h	Fri Sep 28 10:45:23 2018 +0530
+++ b/source/x265cli.h	Thu Sep 27 14:16:15 2018 +0530
@@ -306,6 +306,7 @@ static const struct option long_options[
     { "atc-sei", required_argument, NULL, 0 },
     { "pic-struct", required_argument, NULL, 0 },
     { "nalu-file", required_argument, NULL, 0 },
+    { "dolby-vision-rpu", required_argument, NULL, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 0 },
@@ -357,6 +358,8 @@ static void showHelp(x265_param *param)
     H0("   --[no-]dhdr10-opt             Insert tone mapping SEI only for IDR frames and when the tone mapping information changes. Default disabled\n");
 #endif
     H0("  --dolby-vision-profile <float|integer> Specifies Dolby Vision profile ID. Currently only profile 5 enabled. Specified as '5' or '50'. Default 0 (disabled).\n");
+    H0("   --dolby-vision-rpu <filename> File containing Dolby Vision RPU metadata.\n"
+       "                                 If given, x265's Dolby Vision metadata parser will fill the RPU field of input pictures with the metadata read from the file. Default NULL(disabled).\n");
     H0("   --nalu-file <filename>        Text file containing SEI messages in the following format : <POC><space><PREFIX><space><NAL UNIT TYPE>/<SEI TYPE><space><SEI Payload>\n");
     H0("-f/--frames <integer>            Maximum number of frames to encode. Default all\n");
     H0("   --seek <integer>              First frame to encode\n");