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

Android JNI C++内存泄漏求助:FFmpeg音频波形生成内存过高

Android JNI FFmpeg音频波形生成内存泄漏排查

开发Android平台JNI C++代码时遭遇内存泄漏,该代码基于FFmpeg读取音频样本并生成波形,内存占用随时间持续攀升至1GB,尝试ReleaseStringUTFChars、PopLocalFrame等操作后问题仍未解决。

原代码

extern "C"
JNIEXPORT jint JNICALL
Java_modules_Waveform_decode_1to_1pcm(JNIEnv *env, jobject thiz, jstring input, jstring output) {
    const char * filename = (*env).GetStringUTFChars(input, 0);
    const char * outfilename = (*env).GetStringUTFChars(output, 0);
        if((*env).PushLocalFrame(1) != JNI_OK) {
            __android_log_print(ANDROID_LOG_ERROR, "exception: ", "%s", "Failed to open capacity");
            return -1;
        }
        /* Open File */
        AVFormatContext * format_ctx{nullptr};
        int result_open = avformat_open_input(&format_ctx, filename, nullptr, nullptr);

        result_open = avformat_find_stream_info(format_ctx, nullptr);

        int index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);

        /* Finding decoder */
        AVStream *streams = format_ctx->streams[index];
        const AVCodec *decoder = avcodec_find_decoder(streams->codecpar->codec_id);

        AVCodecContext *codec_ctx{avcodec_alloc_context3(decoder)};

        avcodec_parameters_to_context(codec_ctx, streams->codecpar->codec_id);

        /* Opening decoder */
        result_open = avcodec_open2(codec_ctx, decoder, nullptr);

        /* Decoding the audio */
        AVPacket *packet = av_packet_alloc();
        AVFrame *frame = av_frame_alloc();

        SwrContext *resampler{swr_alloc_set_opts(
                nullptr,
                streams->codecpar->channel_layout,
                AV_SAMPLE_FMT_FLT,
                streams->codecpar->sample_rate,
                streams->codecpar->channel_layout,
                (AVSampleFormat) streams->codecpar->format,
                streams->codecpar->format,
                streams->codecpar->sample_rate,
                0
        )};

        std::ofstream out(outfilename, std::ios::binary);
        while (av_read_frame(format_ctx, packet) == 0) {
            if(packet->stream_index != streams->index) {
                continue;
            }

            result_open = avcodec_send_packet(codec_ctx, packet);
            if(result_open < 0) {
                // AVERROR(EAGAIN) --> Send the packet again getting frames out!
                if(result_open != AVERROR(EAGAIN)) {
                    __android_log_print(ANDROID_LOG_ERROR, "exception: ", "%s", "Error decoding...");
                }
            }
            while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                /* Resample the frame */
                AVFrame *resampler_frame = av_frame_alloc();
                resampler_frame->sample_rate = 100;
                resampler_frame->channel_layout = frame->channel_layout;
                resampler_frame->channels = frame->channels;
                resampler_frame->format = AV_SAMPLE_FMT_S16;

                result_open = swr_convert_frame(resampler, resampler_frame, frame);
                if(result_open >= 0) {
                    int16_t *samples = (int16_t *) frame->data[0];
                    for(int c = 0; c < resampler_frame->channels; c ++) {
                        float sum = 0;
                        for(int i = 0; i < resampler_frame->nb_samples; i ++) {
                            if(samples[i * resampler_frame->channels + c] < 0) {
                                sum += (float) samples[i * resampler_frame->channels + c] * (-1);
                            } else {
                                sum += (float) samples[i * resampler_frame->channels + c];
                            }
                            int average_point = (int) ((sum * 2) / (float) resampler_frame->nb_samples);
                            if(average_point > 0) {
                                out << average_point << "\n";
                            }
                        }
                    }
                }
                av_frame_unref(frame);
                av_frame_free(&resampler_frame);
            }
        }
        av_frame_free(&frame);
        av_packet_unref(packet);
        av_packet_free(&packet);
        out.close();
        (*env).PopLocalFrame(nullptr);
        (*env).ReleaseStringUTFChars(input, filename);
        (*env).ReleaseStringUTFChars(output, outfilename);
    return 1;
}

内存泄漏点定位

  • AVFormatContext未释放:avformat_open_input初始化的format_ctx,未调用avformat_close_input(&format_ctx)关闭输入,也未调用avformat_free_context(format_ctx)释放上下文内存,这是内存占用攀升的核心原因之一。
  • AVCodecContext未释放:avcodec_alloc_context3分配的codec_ctx,未调用avcodec_free_context(&codec_ctx)释放解码器上下文,解码器资源占用较大。
  • SwrContext未释放:swr_alloc_set_opts创建的重采样上下文resampler,未调用swr_free(&resampler)回收,重采样相关内存持续占用。
  • 跳过的AVPacket未释放:当packet->stream_index != streams->index时直接continue,未调用av_packet_unref(packet)释放该包的内存,导致非音频流的数据包内存泄漏。
  • 解码失败的AVPacket未释放:avcodec_send_packet返回非AVERROR(EAGAIN)错误时,未调用av_packet_unref(packet)释放数据包,造成泄漏。
  • 错误路径资源未清理:代码中未对avformat_open_input、avformat_find_stream_info、avcodec_open2等关键步骤的失败情况做资源回滚,一旦某步失败,已分配的资源会全部泄漏。

修复建议

  1. 在函数末尾或错误路径中,依次释放所有FFmpeg资源:
    // 释放重采样上下文
    if (resampler) swr_free(&resampler);
    // 释放解码器上下文
    if (codec_ctx) avcodec_free_context(&codec_ctx);
    // 关闭并释放格式上下文
    if (format_ctx) {
        avformat_close_input(&format_ctx);
        avformat_free_context(format_ctx);
    }
    
  2. 处理跳过的数据包时添加av_packet_unref(packet):
    if(packet->stream_index != streams->index) {
        av_packet_unref(packet);
        continue;
    }
    
  3. avcodec_send_packet失败时,非EAGAIN场景下释放数据包:
    result_open = avcodec_send_packet(codec_ctx, packet);
    if(result_open < 0) {
        if(result_open != AVERROR(EAGAIN)) {
            __android_log_print(ANDROID_LOG_ERROR, "exception: ", "%s", "Error decoding...");
        }
        av_packet_unref(packet); // 释放数据包
    }
    
  4. 为每个关键步骤添加错误处理,失败时立即清理已分配资源并返回。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 13:05:09