目录
目的
因为某些公司可能不想用通用的拉流协议(如http),会自定义一种新的协议比如起名ijkbuffersource://这种url来拉流播放。
私有协议的工作流程
协议注册过程
#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)); \
}
我们以ijkbuffersource为例,则它等效于
ijkav_register_ijkbuffersource_protocol(&ijkimp_ff_ijkbuffersource_protocol, sizeof(URLProtocol));
ijkimp_ff_ijkbuffersource_protocol 实现私有协议的open, read, write,close,init等方法
URLProtocol ijkimp_ff_ijkbuffersource_protocol = {
.name = "ijkbuffersource",
.url_open2 = ijkbuffer_open,
.url_read = ijkbuffer_read,
.url_seek = ijkbuffer_seek,
.url_close = ijkbuffer_close,
.priv_data_size = sizeof(Context),
.priv_data_class = &ijkbuffersource_context_class,
};
IJK_REGISTER_DEMUXER解封装分析
#define IJK_REGISTER_DEMUXER(x) \
{ \
extern AVInputFormat ijkff_##x##_demuxer; \
ijkav_register_input_format(&ijkff_##x##_demuxer); \
}
从上面这段代码我们可以看出,真正注册的函数是av_register_input_format(&ff_aac_demuxer),那我就看看这个和函数的作用,查看一下av_register_input_format()的代码:
void av_register_input_format(AVInputFormat *format)
{
AVInputFormat **p = last_iformat;
// Note, format could be added after the first 2 checks but that implies that *p is no longer NULL
while(p != &format->next && !format->next && avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format))
p = &(*p)->next;
if (!format->next)
last_iformat = &format->next;
}
这段代码是比较容易理解的,首先先提一点,first_iformat是个什么东东呢?其实它是Input Format链表的头部地址,是一个全局静态变量,定义如下:
/** head of registered input format linked list */
static AVInputFormat *first_iformat = NULL;
由此我们可以分析出av_register_input_format()的含义,一句话概括就是:遍历链表并把当前的Input Format加到链表的尾部。
至此REGISTER_DEMUXER (X)分析完毕。
初始化AVIOFormat函数调用关系
私有协议实现过程
step1, 在实现私有协议的open, read, write,close,init等方法。及URLProtocol结构体对象如ijkimp_ff_ijkbuffersource_protocol 于单个.c文件中,如buffersource.c
static const AVClass ijkbuffersource_context_class = {
.class_name = "IjkBufferSource",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
URLProtocol ijkimp_ff_ijkbuffersource_protocol = {
.name = "ijkbuffersource",
.url_open2 = ijkbuffer_open,
.url_read = ijkbuffer_read,
.url_seek = ijkbuffer_seek,
.url_close = ijkbuffer_close,
.priv_data_size = sizeof(Context),
.priv_data_class = &ijkbuffersource_context_class,
};
其中ijkbuffer_open,ijkbuffer_read, ijkbuffer_seek, ijkbuffer_close为要实现的私有协议内容
step2, 将实现好的私有协议文件(buffersource.c文件)加入libavformat目录
step3, 修改ffmpeg/libavformat/protocols.c, 添加自定义网络协议URLProtocol全局变量声明
ffbuild\config.mak(这文件是自动生成,不用修改)
CONFIG_IJKBUFFERSOURCE_PROTOCOL=yes
libavformat\protocols.c
extern const URLProtocol ff_ijkbuffersource_protocol;
libavformat/ijkutils.c
IJK_DUMMY_PROTOCOL(ijkbuffersource);
libavformat\protocol_list.c(这文件是自动生成,不用修改)
&ff_ijkbuffersource_protocol,
libavformat\Makefile
OBJS-$(CONFIG_IJKBUFFERSOURCE_PROTOCOL) += ijkbuffersource.o
私有协议接口分析
typedef struct Context {
AVClass *class;
/* options */
int64_t logical_pos;//当前读取的指针位置
int64_t logical_size;//总文件大小
int64_t media_data_source_ptr;
jobject media_data_source;//id
jbyteArray jbuffer;//data buffer
int jbuffer_capacity;//一次读取的byte,32768
} Context;
/*
* 在 ijkmds_open 中,只需要把 intptr_t 的变量转换成 jobject 就行,不必实际去打开某个文件。 ijkmds_read 、 ijkmds_seek
* 函数通过 J4A 去调用 IMediaDataSource 接口的 readAt 方法。 readAt 方法多了 pos 参数,所以 read 和 seek 都可以通过 readAt
* 实现, ijkmds_close 调用 IMediaDataSource 接口的 close 方法,并释放 NDK 环境中的 jobject 全局引用。
*/
static int ijkmds_open(URLContext *h, const char *arg, int flags, AVDictionary **options)
{
Context *c = h->priv_data;
JNIEnv *env = NULL;
jobject media_data_source = NULL;
char *final = NULL;
av_log(h, AV_LOG_ERROR, "Rapid ijkmds_open arg = %s", &arg);//"ijkmediadatasource:10598"
av_strstart(arg, "ijkmediadatasource:", &arg);//10598,这个值会变化
media_data_source = (jobject) (intptr_t) strtoll(arg, &final, 10);//10598
if (!media_data_source)
return AVERROR(EINVAL);
//SDL_JNI_SetupThreadEnv(&env)这个是干嘛用的?创建线程,得到env
//通过SDL库创建一个线程,该函数允许传入函数指针,使得该函数在创建的线程中执行
if (JNI_OK != SDL_JNI_SetupThreadEnv(&env)) {
av_log(h, AV_LOG_ERROR, "%s: SDL_JNI_SetupThreadEnv: failed", __func__);
return AVERROR(EINVAL);
}
//直播不能读取长度,为什么要获取size?因为回调里会先加载所有buffer,然后通过offset读取偏移的buffer
c->logical_size = J4AC_IMediaDataSource__getSize(env, media_data_source);//如果直播,可以设置为getSize为-1
if (J4A_ExceptionCheck__catchAll(env)) {
return AVERROR(EINVAL);
} else if (c->logical_size < 0) {
h->is_streamed = 1;//直播流 ==1,要不会播放不出来,uc->is_streamed = 0; /* default = not streamed */
c->logical_size = -1;
}
c->media_data_source = (*env)->NewGlobalRef(env, media_data_source);
if (J4A_ExceptionCheck__catchAll(env) || !c->media_data_source) {
return AVERROR(ENOMEM);
}
return 0;
}
调试过程记录
在ijkbds_seek添加
c->buffer = jbuffer_grow(h, 0);
ret = ijk_mediasource_readAt(c->logical_pos, c->buffer, 0, 0);
if (ret < 0)
return AVERROR_EOF;
else if (ret == 0)
return AVERROR(EAGAIN);
会出现两种情况
写缓冲buffer为512K,播放阿里云视频会播放不了,而MFC的影片可以播放正常
而去掉ijkbds_seek这部分代码,阿里云播放正常,seek也正常,但是mfc的影片播放不了
就会出现