You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Flask+Librosa转换音频采样率时内存占用过高问题求助

音频文件处理内存占用过高问题排查与解决方案

问题描述

我基于Flask + Nginx + Gunicorn开发了一个支持文件上传并转换音频采样率的Web服务。处理约40MB的MP3或WAV文件时,内存使用率急剧飙升,即使是AWS Lightsail(2GB内存)的配置,瞬时内存占用也会超出限制。

日志信息

  • Nginx error.log:upstream prematurely closed connection while reading response header from upstream
  • Sys.log:触发OOM(内存不足)并终止进程

目前仅能靠虚拟内存勉强处理单个文件,但需要支持多用户并发,因此必须解决内存占用问题。

内存使用记录(自定义memory_usage函数统计)

  • [#1] memory usage: 153.62500 MB
  • [#2] memory usage: 488.25391 MB
  • [#3] memory usage: 219.86719 MB
  • [#4] memory usage: 220.30078 MB

问题1:内存占用远超文件本身大小的原因

从内存记录的峰值(#2到#3阶段)来看,核心原因集中在音频处理的加载与转码环节:

  1. 压缩音频解码后的体积膨胀
    MP3是有损压缩格式,40MB的MP3解码为未压缩PCM数据后,体积会数倍增长。比如44.1kHz、16位立体声的音频,每分钟约占10MB内存,40MB的MP3对应的原始音频时长可能达到几十分钟,解码后内存占用自然会突破几百MB。
  2. librosa的默认数据格式额外占用内存
    librosa.load默认将音频加载为32位浮点型数组(dtype=np.float32),相比原始WAV的16位整数格式,数据量直接翻倍,进一步推高内存占用。
  3. 中间环节的重复内存加载
    • 处理MP3时,pydub会先将整个音频加载到内存完成转码,转码过程中同时存在压缩版和未压缩版的音频数据。
    • librosa.get_samplerate和librosa.load两次读取文件,底层可能会重复加载部分文件内容。
  4. 内存回收不及时
    代码中仅删除了磁盘文件,但内存中的对象(如audSeg)未显式释放,Python垃圾回收机制的延迟会导致内存占用持续偏高。

问题2:解决方案

一、优化音频处理流程,减少内存占用

  1. 用ffmpeg替代pydub转码,避免全量加载
    直接调用ffmpeg命令行完成转码与采样率转换,无需将整个音频加载到内存:

    ffmpeg -i input.mp3 -acodec pcm_s16le -ar {目标采样率} output.wav
    

    可以用Python的subprocess模块执行该命令,转码过程由ffmpeg高效处理,内存占用极低。

  2. 分块加载处理音频
    如果必须使用librosa,改用librosa.stream分块加载音频,避免一次性加载整个文件到内存:

    import numpy as np
    # 分块读取并处理
    with soundfile.SoundFile(save_location, mode='w', samplerate=target_sr, channels=1) as sf:
        for y, sr in librosa.stream(src, block_length=2048, hop_length=512):
            # 处理单块音频数据
            processed_block = y.astype(np.int16)
            sf.write(processed_block)
    
  3. 降低音频数据精度
    调用librosa.load时指定dtype=np.int16,与原始WAV格式一致,直接减少一半内存占用:

    audio_data, sr = librosa.load(src, sr=sr//6, dtype=np.int16)
    
  4. 强制释放内存
    处理完临时对象后显式清空并触发垃圾回收:

    audSeg = AudioSegment.from_mp3(src)
    audSeg.export(src, format="wav")
    audSeg = None  # 释放对象引用
    import gc
    gc.collect()  # 强制回收内存
    

二、优化Web服务配置

  1. 限制Gunicorn工作进程数
    2GB内存服务器建议设置--workers=2,同时开启进程重启机制防止内存泄漏:

    gunicorn --workers=2 --max-requests=100 --max-requests-jitter=20 app:create_app()
    
  2. 调整Nginx参数适配大文件
    增大上传文件限制与超时时间,避免因处理超时导致连接中断:

    http {
        client_max_body_size 60M;  # 适配40MB文件上传
        server {
            proxy_read_timeout 300s;  # 延长处理超时时间
            proxy_connect_timeout 300s;
        }
    }
    
  3. 强制上传文件写入磁盘
    配置Flask的MAX_CONTENT_LENGTH,确保大文件直接写入磁盘而非内存缓存:

    def create_app():
        app = Flask(__name__)
        app.config['MAX_CONTENT_LENGTH'] = 60 * 1024 * 1024  # 限制最大上传60MB
        # ... 其他配置
    

三、异步化任务处理

将音频转码任务剥离到后台,用Celery+Redis/RabbitMQ实现异步处理:

  • 用户上传文件后,返回任务ID,无需等待处理完成
  • 后台Worker完成转码后,通过邮件或前端通知用户下载
  • 这种方式能让Web进程快速响应请求,避免长时间占用内存,大幅提升并发支持能力

原始代码

import psutil
from flask import Flask, render_template, request, send_file
import librosa, soundfile

import librosa.display
from werkzeug.utils import secure_filename
from datetime import datetime
import os
import io
from pydub import AudioSegment

#memory
def memory_usage(message: str = 'debug'):
    # current process RAM usage
    p = psutil.Process()
    rss = p.memory_info().rss / 2 ** 20 # Bytes to MB
    print(f"[{message}] memory usage: {rss: 10.5f} MB")
    file = open(f"C:\\Users\\kimkunyu\\Desktop\\mmmmm.txt","a")
    file.write(f"[{message}] memory usage: {rss: 10.5f} MB")
    file.close()


def create_app():
    app = Flask(__name__)

    @app.route('/')
    def hello_pybo():
        return render_template('main.html')


    @app.route('/upload', methods=['POST', 'GET'])
    def upload():
        if request.method == 'POST':
            audio_data = request.files["lc"]
            memory_usage('#1')
            if audio_data:
                filename = secure_filename(audio_data.filename)
                src = f"pybo/sound/{filename}"
                audio_data.save(src)
                stem, fileExtension = os.path.splitext(filename)
                if fileExtension == '.mp3':
                    audSeg = AudioSegment.from_mp3(src)
                    ##
                    os.remove(src)
                    src = f"pybo/sound/{stem}" + '.wav'
                    audSeg.export(src, format="wav")

                sr = librosa.get_samplerate(src)
                memory_usage('#2')
                audio_data, sr = librosa.load(src, sr=sr // 6)
                # print(sr)
                # print(audio_data.shape)
                # print(audio_data.dtype)
                # print(len(audio_data))

                memory_usage('#3')
                new_filename = f'{filename.split(".")[0]}_{str(datetime.now())}.wav'
                new_filename = new_filename.replace(':', "_")
                os.remove(src)

                save_location = f'pybo/output/{new_filename}'

                soundfile.write(save_location, audio_data, sr, format='WAV')
                memory_usage('#4')

                return render_template("download.html", save_location=save_location)


    @app.route('/download', methods=['POST', 'GET'])
    def download():
        save_location = request.form["save_location"]
        with open(save_location, 'rb') as fo:
            return_data = io.BytesIO()
            memory_usage('#5')

            return_data.write(fo.read())
            memory_usage('#6')
        # (after writing, cursor will be at last byte, so move it to start)
        return_data.seek(0)

        os.remove(save_location)
        return send_file(return_data, as_attachment=True, download_name='converted_file.wav')
    return app

内容的提问来源于stack exchange,提问作者bbaro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 05:30:49