你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

ijkplayer自定义协议播放加密视频

2021/11/15 19:34:07

FFmpeg实现http、https、hls、tcp、rtmp、ftp这些标准协议,但是要播放加密视频怎么办呢?ijkplayer在FFmpeg的libavformat模块进行扩展ijkio、ijklongurl、ijktcphook、ijkhttphook,我们也可以在这个基础上,自定义协议来进行解密播放。主要基于URLProtocol和AVClass进行扩展,实现protocol对应的方法。

URLProtocol的结构体如下:

typedef struct URLProtocol {
    const char *name;
    int     (*url_open)( URLContext *h, const char *url, int flags);
    int     (*url_open2)(URLContext *h, const char *url, int flags, AVDictionary **options);
    int     (*url_accept)(URLContext *s, URLContext **c);
    int     (*url_handshake)(URLContext *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 priv_data_size;
    const AVClass *priv_data_class;
    const char *default_whitelist;
} URLProtocol;

1、添加自定义协议

新建一个源文件ijkdecrypt.c放在libavformat,实现ijkdecrypt_open、ijkdecrypt_read、ijkdecrypt_seek、ijkdecrypt_close等方法,然后把方法注册到URLProtocol:

static int ijkdecrypt_open(URLContext *h) {
    return decrypt_open(h);
}

static int ijkdecrypt_read(URLContext *h, unsigned char *buf, int size) {
    return decrypt_read(h, buf, size);
}

static int64_t decrypt_seek(URLContext *h, int64_t offset, int whence) {
    return decrypt_seek(h, offset, whence);
}

static int decrypt_close(URLContext *h) {
    return decrypt_close(h);
}

static const AVClass ijkio_context_class = {
    .class_name = "IjkDecrypt",
    .item_name  = av_default_item_name,
    .option     = options,
    .version    = LIBAVUTIL_VERSION_INT,
};

URLProtocol ijkimp_ff_ijkio_protocol = {
    .name                = "ijkdecrypt",
    .url_open2           = ijkdecrypt_open,
    .url_read            = ijkdecrypt_read,
    .url_seek            = ijkdecrypt_seek,
    .url_close           = ijkdecrypt_close,
    .priv_data_size      = sizeof(Context),
    .priv_data_class     = &ijkio_context_class,
};

2、声明自定义协议

修该libavformat/protocols.c,添加自定义协议并声明为全局变量:

extern const URLProtocol ff_ijkdecrypt_protocol;

在ffbuild/comfig.mak会自动生成CONFIG_FF_IJKDECRYPT_PROTOCOL

3、依赖自定义协议

在libavformat/makefile添加依赖文件:

OBJS-$(CONFIG_FF_IJKDECRYPT_PROTOCOL)             += ijkdecrypt.o

4、dummy自定义协议

在libavformat/ijkutils.c添加dummy的ijkdecrypt:

IJK_DUMMY_PROTOCOL(ijkdecrypt);

dummy过程是生成AVClass和URLProtocol:

#define IJK_DUMMY_PROTOCOL(x)                                       \
IJK_FF_PROTOCOL(x);                                                 \
static const AVClass ijk_##x##_context_class = {                    \
    .class_name = #x,                                               \
    .item_name  = av_default_item_name,                             \
    .version    = LIBAVUTIL_VERSION_INT,                            \
    };                                                              \
                                                                    \
URLProtocol ff_##x##_protocol = {                                   \
    .name                = #x,                                      \
    .url_open2           = ijkdummy_open,                           \
    .priv_data_size      = 1,                                       \
    .priv_data_class     = &ijk_##x##_context_class,                \
};

5、注册自定义协议

在allformats.c调用ijkav_register_all进行注册自定义协议:

void ijkav_register_all(void)
{
    av_register_all();
    /* protocols */
#ifdef __ANDROID__
    IJK_REGISTER_PROTOCOL(ijkmediadatasource);
#endif
    IJK_REGISTER_PROTOCOL(ijkio);
    IJK_REGISTER_PROTOCOL(async);
    IJK_REGISTER_PROTOCOL(ijklongurl);
    IJK_REGISTER_PROTOCOL(ijktcphook);
    IJK_REGISTER_PROTOCOL(ijkhttphook);
    IJK_REGISTER_PROTOCOL(ijksegment);
    /* demuxers */
    IJK_REGISTER_DEMUXER(ijklivehook);
    IJK_REGISTER_DEMUXER(ijklas);
}

IJK_REGISTER_PROTOCOL()对应的宏定义:

#define IJK_REGISTER_PROTOCOL(x)                                                        \
    {                                                                                   \
        extern URLProtocol ijkimp_ff_##x##_protocol;                                    \
        int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size);    \
        ijkav_register_##x##_protocol(&ijkimp_ff_##x##_protocol, sizeof(URLProtocol));  \
    }

其中ijkav_register_##x##_protocol的宏定义如下:

int ijkav_register_##x##_protocol(URLProtocol *protocol, int protocol_size)                         \
{                                                                                                   \
    if (protocol_size != sizeof(URLProtocol)) {                                                     \
        av_log(NULL, AV_LOG_ERROR, "ijkav_register_##x##_protocol: ABI mismatch.\n");               \
        return -1;                                                                                  \
    }                                                                                               \
    memcpy(&ff_##x##_protocol, protocol, protocol_size);                                            \
    return 0;                                                                                       \
}

6、拦截自定义协议

首先在ijkioprotocol.c声明自定义协议:

extern IjkURLProtocol ijkio_decrypt_rotocol;

然后对scheme进行拦截,把自定义协议赋值给IjkURLProtocol:

int ijkio_alloc_url(IjkURLContext **ph, const char *url) {
    if (!ph) {
        return -1;
    }
    IjkURLContext *h = NULL;
    if (!strncmp(url, "cache:", strlen("cache:"))) {
        h = (IjkURLContext *)calloc(1, sizeof(IjkURLContext));
        h->prot = &ijkio_cache_protocol;
        h->priv_data = calloc(1, ijkio_cache_protocol.priv_data_size);
    } else if (!strncmp(url, "ffio:", strlen("ffio:"))) {
        h = (IjkURLContext *)calloc(1, sizeof(IjkURLContext));
        h->prot = &ijkio_ffio_protocol;
        h->priv_data = calloc(1, ijkio_ffio_protocol.priv_data_size);
    } else if (!strncmp(url, "httphook:", strlen("httphook:"))) {
        h = (IjkURLContext *)calloc(1, sizeof(IjkURLContext));
        h->prot = &ijkio_httphook_protocol;
        h->priv_data = calloc(1, ijkio_httphook_protocol.priv_data_size);
    } else if (!strncmp(url, "decrypt:", strlen("decrypt:"))) {
        h = (IjkURLContext *)calloc(1, sizeof(IjkURLContext));
        h->prot = &ijkio_decrypt_protocol;
        h->priv_data = calloc(1, ijkio_decrypt_protocol.priv_data_size);
    } else {
        return -1;
    }
    *ph = h;
    return 0;
}

7、查找自定义协议

在avformat_open_input时,会初始化input、打开avio、根据scheme查找对应协议,完整调用路径为init_input->avio_open2->ffurl_open->ffurl_alloc->url_find_protocol。在avio.c的查找协议过程为:

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;

    ......
    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;
}

若有收获,就点个赞吧