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; }
若有收获,就点个赞吧