AVIOContext读取回调的异常处理及socketRead音视频读取技术咨询
处理FFmpeg AVIOContext读取回调(socketRead)的异常问题
针对你用自定义socketRead回调通过网络读取音视频流的场景,这里梳理一下FFmpeg自定义IO回调的异常处理规则和最佳实践——毕竟FFmpeg对IO回调的返回值有严格要求,处理不好很容易导致崩溃、内存泄漏或者逻辑异常。
核心规则:回调返回值的意义
FFmpeg的AVIO读取回调(就是你的socketRead)返回值必须遵循以下规则:
- 返回正整数:实际成功读取的字节数(可以小于请求的
buf_size,比如网络流暂时没有更多数据) - 返回0:表示到达EOF(比如socket连接被对方正常关闭)
- 返回负整数:必须是FFmpeg定义的
AVERROR系列错误码,不能随便返回-1,否则FFmpeg无法正确识别错误类型
常见异常场景与处理方式
1. Socket读取错误(recv返回-1)
当recv系统调用返回-1时,要根据errno转成对应的FFmpeg错误码:
- 如果是非阻塞socket的临时无数据(
errno == EAGAIN或EWOULDBLOCK):返回AVERROR(EAGAIN),FFmpeg会自动重试读取 - 如果是超时(
errno == ETIMEDOUT):返回AVERROR(ETIMEDOUT) - 其他IO错误(比如连接断开、权限问题):返回
AVERROR(EIO)
示例代码:
#include <errno.h> #include <sys/socket.h> #include "libavutil/error.h" #include "libavformat/avformat.h" int socketRead(void *ptr, uint8_t *buf, int buf_size) { printf("socketRead bufsize = %d \n", buf_size); int *sockfd = (int*)ptr; // 假设ptr传入的是socket文件描述符的指针 int ret = recv(*sockfd, buf, buf_size, 0); if (ret == -1) { // 转成FFmpeg可识别的错误码 if (errno == EAGAIN || errno == EWOULDBLOCK) { return AVERROR(EAGAIN); } else if (errno == ETIMEDOUT) { return AVERROR(ETIMEDOUT); } else { av_log(NULL, AV_LOG_ERROR, "Socket recv error: %d\n", errno); return AVERROR(EIO); } } else if (ret == 0) { // 对方关闭连接,返回EOF av_log(NULL, AV_LOG_INFO, "Socket connection closed by peer\n"); return 0; } // 正常读取,返回实际字节数 av_log(NULL, AV_LOG_DEBUG, "Read %d bytes from socket\n", ret); return ret; }
2. 避免错误的处理方式
- 不要在回调里直接调用
exit()或abort():FFmpeg内部还有未释放的资源(比如avio_ctx_buffer、AVIOContext),直接退出会导致内存泄漏 - 不要返回非
AVERROR的负数:比如直接返回-1,FFmpeg无法识别错误类型,可能会触发未定义行为 - 不要忽略部分读取的情况:如果
recv返回的字节数小于buf_size,这是网络流的正常情况,直接返回实际字节数即可,FFmpeg会再次调用回调补全数据
3. 上层错误处理
当回调返回错误码后,FFmpeg的avformat_open_input或后续的avformat_find_stream_info会返回对应的错误码,你需要在上层捕获并清理资源:
int main() { AVFormatContext *m_formatCtx = avformat_alloc_context(); uint8_t *avio_ctx_buffer = av_malloc(200 * 10); int sockfd = ...; // 假设已经建立好socket连接 AVIOContext *avio_ctx = avio_alloc_context(avio_ctx_buffer, 200*10, 0, &sockfd, &socketRead, NULL, NULL); m_formatCtx->pb = avio_ctx; int ret = avformat_open_input(&m_formatCtx, NULL, NULL, NULL); if (ret < 0) { char err_buf[AV_ERROR_MAX_STRING_SIZE]; av_strerror(ret, err_buf, sizeof(err_buf)); fprintf(stderr, "Failed to open input: %s\n", err_buf); // 清理资源 avio_context_free(&avio_ctx); av_free(avio_ctx_buffer); avformat_free_context(m_formatCtx); return ret; } // 后续处理... }
额外注意事项
- 确保
ptr参数指向的socket上下文(比如你的tcp指针)在AVIOContext的生命周期内有效,不要提前释放 - 如果使用阻塞socket,建议设置socket超时(用
setsockopt的SO_RCVTIMEO),避免回调卡住整个进程 - 尽量用FFmpeg的
av_log代替printf,可以通过设置日志级别统一管理输出
内容的提问来源于stack exchange,提问作者geeeek
相关产品推荐
相关产品推荐

