<div dir="ltr">Pls ignore this patch.<div><br clear="all"><div><div dir="ltr" class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><font face="verdana, sans-serif" color="#0c343d">Thanks & Regards</font><div><font face="verdana, sans-serif" color="#0c343d"><b>Akil R</b></font></div><div><font face="verdana, sans-serif" color="#0c343d" size="1">Video Codec Engineer </font></div><div><font face="verdana, sans-serif" color="#0c343d" size="1">Media & AI Analytics</font></div><div><a href="https://multicorewareinc.com/" target="_blank"><img src="https://docs.google.com/uc?export=download&id=1kc3RJu9M8bnIf6Xa5rUw2d-eEVUsPBE5&revid=0B7tw9XJBmynaemR1VUpQUi9DVytRVW5SVkRwVTFjb1hBMUcwPQ"></a><br></div></div></div></div></div></div></div><br></div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Sep 23, 2019 at 10:20 AM Akil <<a href="mailto:akil@multicorewareinc.com">akil@multicorewareinc.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div># HG changeset patch<br># User Akil Ayyappan<<a href="mailto:akil@multicorewareinc.com" target="_blank">akil@multicorewareinc.com</a>><br># Date 1568370446 -19800<br># Fri Sep 13 15:57:26 2019 +0530<br># Node ID 531f6b03eed0a40a38d3589dec03f14743293146<br># Parent c4b098f973e6b0ee4aee3bf0d7b54da4e2734d42<br>Adaptive Frame duplication<br><br>This patch does the following.<br>1. Replaces 2-3 near-identical frames with one frame and sets pic_struct based on frame doubling / tripling.<br>2. Add option "--frame-dup" and "--dup-threshold' to enable frame duplication and to set threshold for frame similarity (optional).<br><br>diff -r c4b098f973e6 -r 531f6b03eed0 doc/reST/cli.rst<br>--- a/doc/reST/cli.rst Tue Aug 13 10:51:21 2019 +0530<br>+++ b/doc/reST/cli.rst Fri Sep 13 15:57:26 2019 +0530<br>@@ -501,6 +501,16 @@<br> second. The decoder must re-combine the fields in their correct<br> orientation for display.<br> <br>+.. option:: --frame-dup, --no-frame-dup<br>+<br>+ Enable Frame duplication. Replaces 2-3 near-identical frames with one <br>+ frame and sets pic_struct based on frame doubling / tripling. <br>+ Default disabled.<br>+<br>+.. option:: --dup-threshold <integer><br>+<br>+ Frame similarity threshold can vary between 1 and 99. Default 70.<br>+<br> .. option:: --seek <integer><br> <br> Number of frames to skip at start of input file. Default 0<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/CMakeLists.txt<br>--- a/source/CMakeLists.txt Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/CMakeLists.txt Fri Sep 13 15:57:26 2019 +0530<br>@@ -29,7 +29,7 @@<br> option(STATIC_LINK_CRT "Statically link C runtime for release builds" OFF)<br> mark_as_advanced(FPROFILE_USE FPROFILE_GENERATE NATIVE_BUILD)<br> # X265_BUILD must be incremented each time the public API is changed<br>-set(X265_BUILD 179)<br>+set(X265_BUILD 180)<br> configure_file("${PROJECT_SOURCE_DIR}/<a href="http://x265.def.in" target="_blank">x265.def.in</a>"<br> "${PROJECT_BINARY_DIR}/x265.def")<br> configure_file("${PROJECT_SOURCE_DIR}/<a href="http://x265_config.h.in" target="_blank">x265_config.h.in</a>"<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/common/frame.cpp<br>--- a/source/common/frame.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/common/frame.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -57,6 +57,7 @@<br> m_addOnPrevChange = NULL;<br> m_classifyFrame = false;<br> m_fieldNum = 0;<br>+ m_picStruct = 0;<br> }<br> <br> bool Frame::create(x265_param *param, float* quantOffsets)<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/common/frame.h<br>--- a/source/common/frame.h Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/common/frame.h Fri Sep 13 15:57:26 2019 +0530<br>@@ -98,6 +98,7 @@<br> <br> float* m_quantOffsets; // points to quantOffsets in x265_picture<br> x265_sei m_userSEI;<br>+ uint32_t m_picStruct; // picture structure SEI message<br> x265_dolby_vision_rpu m_rpu;<br> <br> /* Frame Parallelism - notification between FrameEncoders of available motion reference rows */<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/common/param.cpp<br>--- a/source/common/param.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/common/param.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -135,6 +135,7 @@<br> <br> /* Source specifications */<br> param->internalBitDepth = X265_DEPTH;<br>+ param->sourceBitDepth = 8;<br> param->internalCsp = X265_CSP_I420;<br> param->levelIdc = 0; //Auto-detect level<br> param->uhdBluray = 0;<br>@@ -338,6 +339,9 @@<br> param->pictureStructure = -1;<br> param->bEmitCLL = 1;<br> <br>+ param->bEnableFrameDuplication = 0;<br>+ param->dupThreshold = 0;<br>+<br> /* SVT Hevc Encoder specific params */<br> param->bEnableSvtHevc = 0;<br> param->svtHevcParam = NULL;<br>@@ -1294,6 +1298,8 @@<br> OPT("fades") p->bEnableFades = atobool(value);<br> OPT("field") p->bField = atobool( value );<br> OPT("cll") p->bEmitCLL = atobool(value);<br>+ OPT("frame-dup") p->bEnableFrameDuplication = atobool(value);<br>+ OPT("dup-threshold") p->dupThreshold = atoi(value);<br> OPT("hme") p->bEnableHME = atobool(value);<br> OPT("hme-search")<br> {<br>@@ -1680,6 +1686,8 @@<br> "Supported factor for controlling max AU size is from 0.5 to 1");<br> CHECK((param->dolbyProfile != 0) && (param->dolbyProfile != 50) && (param->dolbyProfile != 81) && (param->dolbyProfile != 82),<br> "Unsupported Dolby Vision profile, only profile 5, profile 8.1 and profile 8.2 enabled");<br>+ CHECK(param->dupThreshold < 0 || 99 < param->dupThreshold,<br>+ "Invalid frame-duplication threshold. Value must be between 1 and 99.");<br> if (param->dolbyProfile)<br> {<br> CHECK((param->rc.vbvMaxBitrate <= 0 || param->rc.vbvBufferSize <= 0), "Dolby Vision requires VBV settings to enable HRD.\n");<br>@@ -1972,6 +1980,9 @@<br> s += sprintf(s, " subme=%d", p->subpelRefine);<br> s += sprintf(s, " merange=%d", p->searchRange);<br> BOOL(p->bEnableTemporalMvp, "temporal-mvp");<br>+ BOOL(p->bEnableFrameDuplication, "frame-dup");<br>+ if(p->bEnableFrameDuplication)<br>+ s += sprintf(s, " dup-threshold=%d", p->dupThreshold);<br> BOOL(p->bEnableHME, "hme");<br> if (p->bEnableHME)<br> s += sprintf(s, " Level 0,1,2=%d,%d,%d", p->hmeSearchMethod[0], p->hmeSearchMethod[1], p->hmeSearchMethod[2]);<br>@@ -2209,6 +2220,7 @@<br> if (src->csvfn) dst->csvfn = strdup(src->csvfn);<br> else dst->csvfn = NULL;<br> dst->internalBitDepth = src->internalBitDepth;<br>+ dst->sourceBitDepth = src->sourceBitDepth;<br> dst->internalCsp = src->internalCsp;<br> dst->fpsNum = src->fpsNum;<br> dst->fpsDenom = src->fpsDenom;<br>@@ -2263,6 +2275,8 @@<br> dst->subpelRefine = src->subpelRefine;<br> dst->searchRange = src->searchRange;<br> dst->bEnableTemporalMvp = src->bEnableTemporalMvp;<br>+ dst->bEnableFrameDuplication = src->bEnableFrameDuplication;<br>+ dst->dupThreshold = src->dupThreshold;<br> dst->bEnableHME = src->bEnableHME;<br> if (src->bEnableHME)<br> {<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/encoder/api.cpp<br>--- a/source/encoder/api.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/encoder/api.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -923,6 +923,7 @@<br> pic->userSEI.numPayloads = 0;<br> pic->rpu.payloadSize = 0;<br> pic->rpu.payload = NULL;<br>+ pic->picStruct = 0;<br> <br> if ((param->analysisSave || param->analysisLoad) || (param->bAnalysisType == AVC_INFO))<br> {<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/encoder/encoder.cpp<br>--- a/source/encoder/encoder.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/encoder/encoder.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -117,6 +117,11 @@<br> m_cR = 1.0;<br> for (int i = 0; i < X265_MAX_FRAME_THREADS; i++)<br> m_frameEncoder[i] = NULL;<br>+ for (int i = 0; i < X265_DUP_BUFFER; i++)<br>+ {<br>+ m_picList[i] = NULL;<br>+ m_plane[i] = NULL;<br>+ }<br> MotionEstimate::initScales();<br> <br> #if ENABLE_HDR10_PLUS<br>@@ -160,6 +165,33 @@<br> int rows = (p->sourceHeight + p->maxCUSize - 1) >> g_log2Size[p->maxCUSize];<br> int cols = (p->sourceWidth + p->maxCUSize - 1) >> g_log2Size[p->maxCUSize];<br> <br>+<br>+<br>+ if (m_param->bEnableFrameDuplication)<br>+ {<br>+#define DUP_THRESHOLD 70<br>+ size_t framesize = 0;<br>+ int pixelbytes = p->sourceBitDepth > 8 ? 2 : 1;<br>+ for (int i = 0; i < x265_cli_csps[p->internalCsp].planes; i++)<br>+ {<br>+ int stride = (p->sourceWidth >> x265_cli_csps[p->internalCsp].width[i]) * pixelbytes;<br>+ framesize += (stride * (p->sourceHeight >> x265_cli_csps[p->internalCsp].height[i]));<br>+ }<br>+<br>+ if (!m_param->dupThreshold)<br>+ m_param->dupThreshold = DUP_THRESHOLD;<br>+<br>+ for (int i = 0; i < X265_DUP_BUFFER; i++)<br>+ {<br>+ m_picList[i] = x265_picture_alloc();<br>+ x265_picture_init(p, m_picList[i]);<br>+ m_plane[i] = X265_MALLOC(char, framesize);<br>+ m_picList[i]->planes[0] = m_plane[i];<br>+ m_picList[i]->lock = 0;<br>+ m_picList[i]->dup = 0;<br>+ }<br>+ }<br>+<br> // Do not allow WPP if only one row or fewer than 3 columns, it is pointless and unstable<br> if (rows == 1 || cols < 3)<br> {<br>@@ -771,6 +803,15 @@<br> m_exportedPic = NULL;<br> }<br> <br>+ if (m_param->bEnableFrameDuplication)<br>+ {<br>+ for (int i = 0; i < X265_DUP_BUFFER; i++)<br>+ {<br>+ x265_picture_free(m_picList[i]);<br>+ X265_FREE(m_plane[i]);<br>+ }<br>+ }<br>+<br> for (int i = 0; i < m_param->frameNumThreads; i++)<br> {<br> if (m_frameEncoder[i])<br>@@ -981,6 +1022,136 @@<br> }<br> }<br> <br>+//Find Sum of Squared Diference (SSD) between two pictures<br>+uint64_t Encoder::computeSSD(pixel *fenc, pixel *rec, intptr_t stride, uint32_t width, uint32_t height)<br>+{<br>+ uint64_t ssd = 0;<br>+<br>+ if ((width | height) & 3)<br>+ {<br>+ /* Slow Path */<br>+ for (uint32_t y = 0; y < height; y++)<br>+ {<br>+ for (uint32_t x = 0; x < width; x++)<br>+ {<br>+ int diff = (int)(fenc[x] - rec[x]);<br>+ ssd += diff * diff;<br>+ }<br>+<br>+ fenc += stride;<br>+ rec += stride;<br>+ }<br>+<br>+ return ssd;<br>+ }<br>+<br>+ uint32_t y = 0;<br>+<br>+ /* Consume rows in ever narrower chunks of height */<br>+ for (int size = BLOCK_64x64; size >= BLOCK_4x4 && y < height; size--)<br>+ {<br>+ uint32_t rowHeight = 1 << (size + 2);<br>+<br>+ for (; y + rowHeight <= height; y += rowHeight)<br>+ {<br>+ uint32_t y1, x = 0;<br>+<br>+ /* Consume each row using the largest square blocks possible */<br>+ if (size == BLOCK_64x64 && !(stride & 31))<br>+ for (; x + 64 <= width; x += 64)<br>+ ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_64x64].sse_pp(fenc + x, stride, rec + x, stride);<br>+<br>+ if (size >= BLOCK_32x32 && !(stride & 15))<br>+ for (; x + 32 <= width; x += 32)<br>+ for (y1 = 0; y1 + 32 <= rowHeight; y1 += 32)<br>+ ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_32x32].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>+<br>+ if (size >= BLOCK_16x16)<br>+ for (; x + 16 <= width; x += 16)<br>+ for (y1 = 0; y1 + 16 <= rowHeight; y1 += 16)<br>+ ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_16x16].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>+<br>+ if (size >= BLOCK_8x8)<br>+ for (; x + 8 <= width; x += 8)<br>+ for (y1 = 0; y1 + 8 <= rowHeight; y1 += 8)<br>+ ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_8x8].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>+<br>+ for (; x + 4 <= width; x += 4)<br>+ for (y1 = 0; y1 + 4 <= rowHeight; y1 += 4)<br>+ ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_4x4].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>+<br>+ fenc += stride * rowHeight;<br>+ rec += stride * rowHeight;<br>+ }<br>+ }<br>+<br>+ return ssd;<br>+}<br>+<br>+//Compute the PSNR weightage between two pictures<br>+double Encoder::ComputePSNR(x265_picture *firstPic, x265_picture *secPic, x265_param *param)<br>+{<br>+ uint64_t ssdY = 0, ssdU = 0, ssdV = 0;<br>+ intptr_t strideL, strideC;<br>+ uint32_t widthL, heightL, widthC, heightC;<br>+ double psnrY = 0, psnrU = 0, psnrV = 0, psnrWeight = 0;<br>+ int hshift = CHROMA_H_SHIFT(firstPic->colorSpace);<br>+ int vshift = CHROMA_V_SHIFT(firstPic->colorSpace);<br>+<br>+ strideL = widthL = firstPic->width;<br>+ heightL = firstPic->height;<br>+<br>+ strideC = widthC = widthL >> hshift;<br>+ heightC = heightL >> vshift;<br>+<br>+ int size = widthL * heightL;<br>+ int maxvalY = 255 << (X265_DEPTH - 8);<br>+ int maxvalC = 255 << (X265_DEPTH - 8);<br>+ double refValueY = (double)maxvalY * maxvalY * size;<br>+ double refValueC = (double)maxvalC * maxvalC * size / 4.0;<br>+<br>+ pixel *yFirstPic = (pixel*)firstPic->planes[0];<br>+ pixel *ySecPic = (pixel*)secPic->planes[0];<br>+ ssdY = computeSSD(yFirstPic, ySecPic, strideL, widthL, heightL);<br>+ psnrY = (ssdY ? 10.0 * log10(refValueY / (double)ssdY) : 99.99);<br>+<br>+ if (param->internalCsp != X265_CSP_I400)<br>+ {<br>+ pixel *uFirstPic = (pixel*)firstPic->planes[1];<br>+ pixel *vFirstPic = (pixel*)firstPic->planes[2];<br>+ pixel *uSecPic = (pixel*)secPic->planes[1];<br>+ pixel *vSecPic = (pixel*)secPic->planes[2];<br>+ ssdU = computeSSD(uFirstPic, uSecPic, strideC, widthC, heightC);<br>+ ssdV = computeSSD(vFirstPic, vSecPic, strideC, widthC, heightC);<br>+ psnrU = (ssdU ? 10.0 * log10(refValueC / (double)ssdU) : 99.99);<br>+ psnrV = (ssdV ? 10.0 * log10(refValueC / (double)ssdV) : 99.99);<br>+ }<br>+<br>+ //Compute PSNR(picN,pic(N+1))<br>+ return psnrWeight = (psnrY * 6 + psnrU + psnrV) / 8;<br>+}<br>+<br>+void Encoder::copyPicture(x265_picture *dest, const x265_picture *src)<br>+{<br>+ dest->poc = src->poc;<br>+ dest->pts = src->pts;<br>+ dest->userSEI = src->userSEI;<br>+ dest->bitDepth = src->bitDepth;<br>+ dest->framesize = src->framesize;<br>+ dest->height = src->height;<br>+ dest->width = src->width;<br>+ dest->colorSpace = src->colorSpace;<br>+ dest->userSEI = src->userSEI;<br>+ dest->rpu.payload = src->rpu.payload;<br>+ dest->picStruct = src->picStruct;<br>+ dest->stride[0] = src->stride[0];<br>+ dest->stride[1] = src->stride[1];<br>+ dest->stride[2] = src->stride[2];<br>+ memcpy(dest->planes[0], src->planes[0], src->framesize * sizeof(char));<br>+ dest->planes[1] = (char*)dest->planes[0] + src->stride[0] * src->height;<br>+ dest->planes[2] = (char*)dest->planes[1] + src->stride[1] * (src->height >> x265_cli_csps[src->colorSpace].height[1]);<br>+}<br>+<br> /**<br> * Feed one new input frame into the encoder, get one frame out. If pic_in is<br> * NULL, a flush condition is implied and pic_in must be NULL for all subsequent<br>@@ -1004,6 +1175,10 @@<br> if (m_aborted)<br> return -1;<br> <br>+ const x265_picture* inputPic = NULL;<br>+ static int written = 0, read = 0;<br>+ bool dontRead = false;<br>+<br> if (m_exportedPic)<br> {<br> if (!m_param->bUseAnalysisFile && m_param->analysisSave)<br>@@ -1012,25 +1187,86 @@<br> m_exportedPic = NULL;<br> m_dpb->recycleUnreferenced();<br> }<br>- if (pic_in && (!m_param->chunkEnd || (m_encodedFrameNum < m_param->chunkEnd)))<br>- {<br>- if (m_latestParam->forceFlush == 1)<br>+ if ((pic_in && (!m_param->chunkEnd || (m_encodedFrameNum < m_param->chunkEnd))) || (m_param->bEnableFrameDuplication && !pic_in && (read < written)))<br>+ {<br>+ if ((m_param->bEnableFrameDuplication && !pic_in && (read < written)))<br>+ dontRead = true;<br>+ else<br> {<br>- m_lookahead->setLookaheadQueue();<br>- m_latestParam->forceFlush = 0;<br>+ if (m_latestParam->forceFlush == 1)<br>+ {<br>+ m_lookahead->setLookaheadQueue();<br>+ m_latestParam->forceFlush = 0;<br>+ }<br>+ if (m_latestParam->forceFlush == 2)<br>+ {<br>+ m_lookahead->m_filled = false;<br>+ m_latestParam->forceFlush = 0;<br>+ }<br>+<br>+ if (pic_in->bitDepth < 8 || pic_in->bitDepth > 16)<br>+ {<br>+ x265_log(m_param, X265_LOG_ERROR, "Input bit depth (%d) must be between 8 and 16\n",<br>+ pic_in->bitDepth);<br>+ return -1;<br>+ }<br> }<br>- if (m_latestParam->forceFlush == 2)<br>+<br>+ if (m_param->bEnableFrameDuplication)<br> {<br>- m_lookahead->m_filled = false;<br>- m_latestParam->forceFlush = 0;<br>+#define doubling 7<br>+#define tripling 8<br>+ double psnrWeight = 0;<br>+<br>+ if (!dontRead)<br>+ {<br>+ if (!m_picList[0]->lock)<br>+ {<br>+ copyPicture(m_picList[0], pic_in);<br>+ m_picList[0]->lock = 1;<br>+ written++;<br>+ return 0;<br>+ }<br>+ else if (!m_picList[1]->lock)<br>+ {<br>+ copyPicture(m_picList[1], pic_in);<br>+ m_picList[1]->lock = 1;<br>+ written++;<br>+ }<br>+<br>+ psnrWeight = ComputePSNR(m_picList[0], m_picList[1], m_param);<br>+<br>+ if (psnrWeight >= m_param->dupThreshold)<br>+ {<br>+ if (m_picList[0]->dup)<br>+ {<br>+ m_picList[0]->picStruct = tripling;<br>+ m_picList[0]->dup = 0;<br>+ read++;<br>+ }<br>+ else<br>+ {<br>+ m_picList[0]->picStruct = doubling;<br>+ m_picList[0]->dup = 1;<br>+ m_picList[1]->lock = 0;<br>+ read++;<br>+ return 0;<br>+ }<br>+ }<br>+ else if (m_picList[0]->dup)<br>+ m_picList[0]->dup = 0;<br>+ else<br>+ m_picList[0]->picStruct = 0;<br>+ }<br>+<br>+ if (read < written)<br>+ {<br>+ inputPic = m_picList[0];<br>+ read++;<br>+ }<br> }<br>-<br>- if (pic_in->bitDepth < 8 || pic_in->bitDepth > 16)<br>- {<br>- x265_log(m_param, X265_LOG_ERROR, "Input bit depth (%d) must be between 8 and 16\n",<br>- pic_in->bitDepth);<br>- return -1;<br>- }<br>+ else<br>+ inputPic = pic_in;<br> <br> Frame *inFrame;<br> x265_param* p = (m_reconfigure || m_reconfigureRc) ? m_latestParam : m_param;<br>@@ -1038,7 +1274,7 @@<br> {<br> inFrame = new Frame;<br> inFrame->m_encodeStartTime = x265_mdate();<br>- if (inFrame->create(p, pic_in->quantOffsets))<br>+ if (inFrame->create(p, inputPic->quantOffsets))<br> {<br> /* the first PicYuv created is asked to generate the CU and block unit offset<br> * arrays which are then shared with all subsequent PicYuv (orig and recon) <br>@@ -1098,34 +1334,38 @@<br> }<br> <br> /* Copy input picture into a Frame and PicYuv, send to lookahead */<br>- inFrame->m_fencPic->copyFromPicture(*pic_in, *m_param, m_sps.conformanceWindow.rightOffset, m_sps.conformanceWindow.bottomOffset);<br>+ inFrame->m_fencPic->copyFromPicture(*inputPic, *m_param, m_sps.conformanceWindow.rightOffset, m_sps.conformanceWindow.bottomOffset);<br> <br> inFrame->m_poc = ++m_pocLast;<br>- inFrame->m_userData = pic_in->userData;<br>- inFrame->m_pts = pic_in->pts;<br>- inFrame->m_forceqp = pic_in->forceqp;<br>+ inFrame->m_userData = inputPic->userData;<br>+ if (m_param->bEnableFrameDuplication)<br>+ inFrame->m_pts = inFrame->m_poc;<br>+ else<br>+ inFrame->m_pts = inputPic->pts;<br>+ inFrame->m_forceqp = inputPic->forceqp;<br> inFrame->m_param = (m_reconfigure || m_reconfigureRc) ? m_latestParam : m_param;<br>+ inFrame->m_picStruct = inputPic->picStruct;<br> if (m_param->bField && m_param->interlaceMode)<br>- inFrame->m_fieldNum = pic_in->fieldNum;<br>-<br>- copyUserSEIMessages(inFrame, pic_in);<br>-<br>- /*Copy Dolby Vision RPU from pic_in to frame*/<br>- if (pic_in->rpu.payloadSize)<br>+ inFrame->m_fieldNum = inputPic->fieldNum;<br>+<br>+ copyUserSEIMessages(inFrame, inputPic);<br>+<br>+ /*Copy Dolby Vision RPU from inputPic to frame*/<br>+ if (inputPic->rpu.payloadSize)<br> {<br>- inFrame->m_rpu.payloadSize = pic_in->rpu.payloadSize;<br>- inFrame->m_rpu.payload = new uint8_t[pic_in->rpu.payloadSize];<br>- memcpy(inFrame->m_rpu.payload, pic_in->rpu.payload, pic_in->rpu.payloadSize);<br>+ inFrame->m_rpu.payloadSize = inputPic->rpu.payloadSize;<br>+ inFrame->m_rpu.payload = new uint8_t[inputPic->rpu.payloadSize];<br>+ memcpy(inFrame->m_rpu.payload, inputPic->rpu.payload, inputPic->rpu.payloadSize);<br> }<br> <br>- if (pic_in->quantOffsets != NULL)<br>+ if (inputPic->quantOffsets != NULL)<br> {<br> int cuCount;<br> if (m_param->rc.qgSize == 8)<br> cuCount = inFrame->m_lowres.maxBlocksInRowFullRes * inFrame->m_lowres.maxBlocksInColFullRes;<br> else<br> cuCount = inFrame->m_lowres.maxBlocksInRow * inFrame->m_lowres.maxBlocksInCol;<br>- memcpy(inFrame->m_quantOffsets, pic_in->quantOffsets, cuCount * sizeof(float));<br>+ memcpy(inFrame->m_quantOffsets, inputPic->quantOffsets, cuCount * sizeof(float));<br> }<br> <br> if (m_pocLast == 0)<br>@@ -1147,9 +1387,9 @@<br> }<br> <br> /* Use the frame types from the first pass, if available */<br>- int sliceType = (m_param->rc.bStatRead) ? m_rateControl->rateControlSliceType(inFrame->m_poc) : pic_in->sliceType;<br>-<br>- /* In analysisSave mode, x265_analysis_data is allocated in pic_in and inFrame points to this */<br>+ int sliceType = (m_param->rc.bStatRead) ? m_rateControl->rateControlSliceType(inFrame->m_poc) : inputPic->sliceType;<br>+<br>+ /* In analysisSave mode, x265_analysis_data is allocated in inputPic and inFrame points to this */<br> /* Load analysis data before lookahead->addPicture, since sliceType has been decided */<br> if (m_param->analysisLoad)<br> {<br>@@ -1157,7 +1397,7 @@<br> static int paramBytes = 0;<br> if (!inFrame->m_poc && m_param->bAnalysisType != HEVC_INFO)<br> {<br>- x265_analysis_data analysisData = pic_in->analysisData;<br>+ x265_analysis_data analysisData = inputPic->analysisData;<br> paramBytes = validateAnalysisData(&analysisData, 0);<br> if (paramBytes == -1)<br> {<br>@@ -1178,10 +1418,10 @@<br> uint32_t outOfBoundaryLowresH = extendedHeight - m_param->sourceHeight / 2;<br> if (outOfBoundaryLowresH * 2 >= m_param->maxCUSize)<br> cuLocInFrame.skipHeight = true;<br>- readAnalysisFile(&inFrame->m_analysisData, inFrame->m_poc, pic_in, paramBytes, cuLocInFrame);<br>+ readAnalysisFile(&inFrame->m_analysisData, inFrame->m_poc, inputPic, paramBytes, cuLocInFrame);<br> }<br> else<br>- readAnalysisFile(&inFrame->m_analysisData, inFrame->m_poc, pic_in, paramBytes);<br>+ readAnalysisFile(&inFrame->m_analysisData, inFrame->m_poc, inputPic, paramBytes);<br> inFrame->m_poc = inFrame->m_analysisData.poc;<br> sliceType = inFrame->m_analysisData.sliceType;<br> inFrame->m_lowres.bScenecut = !!inFrame->m_analysisData.bScenecut;<br>@@ -1202,9 +1442,9 @@<br> }<br> }<br> }<br>- if (m_param->bUseRcStats && pic_in->rcData)<br>+ if (m_param->bUseRcStats && inputPic->rcData)<br> {<br>- RcStats* rc = (RcStats*)pic_in->rcData;<br>+ RcStats* rc = (RcStats*)inputPic->rcData;<br> m_rateControl->m_accumPQp = rc->cumulativePQp;<br> m_rateControl->m_accumPNorm = rc->cumulativePNorm;<br> m_rateControl->m_isNextGop = true;<br>@@ -1228,6 +1468,21 @@<br> }<br> m_param->bUseRcStats = 0;<br> }<br>+<br>+ if (m_param->bEnableFrameDuplication && ((read < written) || (m_picList[0]->picStruct == tripling && (read <= written))))<br>+ {<br>+ if (m_picList[0]->picStruct == tripling)<br>+ {<br>+ m_picList[0]->lock = 0;<br>+ m_picList[1]->lock = 0;<br>+ }<br>+ else<br>+ {<br>+ copyPicture(m_picList[0], m_picList[1]);<br>+ m_picList[1]->lock = 0;<br>+ }<br>+ }<br>+<br> if (m_reconfigureRc)<br> inFrame->m_reconfigureRc = true;<br> <br>@@ -1262,7 +1517,7 @@<br> Slice *slice = outFrame->m_encData->m_slice;<br> x265_frame_stats* frameData = NULL;<br> <br>- /* Free up pic_in->analysisData since it has already been used */<br>+ /* Free up inputPic->analysisData since it has already been used */<br> if ((m_param->analysisLoad && !m_param->analysisSave) || ((m_param->bAnalysisType == AVC_INFO) && slice->m_sliceType != I_SLICE))<br> x265_free_analysis_data(m_param, &outFrame->m_analysisData);<br> <br>@@ -2615,7 +2870,7 @@<br> vui.defaultDisplayWindow.bottomOffset = m_param->vui.defDispWinBottomOffset;<br> vui.defaultDisplayWindow.leftOffset = m_param->vui.defDispWinLeftOffset;<br> <br>- vui.frameFieldInfoPresentFlag = !!m_param->interlaceMode || (m_param->pictureStructure >= 0);<br>+ vui.frameFieldInfoPresentFlag = !!m_param->interlaceMode || (m_param->pictureStructure >= 0) || m_param->bEnableFrameDuplication;<br> vui.fieldSeqFlag = !!m_param->interlaceMode;<br> <br> vui.hrdParametersPresentFlag = m_param->bEmitHRDSEI;<br>@@ -3174,6 +3429,30 @@<br> p->dynamicRd = 0;<br> x265_log(p, X265_LOG_WARNING, "Dynamic-rd disabled, requires RD <= 4, VBV and aq-mode enabled\n");<br> }<br>+<br>+ if (!p->bEnableFrameDuplication && p->dupThreshold)<br>+ {<br>+ x265_log(p, X265_LOG_WARNING, "Frame-duplication threshold works only with frame-duplication enabled. Enabling frame-duplication.\n");<br>+ p->bEnableFrameDuplication = 1;<br>+ }<br>+<br>+ if (p->bEnableFrameDuplication && p->interlaceMode)<br>+ {<br>+ x265_log(p, X265_LOG_WARNING, "Frame-duplication does not support interlace mode. Disabling interlace mode.\n");<br>+ p->interlaceMode = 0;<br>+ }<br>+<br>+ if (p->bEnableFrameDuplication && p->pictureStructure != -1)<br>+ {<br>+ x265_log(p, X265_LOG_WARNING, "Frame-duplication does not work with pic_struct. Disabling pic_struct.\n");<br>+ p->pictureStructure = -1;<br>+ }<br>+<br>+ if (m_param->bEnableFrameDuplication && (!bIsVbv || !m_param->bEmitHRDSEI))<br>+ {<br>+ x265_log(m_param, X265_LOG_WARNING, "Frame-duplication require NAL HRD and VBV parameters. Disabling frame duplication\n");<br>+ m_param->bEnableFrameDuplication = 0;<br>+ }<br> #ifdef ENABLE_HDR10_PLUS<br> if (m_param->bDhdr10opt && m_param->toneMapFile == NULL)<br> {<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/encoder/encoder.h<br>--- a/source/encoder/encoder.h Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/encoder/encoder.h Fri Sep 13 15:57:26 2019 +0530<br>@@ -168,6 +168,7 @@<br> int m_bframeDelay;<br> int m_numPools;<br> int m_curEncoder;<br>+ int m_framesToBeEncoded;<br> <br> // weighted prediction<br> int m_numLumaWPFrames; // number of P frames with weighted luma reference<br>@@ -180,6 +181,9 @@<br> <br> ThreadPool* m_threadPool;<br> FrameEncoder* m_frameEncoder[X265_MAX_FRAME_THREADS];<br>+ x265_picture* m_picList[X265_DUP_BUFFER];<br>+ char* m_plane[X265_DUP_BUFFER];<br>+<br> DPB* m_dpb;<br> Frame* m_exportedPic;<br> FILE* m_analysisFileIn;<br>@@ -324,6 +328,12 @@<br> <br> void calcRefreshInterval(Frame* frameEnc);<br> <br>+ uint64_t computeSSD(pixel *fenc, pixel *rec, intptr_t stride, uint32_t width, uint32_t height);<br>+<br>+ double ComputePSNR(x265_picture *firstPic, x265_picture *secPic, x265_param *param);<br>+<br>+ void copyPicture(x265_picture *dest, const x265_picture *src);<br>+<br> void initRefIdx();<br> void analyseRefIdx(int *numRefIdx);<br> void updateRefIdx();<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/encoder/frameencoder.cpp<br>--- a/source/encoder/frameencoder.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/encoder/frameencoder.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -713,6 +713,8 @@<br> sei->m_picStruct = (poc & 1) ? 2 /* bottom */ : 1 /* top */;<br> }<br> }<br>+ else if (m_param->bEnableFrameDuplication)<br>+ sei->m_picStruct = m_frame->m_picStruct;<br> else<br> sei->m_picStruct = m_param->pictureStructure;<br> <br>diff -r c4b098f973e6 -r 531f6b03eed0 source/encoder/framefilter.cpp<br>--- a/source/encoder/framefilter.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/encoder/framefilter.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -32,7 +32,6 @@<br> <br> using namespace X265_NS;<br> <br>-static uint64_t computeSSD(pixel *fenc, pixel *rec, intptr_t stride, uint32_t width, uint32_t height);<br> static float calculateSSIM(pixel *pix1, intptr_t stride1, pixel *pix2, intptr_t stride2, uint32_t width, uint32_t height, void *buf, uint32_t& cnt);<br> <br> namespace X265_NS<br>@@ -673,7 +672,7 @@<br> uint32_t width = reconPic->m_picWidth - m_pad[0];<br> uint32_t height = m_parallelFilter[row].getCUHeight();<br> <br>- uint64_t ssdY = computeSSD(fencPic->getLumaAddr(cuAddr), reconPic->getLumaAddr(cuAddr), stride, width, height);<br>+ uint64_t ssdY = m_frameEncoder->m_top->computeSSD(fencPic->getLumaAddr(cuAddr), reconPic->getLumaAddr(cuAddr), stride, width, height);<br> m_frameEncoder->m_SSDY += ssdY;<br> <br> if (m_param->internalCsp != X265_CSP_I400)<br>@@ -682,8 +681,8 @@<br> width >>= m_hChromaShift;<br> stride = reconPic->m_strideC;<br> <br>- uint64_t ssdU = computeSSD(fencPic->getCbAddr(cuAddr), reconPic->getCbAddr(cuAddr), stride, width, height);<br>- uint64_t ssdV = computeSSD(fencPic->getCrAddr(cuAddr), reconPic->getCrAddr(cuAddr), stride, width, height);<br>+ uint64_t ssdU = m_frameEncoder->m_top->computeSSD(fencPic->getCbAddr(cuAddr), reconPic->getCbAddr(cuAddr), stride, width, height);<br>+ uint64_t ssdV = m_frameEncoder->m_top->computeSSD(fencPic->getCrAddr(cuAddr), reconPic->getCrAddr(cuAddr), stride, width, height);<br> <br> m_frameEncoder->m_SSDU += ssdU;<br> m_frameEncoder->m_SSDV += ssdV;<br>@@ -825,71 +824,6 @@<br> }<br> }<br> <br>-static uint64_t computeSSD(pixel *fenc, pixel *rec, intptr_t stride, uint32_t width, uint32_t height)<br>-{<br>- uint64_t ssd = 0;<br>-<br>- if ((width | height) & 3)<br>- {<br>- /* Slow Path */<br>- for (uint32_t y = 0; y < height; y++)<br>- {<br>- for (uint32_t x = 0; x < width; x++)<br>- {<br>- int diff = (int)(fenc[x] - rec[x]);<br>- ssd += diff * diff;<br>- }<br>-<br>- fenc += stride;<br>- rec += stride;<br>- }<br>-<br>- return ssd;<br>- }<br>-<br>- uint32_t y = 0;<br>-<br>- /* Consume rows in ever narrower chunks of height */<br>- for (int size = BLOCK_64x64; size >= BLOCK_4x4 && y < height; size--)<br>- {<br>- uint32_t rowHeight = 1 << (size + 2);<br>-<br>- for (; y + rowHeight <= height; y += rowHeight)<br>- {<br>- uint32_t y1, x = 0;<br>-<br>- /* Consume each row using the largest square blocks possible */<br>- if (size == BLOCK_64x64 && !(stride & 31))<br>- for (; x + 64 <= width; x += 64)<br>- ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_64x64].sse_pp(fenc + x, stride, rec + x, stride);<br>-<br>- if (size >= BLOCK_32x32 && !(stride & 15))<br>- for (; x + 32 <= width; x += 32)<br>- for (y1 = 0; y1 + 32 <= rowHeight; y1 += 32)<br>- ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_32x32].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>-<br>- if (size >= BLOCK_16x16)<br>- for (; x + 16 <= width; x += 16)<br>- for (y1 = 0; y1 + 16 <= rowHeight; y1 += 16)<br>- ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_16x16].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>-<br>- if (size >= BLOCK_8x8)<br>- for (; x + 8 <= width; x += 8)<br>- for (y1 = 0; y1 + 8 <= rowHeight; y1 += 8)<br>- ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_8x8].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>-<br>- for (; x + 4 <= width; x += 4)<br>- for (y1 = 0; y1 + 4 <= rowHeight; y1 += 4)<br>- ssd += <a href="http://primitives.cu" target="_blank">primitives.cu</a>[BLOCK_4x4].sse_pp(fenc + y1 * stride + x, stride, rec + y1 * stride + x, stride);<br>-<br>- fenc += stride * rowHeight;<br>- rec += stride * rowHeight;<br>- }<br>- }<br>-<br>- return ssd;<br>-}<br>-<br> /* Function to calculate SSIM for each row */<br> static float calculateSSIM(pixel *pix1, intptr_t stride1, pixel *pix2, intptr_t stride2, uint32_t width, uint32_t height, void *buf, uint32_t& cnt)<br> {<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/input/y4m.cpp<br>--- a/source/input/y4m.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/input/y4m.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -388,6 +388,7 @@<br> pic.bitDepth = depth;<br> pic.framesize = framesize;<br> pic.height = height;<br>+ pic.width = width;<br> pic.colorSpace = colorSpace;<br> pic.stride[0] = width * pixelbytes;<br> pic.stride[1] = pic.stride[0] >> x265_cli_csps[colorSpace].width[1];<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/input/yuv.cpp<br>--- a/source/input/yuv.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/input/yuv.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -204,6 +204,7 @@<br> pic.bitDepth = depth;<br> pic.framesize = framesize;<br> pic.height = height;<br>+ pic.width = width;<br> pic.stride[0] = width * pixelbytes;<br> pic.stride[1] = pic.stride[0] >> x265_cli_csps[colorSpace].width[1];<br> pic.stride[2] = pic.stride[0] >> x265_cli_csps[colorSpace].width[2];<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/test/regression-tests.txt<br>--- a/source/test/regression-tests.txt Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/test/regression-tests.txt Fri Sep 13 15:57:26 2019 +0530<br>@@ -156,6 +156,7 @@<br> 720p50_parkrun_ter.y4m,--preset medium --bitrate 400 --hme<br> ducks_take_off_420_1_720p50.y4m,--preset medium --aq-mode 4 --crf 22 --no-cutree<br> ducks_take_off_420_1_720p50.y4m,--preset medium --selective-sao 4 --sao --crf 20<br>+Traffic_4096x2048_30p.y4m, --preset medium --frame-dup --dup-threshold 70 --hrd --bitrate 10000 --vbv-bufsize 15000 --vbv-maxrate 12000<br> <br> # Main12 intraCost overflow bug test<br> 720p50_parkrun_ter.y4m,--preset medium<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/x265.cpp<br>--- a/source/x265.cpp Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/x265.cpp Fri Sep 13 15:57:26 2019 +0530<br>@@ -541,10 +541,11 @@<br> return true;<br> }<br> <br>- /* Unconditionally accept height/width/csp from file info */<br>+ /* Unconditionally accept height/width/csp/bitDepth from file info */<br> param->sourceWidth = info.width;<br> param->sourceHeight = info.height;<br> param->internalCsp = info.csp;<br>+ param->sourceBitDepth = info.depth;<br> <br> /* Accept fps and sar from file info if not specified by user */<br> if (param->fpsDenom == 0 || param->fpsNum == 0)<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/x265.h<br>--- a/source/x265.h Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/x265.h Fri Sep 13 15:57:26 2019 +0530<br>@@ -455,16 +455,27 @@<br> * multi pass ratecontrol mode. */<br> void* rcData;<br> <br>- uint64_t framesize;<br>+ size_t framesize;<br> <br> int height;<br> <br>+ int width;<br>+<br> // pts is reordered in the order of encoding.<br> int64_t reorderedPts;<br> <br> //Dolby Vision RPU metadata<br> x265_dolby_vision_rpu rpu;<br>+<br>+ //SEI picture structure message<br>+ int picStruct;<br> <br>+ //Flag used to lock the buffer<br>+ int lock;<br>+<br>+ //Flag to check whether the frame has duplicated<br>+ int dup;<br>+<br> int fieldNum;<br> } x265_picture;<br> <br>@@ -545,6 +556,7 @@<br> <br> #define X265_BFRAME_MAX 16<br> #define X265_MAX_FRAME_THREADS 16<br>+#define X265_DUP_BUFFER 2<br> <br> #define X265_TYPE_AUTO 0x0000 /* Let x265 choose the right type */<br> #define X265_TYPE_IDR 0x0001<br>@@ -844,6 +856,9 @@<br> * Future builds may support 12bit pixels. */<br> int internalBitDepth;<br> <br>+ /*Input sequence bit depth. It can be either 8bit, 10bit or 12bit.*/<br>+ int sourceBitDepth;<br>+<br> /* Color space of internal pictures, must match color space of input<br> * pictures */<br> int internalCsp;<br>@@ -1327,6 +1342,19 @@<br> * */<br> int pictureStructure; <br> <br>+ /*<br>+ * Signals picture structure SEI timing message for every frame<br>+ * picture structure 7 is signalled for frame doubling<br>+ * picture structure 8 is signalled for frame tripling<br>+ * */<br>+ int bEnableFrameDuplication;<br>+<br>+ /*<br>+ * For frame duplication, a threshold is set above which the frames are said to be similar.<br>+ * User can set a variable threshold. Default 70.<br>+ * */<br>+ int dupThreshold;<br>+<br> struct<br> {<br> /* Explicit mode of rate-control, necessary for API users. It must<br>diff -r c4b098f973e6 -r 531f6b03eed0 source/x265cli.h<br>--- a/source/x265cli.h Tue Aug 13 10:51:21 2019 +0530<br>+++ b/source/x265cli.h Fri Sep 13 15:57:26 2019 +0530<br>@@ -321,6 +321,9 @@<br> { "hevc-aq", no_argument, NULL, 0 },<br> { "no-hevc-aq", no_argument, NULL, 0 },<br> { "qp-adaptation-range", required_argument, NULL, 0 },<br>+ { "frame-dup", no_argument, NULL, 0 },<br>+ { "no-frame-dup", no_argument, NULL, 0 },<br>+ { "dup-threshold", required_argument, NULL, 0 },<br> #ifdef SVT_HEVC<br> { "svt", no_argument, NULL, 0 },<br> { "no-svt", no_argument, NULL, 0 },<br>@@ -638,6 +641,8 @@<br> H1(" --recon-depth <integer> Bit-depth of reconstructed raw image file. Defaults to input bit depth, or 8 if Y4M\n");<br> H1(" --recon-y4m-exec <string> pipe reconstructed frames to Y4M viewer, ex:\"ffplay -i pipe:0 -autoexit\"\n");<br> H0(" --lowpass-dct Use low-pass subband dct approximation. Default %s\n", OPT(param->bLowPassDct));<br>+ H0(" --[no-]frame-dup Enable Frame duplication. Default %s\n", OPT(param->bEnableFrameDuplication));<br>+ H0(" --dup-threshold <integer> PSNR threshold for Frame duplication. Default %d\n", param->dupThreshold);<br> #ifdef SVT_HEVC<br> H0(" --[no]svt Enable SVT HEVC encoder %s\n", OPT(param->bEnableSvtHevc));<br> H0(" --[no-]svt-hme Enable Hierarchial motion estimation(HME) in SVT HEVC encoder \n");<br></div><div><br></div><br clear="all"><div><div dir="ltr" class="gmail-m_-3376136530438352903gmail_signature"><div dir="ltr"><div><div dir="ltr"><div><div dir="ltr"><div dir="ltr"><font face="verdana, sans-serif" color="#0c343d">Thanks & Regards</font><div><font face="verdana, sans-serif" color="#0c343d"><b>Akil R</b></font></div><div><font face="verdana, sans-serif" color="#0c343d" size="1">Video Codec Engineer </font></div><div><font face="verdana, sans-serif" color="#0c343d" size="1">Media & AI Analytics</font></div><div><font color="#0c343d" face="trebuchet ms, sans-serif">+91 978 791 9223</font></div><div><a href="https://multicorewareinc.com/" target="_blank"><img src="https://docs.google.com/uc?export=download&id=1kc3RJu9M8bnIf6Xa5rUw2d-eEVUsPBE5&revid=0B7tw9XJBmynaemR1VUpQUi9DVytRVW5SVkRwVTFjb1hBMUcwPQ"></a><br></div></div></div></div></div></div></div></div></div></div>
</blockquote></div>