Skip to content

CAMBI heatmaps: when `n_threads > 1 the first pictures are created with 0 bytes #1676

Description

@gdavila

With n_threads > 1, the CAMBI heatmaps written with heatmaps_path can lose their first pictures. In some runs, part or all of the first pictures of a heatmap file are zeros instead of the values a single-threaded run writes, so the output differs from run to run. The CAMBI scores are not affected.

Tested on Linux (FFmpeg 8.1 + libvmaf 3.2.1) and macOS (Homebrew FFmpeg 9.0.2 + libvmaf 3.2.1, which reports itself as 3.2.0).

Steps to reproduce

Two 1280x720 clips of 50 frames: a reference and a blurred copy.

ffmpeg -v error -y -f lavfi -i "testsrc2=s=1280x720:r=25:d=2" -pix_fmt yuv420p -c:v ffv1 ref.mkv
ffmpeg -v error -y -i ref.mkv -vf "gblur=sigma=1" -c:v ffv1 dist.mkv

Claude prepared the following script to check the bug quickly. It computes CAMBI with heatmaps 6 times with n_threads=1 and 6 times with n_threads=4. For each run, it prints how many bytes are not zero in each of the first five pictures of the scale 0 heatmap (p0 to p4). A lost picture is all zeros, so its count drops (to 0 if it is lost entirely).

frames=50
for threads in 1 4; do
  for run in 1 2 3 4 5 6; do
    out="heatmaps_t${threads}_r${run}"
    rm -rf "$out"
    ffmpeg -v error -nostdin -i dist.mkv -i ref.mkv \
      -lavfi "[0:v][1:v]libvmaf=feature=name=cambi\\\\:heatmaps_path=${out}:n_threads=${threads}:log_fmt=json:log_path=${out}.json" \
      -f null -
    f=$(ls "$out"/cambi_heatmap_scale_0_*)
    pic=$(( $(wc -c < "$f") / frames ))
    line="n_threads=$threads run=$run"
    for i in 0 1 2 3 4; do
      nz=$(dd if="$f" bs="$pic" skip="$i" count=1 2>/dev/null | LC_ALL=C tr -d '\0' | wc -c | tr -d ' ')
      line="$line p$i=$nz"
    done
    echo "$line"
  done
done

Expected

Every run gives the same counts of non-zero bytes as the n_threads=1 runs.

Actual

Currently, the script prints:

n_threads=1 run=1 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=1 run=2 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=1 run=3 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=1 run=4 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=1 run=5 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=1 run=6 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=1 p0=0 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=2 p0=4392 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=3 p0=0 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=4 p0=4132 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=5 p0=303 p1=5260 p2=5447 p3=6197 p4=6009
n_threads=4 run=6 p0=4858 p1=5260 p2=5447 p3=6197 p4=6009

The 6 single-threaded runs are identical. With n_threads=4, picture 0 is entirely zero in runs 1 and 3 and partly zero in runs 2, 4 and 5. Pictures 1 to 4 match the single-threaded runs. Which runs are affected changes from one attempt to the next, since it depends on thread timing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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