Closed samiamlabs closed 1 year ago
I tried to create a minimal example to reproduce this without using aiortc and ended up with this:
import av
from av import Codec
import os
import fractions
DEFAULT_BITRATE = 1000000 # 1 Mbps
MAX_FRAME_RATE = 30
test_video = os.path.join(os.path.dirname(
os.path.realpath(__file__)), 'video.mp4')
container = av.open(test_video)
video_stream = next(s for s in container.streams if s.type == 'video')
codec = None
frame_count = 0
for packet in container.demux(video_stream):
for frame in packet.decode():
if codec is None:
codec = av.CodecContext.create("h264_v4l2m2m", "w")
codec.width = frame.width
codec.height = frame.height
codec.bit_rate = DEFAULT_BITRATE
codec.pix_fmt = "yuv420p"
codec.framerate = fractions.Fraction(MAX_FRAME_RATE, 1)
codec.time_base = fractions.Fraction(1, MAX_FRAME_RATE)
codec.options = {
"profile:v": "baseline",
"level": "31",
"tune": "zerolatency", # does nothing using h264_omx
}
print("frame count:", frame_count)
frame_count += 1
for package in codec.encode(frame):
pass
print('finished')
This ran just fine on the Raspberry Pi 4. No segfault. Maybe I should figure out how to write the encoded video to a file, just to make sure it actually worked but I suspect it did.
I already verified that this works:
ffmpeg -i video.mp4 -c:v h264_v4l2m2m -b:v 8M -c:a copy test.mp4
Am I correct in assuming that this means the problem is probably in aiortc and not in PyAV?
In that case I should probably close this issue...
Honestly these issues just depress me, maintaining opensource projects is no fun.
Haha, yea I hear you @jlaine...
I spent way too much time over the weekend trying to figure out what's causing this specific issue is really starting to annoy me.
Thought for quite some time that that the reason was that h264_v4l2m2m only could handle inputs that have yuv420p as their pixel format. (my logitech c922 outputs yuyv422). It just doesn't seem like there are that many other differences between the frames that work and those that segfault.
Webcam Frame:
<av.VideoFrame #0, pts=0 yuyv422 640x480 at 0xffff6f0c12e0>
dts:91271867741
format:<av.VideoFormat yuyv422, 640x480>
index:0
interlaced_frame:0
is_corrupt:False
key_frame:1
pict_type:<av.video.frame.PictureType:I(0x1)>
planes:(<av.VideoPlane 61440...f6d6f2860>,)
pts:0
side_data:<av.sidedata.sidedata.SideDataContainer object at 0xffff6d6f2a40>
time:0.0
time_base:Fraction(1, 1000000)
height:480
width:640
File Frame:
<av.VideoFrame #0, pts=0 yuv420p 1920x1080 at 0xffffa0e000a0>
dts:0
format:<av.VideoFormat yuv420p, 1920x1080>
index:0
interlaced_frame:0
is_corrupt:False
key_frame:1
pict_type:<av.video.frame.PictureType:I(0x1)>
planes:(<av.VideoPlane 20736...f780279a0>, <av.VideoPlane 51840...f78027b80>, <av.VideoPlane 51840...f78027a40>)
pts:0
side_data:<av.sidedata.sidedata.SideDataContainer object at 0xffff78027db0>
time:0.0
time_base:Fraction(1, 12800)
height:1080
width:1920
I wrote a bunch of test with different input frames, and I can't figure out a pattern that explains why some of them segfault.
import os
import av
from av import VideoFrame
import numpy as np
import fractions
import cv2 as cv
import faulthandler
DEFAULT_BITRATE = 1000000 # 1 Mbps
MAX_FRAME_RATE = 30
test_video = os.path.join(os.path.dirname(
os.path.realpath(__file__)), 'video.mp4')
def create_encoder_context(codec_name, frame):
print('creating encoder of type: ' + codec_name)
codec = av.CodecContext.create(codec_name, "w")
codec.width = frame.width
codec.height = frame.height
codec.bit_rate = DEFAULT_BITRATE
codec.pix_fmt = "yuv420p"
codec.framerate = fractions.Fraction(MAX_FRAME_RATE, 1)
codec.time_base = fractions.Fraction(1, MAX_FRAME_RATE)
codec.options = {
}
codec.open()
return codec
def test_codec_libx264_opencv():
cap = cv.VideoCapture(0)
ret, opencv_frame = cap.read()
frame = VideoFrame.from_ndarray(opencv_frame, format="bgr24")
codec = create_encoder_context('libx264', frame)
codec.encode(frame)
cap.release()
print('no segfault in test_codec_libx264_opencv')
def test_codec_h264_v4l2m2m_opencv():
cap = cv.VideoCapture(0)
ret, opencv_frame = cap.read()
frame = VideoFrame.from_ndarray(opencv_frame, format="bgr24")
codec = create_encoder_context('h264_v4l2m2m', frame)
codec.encode(frame)
cap.release()
print('no segfault in test_codec_h264_v4l2m2m_opencv')
def test_codec_h264_v4l2m2m_opencv_yuv420p():
cap = cv.VideoCapture(0)
ret, opencv_frame = cap.read()
yuv_img = cv.cvtColor(opencv_frame, cv.COLOR_BGR2YUV_I420)
frame = VideoFrame.from_ndarray(yuv_img, format="yuv420p")
# frame.pict_type = 'I'
codec = create_encoder_context('h264_v4l2m2m', frame)
codec.encode(frame)
cap.release()
print('no segfault in test_codec_h264_v4l2m2m_opencv_yuv420p')
def test_codec_libx264_opencv_yuv420p():
cap = cv.VideoCapture(0)
ret, opencv_frame = cap.read()
yuv_img = cv.cvtColor(opencv_frame, cv.COLOR_BGR2YUV_I420)
frame = VideoFrame.from_ndarray(yuv_img, format="yuv420p")
# frame.pict_type = 'I'
codec = create_encoder_context('libx264', frame)
codec.encode(frame)
cap.release()
print('no segfault in test_codec_libx264_opencv_yuv420p')
def test_codec_h264_v4l2m2m_file():
container = av.open(test_video)
video_stream = next(s for s in container.streams if s.type == 'video')
for packet in container.demux(video_stream):
for frame in packet.decode():
codec = create_encoder_context('h264_v4l2m2m', frame)
codec.encode(frame)
break
break
container.close()
print('no segfault in test_codec_h264_v4l2m2m_file')
def test_codec_h264_v4l2m2m_webcam():
container = av.open('/dev/video0')
video_stream = next(s for s in container.streams if s.type == 'video')
for packet in container.demux(video_stream):
for frame in packet.decode():
codec = create_encoder_context('h264_v4l2m2m', frame)
codec.encode(frame)
break
break
container.close()
print('no segfault in test_codec_h264_v4l2m2m_webcam')
def test_codec_h264_libx264_webcam():
container = av.open('/dev/video0')
video_stream = next(s for s in container.streams if s.type == 'video')
for packet in container.demux(video_stream):
for frame in packet.decode():
codec = create_encoder_context('libx264', frame)
codec.encode(frame)
break
break
container.close()
print('no segfault in test_codec_h264_libx264_webcam')
# faulthandler.enable()
# works
test_codec_libx264_opencv()
test_codec_libx264_opencv_yuv420p()
test_codec_h264_v4l2m2m_file()
test_codec_h264_libx264_webcam()
# segfaults
# test_codec_h264_v4l2m2m_opencv()
# test_codec_h264_v4l2m2m_webcam()
# test_codec_h264_v4l2m2m_opencv_yuv420p()
I also thought for a good while that the problem had to be outside PyAv, but it turned out that ffmpeg -i /dev/video0 -codec h264_v4l2m2m -pix_fmt yuv420p test.h264
actually works, so I'm not so sure about that anymore.
Pretty close to giving up on solving this, but it's possible that I'm close to a solution also... I don't know, this kind of sucks....
OK you've obviously spent a lot of time on this, thanks so much for looking for a way to reproduce it!
I'm a bit bogged down at the moment, I hope to have time over the summer to pull out my Rpi4 and experiment with it.
Ok, sounds great!
I may end up switching to a jetson tx2 and trying my luck at getting the nvenc encoder to work with aiortc instead for the time being :)
Just to be clear, I really don't want to imply that you have some sort of obligation to fix this and you seem to be doing a really good job maintaining these projects as far as I can tell :)
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Still hoping to get round to this eventually.
I stumbled upon this issue myself and have some different results, this was all run on a Raspberry Pi 3B+ with Raspberry Pi OS 64 bit, using FFmpeg from the repositories.
There is OpenCV image processing code running on a process which gets its frames from the webcam. I then write these frames to a v4l2loopback device in the YUV420p format.
A separate process then streams this loopback device as if it were a webcam stream by WebRTC with aiortc (using the v4l2m2m encoder). I firstly also got the segfaults, but managed to get it working by outputting YUV420P frames from OpenCV instead of BGR3 and setting the pixelformat option in the MediaPlayer from aiortc accordingly to YUV420P.
I also got an error that with v4l2m2m the "profile" option in aiortc/codecs/h264.py "create_encoder_context" didn't exist, this had to be changed to "profile:v".
So according to me the error with @samiamlabs webcam example is the unsupported pixel format and with the opencv example, that the encoder is generated in the "create_encoder_context" with YUV420P, but OpenCV outputs BGR3. I honestly have no idea why your opencv_yuv420p example segfaults.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Any Luck?
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Overview
Hi!
I'm trying to stream video with decent resolution and framerate over WebRTC (aiortc) with my Raspberry Pi 4. This pull request adding support for h264_omx seemed really promising at first: https://github.com/aiortc/aiortc/pull/488 Unfortunately, it seems that h264_omx is considered deprecated and won't be supported on arm64. (At the very least, I can't get it to work...)
I tried to just swap the codec name in the code related to the pull request to aiortc I mentioned above from "h264_omx" to "h264_v4l2m2m". The encoder context seems to be created without any exceptions (some warnings get printed though) but segfaults when trying to run
self.codec.encode(frame)
.Should it be possible to use the "h264_v4l2m2m" codec or is it simply not supported at this point?
Expected behavior
Well... I was hoping that h264_v4l2m2m would work as a drop-in replacement for h264_omx and allow me to stream video to remote control a robot using a Raspberry Pi 4.
Actual behavior
Hard to debug segfault which seems to happen outside the python code
Traceback:
Investigation
Python debugging in VSCode and GDB
Reproduction
Run the webcam.py example in aiortc (modified to select h264 using the approach from this issue: https://github.com/aiortc/aiortc/issues/502) on Ubuntu 20.04.2 LTS for arm64 on a Raspberry Pi 4. Also, change the codec name from "h264_omx" to "h264_v4l2m2m" in https://github.com/aiortc/aiortc/blob/main/src/aiortc/codecs/h264.py Got an error related to the "profile" option after that so changed it to "profile:v" based on https://video.stackexchange.com/questions/2634/how-do-i-specify-baseline-h264-profile-using-ffmbc
I used a source build of FFmpeg 4.4 to get rid of the green overlay issue in 4.2.4 that is distributed by apt on focal (explained more here: https://www.willusher.io/general/2020/11/15/hw-accel-encoding-rpi4) but I don't know if that is relevant for this issue.
I can provide a Dockerfile for building FFmpeg etc if someone wants it.
Versions
Research
I have done the following: