如何使用subprocess(Python)将Picamera视频导入FFMPEG?

6

我看到很多有关将 raspivid 流直接传输到 FFMPEG 进行编码、复用和重新流传的信息,但这些用例大多是来自于 bash shell,类似于:

raspivid -n -w 480 -h 320 -b 300000 -fps 15 -t 0 -o - | ffmpeg -i - -f mpegts udp://192.168.1.2:8090ffmpeg

我希望利用 Picamera 库的功能,以便在使用 OpenCV 和类似工具进行并发处理的同时,仍然能够使用 FFMPEG 进行流传输。但我无法弄清如何正确地打开 FFMPEG 作为子进程,并将视频数据传输到它。我看到了很多尝试、未回答的帖子和声称已经完成此操作的人,但没有任何一个对我的树莓派有效。

我要使用 Picamera 创建视频缓冲区,然后将原始视频传输到 FFMPEG 吗?我可以使用 camera.capture_continuous() 并传递给 FFMPEG 我正在使用的 bgr24 图像来进行 OpenCV 计算吗?

我尝试了各种变化,但我不确定我是否只是误解了如何使用 subprocess 模块、FFMPEG,还是我只是缺少一些设置。我知道原始流没有任何元数据,但我不完全确定我需要为 FFMPEG 提供什么设置,以便它能够理解我正在传递给它的内容。

我有一个 Wowza 服务器,最终将流传输到那里,但我目前正在通过流传输到我的笔记本电脑上的 VLC 服务器进行测试。我目前尝试过这个:

import subprocess as sp
import picamera
import picamera.array
import numpy as np

npimage = np.empty(
        (480, 640, 3),
        dtype=np.uint8)
with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.framerate = 24

    camera.start_recording('/dev/null', format='h264')
    command = [
        'ffmpeg',
        '-y',
        '-f', 'rawvideo',
        '-video_size', '640x480',
        '-pix_fmt', 'bgr24',
        '-framerate', '24',
        '-an',
        '-i', '-',
        '-f', 'mpegts', 'udp://192.168.1.54:1234']
    pipe = sp.Popen(command, stdin=sp.PIPE,
                    stdout=sp.PIPE, stderr=sp.PIPE, bufsize=10**8)
    if pipe.returncode != 0:
        output, error = pipe.communicate()
        print('Pipe failed: %d %s %s' % (pipe.returncode, output, error))
        raise sp.CalledProcessError(pipe.returncode, command)

    while True:
        camera.wait_recording(0)
        for i, image in enumerate(
                        camera.capture_continuous(
                            npimage,
                            format='bgr24',
                            use_video_port=True)):
            pipe.stdout.write(npimage.tostring())
    camera.stop_recording()

我也尝试将流写入类似文件的对象中,该对象仅创建FFMPEG子进程并将其写入stdin(在初始化picam时,camera.start_recording()可以提供此类对象):

class PipeClass():
    """Start pipes and load ffmpeg."""

    def __init__(self):
        """Create FFMPEG subprocess."""
        self.size = 0
        command = [
            'ffmpeg',
            '-f', 'rawvideo',
            '-s', '640x480',
            '-r', '24',
            '-i', '-',
            '-an',
            '-f', 'mpegts', 'udp://192.168.1.54:1234']

        self.pipe = sp.Popen(command, stdin=sp.PIPE,
                         stdout=sp.PIPE, stderr=sp.PIPE)

        if self.pipe.returncode != 0:
            raise sp.CalledProcessError(self.pipe.returncode, command)

    def write(self, s):
        """Write to the pipe."""
        self.pipe.stdin.write(s)

    def flush(self):
        """Flush pipe."""
        print("Flushed")

usage:
(...)
with picamera.PiCamera() as camera:
    p = PipeClass()
    camera.start_recording(p, format='h264')
(...)

任何关于此事的帮助都非常棒!
3个回答

6

我能够通过以下类似的方式将PiCamera的输出流传输到ffmpeg:

import picamera
import subprocess

# start the ffmpeg process with a pipe for stdin
# I'm just copying to a file, but you could stream to somewhere else
ffmpeg = subprocess.Popen([
    'ffmpeg', '-i', '-',
    '-vcodec', 'copy',
    '-an', '/home/pi/test.mpg',
    ], stdin=subprocess.PIPE)

# initialize the camera
camera = picamera.PiCamera(resolution=(800, 480), framerate=25)

# start recording to ffmpeg's stdin
camera.start_recording(ffmpeg.stdin, format='h264', bitrate=2000000)

还是说这不是你想要的内容?


这可能要容易得多 :) 尽管将AVC视频放入MPEG-1 PS容器中很奇怪 - 这甚至有效吗?无论如何,OP正在以TS形式流式传输到UDP,因此该部分已经得到控制。 - hobbs
似乎对我有效!输出在omxplayer中可播放。但我很愿意听取改进建议! - Kevin Villela
这正是我所需要的!我显然误用了子进程的管道,但它帮助我意识到我尝试在FFMPEG中使用的一些设置也给我带来了问题。谢谢! - VeniVidiReliqui
1
@KevinVillela 通常使用 .mp4 文件(ISO BMFF)。 - hobbs

1

我一开始看到的两个问题:

  1. 在您的第一个示例中,您将数据写入子进程的 stdout 而不是其 stdin。这肯定行不通,可能会导致挂起。

  2. 在这两个示例中,您使用 stdin=sp.PIPE, stderr=sp.PIPE 启动进程,但从未从这些管道中读取。这意味着一旦 ffmpeg 写入足够的输出以填充管道缓冲区,它将阻塞,您将遇到死锁。使用默认值 stdout=None, stderr=None 让 ffmpeg 的输出进入您的进程的 stdout 和 stderr,或将它们连接到打开到 /dev/null 的文件句柄以丢弃输出。或者使用 communicate 方法每次写入一些输入时获取输出,并对其进行有用的处理(例如监视流的状态)。


0

你好,你可以使用opencv和ffmpeg将其重新流媒体到wowza或其他平台。

这里有一个使用opencv和ffmpeg的示例。

    int main(int argc, char* argv[])
{
    if (argc < 4){
        cout << "eksik parametre" << endl;
        return -1;
    }
    int fps = 1;                        //fps degeri varsayilan 1
    char *input_adress = argv[1];       //goruntunun alinacagi dosya yada adres bilgisi
    char *output_adress = argv[3];      //ciktinin gonderilecegi dosya yada adres bilgisi
    sscanf(argv[2], "%d", &fps);        //fps degeri okundu
    VideoCapture video(input_adress);   //kamera acildi
    if (!video.isOpened()){
        cout << "Yayin acilamadi!!!" << endl;
        getchar();
        return -1;
    }
    Mat frame;//frame ornegi
    FILE *pipe;//pipe icin acilan process in input streami
    char *cmd = (char*)calloc(100 + sizeof(output_adress), sizeof(char));//komut icin alan alindi
    sprintf(cmd, "ffmpeg -y -f image2pipe -vcodec mjpeg -r %d -i -  -r %d -vcodec libx264 -f flv %s", fps, fps, output_adress);//ffmpeg komutu
    //ffmpeg komutu aciliyor
    if (!(pipe = _popen(cmd, "wb"))){
        cout << "Acilamadi!!!" << endl;
        return 1;
    }
    float wait_time = 1000.0f / (float)fps;//kac milisaniye bekletilecek
    while (true)
    {
        try {
            //videodan siradaki frame okunuyor
            video >> frame;
            if (frame.empty())              //eger bos frame ise video bitmis demektir
                break;
            adjust_brightness(frame);       //parlaklik ayarlamasini yapıyoruz
            write_jpeg(frame, pipe);        //jpeg formatina cevirip pipe a yazıyoruz
            if (waitKey(wait_time) >= 0) break;
        }
        catch (Exception ex){
        }
    }
    video.release();
    return 0;
}

在 write_jpeg 方法中调用 fwrite 和 fflush。
这将使用以下命令进行调用:
testOutput.exe ORIGINAL_SOURCE RTMP_OUTPUT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接