Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

apply_volume() corrupts 16- and 32-bit PCM on big-endian hosts, against its own little-endian contract

オープン
#70 コメント 2 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
3/5
見積もり時間
1〜2日
初心者へのやさしさ
78/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
cpp

調査の方向性

Start in src/pcm_volume.cpp at apply_volume() and read the little-endian contract in src/pcm_volume.h, then run the standalone harness described against the 16- and 32-bit paths on a big-endian target. Done means S16_LE and S32_LE produce the same scaled little-endian samples as the reference while the existing 24-bit behavior remains correct.

索引モデルが issue の本文から書いたものです。

説明

src/pcm_volume.h states the contract in its own words:

Software volume for interleaved signed little-endian PCM, in Q32 fixed point.

and again on the function:

Scales len bytes of signed little-endian PCM in place by a Q32 gain of at most Q32_ONE.

Two of the three sample widths do not honour that on a big-endian host.

The code

src/pcm_volume.cpp, in apply_volume(). The 24-bit path reads and writes byte by byte, which is correct anywhere:

case 3: {
    int32_t sample = static_cast<int32_t>(p[0] | (p[1] << 8) | (p[2] << 16));
    ...
    p[0] = static_cast<uint8_t>(out & 0xFF);
    p[1] = static_cast<uint8_t>((out >> 8) & 0xFF);
    p[2] = static_cast<uint8_t>((out >> 16) & 0xFF);
}

The 16- and 32-bit paths reinterpret the buffer at native width instead:

case 2: {
    auto* samples = reinterpret_cast<int16_t*>(data);
    ...
}
case 4: {
    auto* samples = reinterpret_cast<int32_t*>(data);
    ...
}

On a little-endian host those agree. On a big-endian one they read each sample with its bytes reversed, scale the wrong number, and store it reversed again.

Reproducing it

pcm_volume.{h,cpp} are self-contained — <cmath>, <cstddef>, <cstdint> and nothing else — so they build standalone. Compiled unchanged from 0.3.0 for MIPS big-endian and run under qemu-mips-static, against a little-endian sine scaled by Q32_ONE / 2 (-6 dB) and read back as little-endian:

──────── x86_64 (little-endian) ────────
  16-bit (S16_LE)        ok (0/64 samples wrong)
  24-bit (S24_3LE)       ok (0/64 samples wrong)
  32-bit (S32_LE)        ok (0/64 samples wrong)
  => contract holds

──────── MIPS big-endian under QEMU ────────
  16-bit (S16_LE)        FAIL (57/64 samples wrong)
  24-bit (S24_3LE)       ok  (0/64 samples wrong)
  32-bit (S32_LE)        FAIL (60/64 samples wrong)
  => contract violated

The harness builds a sine in little-endian PCM, calls apply_volume(buf, len, bytes_per_sample, Q32_ONE/2), reads the result back as little-endian and compares against sample * gain >> 32. Glad to attach it, or to open a pull request with it as a test.

That the 24-bit path is right while the other two are not is why this reads as an oversight rather than a design choice.

Why it matters in practice

The ALSA sink opens SND_PCM_FORMAT_S16_LE / S24_3LE / S32_LE, so the bytes handed to the device are little-endian by construction and the 16- and 32-bit volume paths corrupt them on a big-endian build. Anything other than unity gain plays as noise.

I hit this packaging sendspin-cli for OpenWrt, where ath79 — MIPS 24Kc, big-endian, still on kernel 6.18 — is a common "old router with a USB port" host. My package carries @!BIG_ENDIAN for now so it is not offered there.

A related one, read-confirmed but not reproduced: opus_decode() in sendspin-cpp src/decoder.cpp fills its output through an (int16_t*) cast, and libopus writes native-endian opus_int16, so the Opus path looks like it has the same problem one layer down. The same goes for micro-flac's write_samples() fast paths. I am reporting those separately.

Environment

Source sendspin-cli 0.3.0, src/pcm_volume.{h,cpp} unchanged
Toolchain mips-linux-gnu-g++ (Debian), -O2 -static
Runner qemu-mips-static
Reference same harness on x86_64, which passes all three
主要言語
C++
スター
5
フォーク
3
平均マージ
7時間 6分
マージ済み PR(30日)
20

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

Sendspin/sendspin-cpp-cli のほかの issue

Sendspin/sendspin-cpp-cli の issue をすべて見る

似ている issue

C++ の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。