理解ijkplayer(六)从ijkplayer看ffmpeg源码 - 图1

初始化播放器

  1. // 注册编解码器
  2. avcodec_register_all();
  3. // 初始化libavformat,注册所有的复用器,解复用器和协议。
  4. av_register_all();
  5. // 初始化openssl库
  6. avformat_network_init();
  7. // 设置日志回调
  8. av_log_set_callback(ffp_log_callback_brief);

播放准备

从ijkplayer的ffp_prepare_async_l函数开始:
首先会打开音频输出初始化,安卓上就是配置AudioTrack的Jni反射函数,这里还没有创建AudioTrack,只是配置好函数指针,指向ijksdl_aout_android_audiotrack.c文件里面定义的AudioTrack反射函数。

初始化帧队列

在函数stream_open中。
初始化帧队列,帧队列在ijk中是结构体FrameQueue,有三个帧队列,分别是图片帧队列、字幕帧队列、音频帧队列。他们存放的都是解码后的数据。

队列内部是采用数组实现的,他们的大小分别是:
图片帧队列长度:3
音频帧度列长度:9
字幕帧队列长度:16

保存在帧队列中的数据结构来自ffmpeg,是AVFrame结构体,每个结构体都预先分配好了内存空间。采用函数av_frame_alloc

  1. for (i = 0; i < f->max_size; i++)
  2. if (!(f->queue[i].frame = av_frame_alloc()))
  3. return AVERROR(ENOMEM);

理解ijkplayer(六)从ijkplayer看ffmpeg源码 - 图2

初始化包队列

包队列中保存的ffmpeg结构体是AVPacket,表示未解码的压缩数据,即将送入到解码器解码,采用链表的结构来保存。
包队列也是分为3个:视频包队列,音频包队列,字幕包队列。
理解ijkplayer(六)从ijkplayer看ffmpeg源码 - 图3

进入IO线程,探测音视频数据信息

  1. // 初始化AVFormatContext
  2. ic = avformat_alloc_context();
  3. //找到视频格式:AVInputFormat
  4. if (ffp->iformat_name)
  5. is->iformat = av_find_input_format(ffp->iformat_name);
  6. // 打开输入流并读取音视频header信息
  7. err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
  8. // 遍历当前流资源中的所有流,所有的音视频流都找到对应的流信息
  9. avformat_find_stream_info(ic, opts);
  10. // 遍历所有的流,并找到第一个h264视频流,并选中那个视频流。
  11. // 找到最佳的流,把音频、视频、字幕流的轨道位置都记录下来。
  12. av_find_best_stream();

初始化解码器

对音频、视频、字幕轨道分别都调用stream_component_open

  1. // 解码器上下文
  2. AVCodecContext *avctx;
  3. // 解码器
  4. AVCodec *codec = NULL;
  5. avctx = avcodec_alloc_context3(NULL);
  6. // 将AVCodecParameters中的变量赋值给AVCodecContext
  7. ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
  8. // 设置时间基准
  9. av_codec_set_pkt_timebase(avctx, ic->streams[stream_index]->time_base);
  10. // 找到解码器
  11. codec = avcodec_find_decoder(avctx->codec_id);
  12. // 如果有强制解码器,就指定强制的
  13. if (forced_codec_name)
  14. codec = avcodec_find_decoder_by_name(forced_codec_name);
  15. // 把解码器的id赋值给解码器上下文
  16. avctx->codec_id = codec->id;
  17. // 根据解码器类型,配置解码器信息
  18. switch(avctx->codec_type){
  19. case AVMEDIA_TYPE_AUDIO:
  20. // 初始化音频av_filter
  21. // 打开音频输出器,Android平台上是jni反射到AudioTrack对象。
  22. // 解码器初始化 调用:decoder_init
  23. // 启动音频解码器线程
  24. if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)
  25. goto out;
  26. break;
  27. case AVMEDIA_TYPE_VIDEO:
  28. // 解码器初始化 调用:decoder_init
  29. // 打开视频解码器,这一步ijkplayer封装做了一个抽象封装,这个封装后面再单独抽成一篇文章来分析。
  30. // 启动视频解码器线程
  31. break;
  32. case AVMEDIA_TYPE_SUBTITLE:
  33. // 解码器初始化 调用:decoder_init
  34. // 启动字幕解码器线程
  35. break;
  36. }
  1. // 解码器初始化
  2. static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) {
  3. memset(d, 0, sizeof(Decoder));
  4. d->avctx = avctx;
  5. d->queue = queue;
  6. d->empty_queue_cond = empty_queue_cond;
  7. d->start_pts = AV_NOPTS_VALUE;
  8. d->first_frame_decoded_time = SDL_GetTickHR();
  9. d->first_frame_decoded = 0;
  10. SDL_ProfilerReset(&d->decode_profiler, -1);
  11. }

启动视频解码器线程这块再多说一下,如果是软解,线程执行的函数是ff_ffplay.c文件的ffplay_video_thread函数,如果是硬解,执行的是ffpipenode_android_mediacodec_vdec.c文件的func_run_sync函数。

准备完成

此时播放器准备完成,如果有Ijk的start-on-prepared开关,则会直接开始播放,不需要调用start。默认是关闭的。
那么此时执行如下代码段:

  1. ffp->prepared = true;
  2. //发送PREPARED回调
  3. ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
  4. if (!ffp->render_wait_start && !ffp->start_on_prepared) {
  5. while (is->pause_req && !is->abort_request) {
  6. SDL_Delay(20);// 经过测试,只要设置uri,然后不调用start,就会一直卡在这里。
  7. }
  8. }

即不断地在这个IO线程中休眠。SDL_Delay在内部调用的是nanosleep函数,和Java的Thread.sleep的含义一样。

开始播放

下载包数据

开始播放改变的是VideoState结构体的int pause_req变量,然后在多个地方都会检查这个变量,表示是否播放暂停了。这里我们接着正常第一次播放的流程来看,那么还是衔接上面的流程。
在上面发送了PREPARED回调告诉上层播放器底部已经准备好可以开始播放后,io线程就进入了while循环并每隔20毫秒就检查一下这个pause_req的标志位,看看上层是否开始播放了。当开始播放之后:

  1. static int read_thread(void *arg)
  2. {
  3. // ...
  4. for(;;){
  5. // 我们进入了一个for循环,这里是io线程不断拉流的主体逻辑。
  6. //如果是seek 请求
  7. if (is->seek_req) {
  8. //ffmepg 中处理seek
  9. ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
  10. // 清空音频、视频、字幕包队列。
  11. packet_queue_flush(&is->audioq);
  12. packet_queue_put(&is->audioq, &flush_pkt);
  13. }
  14. // 如果队列满了,不再读取,wait 10 ms,并continue进入下一次循环。
  15. // 可能的原因是,包数据没有被解码器消费掉,一般是视频暂停了,或者视频解码过慢。
  16. // 处理播放结束
  17. // 调用ffmpeg函数读取包数据到pkt里面。
  18. ret = av_read_frame(ic, pkt);
  19. // 判断pkt的stream类型,然后放到对应的音视频包队列里面。
  20. packet_queue_put(&is->audioq, pkt);
  21. }
  22. }

解码包数据