python - Python opencv子进程写入返回损坏的管道
问题描述
我想读取一个 rtsp 视频源,添加覆盖文本并将其推送到 RTMP 端点。我正在使用 Videocapture 读取视频源和 python 子进程将帧写回 RTMP 端点。我将这个FFmpeg 流视频从帧 OpenCV python 引用到 rtmp
import sys
import subprocess
import cv2
import ffmpeg
rtmp_url = "rtmp://127.0.0.1:1935/live/test"
path = 0
cap = cv2.VideoCapture("rtsp://10.0.1.7/media.sdp")
# gather video info to ffmpeg
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
command = ['ffmpeg', '-i', '-', "-c", "copy", '-f', 'flv', rtmp_url]
p = subprocess.Popen(command, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
font = cv2.FONT_HERSHEY_SIMPLEX
while cap.isOpened():
ret, frame = cap.read()
cv2.putText(frame, 'TEXT ON VIDEO', (50, 50), font, 1, (0, 255, 255), 2, cv2.LINE_4)
cv2.imshow('video', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
if not ret:
print("frame read failed")
break
try:
p.stdin.write(frame.tobytes())
except Exception as e:
print (e)
cap.release()
p.stdin.close()
p.stderr.close()
p.wait()
python 脚本返回“[Errno 32] Broken pipe”。在终端中运行 ffmpeg 命令可以正常工作。
ffmpeg -i rtsp://10.0.1.7/media.sdp -c copy -f flv rtmp://127.0.0.1:1935/live/test
上面的命令工作正常,我可以将输入流推送到 RTMP 端点。但是我无法将处理后的帧写入运行 ffmpeg 的子进程。
请让我知道,如果我错过了什么。
解决方案
"-c", "copy"
将原始帧写入标准输入管道时不能使用。
frame
返回的是ret, frame = cap.read()
BGRuint8
颜色格式的 NumPy 数组(cap.read()
解码视频并转换颜色格式)。
用FFmpeg
术语来说,frame
格式是“ rawvideo
”。
command
应该告诉 FFmpeg 期望原始视频作为输入,具有特定的大小和像素格式:
command = ['ffmpeg', '-f', 'rawvideo', '-s', f'{width}x{height}', '-pixel_format', 'bgr24', ...
因为输入是原始视频,所以我们必须重新编码。
我们可以指定编码的像素格式和视频编解码器:
'-pix_fmt', 'yuv420p', '-c:v', 'libx264' ...
.
评论:
- 对视频进行解码和重新编码会损失一些质量(但别无选择)。
- 建议的解决方案会丢失音频(有保留音频的解决方案,但 OpenCV 缺乏音频支持)。
- 发布的解决方案重用了以下帖子中的一些代码。
很少有 FFmpeg 参数没有解释(如'-bufsize', '64M'
)。
执行侦听器应用程序:
如果没有接收视频的“侦听器”,RTMP 流将无法工作。
监听器应该在启动 RTMP 流之前启动(由于 TCP 使用)。我们可以使用 FFplay 子进程作为“监听器”应用程序:
ffplay_process = sp.Popen(['ffplay', '-listen', '1', '-i', rtmp_url])
流式合成视频帧:
从一个更简单的代码示例开始,它流式传输合成帧(不捕获 RTSP 视频)。
以下“自包含”代码示例在灰色背景上写入黄色文本,并将帧传递给 FFmpeg 以进行 RTMP 流式传输:
import cv2
import numpy as np
import subprocess as sp
width = 320
height = 240
fps = 5
rtmp_url = "rtmp://127.0.0.1:1935/live/test"
# Start the TCP server first, before the sending client.
ffplay_process = sp.Popen(['ffplay', '-listen', '1', '-i', rtmp_url]) # Use FFplay sub-process for receiving the RTMP video.
command = ['ffmpeg',
'-re',
'-f', 'rawvideo', # Apply raw video as input
'-s', f'{width}x{height}',
'-pixel_format', 'bgr24',
'-r', f'{fps}',
'-i', '-',
'-pix_fmt', 'yuv420p',
'-c:v', 'libx264',
'-bufsize', '64M',
'-maxrate', '4M',
'-f', 'flv',
rtmp_url]
process = sp.Popen(command, stdin=sp.PIPE) # Execute FFmpeg sub-process for RTSP streaming
frame_counter = 0;
while True:
# Build sythetic frame in BGR color format (3D NumPy array).
frame = np.full((height, width, 3), 60, np.uint8)
cv2.putText(frame, 'TEXT ON VIDEO ' + str(frame_counter), (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2, cv2.LINE_4) # Put a frame counter for showing progress.
process.stdin.write(frame.tobytes()) # Write raw frame to stdin pipe.
cv2.imshow('frame', frame) # Show frame for testing
key = cv2.waitKey(int(round(1000/fps))) # We need to call cv2.waitKey after cv2.imshow
if key == ord('q'): # Press 'q' for exit
break
frame_counter += 1
process.stdin.close() # Close stdin pipe
process.wait() # Wait for FFmpeg sub-process to finish
ffplay_process.kill() # Forcefully close FFplay sub-process
cv2.destroyAllWindows() # Close OpenCV window
从 RTSP 流中捕获视频帧。
以下代码示例从公共 RTSP 流中捕获视频帧,写入文本,并将帧传递给 FFmpeg 以进行 RTMP 流:
import cv2
import numpy as np
import subprocess as sp
# Use public RTSP Streaming for testing.
rtsp_stream = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"
rtmp_url = "rtmp://127.0.0.1:1935/live/test"
cap = cv2.VideoCapture(rtsp_stream)
# gather video info to ffmpeg
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Start the TCP server first, before the sending client.
ffplay_process = sp.Popen(['ffplay', '-listen', '1', '-i', rtmp_url]) # Use FFplay sub-process for receiving the RTMP video.
command = ['ffmpeg',
'-re',
'-f', 'rawvideo', # Apply raw video as input
'-s', f'{width}x{height}',
'-pixel_format', 'bgr24',
'-r', f'{fps}',
'-i', '-',
'-pix_fmt', 'yuv420p',
'-c:v', 'libx264',
'-bufsize', '64M',
'-maxrate', '4M',
'-f', 'flv',
rtmp_url]
process = sp.Popen(command, stdin=sp.PIPE) # Execute FFmpeg sub-process for RTSP streaming
frame_counter = 0;
while cap.isOpened():
# Read frame from RTSP stream.
ret, frame = cap.read()
if not ret:
print("frame read failed")
break
cv2.putText(frame, 'TEXT ON VIDEO', (0, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2, cv2.LINE_4)
cv2.putText(frame, str(frame_counter), (100, 100), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2, cv2.LINE_4)
process.stdin.write(frame.tobytes()) # Write raw frame to stdin pipe.
cv2.imshow('frame', frame) # Show frame for testing
key = cv2.waitKey(1) # We need to call cv2.waitKey after cv2.imshow
if key == ord('q'): # Press 'q' for exit
break
frame_counter += 1
cap.release()
process.stdin.close() # Close stdin pipe
process.wait() # Wait for FFmpeg sub-process to finish
ffplay_process.kill() # Forcefully close FFplay sub-process
cv2.destroyAllWindows() # Close OpenCV window
推荐阅读
- php - Laravel 公共内容无法在共享主机上访问
- php - 'referenceindex:update' 后无法登录后端
- android - Android 10 后如何检测 WiFi 连接/断开连接?
- asynchronous - 如何从各种期货的查询中获得未来的组合?
- python - python脚本的JSON power shell命令行参数
- javascript - 使用 Puppeteer 在第一个 Google 搜索结果上单击一个元素
- r - 如何使用因子函数重命名单元格值(在 Rstudio 中)?
- java - 如何在 app.vue 中导入/链接到本地 css 样式表文件
- python - Getter 使用属性装饰器返回对象没有属性
- javascript - HTML使用 vanilla javascript 添加类属性