SRT协议详解

目录

  1. SRT协议入门
  2. SRT协议深度解析
  3. SRT核心API接口使用说明
  4. 真实RKCIF/V4L2摄像头采集推流
  5. SRT拉流+解码+显示完整程序

1. SRT协议入门

1.1 什么是SRT

SRT(Secure Reliable Transport,安全可靠传输) 是专为超低延迟(亚秒级) 实时音视频流设计的开源传输协议,由Haivision公司开发,现由SRT联盟维护。

  • 底层基于UDP协议,添加了可靠性保障
  • 核心优势:在不可靠网络(互联网、4G/5G)下同时实现低延迟和高可靠性
  • 开源仓库:https://github.com/Haivision/srt
  • 生态支持:FFmpeg、OBS、VLC、Wowza等主流工具原生支持

1.2 为什么嵌入式音视频需要SRT

传统协议在弱网环境下存在致命缺陷:

  • TCP(RTMP):丢包导致延迟累积和卡顿,典型延迟1-3秒
  • 原始UDP(RTP/RTSP):速度快但不可靠,丢包直接花屏断流
  • HLS:基于HTTP切片,延迟5-10秒,不适合实时场景

SRT完美解决了这个矛盾:在UDP低延迟基础上,添加智能丢包恢复机制,15%丢包率下仍能保持流畅播放。

1.3 主流流媒体协议对比

协议底层传输典型延迟丢包恢复弱网表现主要场景
SRTUDP100-500msARQ+FEC优秀直播回传、安防监控、无人机图传
RTMPTCP500-2000ms依赖TCP传统直播推流
RTSP+RTPUDP100-300ms无/简单一般局域网监控
HLSHTTP(TCP)5000-10000ms点播、移动端直播
WebRTCUDP<100msFEC+ARQ优秀视频会议、实时通话

2. SRT协议深度解析

2.1 协议栈与数据包结构

协议栈层级

SRT是传输层协议,运行在UDP之上,属于应用层和传输层之间的"中间件协议":

应用层(音视频编码/解码)
    ↓
SRT协议层(可靠性、加密、拥塞控制)
    ↓
UDP协议层(无连接、低延迟)
    ↓
IP协议层
    ↓
数据链路层
  • 完全在用户空间实现,无需修改内核
  • 提供与TCP类似的API接口,易于移植
  • 支持单播、多播和点对点传输
核心数据包类型
  • 数据数据包:携带实际音视频数据
  • 控制数据包:用于握手、确认、重传请求、拥塞控制等

2.2 核心工作原理

1. 4次握手连接建立

比TCP+TLS更快,适合嵌入式设备快速建连:

  1. Caller发送握手请求(版本、加密算法、初始序列号)
  2. Listener响应握手确认(确认参数、生成加密密钥)
  3. Caller发送最终确认(确认密钥,准备发数据)
  4. Listener发送连接就绪
2. 智能丢包恢复(ARQ+FEC)
  • ARQ选择性重传:接收端检测丢包后发送NAK,只重传丢失的特定包,RTO动态调整
  • FEC前向纠错:发送冗余数据包,接收端无需重传即可恢复丢失数据
    • 低丢包(<5%):仅用ARQ
    • 中等丢包(5%-15%):行FEC,冗余率10%-20%
    • 高丢包(>15%):矩阵FEC,冗余率20%-30%
3. 固定端到端延迟(TsbPd)
  • 发送端为每个数据包打时间戳
  • 接收端根据配置的延迟值计算播放时间
  • 数据包放入缓冲区直到播放时间到达,过期直接丢弃
  • 嵌入式调优:局域网50-100ms,4G/5G 200-500ms,卫星1000-2000ms
4. 其他核心特性
  • 动态拥塞控制:LiveCC算法,专为实时音视频设计
  • 内置AES加密:支持128/192/256位端到端加密
  • 防火墙/NAT穿透:特殊握手过程,无需永久开放外部端口

2.3 三种工作模式

  1. Caller-Listener模式(最常用):Listener被动监听,Caller主动连接
    • 典型应用:嵌入式设备向云端推流,客户端从设备拉流
  2. Rendezvous模式(点对点):双方同时作为Caller和Listener,自动NAT穿透
    • 典型应用:无人机图传、机器人远程控制
  3. 广播模式:基于UDP多播,适合局域网一对多传输

3. SRT核心API接口使用说明

3.1 库初始化与清理

srt_startup()
int srt_startup(void);
  • 功能:初始化SRT库,必须在调用任何其他SRT函数前调用
  • 返回值:成功返回0,失败返回-1
  • 注意:只需在程序开始时调用一次
srt_cleanup()
int srt_cleanup(void);
  • 功能:清理SRT库资源,释放所有内部状态
  • 返回值:成功返回0,失败返回-1
  • 注意:在程序退出前调用,确保所有套接字已关闭

3.2 套接字创建与销毁

srt_create_socket()
SRTSOCKET srt_create_socket(void);
  • 功能:创建一个新的SRT套接字
  • 返回值:成功返回套接字句柄,失败返回SRT_ERROR
  • 示例
    SRTSOCKET sock = srt_create_socket();
    if (sock == SRT_ERROR) {
        printf("创建套接字失败: %s\n", srt_getlasterror_str());
        return -1;
    }
    
srt_close()
int srt_close(SRTSOCKET sock);
  • 功能:关闭SRT套接字,释放相关资源
  • 参数
    • sock:要关闭的套接字句柄
  • 返回值:成功返回0,失败返回SRT_ERROR

3.3 参数设置与获取

SRT提供了丰富的参数配置选项,通过srt_setsockoptsrt_getsockopt进行设置和获取。

srt_setsockopt()
int srt_setsockopt(SRTSOCKET sock, int level, SRT_SOCKOPT optname, 
                   const void *optval, int optlen);
  • 功能:设置套接字选项
  • 参数
    • sock:套接字句柄
    • level:选项级别,通常为0
    • optname:选项名称(见下表)
    • optval:指向选项值的指针
    • optlen:选项值的长度
  • 返回值:成功返回0,失败返回SRT_ERROR
srt_getsockopt()
int srt_getsockopt(SRTSOCKET sock, int level, SRT_SOCKOPT optname,
                   void *optval, int *optlen);
  • 功能:获取套接字选项
  • 参数:类似srt_setsockoptoptval为输出参数
常用选项列表
选项名类型说明推荐值
SRTO_LATENCYint端到端延迟(ms)200-500
SRTO_PAYLOADSIZEint最大数据包大小1316(TS包)
SRTO_PASSPHRASEchar*加密密码最多79字符
SRTO_PBKEYLENint加密密钥长度16/24/32(AES-128/192/256)
SRTO_FECintFEC模式0=关闭, 1=开启
SRTO_FECROWSintFEC行数4-8
SRTO_FECCOLSintFEC列数4-8
参数设置示例
// 设置延迟为200ms
int latency = 200;
srt_setsockopt(sock, 0, SRTO_LATENCY, &latency, sizeof(latency));

// 开启FEC
int fec_on = 1;
srt_setsockopt(sock, 0, SRTO_FEC, &fec_on, sizeof(fec_on));

// 设置加密
const char *passphrase = "my_secret_password";
srt_setsockopt(sock, 0, SRTO_PASSPHRASE, passphrase, strlen(passphrase));
int key_len = 16; // AES-128
srt_setsockopt(sock, 0, SRTO_PBKEYLEN, &key_len, sizeof(key_len));

3.4 地址解析与连接建立

srt_parse_uri()
int srt_parse_uri(const char *uri, int urilen, struct sockaddr *sa, int *salen);
  • 功能:解析SRT URI字符串为套接字地址
  • 参数
    • uri:SRT URI,如srt://192.168.1.100:8888?mode=caller
    • urilen:URI长度
    • sa:输出的套接字地址结构
    • salen:输入/输出,地址结构长度
  • 返回值:成功返回0,失败返回SRT_ERROR
  • 支持的URI参数
    • modecaller/listener/rendezvous
    • latency:延迟值
    • passphrase:密码
    • fec:FEC开关
srt_connect()
int srt_connect(SRTSOCKET sock, const struct sockaddr *name, int namelen);
  • 功能:作为Caller主动连接到Listener
  • 参数
    • sock:套接字句柄
    • name:目标地址
    • namelen:地址长度
  • 返回值:成功返回0,失败返回SRT_ERROR
  • 注意:这是阻塞调用,会等待连接建立完成
srt_bind()
int srt_bind(SRTSOCKET sock, const struct sockaddr *name, int namelen);
  • 功能:绑定套接字到本地地址(Listener模式)
  • 参数:类似srt_connect
  • 返回值:成功返回0,失败返回SRT_ERROR
srt_listen()
int srt_listen(SRTSOCKET sock, int backlog);
  • 功能:开始监听连接请求(Listener模式)
  • 参数
    • sock:套接字句柄
    • backlog:等待连接队列的最大长度
  • 返回值:成功返回0,失败返回SRT_ERROR
srt_accept()
SRTSOCKET srt_accept(SRTSOCKET sock, struct sockaddr *addr, int *addrlen);
  • 功能:接受一个传入的连接请求
  • 参数
    • sock:监听套接字句柄
    • addr:输出,客户端地址
    • addrlen:输入/输出,地址长度
  • 返回值:成功返回新的连接套接字,失败返回SRT_ERROR
  • 注意:这是阻塞调用,会等待连接到来
连接建立完整示例

Caller端(主动连接)

SRTSOCKET sock = srt_create_socket();

// 解析URI
sockaddr_wsa sa;
int salen = sizeof(sa);
const char *url = "srt://192.168.1.100:8888?mode=caller&latency=200";
srt_parse_uri(url, strlen(url), &sa, &salen);

// 连接
if (srt_connect(sock, (struct sockaddr *)&sa, salen) == SRT_ERROR) {
    printf("连接失败: %s\n", srt_getlasterror_str());
    return -1;
}
printf("连接成功!\n");

Listener端(被动监听)

SRTSOCKET listen_sock = srt_create_socket();

// 解析URI
sockaddr_wsa sa;
int salen = sizeof(sa);
const char *url = "srt://0.0.0.0:8888?mode=listener";
srt_parse_uri(url, strlen(url), &sa, &salen);

// 绑定并监听
srt_bind(listen_sock, (struct sockaddr *)&sa, salen);
srt_listen(listen_sock, 1);

printf("等待连接...\n");
SRTSOCKET client_sock = srt_accept(listen_sock, NULL, NULL);
printf("收到新连接!\n");

3.5 数据发送与接收

srt_send()
int srt_send(SRTSOCKET sock, const char *buf, int len);
  • 功能:发送数据
  • 参数
    • sock:套接字句柄
    • buf:要发送的数据缓冲区
    • len:数据长度
  • 返回值:成功返回发送的字节数,失败返回SRT_ERROR
  • 注意
    • 这是阻塞调用,会等待数据被确认
    • 单次发送长度不应超过SRTO_PAYLOADSIZE设置的值
srt_sendmsg2()
int srt_sendmsg2(SRTSOCKET sock, const char *buf, int len, 
                  const SRT_MSGCTRL *mctrl);
  • 功能:高级发送函数,支持更多控制选项
  • 参数
    • mctrl:消息控制结构,可设置时间戳、TTL等
  • 返回值:类似srt_send
srt_recv()
int srt_recv(SRTSOCKET sock, char *buf, int len);
  • 功能:接收数据
  • 参数
    • buf:接收数据的缓冲区
    • len:缓冲区大小
  • 返回值:成功返回接收的字节数,连接关闭返回0,失败返回SRT_ERROR
  • 注意:这是阻塞调用,会等待数据到达
srt_recvmsg2()
int srt_recvmsg2(SRTSOCKET sock, char *buf, int len,
                  SRT_MSGCTRL *mctrl);
  • 功能:高级接收函数,可获取消息元数据
  • 参数
    • mctrl:输出,消息控制结构,包含时间戳等信息
数据收发完整示例

发送端

// 发送H.264数据
char h264_data[4096];
int data_len = read_h264_frame(h264_data, sizeof(h264_data));

int sent = srt_send(sock, h264_data, data_len);
if (sent == SRT_ERROR) {
    printf("发送失败: %s\n", srt_getlasterror_str());
} else {
    printf("发送了 %d 字节\n", sent);
}

接收端

// 接收数据
char recv_buf[8192];
int recv_len = srt_recv(sock, recv_buf, sizeof(recv_buf));

if (recv_len > 0) {
    printf("收到 %d 字节\n", recv_len);
    process_h264_data(recv_buf, recv_len);
} else if (recv_len == 0) {
    printf("连接已关闭\n");
} else {
    printf("接收失败: %s\n", srt_getlasterror_str());
}

3.6 错误处理

srt_getlasterror()
int srt_getlasterror(void);
  • 功能:获取最后一次错误的错误码
  • 返回值:错误码
srt_getlasterror_str()
const char *srt_getlasterror_str(void);
  • 功能:获取最后一次错误的可读描述字符串
  • 返回值:错误描述字符串指针
  • 示例
    if (srt_connect(sock, ...) == SRT_ERROR) {
        printf("错误: %s\n", srt_getlasterror_str());
    }
    

3.7 完整的API使用流程

1. srt_startup()          // 初始化库
   ↓
2. srt_create_socket()    // 创建套接字
   ↓
3. srt_setsockopt()       // 设置参数(延迟、FEC、加密等)
   ↓
4. srt_parse_uri()        // 解析地址
   ↓
5a. srt_connect()         // Caller:连接
   或
5b. srt_bind() + srt_listen() + srt_accept()  // Listener:监听并接受
   ↓
6. srt_send() / srt_recv()  // 收发数据(循环)
   ↓
7. srt_close()            // 关闭套接字
   ↓
8. srt_cleanup()          // 清理库

4. RK3568 V4L2摄像头采集推流

使用V4L2摄像头采集,直接从/dev/video0取流,零拷贝硬件编码。

4.1 代码(rk3568_camera_srt_push.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <pthread.h>

#include "rk_mpi.h"
#include <srt/srt.h>

// ====================== 配置区 ======================
#define VIDEO_DEV       "/dev/video0"
#define WIDTH           1920
#define HEIGHT          1080
#define FPS             30
#define BITRATE         4000000

#define SRT_PUSH_URL    "srt://192.168.1.100:8888?mode=caller"
#define SRT_LATENCY     200
// =====================================================

typedef struct {
    void *start;
    size_t length;
} v4l2_buf_t;

static int v4l2_fd = -1;
static v4l2_buf_t *v4l2_buffers = NULL;
static int n_buffers = 0;

static SRTSOCKET srt_sock = SRT_ERROR;
static MppCtx mpp_ctx = NULL;
static MppApi *mpp_api = NULL;

// V4L2摄像头初始化
int v4l2_init(void)
{
    v4l2_fd = open(VIDEO_DEV, O_RDWR | O_NONBLOCK, 0);
    if (v4l2_fd < 0) {
        perror("open video0 failed");
        return -1;
    }

    struct v4l2_capability cap;
    if (ioctl(v4l2_fd, VIDIOC_QUERYCAP, &cap) < 0) {
        perror("VIDIOC_QUERYCAP");
        return -1;
    }

    // 设置NV12格式(RK摄像头默认输出)
    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = WIDTH;
    fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
    fmt.fmt.pix.field = V4L2_FIELD_NONE;

    if (ioctl(v4l2_fd, VIDIOC_S_FMT, &fmt) < 0) {
        perror("VIDIOC_S_FMT");
        return -1;
    }

    // 请求缓冲区
    struct v4l2_requestbuffers req = {0};
    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(v4l2_fd, VIDIOC_REQBUFS, &req) < 0) {
        perror("VIDIOC_REQBUFS");
        return -1;
    }

    // 映射内核缓冲区
    v4l2_buffers = (v4l2_buf_t *)calloc(req.count, sizeof(v4l2_buf_t));
    for (n_buffers = 0; n_buffers < req.count; n_buffers++) {
        struct v4l2_buffer buf = {0};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;

        if (ioctl(v4l2_fd, VIDIOC_QUERYBUF, &buf) < 0) {
            perror("VIDIOC_QUERYBUF");
            return -1;
        }

        v4l2_buffers[n_buffers].length = buf.length;
        v4l2_buffers[n_buffers].start = mmap(
            NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
            v4l2_fd, buf.m.offset
        );

        if (v4l2_buffers[n_buffers].start == MAP_FAILED) {
            perror("mmap failed");
            return -1;
        }
    }

    // 入队缓冲区并开始流
    for (int i = 0; i < n_buffers; i++) {
        struct v4l2_buffer buf = {0};
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
    }

    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ioctl(v4l2_fd, VIDIOC_STREAMON, &type);

    printf("V4L2 摄像头初始化成功 → %dx%d NV12\n", WIDTH, HEIGHT);
    return 0;
}

// 读取一帧NV12数据
int v4l2_get_frame(void **nv12_data, size_t *nv12_size)
{
    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (ioctl(v4l2_fd, VIDIOC_DQBUF, &buf) < 0)
        return -1;

    *nv12_data = v4l2_buffers[buf.index].start;
    *nv12_size = buf.bytesused;

    // 用完放回队列
    ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
    return 0;
}

// SRT初始化
int srt_init(void)
{
    srt_startup();
    srt_sock = srt_create_socket();

    if (srt_sock == SRT_ERROR) {
        printf("srt socket create failed\n");
        return -1;
    }

    int latency = SRT_LATENCY;
    srt_setsockopt(srt_sock, 0, SRTO_LATENCY, &latency, sizeof(latency));

    sockaddr_wsa sa;
    int salen = sizeof(sa);
    if (srt_parse_uri(SRT_PUSH_URL, strlen(SRT_PUSH_URL), &sa, &salen) == SRT_ERROR) {
        printf("srt url error\n");
        return -1;
    }

    if (srt_connect(srt_sock, (struct sockaddr *)&sa, salen) == SRT_ERROR) {
        printf("srt connect failed: %s\n", srt_getlasterror_str());
        return -1;
    }

    printf("SRT 连接成功!\n");
    return 0;
}

// MPP硬件编码器初始化
int mpp_encoder_init(void)
{
    MPP_RET ret;
    ret = mpp_create(&mpp_ctx, &mpp_api);
    if (ret != MPP_OK) return -1;

    ret = mpp_init(mpp_ctx, MPP_CTX_ENC, MPP_VIDEO_CodingAVC);
    if (ret != MPP_OK) return -1;

    MppEncCfg cfg;
    mpp_enc_cfg_init(&cfg);

    cfg.width = WIDTH;
    cfg.height = HEIGHT;
    cfg.fps_in_num = FPS;
    cfg.fps_in_denorm = 1;
    cfg.rc_mode = MPP_ENC_RC_MODE_CBR;
    cfg.bps_target = BITRATE;
    cfg.gop = FPS * 2;

    // 输入格式:NV12
    cfg.pic_width = WIDTH;
    cfg.pic_height = HEIGHT;
    cfg.in_fmt = MPP_FMT_NV12;

    mpp_api->control(mpp_ctx, MPP_ENC_SET_CFG, &cfg);
    printf("MPP 硬件编码器初始化完成\n");
    return 0;
}

int main(void)
{
    if (v4l2_init() < 0 || srt_init() < 0 || mpp_encoder_init() < 0)
        return -1;

    MppFrame frame = NULL;
    MppPacket pkt = NULL;
    mpp_frame_init(&frame);
    mpp_packet_init(&pkt, NULL, 0);

    while (1) {
        void *nv12_data = NULL;
        size_t nv12_size = 0;

        // 取真实摄像头帧
        if (v4l2_get_frame(&nv12_data, &nv12_size) < 0) {
            usleep(10000);
            continue;
        }

        // 送入硬件编码器
        mpp_frame_set_width(frame, WIDTH);
        mpp_frame_set_height(frame, HEIGHT);
        mpp_frame_set_fmt(frame, MPP_FMT_NV12);
        mpp_frame_set_addr(frame, (void *)nv12_data);
        mpp_frame_set_pts(frame, 0);

        mpp_api->encode_put_frame(mpp_ctx, frame);

        // 获取编码结果并推流
        while (mpp_api->encode_get_packet(mpp_ctx, &pkt) == MPP_OK) {
            void *data = mpp_packet_get_data(pkt);
            int len = mpp_packet_get_length(pkt);

            if (len > 0) {
                srt_send(srt_sock, (char *)data, len);
            }
        }
    }

    return 0;
}

5. SRT拉流+解码+显示完整程序

跨平台接收端,使用FFmpeg拉取SRT流、解码、SDL显示,可在PC和嵌入式平台运行。

5.1 完整代码(srt_play.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL2/SDL.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>

#define SRT_PLAY_URL "srt://0.0.0.0:8888?mode=listener"

int main() {
    avformat_network_init();

    AVFormatContext *fmt_ctx = NULL;
    AVDictionary *opts = NULL;
    av_dict_set(&opts, "latency", "200", 0);
    av_dict_set(&opts, "mode", "listener", 0);

    if (avformat_open_input(&fmt_ctx, SRT_PLAY_URL, NULL, &opts) < 0) {
        printf("Could not open SRT stream\n");
        return -1;
    }

    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        printf("Could not find stream info\n");
        return -1;
    }

    // 查找视频流
    int video_idx = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; i++) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_idx = i;
            break;
        }
    }

    if (video_idx == -1) {
        printf("No video stream found\n");
        return -1;
    }

    // 初始化解码器
    AVCodecParameters *codec_par = fmt_ctx->streams[video_idx]->codecpar;
    const AVCodec *codec = avcodec_find_decoder(codec_par->codec_id);
    AVCodecContext *codec_ctx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codec_ctx, codec_par);
    avcodec_open2(codec_ctx, codec, NULL);

    // 初始化SDL
    SDL_Init(SDL_INIT_VIDEO);
    SDL_Window *win = SDL_CreateWindow("SRT Player", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                        codec_ctx->width, codec_ctx->height, 0);
    SDL_Renderer *renderer = SDL_CreateRenderer(win, -1, 0);
    SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
                                               codec_ctx->width, codec_ctx->height);

    // 准备帧缓冲区
    AVFrame *frame = av_frame_alloc();
    AVFrame *sws_frame = av_frame_alloc();
    uint8_t *out_buffer = (uint8_t *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1));
    av_image_fill_arrays(sws_frame->data, sws_frame->linesize, out_buffer, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, 1);

    struct SwsContext *sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
                                                  codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
                                                  SWS_BILINEAR, NULL, NULL, NULL);

    // 主循环
    AVPacket pkt;
    SDL_Event event;
    while (1) {
        SDL_PollEvent(&event);
        if (event.type == SDL_QUIT) break;

        if (av_read_frame(fmt_ctx, &pkt) < 0) break;

        if (pkt.stream_index == video_idx) {
            avcodec_send_packet(codec_ctx, &pkt);
            while (avcodec_receive_frame(codec_ctx, frame) == 0) {
                sws_scale(sws_ctx, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height,
                          sws_frame->data, sws_frame->linesize);

                SDL_UpdateYUVTexture(texture, NULL,
                                     sws_frame->data[0], sws_frame->linesize[0],
                                     sws_frame->data[1], sws_frame->linesize[1],
                                     sws_frame->data[2], sws_frame->linesize[2]);
                SDL_RenderClear(renderer);
                SDL_RenderCopy(renderer, texture, NULL, NULL);
                SDL_RenderPresent(renderer);
            }
        }
        av_packet_unref(&pkt);
    }

    // 清理资源
    av_free(out_buffer);
    av_frame_free(&frame);
    av_frame_free(&sws_frame);
    sws_freeContext(sws_ctx);
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);
    SDL_Quit();
    avcodec_free_context(&codec_ctx);
    avformat_close_input(&fmt_ctx);
    avformat_network_deinit();
    return 0;
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐