You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取数据流时如何清空stdin?C语言H264 RTP流场景需求

解决方案:清空stdin旧H264帧并读取最新帧

核心思路

连接建立完成后,先清空stdin缓冲区中所有历史数据,再开始读取最新的H264帧。关键是用非阻塞读取快速耗尽缓冲区旧数据,同时处理H264帧的边界,确保后续读取从完整帧开始。

实现步骤与代码

1. 文件描述符阻塞模式切换工具函数

用于临时切换stdin为非阻塞模式,读完旧数据后恢复阻塞:

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int set_nonblock(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int set_block(int fd) {
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
}

2. 清空stdin旧数据函数

循环读取所有可用数据并丢弃,确保缓冲区无历史残留:

#include <stdint.h>
#include <string.h>

#define BUF_SIZE 4096

void flush_stdin_old_data() {
    uint8_t buf[BUF_SIZE];
    ssize_t bytes_read;

    // 切换stdin为非阻塞模式
    if (set_nonblock(STDIN_FILENO) == -1) {
        perror("set_nonblock failed");
        return;
    }

    // 耗尽缓冲区所有旧数据
    while ((bytes_read = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
        // 直接丢弃读取到的旧数据,无需额外处理
    }

    // 恢复stdin为阻塞模式,等待新帧
    if (set_block(STDIN_FILENO) == -1) {
        perror("set_block failed");
    }
}

3. 主处理逻辑(videohandler)

连接建立后调用清空函数,再开始读取完整H264帧并发送RTP:

#include <stdio.h>

void videohandler() {
    // 第一步:清空所有历史帧数据
    flush_stdin_old_data();

    uint8_t frame_buf[1024 * 1024]; // 适配H264单帧最大长度
    while (1) {
        uint8_t start_code[4];
        ssize_t ret;

        // 读取帧起始码(兼容3字节/4字节格式)
        ret = read(STDIN_FILENO, start_code, 3);
        if (ret != 3) {
            perror("read start code failed");
            break;
        }

        if (!(start_code[0] == 0x00 && start_code[1] == 0x00 && start_code[2] == 0x01)) {
            // 尝试读取第4字节,判断是否为4字节起始码
            ret = read(STDIN_FILENO, &start_code[3], 1);
            if (ret != 1 || !(start_code[0] == 0x00 && start_code[1] == 0x00 && start_code[2] == 0x00 && start_code[3] == 0x01)) {
                perror("invalid H264 start code");
                break;
            }
        }

        // 读取完整帧数据,直到下一个起始码出现
        ssize_t frame_len = 0;
        uint8_t buf[BUF_SIZE];
        while (1) {
            ret = read(STDIN_FILENO, buf, BUF_SIZE);
            if (ret <= 0) {
                perror("read frame data failed");
                goto end_read;
            }

            // 检查缓冲区中是否包含下一个帧起始码
            for (ssize_t i = 0; i < ret - 3; i++) {
                if ((buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x00 && buf[i+3] == 0x01) ||
                    (buf[i] == 0x00 && buf[i+1] == 0x00 && buf[i+2] == 0x01)) {
                    // 复制当前帧剩余数据
                    memcpy(frame_buf + frame_len, buf, i);
                    frame_len += i;

                    // 回退起始码到stdin,供下一次读取使用
                    ungetc(buf[i+2], stdin);
                    ungetc(buf[i+1], stdin);
                    ungetc(buf[i], stdin);
                    if (buf[i+2] == 0x00) {
                        ungetc(buf[i+3], stdin);
                    }

                    goto frame_ready;
                }
            }

            // 未找到起始码,继续累加帧数据
            memcpy(frame_buf + frame_len, buf, ret);
            frame_len += ret;
        }

frame_ready:
        // 此处已获取完整H264帧,调用RTP发送逻辑
        // send_rtp_packet(frame_buf, frame_len);
        printf("Received H264 frame, length: %zd bytes\n", frame_len);
    }

end_read:
    return;
}

int main() {
    // 模拟网络连接建立过程(此处替换为实际连接代码)
    // ...

    // 连接完成,启动视频处理
    videohandler();
    return 0;
}

注意事项

  • 若stdin是管道/FIFO,无法使用lseek跳过数据,必须用非阻塞读取耗尽缓冲区。
  • H264帧起始码有两种格式,代码中已兼容,需确保生产者输出的帧符合标准格式。
  • ungetc仅能回退少量字符,此处用于回退帧起始码是安全的,因为起始码最多4字节。

内容的提问来源于stack exchange,提问作者Hououin_kyouma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 11:23:09