Запись FFMPEG в приложении ASP. NET Core mvc - PullRequest
2 голосов
/ 31 марта 2020

Я использую ASP. NET Core и ffmpeg для записи живого видео потока. Когда страница получает запрос на получение, поток должен начать запись и сохранение в папку с помощью ffmpeg. Я хочу сделать так, чтобы при посещении конечной точки stop процесс ffmpeg был закрыт полностью.

К сожалению, я не могу отправить 'q' на стандартный ввод после выхода из метода Get. Использование taskkill требует использования / F, чтобы процесс ffmpeg (который не является окном) принудительно завершал работу и не сохранял видео должным образом, что приводило к повреждению файла.

Я попытался использовать Process.Kill(), но это также привело к повреждению файла. Кроме того, я попытался Process.CloseMainWindow(), который работал, но только когда процесс запускается как окно, и я не могу запустить процесс как окно на сервере, который я использую.

У меня есть включите код, который у меня есть, ниже, так что, надеюсь, кто-то может привести меня по правильному пути.

using System;
...
using Microsoft.Extensions.Logging;

namespace MyApp.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class RecordingController : Controller
    {
        private readonly ApplicationDbContext _context;
        private readonly ILogger<HomeController> _logger;

        public RecordingController(ApplicationDbContext context, ILogger<HomeController> logger)
        {
            _context = context;
            _logger = logger;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {

            // Define the os process
            var processStartInfo = new ProcessStartInfo()
            {
                // ffmpeg arguments
                Arguments = "-f mjpeg -i \"https://urlofstream.com/video.gci\" -r 5 \"wwwroot/video.mp4\"",
                FileName = "ffmpeg.exe",
                UseShellExecute = true
            };

            var p1 = Process.Start(processStartInfo);

            // p1.StandardInput.WriteLineAsync("q"); <-- This works here but not in the Stop method

            return Ok(p1.Id);
        }


        // GET: api/Recording/stop
        [HttpGet("stop/{pid}")]
        public ActionResult Stop(int pid)
        {
            Process processes = Process.GetProcessById(pid);
            processes.StandardInput.WriteLineAsync("q");     // Does not work, is not able to redirect input
            return Ok();
        }
    }
}


1 Ответ

1 голос
/ 19 апреля 2020

Я нашел решение этой проблемы, которое, мы надеемся, могло бы помочь другим. Решением является использование комбинации Singleton с ConcurrentDictionary и запись в StandardInput. Единственный способ записи в стандартный ввод запущенного процесса - это если у вас все еще есть доступ к запущенному дескриптору процесса, и у вас не будет доступа к нему из контроллера, поэтому вам нужно будет создать синглтон и хранить процессы в ConcurrentDictionary (который отлично подходит для обновления из нескольких потоков).

Сначала я создал класс диспетчера записи. RecordingManager.cs

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace MyApp.Services
{
    public class RecordingManager
    {
        // Key int is the ProcessId. Value process is the running Process
        private static ConcurrentDictionary<int, Process> _processes = new ConcurrentDictionary<int, Process>();

        public Process Start()
        {
            // Define the os process
            var processStartInfo = new ProcessStartInfo()
            {
                // ffmpeg arguments
                Arguments = "-f mjpeg -i https://urlofstream.com/video.gci -r 5 wwwroot/video.mp4",
                FileName = "ffmpeg.exe",
                RedirectStandardInput = true, // Must be set to true
                UseShellExecute = false       // Must be set to false
            };

            Process p = Process.Start(processStartInfo);

            // Add the Process to the Dictionary with Key: Process ID and value as the running process
            _processes.TryAdd(p1.Id, p);

            return p;
        }

        private Process GetProcessByPid(int pid)
        {
            return _processes.FirstOrDefault(p => p.Key == pid).Value;
        }

        public void Stop(int pid)
        {
            Process p = GetProcessByPid(pid);

            // FFMPEG Expects a q written to the STDIN to stop the process
            p.StandardInput.WriteLine("q\n");
            _processes.TryRemove(pid, out p);

            // Free up resources
            p.Close()
        }
    }
}

Затем я добавляю синглтон в мой startup.cs класс в методе ConfigureServices

services.AddSingleton<RecordingManager>();

Наконец, Я добавляю синглтон в конструктор контроллера и вызываю там методы. RecordingController.cs

namespace MyApp.Controllers
{
    [Route("api/[controller]")]
    [Authorize]
    public class RecordingController : Controller
    {
        private readonly ApplicationDbContext _context;
        private readonly ILogger<HomeController> _logger;
        private readonly RecordingManager _recordingManager;

        public RecordingController(ApplicationDbContext context, ILogger<HomeController> logger, RecordingManager recordingManager)
        {
            _context = context;
            _logger = logger;
            _recordingManager = recordingManager;
        }

        [HttpGet]
        public async Task<ActionResult> Get()
        {
            Process p = _recordingManager.Start();
            return Ok(p.Id); // Wouldn't recommend simply returning an Ok Result here. Check for issues
        }


        // GET: api/Recording/stop
        [HttpGet("stop/{pid}")]
        public ActionResult Stop(int pid)
        {
            _recordingManager.Stop(pid);
            return Ok(); // Wouldn't recommend simply returning an Ok Result here. Check for issues
        }
    }
}
...