OpenVisualCloud / SVT-HEVC

SVT HEVC encoder. Scalable Video Technology (SVT) is a software-based video coding technology that is highly optimized for Intel® Xeon® processors. Using the open source SVT-HEVC encoder, it is possible to spread video encoding processing across multiple Intel® Xeon® processors to achieve a real advantage of processing efficiency.
Other
507 stars 169 forks source link

Reading from stdin doesn't work #634

Open Marzhin971 opened 1 year ago

Marzhin971 commented 1 year ago

Trying to write, in C#, a routine that calls an ffmpeg process, get the piped data inside a named pipe, then give the taken YUV data to SvtHevcEncApp via its stdin stream.

The way I do that works for a lot of external encoders like x264, x265, vvc, aom and xvid.

The shortened code I wrote is at the end of this post.

The behaviour of SvtHevcEncApp is always the same and easily reproductible : After one write is done to the bufferedStream (or directly into the svt_hevc process StandardInput), whichever the amount of data, SvtHevcEncApp will "think" the stream has ended and finishes encoding. It will still encode the number of frames one (and only one) buffer can contain, but the video content has its planes wrong. but I'll post another issue for that. My current problem is about the piping method.

AFAIK, signalling an end of stream to a process is done by closing the writer, that's what I do after receiving a data.Length that is shorter than the buffer (a information telling me ffmpeg has finished sending data to the pipe), but an exception is raised, in the same instruction (bufStream.Write), SvtHevcEncApp received the data, encoded what it can, leaves with exitcode==0, and then the IO exception is raised telling me the channel was closed and it's always in the first run of that loop. When the exception is raised, ffmpeg was still running, but not SvtHevcEncApp.

At this point, I can't be sure if it's something that's wrong in SvtHevcEncApp, but I think it is, since all other standalone encoders are working perfectly well.

static void DoPipe()
{
  int buffersize = 100000000;

  NamedPipeServerStream sPipe = new NamedPipeServerStream("testpipe.y4m", PipeDirection.InOut, 1,       PipeTransmissionMode.Byte, PipeOptions.None, buffersize, buffersize);

  string inputFile = @"h:\test_video.mp4";

  Process ffmpeg = new Process();
  ffmpeg.StartInfo.FileName = "ffmpeg.exe";
  ffmpeg.StartInfo.UseShellExecute = false;
  ffmpeg.StartInfo.RedirectStandardInput = true;
  ffmpeg.StartInfo.RedirectStandardOutput = true;
  ffmpeg.StartInfo.RedirectStandardError = true;
  ffmpeg.OutputDataReceived += new DataReceivedEventHandler(Output_ffmpeg_Received);
  ffmpeg.ErrorDataReceived += new DataReceivedEventHandler(Error_ffmpeg_Received);
  ffmpeg.StartInfo.Arguments = "-hide_banner -nostdin -i " + inputFile + " -t 30 -pix_fmt yuv420p -s 1920x1080 -an -y -f yuv4mpegpipe \"\\\\.\\pipe\\testpipe.y4m\"";

  ffmpeg.Start();
  ffmpeg.BeginErrorReadLine();
  ffmpeg.BeginOutputReadLine();

  sPipe.WaitForConnection();
  BinaryReader reader = new BinaryReader(sPipe);
  bool stop = false;

  Process svt_hevc = new Process();
  svt_hevc.StartInfo.FileName = "SvtHevcEncApp.exe";
  svt_hevc.StartInfo.UseShellExecute = false;
  svt_hevc.StartInfo.RedirectStandardInput = true;
  svt_hevc.StartInfo.RedirectStandardError = true;
  svt_hevc.StartInfo.RedirectStandardOutput = true;
  svt_hevc.OutputDataReceived += new DataReceivedEventHandler(Output_encoder_Received);
  svt_hevc.ErrorDataReceived += new DataReceivedEventHandler(Error_encoder_Received);
  svt_hevc.StartInfo.Arguments = "-color-format 1 -bit-depth 8 -w 1920 -h 1080 -fps 25 -i stdin -rc 0 -q 10 -errlog -encMode 6 - b test.svt-hevc.hevc";

  svt_hevc.Start();
  svt_hevc.BeginErrorReadLine();
  svt_hevc.BeginOutputReadLine();

  var bufStream = new BufferedStream(svt_hevc.StandardInput.BaseStream, buffersize);

  do
  {
    byte[] data = reader.ReadBytes(buffersize);

    if (data.Length > 0)
      try { bufStream.Write(data, 0, data.Length);  }
      catch (ArgumentException e) { Console.WriteLine("Argument Exception {0}", e.Message); }
      catch (IOException e) { Console.WriteLine("IO Exception {0}", e.Message); } // SvtHevcEncApp raises an exception here
      catch (NotSupportedException e) { Console.WriteLine("NotSupportedException {0}", e.Message); }
      catch (ObjectDisposedException e) { Console.WriteLine("Object Disposed Exception {0}",e.Message); }

  if (data.Length < buffersize) stop = true; // If data length is not buffersize, ffmpeg has finished. we stop the loop

  } while (!svt_hevc.HasExited && !stop); // we dont need to wait for ffmpeg to finish, stop will tell us

  sPipe.Close(); // closing ffmpeg pipe
  svt_hevc.StandardInput.BaseStream.Close(); // signalling to svt that the stream is EOF
  ffmpeg.WaitForExit(); // to be sure
  svt_hevc.WaitForExit(); // to be very sure
}
1480c1 commented 1 year ago

Unfortunately, the app is not designed similar to other encoders and has a few issues and quirks, one of them is the fact that it does not properly check for eof from stdin. It is pretty much required to use -n to tell the encoder when to stop

Marzhin971 commented 1 year ago

In the meantime, I added a test to avoid working from a pipe with the two commands :

ffmpeg -hide_banner -t 30 -nostdin -i h:\test_video.mp4 -pix_fmt yuv420p -s 1920x1080 -an -y -f yuv4mpegpipe testpipe.y4m

svthevcencapp -color-format 1 -bit-depth 8 -w 1920 -h 1080 -fps 25 -i testpipe.y4m -rc 0 -q 10 -errlog -encMode 6 -thread-count 24 -b test.svt-hevc.x265

It works perfectly and planes are ok.

So, with the -n option, it now works as expected with good planes, but having to count the number of frames in the source video before encoding isn't a good solution obviously. I'll keep the issue open in order to possibly know if one of the app developpers push that in the "todo queue". thanks for your answer