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

基于FFmpeg的JNI C++视频压缩包装器无效,输出与输入一致

FFmpeg JNI视频压缩无效问题修复

问题根源

当前代码本质是直接复制输入视频的数据包到输出文件,没有执行解码→重新编码的压缩流程,同时编码器参数设置逻辑存在错误,导致压缩配置完全失效:

  • 直接调用av_interleaved_write_frame写入原始输入包,未经过重新编码,输出与输入完全一致
  • 设置编码器参数后,又调用avcodec_parameters_copy覆盖为输入流参数,CRF等压缩配置无效
  • 未初始化编码器上下文,无法执行编码操作

修复方案

1. 实现完整编解码流水线

必须走「读取输入包→解码为AVFrame→像素格式转换→编码为输出包→写入输出文件」的完整流程,才能实现视频压缩。

2. 修复编码器参数配置逻辑

基于输入视频参数调整编码器配置,避免自定义参数被覆盖,确保CRF、比特率等压缩设置生效。

3. 正确初始化编码器

创建编码器上下文后,调用avcodec_open2打开编码器,确保编码功能可用。

修改后的核心代码

以下是重构后的compress函数(原copyAvFormatContext可删除):

void compress(const char *inputPath, const char *outputPath) {
    av_register_all();
    avcodec_register_all();
    avformat_network_init();
    av_log_set_level(AV_LOG_TRACE);
    av_log_set_callback(ffmpeg_log_callback);

    AVFormatContext *inputFormatContext = initInputFormatContext(inputPath);
    if (!inputFormatContext) {
        LOGE("input format context fail");
        return;
    }
    AVFormatContext *outputFormatContext = initOutputFormatContext(outputPath);
    if (!outputFormatContext) {
        LOGE("output format context fail");
        return;
    }

    // 定位视频流索引
    int videoStreamIdx = -1;
    for (int i = 0; i < inputFormatContext->nb_streams; i++) {
        if (inputFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStreamIdx = i;
            break;
        }
    }
    if (videoStreamIdx == -1) {
        LOGE("no video stream found");
        return;
    }

    // 初始化输入视频解码器
    AVCodecParameters *inputCodecParams = inputFormatContext->streams[videoStreamIdx]->codecpar;
    AVCodec *inputCodec = avcodec_find_decoder(inputCodecParams->codec_id);
    AVCodecContext *inputCodecCtx = avcodec_alloc_context3(inputCodec);
    avcodec_parameters_to_context(inputCodecCtx, inputCodecParams);
    if (avcodec_open2(inputCodecCtx, inputCodec, nullptr) < 0) {
        LOGE("open input codec fail");
        return;
    }

    // 创建输出视频流与H.264编码器
    AVCodec *outputCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    AVStream *outputVideoStream = avformat_new_stream(outputFormatContext, outputCodec);
    AVCodecContext *outputCodecCtx = avcodec_alloc_context3(outputCodec);

    // 配置编码器核心参数
    outputCodecCtx->codec_id = AV_CODEC_ID_H264;
    outputCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    outputCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; // H.264标准兼容格式
    outputCodecCtx->width = inputCodecCtx->width;
    outputCodecCtx->height = inputCodecCtx->height;
    outputCodecCtx->time_base = av_inv_q(inputFormatContext->streams[videoStreamIdx]->r_frame_rate);
    outputCodecCtx->framerate = inputFormatContext->streams[videoStreamIdx]->r_frame_rate;
    outputCodecCtx->bit_rate = 500000; // 目标比特率,可根据需求调整(单位:bps)
    av_opt_set(outputCodecCtx->priv_data, "crf", "30", 0); // CRF值越小质量越高、体积越大,范围0-51
    av_opt_set(outputCodecCtx->priv_data, "preset", "medium", 0); // 编码速度:ultrafast < medium < slow
    av_opt_set(outputCodecCtx->priv_data, "movflags", "faststart", 0); // 优化MP4网络播放

    // 同步编码器参数到输出流
    if (avcodec_parameters_from_context(outputVideoStream->codecpar, outputCodecCtx) < 0) {
        LOGE("cant set output codec params");
        return;
    }
    outputVideoStream->time_base = outputCodecCtx->time_base;

    // 打开输出编码器
    if (avcodec_open2(outputCodecCtx, outputCodec, nullptr) < 0) {
        LOGE("open output codec fail");
        return;
    }

    // 打开输出文件IO
    if (avio_open2(&outputFormatContext->pb, outputPath, AVIO_FLAG_WRITE, nullptr, nullptr) < 0) {
        LOGE("error to open file");
        return;
    }

    // 写入文件头
    if (avformat_write_header(outputFormatContext, nullptr) < 0) {
        LOGE("write header fail");
        return;
    }

    AVPacket *inputPacket = av_packet_alloc();
    AVFrame *inputFrame = av_frame_alloc();
    AVFrame *outputFrame = av_frame_alloc();
    AVPacket *outputPacket = av_packet_alloc();

    // 初始化输出帧缓冲区
    outputFrame->format = outputCodecCtx->pix_fmt;
    outputFrame->width = outputCodecCtx->width;
    outputFrame->height = outputCodecCtx->height;
    if (av_frame_get_buffer(outputFrame, 0) < 0) {
        LOGE("allocate output frame buffer fail");
        return;
    }

    // 创建图像格式转换上下文(处理输入帧到编码器要求格式的转换)
    struct SwsContext *swsCtx = sws_getContext(
        inputCodecCtx->width, inputCodecCtx->height, inputCodecCtx->pix_fmt,
        outputCodecCtx->width, outputCodecCtx->height, outputCodecCtx->pix_fmt,
        SWS_BILINEAR, nullptr, nullptr, nullptr
    );

    // 处理输入数据包
    while (av_read_frame(inputFormatContext, inputPacket) >= 0) {
        if (inputPacket->stream_index != videoStreamIdx) {
            // 非视频流(如音频)直接转发
            av_interleaved_write_frame(outputFormatContext, inputPacket);
            av_packet_unref(inputPacket);
            continue;
        }

        // 发送数据包到解码器
        if (avcodec_send_packet(inputCodecCtx, inputPacket) < 0) {
            LOGE("send packet to decoder fail");
            av_packet_unref(inputPacket);
            continue;
        }

        // 接收解码后的帧
        while (avcodec_receive_frame(inputCodecCtx, inputFrame) == 0) {
            // 转换像素格式到编码器要求的YUV420P
            sws_scale(swsCtx, inputFrame->data, inputFrame->linesize, 0, inputCodecCtx->height,
                      outputFrame->data, outputFrame->linesize);

            // 同步时间基
            outputFrame->pts = av_rescale_q(inputFrame->pts, inputCodecCtx->time_base, outputCodecCtx->time_base);

            // 发送帧到编码器
            if (avcodec_send_frame(outputCodecCtx, outputFrame) < 0) {
                LOGE("send frame to encoder fail");
                continue;
            }

            // 接收编码后的数据包并写入输出文件
            while (avcodec_receive_packet(outputCodecCtx, outputPacket) == 0) {
                outputPacket->stream_index = outputVideoStream->index;
                av_packet_rescale_ts(outputPacket, outputCodecCtx->time_base, outputVideoStream->time_base);
                if (av_interleaved_write_frame(outputFormatContext, outputPacket) < 0) {
                    LOGE("write encoded packet fail");
                }
                av_packet_unref(outputPacket);
            }
        }
        av_packet_unref(inputPacket);
    }

    // 发送编码器结束信号,处理剩余帧
    avcodec_send_frame(outputCodecCtx, nullptr);
    while (avcodec_receive_packet(outputCodecCtx, outputPacket) == 0) {
        outputPacket->stream_index = outputVideoStream->index;
        av_packet_rescale_ts(outputPacket, outputCodecCtx->time_base, outputVideoStream->time_base);
        av_interleaved_write_frame(outputFormatContext, outputPacket);
        av_packet_unref(outputPacket);
    }

    // 写入文件尾
    if (av_write_trailer(outputFormatContext) < 0) {
        LOGE("can not write trailer");
    }

    // 释放所有资源
    sws_freeContext(swsCtx);
    av_frame_free(&inputFrame);
    av_frame_free(&outputFrame);
    av_packet_free(&inputPacket);
    av_packet_free(&outputPacket);
    avcodec_close(inputCodecCtx);
    avcodec_close(outputCodecCtx);
    avformat_close_input(&inputFormatContext);
    avio_close(outputFormatContext->pb);
    avformat_free_context(outputFormatContext);
}

额外说明

  • 可以通过调整bit_rate或crf参数控制压缩程度:CRF值推荐范围23-35,值越小质量越高;比特率直接控制输出码率
  • 若需要处理音频流,可参考视频流的编解码逻辑添加音频处理分支
  • 新增了资源释放逻辑,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:07:00