ffmpeg - папка опроса файлов и потоковая передача видео с помощью rtp - PullRequest
0 голосов
/ 16 января 2019

(я новичок, когда дело доходит до ffmpeg).У меня есть источник изображения, который сохраняет файлы в заданную папку со скоростью 30 кадров в секунду.Я хочу подождать каждые (скажем, 30 кадров), кодировать их в h264 и передавать их с RDP в какое-то другое приложение.

Я думал о написании приложения на Python, которое просто ожидает изображения, а затемвыполняет команду ffmpeg.Для этого я написал следующий код:

main.py:

import os
import Helpers
import argparse
import IniParser
import subprocess
from functools import partial

from Queue import Queue
from threading import Semaphore, Thread


def Run(config):

    os.chdir(config.Workdir)
    iteration = 1

    q = Queue()
    Thread(target=RunProcesses, args=(q, config.AllowedParallelRuns)).start()

    while True:

        Helpers.FileCount(config.FramesPathPattern, config.ChunkSize * iteration)

        command = config.FfmpegCommand.format(startNumber = (iteration-1)*config.ChunkSize, vFrames=config.ChunkSize)

        runFunction = partial(subprocess.Popen, command)
        q.put(runFunction)

        iteration += 1

def RunProcesses(queue, semaphoreSize):

    semaphore = Semaphore(semaphoreSize)

    while True:

        runFunction = queue.get()

        Thread(target=HandleProcess, args=(runFunction, semaphore)).start()

def HandleProcess(runFunction, semaphore):

    semaphore.acquire()

    p = runFunction()
    p.wait()

    semaphore.release()

if __name__ == '__main__':

    argparser = argparse.ArgumentParser()
    argparser.add_argument("config", type=str, help="Path for the config file")
    args = argparser.parse_args()

    iniFilePath = args.config

    config = IniParser.Parse(iniFilePath)

    Run(config)

Helpers.py (не очень актуально):

import os
import time
from glob import glob

def FileCount(pattern, count):

    count = int(count)

    lastCount = 0
    while True:

        currentCount = glob(pattern)

        if lastCount != currentCount:
            lastCount = currentCount

        if len(currentCount) >= count and all([CheckIfClosed(f) for f in currentCount]):

            break

        time.sleep(0.05)

def CheckIfClosed(filePath):

    try:
        os.rename(filePath, filePath)
        return True
    except:
        return False

Я использовал следующую конфигурациюfile:

Workdir = "C:\Developer\MyProjects\Streaming\OutputStream\PPM"
; Workdir is the directory of reference from which all paths are relative to.
; You may still use full paths if you wish.

FramesPathPattern = "F*.ppm"
; The path pattern (wildcards allowed) where the rendered images are stored to.
; We use this pattern to detect how many rendered images are available for streaming.
; When a chunk of frames is ready - we stream it (or store to disk).

ChunkSize = 30 ; Number of frames for bulk.
; ChunkSize sets the number of frames we need to wait for, in order to execute the ffmpeg command.
; If the folder already contains several chunks, it will first process the first chunk, then second, and so on...

AllowedParallelRuns = 1 ; Number of parallel allowed processes of ffmpeg.
; This sets how many parallel ffmpeg processes are allowed.
; If more than one chunk is available in the folder for processing, we will execute several ffmpeg processes in parallel.
; Only when on of the processes will finish, we will allow another process execution.

FfmpegCommand = "ffmpeg -re -r 30 -start_number {startNumber} -i F%08d.ppm -vframes {vFrames} -vf vflip -f rtp rtp://127.0.0.1:1234" ; Command to execute when a bulk is ready for streaming.
; Once a chunk is ready for processing, this is the command that will be executed (same as running it from the terminal).
; There is however a minor difference. Since every chunk starts with a different frame number, you can use the
; expression of "{startNumber}" which will automatically takes the value of the matching start frame number.
; You can also use "{vFrames}" as an expression for the ChunkSize which was set above in the "ChunkSize" entry.

Обратите внимание, что если я установлю «AllowedParallelRuns = 2», то это позволит одновременно запускать несколько процессов ffmpeg.

Затем я попытался воспроизвести его с помощью ffplay и посмотреть,Я делаю это правильно.Первый кусок прошел нормально.Следующие куски были не так хороши.Я получил много [sdp @ 0000006de33c9180] RTP: dropping old packet received too late сообщений.

Что мне делать, чтобы я получил ffplay, чтобы воспроизводить его в порядке входящих изображений?Правильно ли запускать параллельные процессы ffmpeg?Есть ли лучшее решение моей проблемы?

Спасибо!

1 Ответ

0 голосов
/ 16 января 2019

Как я уже говорил в комментарии, поскольку вы повторно запускаете ffmpeg каждый раз, значения pts сбрасываются, но клиент воспринимает это как один непрерывный поток ffmpeg и, таким образом, ожидает увеличения значений PTS.

Как я уже сказал, вы можете использовать оболочку Python ffmpeg для управления потоковой передачей самостоятельно, но да, это довольно много кода. Но на самом деле есть грязный обходной путь.

Итак, очевидно, есть параметр -itsoffset, с помощью которого вы можете сместить входные метки времени (см. Документация FFmpeg ). Так как вы знаете и управляете скоростью, вы можете передать возрастающее значение с помощью этого параметра, чтобы каждый следующий поток был смещен на соответствующую длительность. Например. если вы передаете 30 кадров каждый раз, и вы знаете, что частота кадров равна 30, 30 кадров создают интервал времени в одну секунду. Таким образом, при каждом вызове ffmepg вы увеличиваете значение -itsoffset на одну секунду, что должно быть добавлено к выходным значениям PTS. Но я не могу гарантировать, что это работает.

Поскольку идея -itsoffset не сработала, вы также можете попробовать передать jpeg-изображения через stdin в ffmpeg - см. эту ссылку.

...