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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:00:41