Skip to content

Commit bbb01aa

Browse files
committed
Allow configuring hardware encoder sw_format
1 parent d0d7746 commit bbb01aa

3 files changed

Lines changed: 65 additions & 2 deletions

File tree

av/codec/context.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,16 @@ def _setup_encode_hwframes(self) -> cython.void:
396396
return # Already set up.
397397

398398
hw_format: lib.AVPixelFormat = self.hwaccel_ctx.config.ptr.pix_fmt
399-
sw_format: lib.AVPixelFormat = cython.cast(lib.AVPixelFormat, self.ptr.pix_fmt)
399+
sw_format: lib.AVPixelFormat = cython.cast(
400+
lib.AVPixelFormat, self.ptr.sw_pix_fmt
401+
)
402+
403+
# The codec context's sw_pix_fmt holds the software format the user
404+
# wants the hardware frames context to use. Fall back to pix_fmt to
405+
# preserve the existing stream.pix_fmt configuration path.
406+
if sw_format == lib.AV_PIX_FMT_NONE:
407+
sw_format = cython.cast(lib.AVPixelFormat, self.ptr.pix_fmt)
400408

401-
# The codec context's pix_fmt holds the *software* format the user feeds in.
402409
# If they left it as the hardware format (or unset), pick a sane default.
403410
if sw_format == hw_format or sw_format == lib.AV_PIX_FMT_NONE:
404411
sw_format = lib.av_get_pix_fmt(b"nv12")

av/video/codeccontext.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,22 @@ def sw_format(self):
261261
:type: VideoFormat | None
262262
"""
263263
if not self.ptr.hw_frames_ctx:
264+
if self.ptr.sw_pix_fmt != lib.AV_PIX_FMT_NONE:
265+
return get_video_format(
266+
cython.cast(lib.AVPixelFormat, self.ptr.sw_pix_fmt),
267+
self.ptr.width,
268+
self.ptr.height,
269+
)
264270
return None
265271
frames_ctx: cython.pointer[lib.AVHWFramesContext] = cython.cast(
266272
cython.pointer[lib.AVHWFramesContext], self.ptr.hw_frames_ctx.data
267273
)
268274
return get_video_format(frames_ctx.sw_format, self.ptr.width, self.ptr.height)
269275

276+
@sw_format.setter
277+
def sw_format(self, value):
278+
self.ptr.sw_pix_fmt = get_pix_fmt(value)
279+
270280
@property
271281
def framerate(self):
272282
"""

tests/test_encode.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,13 @@ def test_profiles(self) -> None:
517517
}
518518

519519

520+
def get_hwaccel_format(encoder: str, device_type: str) -> str:
521+
for config in av.Codec(encoder, "w").hardware_configs:
522+
if config.device_type.name == device_type and config.format is not None:
523+
return config.format.name
524+
pytest.skip(f"No hardware format for {device_type} on {encoder}")
525+
526+
520527
def test_hardware_encode() -> None:
521528
hwdevices_available = av.codec.hwaccel.hwdevices_available()
522529
if "HWACCEL_DEVICE_TYPE" not in os.environ:
@@ -569,3 +576,42 @@ def test_hardware_encode() -> None:
569576
with av.open(file, "r") as in_container:
570577
decoded = sum(1 for _ in in_container.decode(video=0))
571578
assert decoded == n_frames
579+
580+
581+
def test_hardware_encode_honors_sw_format() -> None:
582+
hwdevices_available = av.codec.hwaccel.hwdevices_available()
583+
if "HWACCEL_DEVICE_TYPE" not in os.environ:
584+
pytest.skip(
585+
"Set the HWACCEL_DEVICE_TYPE to run this test. "
586+
f"Options are {' '.join(hwdevices_available)}"
587+
)
588+
589+
device_type = os.environ["HWACCEL_DEVICE_TYPE"]
590+
assert device_type in hwdevices_available, f"{device_type} not available"
591+
592+
encoder = _HWACCEL_ENCODERS.get(device_type)
593+
if encoder is None:
594+
pytest.skip(f"No hardware encoder mapped for {device_type}")
595+
hw_format = get_hwaccel_format(encoder, device_type)
596+
597+
hwaccel = av.codec.hwaccel.HWAccel(
598+
device_type=device_type, allow_software_fallback=False
599+
)
600+
container = av.open(io.BytesIO(), mode="w", format="mp4")
601+
stream = container.add_stream(encoder, rate=30, hwaccel=hwaccel)
602+
stream.width = 320
603+
stream.height = 240
604+
stream.pix_fmt = hw_format
605+
stream.codec_context.sw_format = "yuv420p"
606+
607+
assert stream.codec_context.sw_format.name == "yuv420p"
608+
609+
frame = VideoFrame(320, 240, "rgb24")
610+
for packet in stream.encode(frame):
611+
container.mux(packet)
612+
613+
assert stream.codec_context.pix_fmt == hw_format
614+
assert stream.codec_context.sw_format.name == "yuv420p"
615+
for packet in stream.encode():
616+
container.mux(packet)
617+
container.close()

0 commit comments

Comments
 (0)