基于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
相关产品推荐
相关产品推荐

