Skip to content

Build fpcalc against the system FFmpeg in CI - #165

Merged
lalinsky merged 3 commits into
masterfrom
ci-build-tools-system-ffmpeg
Jul 28, 2026
Merged

lalinsky merged 3 commits into
masterfrom
ci-build-tools-system-ffmpeg

Conversation

@acoustid-bot

Copy link
Copy Markdown
Contributor

test-linux and test-macos both pass -DBUILD_TOOLS=OFF, so the FFmpeg audio reader is never compiled there — FFmpeg is only pulled in as an FFT provider for the avfft matrix entry. The packaging jobs do build fpcalc, but package/build.sh pins FFMPEG_VERSION=8.0.

So the combination distributions actually use — tools enabled, system FFmpeg — is never built. That is also the configuration the FFmpeg detection in CMakeLists.txt and cmake/modules/FindFFmpeg.cmake exists for, which means changes to it (such as #158, which touched only those two files) land without CI covering what they fix.

This adds a test-linux-tools job that builds with -DBUILD_TOOLS=ON against the FFmpeg ubuntu-22.04 ships, runs the test suite, and checks fpcalc still produces tests/data/test.mp3.fpcalc.out — the same assertion the Linux packaging job makes, but against a different FFmpeg. It prints the FFmpeg package versions first, so a failure reports which ones it built against rather than needing a rerun to find out.

FFT_LIB=fftw3 so the job tests FFmpeg as the audio reader rather than as the FFT.

What I could not check locally

There are no FFmpeg development libraries on the machine I wrote this on, so I could not build fpcalc before pushing — this PR's own CI run is the first real test of it. Two things it will settle:

  • whether the fpcalc binary path is right (build.test.tools/src/cmd/fpcalc);
  • whether decoding test.mp3 with the older FFmpeg gives the same fingerprint as the pinned 8.0 the reference was generated with. If it does not, the output check needs relaxing to a build-and-run smoke test, and the difference is itself worth knowing about.

Happy to adjust once it has run.

The test jobs pass BUILD_TOOLS=OFF, so the FFmpeg audio reader is never
compiled there, and the packaging jobs build it against a pinned FFmpeg 8.0.
Nothing covered the combination distributions actually use: tools enabled,
system FFmpeg. That left the FFmpeg detection in CMakeLists.txt and
FindFFmpeg.cmake untested for older releases.

Add a job that builds with BUILD_TOOLS=ON against the FFmpeg that ubuntu-22.04
ships, runs the test suite, and checks fpcalc still produces the reference
fingerprint. It prints the FFmpeg package versions first so a failure says
which ones it was built against.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@acoustid-bot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08f01531-b80b-4cce-8111-0fdb48caae97

📥 Commits

Reviewing files that changed from the base of the PR and between c1702bc and 8090330.

📒 Files selected for processing (2)
  • src/audio/ffmpeg_audio_reader.h
  • src/audio/ffmpeg_compat.h

Walkthrough

Changes

FFmpeg compatibility and Linux tools validation

Layer / File(s) Summary
FFmpeg compatibility contract
src/audio/ffmpeg_compat.h
Adds version-based selection for FFmpeg channel-layout support and codec channel-count access.
Reader channel-layout flow
src/audio/ffmpeg_audio_reader.h
Updates input options, converter setup, channel-layout handling, and sample allocation for both FFmpeg API variants.
Processor channel-layout wiring
src/audio/ffmpeg_audio_processor_swresample.h
Uses modern channel-layout options when available and legacy integer options otherwise.
Linux tools validation
.github/workflows/build.yml
Adds a Linux job that builds tools with FFTW3, runs tests, and compares fpcalc output against the committed fixture.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding CI coverage for fpcalc against system FFmpeg.
Description check ✅ Passed The description clearly matches the changeset and explains the new CI job plus FFmpeg compatibility work.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci-build-tools-system-ffmpeg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/build.yml:
- Around line 36-39: Add a job-level permissions block to test-linux-tools
granting only contents: read for its actions/checkout step, leaving all other
token permissions disabled.
- Line 39: Update the checkout step using the actions/checkout symbol to
reference a full immutable commit SHA instead of the mutable v3 tag, and set
persist-credentials to false. Keep the workflow’s existing checkout behavior
unchanged otherwise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 07f94b5b-1d12-41d8-a52c-cb29d7165ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 41052e0 and cda942d.

📒 Files selected for processing (1)
  • .github/workflows/build.yml

Comment on lines +36 to +39
test-linux-tools:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow files =="
git ls-files '.github/workflows/*' || true

echo "== build.yml around referenced job =="
sed -n '1,140p' .github/workflows/build.yml 2>/dev/null || true

echo "== all permissions blocks in build.yml =="
rg -n '^\s*permissions:' .github/workflows/build.yml || true

echo "== checkout v3 docs/security? local metadata if present? =="
git ls-files | rg 'actions/.*checkout|\.gitmodules|actionlint|tflint|github-actions' || true

echo "== local workflow syntax/tooling evidence =="
git ls-files | rg '(^|/)(actionlint|gha|github-actions|github-action|\.github)' || true

Repository: acoustid/chromaprint

Length of output: 4532


🌐 Web query:

GitHub Actions workflow syntax permissions writes contents read GITHUB_TOKEN default permissions

💡 Result:

In GitHub Actions, the permissions granted to the GITHUB_TOKEN are determined by a hierarchical configuration that starts with default settings at the enterprise, organization, or repository level and can be refined within the workflow file using the permissions key [1][2]. Key points regarding permissions and syntax: Default Behavior: The GITHUB_TOKEN permissions are initially set based on the configuration of the parent enterprise, organization, or repository [1][2]. As of early 2023, GitHub shifted toward a read-only default for new enterprises, organizations, and repositories to enhance security [3]. Workflow Syntax: You can use the permissions key to modify the GITHUB_TOKEN permissions at either the workflow level or for individual jobs [1][4]. - If you specify access for any permission, all other permissions not explicitly listed are automatically set to none [1][2]. - Available access levels are read, write, and none [1][2]. Note that write access inherently includes read access [1][2]. Security Adjustments: - Workflow configuration overrides the repository/organization/enterprise defaults [1][2]. - Regardless of the configuration, if a workflow is triggered by a pull request from a forked repository (and the Send write tokens to workflows from pull requests setting is disabled), the GITHUB_TOKEN is automatically downgraded to read-only for security reasons [1][2]. - It is a recommended security practice to use the principle of least privilege, explicitly defining only the minimum permissions necessary for your workflow [4].

Citations:


🌐 Web query:

actions checkout v3 metadata permissions contents read required

💡 Result:

Yes, the contents: read permission is required for the actions/checkout action to function correctly when using the default GITHUB_TOKEN [1][2][3]. In GitHub Actions, if you explicitly define the permissions block in your workflow file, all unspecified permissions default to none [1][4]. Because actions/checkout requires access to the repository's contents to perform a clone, failing to grant contents: read will cause the action to fail with an error (typically indicating that repository access was not granted) [1]. It is recommended to explicitly set the following in your workflow if you are restricting permissions: permissions: contents: read This requirement applies unless you are using an alternative authentication method, such as a Personal Access Token (PAT) or an SSH key provided via the token or ssh-key inputs, which bypass the GITHUB_TOKEN constraints [2][3]. Note that for public repositories, GITHUB_TOKEN generally has read access by default, but for private and internal repositories, explicit configuration is necessary if the permissions block is used [1].

Citations:


Declare the required GITHUB_TOKEN permissions explicitly.

This job only needs actions/checkout access, so add a job-level permissions block granting contents: read. Once any permissions is declared, other permissions default to none, keeping the token scope to the workflow’s actual needs.

Proposed fix
   test-linux-tools:
+    permissions:
+      contents: read
     runs-on: ubuntu-22.04
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test-linux-tools:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
test-linux-tools:
permissions:
contents: read
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
🧰 Tools
🪛 actionlint (1.7.12)

[error] 39-39: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 zizmor (1.26.1)

[warning] 39-39: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 36-54: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 39-39: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[info] 36-36: workflow or action definition without a name (anonymous-definition): this job

(anonymous-definition)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml around lines 36 - 39, Add a job-level
permissions block to test-linux-tools granting only contents: read for its
actions/checkout step, leaving all other token permissions disabled.

Sources: MCP tools, Linters/SAST tools

test-linux-tools:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

if rg -nP 'uses:\s*actions/checkout@(v\d+|main|master)' .github/workflows; then
  echo "Mutable checkout reference found"
  exit 1
fi

Repository: acoustid/chromaprint

Length of output: 701


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== workflow relevant sections =="
sed -n '1,220p' .github/workflows/build.yml | nl -ba | sed -n '1,220p'

echo
echo "== GitHub workflows checkout references summary =="
python3 - <<'PY'
import re
from pathlib import Path
p=Path('.github/workflows/build.yml')
for i,l in enumerate(p.read_text().splitlines(),1):
    if re.search(r'uses:\s*actions/checkout@(v\d+|main|master|[\w-]+)', l):
        print(f"{i}:{l}")
PY

echo
echo "== action metadata if present =="
fd -a 'action.yml|action.yaml' . | sed 's#^\./##' | while read -r f; do
  printf "\n-- %s --\n" "$f"
  sed -n '1,220p' "$f"
done

Repository: acoustid/chromaprint

Length of output: 231


🌐 Web query:

actions/checkout v3 persist-credentials default true README

💡 Result:

The persist-credentials input for the actions/checkout action has a default value of true [1][2][3]. This setting configures the repository to use the GitHub Actions authentication token (or SSH key) in the local Git configuration [2][3], allowing your workflow scripts to run authenticated Git commands (such as git push or git fetch) without needing to manually configure additional credentials [2][3]. If you wish to opt out of this behavior—for instance, to prevent the token from being persisted for security reasons—you can explicitly set persist-credentials: false in your workflow step [2][3]. It is worth noting that while the default remains true across major versions, security improvements have been implemented over time. In later versions (specifically starting with v6), the action was updated to persist these credentials to a separate, isolated configuration file using Git's includeIf mechanism, rather than writing them directly to the repository's primary .git/config file [4][5][6]. This change was designed to improve security by isolating the credentials [5][6]. Discussions regarding changing the default value to false to further reduce potential risk have occurred in the repository, but the default remains true [7][8].

Citations:


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== .github/workflows/build.yml checkout steps =="
python3 - <<'PY'
from pathlib import Path
for i, line in enumerate(Path('.github/workflows/build.yml').read_text().splitlines(), 1):
    if 'actions/checkout' in line or 'persist-credentials' in line:
        print(f'{i}: {line}')
PY

echo
echo "== workflow permissions and checkout steps =="
python3 - <<'PY'
from pathlib import Path
lines = Path('.github/workflows/build.yml').read_text().splitlines()
for i, line in enumerate(lines, 1):
    if line.strip().startswith('permissions:') or line.strip().startswith('- uses:'):
        print(f'{i}: {line}')
PY

echo
echo "== search for other checkout pins =="
python3 - <<'PY'
import re
from pathlib import Path
for path in Path('.github/workflows').iterdir():
    if path.is_file():
        for i, line in enumerate(path.read_text().splitlines(), 1):
            m = re.search(r'uses:\s*actions/checkout@([^ #\s\n]+)', line)
            if m:
                ref = m.group(1)
                print(f'{path}:{i}: {ref}')
PY

Repository: acoustid/chromaprint

Length of output: 1316


🌐 Web query:

actions/checkout releases v3 latest full commit SHA

💡 Result:

The latest release of the actions/checkout v3 branch is v3.7.0, which was published on July 20, 2026 [1][2]. The commit SHA associated with the v3.7.0 tag is 8e5e7e5ab8b370d6c329ec480221332ada57f0ab [1]. Please note that as of July 28, 2026, the actions/checkout repository has newer major versions available, such as v4, v5, v6, and v7 [2]. It is generally recommended to use the latest stable version of the action [3].

Citations:


🌐 Web query:

actions/checkout v3.7.0 latest supported v3 release commit 8e5e7e5ab8b370d6c329ec480221332ada57f0ab

💡 Result:

As of July 28, 2026, actions/checkout v3.7.0 is the latest released version for the v3 major release line [1][2][3]. The specific commit hash 8e5e7e5ab8b370d6c329ec480221332ada57f0ab corresponds to the release of v3.5.2, not v3.7.0 [4][5][6]. The v3.5.2 release was published prior to v3.7.0 [4][3]. The v3.7.0 release is the most recent v3 version, having been released on July 20, 2026 [1]. Users are advised that higher major versions of actions/checkout, such as v7, are currently available and maintained [7][8].

Citations:


Pin checkout and disable persisted credentials.

actions/checkout@v3 is an outdated mutable reference, and persist-credentials defaults to true for build/test steps. Pin the checkout action to a full commit SHA and set persist-credentials: false; this workflow only checks out the repo, so authenticated Git commands are not needed.

Proposed fix
-    - uses: actions/checkout@v3
+    - uses: actions/checkout@<reviewed-full-commit-sha>
+      with:
+        persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v3
- uses: actions/checkout@<reviewed-full-commit-sha>
with:
persist-credentials: false
🧰 Tools
🪛 actionlint (1.7.12)

[error] 39-39: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

🪛 zizmor (1.26.1)

[warning] 39-39: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 39-39: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/build.yml at line 39, Update the checkout step using the
actions/checkout symbol to reference a full immutable commit SHA instead of the
mutable v3 tag, and set persist-credentials to false. Keep the workflow’s
existing checkout behavior unchanged otherwise.

Sources: MCP tools, Linters/SAST tools

The audio reader uses AVChannelLayout, ch_layout and av_channel_layout_*,
all of which arrived in libavutil 57.24.100 (FFmpeg 5.1). Building the tools
against anything older fails outright, so despite #158 the 4.2 to 5.0 range
that CMake accepts could not actually be compiled.

Add ffmpeg_compat.h with the version check and a macro for the channel count,
and give both call sites in the reader and both setters in the swresample
processor a pre-5.1 path using the bitmask layout, av_get_default_channel_layout
and the in_channel_layout/out_channel_layout options.

The new test-linux-tools job is what found this, building against the FFmpeg
that ubuntu-22.04 ships.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/audio/ffmpeg_audio_reader.h`:
- Around line 207-225: Update the converter configuration used by Read() to
derive input sample format, sample rate, and channel layout from m_frame rather
than only m_codec_ctx. Before converting each frame, compare those properties
with the current FFmpegAudioProcessor configuration and reset/rebuild
m_converter when they change, while preserving the existing output settings and
channel-layout API branches.
- Line 342: Update the av_samples_alloc call in the converter buffer allocation
path to use m_output_channels rather than
CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), matching the channel count produced by
m_converter->Convert(...). Keep the existing packed S16 format and sample-count
allocation behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a228d104-bded-420a-a8ca-fb6a0c570073

📥 Commits

Reviewing files that changed from the base of the PR and between cda942d and c1702bc.

📒 Files selected for processing (3)
  • src/audio/ffmpeg_audio_processor_swresample.h
  • src/audio/ffmpeg_audio_reader.h
  • src/audio/ffmpeg_compat.h

Comment on lines +207 to +225
if (m_codec_ctx->sample_fmt != AV_SAMPLE_FMT_S16 || CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx) != m_output_channels || m_codec_ctx->sample_rate != m_output_sample_rate) {
m_converter.reset(new FFmpegAudioProcessor());
m_converter->SetCompatibleMode();
m_converter->SetInputSampleFormat(m_codec_ctx->sample_fmt);
m_converter->SetInputSampleRate(m_codec_ctx->sample_rate);
#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT
m_converter->SetInputChannelLayout(&(m_codec_ctx->ch_layout));
#else
m_converter->SetInputChannelLayout(m_codec_ctx->channel_layout
? m_codec_ctx->channel_layout
: av_get_default_channel_layout(m_codec_ctx->channels));
#endif
m_converter->SetOutputSampleFormat(AV_SAMPLE_FMT_S16);
m_converter->SetOutputSampleRate(m_output_sample_rate);
#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT
m_converter->SetOutputChannelLayout(&output_channel_layout);
#else
m_converter->SetOutputChannelLayout(output_channel_layout);
#endif

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate ffmpeg_audio_reader.h =="
fd -a 'ffmpeg_audio_reader\.h$' . || true

echo "== outline =="
file=$(fd 'ffmpeg_audio_reader\.h$' . | head -n1)
if [ -n "${file:-}" ]; then
  wc -l "$file"
  ast-grep outline "$file" --view compact || true
  echo "== relevant sections =="
  sed -n '1,280p' "$file" | cat -n
fi

echo "== related symbols =="
rg -n "FFmpegAudioProcessor|FFmpegAudioReader|m_converter|SetInput|SetOutput|Reinitializ|AVRational|Read\\(|Open\\(" -S src include . 2>/dev/null | head -n 200

Repository: acoustid/chromaprint

Length of output: 26479


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file=$(fd 'ffmpeg_audio_reader\.h$' . | head -n1)
echo "== Read() implementation =="
sed -n '278,370p' "$file" | cat -n

echo "== FFmpegAudioProcessor implementation =="
processor_file=$(fd 'ffmpeg_audio_processor_swresample\.h$|ffmpeg_audio_processor.*\.h$|ffmpeg_audio_processor.*\.cpp$' src/audio | head -n3 | tr '\n' ' ')
for p in $processor_file; do
  echo "-- $p --"
  wc -l "$p"
  sed -n '1,220p' "$p" | cat -n
done

echo "== compat macro definitions =="
fd -a 'ffmpeg_compat\.h$' src/audio | xargs -r -I{} sh -c 'echo "-- {} --"; sed -n "1,140p" "{}" | cat -n'

echo "== FFmpeg channel layout doc extraction from system headers if present =="
if [ -f /usr/include/libavutil/channel_layout.h ]; then
  rg -n "channel_layout|ChannelLayout" /usr/include/libavutil/channel_layout.h | head -n 60
fi

echo "== deterministic model: current Read() path vs frame property deltas =="
python3 - <<'PY'
from pathlib import Path
p=list(Path('.').rglob('ffmpeg_audio_reader.h'))[0]
s=p.read_text()
print("Open creates converter from m_codec_ctx:", "m_converter.reset(new FFmpegAudioProcessor())" in s[s.find("FFmpegAudioReader::Open"):s.find("return m_opened = true;")])
print("Read sets input layout from m_frame before using converter:", "SetInputChannelLayout" in s[s.find("FFmpegAudioReader::Read"):s.find("return false;", s.find("FFmpegAudioReader::Read"))] or "m_frame->ch_layout" in s[s.find("FFmpegAudioReader::Read"):s.find("m_converter->Convert")] or "av_channel_layout_compare" in s)
read = s[s.find("FFmpegAudioReader::Read"):s.find("return false;")]
print("Read contains format/rate/layout reinit or compare before Convert:", any(x in read for x in ["Reinit", "avcodec_reinit2", "av_frame_side_data_create", "Convert", "Flush"]))
PY

Repository: acoustid/chromaprint

Length of output: 8557


🌐 Web query:

FFmpeg AVCodecContext channels sample_rate ch_layout decoder may override context frame input samples

💡 Result:

In FFmpeg, the AVCodecContext fields 'ch_layout' (which replaced the deprecated 'channel_layout') and 'sample_rate' are used to describe the audio stream's configuration [1][2][3]. When decoding, these fields may be set by the caller (e.g., initialized from container metadata) [1][2]. However, the decoder is permitted to override these values during the decoding process if the actual stream properties differ from the initial assumptions or if the decoder dynamically updates its state based on the input packets [1][2][3]. Key points regarding this behavior: 1. Dynamic Updates: Decoders often perform this override to reflect the true stream parameters discovered during parsing or decoding of the bitstream [1][2]. 2. Side Data: Libavcodec also handles parameter changes during decoding via AV_SIDE_DATA_PARAM_CHANGE events [4][5]. If a packet contains side data indicating a change in channel count, channel layout, or sample rate, the decoder may update the AVCodecContext accordingly [4][5]. 3. Frame Propagation: When a decoder produces a decoded frame, it populates the AVFrame with the current audio properties (like 'ch_layout' and 'sample_rate') [4][5]. Applications should generally rely on the parameters attached to the output AVFrame [4][5]. 4. API Usage: The 'ch_layout' field (using the AVChannelLayout structure) is the modern, preferred API [3]. Older code using 'channel_layout' is deprecated [3]. In summary, the AVCodecContext fields in the decoder context represent a best-effort configuration that can be modified by the codec during operation; therefore, robust applications should read the actual format information from the output frames rather than assuming the values in the AVCodecContext remain static [4][5].

Citations:


Reconfigure the resampler from m_frame audio properties.

Open() builds FFmpegAudioProcessor with m_codec_ctx, but decoded frames can carry the actual sample_fmt, sample_rate, and channel layout. Read() checks nb_samples only and then sends m_frame data through converters initialized with m_codec_ctx, so changed format/later dynamic audio properties get converted with stale layout/rate settings. Compare to m_frame before conversion and reset m_converter when these properties change, or reconfigure the swresample context accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/audio/ffmpeg_audio_reader.h` around lines 207 - 225, Update the converter
configuration used by Read() to derive input sample format, sample rate, and
channel layout from m_frame rather than only m_codec_ctx. Before converting each
frame, compare those properties with the current FFmpegAudioProcessor
configuration and reset/rebuild m_converter when they change, while preserving
the existing output settings and channel-layout API branches.

av_freep(&m_convert_buffer[0]);
m_convert_buffer_nb_samples = std::max(1024 * 8, m_frame->nb_samples);
ret = av_samples_alloc(m_convert_buffer, &linsize, m_codec_ctx->ch_layout.nb_channels, m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);
ret = av_samples_alloc(m_convert_buffer, &linsize, CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '300,375p' src/audio/ffmpeg_audio_reader.h

echo
echo "== relevant symbols/usages =="
rg -n "m_convert_buffer|m_convert_buffer_nb_samples|m_output_channels|CHROMAPRINT_CODEC_CHANNELS|av_samples_alloc|swr_convert|SwrContext" src/audio/ffmpeg_audio_reader.h src/audio || true

echo
echo "== diff stat/name status if available =="
git diff --stat || true
git diff -- src/audio/ffmpeg_audio_reader.h | sed -n '1,220p' || true

Repository: acoustid/chromaprint

Length of output: 7178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ffmpeg_audio_processor_swresample.h =="
sed -n '1,120p' src/audio/ffmpeg_audio_processor_swresample.h

echo
echo "== broader av_samples_alloc usage =="
rg -n "av_samples_alloc|CHROMAPRINT_CODEC_CHANNELS\\(m_codec_ctx\\)|m_output_channels" src/audio -g '*.h' -g '*.cpp'

echo
echo "== FFmpeg doxygen evidence via shell curl if available =="
if command -v curl >/dev/null 2>&1; then
  curl -fsSL --max-time 15 "https://ffmpeg.org/doxygen/5.1/group__lswr__audio__conversion__util.html" | \
    grep -C 5 "av_samples_alloc" || true
else
  echo "curl not available"
fi

Repository: acoustid/chromaprint

Length of output: 3854


🌐 Web query:

FFmpeg av_samples_alloc nb_channels parameter documentation lswr

💡 Result:

In the FFmpeg API, the nb_channels parameter in av_samples_alloc represents the number of audio channels to be allocated in the buffer [1][2][3]. The function is part of the libavutil samples manipulation API [1][4]. Its full signature is: int av_samples_alloc(uint8_t audio_data, int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align); Parameter Details: - nb_channels: The number of audio channels for which memory should be allocated [1][3]. - audio_data: An output array that will be populated with the pointers to the data for each channel [1][3]. - nb_samples: The number of samples per channel [1][3]. - sample_fmt: The audio sample format (e.g., AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLTP) [1][3]. - linesize: An output pointer to store the aligned size of the audio buffer(s); this may be set to NULL if not needed [1][3]. - align: The alignment requirement for the buffer size (0 for default, 1 for no specific alignment) [1][3]. While av_samples_alloc is frequently used alongside libswresample (lswr) to manage buffers for converted audio [4][5], it is defined in libavutil/samplefmt.h [6][3]. Libswresample handles the conversion logic, such as resampling or channel layout changes, and its configuration is managed via SwrContext [4][7]. When working with channel layouts in newer FFmpeg versions, developers are encouraged to use the AVChannelLayout API in functions like swr_alloc_set_opts2 for context initialization, while av_samples_alloc remains the standard tool for the low-level memory allocation of the sample buffers themselves [1][4][8].

Citations:


Allocate the converter output buffer by output channels.

m_convert_buffer is packed AV_SAMPLE_FMT_S16, and this allocation currently uses the codec input channels even though m_converter outputs m_output_channels. If mono input is requested as stereo, m_converter->Convert(...) can write 2 channels into size for 1.

Proposed fix
- ret = av_samples_alloc(m_convert_buffer, &linsize, CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);
+ ret = av_samples_alloc(m_convert_buffer, &linsize, m_output_channels, m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ret = av_samples_alloc(m_convert_buffer, &linsize, CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);
ret = av_samples_alloc(m_convert_buffer, &linsize, m_output_channels, m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/audio/ffmpeg_audio_reader.h` at line 342, Update the av_samples_alloc
call in the converter buffer allocation path to use m_output_channels rather
than CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), matching the channel count
produced by m_converter->Convert(...). Keep the existing packed S16 format and
sample-count allocation behavior unchanged.

avformat_open_input() and av_find_best_stream() took non-const pointers before
libavformat 59 (FFmpeg 5.0), so the const declarations the reader uses do not
compile there.

Qualify the two declarations through a macro that is empty below 59.
@lalinsky
lalinsky merged commit 2cb30e2 into master Jul 28, 2026
29 checks passed
@lalinsky
lalinsky deleted the ci-build-tools-system-ffmpeg branch July 28, 2026 06:08
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