Skip to content

ASR pipeline silently destroys stereo audio in channels-last layout (what soundfile/librosa return): mean(axis=0) averages across time #47886

Description

@Kayvan-Zahiri

System Info

  • transformers 5.7.0
  • torch 2.11.0, numpy 1.26.4, soundfile 0.13.1
  • Python 3.12, macOS, CPU

Who can help?

AutomaticSpeechRecognitionPipeline / audio pipelines.

Reproduction

Passing a stereo waveform loaded with soundfile silently discards almost all of the
audio. The pipeline logs a warning saying it converted to mono, and then transcribes
about 3 seconds of speech as ' you'.

import io, numpy as np, soundfile as sf
from transformers import pipeline

rng = np.random.default_rng(0)
mono = (rng.standard_normal(16000 * 3) * 0.05).astype(np.float32)
buf = io.BytesIO()
sf.write(buf, np.stack([mono, mono], axis=1), 16000, format="WAV", subtype="FLOAT")
buf.seek(0)

stereo, sr = sf.read(buf, dtype="float32")     # soundfile returns (samples, channels)
print("soundfile.read shape :", stereo.shape)

asr = pipeline("automatic-speech-recognition", model="openai/whisper-tiny", device="cpu")
print("mono   :", repr(asr(mono.copy())["text"]))
print("stereo :", repr(asr(stereo.copy())["text"]))
print("after pipeline's mean(axis=0):", stereo.mean(axis=0).shape)

Output:

soundfile.read shape : (48000, 2)
UserWarning: We expect a single channel audio input for AutomaticSpeechRecognitionPipeline,
             got 2. Taking the mean of the channels for mono conversion.
mono   : ' See you in the next video!'
stereo : ' you'
after pipeline's mean(axis=0): (2,)

Expected behavior

The stereo call should transcribe the same as the mono call. Instead 48,000 samples
become 2.

pipelines/automatic_speech_recognition.py:

if inputs.ndim != 1:
    logger.warning(
        f"We expect a single channel audio input for AutomaticSpeechRecognitionPipeline, "
        f"got {inputs.ndim}. Taking the mean of the channels for mono conversion."
    )
    inputs = inputs.mean(axis=0)

mean(axis=0) assumes channels-first, (channels, samples). soundfile.read and
librosa.load(..., mono=False) both return channels-last, (samples, channels), which is
also what scipy.io.wavfile.read returns. On that layout axis=0 averages across time,
so a 3-second stereo clip collapses to 2 numbers, gets padded back to 30s of silence, and
decodes to ' you'.

(2, N) input works correctly, so whether the pipeline destroys your audio depends on
which loader you used, with no error either way.

Two smaller things in the same three lines:

  • The warning interpolates inputs.ndim, not the channel count, so it always says
    "got 2" for any 2-D input regardless of how many channels there are. For the
    channels-last case the number that would actually help ("got 48000") never appears.
  • A user who reads the warning is told the conversion happened. It did, along the wrong
    axis.

Suggested fix: treat the smaller of the two dimensions as the channel axis, which is
unambiguous for any realistic audio since channels are ~1-8 and samples are thousands:

if inputs.ndim != 1:
    ch_axis = int(np.argmin(inputs.shape))
    logger.warning(
        f"We expect a single channel audio input for AutomaticSpeechRecognitionPipeline, "
        f"got shape {tuple(inputs.shape)}. Taking the mean over axis {ch_axis} for mono conversion."
    )
    inputs = inputs.mean(axis=ch_axis)

Raising on ndim > 2 would also be reasonable. Happy to open a PR with tests covering
both layouts if you would like.

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