FFmpeg实现http、https这些标准协议,但是要播放加密视频怎么办呢?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;
FFmpeg实现的标准协议有包括http、https、hls、tcp、rtmp等,如下图所示:
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/config.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 ijkmp_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, "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, "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) {
......
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;
}
|