Как отправить данные с асинхронного сервера сокетов Python в подпроцесс?

1

Python 3.6

Эта программа:

  1. запускает ffmpeg в качестве подпроцесса
  2. ожидает соединения сокета
  3. получает PNG-изображения в гнезде
  4. отправляет изображения PNG в ffmpeg stdin

Проблема заключается в шаге 4. Я не могу решить, как отправить полученное изображение PNG из сопрограммы в stdin подпроцесса ffmpeg. Может ли кто-нибудь указать мне в правильном направлении отправить изображение PNG в stdin подпроцесса ffmpeg?

EDIT: уточнить - нет ничего плохого в этом коде, он получает PNG в порядке над сокетом. Я просто не знаю, как отправить PNG в stdin ffmpeg. Я сделал довольно много Python, но асинчио для меня новичок, и как все связывает тайну.

Спасибо!

import asyncio
import argparse, sys
import sys
import base64
from struct import unpack

parser = argparse.ArgumentParser()
parser.add_argument('--port', help='ffmpeg listen port')
parser.add_argument('--outputfilename', help='ffmpeg output filename')
args = parser.parse_args()
if not args.port:
    print("port is required")
    sys.exit(1)
if not args.outputfilename:
    print("outputfilename is required")
    sys.exit(1)

async def _read_stream(stream, cb):
    while True:
        line = await stream.readline()
        if line:
            cb(line)
        else:
            break

async def _stream_subprocess(cmd, stdout_cb, stderr_cb):
    process = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        stdin=asyncio.subprocess.PIPE,
    )

    await asyncio.wait([
        _read_stream(process.stdout, stdout_cb),
        _read_stream(process.stderr, stderr_cb)
    ])
    return await process.wait()


def process_stderr(line):
    # ffmpeg finishes processing and writes the output file when its input is closed
    # thus the completion message will come out of stderr only when the socket or stdin or whatever is closed
    line = line.decode()
    print(line)
    if "Output" in line:
        if args.outputfilename in line:
            print('finished!!!!')
            sys.exit(0)

def process_stdout(line):
    print("STDOUT: %s" % line)

def spawn_ffmpeg(listenport, outputfilename, framerate=30, format='webm'):
    outputdirectory = "sftp://username:[email protected]/var/www/static/"
    input_type = "pipe:0" #stdin

    params = \
        f"ffmpeg  " \
        f"-loglevel 56 " \
        f"-y -framerate {framerate} " \
        f"-f image2pipe " \
        f"-i {input_type} " \
        f"-c:v libvpx-vp9 " \
        f"-b:v 1024k " \
        f"-q:v 0 " \
        f"-pix_fmt yuva420p " \
        f"{outputdirectory}{outputfilename} "

    return params


async def socket_png_receiver(reader, writer):
    while True:
        # first the client sends the length of the data to us
        lengthbuf = await reader.read(4)
        length, = unpack('!I', lengthbuf)
        if length == 0:
            print("length was 0, finish") # a zero length PNG says that there are no more frames
            break
        # then we read the PNG
        data = await reader.read(length)
        data = data.decode() # from bytes to string
        png_bytes = base64.b64decode(data) # from base64 to bytes
        # next line was just a guess, so I have commented it out.
        #await proc.communicate(png_bytes)
        print("Got PNG, length", length)
    return


loop = asyncio.get_event_loop()
command = spawn_ffmpeg("24897", args.outputfilename)
ffmpeg_process = _stream_subprocess(
    command.split(),
    process_stdout,
    process_stderr,
)
#coro = asyncio.start_server(socket_png_receiver, '0.0.0.0', args.port, ffmpeg_process, loop=loop)
coro = asyncio.start_server(socket_png_receiver, '0.0.0.0', args.port, loop=loop)
several_futures = asyncio.gather(ffmpeg_process, coro)
server = loop.run_until_complete(several_futures)
server.close()
loop.close()

Вот изменения, предложенные @user4815162342

import asyncio
import argparse, sys
import sys
import base64
from struct import unpack

parser = argparse.ArgumentParser()
parser.add_argument('--port', help='ffmpeg listen port')
parser.add_argument('--outputfilename', help='ffmpeg output filename')
args = parser.parse_args()
if not args.port:
    print("port is required")
    sys.exit(1)
if not args.outputfilename:
    print("outputfilename is required")
    sys.exit(1)
if not args.outputfilename.endswith('.webm'):
    print("outputfilename must end with '.webm'")
    sys.exit(1)

async def _read_stream(stream, cb):
    while True:
        line = await stream.readline()
        if line:
            cb(line)
        else:
            break


async def _stream_subprocess(cmd, stdout_cb, stderr_cb):
    global process
    process = await asyncio.create_subprocess_exec(
        *cmd,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
        stdin=asyncio.subprocess.PIPE,
    )

    await asyncio.wait([
        _read_stream(process.stdout, stdout_cb),
        _read_stream(process.stderr, stderr_cb)
    ])
    return await process.wait()


def process_stderr(line):
    # ffmpeg finishes processing and writes the output file when its input is closed
    # thus the completion message will come out of stderr only when the socket or stdin or whatever is closed
    line = line.decode()
    print(line)
    if "Output" in line:
        if args.outputfilename in line:
            print('finished!!!!')
            sys.exit(0)


def process_stdout(line):
    print("STDOUT: %s" % line)


def spawn_ffmpeg(listenport, outputfilename, framerate=30, format='webm'):
    outputdirectory = "sftp://username:[email protected]/var/www/static/"
    input_type = "pipe:0"  # stdin

    params = \
        f"ffmpeg  " \
        f"-loglevel 56 " \
        f"-y " \
        f"-framerate {framerate} " \
        f"-i {input_type} " \
        f"{outputdirectory}{outputfilename} "

    print(params)
    return params


async def socket_png_receiver(reader, writer):
    while True:
        # first the client sends the length of the data to us
        lengthbuf = await reader.readexactly(4)
        length, = unpack('!I', lengthbuf)
        if length == 0:
            print("length was 0, finish")  # a zero length PNG says that there are no more frames
            break
        # then we read the PNG
        print("Got PNG, length", length)
        data = await reader.readexactly(length)
        print(data)
        png_bytes = base64.b64decode(data)  # from base64 to bytes
        process.stdin.write(png_bytes)
    return


loop = asyncio.get_event_loop()
command = spawn_ffmpeg("24897", args.outputfilename)
ffmpeg_process = _stream_subprocess(
    command.split(),
    process_stdout,
    process_stderr,
)
coro = asyncio.start_server(socket_png_receiver, '0.0.0.0', args.port, loop=loop)
several_futures = asyncio.gather(ffmpeg_process, coro)
server = loop.run_until_complete(several_futures)
server.close()
loop.close()
  • 0
    Вопрос не определяет, что именно не так с предоставленной реализацией, но, возможно, вам следует попытаться отправить данные, используя: proc.stdin.write(png_bytes) а затем proc.stdin.close() ?
  • 1
    Кроме того, await reader.read(length) действительно должен быть await reader.readexactly(length) . Аргумент для StreamReader.read - это максимальное , а не точное число байтов для чтения.
Показать ещё 19 комментариев
Теги:
python-3.x
python-asyncio
coroutine
subprocess

1 ответ

1
Лучший ответ

В коде есть несколько проблем:

  • await reader.read(length) должен await reader.readexactly(length) потому что аргумент StreamReader.read - это максимальное количество прочитанных байтов, и он может вернуть меньше.

  • proc.communicate(png_bytes) следует изменить на proc.stdin.write(png_bytes). Вызов для communicate() здесь некорректен, потому что вы хотите продолжить разговор с программой, в то время как communicate() ждет, пока все потоки будут закрыты.

  • Экземпляр процесса, возвращаемого asyncio.create_subprocess_exec(...) должен быть доступен для socket_png_receiver, например, путем создания глобальной переменной process с использованием global process. (Лучше использовать класс и присваивать self.process, но это выходит за рамки этого ответа.)

Некоторые потенциальные проблемы:

  • Нет необходимости декодировать data из байтов в строку, base64.b64decode может принимать байты просто отлично.

  • spawn_ffmpeg() не использует свой параметр listenport.

  • 0
    Эй, спасибо, ты действительно прошел дистанцию здесь.
  • 0
    @ DukeDougal Рад помочь! Вы заставили это работать?
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню