ffmpeg调用avformat_open_input打开rtmp转发流阻塞

 

G28181协议接入时,为了减少视频处理服务的改动,故采用了rtmp转发的方案。使用nginx搭建rtmp转发服务器,设备代理将流推送给nginx,nginx再将流转发给视频处理服务。但设备代理并不是每次都能够成功推流,如果失败,视频处理服务使用ffmpeg的avformat_open_input会阻塞住,导致整个服务卡死。各种超时设置均无效。经过调试发现avformat_open_input卡在了进行流侦测的时候,具体的代码如下:

本段代码位于rtmpproto.c的rtmp_open函数。代码大体意思就是收不到音频数据,视频数据或元数据,就一直接收。调试时发现,第一调用get_packet时返回0,之后再调用get_packet再无返回。期初使用简单粗暴的处理方式,即如果ret==0,也执行goto fail。但一运行程序变当掉了,原因很简单,ret=0被认为调用成功,其他函数便会进行数据拷贝,地址无效,程序直接就挂了。

不知为什么,ffmpeg在此处没有做超时处理,深层次的代码也为做任何超时处理。通过strace进行调用跟踪,发现底层采用异步IO,读取超时,但该错误没有抛上来。

突发奇想,启动另一个线程调用avformat_open_input,待取流超时后,杀掉线程。但事实证明,这是一个天真的想法,因为pthread_cancel不是一个靠的住的函数。虽然知道了问题所在,但却束手无策。但最终还是在网上找到了解决方案。

该方案具体如下:创建AVFormatContext成功后,设置中断回调(数据成员interrupt_callback.opaque(自定义指针),和interrupt_callback.callback(回调函数)),设置一个全局变量(类成员变量)last_packet_timestamp,调用avformat_open_input前初始化为time(nullptr),在回调函数里计算当前时间和last_packet_timestame的差值,当差值超过某一阈值时,比如说10s时,则认为取流超时,返回一个小于0的值,告诉调用者取流失败,其他情况下返回0。

但是用该方案一定要注意,成功取到流,每次调用av_read_frame成功后,一定要更新last_packet_timestamp。否则,av_read_frame还会超值,因为设置的中断回调会被循环调用,如果last_packet_timestamp不更新,很快就会超时。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用FFmpegavformat_open_input函数打开一个媒体文件之后,为了正常关闭并方便再次打开,需要按照以下步骤: 1. 调用avformat_close_input函数关闭输入,释放AVFormatContext相关资源。 2. 调用avformat_free_context函数,释放AVFormatContext结构体及其内部的AVStream、AVCodecContext等结构体相关的资源。 为了方便再次打开同一文件,可以在关闭后保留AVFormatContext结构体及其内部结构体的信息,以便下一次打开时可以直接使用,而不需要再次调用avformat_find_stream_info函数获取信息。具体操作步骤如下: 1. 定义一个全局AVFormatContext结构体变量,用于保存读取媒体文件时的上下文信息。 2. 在第一次读取媒体文件时,先调用avformat_open_inputavformat_find_stream_info等函数获取信息,并将AVFormatContext结构体保存到全局变量中。 3. 在关闭输入时,只需要调用avformat_close_input函数关闭输入即可,AVFormatContext结构体及其内部结构体的信息不需要释放。 4. 下一次读取同一媒体文件时,直接使用保存在全局变量中的AVFormatContext结构体即可。 示例代码: ``` AVFormatContext *pFormatCtx = NULL; int videoStream = -1; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVPacket packet; static AVFormatContext *globalFormatCtx = NULL; // 全局变量,用于保存AVFormatContext结构体 av_register_all(); if (!globalFormatCtx) { if(avformat_open_input(&pFormatCtx, filename, NULL, NULL)!=0){ printf("Could not open file\n"); return -1; } if(avformat_find_stream_info(pFormatCtx, NULL)<0){ printf("Could not find stream information\n"); return -1; } videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if(videoStream == -1){ printf("Could not find video stream\n"); return -1; } pCodecCtx = pFormatCtx->streams[videoStream]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL){ printf("Codec not found\n"); return -1; } if(avcodec_open2(pCodecCtx, pCodec, NULL)<0){ printf("Could not open codec\n"); return -1; } globalFormatCtx = pFormatCtx; // 将AVFormatContext结构体保存到全局变量中 } else { pFormatCtx = globalFormatCtx; // 直接使用全局变量中的AVFormatContext结构体 pCodecCtx = pFormatCtx->streams[videoStream]->codec; } av_init_packet(&packet); while(av_read_frame(pFormatCtx, &packet)>=0){ // 处理读取到的数据帧 av_packet_unref(&packet); } avformat_close_input(&pFormatCtx); // 关闭输入,释放输入相关资源 if (globalFormatCtx != pFormatCtx) { avformat_free_context(pFormatCtx); // 释放AVFormatContext结构体及其内部资源 } globalFormatCtx = NULL; // 清空全局变量,以便下一次打开同一文件 avcodec_free_context(&pCodecCtx); ``` 以上代码演示了如何打开一个媒体文件,读取其中的数据帧,并在完成后正常关闭并保留AVFormatContext结构体及其内部结构体的信息,以便下一次再次打开同一文件。在关闭输入时,只需要调用avformat_close_input函数关闭输入AVFormatContext结构体及其内部结构体的信息不需要释放,只有在不再需要读取该文件时才需要释放。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值