FFmpeg 库组成
首先,FFmpeg 是一个模块化的多媒体框架,主要由以下几个库组成:
- libavcodec:提供编解码器(编码/解码视频和音频)。
- libavformat:处理多媒体容器格式(如 MP4、AVI)的封装和解封装。
- libavdevice:提供输入输出设备支持(如摄像头、麦克风)。
- libavutil:工具库,包含内存管理、数学运算、数据结构等辅助功能。
- libswscale:用于图像格式转换、缩放、色彩空间转换。
我们的程序用到了所有这些库,下面按照代码执行顺序解释每个 API 的作用。
1. 初始化与设备注册
avdevice_register_all();
- 作用:注册所有可用的输入输出设备。对于使用摄像头(v4l2)等设备,必须先调用此函数,否则无法找到设备格式。
2. 设置输入参数
AVDictionary* input_options = nullptr;
av_dict_set(&input_options, "video_size", "640x480", 0);
av_dict_set(&input_options, "framerate", "30", 0);
av_dict_set(&input_options, "pixel_format", "yuyv422", 0);
AVDictionary是 FFmpeg 中用于传递键值对选项的通用结构。av_dict_set用于设置选项,比如我们希望摄像头输出 640x480 分辨率、30 帧/秒、yuyv422 像素格式。
3. 查找输入格式并打开设备
const AVInputFormat* input_format = av_find_input_format("v4l2");
av_find_input_format根据名称(如 “v4l2”)查找对应的输入格式描述符,返回一个AVInputFormat指针,后续打开设备时需要用到。
AVFormatContext* input_ctx = nullptr;
int ret = avformat_open_input(&input_ctx, "/dev/video2", input_format, &input_options);
avformat_open_input打开输入设备(或文件)。它根据提供的格式和设备路径,初始化AVFormatContext。如果成功,input_ctx将包含输入流的元信息。选项字典中的参数(如分辨率)在此阶段传递给设备驱动。
4. 获取流信息
avformat_find_stream_info(input_ctx, nullptr);
avformat_find_stream_info从输入中读取一部分数据,探测并填充各个流的详细信息(如编解码器类型、参数等)。它会填充input_ctx->streams数组,每个元素代表一个流(视频、音频等)。
5. 查找视频流
for (...) {
if (par->codec_type == AVMEDIA_TYPE_VIDEO) { ... }
}
- 遍历所有流,找到类型为
AVMEDIA_TYPE_VIDEO的流,记录其索引。后续操作将针对这个视频流。
6. 准备解码器
const AVCodec* decoder = avcodec_find_decoder(input_codecpar->codec_id);
avcodec_find_decoder根据编码器 ID(如AV_CODEC_ID_YUYV422)找到对应的解码器。摄像头输出的原始格式(yuyv422)在 FFmpeg 中也是一个编码器 ID,需要解码才能得到可处理的帧。
AVCodecContext* decoder_ctx = avcodec_alloc_context3(decoder);
avcodec_parameters_to_context(decoder_ctx, input_codecpar);
avcodec_alloc_context3分配解码器上下文,用于保存解码过程中的参数和状态。avcodec_parameters_to_context将从流中获取的编解码参数(AVCodecParameters)复制到解码器上下文中。
avcodec_open2(decoder_ctx, decoder, nullptr);
avcodec_open2真正打开解码器,初始化内部资源。之后就可以使用此上下文进行解码。
7. 设置输出(MP4 文件)
AVFormatContext* output_ctx = nullptr;
avformat_alloc_output_context2(&output_ctx, nullptr, nullptr, "output.mp4");
avformat_alloc_output_context2创建输出上下文,根据文件名后缀自动推断输出格式(如 MP4)。output_ctx将用于管理输出文件。
8. 准备编码器(H.264)
const AVCodec* encoder = avcodec_find_encoder_by_name("libx264");
avcodec_find_encoder_by_name通过编码器名称(如 “libx264”)查找编码器。也可以使用avcodec_find_encoder(AV_CODEC_ID_H264)。
AVStream* output_stream = avformat_new_stream(output_ctx, encoder);
AVCodecContext* encoder_ctx = avcodec_alloc_context3(encoder);
avformat_new_stream在输出上下文中创建一个新的流,并与指定的编码器关联。avcodec_alloc_context3为编码器分配上下文。
// 设置编码参数
encoder_ctx->height = decoder_ctx->height;
encoder_ctx->width = decoder_ctx->width;
encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
encoder_ctx->time_base = (AVRational){1, 30};
encoder_ctx->framerate = (AVRational){30, 1};
encoder_ctx->bit_rate = 400000;
- 设置编码器的参数:分辨率、像素格式、时间基、帧率、比特率等。时间基
time_base用于表示时间戳的单位,这里设为 1/30 秒。
av_dict_set(&encoder_options, "preset", "ultrafast", 0);
av_dict_set(&encoder_options, "tune", "zerolatency", 0);
avcodec_open2(encoder_ctx, encoder, &encoder_options);
- 使用字典设置编码器私有选项(如 x264 的 preset 和 tune),然后打开编码器。
avcodec_parameters_from_context(output_stream->codecpar, encoder_ctx);
output_stream->time_base = encoder_ctx->time_base;
- 将编码器上下文的参数复制到输出流的
codecpar中,这样输出文件才能正确描述视频流。 - 设置输出流的时间基与编码器一致。
9. 打开输出文件
if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_open(&output_ctx->pb, output_filename, AVIO_FLAG_WRITE);
}
- 如果输出格式需要文件 IO(MP4 需要),则调用
avio_open打开文件,准备写入。
10. 初始化图像转换(SWS)
SwsContext* sws_ctx = sws_getContext(
decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt,
encoder_ctx->width, encoder_ctx->height, encoder_ctx->pix_fmt,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
sws_getContext创建并初始化一个图像转换上下文。它描述了从输入格式(解码后的 yuyv422)到输出格式(编码器需要的 YUV420P)的转换参数。后续的sws_scale将使用这个上下文进行转换。
11. 分配帧缓冲区
AVFrame* yuv_frame = av_frame_alloc();
yuv_frame->format = encoder_ctx->pix_fmt;
yuv_frame->width = encoder_ctx->width;
yuv_frame->height = encoder_ctx->height;
av_frame_get_buffer(yuv_frame, 0);
av_frame_alloc分配一个空的AVFrame结构体。av_frame_get_buffer为帧分配实际的数据缓冲区。这里我们为转换后的 YUV420P 帧分配内存。
12. 写入文件头
avformat_write_header(output_ctx, nullptr);
avformat_write_header写入输出文件的头部信息(如 MP4 的 moov 等元数据)。必须在写入任何数据包之前调用。
13. 主循环:读取、解码、转换、编码、写入
读取数据包
av_read_frame(input_ctx, input_pkt);
av_read_frame从输入中读取下一个数据包(AVPacket)。对于摄像头,每个包通常包含一帧图像数据(压缩或原始)。
解码
avcodec_send_packet(decoder_ctx, input_pkt);
avcodec_receive_frame(decoder_ctx, decoded_frame);
avcodec_send_packet向解码器发送一个压缩数据包。avcodec_receive_frame从解码器接收解码后的原始帧。这两个函数是 FFmpeg 3.0 之后推荐的编解码 API,采用异步方式,可能需要多次调用才能获得所有输出帧。
图像格式转换
sws_scale(sws_ctx,
decoded_frame->data, decoded_frame->linesize,
0, decoder_ctx->height,
yuv_frame->data, yuv_frame->linesize);
sws_scale执行实际的图像转换。它将decoded_frame中的数据按照sws_ctx中定义的规则转换后填入yuv_frame。
编码
avcodec_send_frame(encoder_ctx, yuv_frame);
avcodec_receive_packet(encoder_ctx, output_pkt);
avcodec_send_frame向编码器发送一帧原始图像。avcodec_receive_packet从编码器接收压缩后的数据包。同样需要循环直到返回EAGAIN或EOF。
写入文件
av_packet_rescale_ts(output_pkt, encoder_ctx->time_base, output_stream->time_base);
av_interleaved_write_frame(output_ctx, output_pkt);
av_packet_rescale_ts将包的时间戳从编码器的时间基转换到输出流的时间基。av_interleaved_write_frame将数据包写入输出文件。它会负责交错(interleaving)不同流的数据包,确保文件格式正确。
14. 清理与收尾
当用户停止录制(收到信号)后,需要:
- Flush 编码器:调用
avcodec_send_frame(encoder_ctx, nullptr)并循环接收剩余包。 - 写入文件尾:
av_write_trailer(output_ctx)写入文件末尾的元数据(如 MP4 的索引信息),使文件完整可播。 - 释放所有资源:使用对应的 free 函数:
av_packet_free、av_frame_free、sws_freeContext、avcodec_free_context、avformat_close_input、avio_closep等。
数据流总结
- 摄像头(V4L2)→ 原始包(
AVPacket,格式 yuyv422) - 解码 → 原始帧(
AVFrame,格式 yuyv422) - 图像转换 → 目标帧(
AVFrame,格式 yuv420p) - 编码 → 压缩包(
AVPacket,H.264) - 封装 → MP4 文件
进一步学习建议
- 官方文档:FFmpeg Doxygen 是最终的参考。
- 示例代码:FFmpeg 源码的
doc/examples目录下有大量示例,如demuxing_decoding.c、muxing.c等,非常值得阅读。 - 书籍:《FFmpeg从入门到精通》或《FFmpeg Basics》可以帮助系统学习。
- 实践:尝试修改代码,比如录制音频、调整编码参数、支持更多输入格式等。
demo
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <atomic>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavutil/error.h>
#include <libswscale/swscale.h>
}
std::atomic<bool> is_recording(true);
void signal_handler(int sig) {
if (sig == SIGINT) {
std::cout << "\n收到停止信号,正在结束录制..." << std::endl;
is_recording = false;
}
}
int main() {
// 注册信号处理
signal(SIGINT, signal_handler);
avdevice_register_all();
const char* input_device = "/dev/video2";
const char* input_format_name = "v4l2";
AVDictionary* input_options = nullptr;
av_dict_set(&input_options, "video_size", "640x480", 0);
av_dict_set(&input_options, "framerate", "30", 0);
av_dict_set(&input_options, "pixel_format", "yuyv422", 0);
av_dict_set(&input_options, "noblock", "1", 0); // 非阻塞模式
AVInputFormat* input_format = av_find_input_format(input_format_name);
if (!input_format) {
std::cerr << "无法找到输入格式: " << input_format_name << std::endl;
return -1;
}
AVFormatContext* input_ctx = nullptr;
int ret = avformat_open_input(&input_ctx, input_device, input_format, &input_options);
if (ret != 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "无法打开摄像头设备: " << input_device << ",错误: " << errbuf << std::endl;
return -1;
}
ret = avformat_find_stream_info(input_ctx, nullptr);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "avformat_find_stream_info 失败: " << errbuf << std::endl;
return -1;
}
std::cout << "成功获取流信息,共有 " << input_ctx->nb_streams << " 个流" << std::endl;
int video_stream_index = -1;
for (unsigned int i = 0; i < input_ctx->nb_streams; i++) {
AVCodecParameters* par = input_ctx->streams[i]->codecpar;
std::cout << "流 " << i << " 类型: " << av_get_media_type_string(par->codec_type) << std::endl;
if (par->codec_type == AVMEDIA_TYPE_VIDEO) {
video_stream_index = i;
break;
}
}
if (video_stream_index == -1) {
std::cerr << "未找到视频流" << std::endl;
return -1;
}
std::cout << "视频流索引: " << video_stream_index << std::endl;
AVCodecParameters* input_codecpar = input_ctx->streams[video_stream_index]->codecpar;
const AVCodec* decoder = avcodec_find_decoder(input_codecpar->codec_id);
if (!decoder) {
std::cerr << "找不到解码器,codec_id: " << input_codecpar->codec_id << std::endl;
return -1;
}
AVCodecContext* decoder_ctx = avcodec_alloc_context3(decoder);
avcodec_parameters_to_context(decoder_ctx, input_codecpar);
ret = avcodec_open2(decoder_ctx, decoder, nullptr);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "无法打开解码器: " << errbuf << std::endl;
return -1;
}
std::cout << "解码器打开成功,输入格式: " << av_get_pix_fmt_name(decoder_ctx->pix_fmt)
<< ",分辨率: " << decoder_ctx->width << "x" << decoder_ctx->height << std::endl;
const char* output_filename = "output.mp4";
AVFormatContext* output_ctx = nullptr;
avformat_alloc_output_context2(&output_ctx, nullptr, nullptr, output_filename);
if (!output_ctx) {
std::cerr << "无法创建输出上下文" << std::endl;
return -1;
}
const AVCodec* encoder = avcodec_find_encoder_by_name("libx264");
if (!encoder) {
std::cerr << "找不到 H.264 编码器 (libx264)" << std::endl;
return -1;
}
AVStream* output_stream = avformat_new_stream(output_ctx, encoder);
AVCodecContext* encoder_ctx = avcodec_alloc_context3(encoder);
encoder_ctx->height = decoder_ctx->height;
encoder_ctx->width = decoder_ctx->width;
encoder_ctx->sample_aspect_ratio = decoder_ctx->sample_aspect_ratio;
encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
encoder_ctx->time_base = (AVRational){1, 30};
encoder_ctx->framerate = (AVRational){30, 1};
encoder_ctx->bit_rate = 400000;
AVDictionary* encoder_options = nullptr;
av_dict_set(&encoder_options, "preset", "ultrafast", 0);
av_dict_set(&encoder_options, "tune", "zerolatency", 0);
ret = avcodec_open2(encoder_ctx, encoder, &encoder_options);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "无法打开编码器: " << errbuf << std::endl;
return -1;
}
avcodec_parameters_from_context(output_stream->codecpar, encoder_ctx);
output_stream->time_base = encoder_ctx->time_base;
if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
ret = avio_open(&output_ctx->pb, output_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "无法打开输出文件: " << errbuf << std::endl;
return -1;
}
}
SwsContext* sws_ctx = sws_getContext(
decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt,
encoder_ctx->width, encoder_ctx->height, encoder_ctx->pix_fmt,
SWS_BILINEAR, nullptr, nullptr, nullptr
);
if (!sws_ctx) {
std::cerr << "sws_getContext 失败" << std::endl;
return -1;
}
AVFrame* yuv_frame = av_frame_alloc();
yuv_frame->format = encoder_ctx->pix_fmt;
yuv_frame->width = encoder_ctx->width;
yuv_frame->height = encoder_ctx->height;
ret = av_frame_get_buffer(yuv_frame, 0);
if (ret < 0) {
std::cerr << "无法分配帧缓冲区" << std::endl;
return -1;
}
ret = avformat_write_header(output_ctx, nullptr);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "写入文件头失败: " << errbuf << std::endl;
return -1;
}
std::cout << "开始录制,按 Ctrl+C 停止..." << std::endl;
AVPacket* input_pkt = av_packet_alloc();
AVPacket* output_pkt = av_packet_alloc();
int64_t frame_counter = 0;
int encoded_frame_count = 0;
int loop_counter = 0;
while (is_recording) {
loop_counter++;
if (loop_counter % 100 == 0) {
std::cout << "循环 " << loop_counter << " 次" << std::endl;
}
ret = av_read_frame(input_ctx, input_pkt);
if (ret < 0) {
if (ret == AVERROR(EAGAIN)) {
// 非阻塞模式下无数据,等待后继续
usleep(10000); // 10ms
continue;
}
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "av_read_frame 失败: " << errbuf << std::endl;
break;
}
if (input_pkt->stream_index == video_stream_index) {
ret = avcodec_send_packet(decoder_ctx, input_pkt);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "avcodec_send_packet 失败: " << errbuf << std::endl;
av_packet_unref(input_pkt);
continue;
}
while (true) {
AVFrame* decoded_frame = av_frame_alloc();
ret = avcodec_receive_frame(decoder_ctx, decoded_frame);
if (ret == AVERROR(EAGAIN)) {
av_frame_free(&decoded_frame);
break;
} else if (ret == AVERROR_EOF) {
av_frame_free(&decoded_frame);
break;
} else if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "avcodec_receive_frame 错误: " << errbuf << std::endl;
av_frame_free(&decoded_frame);
break;
}
decoded_frame->pts = frame_counter++;
sws_scale(sws_ctx,
decoded_frame->data, decoded_frame->linesize,
0, decoder_ctx->height,
yuv_frame->data, yuv_frame->linesize);
yuv_frame->pts = decoded_frame->pts;
ret = avcodec_send_frame(encoder_ctx, yuv_frame);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "avcodec_send_frame 失败: " << errbuf << std::endl;
av_frame_free(&decoded_frame);
break;
}
while (true) {
ret = avcodec_receive_packet(encoder_ctx, output_pkt);
if (ret == AVERROR(EAGAIN)) {
break;
} else if (ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "avcodec_receive_packet 错误: " << errbuf << std::endl;
break;
}
encoded_frame_count++;
std::cout << "写入包 #" << encoded_frame_count << ",大小: " << output_pkt->size << " 字节" << std::endl;
av_packet_rescale_ts(output_pkt, encoder_ctx->time_base, output_stream->time_base);
output_pkt->stream_index = output_stream->index;
ret = av_interleaved_write_frame(output_ctx, output_pkt);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
std::cerr << "av_interleaved_write_frame 失败: " << errbuf << std::endl;
}
av_packet_unref(output_pkt);
}
av_frame_free(&decoded_frame);
}
}
av_packet_unref(input_pkt);
}
std::cout << "录制结束,共编码 " << encoded_frame_count << " 帧" << std::endl;
// Flush 编码器
avcodec_send_frame(encoder_ctx, nullptr);
while (true) {
ret = avcodec_receive_packet(encoder_ctx, output_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
}
if (ret < 0) break;
av_packet_rescale_ts(output_pkt, encoder_ctx->time_base, output_stream->time_base);
output_pkt->stream_index = output_stream->index;
av_interleaved_write_frame(output_ctx, output_pkt);
av_packet_unref(output_pkt);
}
// 写入文件尾
av_write_trailer(output_ctx);
// 释放资源
av_packet_free(&input_pkt);
av_packet_free(&output_pkt);
av_frame_free(&yuv_frame);
sws_freeContext(sws_ctx);
avcodec_free_context(&decoder_ctx);
avcodec_free_context(&encoder_ctx);
if (output_ctx && !(output_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&output_ctx->pb);
}
avformat_free_context(output_ctx);
avformat_close_input(&input_ctx);
std::cout << "完成!" << std::endl;
return 0;
}
安装依赖:确保你的系统已经安装了 FFmpeg 开发库。
sudo apt update
sudo apt install ffmpeg libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev
编译代码:将上面的代码保存为 record_camera.cpp,然后使用以下命令编译。注意链接器需要找到所有 FFmpeg 的库。
g++ -o record_camera record_camera.cpp -lavcodec -lavformat -lavdevice -lavutil -lswscale
openssl:
url: ${git_url}/thdpty/openssl.git
branch: OpenSSL_1_1_1o
dependencies:
- arch_pattern: '*'
dependencies: []
commands:
configure:
# 核心修改1:按系统分支指定 command(Linux 用原逻辑,Windows 用 perl 调用)
command:
- arch_pattern: '*linux*' # Linux 系统保留原命令
command: ${PROJECT_SOURCE_DIR}/Configure
- arch_pattern: '*x86_64-windows*' # Windows 系统用 perl 调用 Configure
command: perl
- arch_pattern: '*' # 兜底分支(非windows/linux用原命令)
command: ${PROJECT_SOURCE_DIR}/Configure
arguments:
- arch_pattern: '*aarch64-linux*'
arguments:
- shared
- --prefix=${IMOBUILDER_TARGET_OUTPUT}/3rdparty
- --openssldir=${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- linux-aarch64
- '&&'
- perl
- configdata.pm
- -d
- arch_pattern: '*aarch64-qnx*'
arguments:
- shared
- --prefix=${IMOBUILDER_TARGET_OUTPUT}/3rdparty
- --openssldir=${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- linux-aarch64
- '&&'
- perl
- configdata.pm
- -d
- arch_pattern: '*x86_64-linux*'
arguments:
- shared
- --prefix=${IMOBUILDER_TARGET_OUTPUT}/3rdparty
- --openssldir=${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- linux-x86_64
- '&&'
- perl
- configdata.pm
- -d
# 核心修改2:修复缩进 + 正确配置 Windows 分支参数
- arch_pattern: '*x86_64-windows*'
arguments:
- ${PROJECT_SOURCE_DIR}/Configure # 传给 perl 的第一个参数(Configure 路径)
- shared
- --prefix=${IMOBUILDER_TARGET_OUTPUT}/3rdparty
- --openssldir=${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- VC-WIN64A # Windows 64位编译目标
- '&&'
- perl
- configdata.pm
- -d
- arch_pattern: '*'
arguments:
- shared
- --prefix=${IMOBUILDER_TARGET_OUTPUT}/3rdparty
- --openssldir=${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- linux-x86_64
- '&&'
- perl
- configdata.pm
- -d
build:
# 核心修改3:build 命令按系统分支区分(Linux=make,Windows=nmake)
command:
- arch_pattern: '*linux*'
command: make
- arch_pattern: '*x86_64-windows*'
command: nmake
- arch_pattern: '*'
command: make
arguments:
- arch_pattern: '*'
arguments: []
install:
# 核心修改4:install 命令按系统分支区分
command:
- arch_pattern: '*linux*'
command: make
- arch_pattern: '*x86_64-windows*'
command: nmake
- arch_pattern: '*'
command: make
arguments:
- arch_pattern: '*'
arguments:
- install
package:
manifest: null
files:
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/libcrypto*
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/libssl*
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/pkgconfig/openssl.pc
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/pkgconfig/libcrypto.pc
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/pkgconfig/libssl.pc
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/bin/openssl
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/bin/c_rehash
directories:
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/engines-1.1
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/lib/ssl
- ${IMOBUILDER_TARGET_OUTPUT}/3rdparty/include/openssl

4万+

被折叠的 条评论
为什么被折叠?



