美文网首页
ffmpeg中samba网络协议的兼容分析(一)

ffmpeg中samba网络协议的兼容分析(一)

作者: yellowei | 来源:发表于2020-08-21 17:26 被阅读0次

    使用ffmpeg解码流,首先通常是调用FFmpeg的avformat_open_input打开filename(其实就是链接)

    而在avformat_open_input内部,做了一个网络协议匹配的机制,以samba举例

    首先,看到调用流程

    graph LR
    avformat_open_input-->init_input
    init_input-->io_open(io_open_default) 
    io_open(io_open_default)-->ffio_open_whitelist
    ffio_open_whitelist-->ffurl_open_whitelist
    ffurl_open_whitelist-->ffurl_alloc
    ffurl_alloc-->URLContext
    
    ffio_open_whitelist-->ffio_fdopen
    
    image

    可以看到avformat_open_input中,先初始化input

    int avformat_open_input(AVFormatContext **ps, const char *filename,
                            AVInputFormat *fmt, AVDictionary **options)
        ......
        av_strlcpy(s->filename, filename ? filename : "",sizeof(s->filename));
        if ((ret = init_input(s, filename, &tmp)) < 0)
            goto fail;
        s->probe_score = ret;
        ......
                            
    

    在init_input函数中,可以看到对io库的调用,这里是以samba举例,那么io库会对应到ffurl

    /* Open input file and probe the format if necessary. */
    static int init_input(AVFormatContext *s, const char *filename,
                          AVDictionary **options)
    {
        int ret;
        AVProbeData pd = { filename, NULL, 0 };
        int score = AVPROBE_SCORE_RETRY;
    
        if (s->pb) {
            s->flags |= AVFMT_FLAG_CUSTOM_IO;
            if (!s->iformat)
                return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                             s, 0, s->format_probesize);
            else if (s->iformat->flags & AVFMT_NOFILE)
                av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
                                          "will be ignored with AVFMT_NOFILE format.\n");
            return 0;
        }
    
        if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
            (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
            return score;
    
        if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
            return ret;
    
        if (s->iformat)
            return 0;
        return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                     s, 0, s->format_probesize);
    }
    

    上面是完整的init_input,通过调试,找到关键的代码是这句

    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;
    

    这里又调用了io_open,这里的io_open其实是一个函数指针

    int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
                       int flags, AVDictionary **options);
    

    那么这个函数指针会执行什么样的代码呢?接下来在哪里调用呢?
    那就调试一下吧

    image

    不难发现,这个io_open的指针在options.c的104行被初始化了,那我就去找找看

    AVFormatContext *avformat_alloc_context(void)
    {
        AVFormatContext *ic;
        ic = av_malloc(sizeof(AVFormatContext));
        if (!ic) return ic;
        avformat_get_context_defaults(ic);
    
        ic->internal = av_mallocz(sizeof(*ic->internal));
        if (!ic->internal) {
            avformat_free_context(ic);
            return NULL;
        }
        ic->internal->offset = AV_NOPTS_VALUE;
        ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
        ic->internal->shortest_end = AV_NOPTS_VALUE;
    
        return ic;
    }
    
    static void avformat_get_context_defaults(AVFormatContext *s)
    {
        memset(s, 0, sizeof(AVFormatContext));
    
        s->av_class = &av_format_context_class;
    
        s->io_open  = io_open_default;
        s->io_close = io_close_default;
    
        av_opt_set_defaults(s);
    }
    
    static int io_open_default(AVFormatContext *s, AVIOContext **pb,
                               const char *url, int flags, AVDictionary **options)
    {
        int loglevel;
    
        if (!strcmp(url, s->filename) ||
            s->iformat && !strcmp(s->iformat->name, "image2") ||
            s->oformat && !strcmp(s->oformat->name, "image2")
        ) {
            loglevel = AV_LOG_DEBUG;
        } else
            loglevel = AV_LOG_INFO;
    
        av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");
    
    #if FF_API_OLD_OPEN_CALLBACKS
    FF_DISABLE_DEPRECATION_WARNINGS
        if (s->open_cb)
            return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
    FF_ENABLE_DEPRECATION_WARNINGS
    #endif
    
        return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
    }
    

    这下可好,找到关键代码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;
    
        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;
    }
    

    这个函数中有两处关键代码,分别是对函数ffurl_open_whitelist和ffio_fdopen的调用,并且ffio_fdopen的第二参数是由ffurl_open_whitelist修改而来的,可以确定两个函数分工明确,关系纽带就是这个参数h

    h在这里是一个URLContext*的类型

    typedef struct URLContext {
        const AVClass *av_class;    /**< information for av_log(). Set by url_open(). */
        const struct URLProtocol *prot;
        void *priv_data;
        char *filename;             /**< specified URL */
        int flags;
        int max_packet_size;        /**< if non zero, the stream is packetized with this max packet size */
        int is_streamed;            /**< true if streamed (no seek possible), default = false */
        int is_connected;
        AVIOInterruptCB interrupt_callback;
        int64_t rw_timeout;         /**< maximum time to wait for (network) read/write operation completion, in mcs */
        const char *protocol_whitelist;
        const char *protocol_blacklist;
        int min_packet_size;        /**< if non zero, the stream is packetized with this min packet size */
    } URLContext;
    
    

    上面就是这个URLContext结构体了,那么他是怎么被初始化的呢?接着回到ffurl_open_whitelist中分析

    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)
            av_opt_copy(*puc, parent);
        if (options &&
            (ret = av_opt_set_dict(*puc, options)) < 0)
            goto fail;
        if (options && (*puc)->prot->priv_data_class &&
            (ret = av_opt_set_dict((*puc)->priv_data, options)) < 0)
            goto fail;
    
        if (!options)
            options = &tmp_opts;
    
        av_assert0(!whitelist ||
                   !(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
                   !strcmp(whitelist, e->value));
        av_assert0(!blacklist ||
                   !(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
                   !strcmp(blacklist, e->value));
    
        if ((ret = av_dict_set(options, "protocol_whitelist", whitelist, 0)) < 0)
            goto fail;
    
        if ((ret = av_dict_set(options, "protocol_blacklist", blacklist, 0)) < 0)
            goto fail;
    
        if ((ret = av_opt_set_dict(*puc, options)) < 0)
            goto fail;
    
        ret = ffurl_connect(*puc, options);
    
        if (!ret)
            return 0;
    fail:
        ffurl_close(*puc);
        *puc = NULL;
        return ret;
    }
    

    又是很长的一个函数,一对操作猛如虎有木有?其实仔细一看,内容除了对AVDictionary的操作之外也没有啥了,关键的ffurl_allocffurl_connect成为了我们接下来分析的重点

    继续剥开

    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;
        if (av_strstart(filename, "https:", NULL))
            av_log(NULL, AV_LOG_WARNING, "https protocol not found, recompile FFmpeg with "
                                         "openssl, gnutls "
                                         "or securetransport enabled.\n");
        return AVERROR_PROTOCOL_NOT_FOUND;
    }
    
    

    这下就有意思了,还记得我么熟悉的老朋友filename吗?这不正是我们传入的链接吗?当看到url_find_protocol函数的时候我就明白了

    此函数寻找相匹配的协议, 找不到就AVERROR_PROTOCOL_NOT_FOUND, 找到就url_alloc_for_protocol()

    那我先看这个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);
    
        return NULL;
    }
    

    简单解析一下这个函数:url_find_protocol就是通过for循环匹配protocols = ffurl_get_protocols(NULL, NULL)列表,而ffurl_get_protocols是获取一个url协议列表,这个列表根据在make之前的configure文件中的url_protocols来构造数据,下面展示configure文件部分代码:

    ...
    
    # generate the lists of enabled components
    print_enabled_components(){
        file=$1
        struct_name=$2
        name=$3
        shift 3
        echo "static const $struct_name * const $name[] = {" > $TMPH
        for c in $*; do
            enabled $c && printf "    &ff_%s,\n" $c >> $TMPH
        done
        echo "    NULL };" >> $TMPH
        cp_if_changed $TMPH $file
    }
    
    ...
    
    print_enabled_components libavformat/protocol_list.c URLProtocol url_protocols $PROTOCOL_LIST
    

    其实就是在执行configure脚本的时候,通过print_enabled_components在ffmpeg源文件中添加关于url_protocols的相关代码

    匹配了支持的网络协议之后,回到ffurl_alloc中的url_alloc_for_protocol,看到代码

    static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                      const char *filename, int flags,
                                      const AVIOInterruptCB *int_cb)
    {
        URLContext *uc;
        int err;
    
    #if CONFIG_NETWORK
        if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
            return AVERROR(EIO);
    #endif
        if ((flags & AVIO_FLAG_READ) && !up->url_read) {
            av_log(NULL, AV_LOG_ERROR,
                   "Impossible to open the '%s' protocol for reading\n", up->name);
            return AVERROR(EIO);
        }
        if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
            av_log(NULL, AV_LOG_ERROR,
                   "Impossible to open the '%s' protocol for writing\n", up->name);
            return AVERROR(EIO);
        }
        uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
        if (!uc) {
            err = AVERROR(ENOMEM);
            goto fail;
        }
        uc->av_class = &ffurl_context_class;
        uc->filename = (char *)&uc[1];
        strcpy(uc->filename, filename);
        uc->prot            = up;
        uc->flags           = flags;
        uc->is_streamed     = 0; /* default = not streamed */
        uc->max_packet_size = 0; /* default: stream file */
        if (up->priv_data_size) {
            uc->priv_data = av_mallocz(up->priv_data_size);
            if (!uc->priv_data) {
                err = AVERROR(ENOMEM);
                goto fail;
            }
            if (up->priv_data_class) {
                int proto_len= strlen(up->name);
                char *start = strchr(uc->filename, ',');
                *(const AVClass **)uc->priv_data = up->priv_data_class;
                av_opt_set_defaults(uc->priv_data);
                if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){
                    int ret= 0;
                    char *p= start;
                    char sep= *++p;
                    char *key, *val;
                    p++;
    
                    if (strcmp(up->name, "subfile"))
                        ret = AVERROR(EINVAL);
    
                    while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){
                        *val= *key= 0;
                        if (strcmp(p, "start") && strcmp(p, "end")) {
                            ret = AVERROR_OPTION_NOT_FOUND;
                        } else
                            ret= av_opt_set(uc->priv_data, p, key+1, 0);
                        if (ret == AVERROR_OPTION_NOT_FOUND)
                            av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);
                        *val= *key= sep;
                        p= val+1;
                    }
                    if(ret<0 || p!=key){
                        av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);
                        av_freep(&uc->priv_data);
                        av_freep(&uc);
                        err = AVERROR(EINVAL);
                        goto fail;
                    }
                    memmove(start, key+1, strlen(key));
                }
            }
        }
        if (int_cb)
            uc->interrupt_callback = *int_cb;
    
        *puc = uc;
        return 0;
    fail:
        *puc = NULL;
        if (uc)
            av_freep(&uc->priv_data);
        av_freep(&uc);
    #if CONFIG_NETWORK
        if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
            ff_network_close();
    #endif
        return err;
    }
    

    此函数真不好阅读,都是些没见过的函数

    仔细分析,首先理解函数的功能:构造URLContext

    这里不得不提一嘴这个:

    static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                      const char *filename, int flags,
                                      const AVIOInterruptCB *int_cb)
        ......
        c->av_class = &ffurl_context_class;
        uc->filename = (char *)&uc[1];
        strcpy(uc->filename, filename);
        uc->prot            = up;//这句很关键
        uc->flags           = flags;
        ......
    }
    

    这里的up就是刚刚通过url_find_protocol函数找到的URLProtocol结构体指针,

    
    typedef struct URLProtocol {
        const char *name;
        int     (*url_open)( URLContext *h, const char *url, int flags);
        /**
         * This callback is to be used by protocols which open further nested
         * protocols. options are then to be passed to ffurl_open()/ffurl_connect()
         * for those nested protocols.
         */
        int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
        int     (*url_accept)(URLContext *s, URLContext **c);
        int     (*url_handshake)(URLContext *c);
    
        /**
         * Read data from the protocol.
         * If data is immediately available (even less than size), EOF is
         * reached or an error occurs (including EINTR), return immediately.
         * Otherwise:
         * In non-blocking mode, return AVERROR(EAGAIN) immediately.
         * In blocking mode, wait for data/EOF/error with a short timeout (0.1s),
         * and return AVERROR(EAGAIN) on timeout.
         * Checking interrupt_callback, looping on EINTR and EAGAIN and until
         * enough data has been read is left to the calling function; see
         * retry_transfer_wrapper in avio.c.
         */
        int     (*url_read)( URLContext *h, unsigned char *buf, int size);
        int     (*url_write)(URLContext *h, const unsigned char *buf, int size);
        int64_t (*url_seek)( URLContext *h, int64_t pos, int whence);
        int     (*url_close)(URLContext *h);
        int (*url_read_pause)(URLContext *h, int pause);
        int64_t (*url_read_seek)(URLContext *h, int stream_index,
                                 int64_t timestamp, int flags);
        int (*url_get_file_handle)(URLContext *h);
        int (*url_get_multi_file_handle)(URLContext *h, int **handles,
                                         int *numhandles);
        int (*url_get_short_seek)(URLContext *h);
        int (*url_shutdown)(URLContext *h, int flags);
        int priv_data_size;
        const AVClass *priv_data_class;
        int flags;
        int (*url_check)(URLContext *h, int mask);
        int (*url_open_dir)(URLContext *h);
        int (*url_read_dir)(URLContext *h, AVIODirEntry **next);
        int (*url_close_dir)(URLContext *h);
        int (*url_delete)(URLContext *h);
        int (*url_move)(URLContext *h_src, URLContext *h_dst);
        const char *default_whitelist;
    } URLProtocol;
    

    它代表着某种协议,这里以samba举例

    在开发人员编写ffmpeg的时候,当考虑到要支持samba,那么会在相关代码(libsmbclient.h)的文件内构造一个URLProtocol结构体,此结构体如下:

    const URLProtocol ff_libsmbclient_protocol = {
        .name                = "smb",
        .url_open            = libsmbc_open,
        .url_read            = libsmbc_read,
        .url_write           = libsmbc_write,
        .url_seek            = libsmbc_seek,
        .url_close           = libsmbc_close,
        .url_delete          = libsmbc_delete,
        .url_move            = libsmbc_move,
        .url_open_dir        = libsmbc_open_dir,
        .url_read_dir        = libsmbc_read_dir,
        .url_close_dir       = libsmbc_close_dir,
        .priv_data_size      = sizeof(LIBSMBContext),
        .priv_data_class     = &libsmbclient_context_class,
        .flags               = URL_PROTOCOL_FLAG_NETWORK,
    };
    

    当调用ffurl_alloc时,会通过url_find_protocol找到samba协议的结构体指针

    该结构体指针描述了samba协议要用到的那些函数:libsmbc_open、libsmbc_read、libsmbc_read等

    到此,我大致明白了程序的执行关系

    graph LR
    ffurl_alloc-->url_find_protocol
    url_find_protocol-->URLProtocol*
    URLProtocol*-->url_open等
    
    image

    再回到url_alloc_for_protocol函数,这里的interrupt_callback是一个回调,用于解码的流程控制,是否终中断解码

    Objective-C的代码是这样的:

    //定义block
    static int interrupt_callback(void *ctx)
    {
        if (!ctx)
            return 0;
        __unsafe_unretained CYPlayerDecoder *p = (__bridge CYPlayerDecoder *)ctx;
        const BOOL r = [p interruptDecoder];
        if (r) LoggerStream(1, @"DEBUG: INTERRUPT_CALLBACK!");
        return r;
    }
    //传入ffmpeg
    - (cyPlayerError) openInput: (NSString *) path
    {
        AVFormatContext *formatCtx = NULL;
    
        if (_interruptCallback) {
            
            formatCtx = avformat_alloc_context();
            if (!formatCtx)
                return cyPlayerErrorOpenFile;
            
            __weak typeof(&*self)weakSelf = self;
            AVIOInterruptCB cb = {
                interrupt_callback,
                (__bridge void *)(weakSelf)
            };
            formatCtx->interrupt_callback = cb;
        }
        else
        {
            formatCtx = avformat_alloc_context();
            if (!formatCtx)
                return cyPlayerErrorOpenFile;
        }
        
        ......//略
        
        if (avformat_open_input(&formatCtx, [path cStringUsingEncoding: NSUTF8StringEncoding], NULL, &_options) < 0) {
            
            if (formatCtx)
                avformat_free_context(formatCtx);
            return cyPlayerErrorOpenFile;
        }
        
        ......//略
    }
    

    接着,分下下一个关键函数ffurl_connect

    int ffurl_connect(URLContext *uc, AVDictionary **options)
    {
        int err;
        AVDictionary *tmp_opts = NULL;
        AVDictionaryEntry *e;
    
        if (!options)
            options = &tmp_opts;
    
        // Check that URLContext was initialized correctly and lists are matching if set
        av_assert0(!(e=av_dict_get(*options, "protocol_whitelist", NULL, 0)) ||
                   (uc->protocol_whitelist && !strcmp(uc->protocol_whitelist, e->value)));
        av_assert0(!(e=av_dict_get(*options, "protocol_blacklist", NULL, 0)) ||
                   (uc->protocol_blacklist && !strcmp(uc->protocol_blacklist, e->value)));
    
        if (uc->protocol_whitelist && av_match_list(uc->prot->name, uc->protocol_whitelist, ',') <= 0) {
            av_log(uc, AV_LOG_ERROR, "Protocol '%s' not on whitelist '%s'!\n", uc->prot->name, uc->protocol_whitelist);
            return AVERROR(EINVAL);
        }
    
        if (uc->protocol_blacklist && av_match_list(uc->prot->name, uc->protocol_blacklist, ',') > 0) {
            av_log(uc, AV_LOG_ERROR, "Protocol '%s' on blacklist '%s'!\n", uc->prot->name, uc->protocol_blacklist);
            return AVERROR(EINVAL);
        }
    
        if (!uc->protocol_whitelist && uc->prot->default_whitelist) {
            av_log(uc, AV_LOG_DEBUG, "Setting default whitelist '%s'\n", uc->prot->default_whitelist);
            uc->protocol_whitelist = av_strdup(uc->prot->default_whitelist);
            if (!uc->protocol_whitelist) {
                return AVERROR(ENOMEM);
            }
        } else if (!uc->protocol_whitelist)
            av_log(uc, AV_LOG_DEBUG, "No default whitelist set\n"); // This should be an error once all declare a default whitelist
    
        if ((err = av_dict_set(options, "protocol_whitelist", uc->protocol_whitelist, 0)) < 0)
            return err;
        if ((err = av_dict_set(options, "protocol_blacklist", uc->protocol_blacklist, 0)) < 0)
            return err;
    
        err =
            uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                      uc->filename,
                                                      uc->flags,
                                                      options) :
            uc->prot->url_open(uc, uc->filename, uc->flags);
    
        av_dict_set(options, "protocol_whitelist", NULL, 0);
        av_dict_set(options, "protocol_blacklist", NULL, 0);
    
        if (err)
            return err;
        uc->is_connected = 1;
        /* We must be careful here as ffurl_seek() could be slow,
         * for example for http */
        if ((uc->flags & AVIO_FLAG_WRITE) || !strcmp(uc->prot->name, "file"))
            if (!uc->is_streamed && ffurl_seek(uc, 0, SEEK_SET) < 0)
                uc->is_streamed = 1;
        return 0;
    }
    

    这个函数就是播放网络流的窗口,如果之前的代码执行都没问题,就会来到这里

    err =
            uc->prot->url_open2 ? uc->prot->url_open2(uc,
                                                      uc->filename,
                                                      uc->flags,
                                                      options) :
            uc->prot->url_open(uc, uc->filename, uc->flags);
    

    由于samba没有定义url_open2,那uc->prot->url_open就是通过函数指针去调用samba的libsmbc_open()了

    static av_cold int libsmbc_open(URLContext *h, const char *url, int flags)
    {
        LIBSMBContext *libsmbc = h->priv_data;
        int access, ret;
        struct stat st;
    
        libsmbc->fd = -1;
        libsmbc->filesize = -1;
    
        if ((ret = libsmbc_connect(h)) < 0)
            goto fail;
    
        if ((flags & AVIO_FLAG_WRITE) && (flags & AVIO_FLAG_READ)) {
            access = O_CREAT | O_RDWR;
            if (libsmbc->trunc)
                access |= O_TRUNC;
        } else if (flags & AVIO_FLAG_WRITE) {
            access = O_CREAT | O_WRONLY;
            if (libsmbc->trunc)
                access |= O_TRUNC;
        } else
            access = O_RDONLY;
    
        /* 0666 = -rw-rw-rw- = read+write for everyone, minus umask */
        if ((libsmbc->fd = smbc_open(url, access, 0666)) < 0) {
            ret = AVERROR(errno);
            av_log(h, AV_LOG_ERROR, "File open failed: %s\n", strerror(errno));
            goto fail;
        }
    
        if (smbc_fstat(libsmbc->fd, &st) < 0)
            av_log(h, AV_LOG_WARNING, "Cannot stat file: %s\n", strerror(errno));
        else
            libsmbc->filesize = st.st_size;
    
        return 0;
      fail:
        libsmbc_close(h);
        return ret;
    }
    

    这个函数的关键正是对samba库的接入,看,它终于在这里忍不住调用smbc_open()了

    可是为什么,我的播放器在模拟器上可以正常播放smb://的链接,到真机上就是报错了呢:

    [smb @ 0x107d041d0] File open failed: Permission denied
    

    好吧,又要看下samba的代码了......

    相关文章

      网友评论

          本文标题:ffmpeg中samba网络协议的兼容分析(一)

          本文链接:https://www.haomeiwen.com/subject/pnqpjktx.html