Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encode_lazy method to CodecContext #1092

Merged
merged 2 commits into from
Nov 25, 2023

Conversation

rawler
Copy link
Contributor

@rawler rawler commented Feb 25, 2023

Some codecs (libvpx-vp9) can both buffer many frames, and take a long time encoding each frame. Accumulated, the last encode(None)-flush can end up taking a long time >30s, without detectable progress.

FFmpeg and many encoders themselves output one frame at a time, but PyAV currently buffer them all up into lists returned.

This change adds a encode_lazy(), yielding frames as they are made ready.

The change was benchmarked to also yield a net performance improvement. For both encode() and encode_lazy, encoding really small (24x18) frames using the mpeg4 encoder seem to take ~11% less time.

@rawler rawler marked this pull request as draft February 25, 2023 14:46
Some codecs (VP9) can both buffer _many_ frames, and take a long time
encoding each frame. Accumulated, the last `encode(None)`-flush can end
taking a long time >30s, without detectable progress.

FFmpeg and many encoders themselves output one frame at a time, but PyAV
currently buffer them all up into lists returned.

This change adds a `encode_lazy` yielding frames as they are made ready.

The change was benchmarked to also yield a net performance improvement. For
both `encode()` and `encode_lazy` encoding really small (24x18) frames
using the `mpeg4` encoder seem to take ~11% less time.
@rawler rawler changed the title [codec context] Let decode return a generator [codec context] Add decode_lazy() returning a generator Feb 25, 2023
@rawler
Copy link
Contributor Author

rawler commented Feb 25, 2023

I also attempted to make the same change to decode(), but there I instead saw a clear (~10%) performance regression, so letting it be for now.

Benchmarked as

# ----------------------------------------------------------------
# Start encoding

for i in range(200):
    start = monotonic()
    with av.open("/tmp/test.mp4", mode="w") as container:
        stream = container.add_stream("mpeg4", rate=24, width=24, height=18, pix_fmt="yuv420p")
        for frame in frames:
            for packet in stream.encode(frame):
                container.mux(packet)

        # Flush stream
        for packet in stream.encode():
            container.mux(packet)

    encoding_times.push(monotonic()-start)

# ----------------------------------------------------------------
# Start encoding lazy

for i in range(200):
    start = monotonic()
    with av.open("/tmp/test.mp4", mode="w") as container:
        stream = container.add_stream("mpeg4", rate=24, width=24, height=18, pix_fmt="yuv420p")
        for frame in frames:
            for packet in stream.encode_lazy(frame):
                container.mux(packet)

        # Flush stream
        for packet in stream.encode_lazy():
            container.mux(packet)

    encoding_lazy_times.push(monotonic()-start)

Encode() elapsed before:

==== 200 samples ====
   0.107: ▌ (1)
   0.112: ███████████████████████▎ (43)
   0.117: ██████▌ (12)
   0.123: ███▊ (7)
   0.129: ███▊ (7)
   0.135: ████████████████████████████████████████████████████████████  (111)
   0.141: ██████████▎ (19)
  min: 0.105 avg: 0.129 max: 0.145
  perc: 95th: 0.141 99th: 0.144 99.9th: 0.144

Encode() elapsed after:

==== 200 samples ====
   0.107: ████████████████████████████████████████████  (66)
   0.112: ████████████████████████████████████████████████████████████  (90)
   0.117: ████████████  (18)
   0.123: █████▍ (8)
   0.129: █████▍ (8)
   0.135: ██████  (9)
   0.141: ▋ (1)
  min: 0.105 avg: 0.113 max: 0.145
  perc: 95th: 0.132 99th: 0.137 99.9th: 0.143

Encode_lazy() after:

==== 200 samples ====
   0.107: ████████████████  (31)
   0.112: ████████████████████████████████████████████████████████████  (116)
   0.117: ██████████▍ (20)
   0.123: █████▏ (10)
   0.129: █  (2)
   0.135: ████████▊ (17)
   0.141: ██▏ (4)
  min: 0.105 avg: 0.115 max: 0.145
  perc: 95th: 0.136 99th: 0.141 99.9th: 0.144

@rawler rawler marked this pull request as ready for review February 25, 2023 23:30
@WyattBlue WyattBlue added the tests requested Please add tests to your PR label Nov 13, 2023
@WyattBlue WyattBlue changed the title [codec context] Add decode_lazy() returning a generator Add encode_lazy method to CodecContext Nov 25, 2023
@WyattBlue WyattBlue removed the tests requested Please add tests to your PR label Nov 25, 2023
@WyattBlue
Copy link
Member

Thanks, merging.

@WyattBlue WyattBlue merged commit bdacaac into PyAV-Org:main Nov 25, 2023
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants