2

I'm struggling with the following issue, on a Windows system:

  • I get a live stream from an SDK; I get chunks of bytes via a callback; the container is MPEG-PS
  • I have to display this live stream to the end-user using VLC. But VLC cannot play this stream I receive
  • My solution was to use ffmpeg to convert this to MPEG-TS (or something else) that can be played by VLC

I tried different possible solutions, like making a TCP server which exposes this stream, instructing ffmpeg to read the input from there. But ffmpeg is complaining that it cannot detect the container type. If I save to disk the bytes that I receive via that callback, I can see that the first bytes are ‘IMKH’, indicating a MPEG-PS container. If I then use ffmpeg to transcode it to MP4 (as a file), that works. But I need to do this ‘dynamically’, so a live transcoding of the stream that I receive.

Last thing that I have tried was to create a named pipe and write the received bytes in it, then used the following command to create the live stream: ffmpeg -i \.\pipe\testpipe -f mpegts -c copy -listen 1 "http://127.0.0.1:5002” and then play the URL “http://127.0.0.1:5002” in VLC.

Something strange is happening, because only when I close the pipe, the ffmpeg start to output (in console) the found streams and VLC can connect to its http server and starts playing correctly. I flush the pipe after writing each chunk of bytes.

I would expect ffmpeg to start processing the pipe input as it is received, and immediately output the result for VLC. I found something similar in the following post, but without an answer: https://ffmpeg.org/pipermail/ffmpeg-user/2016-December/034639.html

Why is ffmpeg waiting to close the pipe to start processing? Can it be configured to start a live transcoding of the received stream?

EDIT - MORE DETAILS ====================================

I cannot make available the source stream (it is isolated in an intranet), but as I mentioned I use a 3rd party C# SDK to access it (actually this is the only way to access it). That SDK provides me access to that stream via a simple callback which looks like this: callback(byte[] data). So using this callback I receive all the stream bytes. I saved these bytes to a file on disk. The received stream is MPEG-PS. If I try to convert this file to another file format (like avi) that works: ffmpeg.exe -i input.mpeg out.avi and VLC can play this out.avi. So I know that the callback data that I get is correct, so input.mpeg is a correct file, ffmpeg can deal with it.

For my testing I also use this input.mpeg file, I created a simple C# server which reads it, and exposes it in chunks via a named pipe using this code:

_pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.Out);
_pipeServer.WaitForConnection(); // wait for ffmpeg to connect

byte[] allBytes = System.IO.File.ReadAllBytes(“input.mpeg”);

using (BinaryWriter bw = new BinaryWriter(_pipeServer))
{                    
   int index = 0;                       
    for(;;)
    {
      bw.Write(allBytes, idx, 100);
      index += 100;

      // added some delay to emulate a live stream
      System.Threading.Thread.Sleep(100);

     if (idx >= allBytes.Length) break;
    }
}

This is the exact description of the flow:

But nothing happens; only when the C# server completes sending (so the BinaryWriter object is disposed, so ffmpeg detects the end of input stream) VLC starts playing. It seems that ffmpeg accumulates the received stream and only provides the output when the input completes.

I made also the following test:

  • I converted input.mpeg which has MPEG_PS format to MPEG-TS format using: ffmpeg.exe -i input.mpeg input.ts
  • then I feed input.ts in the named pipe “testpipe” and start ffmpeg with the same args: ffmpeg -i \.\pipe\testpipe -f mpegts –c copy -listen 1 http://127.0.0.1:5002
  • I start VLC with http://127.0.0.1:5002 and it starts immediately.

So I think this proves is not a pipe issue, but a format/container issue. When the pipe is fed with MPEG-PS, ffmpeg waits for EOF; but when is fed with MPEG-TS, ffmpeg starts producing output immediately (and this is what I want to achieve, live streaming).

BTW: if I kill the C# server above when it gets to the middle of the input.mpeg, the effect is the same, VLC starts playing the available stream. So I don’t think ffmpeg really needs the entire stream to be downloaded in order to start forwarding it to output.

Do you know how can I convince ffmpeg to start producing output immediately? Probably is some parameter for ffmpeg, but I could not find it … I have placed the input.mpeg file here:

https://www.dropbox.com/s/zsdktikfq4yuak0/input.mpeg?dl=0

Thank you!

user1673443
  • 21
  • 1
  • 3
  • Is there any way you can make a reproducible example? The part that receive the MPEG-TS stream and write it to the pipe is missing. How do we create named pipe in Windows? Can you share the MPEG-PS stream in your post? – Rotem Mar 06 '22 at 06:59
  • Thank you for your reply! I have edited the initial post above for more details and put the MPEG-PS file on dropbox. – user1673443 Mar 06 '22 at 10:39
  • Experiencing the same problem with a very similar setup. Tried using sockets instead of pipes (tcp://host:port protocol) but the behaviour is the same. If I push enough stuff in the input pipe I finally get something on the output pipe but that's not what I need. Did you find any solution? – Guy Moreillon May 05 '22 at 09:32

3 Answers3

1

I had the same issue in a slightly different context, and after much hair-pulling and deep-diving in the FFMPEG code, I finally found the reason for this hanging of the input pipe: FFMPEG tries to find as much information about the input stream as possible at the start, and to do this it reads and decodes the input until it is satisfied it has enough information.

Depending on your codec and format, this can require more or less data and in some cases (mine at least), 4 seconds worth of video wasn't enough (why is another subject).

The default maximum probe size is 500000 bytes, but it can be modified with the -probesize option (see https://ffmpeg.org/ffmpeg-formats.html), e.g.:

fmpeg -probesize 8192 -i input ...

Setting this value to the minimum data size you are sure to have available at the start of the stream will get you rolling (but you may have less information about the stream than you'd like).

0

You need to use a format that works on-the-fly. Like ogv, webm or better hls. Try for example: $ ffmpeg -i video.mp4 -f hls ./m3u8/video.m3u8 And, ffplay ./m3u8/video.m3u8 it's ok from the first time. I recommend the series of posts : https://www.martin-riedl.de/ffmpeg/

JuanPC
  • 21
  • 3
  • I tried it, but the main problem is that ffmpeg waits for the entire MPEG-PS stream to arrive before even attempting to convert it to HLS, or something else. So what I actually need is a way to force ffmpeg to start transcoding while the input stream is still arriving (this is a live stream). – user1673443 Mar 07 '22 at 10:50
0

I can't make it work with VLC, but with FFplay or mpv, it's working without the need to convert the format.

We may write the data directly to stdin pipe of FFplay (or mpv) sub-process.

Here is a C# code sample:

using System;
using System.Diagnostics;


namespace PipeFfmpeg
{
    class Program
    {
        static void Main(string[] args)
        {
            //https://stackoverflow.com/questions/19658415/how-to-use-pipe-in-ffmpeg-within-c-sharp
            Process proc = new Process();

            //Get mpegps in stdin pipe, and convert to mpegts stream (the -bsf:v dump_extra may or may not be need).
            //proc.StartInfo.FileName = @"ffplay.exe";
            proc.StartInfo.FileName = @"mpv.exe";            
            proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.RedirectStandardInput = true;
            proc.StartInfo.RedirectStandardOutput = false;

            proc.Start();

            //https://superuser.com/questions/1708271/ffmpeg-waits-to-close-the-pipe-in-order-to-start-processing-data
            byte[] allBytes = System.IO.File.ReadAllBytes("input.mpeg");

            //Write the same content 500 times.
            for (int i = 0; i < 500; i++)
            {
                int idx = 0;
                for (;;)
                {
                    proc.StandardInput.BaseStream.Write(allBytes, idx, Math.Min(100, allBytes.Length - idx));
                    idx += 100;

                    if (idx >= allBytes.Length) break;
                }
            }
        }
    }
}

Sample output:
enter image description here


Update:

  • Write the mpeg-ps data to stdin pipe of FFmpeg.
  • FFmpeg generates new timestamps, converts to raw AVI format, and passes to stdout pipe.
  • A thread reads data from stdout of FFmpeg and write it to stdin pipe of MPV.

Code sample:

using System;
using System.Diagnostics;
using System.Threading.Tasks;


namespace PipeFfmpeg
{
    class Program
    {
        static void Main(string[] args)
        {
            //https://stackoverflow.com/questions/19658415/how-to-use-pipe-in-ffmpeg-within-c-sharp
            //Process proc = new Process();

            //https://stackoverflow.com/questions/21213895/how-to-stream-live-videos-with-no-latency-ffplay-mplayer-and-what-kind-of-wra
            //https://github.com/mpv-player/mpv/issues/4213
            //proc.StartInfo.FileName = @"mpv.exe";
            //proc.StartInfo.Arguments = String.Format("--profile=low-latency -f mpeg -vcodec h264 -acodec pcm_alaw - ");    //Get data from input pipe
            //proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe

            Process ffmpeg_proc = new Process();
            ffmpeg_proc.StartInfo.FileName = @"ffmpeg.exe";
            ffmpeg_proc.StartInfo.Arguments = String.Format("-fflags +genpts+igndts+ignidx -probesize 1024 -i pipe: -vf setpts=PTS-STARTPTS -af asetpts=PTS-STARTPTS -vcodec rawvideo -acodec pcm_s16le -pix_fmt bgr24 -f avi pipe:");
            ffmpeg_proc.StartInfo.UseShellExecute = false;
            ffmpeg_proc.StartInfo.RedirectStandardInput = true;
            ffmpeg_proc.StartInfo.RedirectStandardOutput = true;

            Process mpv_proc = new Process();
            mpv_proc.StartInfo.FileName = @"mpv.exe";
            mpv_proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe
            mpv_proc.StartInfo.UseShellExecute = false;
            mpv_proc.StartInfo.RedirectStandardInput = true;
            mpv_proc.StartInfo.RedirectStandardOutput = false;


            //https://stackoverflow.com/questions/16658873/how-to-minimize-the-delay-in-a-live-streaming-with-ffmpeg
            //proc.StartInfo.FileName = @"ffplay.exe";
            //proc.StartInfo.Arguments = String.Format("-f mpegts -i pipe:");    //Get data from input pipe

            //proc.StartInfo.FileName = @"c:\\Program Files\\VideoLAN\\VLC\\vlc.exe";
            //proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe

            mpv_proc.Start();
            ffmpeg_proc.Start();
            
            // Added 3 seconds delay - wait for FFplay or MPV to start.
            System.Threading.Thread.Sleep(3000);

            byte[] allBytes = System.IO.File.ReadAllBytes("input.mpeg");    //12fps

            var helper = Task.Run(() =>
            {
                int n_bytes;
                byte[] buf = new byte[1024];

                while (true)
                {
                    n_bytes = ffmpeg_proc.StandardOutput.BaseStream.Read(buf, 0, 1024);

                    if (n_bytes > 0)
                    {
                        mpv_proc.StandardInput.BaseStream.Write(buf, 0, n_bytes);
                    }
                }
            });

            //Write the same content 500000 times.
            for (int i = 0; i < 500000; i++)
            {
                int idx = 0;

                while (true)
                {
                    int block_size = 500;

                    ffmpeg_proc.StandardInput.BaseStream.Write(allBytes, idx, Math.Min(block_size, allBytes.Length - idx));
                    ffmpeg_proc.StandardInput.BaseStream.Flush();
                    idx += block_size;

                    System.Threading.Thread.Sleep(50);  //Sleep 50msec (83.333msec applies 12fps, assume some overhead).

                    Console.WriteLine("idx = {0}", idx);

                    if (idx >= allBytes.Length) break;
                }
            }
        }
    }
}

Same code with FFplay instead of mpv player:

using System;
using System.Diagnostics;
using System.Threading.Tasks;


namespace PipeFfmpeg
{
    class Program
    {
        static void Main(string[] args)
        {
            Process ffmpeg_proc = new Process();
            ffmpeg_proc.StartInfo.FileName = @"ffmpeg.exe";
            ffmpeg_proc.StartInfo.Arguments = String.Format("-fflags +genpts+igndts+ignidx -probesize 1024 -i pipe: -vf setpts=PTS-STARTPTS -af asetpts=PTS-STARTPTS -vcodec rawvideo -acodec pcm_s16le -pix_fmt bgr24 -f avi pipe:");
            ffmpeg_proc.StartInfo.UseShellExecute = false;
            ffmpeg_proc.StartInfo.RedirectStandardInput = true;
            ffmpeg_proc.StartInfo.RedirectStandardOutput = true;

            //Process mpv_proc = new Process();
            //mpv_proc.StartInfo.FileName = @"mpv.exe";
            //mpv_proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe
            //mpv_proc.StartInfo.UseShellExecute = false;
            //mpv_proc.StartInfo.RedirectStandardInput = true;
            //mpv_proc.StartInfo.RedirectStandardOutput = false;

            Process ffplay_proc = new Process();
            ffplay_proc.StartInfo.FileName = @"ffplay.exe";
            ffplay_proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe
            ffplay_proc.StartInfo.UseShellExecute = false;
            ffplay_proc.StartInfo.RedirectStandardInput = true;
            ffplay_proc.StartInfo.RedirectStandardOutput = false;


            ffplay_proc.Start();
            ffmpeg_proc.Start();
            
            // Added 3 seconds delay - wait for FFplay or MPV to start.
            System.Threading.Thread.Sleep(3000);

            byte[] allBytes = System.IO.File.ReadAllBytes("input.mpeg");    //12fps

            var helper = Task.Run(() =>
            {
                int n_bytes;
                byte[] buf = new byte[1024];

                while (true)
                {
                    n_bytes = ffmpeg_proc.StandardOutput.BaseStream.Read(buf, 0, 1024);

                    if (n_bytes > 0)
                    {
                        //mpv_proc.StandardInput.BaseStream.Write(buf, 0, n_bytes);
                        ffplay_proc.StandardInput.BaseStream.Write(buf, 0, n_bytes);
                    }
                }
            });

            //Write the same content 500000 times.
            for (int i = 0; i < 500000; i++)
            {
                int idx = 0;

                while (true)
                {
                    int block_size = 500;

                    ffmpeg_proc.StandardInput.BaseStream.Write(allBytes, idx, Math.Min(block_size, allBytes.Length - idx));
                    ffmpeg_proc.StandardInput.BaseStream.Flush();
                    idx += block_size;

                    System.Threading.Thread.Sleep(50);  //Sleep 50msec (83.333msec applies 12fps, assume some overhead).

                    Console.WriteLine("idx = {0}", idx);

                    if (idx >= allBytes.Length) break;
                }
            }
        }
    }
}

VLC Example - re-encoding to H.264 (and aac) and re-muxing to mpegts:

using System;
using System.Diagnostics;
using System.Threading.Tasks;


namespace PipeFfmpeg
{
    class Program
    {
        static void Main(string[] args)
        {
            Process ffmpeg_proc = new Process();
            ffmpeg_proc.StartInfo.FileName = @"ffmpeg.exe";
            ffmpeg_proc.StartInfo.Arguments = String.Format("-fflags +genpts+igndts+ignidx -probesize 1024 -i pipe: -vf setpts=PTS-STARTPTS -af asetpts=PTS-STARTPTS -vcodec libx264 -crf 10 -acodec aac -pix_fmt yuv420p -f mpegts pipe:");
            ffmpeg_proc.StartInfo.UseShellExecute = false;
            ffmpeg_proc.StartInfo.RedirectStandardInput = true;
            ffmpeg_proc.StartInfo.RedirectStandardOutput = true;

            Process vlc_proc = new Process();
            vlc_proc.StartInfo.FileName = @"c:\\Program Files\\VideoLAN\\VLC\\vlc.exe";
            vlc_proc.StartInfo.Arguments = String.Format(" - ");    //Get data from input pipe
            vlc_proc.StartInfo.UseShellExecute = false;
            vlc_proc.StartInfo.RedirectStandardInput = true;
            vlc_proc.StartInfo.RedirectStandardOutput = false;

            vlc_proc.Start();
            ffmpeg_proc.Start();
            
            byte[] allBytes = System.IO.File.ReadAllBytes("input.mpeg");    //12fps

            var helper = Task.Run(() =>
            {
                int n_bytes;
                byte[] buf = new byte[1024];

                while (true)
                {
                    n_bytes = ffmpeg_proc.StandardOutput.BaseStream.Read(buf, 0, 1024);

                    if (n_bytes > 0)
                    {
                        vlc_proc.StandardInput.BaseStream.Write(buf, 0, n_bytes);
                    }
                }
            });

            //Write the same content 500000 times.
            for (int i = 0; i < 500000; i++)
            {
                int idx = 0;

                while (true)
                {
                    int block_size = 500;

                    ffmpeg_proc.StandardInput.BaseStream.Write(allBytes, idx, Math.Min(block_size, allBytes.Length - idx));
                    ffmpeg_proc.StandardInput.BaseStream.Flush();
                    idx += block_size;

                    System.Threading.Thread.Sleep(50);  //Sleep 50msec (83.333msec applies 12fps, assume some overhead).

                    Console.WriteLine("idx = {0}", idx);

                    if (idx >= allBytes.Length) break;
                }
            }
        }
    }
}
Rotem
  • 1,607
  • 3
  • 10
  • 11
  • The test you described above pushes all the input data (almost) at once, so you can see mpv play. But if in the code above you insert a Sleep(10) (to simulate a real time live stream), you will see that the playing starts only when the pushing of all input completes. So this player behaves the same as ffmpeg: it waits for all input stream to arrive, then it starts playing it. And exactly this is my problem: I have this input stream MPEG-PS, and I need to display it in VLC, so I need a way to transcode it in which ffmpeg will not wait for the live stream to finish before transcodes it. – user1673443 Mar 07 '22 at 10:48
  • Are you referring to pushing the sample file or the entire 500 iterations? `input.mpeg` file size less than 1MB, and it's normal buffering. I did some research about latency in [this post](https://stackoverflow.com/questions/60462840/ffmpeg-delay-in-decoding-h264). Setting `-probesize 32` (in FFplay) reduces the latency. There are also tweaks for MPV player. – Rotem Mar 07 '22 at 13:12
  • You are right - it's not working! MPEG-PS is not supposed to be streamed, and it could be a bug in FFmpeg / FFplay / MPV. – Rotem Mar 07 '22 at 19:41
  • I had some progress... I updated my post. Please test it as is first, and then test is with your input stream (note: in the example, the audio is broken). – Rotem Mar 08 '22 at 18:45
  • I tried your sample code, but mpv player does not show; I can find it in TaskManager running as a process, but no window is shown; which version of mpv are you using? I tried to replace it with VLC, and the VLC window appears, but unfortunately plays nothing. – user1673443 Mar 11 '22 at 20:54
  • MPV Version mpv-x86_64-20220306-git-1c49d57, from [here](https://sourceforge.net/projects/mpv-player-windows/files/64bit/). FFmpeg version: version 4.4.1-full_build-www.gyan.dev. I placed mpv.exe (the entire content for the z7 archive) and ffmpeg.exe in the working directory of the C# project. – Rotem Mar 11 '22 at 21:11
  • You may try FFplay instead of mpv (both players are based on FFmpeg). I updated my post. – Rotem Mar 11 '22 at 21:20
  • I added an example using VLC, but it uses re-encoding (I can't say it is really working, but we can see some video and hear some audio). – Rotem Mar 11 '22 at 22:04
  • Unfortunatly, it doesn't work with ffplay, mpv or vlc on my system. I also tried on a different system with the same result. In the thread that reads data from stdout of FFmpeg and writes it to stdin pipe, the execution never passes the "ffmpeg_proc.StandardOutput.BaseStream.Read" line, indicating that ffmpeg outputs nothing. If I replace the input.mpeg with input.ts(which has MPEG-TS format), all the players work. I don't know why with MPEG-PS input is working on your system and not on mine... Thanks a lot for your effort! – user1673443 Mar 12 '22 at 23:29