Closed alexhutnik closed 2 years ago
Turns out #507 is having a similar issue. One commenter mentions that avcodec_copy_context is deprecated and that two other methods should be used. I've implemented these and the relevant data structures, and yet the problem persists. See this commit: https://github.com/alexhutnik/PyAV/commit/1213f02dc28a14aa803753a8b57f9eae9e07604a
Something else I noticed. When copying the input codec via the template option the codec context created in the output doesn't strictly qualify as an encoder. See the following assertion fails:
input_container = av.open('in.mp4', 'r')
input_stream = input_container.streams.get(video=0)[0]
output_container = av.open('out.mp4', 'w')
output_stream = output_container.add_stream(template=input_stream)
# this fails
assert output_stream.codec_context.codec.is_encoder == 1
for frame in input_container.decode(input_stream):
# encode and mux
for packet in output_stream.encode(frame):
packet.pts = frame.pts
packet.time_base = frame.time_base
# packet.stream = output_stream
output_container.mux(packet)
output_container.close()
Yet, when I create the codec by hand, the assertion passes.
This seems to matter in https://github.com/FFmpeg/FFmpeg/blob/069d2b4a50a6eb2f925f36884e6b9bd9a1e54670/libavcodec/encode.c#L312 where the codec is checked to see if it supports encoding. If that test fails, it returns error code 22, which is the behavior we're seeing.
I think I've got it figured out. PyAV is copying the input codec as is. However, that codec is constructed to be a decoder. In ffmpeg examples (transcoding.c), they get the ID of the input codec, and look up the matching encoder by that ID. I've implemented this functionality in PyAV and will submit the PR.
I think I've got it figured out. PyAV is copying the input codec as is. However, that codec is constructed to be a decoder. In ffmpeg examples (transcoding.c), they get the ID of the input codec, and look up the matching encoder by that ID. I've implemented this functionality in PyAV and will submit the PR.
yep, I met the same problem, when will this be fixed? thanks~
I met the same issue, but only for version 8.0.3 with python 3.9. Version 8.0.2 with python 3.8 (because this version has no wheel for 3.9) will work just fine. Hopefully, this issue will be fixed soon.
I met the same issue, but only for version 8.0.3 with python 3.9. Version 8.0.2 with python 3.8 (because this version has no wheel for 3.9) will work just fine. Hopefully, this issue will be fixed soon.
Did you try building 8.0.3 against python 3.9? You could just install the wheel you built for yourself.
Reproducing:
Not reproducing:
After passing some options to the recorder, the issue is gone:
'vbsf': 'hevc_mp4toannexb',
'x264opts': 'keyint=24:min-keyint=24:no-scenecut',
The work-in-progress PR #910 reworks how codec contexts are created for streams, I believe it should solve your issue. Would you mind giving it a spin?
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
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.
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
I think I've got it figured out. PyAV is copying the input codec as is. However, that codec is constructed to be a decoder. In ffmpeg examples (transcoding.c), they get the ID of the input codec, and look up the matching encoder by that ID. I've implemented this functionality in PyAV and will submit the PR.
hello my friend! I met the same problem and really did not know how to fix it. Now I use the latest version 10.0.0 for PyAV. How could I solve this problem?
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
Something else I noticed. When copying the input codec via the template option the codec context created in the output doesn't strictly qualify as an encoder. See the following assertion fails:
input_container = av.open('in.mp4', 'r') input_stream = input_container.streams.get(video=0)[0] output_container = av.open('out.mp4', 'w') output_stream = output_container.add_stream(template=input_stream) # this fails assert output_stream.codec_context.codec.is_encoder == 1 for frame in input_container.decode(input_stream): # encode and mux for packet in output_stream.encode(frame): packet.pts = frame.pts packet.time_base = frame.time_base # packet.stream = output_stream output_container.mux(packet) output_container.close()
Yet, when I create the codec by hand, the assertion passes.
This seems to matter in https://github.com/FFmpeg/FFmpeg/blob/069d2b4a50a6eb2f925f36884e6b9bd9a1e54670/libavcodec/encode.c#L312 where the codec is checked to see if it supports encoding. If that test fails, it returns error code 22, which is the behavior we're seeing. How to create the codec by hand?
I case someone is looking for a workaround. This SO post shows one way to fix it. Just copy the codec parameters manually. It worked for me.
codec_name = in_stream.codec_context.name # Get the codec name from the input video stream.
fps = in_stream.codec_context.rate # Get the framerate from the input video stream.
out_stream = test_output.add_stream(codec_name, str(fps))
out_stream.width = in_stream.codec_context.width # Set frame width to be the same as the width of the input stream
out_stream.height = in_stream.codec_context.height # Set frame height to be the same as the height of the input stream
out_stream.pix_fmt = in_stream.codec_context.pix_fmt # Copy pixel format from input stream to output stream
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
The workaround doesn't help for me. I get "Cannot rebase to zero time" for every packet sent to the muxer, even when the frame's dts/pts aren't zero.
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
Can confirm, I'm also having the same problem
This is my python application:
import av
import av.datasets
import base64
import copy
SOURCE_FILE = os.getenv('SOURCE_FILE', "launch.mp4")
DESTINATION = os.getenv('DESTINATION', "launch_muxed.ts")
def mux_video(source: str, destination: str):
# Change 'input.mp4' with actual path to mp4 file
input_container = av.open(source)
# Get the video stream from the input container
video_stream = next(s for s in input_container.streams if s.type == 'video')
# metadata you want to add
metadata = {'somekey':'some value'}
output_container = av.open(destination, 'w', format="mpegts")
# Create new video stream in the output container with the same codec
out_stream = output_container.add_stream(template=video_stream)
#out_stream = test_output.add_stream(template=in_stream) # Using template=in_stream is not working (probably meant to be used for re-muxing and not for re-encoding).
# TODO find out why this returns None
# fps = video_stream.codec_context.rate # Get the framerate from the input video stream.
# From ffprobe
fps = "572000/23857"
meta_stream = output_container.add_stream("srt", str(fps))
meta_stream.time_base = video_stream.time_base
# Loop over packets in input container
for packet in input_container.demux(video_stream):
# Decode the packet into frames
for frame in packet.decode():
# Mux the frame to the output container
# img_frame = frame.to_image()
# out_frame = av.VideoFrame.from_image(img_frame) # Note: to_image and from_image is not required in this specific example.
# out_packet = out_stream.encode(out_frame) # Encode video frame
# output_container.mux(out_packet)
for packet in out_stream.encode(frame):
packet.pts = frame.pts
packet.time_base = frame.time_base
# packet.stream = output_stream
output_container.mux(packet)
# Create a new subtitle packet for each frame
# Encode binary data as base64
binary_data = b'some binary data'
encoded_data = base64.b64encode(binary_data).decode()
meta_packet = av.Packet(encoded_data)
meta_packet.pts = frame.pts
meta_packet.time_base = meta_stream.time_base
meta_packet.stream = meta_stream
output_container.mux(meta_packet)
# Write metadata to the output container
output_container.mux(metadata)
# Close the containers
input_container.close()
output_container.close()
def main():
mux_video(SOURCE_FILE, DESTINATION)
if __name__ == '__main__':
main()
And it gives me this response:
❯ python3 send_video_data.py
Traceback (most recent call last):
File "send_video_data.py", line 146, in <module>
main()
File "send_video_data.py", line 143, in main
send_video(SOURCE_FILE, DESTINATION)
File "send_video_data.py", line 103, in send_video
for packet in out_stream.encode(frame):
File "av/stream.pyx", line 149, in av.stream.Stream.encode
File "av/codec/context.pyx", line 479, in av.codec.context.CodecContext.encode
File "av/frame.pyx", line 50, in av.frame.Frame._rebase_time
ValueError: Cannot rebase to zero time.
(astro_env_3.8.10)
您的来信已收到,谢谢!陈雷同济大学测绘与地理信息学院Thanks for your attention.Chen Lei College of survey and geo-information of Tongji university
Overview
When I create a new Container, and add a new stream to it using a template, I can only remux packets. I cannot decode and then re-encode packets.
Expected behavior
I would expect to be able to roundtrip a packet like
decode packet
->frame
->encode frame
->packet
.Actual behavior
When trying to do this roundtrip, I am presented with an error.
Traceback:
Investigation
I've dug about as far in to the pyav source as I can, but I'm not very familiar with Cython
Reproduction
Versions
Research
I have done the following:
Additional context
I only installed ffmpeg so I could inspect some files. I did not build PyAV against it.