C++读取.mp4文件为字节数组时读取循环仅执行一次问题
C++二进制读取MP4文件异常修复方案
问题现象
需要在C++中读取.mp4文件为字节数组,通过UDP套接字发送,发送逻辑无异常,但文件读取环节出错:目标文件大小208KB,读取循环仅运行1次。
原有问题代码:
#include <iostream> #include <bits/stdc++.h> #include "VideoPacket.h" #include <winsock2.h> #include <stdlib.h> #include <stdio.h> using namespace std; class VideoPacket { public: bool flag; string filename; int packetNum; int index; char *data; time_t timestamp{}; explicit VideoPacket(bool p_flag, string &p_filename, int p_packetNum, char *p_data, int p_index) { flag = p_flag; packetNum = p_packetNum; index = p_index; data = p_data; filename = p_filename; } }; vector<VideoPacket> readFile(string &filename) { ifstream fin(filename); vector<VideoPacket> videoData; char buff[4096]; int counter = 0; do { fin.read(buff, sizeof(buff)); cout << buff << endl; VideoPacket newPacket(true, filename, -1, buff, counter); counter++; videoData.push_back(newPacket); } while (!fin.eof()); return videoData; } int main() { string filename = R"(videoSrc)"; vector<VideoPacket> videoData = readFile(filename); return 0; }
根因分析
- 未以二进制模式打开文件:
ifstream默认以文本模式打开文件,MP4是二进制格式文件,Windows平台下文本模式读取时遇到0x1A字节会被判定为文件结束符,直接终止读取,这是循环仅执行一次的核心原因。 - 未校验文件打开状态:代码中传入的文件名为
videoSrc,未加.mp4后缀,也没有判断文件是否成功打开,如果文件路径/后缀不匹配,文件打开失败后续读取逻辑直接失效。 - 内存指针悬空+数据覆盖:定义的
buff是readFile函数内的栈上局部数组,所有VideoPacket实例的data指针都指向这同一块内存,每次循环读取都会覆盖buff原有内容,且函数返回后栈内存被回收,data指针直接成为野指针,后续UDP发送会读到无效数据。 - 循环逻辑错误:使用
do-while搭配eof()判断会多构造一个无效数据包——哪怕最后一次读取已经失败、没有读到有效数据,也会先执行循环体创建无效包;同时没有调用gcount()获取每次实际读取的字节长度,默认每次读满4096字节,最后一个不足4096字节的包会携带脏数据。 - 用
cout输出二进制buff:cout打印char*时遇到\0就会停止输出,MP4二进制数据中存在大量\0字节,打印结果完全不具备参考性。
修复方案
- 打开文件时追加
ios::binary标记指定二进制读取模式 - 打开文件后立即校验是否打开成功,失败直接返回错误
- 每个数据包单独分配堆内存存储读取到的字节,避免指针悬空和数据覆盖,发送完成后要手动释放内存避免泄漏
- 替换错误的循环逻辑,每次读取后先判断读取状态,通过
gcount()拿到实际读取的字节数,再构造有效数据包 - 补全正确的文件路径/后缀,不要用
cout直接打印二进制缓冲区
修复后的参考代码:
#include <iostream> #include <fstream> #include <vector> #include <string> #include <winsock2.h> #include <ctime> #include <cstring> using namespace std; class VideoPacket { public: bool flag; string filename; int packetNum; int index; char *data; int dataLen; time_t timestamp; VideoPacket(bool p_flag, string p_filename, int p_packetNum, char *p_data, int p_len, int p_index) { flag = p_flag; packetNum = p_packetNum; index = p_index; dataLen = p_len; data = new char[p_len]; memcpy(data, p_data, p_len); filename = p_filename; timestamp = time(nullptr); } ~VideoPacket() { delete[] data; } }; vector<VideoPacket> readFile(string &filename) { ifstream fin(filename, ios::binary); vector<VideoPacket> videoData; const int PACKET_SIZE = 1472; // 适配公网UDP MTU,避免分片丢包 char buff[PACKET_SIZE]; if (!fin.is_open()) { cerr << "打开文件失败,检查路径是否正确: " << filename << endl; return videoData; } int counter = 0; while (true) { fin.read(buff, PACKET_SIZE); int readLen = fin.gcount(); if (readLen <= 0) { break; } VideoPacket newPacket(true, filename, -1, buff, readLen, counter); counter++; videoData.push_back(newPacket); if (fin.eof()) { break; } } fin.close(); return videoData; } int main() { string filename = R"(test.mp4)"; // 替换为实际文件路径 vector<VideoPacket> videoData = readFile(filename); cout << "共读取到数据包数量: " << videoData.size() << endl; // 后续UDP发送逻辑 return 0; }
注意:UDP单次发送的数据包大小建议不超过1472字节,超过会触发IP分片,大幅提升公网传输的丢包概率。
内容的提问来源于stack exchange,提问作者Keshav Gambhir
相关产品推荐
相关产品推荐

