需求背景
最近看视频,过几天后经常忘记内容,所以有了把重点内容总结提炼到自己知识库的需求,这涉及到了提取视频中的音频数据、离线语音识别等功能。
提取视频中的音频数据,可以使用格式工厂或 FFmpeg 等工具, FFmpeg 可以使用命令 ffmpeg -i test.mp4 -f mp3 -vn test.mp3
将视频文件转换为 MP3 格式的音频文件,其中,-i test.mp4
:-i
参数指定输入文件,这里的输入文件是 test.mp4
。-f mp3
:-f
参数用于指定输出文件的格式,这里指定为 mp3 格式。-vn
:这个参数告诉 FFmpeg 在处理文件时忽略视频流,即不处理视频数据,只处理音频数据。test.mp3
:指定了输出文件的名称,即转换后的音频文件将被保存为当前目录下的 test.mp3
。
Whisper 是由 OpenAI 创建的开源通用语音识别项目,是一个经过大量的音频数据训练出来的支持多任务处理的语言识别模型。项目的 GitHub 仓库地址是 OpenAI Whisper ,在仓库介绍的 Show and tell 中发现这个项目 whisper-asr-webservice ,它在 whisper 基础上提供了 web 界面,并且可以用 docker 部署。
下面基于开源项目,体验了本地离线部署自己的 ASR 语音识别服务的过程。
镜像构建
克隆 whisper-asr-webservice 仓库到本地,切换到稳定版本,
git clone https://github.com/ahmetoner/whisper-asr-webservice.git
cd whisper-asr-webservice
git checkout v1.3.0
进入仓库目录,分析 Dockerfile.gpu 文件内容,这个 Dockerfile 使用了多阶段构建,
# 第一阶段:构建 FFmpeg
# 使用 Debian bookworm slim 版本作为基础镜像来构建 FFmpeg
FROM debian:bookworm-slim AS ffmpeg
# 安装 FFmpeg 编译所需的依赖包
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq update \
&& apt-get -qq install --no-install-recommends \
build-essential \
git \
pkg-config \
yasm \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# 从 GitHub 克隆特定版本的 FFmpeg 源码
RUN git clone https://github.com/FFmpeg/FFmpeg.git --depth 1 --branch n6.1.1 --single-branch /FFmpeg-6.1.1
# 设置工作目录为 FFmpeg 源代码目录
WORKDIR /FFmpeg-6.1.1
# 配置 FFmpeg 编译选项,禁用不需要的功能,以优化最终构建
RUN PATH="$HOME/bin:$PATH" PKG_CONFIG_PATH="$HOME/ffmpeg_build/lib/pkgconfig" ./configure \
--prefix="$HOME/ffmpeg_build" \
--pkg-config-flags="--static" \
--extra-cflags="-I$HOME/ffmpeg_build/include" \
--extra-ldflags="-L$HOME/ffmpeg_build/lib" \
--extra-libs="-lpthread -lm" \
--ld="g++" \
--bindir="$HOME/bin" \
--disable-doc \
--disable-htmlpages \
--disable-podpages \
--disable-txtpages \
--disable-network \
--disable-autodetect \
--disable-hwaccels \
--disable-ffprobe \
--disable-ffplay \
--enable-filter=copy \
--enable-protocol=file \
--enable-small && \
PATH="$HOME/bin:$PATH" make -j$(nproc) && \
make install && \
hash -r
# 第二阶段:准备 Swagger UI
# 使用 swagger-ui 镜像作为基础来提供 API 文档界面
FROM swaggerapi/swagger-ui:v5.9.1 AS swagger-ui
# 第三阶段:应用程序与 FFmpeg 集成
# 使用 NVIDIA CUDA 基础镜像,准备 Python 环境
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
# 设置 Python 版本和 poetry 虚拟环境路径
ENV PYTHON_VERSION=3.10
ENV POETRY_VENV=/app/.venv
# 安装 Python 和 pip
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq update \
&& apt-get -qq install --no-install-recommends \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-venv \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# 创建软链接以确保 python 和 pip 命令指向正确的版本
RUN ln -s -f /usr/bin/python${PYTHON_VERSION} /usr/bin/python3 && \
ln -s -f /usr/bin/python${PYTHON_VERSION} /usr/bin/python && \
ln -s -f /usr/bin/pip3 /usr/bin/pip
# 创建并配置 Python 虚拟环境
RUN python3 -m venv $POETRY_VENV \
&& $POETRY_VENV/bin/pip install -U pip setuptools \
&& $POETRY_VENV/bin/pip install poetry==1.6.1
# 添加 poetry 虚拟环境到 PATH
ENV PATH="${PATH}:${POETRY_VENV}/bin"
# 设置工作目录并复制项目依赖文件
WORKDIR /app
COPY poetry.lock pyproject.toml ./
# 配置 poetry 并安装项目依赖
RUN poetry config virtualenvs.in-project true
RUN poetry install --no-root
# 复制项目文件和依赖
COPY . .
COPY --from=ffmpeg /FFmpeg-6.1.1 /FFmpeg-6.1.1
COPY --from=ffmpeg /root/bin/ffmpeg /usr/local/bin/ffmpeg
COPY --from=swagger-ui /usr/share/nginx/html/swagger-ui.css swagger-ui-assets/swagger-ui.css
COPY --from=swagger-ui /usr/share/nginx/html/swagger-ui-bundle.js swagger-ui-assets/swagger-ui-bundle.js
# 再次运行 poetry 安装以确保所有依赖都已安装
RUN poetry install
# 安装 PyTorch
RUN $POETRY_VENV/bin/pip install torch==1.13.0+cu117 -f https://download.pytorch.org/whl/torch
# 暴露服务端口
EXPOSE 9000
# 定义容器启动时执行的命令
CMD gunicorn --bind 0.0.0.0:9000 --workers 1 --timeout 0 app.webservice:app -k uvicorn.workers.UvicornWorker
第一阶段:构建 FFmpeg
-
FROM debian:bookworm-slim AS ffmpeg
:使用debian:bookworm-slim
作为基础镜像开始第一个构建阶段,并命名这个阶段为ffmpeg
。 -
安装构建依赖:
RUN export DEBIAN_FRONTEND=noninteractive
:设置环境变量,使 apt-get 在非交互模式下运行,避免安装过程中的提示。apt-get -qq update
:更新包列表,-qq
选项让更新过程尽可能静默。apt-get -qq install --no-install-recommends
:安装必要的包,不安装推荐的依赖包以减少镜像大小。- 包括
build-essential
、git
、pkg-config
、yasm
、ca-certificates
:这些都是编译 FFmpeg 所需的工具和库。 rm -rf /var/lib/apt/lists/*
:清理 apt 缓存以减小镜像大小。
-
RUN git clone https://github.com/FFmpeg/FFmpeg.git --depth 1 --branch n6.1.1 --single-branch /FFmpeg-6.1.1
:从 GitHub 克隆 FFmpeg 仓库的特定分支(n6.1.1),使用--depth 1
进行浅克隆以减少下载的数据量。 -
WORKDIR /FFmpeg-6.1.1
:将工作目录切换到 FFmpeg 源码目录。 -
编译安装 FFmpeg:
- 设置编译配置,禁用了一些不需要的功能以缩小最终安装的大小,并优化为静态链接。
- 使用
make -j$(nproc)
并行编译 FFmpeg,以加速编译过程。 make install
将编译好的程序安装到指定目录。hash -r
刷新 shell 的命令缓存。
第二阶段:准备 Swagger UI
FROM swaggerapi/swagger-ui:v5.9.1 AS swagger-ui
:这是一个新的阶段,使用 Swagger UI 的官方镜像作为基础,用于提供 API 文档界面。
第三阶段:应用程序与 FFmpeg 集成
-
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
:这是最终构建阶段的基础镜像,使用 NVIDIA CUDA 的镜像来支持 GPU 加速。这意味着基础镜像是nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
,而不是debian:bookworm-slim
。 -
安装 Python 环境和依赖:
- 类似于第一阶段,设置非交互模式,更新包列表,并安装 Python 以及相关工具。
- 创建和配置 Python 虚拟环境,安装 Poetry 作为依赖管理工具。
-
配置工作目录
/app
并安装 Python 依赖。 -
从之前的阶段复制必要的文件和目录到当前镜像中,包括 FFmpeg 可执行文件和 Swagger UI 静态文件。
-
安装额外的 Python 依赖,包括 PyTorch。
-
配置容器启动时运行的命令,使用 gunicorn 启动 Web 服务。
修改 Dockerfile
针对本地机器环境,修改 Dockerfile 文件内容,
- 本地机器环境,输入命令 nvidia-smi
查询显卡驱动信息,当前 Nvidia 驱动版本为 515.48.07 ,CUDA 版本最高可以支持到 11.7 ,whisper-asr-webservice 仓库 v1.3.0 版本原来的 Dockerfile 文件使用的基础镜像为 nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04
,修改为 nvidia/cuda:11.7.1-cudnn8-runtime-ubuntu22.04
。
- 在构建的镜像中包含 net-tools
、curl
、wget
、vim
、iputils-ping
等工具,方便容器启动后的开发调试。
修改后的例子如下,
# 修改的部分
FROM nvidia/cuda:11.7.1-cudnn8-runtime-ubuntu22.04
# 未修改的部分
ENV PYTHON_VERSION=3.10
ENV POETRY_VENV=/app/.venv
# 修改的部分
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt-get -qq update \
&& apt-get -qq install --no-install-recommends \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-venv \
python3-pip \
# ----- add software begin -----
curl \
wget \
vim \
net-tools \
iputils-ping \
# ----- add software end -----
&& rm -rf /var/lib/apt/lists/*
# 剩下的部分保持不变
# ...
修改 Dockerfile 后,需要重新构建镜像。
docker build -f Dockerfile.gpu -t whisper-asr-webservice-gpu:v1.3.0 .
镜像部署
docker save -o whisper_asr_webservice_gpu_v1.3.0.tar whisper-asr-webservice-gpu:v1.3.0
docker load --input whisper_asr_webservice_gpu_v1.3.0.tar
docker tag <IMAGE ID> whisper_asr_webservice_gpu:v1.3.0
docker run -d --gpus '"device=0"' -p 9000:9000 -e ASR_MODEL=base -e ASR_ENGINE=openai_whisper -e ASR_MODEL_PATH=/data/whisper -v /data/whisper:/data/whisper whisper_asr_webservice_gpu:v1.3.0
观察容器日志,
[2024-04-06 06:38:59 +0000] [27] [INFO] Starting gunicorn 21.2.0
[2024-04-06 06:38:59 +0000] [27] [INFO] Listening at: http://127.0.0.1:9000 (27)
[2024-04-06 06:38:59 +0000] [27] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2024-04-06 06:38:59 +0000] [28] [INFO] Booting worker with pid: 28
OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 64: Operation not permitted
OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max
[2024-04-06 06:38:59 +0000] [27] [ERROR] Worker (pid:28) was sent SIGINT!
[2024-04-06 06:38:59 +0000] [29] [INFO] Booting worker with pid: 29
OpenBLAS blas_thread_init: pthread_create failed for thread 1 of 64: Operation not permitted
OpenBLAS blas_thread_init: RLIMIT_NPROC -1 current, -1 max
[2024-04-06 06:38:59 +0000] [27] [ERROR] Worker (pid:29) was sent SIGINT!
结合 GPT 给出的排查步骤逐步验证,发现容器中的进程(例如 OpenBLAS 创建的线程)可能因为 Docker 的安全性配置(特别是 seccomp 默认策略)而受到限制,导致操作被禁止。尝试使用更宽松的安全选项启动容器,例如 --security-opt seccomp=unconfined
或 --privileged
。这些选项会降低容器的安全性,因此最好分析和定制 Seccomp 策略。
再次观察容器日志,成功启动了容器服务。
[2024-04-06 06:50:26 +0000] [27] [INFO] Starting gunicorn 21.2.0
[2024-04-06 06:50:26 +0000] [27] [INFO] Listening at: http://127.0.0.1:9000 (27)
[2024-04-06 06:50:26 +0000] [27] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2024-04-06 06:50:26 +0000] [28] [INFO] Booting worker with pid: 28
[2024-04-06 06:50:30 +0000] [28] [INFO] Started server process [28]
[2024-04-06 06:50:30 +0000] [28] [INFO] Waiting for application startup.
[2024-04-06 06:50:30 +0000] [28] [INFO] Application startup complete.
验证使用
这个项目提供了 2 个接口:
- /asr ,自动语音识别,上传语音或视频文件,输出文本。
- /detect-language ,检测语言,检测上传文件中使用的语言。仅处理前 30 秒。
自动语音识别服务 /asr
- encode: 识别之前通过 FFmpeg 编码音视频文件。
- task: transcribe ,(默认)任务,转写上传的文件,中文语音识别为中文文字,英文语音识别为英文文字;translate ,无论源文件中是什么语言,识别后翻译为英文再输出。
- language: 告诉接口源文件是什么语言,可不指定,可以自动识别出来,如果指定错了,输出的结果是不对的。
- initial_prompt: prompt 工程可以提高语音识别结果的准确性。
- word_timestamps: 单词级别的时间戳,在输出为 json 时起作用,可输出每个单词的开始时间、结束时间、识别正确的可能性。
- output: 输出格式。
- 可以启用语音活动检测(VAD),通过参数
vad_filter
过滤掉没有语音的音频部分(目前仅支持Faster Whisper
)。
请求 URL 查询参数如下:
Name | Values |
---|---|
audio_file | File |
encode | true (default) |
task | transcribe, translate |
language | en (default is auto recognition) |
initial_prompt | prompt |
word_timestamps | false (default) |
output | text (default), json, vtt, srtt, tsv |
使用 curl 的示例请求,
curl -X POST -H "content-type: multipart/form-data" -F "audio_file=@test.mp3" http://127.0.0.1:9000/asr?output=json
响应(JSON),
- text :包含完整的文字记录
- segments :每个分段包含一个条目。每个条目提供
timestamps
、transcript
、token ids
和word level
、timestamps
、其他元数据 - language :检测到或提供的语言(as a language code)
语言检测服务 /detect-language
检测上传文件中使用的语言,仅处理前 30 秒。返回包含以下字段的 json:
- detected_language: “english”
- language_code: “en”
使用 curl 的示例请求,
curl -X POST -H "content-type: multipart/form-data" -F "audio_file=@test.mp3" http://127.0.0.1:9000/detect-language
{"detected_language":"chinese","language_code":"zh"}
针对标点符号,和中英文混杂的情况,可以加入 prompt 提升识别的准确率:‘这是一段中文、英文混合的录音,输出请记得加标点符号。’
curl -X POST -H "content-type: multipart/form-data" -F "initial_prompt="这是一段中文、英文混合的录音,输出请记得加标点符号。"" -F "audio_file=@test.mp3" http://127.0.0.9:9000/asr > test.txt
base 模型对短音频的识别效果不错,一般模型越大,识别效果越好。
在看解决方案的过程中,我还发现一个好玩的语音日记应用 Alog ,只需要在想说话的时候,用它录音就行了,它会自动帮你把语音转化为文字,然后同时保存在手机里,转写服务支持 iOS 系统自带或者 Whisper ,自带的仅限于 1 分钟的音频时长。还可以开启 AI 总结功能,自定义提示词,对转写内容总结,输出一份标准日记。
微信公众号「padluo」,分享数据科学家的自我修养,既然遇见,不如一起成长。关注【数据分析】公众号,后台回复【文章】,获得整理好的【数据分析】文章全集。