gcli2api / src /router /xtc_recognize /amr_decoder.py
a3216's picture
fix: amr_decoder 支持 audio/pcm 直通跳过 ffmpeg
6d61ecf
Raw
History Blame Contribute Delete
1.99 kB
"""AMR/任意音频 -> 8kHz 单声道 PCM s16le 转码。
使用系统 ffmpeg(Dockerfile 已 apt-get install ffmpeg)。
"""
from __future__ import annotations
import asyncio
import logging
from typing import Optional
logger = logging.getLogger(__name__)
async def decode_to_pcm_s16le(
audio_bytes: bytes,
*,
duration_sec: int = 3,
sample_rate: int = 8000,
channels: int = 1,
mime: str = "",
) -> bytes:
"""把任意音频(amr/mp3/wav/m4a 等)转成 8kHz 单声道 PCM s16le。
Args:
audio_bytes: 输入音频字节
duration_sec: 截取时长(秒),默认 3
sample_rate: 采样率,默认 8000(网易云 shazam_v2 要求)
channels: 声道数,默认 1
mime: MIME 类型。若为 audio/pcm 则视为已是 PCM s16le 直接截断返回
Returns:
PCM s16le 字节流
Raises:
RuntimeError: ffmpeg 失败
"""
# 裸 PCM 直通(便于测试 / 手表若直接录 PCM 也支持)
if mime in ("audio/pcm", "audio/x-pcm", "application/pcm"):
needed = sample_rate * channels * 2 * duration_sec
return audio_bytes[:needed]
args = [
"ffmpeg",
"-i", "pipe:0",
"-f", "s16le",
"-acodec", "pcm_s16le",
"-ar", str(sample_rate),
"-ac", str(channels),
"-t", str(duration_sec),
"pipe:1",
]
logger.info("[amr-decoder] ffmpeg start, input=%d bytes mime=%s", len(audio_bytes), mime)
proc = await asyncio.create_subprocess_exec(
*args,
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate(input=audio_bytes)
if proc.returncode != 0:
err = stderr.decode("utf-8", errors="replace")[-500:]
raise RuntimeError(f"ffmpeg exit {proc.returncode}: {err}")
logger.info("[amr-decoder] ffmpeg ok, pcm=%d bytes", len(stdout))
return stdout