PS: 控制新技术对你的影响而不是被控制。
本文分析下 IjkPlayer 的数据读取线程 read_thread,目的是理清其基本流程以及关键函数的调用,主要内容如下:
IjkPlayer基本使用
read_thread创建
avformat_alloc_context
avformat_open_input
avformat_find_stream_info
avformat_seek_file
av_dump_format
av_find_best_stream
stream_component_open
read_thread主循环
IjkPlayer基本使用 简单回顾下 IjkPlayer 的基本使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 IjkMediaPlayer mMediaPlayer = new IjkMediaPlayer ();mMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec" , 1 ); mMediaPlayer.setOnPreparedListener(mPreparedListener); mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); mMediaPlayer.setOnCompletionListener(mCompletionListener); mMediaPlayer.setOnErrorListener(mErrorListener); mMediaPlayer.setOnInfoListener(mInfoListener); mMediaPlayer.setSurface(surface) mMediaPlayer.setDataSource(dataSource); mMediaPlayer.prepareAsync();
当调用 prepareAsync 之后收到 onPrepared 回调是调用 start 开始播放:
1 2 3 4 5 @Override public void onPrepared (IMediaPlayer mp) { mMediaPlayer.start(); }
到此,一般情况下视频就能正常播放了,这里只关注调用流程。
read_thread创建 从 IjkMediaPlayer 的方法 prepareAsync 开始看,其调用流程如下:
1 2 3 4 5 6 7 sequenceDiagram Activity->>IjkMediaPlayer.java: prepareAsync IjkMediaPlayer.java->>ijkplayer_jni.c: _prepareAsync ijkplayer_jni.c->>ijkplayer.c: ijkmp_prepare_async ijkplayer.c->>ijkplayer.c: ijkmp_prepare_async_l ijkplayer.c->>ff_ffplay.c: ffp_prepare_async_l ff_ffplay.c->>ff_ffplay.c: stream_open
可知 prepareAsync 最后调用的是函数 stream_open ,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 static VideoState *stream_open (FFPlayer *ffp, const char *filename, AVInputFormat *iformat) { av_log (NULL , AV_LOG_INFO, "stream_open\n" ); assert (!ffp->is); VideoState *is; is = av_mallocz (sizeof (VideoState)); if (!is) return NULL ; is->filename = av_strdup (filename); if (!is->filename) goto fail; is->iformat = iformat; is->ytop = 0 ; is->xleft = 0 ; #if defined(__ANDROID__) if (ffp->soundtouch_enable) { is->handle = ijk_soundtouch_create (); } #endif if (frame_queue_init (&is->pictq, &is->videoq, ffp->pictq_size, 1 ) < 0 ) goto fail; if (frame_queue_init (&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0 ) < 0 ) goto fail; if (frame_queue_init (&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1 ) < 0 ) goto fail; if (packet_queue_init (&is->videoq) < 0 || packet_queue_init (&is->audioq) < 0 || packet_queue_init (&is->subtitleq) < 0 ) goto fail; if (!(is->continue_read_thread = SDL_CreateCond ())) { av_log (NULL , AV_LOG_FATAL, "SDL_CreateCond(): %s\n" , SDL_GetError ()); goto fail; } if (!(is->video_accurate_seek_cond = SDL_CreateCond ())) { av_log (NULL , AV_LOG_FATAL, "SDL_CreateCond(): %s\n" , SDL_GetError ()); ffp->enable_accurate_seek = 0 ; } if (!(is->audio_accurate_seek_cond = SDL_CreateCond ())) { av_log (NULL , AV_LOG_FATAL, "SDL_CreateCond(): %s\n" , SDL_GetError ()); ffp->enable_accurate_seek = 0 ; } init_clock (&is->vidclk, &is->videoq.serial); init_clock (&is->audclk, &is->audioq.serial); init_clock (&is->extclk, &is->extclk.serial); is->audio_clock_serial = -1 ; if (ffp->startup_volume < 0 ) av_log (NULL , AV_LOG_WARNING, "-volume=%d < 0, setting to 0\n" , ffp->startup_volume); if (ffp->startup_volume > 100 ) av_log (NULL , AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n" , ffp->startup_volume); ffp->startup_volume = av_clip (ffp->startup_volume, 0 , 100 ); ffp->startup_volume = av_clip (SDL_MIX_MAXVOLUME * ffp->startup_volume / 100 , 0 , SDL_MIX_MAXVOLUME); is->audio_volume = ffp->startup_volume; is->muted = 0 ; is->av_sync_type = ffp->av_sync_type; is->play_mutex = SDL_CreateMutex (); is->accurate_seek_mutex = SDL_CreateMutex (); ffp->is = is; is->pause_req = !ffp->start_on_prepared; is->video_refresh_tid = SDL_CreateThreadEx (&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout" ); if (!is->video_refresh_tid) { av_freep (&ffp->is); return NULL ; } is->initialized_decoder = 0 ; is->read_tid = SDL_CreateThreadEx (&is->_read_tid, read_thread, ffp, "ff_read" ); if (!is->read_tid) { av_log (NULL , AV_LOG_FATAL, "SDL_CreateThread(): %s\n" , SDL_GetError ()); goto fail; } if (ffp->async_init_decoder && !ffp->video_disable && ffp->video_mime_type && strlen (ffp->video_mime_type) > 0 && ffp->mediacodec_default_name && strlen (ffp->mediacodec_default_name) > 0 ) { if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2) { decoder_init (&is->viddec, NULL , &is->videoq, is->continue_read_thread); ffp->node_vdec = ffpipeline_init_video_decoder (ffp->pipeline, ffp); } } is->initialized_decoder = 1 ; return is; fail: is->initialized_decoder = 1 ; is->abort_request = true ; if (is->video_refresh_tid) SDL_WaitThread (is->video_refresh_tid, NULL ); stream_close (ffp); return NULL ; }
可知函数 stream_open 主要做了如下几件事:
初始化 VideoState 及部分参数。
初始化帧队列,包括初始化已解码的视频帧队列 pictq、音频帧队列 sampq 和字幕帧队列 subpq和未解码的视频数据队列 videoq、音频数据队列 audioq 和字幕数据队列 subtitleq。
音视频同步方式及时钟初始化,默认 AV_SYNC_AUDIO_MASTER,也就是音频时钟作为主时钟。
音量初始化范围。
创建了线程名为 ff_vout 的视频渲染线程 video_refresh_thread。
创建了线程名为 ff_read 的视频渲染线程 read_thread。
到此开始本文主题数据读取线程 read_thread 函数的分析, 函数 read_thread 关键部分简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static int read_thread (void *arg) { ic = avformat_alloc_context (); if (!ic) { av_log (NULL , AV_LOG_FATAL, "Could not allocate context.\n" ); ret = AVERROR (ENOMEM); goto fail; } err = avformat_open_input (&ic, is->filename, is->iformat, &ffp->format_opts); if (err < 0 ) { print_error (is->filename, err); ret = -1 ; goto fail; } ffp_notify_msg1 (ffp, FFP_MSG_OPEN_INPUT); if (ffp->find_stream_info) { err = avformat_find_stream_info (ic, opts); } ffp_notify_msg1 (ffp, FFP_MSG_FIND_STREAM_INFO); if (ffp->start_time != AV_NOPTS_VALUE) { int64_t timestamp; timestamp = ffp->start_time; if (ic->start_time != AV_NOPTS_VALUE) timestamp += ic->start_time; ret = avformat_seek_file (ic, -1 , INT64_MIN, timestamp, INT64_MAX, 0 ); } av_dump_format (ic, 0 , is->filename, 0 ); }
下面内容仅限于 read_thread 函数的主要流程。
avformat_alloc_context avformat_alloc_context 函数主要是为 AVFormatContext 分配内存、初始化 ic->internal 部分参数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 AVFormatContext *avformat_alloc_context (void ) { AVFormatContext *ic; ic = av_malloc (sizeof (AVFormatContext)); if (!ic) return ic; avformat_get_context_defaults (ic); ic->internal = av_mallocz (sizeof (*ic->internal)); if (!ic->internal) { avformat_free_context (ic); return NULL ; } ic->internal->offset = AV_NOPTS_VALUE; ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; ic->internal->shortest_end = AV_NOPTS_VALUE; return ic; }
继续查看 avformat_get_context_defaults 函数如下:
1 2 3 4 5 6 7 static void avformat_get_context_defaults (AVFormatContext *s) { memset (s, 0 , sizeof (AVFormatContext)); s->av_class = &av_format_context_class; s->io_open = io_open_default; s->io_close = io_close_default; av_opt_set_defaults (s); }
这里指定了打开流、关闭流的默认函数分别为 io_open_default 和 io_close_default,这里暂不关注后续流程。
avformat_open_input 函数主要用于打开码流获取 header 信息,其定义简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 int avformat_open_input (AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options) { av_log (NULL , AV_LOG_FATAL, "avformat_open_input > init_input before > nb_streams:%d\n" ,s->nb_streams); if ((ret = init_input (s, filename, &tmp)) < 0 ) goto fail; s->probe_score = ret; if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((ret = s->iformat->read_header (s)) < 0 ) goto fail; if ((ret = avformat_queue_attached_pictures (s)) < 0 ) goto fail; update_stream_avctx (s); }
可知 avformat_open_input 函数主要是打开码流探测码流输入格式、协议黑白名单和码流格式白名单检查、读取文件 header 信息等,,最后使用函数 update_stream_avctx 更新 AVStream 解码器相关信息到对应的 AVCodecContext 中,这个操作会在后续流程中经常看到。
最重要的是打开码流探测码流输入格式和读取文件 header 信息,分别调用了函数 init_input 和 read_header 函数,read_header 会在读取 header 信息过程中完成 AVStream 的初始化。
init_input 函数主要是探测码流格式并返回该码流格式的得分,最终找到对应该码流格式的最佳 AVInputFormat,这个结构体是初始化时注册的解复用器,每个解复用器都对应一个 AVInputFormat 对象,同理复用器对应的是 AVOutputFormat 这里暂且了解一下。
init_input 函数如果执行成功,则对应的码流格式已经确定,此时就可以调用 read_header 函数了,其对应的是当前码流格式 AVInputFormat 对应的解复用器 demuxer 中的 xxx_read_header 函数,如果是 hls 格式的码流,则对应的则是 hls_read_header,其定义如下:
1 2 3 4 5 6 7 8 9 10 AVInputFormat ff_hls_demuxer = { .name = "hls,applehttp" , .read_header = hls_read_header, }; static int hls_read_header (AVFormatContext *s, AVDictionary **options) { }
avformat_find_stream_info 函数主要用来获取码流信息,用来探测没有 header 的文件格式比较有用,可以通过该函数获取视频宽高、总时长、码率、帧率、像素格式等等,其定义简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 int avformat_find_stream_info (AVFormatContext *ic, AVDictionary **options) { for (i = 0 ; i < ic->nb_streams; i++) { st->parser = av_parser_init (st->codecpar->codec_id); codec = find_probe_decoder (ic, st, st->codecpar->codec_id); if (!has_codec_parameters (st, NULL ) && st->request_probe <= 0 ) { if (codec && !avctx->codec) if (avcodec_open2 (avctx, codec, options ? &options[i] :&thread_opt) < 0 ) av_log (ic, AV_LOG_WARNING, "Failed to open codec in %s\n" ,__FUNCTION__); } } for (;;) { if (ff_check_interrupt (&ic->interrupt_callback)) { break ; } for (i = 0 ; i < ic->nb_streams; i++) { int fps_analyze_framecount = 20 ; st = ic->streams[i]; if (!has_codec_parameters (st, NULL )) break ; } if (i == ic->nb_streams) { analyzed_all_streams = 1 ; if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) { ret = count; av_log (ic, AV_LOG_DEBUG, "All info found\n" ); flush_codecs = 0 ; break ; } } if (read_size >= probesize) { break ; } ret = read_frame_internal (ic, &pkt1); if (ret == AVERROR (EAGAIN)) continue ; if (ret < 0 ) { eof_reached = 1 ; break ; } ret = add_to_pktbuf (&ic->internal->packet_buffer, pkt, &ic->internal->packet_buffer_end, 0 ); try_decode_frame (ic, st, pkt,(options && i < orig_nb_streams) ? &options[i] : NULL ); } if (eof_reached) { for (stream_index = 0 ; stream_index < ic->nb_streams; stream_index++) { if (!has_codec_parameters (st, NULL )) { const AVCodec *codec = find_probe_decoder (ic, st, st->codecpar->codec_id); if (avcodec_open2 (avctx, codec, (options && stream_index < orig_nb_streams) ? &options[stream_index] : &opts) < 0 ) av_log (ic, AV_LOG_WARNING, } } if (flush_codecs) { AVPacket empty_pkt = { 0 }; int err = 0 ; av_init_packet (&empty_pkt); for (i = 0 ; i < ic->nb_streams; i++) { st = ic->streams[i]; if (st->info->found_decoder == 1 ) { do { err = try_decode_frame (ic, st, &empty_pkt, (options && i < orig_nb_streams) ? &options[i] : NULL ); } while (err > 0 && !has_codec_parameters (st, NULL )); if (err < 0 ) { av_log (ic, AV_LOG_INFO, "decoding for stream %d failed\n" , st->index); } } } } for (i = 0 ; i < ic->nb_streams; i++) { ret = avcodec_parameters_from_context (st->codecpar, st->internal->avctx); } }
由于 avformat_find_stream_info 函数代码量比较多,上述代码省略了大部分细节,保留了比较关键的部分,这里只看主要流程,从源码可知,该函数中会频繁的使用 has_codec_parameters 函数来检查流内部解码器上下问参数是否合理,如果不合理则要尽可能的采取措施保证流内部解码器上下文参数合理,当 ret = 0; 标识 avformat_find_stream_info 执行成功,其主要流程如下:
遍历流,根据流中的一些参数初始化 AVCodecParser 和 AVCodecParserContext,通过函数 find_probe_decoder 来探测解码器,使用函数 avcodec_open2 来初始化 AVCodecContext 并调用解码器的 init 函数来初始化解码器静态数据。
for (;;) 死循环主要是使用 ff_check_interrupt 函数进行中断检测、遍历流使用 has_codec_parameters 函数检测码流内部的解码器上下文参数是否合理及数据读取的,如果合理且当前码流有 header 信息,则标识 analyzed_all_streams = 1; 且 flush_codecs = 0; 直接 break 退出该死循环,如果当前码流有 header 信息,也就是 ic->ctx_flags 被设置为 AVFMTCTX_NOHEADER 的时候,此时需要调用 read_frame_internal 函数读取一帧编解码数据,并将其添加到缓存中,调用 try_decode_frame 函数解码一帧数据进一步填充流中的 AVCodecContext。
eof_reached = 1 表示前面死循环使用 read_frame_internal 函数读取到流的末尾了,遍历流,再次使用 has_codec_parameters 函数来检查流内部解码器上下文参数是否合理,不合理则再重复上面 2 中的步骤来进行解码器上下文相关参数的初始化。
解码的过程就是一个不断放数据和取数据的过程,分别对应的是 avcodec_send_packet 和 avcodec_receive_frame 函数,为了避免解码器数据残留,这里通过空的 AVPacket 来 flushing 解码器,执行的条件是 flush_codecs = 1 的时候,也就是需要需要上面 2 中调用了 try_decode_frame 执行了解码操作。
后续就是一些码流信息的计算,比如pix_fmt、横纵比SAR、实际帧率、平均帧率等等。
遍历流,调用 avcodec_parameters_from_context 函数将之前填充的流的内部 AVCodecContext 中的解码器参数填充到流的解码器参数 st->codecpar中,对应结构体 AVCodecParameters,到此 avformat_find_stream_info 函数主要流程分析完毕。
avformat_seek_file 主要用来执行 seek 操作的,其定义简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int avformat_seek_file (AVFormatContext *s, int stream_index, int64_t min_ts, int64_t ts, int64_t max_ts, int flags) { if (s->iformat->read_seek2) { int ret; ff_read_frame_flush (s); ret = s->iformat->read_seek2 (s, stream_index, min_ts, ts, max_ts, flags); if (ret >= 0 ) ret = avformat_queue_attached_pictures (s); return ret; } if (s->iformat->read_seek || 1 ) { int ret = av_seek_frame (s, stream_index, ts, flags | dir); return ret; } return -1 ; }
可知 avformat_seek_file 函数执行时。如果当前解复用器(AVInputFormat)支持 read_seek2,则使用对应的 read_seek2 函数,否则调用旧版 API 里面的 av_seek_frame 函数进行 seek,av_seek_frame 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 int av_seek_frame (AVFormatContext *s, int stream_index,int64_t timestamp, int flags) { int ret; if (s->iformat->read_seek2 && !s->iformat->read_seek) { return avformat_seek_file (s, stream_index, min_ts, timestamp, max_ts, flags & ~AVSEEK_FLAG_BACKWARD); } ret = seek_frame_internal (s, stream_index, timestamp, flags); return ret; }
可知如果当前 AVInputFormat 支持 read_seek2 且不支持 read_seek 则使用 avformat_seek_file 函数也就是 read_seek2 函数进行 seek,如果支持 read_seek 则优先调用内部 seek 函数 seek_frame_internal 进行 seek,seek_frame_internal 函数主要提供寻帧的几种方式:
seek_frame_byte:按照字节方式寻帧
read_seek:按照当前指定格式的方式寻帧,具体由该格式对应的解复用器提供支持。
ff_seek_frame_binary:按照二分查找的方式寻帧。
seek_frame_generic:按照通用方式寻帧。
这也是 seek 操作的逻辑,比如 hls 格式的解复用器就不支持 read_seek2,仅支持 read_seek, ff_hls_demuxer 定义如下:
1 2 3 4 AVInputFormat ff_hls_demuxer = { .read_seek = hls_read_seek, };
av_dump_format 函数用来根据当前 AVFormatContext 来打印码流输入格式的详细信息,直接看 IjkPlayer 正常播放视频打印信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 IJKMEDIA: Input #0, hls,applehttp, from 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8': IJKMEDIA: Duration: IJKMEDIA: 00:30:00.00 IJKMEDIA: , start: IJKMEDIA: 19.888800 IJKMEDIA: , bitrate: IJKMEDIA: 0 kb/s IJKMEDIA: IJKMEDIA: Program 0 IJKMEDIA: Metadata: IJKMEDIA: variant_bitrate : IJKMEDIA: 0 IJKMEDIA: IJKMEDIA: Stream #0:0 IJKMEDIA: , 23, 1/90000 IJKMEDIA: : Video: h264, 1 reference frame ([27][0][0][0] / 0x001B), yuv420p(tv, smpte170m/smpte170m/bt709, topleft), 400x300 (400x304), 0/1 IJKMEDIA: , IJKMEDIA: 29.92 tbr, IJKMEDIA: 90k tbn, IJKMEDIA: 180k tbc IJKMEDIA: IJKMEDIA: Metadata: IJKMEDIA: variant_bitrate : IJKMEDIA: FFP_MSG_FIND_STREAM_INFO: IJKMEDIA: 0 IJKMEDIA: IJKMEDIA: Stream #0:1 IJKMEDIA: , 9, 1/90000 IJKMEDIA: : Audio: aac ([15][0][0][0] / 0x000F), 22050 Hz, stereo, fltp IJKMEDIA: IJKMEDIA: Metadata: IJKMEDIA: variant_bitrate : IJKMEDIA: 0
av_find_best_stream av_find_best_stream 函数主要用来选择最合适的音视频流,其定义简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 int av_find_best_stream (AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags) { for (i = 0 ; i < nb_streams; i++) { int real_stream_index = program ? program[i] : i; AVStream *st = ic->streams[real_stream_index]; AVCodecParameters *par = st->codecpar; if (par->codec_type != type) continue ; if (wanted_stream_nb >= 0 && real_stream_index != wanted_stream_nb) continue ; if (type == AVMEDIA_TYPE_AUDIO && !(par->channels && par->sample_rate)) continue ; if (decoder_ret) { decoder = find_decoder (ic, st, par->codec_id); if (!decoder) { if (ret < 0 ) ret = AVERROR_DECODER_NOT_FOUND; continue ; } } disposition = !(st->disposition & (AV_DISPOSITION_HEARING_IMPAIRED | AV_DISPOSITION_VISUAL_IMPAIRED)); count = st->codec_info_nb_frames; bitrate = par->bit_rate; multiframe = FFMIN (5 , count); if ((best_disposition > disposition) || (best_disposition == disposition && best_multiframe > multiframe) || (best_disposition == disposition && best_multiframe == multiframe && best_bitrate > bitrate) || (best_disposition == disposition && best_multiframe == multiframe && best_bitrate == bitrate && best_count >= count)) continue ; best_disposition = disposition; best_count = count; best_bitrate = bitrate; best_multiframe = multiframe; ret = real_stream_index; best_decoder = decoder; } return ret; }
可知 av_find_best_stream 函数主要从三个维度进行选择,比较顺序依次是 disposition、multiframe 和 bitrate,在 disposition 相同的时候选择已解码帧数多的,对应 multiframe,最后选择比特率高的,对应 bitrate。
disposition 对应的是 AVStream 的 disposition 成员,具体值是 AV_DISPOSITION_ 标识符,比如上面的 AV_DISPOSITION_HEARING_IMPAIRED 表示该流是面向听障人群的,这个暂时了解一下。
对应 read_thread 函数中 av_find_best_stream 找到了最佳的音频、视频、字幕流,接下来就是解码播放了。
stream_component_open stream_component_open 函数主要是创建音频渲染线程,音频、视频、字幕解码线程以及初始化 VideoState,其定义简化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 static int stream_component_open (FFPlayer *ffp, int stream_index) { avctx = avcodec_alloc_context3(NULL); if (!avctx) return AVERROR(ENOMEM); ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar); if (ret < 0 ) goto fail; av_codec_set_pkt_timebase(avctx, ic->streams[stream_index]->time_base); codec = avcodec_find_decoder(avctx->codec_id); if (forced_codec_name) codec = avcodec_find_decoder_by_name(forced_codec_name); if (!codec) { if (forced_codec_name) av_log(NULL, AV_LOG_WARNING, "No codec could be found with name '%s'\n" , forced_codec_name); else av_log(NULL, AV_LOG_WARNING, "No codec could be found with id %d\n" , avctx->codec_id); ret = AVERROR(EINVAL); goto fail; } switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread); if ((is->ic->iformat->flags & (AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK)) && !is->ic->iformat->read_seek) { is->auddec.start_pts = is->audio_st->start_time; is->auddec.start_pts_tb = is->audio_st->time_base; } if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec" )) < 0 ) goto out; SDL_AoutPauseAudio(ffp->aout, 0 ); break ; case AVMEDIA_TYPE_VIDEO: is->video_stream = stream_index; is->video_st = ic->streams[stream_index]; if (ffp->async_init_decoder) { } else { decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread); ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp); if (!ffp->node_vdec) goto fail; } if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec" )) < 0 ) goto out; break ; case AVMEDIA_TYPE_SUBTITLE: decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread); if ((ret = decoder_start(&is->subdec, subtitle_thread, ffp, "ff_subtitle_dec" )) < 0 ) goto out; break ; default : break ; } goto out; fail: avcodec_free_context(&avctx); out: av_dict_free(&opts); return ret; }
可知 stream_component_open 函数已经来是了创建了对应的解码线程了,上述代码注释比较详细,这里不再赘述,在对应 read_thread 中,该函数之后填充了 IjkMediaMeta 的一些数据,此时 ffp->prepared = true; 并向应用层发送播放准备完成的事件消息 FFP_MSG_PREPARED,最终回调给 OnPreparedListener 中。
read_thread主循环 这里的主循环是指 read_thread 中读取数据的主循环,关键流程如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 for (;;) { if (is->abort_request) break ; if (is->seek_req) { is->seek_req = 0 ; ffp_notify_msg3 (ffp, FFP_MSG_SEEK_COMPLETE, (int )fftime_to_milliseconds (seek_target), ret); ffp_toggle_buffering (ffp, 1 ); } if (is->queue_attachments_req) { if (is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) { AVPacket copy = { 0 }; if ((ret = av_packet_ref (©, &is->video_st->attached_pic)) < 0 ) goto fail; packet_queue_put (&is->videoq, ©); packet_queue_put_nullpacket (&is->videoq, is->video_stream); } is->queue_attachments_req = 0 ; } if (ffp->infinite_buffer<1 && !is->seek_req && SDL_LockMutex (wait_mutex); SDL_CondWaitTimeout (is->continue_read_thread, wait_mutex, 10 ); SDL_UnlockMutex (wait_mutex); continue ; } if ((!is->paused || completed) && (!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining (&is->sampq) == 0 )) && (!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining (&is->pictq) == 0 ))) { if (ffp->loop != 1 && (!ffp->loop || --ffp->loop)) { stream_seek (is, ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0 , 0 , 0 ); } else if (ffp->autoexit) { ret = AVERROR_EOF; goto fail; } else { ffp_notify_msg1 (ffp, FFP_MSG_ERROR); ffp_notify_msg1 (ffp, FFP_MSG_COMPLETED); } } pkt->flags = 0 ; ret = av_read_frame (ic, pkt); if (ret < 0 ) { if (pb_eof) { if (is->video_stream >= 0 ) packet_queue_put_nullpacket (&is->videoq, is->video_stream); if (is->audio_stream >= 0 ) packet_queue_put_nullpacket (&is->audioq, is->audio_stream); if (is->subtitle_stream >= 0 ) packet_queue_put_nullpacket (&is->subtitleq, is->subtitle_stream); is->eof = 1 ; } if (pb_error) { if (is->video_stream >= 0 ) packet_queue_put_nullpacket (&is->videoq, is->video_stream); if (is->audio_stream >= 0 ) packet_queue_put_nullpacket (&is->audioq, is->audio_stream); if (is->subtitle_stream >= 0 ) packet_queue_put_nullpacket (&is->subtitleq, is->subtitle_stream); is->eof = 1 ; ffp->error = pb_error; av_log (ffp, AV_LOG_ERROR, "av_read_frame error: %s\n" , ffp_get_error_string (ffp->error)); } else { ffp->error = 0 ; } if (is->eof) { ffp_toggle_buffering (ffp, 0 ); SDL_Delay (100 ); } SDL_LockMutex (wait_mutex); SDL_CondWaitTimeout (is->continue_read_thread, wait_mutex, 10 ); SDL_UnlockMutex (wait_mutex); ffp_statistic_l (ffp); continue ; } else { is->eof = 0 ; } if (pkt->stream_index == is->audio_stream && pkt_in_play_range) { packet_queue_put (&is->audioq, pkt); } else if (pkt->stream_index == is->video_stream && pkt_in_play_range && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) { packet_queue_put (&is->videoq, pkt); } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) { packet_queue_put (&is->subtitleq, pkt); } else { av_packet_unref (pkt); } }
可知 read_thread 中的死循环主要用来进行数据读取的,每次读取的一帧压缩编码数据都添加到未解码的帧队列中,具体如下:
如果打开流失败等会调用 stream_close 函数或者应用层调用函数 release 释放播放器直接 break。
处理播放过程中的 seek 操作。
处理流中的 attached_pic,如果⼀个流中含有 AV_DISPOSITION_ATTACHED_PIC 说明这个流是 *.mp3 等⽂件中的⼀个 Video Stream,该流只有⼀个 AVPacket 就是 attached_pic。
处理队列满了的情况,一是当未解码的队列,即未解码的音频、视频、字幕对应的队列大小之和超过 15M,二是使用 stream_has_enough_packets 判断音频、视频、字幕流是否已经有了足够的待解码的 AVPacket,大于则相当于解复用器缓存满了,延迟 10ms 供解码器消耗数据。
检查码流是否播放完、是否设置循环播放、是否自动退出以及播放出错的处理等。
av_read_frame 可以说是 read_thread 线程的关键函数,其作用就是解复用,每次读取的一帧压缩编码数据都添加到未解码的帧队列中供相应的解码线程使用。
检测数据读取情况,主要是数据读取到流末尾的处理和数据读取出错的处理。
如果 av_read_frame 数据读取成功则将其添加到对应的未解码的帧队列中。
到此 IjkPlayer 的数据读取线程 read_thread 线程基本梳理完毕,其实大多是还是 ffmpeg 的东西。