[x265] [PATCH] cli: add an output preview feature, activated by --recon-y4m-exec

Steve Borho steve at borho.org
Mon Apr 13 20:34:17 CEST 2015


# HG changeset patch
# User Peixuan Zhang <zhangpeixuancn at gmail.com>
# Date 1428568278 -28800
#      Thu Apr 09 16:31:18 2015 +0800
# Node ID 3749af0b42772c8d5a11d0b0f8710a532e7a8319
# Parent  1ca06e792b1ea06b0620e4481a5f1092b8633e41
cli: add an output preview feature, activated by --recon-y4m-exec

if you have an application which can play a Y4MPEG stream received on stdin,
the x265 CLI can feed it reconstructed pictures in display order.  The pictures
will have no timing info, obviously, so the picture timing will be determined
primarily by encoding elapsed time and latencies, but it can be useful to
preview the pictures being output by the encoder to validate input settings and
rate control parameters.

diff -r 1ca06e792b1e -r 3749af0b4277 doc/reST/cli.rst
--- a/doc/reST/cli.rst	Sun Apr 12 10:02:24 2015 -0500
+++ b/doc/reST/cli.rst	Thu Apr 09 16:31:18 2015 +0800
@@ -1562,4 +1562,20 @@
 
 	**CLI ONLY**
 
+.. option:: --recon-y4m-exec <string>
+
+	If you have an application which can play a Y4MPEG stream received
+	on stdin, the x265 CLI can feed it reconstructed pictures in display
+	order.  The pictures will have no timing info, obviously, so the
+	picture timing will be determined primarily by encoding elapsed time
+	and latencies, but it can be useful to preview the pictures being
+	output by the encoder to validate input settings and rate control
+	parameters.
+
+	Example command for ffplay (assuming it is in your PATH):
+
+	--recon-y4m-exec "ffplay -i pipe:0 -autoexit"
+
+	**CLI ONLY**
+
 .. vim: noet
diff -r 1ca06e792b1e -r 3749af0b4277 source/CMakeLists.txt
--- a/source/CMakeLists.txt	Sun Apr 12 10:02:24 2015 -0500
+++ b/source/CMakeLists.txt	Thu Apr 09 16:31:18 2015 +0800
@@ -467,7 +467,7 @@
 option(ENABLE_CLI "Build standalone CLI application" ON)
 if(ENABLE_CLI)
     file(GLOB InputFiles input/input.cpp input/yuv.cpp input/y4m.cpp input/*.h)
-    file(GLOB OutputFiles output/output.cpp output/*.h
+    file(GLOB OutputFiles output/output.cpp output/reconplay.cpp output/*.h
                           output/yuv.cpp output/y4m.cpp # recon
                           output/raw.cpp)               # muxers
     file(GLOB FilterFiles filters/*.cpp filters/*.h)
diff -r 1ca06e792b1e -r 3749af0b4277 source/output/reconplay.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/output/reconplay.cpp	Thu Apr 09 16:31:18 2015 +0800
@@ -0,0 +1,193 @@
+/*****************************************************************************
+ * Copyright (C) 2013 x265 project
+ *
+ * Authors: Peixuan Zhang <zhangpeixuancn at gmail.com>
+ *          Chunli Zhang <chunli at multicorewareinc.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
+ *
+ * This program is also available under a commercial proprietary license.
+ * For more information, contact us at license @ x265.com.
+ *****************************************************************************/
+
+#include "common.h"
+#include "reconplay.h"
+
+#include <signal.h>
+
+using namespace x265;
+
+#if _WIN32
+#define popen  _popen
+#define pclose _pclose
+#define pipemode "wb"
+#else
+#define pipemode "w"
+#endif
+
+bool ReconPlay::pipeValid;
+
+static void sigpipe_handler(int)
+{
+    if (ReconPlay::pipeValid)
+        general_log(NULL, "exec", X265_LOG_ERROR, "pipe closed\n");
+    ReconPlay::pipeValid = false;
+}
+
+ReconPlay::ReconPlay(const char* commandLine, x265_param& param)
+{
+    if (signal(SIGPIPE, sigpipe_handler) == SIG_ERR)
+        general_log(&param, "exec", X265_LOG_ERROR, "Unable to register SIGPIPE handler: %s\n", strerror(errno));
+
+    width = param.sourceWidth;
+    height = param.sourceHeight;
+    colorSpace = param.internalCsp;
+
+    frameSize = 0;
+    for (int i = 0; i < x265_cli_csps[colorSpace].planes; i++)
+        frameSize += (uint32_t)((width >> x265_cli_csps[colorSpace].width[i]) * (height >> x265_cli_csps[colorSpace].height[i]));
+
+    for (int i = 0; i < RECON_BUF_SIZE; i++)
+    {
+        poc[i] = -1;
+        CHECKED_MALLOC(frameData[i], pixel, frameSize);
+    }
+
+    outputPipe = popen(commandLine, pipemode);
+    if (outputPipe)
+    {
+        const char* csp = (colorSpace >= X265_CSP_I444) ? "444" : (colorSpace >= X265_CSP_I422) ? "422" : "420";
+        const char* depth = (param.internalBitDepth == 10) ? "p10" : "";
+
+        fprintf(outputPipe, "YUV4MPEG2 W%d H%d F%d:%d Ip C%s%s\n", width, height, param.fpsNum, param.fpsDenom, csp, depth);
+
+        pipeValid = true;
+        threadActive = true;
+        start();
+        return;
+    }
+    else
+        general_log(&param, "exec", X265_LOG_ERROR, "popen(%s) failed\n", commandLine);
+
+fail:
+    threadActive = false;
+}
+
+ReconPlay::~ReconPlay()
+{
+    if (threadActive)
+    {
+        threadActive = false;
+        writeCount.poke();
+        stop();
+    }
+
+    if (outputPipe) 
+        pclose(outputPipe);
+
+    for (int i = 0; i < RECON_BUF_SIZE; i++)
+        X265_FREE(frameData[i]);
+}
+
+bool ReconPlay::writePicture(const x265_picture& pic)
+{
+    if (!threadActive || !pipeValid)
+        return false;
+
+    int written = writeCount.get();
+    int read = readCount.get();
+    int currentCursor = pic.poc % RECON_BUF_SIZE;
+
+    /* TODO: it's probably better to drop recon pictures when the ring buffer is
+     * backed up on the display app */
+    while (written - read > RECON_BUF_SIZE - 2 || poc[currentCursor] != -1)
+    {
+        read = readCount.waitForChange(read);
+        if (!threadActive)
+            return false;
+    }
+
+    X265_CHECK(pic.colorSpace == colorSpace, "invalid color space\n");
+    X265_CHECK(pic.bitDepth == X265_DEPTH,   "invalid bit depth\n");
+
+    pixel* buf = frameData[currentCursor];
+    for (int i = 0; i < x265_cli_csps[colorSpace].planes; i++)
+    {
+        char* src = (char*)pic.planes[i];
+        int pwidth = width >> x265_cli_csps[colorSpace].width[i];
+
+        for (int h = 0; h < height >> x265_cli_csps[colorSpace].height[i]; h++)
+        {
+            memcpy(buf, src, pwidth * sizeof(pixel));
+            src += pic.stride[i];
+            buf += pwidth;
+        }
+    }
+
+    poc[currentCursor] = pic.poc;
+    writeCount.incr();
+
+    return true;
+}
+
+void ReconPlay::threadMain()
+{
+    THREAD_NAME("ReconPlayOutput", 0);
+
+    do
+    {
+        /* extract the next output picture in display order and write to pipe */
+        if (!outputFrame())
+            break;
+    }
+    while (threadActive);
+
+    threadActive = false;
+    readCount.poke();
+}
+
+bool ReconPlay::outputFrame()
+{
+    int written = writeCount.get();
+    int read = readCount.get();
+    int currentCursor = read % RECON_BUF_SIZE;
+
+    while (poc[currentCursor] != read)
+    {
+        written = writeCount.waitForChange(written);
+        if (!threadActive)
+            return false;
+    }
+
+    char* buf = (char*)frameData[currentCursor];
+    intptr_t remainSize = frameSize * sizeof(pixel);
+
+    fprintf(outputPipe, "FRAME\n");
+    while (remainSize > 0)
+    {
+        intptr_t retCount = (intptr_t)fwrite(buf, sizeof(char), remainSize, outputPipe);
+
+        if (retCount < 0 || !pipeValid)
+            /* pipe failure, stop writing and start dropping recon pictures */
+            return false;
+    
+        buf += retCount;
+        remainSize -= retCount;
+    }
+
+    poc[currentCursor] = -1;
+    readCount.incr();
+    return true;
+}
diff -r 1ca06e792b1e -r 3749af0b4277 source/output/reconplay.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/output/reconplay.h	Thu Apr 09 16:31:18 2015 +0800
@@ -0,0 +1,74 @@
+/*****************************************************************************
+ * Copyright (C) 2013 x265 project
+ *
+ * Authors: Peixuan Zhang <zhangpeixuancn at gmail.com>
+ *          Chunli Zhang <chunli at multicorewareinc.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
+ *
+ * This program is also available under a commercial proprietary license.
+ * For more information, contact us at license @ x265.com.
+ *****************************************************************************/
+
+#ifndef X265_RECONPLAY_H
+#define X265_RECONPLAY_H
+
+#include "x265.h"
+#include "threading.h"
+#include <cstdio>
+
+namespace x265 {
+// private x265 namespace
+
+class ReconPlay : public Thread
+{
+public:
+
+    ReconPlay(const char* commandLine, x265_param& param);
+
+    virtual ~ReconPlay();
+
+    bool writePicture(const x265_picture& pic);
+
+    static bool pipeValid;
+
+protected:
+
+    enum { RECON_BUF_SIZE = 40 };
+
+    FILE*  outputPipe;     /* The output pipe for player */
+    size_t frameSize;      /* size of one frame in pixels */
+    bool   threadActive;   /* worker thread is active */
+    int    width;          /* width of frame */
+    int    height;         /* height of frame */
+    int    colorSpace;     /* color space of frame */
+
+    int    poc[RECON_BUF_SIZE];
+    pixel* frameData[RECON_BUF_SIZE];
+
+    /* Note that the class uses read and write counters to signal that reads and
+     * writes have occurred in the ring buffer, but writes into the buffer
+     * happen in decode order and the reader must check that the POC it next
+     * needs to send to the pipe is in fact present.  The counters are used to
+     * prevent the writer from getting too far ahead of the reader */
+    ThreadSafeInteger readCount;
+    ThreadSafeInteger writeCount;
+
+    void threadMain();
+    bool outputFrame();
+};
+}
+
+#endif // ifndef X265_RECONPLAY_H
diff -r 1ca06e792b1e -r 3749af0b4277 source/x265.cpp
--- a/source/x265.cpp	Sun Apr 12 10:02:24 2015 -0500
+++ b/source/x265.cpp	Thu Apr 09 16:31:18 2015 +0800
@@ -27,6 +27,7 @@
 
 #include "input/input.h"
 #include "output/output.h"
+#include "output/reconplay.h"
 #include "filters/filters.h"
 #include "common.h"
 #include "param.h"
@@ -73,6 +74,7 @@
     ReconFile* recon;
     OutputFile* output;
     FILE*       qpfile;
+    const char* reconPlayCmd;
     bool bProgress;
     bool bForceY4m;
     bool bDither;
@@ -98,6 +100,7 @@
         prevUpdateTime = 0;
         bDither = false;
         qpfile = NULL;
+        reconPlayCmd = NULL;
     }
 
     void destroy();
@@ -252,6 +255,7 @@
             OPT("profile") profile = optarg; /* handled last */
             OPT("preset") /* handled above */;
             OPT("tune")   /* handled above */;
+            OPT("recon-y4m-exec") reconPlayCmd = optarg;
             OPT("qpfile")
             {
                 this->qpfile = fopen(optarg, "rb");
@@ -458,7 +462,8 @@
     if (!api)
         api = x265_api_get(0);
 
-    x265_param *param = api->param_alloc();
+    ReconPlay* reconPlay = NULL;
+    x265_param* param = api->param_alloc();
     CLIOptions cliopt;
 
     if (cliopt.parse(argc, argv, param))
@@ -471,6 +476,9 @@
     /* This allow muxers to modify bitstream format */
     cliopt.output->setParam(param);
 
+    if (cliopt.reconPlayCmd)
+        reconPlay = new ReconPlay(cliopt.reconPlayCmd, *param);
+
     /* note: we could try to acquire a different libx265 API here based on
      * the profile found during option parsing, but it must be done before
      * opening an encoder */
@@ -496,7 +504,7 @@
     x265_picture *pic_in = &pic_orig;
     /* Allocate recon picture if analysisMode is enabled */
     std::priority_queue<int64_t>* pts_queue = cliopt.output->needPTS() ? new std::priority_queue<int64_t>() : NULL;
-    x265_picture *pic_recon = (cliopt.recon || !!param->analysisMode || pts_queue) ? &pic_out : NULL;
+    x265_picture *pic_recon = (cliopt.recon || !!param->analysisMode || pts_queue || reconPlay) ? &pic_out : NULL;
     uint32_t inFrameCount = 0;
     uint32_t outFrameCount = 0;
     x265_nal *p_nal;
@@ -567,6 +575,10 @@
             ret = 4;
             break;
         }
+
+        if (reconPlay && numEncoded)
+            reconPlay->writePicture(*pic_recon);
+
         outFrameCount += numEncoded;
 
         if (numEncoded && pic_recon && cliopt.recon)
@@ -594,6 +606,10 @@
             ret = 4;
             break;
         }
+
+        if (reconPlay && numEncoded)
+            reconPlay->writePicture(*pic_recon);
+
         outFrameCount += numEncoded;
         if (numEncoded && pic_recon && cliopt.recon)
             cliopt.recon->writePicture(pic_out);
@@ -619,6 +635,9 @@
         fprintf(stderr, "%*s\r", 80, " ");
 
 fail:
+
+    delete reconPlay;
+
     api->encoder_get_stats(encoder, &stats, sizeof(stats));
     if (param->csvfn && !b_ctrl_c)
         api->encoder_log(encoder, argc, argv);
diff -r 1ca06e792b1e -r 3749af0b4277 source/x265cli.h
--- a/source/x265cli.h	Sun Apr 12 10:02:24 2015 -0500
+++ b/source/x265cli.h	Thu Apr 09 16:31:18 2015 +0800
@@ -208,6 +208,7 @@
     { "temporal-layers",      no_argument, NULL, 0 },
     { "no-temporal-layers",   no_argument, NULL, 0 },
     { "qg-size",        required_argument, NULL, 0 },
+    { "recon-y4m-exec", required_argument, NULL, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 0 },
     { 0, 0, 0, 0 },
@@ -399,6 +400,7 @@
     H1("\nReconstructed video options (debugging):\n");
     H1("-r/--recon <filename>            Reconstructed raw image YUV or Y4M output file name\n");
     H1("   --recon-depth <integer>       Bit-depth of reconstructed raw image file. Defaults to input bit depth, or 8 if Y4M\n");
+    H1("   --recon-y4m-exec <string>     pipe reconstructed frames to Y4M viewer, ex:\"ffplay -i pipe:0 -autoexit\"\n");
     H1("\nExecutable return codes:\n");
     H1("    0 - encode successful\n");
     H1("    1 - unable to parse command line\n");


More information about the x265-devel mailing list