欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > 音视频开发(二)FFmpeg调用avio_open返回Protocol not found的追踪

音视频开发(二)FFmpeg调用avio_open返回Protocol not found的追踪

2024/10/25 1:33:43 来源:https://blog.csdn.net/cuxqblt/article/details/141961626  浏览:    关键词:音视频开发(二)FFmpeg调用avio_open返回Protocol not found的追踪

Protocol not found

近日,在C++中使用FFmpeg把一些本地的视频文件,推送到远程RTSP服务器的时候,使用了如下这个过程:

  • avformat_alloc_output_context2() 申请上下文
  • avcodec_find_encoder 找到编码器
  • avcodec_alloc_context3 通过找到的编码器,申请编码器上下文,设置编码器上下文
  • avcodec_open2 打开编码器
  • avformat_new_stream 新建媒体流,设置媒体流
  • avio_open 打开推送媒体的网络IO
  • avformat_write_header 写入媒体头
  • av_interleaved_frame …… 依次写入视频帧

前面的过程都一切正常,但是到了上面倒数第二步,即avio_open的时候,怎是失败,错误信息是:Protocol not found。

FFmpeg源代码

网络上已有的信息,有的说是版本问题,有的说是因为相关协议没有注册,但是都不解决问题。

于是,从FFmpeg的地址上,克隆了一份源代码,读了一下,发现了问题根源。

我们以这个版本为准,这个版本提交信息为:

commit 9d15fe77e33b757c75a4186fa049857462737713
Author: James Almer <jamrial@gmail.com>
Date:   Wed Aug 21 15:12:46 2024 -0300avcodec/container_fifo: add missing stddef.h includeFixes make checkheadersSigned-off-by: James Almer <jamrial@gmail.com>

提交于2024年8月21日。

我们在源代码根目录下的libavformat目录中的avio.c中的第497行,找到avio_open,发现它的定义为:

int avio_open(AVIOContext **s, const char *filename, int flags)  
{  return avio_open2(s, filename, flags, NULL, NULL);  
}

而avio_open2的定义为:

int avio_open2(AVIOContext **s, const char *filename, int flags,  const AVIOInterruptCB *int_cb, AVDictionary **options)  
{  return ffio_open_whitelist(s, filename, flags, int_cb, options, NULL, NULL);  
}

即,avio_open的定义实际上为ffio_open_whitelist。

而avio.c的471行就是ffio_open_whitelist:

int ffio_open_whitelist(AVIOContext **s, const char *filename, int flags,  const AVIOInterruptCB *int_cb, AVDictionary **options,  const char *whitelist, const char *blacklist)  
{  URLContext *h;  int err;  *s = NULL;  err = ffurl_open_whitelist(&h, filename, flags, int_cb, options, whitelist, blacklist, NULL);  if (err < 0)  return err;  err = ffio_fdopen(s, h);  if (err < 0) {  ffurl_close(h);  return err;  }  return 0;  
}

我们看到,ffio_open_whitelist首先调用了ffurl_open_whitelist,我们先看这个函数。

ffurl_open_whitelist的定义在avio.c的362行:

int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,  const AVIOInterruptCB *int_cb, AVDictionary **options,  const char *whitelist, const char* blacklist,  URLContext *parent)  
{  AVDictionary *tmp_opts = NULL;  AVDictionaryEntry *e;  int ret = ffurl_alloc(puc, filename, flags, int_cb);  if (ret < 0)  return ret;  if (parent) {  ret = av_opt_copy(*puc, parent);  if (ret < 0)  goto fail;  }……

我们看到,又是先调用了ffurl_alloc。通过函数名称,我们大胆猜测,这应该是一个根据文件名来创建URL结构的函数,而URL中有一个关键字段就是协议,即Protocol,所以这个函数非常可能跟“Protocol not found”有关。

果然,我们在avio.c的349行,看到这个函数的定义:

int ffurl_alloc(URLContext **puc, const char *filename, int flags,  const AVIOInterruptCB *int_cb)  
{  const URLProtocol *p = NULL;  p = url_find_protocol(filename);  if (p)  return url_alloc_for_protocol(puc, p, filename, flags, int_cb);  *puc = NULL;  return AVERROR_PROTOCOL_NOT_FOUND;  
}

最后,如果p == NULL,则*pub会被设置为NULL,之后返回AVERROR_PROTOCOL_NOT_FOUND。

而AVERROR_PROTOCOL_NOT_FOUND在libavutil/error.h中,是一个宏,最后注释文字为:Protocol not found。

#define AVERROR_PROTOCOL_NOT_FOUND FFERRTAG(0xF8,'P','R','O') ///< Protocol not found

我们看一下url_find_protocol在什么情况下会返回NULL。

url_find_protocol的实现为:

static const struct URLProtocol *url_find_protocol(const char *filename)  
{  const URLProtocol **protocols;  char proto_str[128], proto_nested[128], *ptr;  size_t proto_len = strspn(filename, URL_SCHEME_CHARS);  int i;  if (filename[proto_len] != ':' &&  (strncmp(filename, "subfile,", 8) || !strchr(filename + proto_len + 1, ':')) ||  is_dos_path(filename))  strcpy(proto_str, "file");  else  av_strlcpy(proto_str, filename,  FFMIN(proto_len + 1, sizeof(proto_str)));  av_strlcpy(proto_nested, proto_str, sizeof(proto_nested));  if ((ptr = strchr(proto_nested, '+')))  *ptr = '\0';  protocols = ffurl_get_protocols(NULL, NULL);  if (!protocols)  return NULL;  for (i = 0; protocols[i]; i++) {  const URLProtocol *up = protocols[i];  if (!strcmp(proto_str, up->name)) {  av_freep(&protocols);  return up;  }  if (up->flags & URL_PROTOCOL_FLAG_NESTED_SCHEME &&  !strcmp(proto_nested, up->name)) {  av_freep(&protocols);  return up;  }  }  av_freep(&protocols);  if (av_strstart(filename, "https:", NULL) || av_strstart(filename, "tls:", NULL))  av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "  "openssl, gnutls or securetransport enabled.\n");  return NULL;  
}

这个函数的开头,先把传入的filename根据:截取出来,做为协议名,之后就用ffurl_get_protocols取得的数组依次对比,如果没有相等的,就会返回NULL。

而ffurl_get_protocols定义的libavformat/protocols.c中,这个函数的返回值直接复制自一个数组url_protocols。

const URLProtocol **ffurl_get_protocols(const char *whitelist,  const char *blacklist)  
{  const URLProtocol **ret;  int i, ret_idx = 0;  ret = av_calloc(FF_ARRAY_ELEMS(url_protocols), sizeof(*ret));  if (!ret)  return NULL;  for (i = 0; url_protocols[i]; i++) {  const URLProtocol *up = url_protocols[i];  if (whitelist && *whitelist && !av_match_name(up->name, whitelist))  continue;  if (blacklist && *blacklist && av_match_name(up->name, blacklist))  continue;  ret[ret_idx++] = up;  }  return ret;  
}

而url_protocols的定义在libavformat/protocol_list.c中,是一个数组:

static const URLProtocol * const url_protocols[] = {  &ff_async_protocol,  &ff_cache_protocol,  &ff_concat_protocol,  &ff_concatf_protocol,  &ff_crypto_protocol,  &ff_data_protocol,  &ff_fd_protocol,  &ff_ffrtmphttp_protocol,  &ff_file_protocol,  &ff_ftp_protocol,  &ff_gopher_protocol,  &ff_hls_protocol,  &ff_http_protocol,  &ff_httpproxy_protocol,  &ff_icecast_protocol,  &ff_mmsh_protocol,  &ff_mmst_protocol,  &ff_md5_protocol,  &ff_pipe_protocol,  &ff_prompeg_protocol,  &ff_rtmp_protocol,  &ff_rtmpt_protocol,  &ff_rtp_protocol,  &ff_srtp_protocol,  &ff_subfile_protocol,  &ff_tee_protocol,  &ff_tcp_protocol,  &ff_udp_protocol,  &ff_udplite_protocol,  &ff_unix_protocol,  NULL };

这其中,根本就没有一个协议的名称会以rtsp开头。

解决

所以,解决的办法也出来了,有几个:

  1. 更改FFmpeg的源码,在protocol_list.c的protocol_list中,加入rtsp的协议。
  2. 不使用avio_open这个函数,自己实现一个打开AVIOContext的函数,在这个函数中,打开rtsp服务器的连接。
  3. 给avio_open传入一个不是rtsp协议的文件名,但是可以打开到rtsp服务器的连接。

其中,第一个最彻底,但是需要改变宿主系统上的FFmpeg,这个有时候不是很方便。

第二个可以实现,但是稍微麻烦一点。

第三个最简单,RTSP协议走的就是TCP协议,而protocol_list中就包括TCP。

所以,我使用了:

int my_avio_open(AVFormatContext *fmt, const string &uri) {auto real_uri = uri;  auto pos = uri.find (':');  if (pos == string::npos)  {  _warning ("uri '%s' error", uri.c_str ());  return -1;  }  real_uri = string ("tcp") + uri.substr (pos);  s = avio_open (&fmt->pb, real_uri.c_str (), AVIO_FLAG_WRITE);return 0;}

就暂时性地算是丑陋地解决了Protocol not found的问题了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com