资讯动态

FFmpeg 开源多媒体处理框架深度解析:从命令行到 libav* 全家桶

发布时间:2026/10/13 10:19:13 来源:尧图企业网站定制
适用人群C/C 开发者、音视频处理入门到进阶、工业数采项目中需要音视频能力的边缘设备开发者 技术栈C 语言实现提供 C API 与命令行双形态1. 背景视频格式的混乱是历史常态同一段视频由封装格式MP4/MKV/FLV/TS...和编码格式H.264/H.265/VP9/AV1...两个正交维度描述再加上音频编码AAC/MP3/Opus...、字幕、流媒体协议RTMP/HLS/DASH...的排列组合单靠手写解析器维护一个项目几乎是灾难。FFmpeg 之所以成为多媒体领域的事实标准在于它用一个统一框架吃下了这条长尾全格式覆盖数百种解封装/封装格式、编解码器、滤镜ffmpeg -formats 一屏都列不完跨平台纯 C 编写Windows/Linux/macOS/Android/iOS/嵌入式全平台可编译双形态命令行 ffmpeg 适合快速验证与批处理libavcodec / libavformat / libavfilter 等 C API 适合深度集成——OBS、VLC、Chrome、Android、几乎所有视频播放器的底层都在用它硬件加速生态NVENC/NVDEC、VAAPI、QSV、VideoToolbox、MediaCodec、AMF 全覆盖。对工业数采场景而言FFmpeg 的价值同样直接摄像头/网络流RTSP取流 → 转码/抽帧 → 本地落盘或推流上云是一条完整的视觉数据采集链路与传统的传感器点位数采互补。2. 架构全景FFmpeg 由一套共享基础设施 多个功能库 三个命令行工具组成。2.1 六大核心库库全称职责开发者最常用 APIlibavutilAV Util基础工具内存、日志、数学、像素/采样格式、时间基、AVFrame/AVPacket/AVBufferav_frame_alloc/ref/unref、av_log、av_rescale_q、AVRationallibavcodecAV Codec编解码器核心H.264/H.265/VP9/AV1/AAC/MP3... 与硬件加速avcodec_send_packet/receive_framelibavformatAV Format解封装/封装/协议MP4/MKV/FLV/TS/RTSP/HLS...avformat_open_input/read_frame/write_framelibavfilterAV Filter滤镜图缩放/裁剪/水印/转场/音频 EQ...avfilter_graph_*、av_buffersrc_add_framelibswscaleSW Scale像素格式与尺寸转换CPUsws_getContext/sws_scalelibswresampleSW Resample音频重采样与声道/采样率转换swr_alloc/swr_convert2.2 三件套工具ffmpeg转码/转封装/滤镜/推流主工具ffprobe探针工具读取媒体文件/流的元信息格式、编码、时长、帧率、码率调试时第一选择ffplay极简播放器快速预览。2.3 构建与安装# Windows推荐从 gyan.dev 或 BtbN 下载预编译包加入 PATH ffmpeg -version # Ubuntu sudo apt install ffmpeg # 从源码构建最小可用 ./configure --enable-shared --disable-everything --enable-decoderh264 --enable-demuxermov --enable-parserh264 make -j$(nproc)提示正式集成建议用 --disable-everything 按需裁剪组件二进制体积可从 100MB 降到几 MB这是嵌入式设备的标准做法。3. 核心概念这几个概念不搞懂写 C 代码必踩坑是 FFmpeg 的心智门槛。3.1 封装格式 vs 编码格式编码Codec像素/采样数据如何压缩。H.264 是编码器压缩的是帧。封装Container压缩后的数据如何打包成文件/流。MP4 是容器描述的是H.264 视频轨 AAC 音频轨 元数据怎么排布。ffprobe 输出中 codec_name 是编码format_name 是容器Input #0, mov,mp4,m4a,3gp,3g2,mj2, from input.mp4: Duration: 00:00:10.00, bitrate: 1000 kb/s Stream #0:0: Video: h264 (High) ... ← 编码 Stream #0:1: Audio: aac (LC) ... ← 编码上面的 mov,mp4,... 就是容器。3.2 时间基Time Base与 AVRationalFFmpeg 不用浮点秒表示时间而是用有理数时间基AVRational { num, den }即每秒 den 个 tick、每 tick 为 num/den 秒。AVRational tb {1, 90000}; // 90000Hz 时钟常见于 MPEG-TS double seconds pts * av_q2d(tb); // pts 数值 × 1/90000 秒关键坑不同层的时间基不一样——容器层AVStream.time_base、编解码器层AVCodecContext.time_base、滤镜层各有各的基。跨层换算必须用 av_rescale_q()禁止直接乘除。// 把 AVPacket.ptsstream 时间基换算成毫秒 int64_t ms av_rescale_q(pkt-pts, stream-time_base, (AVRational){1, 1000});3.3 PTS / DTSPTSPresentation Timestamp帧显示时间DTSDecode Timestamp帧解码顺序时间。B 帧存在时两者不同I/P 帧先解、B 帧后解但先显示因此DTS 单调不减PTS 不一定。B 帧常见于 H.264 High Profile流式转码如不需要 B 帧可用 -bf 0。3.4 滤镜图Filter Graph滤镜 对帧做变换的节点节点连成有向图。最简单的图是 scale1280:720复杂图如input(视频) → scale → watermark → output input(音频) → volume → output滤镜图在 libavfilter 中由 AVFilterGraph、AVFilterContext节点、AVFilterLink边表示数据通过 av_buffersrc_add_frame进与 av_buffersink_get_frame出流动。命令行里用逗号同一链和分号并行链分隔。3.5 AVFrame / AVPacket 与引用计数AVPacket编码后的数据压缩字节一头在解封装侧、一头在编码侧AVFrame解码后/编码前的数据原始像素/采样或解码器输出的 YUV 帧。两者都不是一次性 malloc 的结构而是引用计数的av_frame_ref() 只增加引用不拷贝数据av_frame_unref() 释放本引用数据在最后一个引用释放时才真正回收AVFrame *dst av_frame_alloc(); av_frame_ref(dst, src); // 浅拷贝 引用 1 // ... 使用 dst ... av_frame_unref(dst); // 引用 -1src 仍可用永远不要手工 free 帧数据这是内存管理的第一铁律。4. API 说明4.1 解封装libavformat核心 API// 1. 打开输入 AVFormatContext *fmt_ctx NULL; if (avformat_open_input(fmt_ctx, url, NULL, NULL) 0) { /* 失败 */ } // 2. 读取流信息流数量、时长、元数据 avformat_find_stream_info(fmt_ctx, NULL); // 3. 遍历流找到视频流 int v_idx av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 4. 循环读取 AVPacket AVPacket *pkt av_packet_alloc(); while (av_read_frame(fmt_ctx, pkt) 0) { if (pkt-stream_index v_idx) { /* 处理视频包 */ } av_packet_unref(pkt); } // 5. 收尾 avformat_close_input(fmt_ctx); av_packet_free(pkt);4.2 解码libavcodec核心 API新版 API 采用send/receive 推拉模型发送一个压缩包循环接收零到多个帧解码器有内部缓冲可能一次输入产出多帧、也可能需要多包才能出一帧。const AVCodec *codec avcodec_find_decoder(AV_CODEC_ID_H264); AVCodecContext *dec_ctx avcodec_alloc_context3(codec); // 关键把流的参数SPS/PPS、宽高、时间基拷给解码器 avcodec_parameters_to_context(dec_ctx, stream-codecpar); avcodec_open2(dec_ctx, codec, NULL); // 推拉循环 avcodec_send_packet(dec_ctx, pkt); // 输入压缩包 while (avcodec_receive_frame(dec_ctx, frame) 0) { // 拿到原始帧frame-data[0..3]、frame-linesize[0..3]YUV420 为例 // frame-pts 是解码器时间基下的显示时间 }avcodec_receive_frame 返回值语义0拿到一帧继续循环AVERROR(EAGAIN)当前输入还不够出帧需要继续喂包AVERROR_EOF输入结束解码器内部缓冲已全部冲刷。4.3 编码libavcodec核心 APIconst AVCodec *enc avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *enc_ctx avcodec_alloc_context3(enc); enc_ctx-width 1280; enc_ctx-height 720; enc_ctx-pix_fmt AV_PIX_FMT_YUV420P; enc_ctx-time_base (AVRational){1, 25}; // 帧率 25fps enc_ctx-framerate (AVRational){25, 1}; enc_ctx-bit_rate 2000000; // 2Mbps // x264 私有参数通过字典传递 av_opt_set(enc_ctx-priv_data, preset, fast, 0); avcodec_open2(enc_ctx, enc, NULL); // 推拉模型送原始帧收压缩包 avcodec_send_frame(enc_ctx, frame); while (avcodec_receive_packet(enc_ctx, pkt) 0) { // pkt 为编码后的压缩数据可写入容器 }4.4 封装输出libavformat核心 APIAVFormatContext *out_ctx NULL; avformat_alloc_output_context2(out_ctx, NULL, NULL, out.mp4); // 创建视频流 AVStream *out_stream avformat_new_stream(out_ctx, NULL); avcodec_parameters_from_context(out_stream-codecpar, enc_ctx); // 打开输出 avio_open(out_ctx-pb, out.mp4, AVIO_FLAG_WRITE); avformat_write_header(out_ctx, NULL); // 写包注意包的时间基要换算到流时间基 pkt-stream_index out_stream-index; av_packet_rescale_ts(pkt, enc_ctx-time_base, out_stream-time_base); av_interleaved_write_frame(out_ctx, pkt); // 交错写帧自动排序 // 收尾 av_write_trailer(out_ctx); avio_closep(out_ctx-pb); avformat_free_context(out_ctx);4.5 滤镜libavfilter最小骨架// 1. 建图 AVFilterGraph *graph avfilter_graph_alloc(); // 2. 建输入节点buffer AVFilterContext *src_ctx NULL; avfilter_graph_create_filter(src_ctx, avfilter_get_by_name(buffer), in, video_size1280x720:pix_fmtyuv420p:time_base1/25:frame_rate25/1, NULL, graph); // 3. 建输出节点buffersink AVFilterContext *sink_ctx NULL; avfilter_graph_create_filter(sink_ctx, avfilter_get_by_name(buffersink), out, NULL, NULL, graph); // 4. 中间滤镜scale AVFilterContext *scale_ctx NULL; avfilter_graph_create_filter(scale_ctx, avfilter_get_by_name(scale), scale, 640:360, NULL, graph); // 5. 连边in - scale - out avfilter_link(src_ctx, 0, scale_ctx, 0); avfilter_link(scale_ctx, 0, sink_ctx, 0); avfilter_graph_config(graph, NULL); // 6. 喂帧/收帧 av_buffersrc_add_frame_flags(src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); AVFrame *out av_frame_alloc(); av_buffersink_get_frame(sink_ctx, out);4.6 像素转换libswscale与音频重采样libswresample// 像素格式/尺寸转换如 YUV420P 1280x720 - RGB24 640x360 struct SwsContext *sws sws_getContext(1280, 720, AV_PIX_FMT_YUV420P, 640, 360, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL); sws_scale(sws, frame-data, frame-linesize, 0, 720, rgb_data, rgb_linesize); sws_freeContext(sws); // 音频重采样如 44100Hz 立体声 - 48000Hz 单声道 SwrContext *swr swr_alloc(); av_opt_set_int(swr, in_channel_layout, AV_CH_LAYOUT_STEREO, 0); av_opt_set_int(swr, out_channel_layout, AV_CH_LAYOUT_MONO, 0); av_opt_set_int(swr, in_sample_rate, 44100, 0); av_opt_set_int(swr, out_sample_rate, 48000, 0); av_opt_set_sample_fmt(swr, in_sample_fmt, AV_SAMPLE_FMT_FLTP, 0); av_opt_set_sample_fmt(swr, out_sample_fmt, AV_SAMPLE_FMT_FLT, 0); swr_init(swr); swr_convert(swr, out_data, out_nb_samples, in_data, in_nb_samples); swr_free(swr);4.7 错误处理FFmpeg 错误码统一为负的 AVERROR 值本质是 errno 的负映射。调试时把错误码转成可读文本char errbuf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(errcode, errbuf, sizeof(errbuf)); av_log(NULL, AV_LOG_ERROR, 错误: %s\n, errbuf);常见AVERROR(EAGAIN)、AVERROR_EOF、AVERROR(ENOMEM)、AVERROR_INVALIDDATA。5. 详细使用5.1 命令行实战高频场景# 转码H.264 视频 AAC 音频 封装为 MP4 ffmpeg -i input.mkv -c:v libx264 -preset fast -crf 23 -c:a aac -b:a 128k output.mp4 # 只换封装不重新编码秒级完成 ffmpeg -i input.mkv -c copy output.mp4 # 裁剪前 10 秒 ffmpeg -ss 00:00:05 -t 10 -i input.mp4 -c copy output.mp4 # 抽帧每 2 秒存一张 JPEG ffmpeg -i input.mp4 -vf fps1/2 frame_%03d.jpg # 缩放到 720p 并加水印 ffmpeg -i input.mp4 -vf scale-2:720,drawtexttextHello:x10:y10:fontsize24:fontcolorwhite output.mp4 # RTSP 摄像头取流实时转码推流到 RTMP ffmpeg -rtsp_transport tcp -i rtsp://user:pass192.168.1.10:554/stream \ -c:v libx264 -preset ultrafast -tune zerolatency -f flv rtmp://push.example.com/live/cam01 # HLS 直播切片3 秒一片 ffmpeg -i input.mp4 -c:v libx264 -hls_time 3 -hls_list_size 0 -f hls out.m3u8 # 音视频分离 ffmpeg -i input.mp4 -vn audio.aac -an video.mp4 # 注意-vn 去掉视频-an 去掉音频 # 视频转 GIF ffmpeg -i input.mp4 -vf fps10,scale320:-1 -loop 0 out.gif5.2 C 语言完整转码管线可编译骨架下面是一个最小可用的转码程序骨架串起解封装 → 解码 → 缩放 → 编码 → 封装整条链已省略逐行错误处理完整工程请按 4.x 的 API 逐段补齐#include libavformat/avformat.h #include libavcodec/avcodec.h #include libswscale/swscale.h #include libavutil/opt.h #include libavutil/imgutils.h int main(int argc, char *argv[]) { const char *src_file argv[1], *dst_file argv[2]; // ---------- 解封装 ---------- AVFormatContext *in_fmt NULL; avformat_open_input(in_fmt, src_file, NULL, NULL); avformat_find_stream_info(in_fmt, NULL); int v_in av_find_best_stream(in_fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); AVStream *in_stream in_fmt-streams[v_in]; // ---------- 解码器 ---------- const AVCodec *dec avcodec_find_decoder(in_stream-codecpar-codec_id); AVCodecContext *dec_ctx avcodec_alloc_context3(dec); avcodec_parameters_to_context(dec_ctx, in_stream-codecpar); avcodec_open2(dec_ctx, dec, NULL); // ---------- 编码器H.264 720p ---------- const AVCodec *enc avcodec_find_encoder(AV_CODEC_ID_H264); AVCodecContext *enc_ctx avcodec_alloc_context3(enc); enc_ctx-width 1280; enc_ctx-height 720; enc_ctx-pix_fmt AV_PIX_FMT_YUV420P; enc_ctx-time_base (AVRational){1, 25}; enc_ctx-framerate (AVRational){25, 1}; av_opt_set(enc_ctx-priv_data, preset, fast, 0); avcodec_open2(enc_ctx, enc, NULL); // ---------- 封装输出 ---------- AVFormatContext *out_fmt NULL; avformat_alloc_output_context2(out_fmt, NULL, NULL, dst_file); AVStream *out_stream avformat_new_stream(out_fmt, NULL); avcodec_parameters_from_context(out_stream-codecpar, enc_ctx); avio_open(out_fmt-pb, dst_file, AVIO_FLAG_WRITE); avformat_write_header(out_fmt, NULL); // ---------- 像素转换输入帧 - 720p YUV ---------- struct SwsContext *sws sws_getContext(dec_ctx-width, dec_ctx-height, dec_ctx-pix_fmt, enc_ctx-width, enc_ctx-height, enc_ctx-pix_fmt, SWS_BILINEAR, NULL, NULL, NULL); // ---------- 主循环 ---------- AVPacket *pkt av_packet_alloc(); AVFrame *frame av_frame_alloc(); AVFrame *scaled av_frame_alloc(); scaled-format enc_ctx-pix_fmt; scaled-width enc_ctx-width; scaled-height enc_ctx-height; av_frame_get_buffer(scaled, 0); int64_t pts 0; while (av_read_frame(in_fmt, pkt) 0) { if (pkt-stream_index v_in) { avcodec_send_packet(dec_ctx, pkt); while (avcodec_receive_frame(dec_ctx, frame) 0) { sws_scale(sws, frame-data, frame-linesize, 0, dec_ctx-height, scaled-data, scaled-linesize); scaled-pts pts; // 重新生成时间戳 avcodec_send_frame(enc_ctx, scaled); while (avcodec_receive_packet(enc_ctx, pkt) 0) { pkt-stream_index out_stream-index; av_packet_rescale_ts(pkt, enc_ctx-time_base, out_stream-time_base); av_interleaved_write_frame(out_fmt, pkt); } } } av_packet_unref(pkt); } // flush 编码器尾帧 avcodec_send_frame(enc_ctx, NULL); while (avcodec_receive_packet(enc_ctx, pkt) 0) { pkt-stream_index out_stream-index; av_packet_rescale_ts(pkt, enc_ctx-time_base, out_stream-time_base); av_interleaved_write_frame(out_fmt, pkt); } // ---------- 收尾 ---------- av_write_trailer(out_fmt); avio_closep(out_fmt-pb); avformat_free_context(out_fmt); avcodec_free_context(dec_ctx); avcodec_free_context(enc_ctx); avformat_close_input(in_fmt); sws_freeContext(sws); av_frame_free(frame); av_frame_free(scaled); av_packet_free(pkt); return 0; }5.3 工业数采结合场景RTSP 取流抽帧 推流// 核心思路RTSP 源 - 解码 - 抽关键帧或按 fps 抽帧- JPEG 编码 - 上报/落盘 // 1) 打开 RTSPTCP 传输更稳 AVDictionary *opts NULL; av_dict_set(opts, rtsp_transport, tcp, 0); av_dict_set(opts, stimeout, 3000000, 0); // 3 秒超时 avformat_open_input(fmt_ctx, rtsp://192.168.1.10:554/stream, NULL, opts); // 2) 解码后每 N 帧编码一张 JPEG AVCodecContext *jpeg_ctx /* avcodec_find_encoder(AV_CODEC_ID_MJPEG) ... */; // 3) JPEG 数据可直接写 Kafka/TDengine/对象存储或经 MQTT 上报 // 4) 断流检测av_read_frame 返回 AVERROR_EOF / ETIMEDOUT 后按退避策略重连6. 常错点按最容易踩、后果最重排序坑点后果正确做法1时间基混用直接用 pkt-pts 乘除秒音画不同步、时长错乱一律 av_rescale_q 换算2忘 avcodec_parameters_to_context解码失败/花屏open2 前必拷参数3手工 free AVFrame/AVPacket 数据双重释放崩溃用 av_frame_unref/av_packet_unref4只 av_frame_alloc 不 av_frame_get_buffer解码器写入野指针崩溃解码帧由解码器分配自建帧先 get_buffer5avcodec_receive_frame 返回 EAGAIN 就放弃丢帧EAGAIN 是正常状态继续喂包6包写到输出前不 rescale_ts输出文件时长错误/播放器快进编码时间基 → 流时间基换算7-c copy 时源与目标容器不兼容输出损坏先 ffprobe 确认 codec tag 兼容8输入像素格式与编码器要求不一致直接 send_frame编码失败先用 sws_scale 转 YUV420P多数编码器要求9滤镜图不调 avfilter_graph_config图不生效/崩溃建图后必须 config10av_buffersrc_add_frame 后还继续用 frame数据被滤镜改坏加 AV_BUFFERSRC_FLAG_KEEP_REF 或拷贝11RTSP 默认 UDP 传输丢包花屏/卡顿-rtsp_transport tcp12解码循环只处理视频包不 unref 音频包内存暴涨每个 pkt 用后必 unref13多线程访问同一 AVCodecContext未定义行为/崩溃编解码器上下文线程亲和加锁或每线程一实例14硬解/硬编不检查设备初始化静默回退 CPU 性能暴跌检查 open2 返回码与 hw_frames_ctx15B 帧场景下用 DTS 当 PTS 显示播放顺序错乱显示用 PTS写入用解码排序16忘 flush不 avcodec_send_frame(enc_ctx, NULL)末尾丢几帧编码结束必须 flush17-ss 放在 -i 前/后语义不同剪切位置不对前面是快进到时间点再解后面是解到时间点再剪18HLS 切片时间基准与 -hls_time 不符直播延迟/列表异常注意 GOP 对齐加 -g 帧率×秒数19音视频同步只对齐起始 PTS 不持续校正越播越不同步以音频为时钟视频持续追赶或等待20忽略 av_read_frame 的 EOF/超时死循环/无限重连风暴区分正常 EOF 与错误退避重连6.1 两个经典疑难详解画面花屏但文件时长正常多半是编码器输入像素格式不对比如编码器要 NV12 你给了 YUV420P或 avcodec_parameters_to_context 缺失导致 SPS/PPS 解析错位。先用 ffprobe -show_streams 确认 pix_fmt再用 ffmpeg -pix_fmts 查编码器支持列表。音画不同步转码时最常见原因是时间基换算出错或 pts 重生成逻辑不一致。排查步骤ffprobe -show_frames 对比音视频 pkt_pts_time 序列检查首帧差值是否恒定若差值恒定说明起始不同步若递增说明时钟漂移。7. 性能优化按需裁剪组件--disable-everything 只开需要的 demuxer/decoder/encoder减小体积与初始化开销线程化解码 avcodec_open2 前设 dec_ctx-thread_countx264 编码器默认已多线程多路流并行解码用多线程/多进程零拷贝路径解码帧直接送滤镜/编码器避免 av_frame_clone 后改数据硬件加速NVIDIA 用 NVENC/NVDECh264_nvencIntel 用 QSVh264_qsvLinux 通用 VAAPI纯 CPU 场景 -preset 从 slow 降到 fast/veryfast 可显著提速转封装优先于转码能 -c copy 就不要重编码吞吐量差一个数量级滤镜图合并多个 scale/crop 合并进一张图避免多次像素格式转换音频格式对齐解码后直接按编码器格式 swr_convert避免中间格式转换缓冲调优推流场景 -max_delay、-re实时读按网络带宽调整-g GOP 与切片对齐。8. 总结五句话带走容器 ≠ 编码排查问题先 ffprobe 分清 format_name 与 codec_name时间基是一切PTS/DTS 换算永远用 av_rescale_q跨层必转引用计数内存模型AVFrame/AVPacket 只 ref/unref不手工 freesend/receive 推拉模型EAGAIN 是正常状态EOF 要 flush 冲刷缓冲先命令行验证、再 C API 集成ffmpeg -vf 能跑通的效果转成 C 就是一段可对照的滤镜图代码。FFmpeg 是读文档不如读代码、读代码不如跑命令的典型项目——每一个命令行参数背后都是 libav* 的一串 API 调用用 ffmpeg -h full 和 ffprobe 做探针是学习它最快的路径。9. FAQ问题速答视频转码 CPU 占用 100% 正常吗软件编码正常要降占用用硬件编码或低 preset-c copy 为什么有时报错目标容器不支持源编码的 codec tag需转码如何判断文件是什么编码ffprobe -show_streams file 看 codec_nameRTSP 画面花屏怎么处理换 TCP 传输、加大 -analyzeduration、检查设备码流抽帧用什么格式最快MJPEG 编码器抽 JPEG比 PNG 快且体积小FFmpeg 可以商用吗核心是 LGPL/GPL按自己是否修改源码/链接方式选许可证商用前评估 LGPL 动态链接HLS 直播延迟大怎么调-hls_time 1-2、-hls_flags independent_segments、减小 GOP如何实时预览滤镜效果ffplay -vf scale640:360,drawtext... input.mp4多路 RTSP 同时转码怎么规划每路一个进程/线程组独立 AVFormatContext必要时 GPU 硬解多路音频声道数变了但时长不变正常吗正常重采样只改变采样布局不影响内容时长

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑