🎬 FFmpeg入门指南:从零开始掌握FFmpeg

摘要:本文全面介绍FFmpeg的核心概念、编译安装、核心数据结构及常用API,帮助开发者快速入门音视频处理领域。适合音视频开发初学者和想要系统学习FFmpeg的开发者。


📖 前言

在音视频开发领域,FFmpeg 无疑是最重要的开源框架之一。无论是短视频应用、直播平台、视频会议软件,还是视频编辑工具,背后几乎都有FFmpeg的身影。

如果你是一名音视频开发初学者,可能会被FFmpeg庞大的代码库和复杂的API所困扰。别担心!本文将从零开始,带你系统地了解FFmpeg的核心概念、安装编译、数据结构和常用API,让你快速入门音视频开发!🚀


📑 目录


一、FFmpeg简介

1.1 什么是FFmpeg

FFmpeg 既是一款音视频编解码工具,同时也是一组音视频编解码开发套件。作为编解码开发套件,它为开发者提供了丰富的音视频处理调用接口。

FFmpeg提供了多种媒体格式的封装和解封装,包括:

  • ✅ 多种音视频编码
  • ✅ 多种协议的流媒体
  • ✅ 多种色彩格式转化
  • ✅ 多种采样率转换
  • ✅ 多种码率转换等

FFmpeg框架提供了多种丰富的插件模块,包含封装与解封装的插件、编码与解码的插件。

1.2 FFmpeg的基本组成

FFmpeg主要由三部分组成:

第一部分:工具 🔧

四个作用不同的工具软件:

工具 功能
ffmpeg.exe 音视频转码、转换器
ffplay.exe 简单的音视频播放器
ffserver.exe 流媒体服务器(已废弃,见下文说明)
ffprobe.exe 简单的多媒体码流分析器

⚠️ 废弃说明ffserver 在 FFmpeg 4.0 版本中被移除。官方推荐使用其他流媒体服务器替代,如 Nginx-RTMPSRS 等。

第二部分:SDK 📦

可供开发者使用的SDK,为各个不同平台编译完成的库。这些库可以像乐高积木一样根据需要开发自己的应用程序:

功能
libavcodec 包含音视频编码器和解码器
libavutil 包含多媒体应用常用的简化编程工具,如随机数生成器、数据结构、数学函数等
libavformat 包含多种多媒体容器格式的封装、解封装工具
libavfilter 包含多媒体处理常用的滤镜功能
libavdevice 用于音视频数据采集和渲染功能的设备相关
libswscale 用于图像缩放和色彩空间和像素格式转化功能
libswresample 用于音频重采样和格式转换功能
第三部分:源码 💻

整个工程的源代码,无论是编译出来的可执行程序还是SDK,都由这些源代码编译而来。

重要提示:FFmpeg的源代码由C语言实现,主要在Linux平台上进行开发。FFmpeg不是一个孤立的工程,它还存在多个依赖的第三方工程来增强它自身的功能。

1.3 FFmpeg主要功能

1.3.1 视频采集功能 📹

ffmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用。

1.3.2 视频格式转换功能 🔄

ffmpeg可以轻易地实现多种视频格式之间的相互转换(wma, rm, avi, mod等),例如可以将摄录下的视频avi等转成视频网站所采用的flv格式。

1.3.3 视频截图功能 📸

对于选定的视频,截取指定时间的缩略图。视频抓图,获取静态图和动态图。

💡 提示:不提倡抓gif文件,因为抓出的gif文件大而播放不流畅。

1.3.4 滤镜功能 🎨

使用ffmpeg为视频添加水印(logo)。

1.4 FFmpeg八大库介绍

1.4.1 libavutil

核心工具库,最基础模块之一,其他模块都会依赖该库做一些基本的音视频处理操作。

1.4.2 libavformat

文件格式和协议库,封装了Protocol层和Demuxer、Muxer层,使得协议和格式对于开发者来说是透明的。

1.4.3 libavcodec

编解码库,封装了Codec层。有一些codec是具备自己的License的,FFmpeg不会默认添加像libx264、FDK-AAC、Lame等库,但是FFmpeg是一个平台,可以将其他的第三方codec以插件的方式添加进来,为开发者提供统一接口。

1.4.4 libavfilter

音视频滤镜库,该模块包含了音频特效和视频特效的处理。在使用FFmpeg的API进行编解码的过程中,可以使用该模块高效地为音视频数据做特效处理。

1.4.5 libavdevice

输入输出设备库。比如需要编译出播放声音或者视频的工具ffplay,就需要确保该模块是打开的,同时也需要libsdl的预先编译,该设备模块播放声音和视频都又是使用libsdl库。

1.4.6 libswresample

用于音频重采样,可以对数字音频进行声道数、数据格式、采样率等多种基本信息的转换。

1.4.7 libswscale

该模块用于图像格式转换,可以将YUV的数据转换为RGB的数据。

1.4.8 libpostproc

该模块用于进行后期处理。当我们使用filter的时候,需要打开这个模块,filter会用到这个模块的一些基础函数。

📌 补充说明:比较老的ffmpeg还会编译出 avresample 模块,也是用于对音频原始数据进行重采样的,但是已经被废弃,推荐使用 libswresample 替代。

另外,库里还可以包含对 H.264/MPEG-4 AVC 视频编码的 X264 库,是最常用的有损视频编码器,支持 CBR、VBR 模式,可以在编码的过程中直接改变码率的设置,在直播的场景中非常适用,可以做码率自适应的功能。


二、安装与编译

2.1 Linux编译安装

2.1.1 FFmpeg下载

下载地址:http://ffmpeg.org/download.html#releases

解压:

tar xvJf ffmpeg-4.4.2.tar.xz
2.1.2 安装x264

下载x264源码:

git clone https://code.videolan.org/videolan/x264.git

进入x264目录,配置、编译、安装:

cd x264
./configure --enable-shared --disable-asm --prefix=/usr/local/x264
make
sudo make install

参数说明

参数 说明
--enable-shared 生成动态库
--prefix 指定安装目录
2.1.3 编译FFmpeg
./configure \
   --enable-shared \
   --prefix=/usr/local/ffmpeg \
   --enable-libx264 \
   --enable-gpl

make
sudo make install

参数说明

参数 说明
--enable-shared 生成动态库
--prefix 选择安装目录
--extra-cflags 指定依赖的头文件位置
--extra-ldflags 指定依赖的库文件位置
2.1.4 配置库路径

编译完成后需要配置库路径:

# 添加库路径到配置文件
echo "/usr/local/ffmpeg/lib" >> /etc/ld.so.conf
echo "/usr/local/x264/lib" >> /etc/ld.so.conf

# 更新动态链接库缓存
sudo ldconfig

2.2 GPU版本编译(可选)

前提条件:需要安装NVIDIA CUDA环境

2.2.1 安装 nv-codec-headers
git clone https://github.com/FFmpeg/nv-codec-headers.git
cd nv-codec-headers
make
sudo make install

📌 说明:nv-codec-headers 只是头文件,提供 NVENC/NVDEC 的 API 定义。

2.2.2 配置环境变量
apt install pkg-config
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:${PKG_CONFIG_PATH}
pkg-config --modversion ffnvcodec

CUDA_PATH=/usr/local/cuda
export ECFLAG="-fPIC"
2.2.3 编译FFmpeg(带GPU支持)
./configure \
   --enable-shared \
   --prefix=/usr/local/ffmpeg \
   --enable-libx264 \
   --enable-gpl \
   --enable-cuda-sdk \
   --enable-cuvid \
   --enable-nvenc \
   --enable-nonfree \
   --enable-libnpp \
   --extra-cflags="-I/usr/local/cuda/include -I/usr/local/x264/include -I/usr/local/include/ffnvcodec -fPIC" \
   --extra-ldflags="-L/usr/local/cuda/lib64 -L/usr/local/x264/lib" \
   --nvcc=${CUDA_PATH}/bin/nvcc

make
sudo make install

GPU相关参数说明

参数 说明
--enable-cuda-sdk 启用CUDA SDK支持
--enable-cuvid 启用NVDEC硬件解码
--enable-nvenc 启用NVENC硬件编码
--enable-libnpp 启用NVIDIA图像处理库

三、核心数据结构

3.1 结构体层次关系

FFmpeg中主要数据结构存在包含关系:

AVFormatContext -> AVStream -> AVCodecContext -> AVCodec

其中后者是前者的数据成员。AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器。

FFmpeg中的结构体可以分为几类:

类别 主要结构体
解协议 URLContext等
解封装 AVFormatContext、AVInputFormat、AVOutputFormat
解码 AVStream、AVCodecContext、AVCodec
存数据 AVFrame、AVPacket

3.2 AVFormatContext

📌 描述

AVFormatContext 主要存储视音频封装格式中包含的信息,位于 avformat.h 中。在使用FFmpeg进行开发的时候,AVFormatContext是一个贯穿始终的数据结构,很多函数都要用到它作为参数。它是FFmpeg解封装(flv,mp4,rmvb,avi)功能的结构体。

📋 常用成员
成员 说明
duration 视频时长,以AV_TIME_BASE为单位。计算秒:duration / AV_TIME_BASE;计算毫秒:duration / (AV_TIME_BASE / 1000)
AVInputFormat 输入格式(播放视频时生效)
AVOutputFormat 输出格式(录制视频时生效)
AVStream **streams 流数组,包含所有音视频流信息
AVCodecContext 编解码器上下文(已废弃,见下文)
AVCodec 编解码器信息
AVFrame 编码后的帧信息
💡 使用提示

处理 avformat_open_input()av_read_frame() 在断流时的阻塞问题,可参考:

  • https://stackoverflow.com/questions/14558172/ffmpeg-av-read-frame-need-very-long-time-to-stop
  • https://www.cnblogs.com/shuiche/p/11983533.html

3.3 AVStream

📌 描述

每个 AVStream 存储一个视频/音频流的相关数据。每个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。

📋 常用成员
成员 类型 说明
index int 标识该视频/音频流
codec AVCodecContext* 已废弃,应使用 codecpar
codecpar AVCodecParameters* 编码器参数(推荐使用
time_base AVRational 时基,PTS*DTS转化为真正的时间
duration int64_t 该视频/音频流长度
metadata AVDictionary* 元数据信息
avg_frame_rate AVRational 帧率
attached_pic AVPacket 附带的图片(如MP3专辑封面)

⚠️ 废弃说明AVStream::codec 成员已在 FFmpeg 3.1 版本废弃,FFmpeg 4.0 版本移除。应使用 AVStream::codecpar 替代。

💡 时基转换示例
// 将duration转化为秒
double seconds = duration * ((double)time_base.num / (double)time_base.den);

3.4 AVCodecContext

📌 描述

编解码器上下文结构体,用于存储音视频编解码器的参数和状态信息。它包含了进行音视频编解码所需的各种设置和配置,如编码器类型、编码参数、解码参数、输入输出格式等。每个音视频流在编解码过程中都需要一个对应的AVCodecContext。

⚠️ 线程安全:同一个AVCodecContext只能在一个线程中使用。之前试过在线程A中 avcodec_send_packet,在线程B中 avcodec_receive_frame,程序会崩溃。

📋 常用成员
成员 说明
frame_size 音频帧中一个声道的采样数(如AAC默认为1024)。对于音频编码,每次送入编码器的采样数要等于frame_size或能被整除,否则会异常。可使用AVAudioFifo做缓冲区。
codec_type 编解码器类型(AVMEDIA_TYPE_VIDEO/AUDIO)
codec_id 编解码器ID
width/height 视频宽高
pix_fmt 像素格式
sample_rate 音频采样率
channels 音频声道数

3.5 AVFrame

📌 描述

AVFrame 用于存放解码后的数据,原始视频数据(YUV、RGB等)或音频数据(PCM)。该结构体位于 avcodec.h 中。

AVFrame结构体一般用于存储原始数据(即非压缩数据),此外还包含了一些相关的信息。比如说,解码的时候存储了宏块类型表、QP表、运动矢量表等数据。编码的时候也存储了相关的数据。

📋 常用成员
/**
 * 存储原始帧数据(对视频是YUV/RGB,对音频是PCM)
 * data是指针数组,元素指向视频图像的某一plane或音频中某声道的某一plane
 * 
 * 视频存储方式:
 *   Packet: Y,U,V交织存储在一个plane中,如YUVYUV...,data[0]指向这个plane
 *   Planar: Y,U,V存储在三个plane中,data[0]->Y, data[1]->U, data[2]->V
 * 
 * 音频存储方式:
 *   Packet: L,R交织存储在一个plane中,如LRLRLR...,data[0]指向这个plane
 *   Planar: L,R存储在两个plane中,data[0]->L, data[1]->R
 */
uint8_t *data[AV_NUM_DATA_POINTERS];

/**
 * linesize是数组,data中"一行"数据的大小
 * 视频中:每个元素是图像plane中一行图像的大小(字节数),有对齐要求
 * 音频中:每个元素是音频plane的大小(字节数)
 */
int linesize[AV_NUM_DATA_POINTERS];

int width, height;           // 视频帧宽和高
int nb_samples;              // 音频帧中包含的采样数
int format;                  // 解码后原始数据类型(YUV420,YUV422,RGB24...)
int key_frame;               // 是否是关键帧
enum AVPictureType pict_type; // 帧类型(I,B,P...)
AVRational sample_aspect_ratio; // 宽高比(16:9,4:3...)
int64_t pts;                 // 显示时间戳
int coded_picture_number;    // 编码帧序号
int display_picture_number;  // 显示帧序号
int8_t *qscale_table;        // QP表
uint8_t *mbskip_table;       // 跳过宏块表
int16_t (*motion_val[2])[2]; // 运动矢量表
uint32_t *mb_type;           // 宏块类型表
int interlaced_frame;        // 是否是隔行扫描

3.6 AVPacket

📌 描述

AVPacket 存放解码前的数据,比如H264数据。

⚠️ 内存管理:AVPacket有两块内存,一块是它本身的;另一块是它里面数据的,即data指向的内存。释放时需要分别释放,AVPacket本身的内存调用 av_packet_free() 来释放,而内部的data通过引用计数来释放,当引用计数减到0的时候,其内存就释放了。

📋 常用成员
成员 类型 说明
buf AVBufferRef* 引用计数,当为0时AVPacket空间将被清除
data uint8_t* 压缩编码的数据。对于H.264,1个AVPacket的data通常对应一个NAL
size int data的大小
pts int64_t 显示时间戳
dts int64_t 解码时间戳
stream_index int 标识该AVPacket所属的视频/音频流
opaque void* 用户自定义数据。需要开启 `codec_ctx->flags
💡 用户自定义数据说明

⚠️ 注意:尝试了各种用户自定义数据的方法(opaque、side_data、opaque_ref),这些数据通过软解码在AVFrame中都可以获取。但是使用NVIDIA硬解码时这些数据都会丢失,连AVFrame中的pkt_dts都是无效数据。目前能确认的是AVPacket中的pts与AVFrame中的pts是相同的,可以暂且拿pts作为帧的唯一标识。


四、常用API概览

4.1 解码流程对比

FFmpeg 2.x 解码流程(已废弃)❌
av_read_frame() -> avcodec_decode_video2() -> 处理AVFrame
FFmpeg 4.x+ 解码流程(推荐)✅
av_read_frame() -> avcodec_send_packet() -> avcodec_receive_frame() -> 处理AVFrame

4.2 图像处理函数

4.2.1 sws_getContext

[函数原型]

struct SwsContext *sws_getContext(
    int srcW, int srcH, enum AVPixelFormat srcFormat,
    int dstW, int dstH, enum AVPixelFormat dstFormat,
    int flags, SwsFilter *srcFilter,
    SwsFilter *dstFilter, const double *param);

[描述]

sws_getContextsws_scale 是 FFmpeg libswscale 里最核心的一对函数:

  • sws_getContext 负责"建立转换规则"
  • sws_scale 负责"按照规则进行转换"

[参数说明]

参数 说明
srcW, srcH 源图像的宽、高
srcFormat 源图像的像素格式
dstW, dstH 目标图像的宽、高
dstFormat 目标图像的像素格式
flags 图像拉伸算法。SWS_BICUBIC性能好;SWS_FAST_BILINEAR平衡;SWS_POINT效果差
srcFilter 源过滤器,一般填NULL
dstFilter 目标过滤器,一般填NULL
param 参数,一般填NULL
4.2.2 sws_scale

[函数原型]

int sws_scale(
    struct SwsContext *c,
    const uint8_t *const srcSlice[],
    const int srcStride[],
    int srcSliceY,
    int srcSliceH,
    uint8_t *const dst[],
    const int dstStride[]
);

[参数说明]

参数 说明
c SwsContext,由sws_getContext创建
srcSlice[] 源数据,即frame->data
srcStride[] 源行大小,即frame->linesize
srcSliceY 从哪一行开始转换,一般从0开始
srcSliceH 转换多少行,frame->height
dst[] 目标数据
dstStride[] 目标图像每一行占多少字节

[使用示例]

// 创建转换上下文
struct SwsContext *sws_ctx = sws_getContext(
    src_width, src_height, src_pix_fmt,
    dst_width, dst_height, dst_pix_fmt,
    SWS_BILINEAR, NULL, NULL, NULL);

// 执行转换
sws_scale(sws_ctx, 
          src_frame->data, src_frame->linesize, 
          0, src_frame->height,
          dst_data, dst_linesize);

// 释放上下文
sws_freeContext(sws_ctx);
4.2.3 av_image_alloc

[函数原型]

int av_image_alloc(
    uint8_t *pointers[4], 
    int linesizes[4],
    int w, int h, 
    enum AVPixelFormat pix_fmt, 
    int align);

[描述]

主要用于为图像分配内存并初始化相关的参数。根据给定的图像宽度、高度、像素格式等信息,分配足够的内存来存储图像数据。

⚠️ 内存释放:图像使用完成后必须使用 av_freep(&pointers[0]) 来释放内存。

[参数说明]

参数 说明
pointers[4] 指针数组,包含指向图像每个平面的指针。pointers[0]对应Y平面,pointers[1]对应U平面,依此类推
linesizes[4] 整数数组,存储每个图像平面的行大小(每行字节数)
w 图像的宽度(像素)
h 图像的高度(像素)
pix_fmt 图像的像素格式(YUV420P、RGB24等)
align 缓冲区对齐的字节数,如16字节对齐

[返回值]

  • ✅ 成功:返回所分配的总字节数
  • ❌ 失败:返回负数错误代码

[使用示例]

uint8_t *data[4] = {NULL};
int linesize[4] = {0};

// 分配YUV420P格式的图像内存
int ret = av_image_alloc(data, linesize, 1920, 1080, AV_PIX_FMT_YUV420P, 16);
if (ret < 0) {
    fprintf(stderr, "Could not allocate image\n");
    return -1;
}

// 使用图像数据...
// data[0] 指向Y平面
// data[1] 指向U平面
// data[2] 指向V平面

// 释放内存
av_freep(&data[0]);  // 释放整个内存块

4.3 其他常用函数

4.3.1 avcodec_send_packet / avcodec_receive_frame

[函数原型]

int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

[描述]

FFmpeg 4.x 新版解码API,替代已废弃的 avcodec_decode_video2()。使用 avcodec_send_packet 将数据发送给解码线程进行解码,使用 avcodec_receive_frame 从解码线程中接收解码后的数据。

⚠️ 重要:这两个函数都是异步的,且一次send可能要对应多次receive。

[返回值]

avcodec_receive_frame 返回值:

返回值 说明
0 成功,返回了一帧数据
AVERROR(EAGAIN) 当前没有输出数据,需要继续输入新数据
AVERROR_EOF 解码器已完全刷新,不会再有更多输出
其他负数 解码错误

[使用示例]

while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    // 发送压缩数据到解码器
    ret = avcodec_send_packet(codec_ctx, &pkt);
    if (ret < 0) {
        fprintf(stderr, "Error sending packet for decoding\n");
        break;
    }
    
    // 接收解码后的帧(可能需要多次receive)
    while (ret >= 0) {
        ret = avcodec_receive_frame(codec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        } else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            break;
        }
        
        // 处理解码后的帧
        process_frame(frame);
    }
    
    av_packet_unref(&pkt);
}
4.3.2 av_read_frame

[函数原型]

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

[描述]

读取码流中的音频若干帧或者视频一帧。读取成功会将数据写入到 pkt->data 中,并通过引用计数对数据进行内存管理。

[参数说明]

参数 说明
s 输入的AVFormatContext
pkt 输出的AVPacket

[返回值]

返回0表示读取正常。

[使用示例]

// 推荐方式:使用栈空间
AVPacket pkt;
av_init_packet(&pkt);

while (av_read_frame(fmt_ctx, &pkt) >= 0) {
    // 处理 pkt
    av_packet_unref(&pkt);  // 使用完必须调用
}

⚠️ 注意av_init_packet() 在 FFmpeg 4.4 中已被废弃,可以直接使用 AVPacket pkt = {0}; 初始化。

4.3.3 avformat_open_input / avformat_close_input

[函数原型]

int avformat_open_input(
    AVFormatContext **ps, 
    const char *filename, 
    AVInputFormat *fmt,
    AVDictionary **options);

void avformat_close_input(AVFormatContext **s);

[描述]

  • avformat_open_input:用于打开多媒体数据并获得相关信息
  • avformat_close_input:关闭输入文件,不仅清理AVFormatContext本身的空间,还会清除其成员指针指向的空间

[参数说明]

参数 说明
ps AVFormatContext结构体的指针地址
filename 视音频流的URL,支持本地文件、http、rtsp
fmt 强制指定AVInputFormat,一般设为NULL让FFmpeg自动检测
options 附加选项,一般设为NULL

[使用示例]

AVFormatContext *fmt_ctx = NULL;

// 打开输入文件
if (avformat_open_input(&fmt_ctx, filename, NULL, NULL) != 0) {
    fprintf(stderr, "Could not open input file\n");
    return -1;
}

// 获取流信息
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
    fprintf(stderr, "Could not find stream info\n");
    return -1;
}

// 使用完毕后关闭
avformat_close_input(&fmt_ctx);
4.3.4 av_dump_format

[函数原型]

void av_dump_format(
    AVFormatContext *ic, 
    int index, 
    const char *url, 
    int is_output);

[描述]

打印关于输入或输出格式的详细信息,如持续时间、比特率、流、容器、元数据、编解码器和时基。

[参数说明]

参数 说明
ic AVFormatContext
index 用户自定义index,对打印结果无影响
url 用户自定义url,对打印结果无影响
is_output 0表示输入流,1表示输出流

4.4 废弃API汇总 📋

废弃API 替代API 废弃版本 移除版本
av_register_all() 不再需要调用 4.0 4.0
avcodec_decode_video2() avcodec_send_packet() + avcodec_receive_frame() 3.1 4.0
avcodec_encode_video2() avcodec_send_frame() + avcodec_receive_packet() 3.1 4.0
avpicture_fill() av_image_fill_arrays() 3.0 4.0
avpicture_alloc() av_image_alloc() 3.0 4.0
avpicture_free() av_freep(&pointers[0]) 3.0 4.0
av_init_packet() 直接初始化 AVPacket pkt = {0}; 4.4 -
AVStream::codec AVStream::codecpar 3.1 4.0
ffserver Nginx-RTMP, SRS 等替代 4.0 4.0

📚 参考资料


写在最后

FFmpeg是音视频开发的基石,掌握它需要时间和实践。本文只是入门指南,更多精彩等待你去探索!

💡 小贴士:学习过程中遇到问题,多查阅官方文档和社区资源。FFmpeg社区活跃,很多问题都能找到解决方案。

如果本文对你有帮助,欢迎点赞、收藏、评论!有问题也欢迎留言讨论~ 👋


版权声明:本文整合自多篇技术笔记,仅供学习交流使用。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐