读取数据流时如何清空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
相关产品推荐
相关产品推荐

