Skip to content

How do I trim padding off of a frame? #802

@oczkoisse

Description

@oczkoisse

Overview

I'm trying to render a yuvj420p frame as an OpenGL texture. In my testing, frames that do not have padding work great. However, frames with padding are a mess. To prepare data for texture, I'm using the following function which is mostly the same as useful_array function in av/video/frame.pyx.

def _remove_padding(self, plane):
    """Remove padding from a video plane.

    Args:
        plane (av.video.plane.VideoPlane): the plane to remove padding from

    Returns:
        numpy.array: an array with proper memory aligned width
    """
    buf_width = plane.line_size
    bytes_per_pixel = 1
    frame_width = plane.width * bytes_per_pixel
    arr = np.frombuffer(plane, np.uint8)
    if buf_width != frame_width:
        arr = arr.reshape(-1, buf_width)[:, :frame_width]
    return arr.reshape(-1, frame_width)

With frames that have padding, the above function does not work properly.

Expected behavior

The given function should be able to remove any padding from the frame and be rendered as a texture properly.

Actual behavior

The output frame is completely messed up:

image

Traceback:

N\A

Investigation

I felt that the above behavior may be a result of plane.width being off by some pixels. In my case, the width is 1198 px for which 16 pixel alignment would be 1200 px. So, I adjusted the above function to align the width value:

def _remove_padding(self, plane):
    """Remove padding from a video plane.

    Args:
        plane (av.video.plane.VideoPlane): the plane to remove padding from

    Returns:
        numpy.array: an array with proper memory aligned width
    """
    buf_width = plane.line_size
    bytes_per_pixel = 1
    frame_width = plane.width * bytes_per_pixel
    arr = np.frombuffer(plane, np.uint8)
    if buf_width != frame_width:
        align_to = 16
        # Frame width that is aligned up with a 16 pixel boundary
        # See avcodec_align_dimensions2 function and FFALIGN macro
        frame_width = (frame_width + align_to - 1) & ~(align_to - 1)
        # Slice (create a view) at the aligned boundary
        arr = arr.reshape(-1, buf_width)[:, :frame_width]
    return arr.reshape(-1, frame_width)

Now at least Y plane seems to be aligned properly, but chroma planes still are not. In my case, the chroma width is 599 which when adjusted by the above function comes out to be 608.
image

If I adjust chroma planes' width manually to 600, it seems to work:

image

Research

I have done the following:

Additional context

  • This answer on SO seems useful.
  • I also tried using frame.to_ndarray() directly with same results.
    def frame_to_ycbcr(self, frame):
        frame_data = frame.to_ndarray(format=frame.format.name)
        start_offset = 0
        end_offset = frame.height
        y_data = frame_data[start_offset:end_offset, :]
        start_offset = end_offset
        end_offset = (5 * frame.height) // 4
        cb_data = frame_data[start_offset:end_offset, :].reshape(-1, frame.width // 2)
        start_offset = end_offset
        end_offset = None
        cr_data = frame_data[start_offset:, :].reshape(-1, frame.width // 2)
        return y_data, cb_data, cr_data
  • FFALIGN macro
  • avcodec_align_dimensions2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions