iOS项目集成静态构建FFmpeg:CLI接口与库调用方法问询
解决iOS集成FFmpeg后的命令式调用与API使用问题
我之前也折腾过这个预编译FFmpeg库,给你分享几个实用的思路,帮你快速实现和Windows/Android一致的功能:
一、直接复用FFmpeg命令行逻辑(和独立应用同款体验)
如果你想直接用熟悉的命令字符串来执行操作,其实可以把FFmpeg的命令行入口函数暴露出来调用:
- 确认库编译配置:首先检查你用的编译脚本是否启用了
ffmpeg工具组件——默认情况下kewlbear的脚本可能没开这个选项,需要重新运行脚本时,在build-ffmpeg.sh里确保添加了--enable-ffmpeg参数,这样才会包含命令行相关的代码。 - 封装命令调用函数:在iOS项目里写一个简单的OC/C wrapper,把OC的参数数组转换成C风格的argv,调用FFmpeg的主函数:
#include "ffmpeg.h" - (int)runFFmpegCommand:(NSArray<NSString *> *)arguments { int argc = (int)arguments.count + 1; char **argv = malloc(sizeof(char *) * argc); argv[0] = "ffmpeg"; // 模拟命令名,FFmpeg内部会用到 for (int i = 0; i < arguments.count; i++) { argv[i+1] = (char *)[arguments[i] UTF8String]; } // 调用FFmpeg命令行入口 int result = ffmpeg_main(argc, argv); free(argv); return result; }
调用示例:
NSArray *cmdArgs = @["-i", inputFilePath, "-c:v", "libx264", "-crf", "23", outputFilePath]; int ret = [self runFFmpegCommand:cmdArgs]; if (ret == 0) { NSLog(@"命令执行成功"); } else { NSLog(@"命令执行失败,错误码:%d", ret); }
二、直接调用FFmpeg原生API(无需命令行)
如果想更灵活地控制流程(比如实时处理、自定义回调),直接用FFmpeg的原生API是更好的选择——这也是你在Windows/Android上实现功能的底层逻辑,跨平台的API用法基本一致:
核心的API模块包括:
avformat:处理媒体文件的封装/解封装(打开、读取、写入文件)avcodec:处理音视频编解码avfilter:处理滤镜效果(裁剪、转码、水印等)swscale:处理图像格式转换与缩放
举个简单的转码流程伪代码(带基础错误处理):
#include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> int transcodeVideo(const char *inputPath, const char *outputPath) { // 1. 注册所有组件 av_register_all(); avfilter_register_all(); // 2. 打开输入文件 AVFormatContext *inputCtx = NULL; if (avformat_open_input(&inputCtx, inputPath, NULL, NULL) != 0) { fprintf(stderr, "无法打开输入文件\n"); return -1; } if (avformat_find_stream_info(inputCtx, NULL) < 0) { fprintf(stderr, "无法获取流信息\n"); avformat_close_input(&inputCtx); return -1; } // 3. 找到视频流索引 int videoStreamIdx = av_find_best_stream(inputCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (videoStreamIdx < 0) { fprintf(stderr, "未找到视频流\n"); avformat_close_input(&inputCtx); return -1; } // 4. 初始化解码器 AVCodecParameters *codecPar = inputCtx->streams[videoStreamIdx]->codecpar; AVCodec *decoder = avcodec_find_decoder(codecPar->codec_id); AVCodecContext *decoderCtx = avcodec_alloc_context3(decoder); avcodec_parameters_to_context(decoderCtx, codecPar); if (avcodec_open2(decoderCtx, decoder, NULL) != 0) { fprintf(stderr, "无法打开解码器\n"); avcodec_free_context(&decoderCtx); avformat_close_input(&inputCtx); return -1; } // 5. 初始化输出格式与编码器 AVFormatContext *outputCtx = NULL; if (avformat_alloc_output_context2(&outputCtx, NULL, NULL, outputPath) < 0) { fprintf(stderr, "无法初始化输出上下文\n"); avcodec_free_context(&decoderCtx); avformat_close_input(&inputCtx); return -1; } AVStream *outputStream = avformat_new_stream(outputCtx, NULL); AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *encoderCtx = avcodec_alloc_context3(encoder); // 配置编码器参数(根据你的需求调整) encoderCtx->width = decoderCtx->width; encoderCtx->height = decoderCtx->height; encoderCtx->pix_fmt = AV_PIX_FMT_YUV420P; encoderCtx->bit_rate = 5000000; // 5Mbps encoderCtx->time_base = av_inv_q(inputCtx->streams[videoStreamIdx]->r_frame_rate); encoderCtx->framerate = inputCtx->streams[videoStreamIdx]->r_frame_rate; if (avcodec_open2(encoderCtx, encoder, NULL) != 0) { fprintf(stderr, "无法打开编码器\n"); avcodec_free_context(&encoderCtx); avformat_free_context(outputCtx); avcodec_free_context(&decoderCtx); avformat_close_input(&inputCtx); return -1; } avcodec_parameters_from_context(outputStream->codecpar, encoderCtx); // 6. 打开输出文件并写入头信息 if (avio_open(&outputCtx->pb, outputPath, AVIO_FLAG_WRITE) < 0) { fprintf(stderr, "无法打开输出文件\n"); avcodec_free_context(&encoderCtx); avformat_free_context(outputCtx); avcodec_free_context(&decoderCtx); avformat_close_input(&inputCtx); return -1; } if (avformat_write_header(outputCtx, NULL) < 0) { fprintf(stderr, "无法写入输出头\n"); avio_close(outputCtx->pb); avcodec_free_context(&encoderCtx); avformat_free_context(outputCtx); avcodec_free_context(&decoderCtx); avformat_close_input(&inputCtx); return -1; } // 7. 循环处理:读取->解码->编码->写入 AVPacket pkt; AVFrame *frame = av_frame_alloc(); while (av_read_frame(inputCtx, &pkt) == 0) { if (pkt.stream_index == videoStreamIdx) { if (avcodec_send_packet(decoderCtx, &pkt) == 0) { while (avcodec_receive_frame(decoderCtx, frame) == 0) { if (avcodec_send_frame(encoderCtx, frame) == 0) { AVPacket outputPkt = {0}; while (avcodec_receive_packet(encoderCtx, &outputPkt) == 0) { // 调整时间基 av_packet_rescale_ts(&outputPkt, encoderCtx->time_base, outputStream->time_base); av_interleaved_write_frame(outputCtx, &outputPkt); av_packet_unref(&outputPkt); } } } } } av_packet_unref(&pkt); } // 8. 写入尾信息并释放资源 av_write_trailer(outputCtx); avio_close(outputCtx->pb); av_frame_free(&frame); avcodec_free_context(&encoderCtx); avcodec_free_context(&decoderCtx); avformat_free_context(outputCtx); avformat_close_input(&inputCtx); return 0; }
你可以把这个C函数封装成OC方法,方便在iOS项目里调用,同时记得添加完整的错误处理和内存管理。
三、获取FFmpeg原生API的参考文档
- 官方Doxygen文档:这是最权威的参考,里面详细说明了每个API的参数、返回值和使用场景,覆盖了所有核心模块。
- 开源项目参考:比如ijkplayer(B站的开源播放器),它在iOS上深度封装了FFmpeg,你可以参考它的源码学习如何在移动端优雅地调用FFmpeg API。
- FFmpeg官方示例:官方仓库里有很多example代码(比如
doc/examples目录下的转码、滤镜示例),可以直接拿来参考适配iOS平台。
最后提醒一句:iOS上使用FFmpeg要注意协议合规(这个库是LGPL协议),如果是静态链接,需要确保你的代码符合协议要求,避免审核问题。
内容的提问来源于stack exchange,提问作者deek0146




