转:analyze ijkplayer code

看了很久的ijkplayer的视频播放,其实还是没有怎么看懂,只是个人浅浅的笔记

关键部分就是联网获取数据那部分,还没有搞定其实

从用户点击一个已有地址的网络视频开始,从源码分析播放流程。

1.        // init player  加载native底层库

IjkMediaPlayer.loadLibrariesOnce(null);

IjkMediaPlayer.native_profileBegin("libijkplayer.so");

第一句话是加载三个重要的so文件

libLoader.loadLibrary("ijkffmpeg");

libLoader.loadLibrary("ijksdl");

libLoader.loadLibrary("ijkplayer");

第二句话调用了一个native方法public static native void native_profileBegin(String libName);

2.  设置uri,可以是rtmp,rtsp,http等,native ffplay代码中会根据该uri匹配不同的流媒体协议,具体参考ffplay。 支持本地和网络视频。

if (mVideoPath != null)

mVideoView.setVideoPath(mVideoPath);

else if (mVideoUri != null)

mVideoView.setVideoURI(mVideoUri);

3. 分析网络视频源:接着openVideo()

先创建播放器 mMediaPlayer = createPlayer(mSettings.getPlayer());接着是播放器的相关设置

诸如:       mMediaPlayer.setOnPreparedListener(mPreparedListener);

mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);

mMediaPlayer.setOnCompletionListener(mCompletionListener);

mMediaPlayer.setOnErrorListener(mErrorListener);

mMediaPlayer.setOnInfoListener(mInfoListener);

mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);等

当然,还需要设置播放源,会调用

tv.danmaku.ijk.media.player.IMediaPlayer#setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>)

开始播放mVideoView.start();调用tv.danmaku.ijk.media.player.IMediaPlayer#start,这里java层就结束了,进入c底层,其实从加载native底层库开始就进入c底层了

4. 分析c底层:/ijkmedia/ijkplayer/android/ijkplayer_jni.c这个文件里 static JNINativeMethod g_methods[] 一个数组,声明了全部的java层的native方法 ,而调用native的方法集中在类 tv.danmaku.ijk.media.player.IjkMediaPlayer

5. 没想到居然是c主动调用java,通过反射生成java端所需要的函数,这个地方不是我们正常JNI的调用思路

正常调用JNI的思路:是通过javah,获取一组带签名函数,然后实现这些函数。这种方法很常用,也是官方推荐的方法。

分析一下JNI运行时机制:

当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库,然后首先试图找到"JNI_OnLoad"函数,如果该函数存在,则调用它。
JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。很显然ijk的作者是使用的这种调用方式

在 ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/android/ijkplayer_jni.c

然后在JNI_OnLoad 初始化好多内容,比如上图三个 global 型的函数

这样就可以理解作者为什么一开始就先调用三个 so 文件了

ijkplayer-sample/src/main/jni/ijkmedia/ijksdl/android/ijksdl_android_jni.c里也有JNI_OnLoad。

Ffmpeg的个人感觉是不需要,因为作者直接使用的ffmpeg的代码,只需要正常引入就好。

6. J4a里生成的两个java文件也是很重要的类

目前还不清楚作用

7. ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay_def.h  这个定义了ijk播放器核心数据结构体的类,作者是直接使用的ffmpeg的ijkplayer-sample/src/main/jni/ffmpeg/ffplay.c在ffmpeg的文件的基础上的扩展,里面引用了很多ffmpeg的源文件。基本上就不需要关心ffmpeg的源代码了。

8. 这里只分析libijkplayer.so

8.1  ijkmp_global_init 在 ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.c

void ijkmp_global_init()

{

ffp_global_init();

}

ffp_global_init() 在

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer_internal.h的include的

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay.h里,具体定义代码在

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ff_ffplay.c中,主要是ffmpeg的初始化工作。

8.2 ijkmp_global_set_inject_callback(inject_callback)是在

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.c的include的

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkavformat/ijkavformat.h里声明,

在ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkavformat/utils.c里定义

这里使用最多的是ijkavformat.h里的

typedef int (*IjkAVInjectCallback)(void *opaque, int message, void *data, size_t data_size);

这个声明引入了 IjkAVInjectCallback 类型作为函数指针的同义字,该函数有四个不同 类型的参数以及一个 int 类型的返回值

IjkAVInjectCallback ijkav_register_inject_callback(IjkAVInjectCallback callback);定义一个函数ijkav_register_inject_callback,这个函数的参数是一个函数指针,返回值也是个函数指针,相当于int ( *ijkav_register_inject_callback( int (*callback)(void *opaque, int message, void *data, size_t data_size) ) )(void *opaque, int message, void *data, size_t data_size);

由于int (*callback)(void *opaque, int message, void *data, size_t data_size)

是IjkAVInjectCallback ,上面的简化一下就是

int ( *ijkav_register_inject_callback( IjkAVInjectCallback ) )(void *opaque, int message, void *data, size_t data_size);这里的

*ijkav_register_inject_callback( IjkAVInjectCallback ) 又是一个返回IjkAVInjectCallback的函数 ,再简化一下,即

int IjkAVInjectCallback (void *opaque, int message, void *data, size_t data_size);

8.3 FFmpegApi_global_init(env)在

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/android/ffmpeg_api_jni.c

主要作用是 初始化 tv.danmaku.ijk.media.player.ffmpeg.FFmpegApi#av_base64_encode 的函数

9. 在System.loadLibrary 三个so文件时作者做了一些初始化的工作,接着调用native native_profileBegin方法,在

ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/android/ijkplayer_jni.c的

IjkMediaPlayer_native_profileBegin 方法,卡在void monstartup(const char *libname);这个函数,没有找到定义的地方。决定试试注释IjkMediaPlayer.native_profileBegin("libijkplayer.so");

tv/danmaku/ijk/media/sample/activities/VideoActivity.java:139

这句话不执行,注释后依旧可以播放,我就不能理解了。哈哈,此处留坑。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

10. 接着看看_setDataSource(String path, String[] keys, String[] values)别看有三个参数,实际调用时只需要一个参数

tv.danmaku.ijk.media.player.IjkMediaPlayer#383     _setDataSource(path, null, null);

关键就是 ijkmp_set_data_source(mp, c_path);这是通过include "ijkplayer_android.h"然后#include "../ijkplayer.h",具体定义在ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.c

ijkmp_set_data_source(IjkMediaPlayer *mp, const char *url)两个参数,一个参数是是IjkMediaPlayer,

typedef struct IjkMediaPlayer IjkMediaPlayer;

(ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer.h)

只是声明了名字,定义在ijkplayer-sample/src/main/jni/ijkmedia/ijkplayer/ijkplayer_internal.h

另一个参数是视频源地址。

这是视频源 mp->data_source = strdup(url);这样IjkMediaPlayer类型的mp 里的VideoState (ff_ffplay_def.h)类型的 is 里的filename 就有了视频源

接着就执行到 ijkmp_change_state_l(mp, MP_STATE_INITIALIZED);更换播放器状态

void ijkmp_change_state_l(IjkMediaPlayer *mp, int new_state)

{

mp->mp_state = new_state;

ffp_notify_msg1(mp->ffplayer, FFP_MSG_PLAYBACK_STATE_CHANGED);

}

ffp_notify_msg1这个函数是通过#include "ijkplayer_internal.h"再#include "ff_ffplay.h"再#include "ff_ffplay_def.h"进来,定义为

inline static void ffp_notify_msg1(FFPlayer *ffp, int what) {

msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);

}

其中 inline这个关键词会给编译器这样一种建议:不要进行实际的函数调用而只是将这段代码插入到进行调用的地方。

上面用到的一个比较重要的结构体FFPlayer 定义在ijkmedia/ijkplayer/ff_ffplay_def.h#494

目前还不知道其具体作用,但是感觉这是播放时一个很重要的结构体。

msg_queue_put_simple3在 ijkmedia/ijkplayer/ff_ffmsg_queue.h

继续跟踪代码,发现走到msg_queue_put_private这个函数,将其放在消息队列里。

至于放在哪个消息队列里,谁又负责读取消息队列里的消息,这个在后面分析。

按照递归的思想,依次返回,

在ijkplayer_jni.c里 IJK_CHECK_MPRET_GOTO(retval, env, LABEL_RETURN); 这个不知是干嘛的,只是看见difine里,继续留坑。。。。。。。。。。。。。。。。。。。。。。。。。。。。

11. 现在开始执行tv.danmaku.ijk.media.player.IjkMediaPlayer#_start的方法,

在ijkmedia/ijkplayer/android/ijkplayer_jni.c#226  ijkmp_start(mp);根据作者的命名规则,ijkmp...都是在ijkmedia/ijkplayer/ijkplayer.h里,这个函数定义在197行

实现自然就在ijkplayer.c#441

最重要的应该就是这句话了 ffp_notify_msg1(mp->ffplayer, FFP_REQ_START);

ffp_notify_msg1 通过 #include "ijkplayer_internal.h",#include "ff_ffplay.h",#include "ff_ffplay_def.h",调用了 msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);这个跟第10条是调用一个函数,都是msg_queue_put_simple3,这次传递的消息是FFP_REQ_START,定义在ijkmedia/ijkplayer/ff_ffmsg.h里,关于消息队列的分析,在后面分析。

12. 其实在这里会有个疑问,作者先是设置了视频源,接着开始播放,这是两个不同的C函数,都使用了 IjkMediaPlayer 这个对象,用面向对象的语言来提问就是:如何保证这两个对象是同一个对象,即设置播放源的IjkMediaPlayer和播放的IjkMediaPlayer是同一个对象

设置播放源里:IjkMediaPlayer *mp = jni_get_media_player(env, thiz);

播放函数里面:IjkMediaPlayer *mp = jni_get_media_player(env, thiz); (JNIEnv *env, jobject thiz)

jni_get_media_player在ijkplayer_jni.c#61 ,调用了IjkMediaPlayer *mp = (IjkMediaPlayer *) (intptr_t) J4AC_IjkMediaPlayer__mNativeMediaPlayer__get__catchAll(env, thiz);

这里用到的就是上文第六点提到j4a,在

ijkmedia/ijkj4a/j4a/class/tv/danmaku/ijk/media/player/IjkMediaPlayer.h#53  define

#define A B 的意思就是在程序里使用A替代B,A就代表B,

看看作者是怎么实现的

在这里使用了一个JNI函数

GetLongField()      NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);

Static 修饰的fieldID即可以保证两次获取的是同一个对象。

上面的方法里有个强转 ,intptr_t类型是为指针准备的,C语言指针用来保存变量或常量的地址,地址由处理器的位数决定,intptr_t在不同的平台是不一样的,始终与地址位数相同,因此用来存放地址,即地址。

13. 分析消息队列:现在知道了c里面关键变量都是做了单例的,比如我们现在要分析的IjkMediaPlayer。

其定义在ijkmedia/ijkplayer/ijkplayer_internal.h

标红的,一个是线程互斥锁pthread_mutex_t,这个的使用在源码中随处可见,另一个是很重要的FFPlayer,定义在ijkmedia/ijkplayer/ff_ffplay_def.h里,结构体里面包含了很多内容,目前只分析一个MessageQueue  msg_queue;定义在ijkmedia/ijkplayer/ff_ffmsg_queue.h

msg_queue_put_private一大段处理消息的算法,找出关键函数 SDL_CondSignal(q->cond);通过#include "ff_ffinc.h",#include "ijksdl/ijksdl.h",#include "ijksdl_mutex.h"。具体定义在ijkmedia/ijksdl/ijksdl_mutex.c,

pthread_cond_signal(pthread_cond_t *cond)函数是用来在条件满足时,给正在等待的对象发送信息,表示唤醒该变量,一般和pthread_cond_wait函数联合使用

其实到这个地方,就会发现源代码分析不下去了,因为找不到可用函数了,这里用来唤醒线程,但是又不清楚唤醒的是哪个进程,这个进程什么时候堵塞的,其实是我们少分析了一部分,作者在最开始加载三个so文件时,做了很多初始化的工作,这里就不可避免要去分析ffmpeg的源代码了。

回过头来再看看初始化三个so文件时的操作

av_ 开头的函数都是ffmpeg里的,avformat_network_init在ffmpeg/libavformat/avformat.h里声明,做网络的初始化,可惜我还没有找到定义的位置。这里关注ijkav_register_all,声明在 ijkmedia/ijkplayer/ijkavformat/ijkavformat.h ,定义在ijkavformat/allformats.c

标红的部分是我要重点分析的地方

URLProtocol ffmpeg里一个很重要的结构体,定义在libavformat/url.h

URLProtocol存储输入视音频使用的封装格式,功能就是完成各种输入协议的读写等操作

其中包含了用于协议读写的函数指针。例如:
url_open():打开协议。
url_read():读数据。
url_write():写数据。
url_close():关闭协议。

但输入协议种类繁多,它是怎样做到“大一统”的呢?

原来,每个具体的输入协议都有自己对应的URLProtocol。

比如ijktcphook协议

ijklongurl:

还有好多,不一一例举了,它们把各自的函数指针都赋值给了URLProtocol结构体的函数指针。

上面的代码中,有个很关键的关键字,extern,使用时extern URLProtocol ijkff_##x##_protocol;

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

上面的声明最终都是调用ffurl_register_protocol这个ffmpeg里的函数,无从下手,看来得需要阅读ffmpeg的源码

原谅我源码读的有够失败,没有找到ffurl_register_protocol的声明和定义。。。。。。。

但是,从这个同名大概可以猜测一些,应该就是引用这些文件吧,这只是猜测而已。

14. 到这里好像已经进行不下去了,因为全部都是开始调用ffmpeg里的代码了,顺序走不下去可以逆序嘛。现在就逆序分析,我们知道ijk的作者写的ijkmedia/ijkplayer/ff_ffplay.c是建立在ffmpeg的ffmpeg/ffplay.c的基础上的,基本上这里面的代码是直接使用ffmpeg的代码,在这个文件里有一个read_thread函数,ffmpeg的作者写了一个注释

Ijk的作者应该是重写了ffplay.c,从read_thread的被调用来看,ffp_prepare_async_l --> stream_open --> read_thread,我没有找到ffp_prepare_async_l 被调用的地方,然后看到read_thread里面的代码

很熟悉有没有,输出的日志嘛

可以猜测这段代码是在设置视频源初始化时被调用的,不知是否还记得分析的第10条,而且看参数ffp_prepare_async_l(FFPlayer *ffp, const char *file_name) ,一个是FFPlayer ,一个是file_name,如果是网络视频,那就是视频源地址了。

虽然还没有分析代码里线程池睡眠唤醒的机制,但是可以猜测使用的是同一个一个FFPlayer 对象。

继续分析代码,在stream_open 函数里,不是我瞎猜想,看ijk作者注释

证明我的猜想分析是正确的,然后就到了我们很重要的read_thread。

这个函数主要就是读取二进制流,里面用到了以下这些很重要的结构体:FFPlayer,VideoState,AVFormatContext,AVPacket,

可以看见执行了avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

这个函数是ffmpeg里的,应该是读取视频文件。这个函数的声明int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options); 在ffmpeg/libavformat/avformat.h

定义在ffmpeg/libavformat/utils.c ,知道这几个参数很重要,但是还清楚其具体作用,那我怎么阅读呢,int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options),看参数,有个filename,那我就已filename为主线,第一次被调用是init_input(s, filename, &tmp),这个函数在utils.c,好像是一个什么评分规则,没看懂反正,估计是判断视频源是什么格式的。我不是瞎猜测的哈

Ffmpeg的作者对这个函数有注释,它的主要工作就是打开输入的视频数据并且探测视频的格式。这里没有几行代码,但是好多return,足见次函数的算法逻辑。

调用av_probe_input_buffer2这个内容很多,此处不分析,在第16条分析

这里有个目测是最基础的结构体 AVFormatContext ,定义在 ffmpeg/libavformat/avformat.h

作用就是 Format I/O context 中文讲就是处理封装格式(FLV/RMVB/MP4等),你要是好奇ffmpeg到底支持哪些格式,我告诉你个地方,ffmpeg/libavformat/allformats.c,里面有av_register_all,熟悉吧,初始化时都执行的函数,里面出现的格式都是ffmpeg支持的。

这个结构体里有两个指针 protocol_whitelist 和 protocol_blacklist ,大概猜测就是白名单黑名单的作用,这个出现应该是处理上文第13条里种类繁多的URLProtocol。

接着执行到

/* e.g. AVFMT_NOFILE formats will not have a AVIOContext */

if (s->pb)

ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, 0);

这里有个

AVIOContext *pb;类型的pb。AVIOContext 结构体的分析放在第15条,这里不分析。

ff_id3v2_read的定义在ffmpeg/libavformat/id3v2.c。目测是处理id3v2头信息的。这个函数调用id3v2_read_internal,里面有个函数avio_tell定义在ffmpeg/libavformat/avio.h,

接着执行 avio_read。声明在ffmpeg/libavformat/avio.h

原谅我没有找到这个函数的定义,avio_read应该是联网读取是调用。联网的分析放在第16条。

读取网络数据包头信息时调用id3v2_parse(pb, metadata, s, len, buf[3], buf[5], extra_meta);接着调用get_extra_meta_func,会使用到id3v2_extra_meta_funcs[]这个数组,里面有read_geobtag这样一个方法,这个方法里会read encoding type byte,read MIME type (always ISO-8859),read file name,read content description 。

别小看这里的读取头信息,这里其实ff_id3v2_read的操作结果是直接对参数 &id3v2_extra_meta 进行更改的。获取到头信息后会执行ff_id3v2_parse_apic,这个函数也是在id3v2.c 里,这个函数带给我们有一个ffmpeg里很重要的结构体AVStream。定义在ffmpeg/libavformat/avformat.h。视音频流对应的结构体,用于视音频编解码,

同时AVStream这个结构体里包含了另外一个很重要的结构体,AVPacket attached_pic,这个attached_pic在ff_id3v2_parse_apic使用率较高

AVPacket 的定义在ffmpeg/libavcodec/avcodec.h ,主要是存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据),这个结构真的很重要,ffmpeg的原作者注释

For video, it should typically contain one compressed frame. For audio it maycontain several compressed frames. 即视频和音频还不一样。

avformat_open_input这个函数同样的,执行完成后对参数AVFormatContext **ps进行了更新。

在ffmpeg/libavformat/utils.c里发现一个函数avformat_find_stream_info,虽然还没有发现其使用的地方,但是大概看了一下函数方法,应该是解析流,可以解析出很多信息,没有细看内容,应该是可以解析出视频流的详细信息,时长,比特率等等。

退回到ff_ffplay.c的read_thread,作者在寻找h246流,即视频流

我们知道访问网络是获取的数据包有很多,作者取出其中的H264包。

av_find_best_stream的声明在 ffmpeg/libavformat/avformat.h,定义在ffmpeg/libavformat/utils.c,ffmpeg的作者有一套算法找出用户最想要的streams,我没有去研究去算法。

接着就是 open the streams

stream_component_open 函数在同一个文件里

其实这个函数的最主要的作用应该是创建audio_thread和video_thread线程,关于这两个线程,放在18条分析

在decoder_start 里 调用packet_queue_start(d->queue);

当然前面调用了avcodec_find_decoder,根据id找到解码器。

现在audio和video都有了,就准备播放了,主要包括SDL_mutex信号量创建(这个就是类似13条里说的线程锁

)、AVFormatContext创建、打开输入文件、解析码流信息、查找音视频数据流并打开对应的数据流,这是函数里的第一部分。

接着后面跟了一个死循环,开始循环读取视频流了

循环读取代码里好多的算法逻辑,好些地方不太明白。具体分析放在17条,这是第二部分。

代码里还有一部分fail时的操作,在代码的最后,主要包括退出前的等待、关闭音视频流、关闭avformat、给主线程发送FF_QUIT_EVENT消息以及销毁SDL_mutex信号量,这是第三部分。

15. AVIOContext 是ffmpeg中很重要的结构体,定义在libavformat/avio.h,输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等),ffmpeg的作者给了一个形象的图来解释其重要性

16. 分析ffmpeg的获取数据(联网)

av_probe_input_buffer2,调用在ffmpeg/libavformat/utils.c的init_input函数

声明在ffmpeg/libavformat/avformat.h,作用Probe a bytestream to determine the input format.定义在 ffmpeg/libavformat/format.c

使用了一个结构体AVProbeData 定义在libavformat/avformat.h,用于存储输入文件的一些信息

av_probe_input_buffer2也可以分为三部分分析,初始化,循环读取,反初始化,重点分析循环读取部分,循环部分调用avio_read()读取数据,调用av_probe_input_format2推测文件格式

int avio_read(AVIOContext *s, unsigned char *buf, int size); Read size bytes from AVIOContext into buf.

av_probe_input_format2定义在ffmpeg/libavformat/format.c ,里面调用av_probe_input_format3,这个也是定义在ffmpeg/libavformat/format.c,这个函数的返回值是AVInputFormat类型的,定义在ffmpeg/libavformat/avformat.h,里面定义了一个int (*read_probe)(AVProbeData *) 这个是av_probe_input_format3函数里循环部分调用的比较重要的,关键read_probe 是一个函数呀,这个函数用于获得匹配函数的函数指针,不同的封装格式包含不同的实现函数。以常见的flv格式来说,在ffmpeg/libavformat/flvdec.c

read_probe对应的就是 live_flv_probe,live_flv_probe定义在同一个文件里。在比如swf格式

每个格式都有不同的匹配规则,为了判断当前文件的格式,就已得分的高低来判断,av_probe_input_format2的操作结果是更新参数里的得分,同时得出文件是什么格式的。

现在可以回到init_input函数,在avformat_open_input里调用结束后,接着执行到ff_id3v2_read函数,这个在14条里已分析,接着执行s->iformat->read_header(s),没错,会调用AVInputFormat的read_header()方法读取媒体文件的文件头并且完成相关的初始化工作。以flv格式来看,会调用flv里实现的flv_read_header方法,在函数里没有找到代码,但是我感觉如果读取头文件成功应该会继续调用AVInputFormat的read_packet()读取网络数据包,原谅我没有找到。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

虽然我没有找到,但代码就是这么执行的,可以接着分析,

回到avformat_open_input,接着调用avformat_queue_attached_pictures,这里面有个方法,add_to_pktbuf,作用是 Add the packet in the buffered packet list。完成后,紧接着就是update_stream_avctx,有新的packet到达,里面做的操作无非就是判断当前状态,然后清空队列等等。

由于正向没有找到入口函数,这里才有逆向分析

主要是分析两个文件 ffmpeg/libavformat/url.c 和 ffmpeg/libavformat/network.c 从源码中分析出ffmpeg是使用socket联网

不知是否还记得13条里初始化时有个网络的输出化avformat_network_init,这个函数的定义在ffmpeg/libavformat/utils.c,这里有两个初试化。一个ff_network_init,一个ff_tls_init。

ff_network_init在ffmpeg/libavformat/network.c里定义,里面用了一个全局变量ff_network_inited_globally,在 .h 文件里声明了extern int ff_network_inited_globally;还没有找到其定义哈,哎呀都什么眼神,在avformat_network_init函数里,就调用了ff_network_inited_globally = 1;

ff_tls_init定义也在network.c,会用到两个函数ff_openssl_init和ff_gnutls_init,都是声明在ffmpeg/libavformat/tls.h,至于定义嘛,我还没有找见。只好看看注释咯

17. 循环读取分析:即for死循环里的代码

先是对是否暂停(暂停就不接受数据包了

,看来这个网络接收这块是一直连接着,只要没停止就一直接收着数据),是否拖动快进或快退判断接着往packet的队列里put

还有判断队列是否满了 if the queue are full, no need to read more

av_read_frame 声明在 ffmpeg/libavformat/avformat.h ,作用:Return the next frame of a stream.

作者的算法逻辑很强,当然这段代码里只是往packet队列里put,只是取出packet,在取的时候要判断当前的各种状态,暂停?快进?队列是否满,正在播放?等等,这些函数都是处理这些逻辑的,比如:ffp_toggle_buffering,step_to_next_frame_l,stream_update_pause_l等 。

代码中多次出现continue_read_thread,

从其名字上来看,是一个控制read_thread线程是否继续阻塞的信号量,上面两次阻塞的地方分别是:packet队列已满,需要等待一会(即超时10ms)或者收到信号重新循环;读数据失败,av_read_frame出错时,如读取网络实时数据时取不到数据,此时也需要等待或者收到信号重新循环。

总结一下,循环读取数据部分:主要包括pause和resume操作处理、seek操作处理、packet队列写入失败处理、读数据结束处理、然后是读数据并写入到对应的音视频队列中。

处理真正播放不在这里,继续分析

18. audio_thread和video_thread

定义在ff_ffplay.c,会发现作者开启线程是把其设为异步线程

ffplay_video_thread是处理视频核心实现部分,这个函数同read_thread一样也是分了三部分

初始化部分:主要包括AVFrame创建和AVFilterGraph创建。

AVFrame *frame = av_frame_alloc();

AVFilterGraph *graph = avfilter_graph_alloc();

循环解码部分(for循环):主要包括pause和resume操作处理、读取frame处理、AVFILTER处理、然后是将frame写入视频picture队列中以及每次解码后的清理动作。

读取get_video_frame,作者每次都是先释放再初始化AVFilterGraph 。

avfilter_graph_free(&graph);

graph = avfilter_graph_alloc();

添加frame到buffer缓存区av_buffersrc_add_frame,

其中AVFILTER部分,过滤器嘛,学过通信原理都知道,接收到的信号会有损失,有杂波,需要对解码后的数据进行滤波,去除方块效应

出现了一个结构体,AVRational,没有找到其定义,但是从使用来看,这个结构体里面有两个参数:frame_rate.num && frame_rate.den,应该就是用这两个参数算出一个值,类似分式那种分子分母。

queue_picture函数里,调用里YUV

SDL_VoutFillFrameYUVOverlay(vp->bmp, src_frame)

这里使用到了YUV,大概流程就是ffmpeg解码,转成YUV格式的视频帧,然后再使用sdl的yuv覆盖的模式进行渲染,剩下的就是显示了,这个是SDL处理的,用于视频图像显示和刷新,暂时不做分析。

结束部分:主要包括刷新codec中的数据、释放AVFilterGraph、释放AVPacket以及释放AVFrame。

这里要总结两个结构体:

AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)

AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)

audio_thread的分析类似,只是少了YUV的渲染,不做重复分析了,源码在ff_ffplay.c。

时间: 2024-10-11 02:29:53

转:analyze ijkplayer code的相关文章

Improving Your Code with lint——使用lint改善你的代码

要进一步测试你的Android应用是否满足功能需求,确保你的代码没有结构上的错误是很重要的.糟糕的结构代码会影响你的Android应用的可靠性和效率,让你的代码难以维护.例如,如果你的XML资源文件包含了未使用的命名空间,这会占用空间并带来不必要的处理.其它结构性问题,例如使用废弃的元素或调用目标API版本不支持的API,会导致不能正确运行代码. 概述 Android SDK提供了一个叫做 lint 的代码扫描工具,可以帮助你很轻松地识别和纠正代码的结构质量问题,而不用执行应用程序或写任何测试用

PatentTips - Method and Apparatus to Support Virtualization with Code Patches

BACKGROUND As recognized in Revision 2.0 of the Intel? Virtualization Technology Specification for the Intel? Itanium? Architecture (VT-I), dated April 2005 (hereinafter "the VT-I Specification"), conventional operating system (OS) designs typic

Improving Code Inspection with Annotations——使用注解改善代码检查

使用代码检查工具,例如lint,可以帮助你检查问题并改善代码,但是检查工具也就只能推断这么多.例如Android资源id,使用int来标识字符串,图形,颜色和其他资源类型,而检查工具不能告诉你当在需要指定一个颜色的地方你指定了一个字符串资源.这种情况意味着你的应用可能渲染不正确或根本运行失败,即使你使用了代码检查. 注解允许你提供暗示给像lint的代码检查工具,来帮助检测这些更精细的代码问题.它们以元数据标签添加,你可以附在变量,参数,和返回值来检查方法返回值,传递的参数,局部变量和字段.当使用

Top 40 Static Code Analysis Tools

https://www.softwaretestinghelp.com/tools/top-40-static-code-analysis-tools/ In this article, I have summarised some of the top static code analysis tools. Can we ever imagine sitting back and manually reading each line of codes to find flaws? To eas

Android性能测试

测试应用的启动时间 adb shell am start -W packagename/activity,eg:adb shell am start -W com.tencent.mm/.ui.LauncherUI,显示的结果中,thisTime和totalTime的含义分别为: thisTime: just current activity launched time totalTime:the activity you started may be on the bottom of acti

Android - 抑制lint的Android XML的警告:tools:ignore

抑制lint的Android XML的警告:tools:ignore 本文地址:http://blog.csdn.net/caroline_wendy Android的XML经常会出现警告,对于一个良好的程序,应该认真对待所有的警告. 除非我们可以确认警告,才可以排除. 显示所有警告的方法:Analyze -> Inspect Code; 就可以检查出所有的警告: 抑制警告使用: tools:ignore. // 忽略全部 xmlns:tools="http://schemas.andro

Android 性能优化:使用 Lint 优化代码、去除多余资源

读完本文你将了解到: 前言 什么是 Lint Lint 工作方式简单介绍 从命令行运行 Lint Android Studio 中使用 Lint 团队中建立代码规范利器提升降低问题的等级 Lint 虽好也不能贪杯 在 Java 代码中忽略 Lint 警告 在 XML代码中忽略 Lint 警告 Gradle 中配置 Lint 自动删除查找出来的无用资源文件 总结 Thanks 前言 在保证代码没有功能问题,完成业务开发之余,有追求的程序员还要追求代码的规范.可维护性. 今天,以"成为优秀的程序员&

light_oj 1236 求最小公倍数( lcm(a,b) )等于n的数对 素因数分解

light_oj 1236 求最小公倍数( lcm(a,b) )等于n的数对  素因数分解 H - Pairs Forming LCM Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Submit Status Practice LightOJ 1236 Description Find the result of the following code: long long pairsFormL

Source Monitor Tutorial

Source Monitor is a code analyzing tool that is capable of finding the complexity for Java, C++.C.C#.Delphi.Visual Basic and HTML source codes. It's a standalone software which does not rely upon any specific IDE to work. What's even better is that i