IjkPlayer之數(shù)據(jù)讀取線程read_thread
??

PS: 控制新技術對你的影響而不是被控制。這是 IjkPlayer 系列的第五篇文章了,前四篇依次如下:本文分析下 IjkPlayer 的數(shù)據(jù)讀取線程
read_thread,目的是理清其基本流程以及關鍵函數(shù)的調(diào)用,主要內(nèi)容如下:IjkPlayer基本使用
read_thread創(chuàng)建
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主循環(huán)
IjkPlayer基本使用
簡單回顧下 IjkPlayer 的基本使用方式如下://?創(chuàng)建IjkMediaPlayer當調(diào)用
IjkMediaPlayer?mMediaPlayer?=?new?IjkMediaPlayer();
//?設置Log級別
mMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
//?設置Option
mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,?"mediacodec",?1);
//?...
//?設置事件監(jiān)聽
mMediaPlayer.setOnPreparedListener(mPreparedListener);
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
mMediaPlayer.setOnCompletionListener(mCompletionListener);
mMediaPlayer.setOnErrorListener(mErrorListener);
mMediaPlayer.setOnInfoListener(mInfoListener);
//?設置Surface
mMediaPlayer.setSurface(surface)
//?...
//?設置url
mMediaPlayer.setDataSource(dataSource);
//?準備播放
mMediaPlayer.prepareAsync();
prepareAsync 之后收到 onPrepared 回調(diào)是調(diào)用 start 開始播放:@Override到此,一般情況下視頻就能正常播放了,這里只關注調(diào)用流程。
public?void?onPrepared(IMediaPlayer?mp)?{
????//?開始播放
????mMediaPlayer.start();
}
read_thread創(chuàng)建
從IjkMediaPlayer 的方法 prepareAsync 開始看,其調(diào)用流程如下: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 最后調(diào)用的是函數(shù) stream_open ,其定義如下:static?VideoState?*stream_open(FFPlayer?*ffp,?const?char?*filename,?AVInputFormat?*iformat){可知函數(shù)
????av_log(NULL,?AV_LOG_INFO,?"stream_open\n");
????assert(!ffp->is);
????//?初始化VideoState及部分參數(shù)。
????VideoState?*is;
????is?=?av_mallocz(sizeof(VideoState));
????if?(!is)
????????return?NULL;
????is->filename?=?av_strdup(filename);
????if?(!is->filename)
????????goto?fail;
????//?這里iformat還沒被賦值,后面通過探測找到最佳的AVInputFormat
????is->iformat?=?iformat;
????is->ytop????=?0;
????is->xleft???=?0;
#if?defined(__ANDROID__)
????if?(ffp->soundtouch_enable)?{
????????is->handle?=?ijk_soundtouch_create();
????}
#endif
????/*?start?video?display?*/
????//?解碼后幀隊列初始化
????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;
????//?未解碼數(shù)據(jù)隊列初始化
????if?(packet_queue_init(&is->videoq)?<?0?||
????????packet_queue_init(&is->audioq)?<?0?||
????????packet_queue_init(&is->subtitleq)?<?0)
????????goto?fail;
????//?條件變量(信號量)初始化,包括讀線程、視頻seek、音頻seek相關信號量
????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;
????//?設置音視頻同步方式,默認AV_SYNC_AUDIO_MASTER
????is->av_sync_type?=?ffp->av_sync_type;
????//?播放互斥鎖
????is->play_mutex?=?SDL_CreateMutex();
????//?精準seek互斥鎖
????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)?{
????????//?mediacodec
????????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及部分參數(shù)。初始化幀隊列,包括初始化已解碼的視頻幀隊列
pictq、音頻幀隊列sampq和字幕幀隊列subpq和未解碼的視頻數(shù)據(jù)隊列videoq、音頻數(shù)據(jù)隊列audioq和字幕數(shù)據(jù)隊列subtitleq。音視頻同步方式及時鐘初始化,默認
AV_SYNC_AUDIO_MASTER,也就是音頻時鐘作為主時鐘。音量初始化范圍。
創(chuàng)建了線程名為
ff_vout的視頻渲染線程video_refresh_thread。創(chuàng)建了線程名為
ff_read的視頻渲染線程read_thread。
read_thread 函數(shù)的分析, 函數(shù) read_thread 關鍵部分簡化如下:static?int?read_thread(void?*arg){下面內(nèi)容僅限于
????//?...
????//?1.?創(chuàng)建AVFormatContext,指定打開流、關閉流的默認函數(shù)等
????ic?=?avformat_alloc_context();
????if?(!ic)?{
????????av_log(NULL,?AV_LOG_FATAL,?"Could?not?allocate?context.\n");
????????ret?=?AVERROR(ENOMEM);
????????goto?fail;
????}
????//?...
????//?2.?打開碼流獲取header信息
????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);
????//?...
????//?3.?獲取碼流信息
????if?(ffp->find_stream_info)?{
????????err?=?avformat_find_stream_info(ic,?opts);
????}?
????ffp_notify_msg1(ffp,?FFP_MSG_FIND_STREAM_INFO);
????//?...
????//?4.?如果有指定播放起始時間則seek到該播放位置
????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);
????}
????//?...
????//?5.?打印格式信息
????av_dump_format(ic,?0,?is->filename,?0);
}
read_thread 函數(shù)的主要流程。avformat_alloc_context
avformat_alloc_context 函數(shù)主要是為 AVFormatContext 分配內(nèi)存、初始化 ic->internal 部分參數(shù),如下:AVFormatContext?*avformat_alloc_context(void){繼續(xù)查看
????//?為AVFormatContext分配內(nèi)存
????AVFormatContext?*ic;
????ic?=?av_malloc(sizeof(AVFormatContext));
????if?(!ic)?return?ic;
????//?初始化打開流、關閉流的默認函數(shù)
????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 函數(shù)如下:static?void?avformat_get_context_defaults(AVFormatContext?*s){這里指定了打開流、關閉流的默認函數(shù)分別為
????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,這里暫不關注后續(xù)流程。avformat_open_input
avformat_open_input 函數(shù)主要用于打開碼流獲取 header 信息,其定義簡化如下: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;
????//?協(xié)議黑白名單檢查、碼流格式白名單檢查等
????//?...
????//?讀取媒體頭部
????//?read_header主要是做某種格式的初始化工作,如填充自己的私有結構
????//?根據(jù)流的數(shù)量分配流結構并初始化,把文件指針指向數(shù)據(jù)區(qū)開始處等
????//?創(chuàng)建好AVStream,并等待在后續(xù)的流程中,可以取出或?qū)懭胍粢曨l流信息
????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;
????//?...
????//?更新AVStream解碼器相關信息到AVCodecContext
????update_stream_avctx(s);
????//?...
}
avformat_open_input 函數(shù)主要是打開碼流探測碼流輸入格式、協(xié)議黑白名單和碼流格式白名單檢查、讀取文件 header 信息等,,最后使用函數(shù) update_stream_avctx 更新 AVStream 解碼器相關信息到對應的 AVCodecContext 中,這個操作會在后續(xù)流程中經(jīng)常看到。最重要的是打開碼流探測碼流輸入格式和讀取文件 header 信息,分別調(diào)用了函數(shù) init_input 和 read_header 函數(shù),read_header 會在讀取 header 信息過程中完成 AVStream 的初始化。init_input 函數(shù)主要是探測碼流格式并返回該碼流格式的得分,最終找到對應該碼流格式的最佳 AVInputFormat,這個結構體是初始化時注冊的解復用器,每個解復用器都對應一個 AVInputFormat 對象,同理復用器對應的是 AVOutputFormat 這里暫且了解一下。init_input 函數(shù)如果執(zhí)行成功,則對應的碼流格式已經(jīng)確定,此時就可以調(diào)用 read_header 函數(shù)了,其對應的是當前碼流格式 AVInputFormat 對應的解復用器 demuxer 中的 xxx_read_header 函數(shù),如果是 hls 格式的碼流,則對應的則是 hls_read_header,其定義如下:AVInputFormat?ff_hls_demuxer?=?{
????.name???????????=?"hls,applehttp",
????.read_header???=?hls_read_header,
????//?...
};
//?hls_read_header
static?int?hls_read_header(AVFormatContext?*s,?AVDictionary?**options){
????//?...
}
avformat_find_stream_info
avformat_find_stream_info 函數(shù)主要用來獲取碼流信息,用來探測沒有 header 的文件格式比較有用,可以通過該函數(shù)獲取視頻寬高、總時長、碼率、幀率、像素格式等等,其定義簡化如下:int?avformat_find_stream_info(AVFormatContext?*ic,?AVDictionary?**options){由于
????//?...
????//?1.?遍歷流
????for?(i?=?0;?i?<?ic->nb_streams;?i++)?{
????????//?初始化流中的解析器,具體是AVCodecParserContext和AVCodecParser初始化
????????st->parser?=?av_parser_init(st->codecpar->codec_id);
????????//?...
????????//?根據(jù)AVStream中的解碼器參數(shù)探測對應的解碼器并返回
????????codec?=?find_probe_decoder(ic,?st,?st->codecpar->codec_id);
????????//?...
????????//?如果解碼器參數(shù)不全,根據(jù)指定的AVCodec初始化AVCodecContext以及調(diào)用解碼器的init函數(shù)以初始化解碼器
????????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__);
????????}
????}
????//?2.?死循環(huán)獲取碼流信息
????for?(;;)?{
????????//?...
????????//?檢查是否有中斷請求,如果有則調(diào)用中斷函數(shù)
????????if?(ff_check_interrupt(&ic->interrupt_callback))?{
????????????break;
????????}
????????//?遍歷流,檢查是否還需要處理解碼器相關參數(shù)
????????for?(i?=?0;?i?<?ic->nb_streams;?i++)?{
????????????int?fps_analyze_framecount?=?20;
????????????st?=?ic->streams[i];
????????????//?檢查流中的的解碼器參數(shù),如果完整則break,反之則繼續(xù)執(zhí)行,以便進一步分析
????????????if?(!has_codec_parameters(st,?NULL))
????????????????break;
????????????//?...
????????}
????????if?(i?==?ic->nb_streams)?{
????????????//?標識所有的流分析結束
????????????analyzed_all_streams?=?1;
????????????//?如果當前AVFormatContext設置ctx_flags為AVFMTCTX_NOHEADER則表明當前碼流是沒有header信息的
????????????//?此時需要讀取一些數(shù)據(jù)包來獲取流信息,反之則直接break出去,死循環(huán)正常結束
????????????if?(!(ic->ctx_flags?&?AVFMTCTX_NOHEADER))?{
????????????????/*?If?we?found?the?info?for?all?the?codecs,?we?can?stop.?*/
????????????????ret?=?count;
????????????????av_log(ic,?AV_LOG_DEBUG,?"All?info?found\n");
????????????????flush_codecs?=?0;
????????????????break;
????????????}
????????}
????????//?讀取的數(shù)據(jù)已經(jīng)多余允許探測的數(shù)據(jù)大小,但是還未得到所有的編解碼器信息
????????if?(read_size?>=?probesize)?{
????????????break;
????????}
????????//?以下針對當前碼流是沒有header信息的處理
????????//?讀取一幀壓縮編碼數(shù)據(jù)
????????ret?=?read_frame_internal(ic,?&pkt1);
????????if?(ret?==?AVERROR(EAGAIN))?continue;
????????if?(ret?<?0)?{
????????????/*?EOF?or?error*/
????????????eof_reached?=?1;
????????????break;
????????}
????????//?讀取的數(shù)據(jù)添加到緩存中,后續(xù)會先從緩存中讀這些數(shù)據(jù)
????????ret?=?add_to_pktbuf(&ic->internal->packet_buffer,?pkt,
????????????????????????????????&ic->internal->packet_buffer_end,?0);
????????//?嘗試解碼一些壓縮編碼數(shù)據(jù)???????????????????????
????????try_decode_frame(ic,?st,?pkt,(options?&&?i?<?orig_nb_streams)???&options[i]?:?NULL);
????????//?...
????}
????//?3.?讀取數(shù)據(jù)到流的末尾處理
????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,
?????????}
????}
????//?4.?解碼器執(zhí)行flushing操作,避免緩存的數(shù)據(jù)未被取出
????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];
????????????/*?flush?the?decoders?*/
????????????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);
????????????????}
????????????}
????????}
????}
????//?5.?后續(xù)就是一些碼流信息的計算,比如pix_fmt、橫縱比SAR、實際幀率、平均幀率等等
????//?...
????//?6.?從流的內(nèi)部AVCodecContext(avctx)更新流對應的解碼器參數(shù)AVCodecParameters
????for?(i?=?0;?i?<?ic->nb_streams;?i++)?{
????????ret?=?avcodec_parameters_from_context(st->codecpar,?st->internal->avctx);
????????//?...
????}
????//?...
}
avformat_find_stream_info 函數(shù)代碼量比較多,上述代碼省略了大部分細節(jié),保留了比較關鍵的部分,這里只看主要流程,從源碼可知,該函數(shù)中會頻繁的使用 has_codec_parameters 函數(shù)來檢查流內(nèi)部解碼器上下問參數(shù)是否合理,如果不合理則要盡可能的采取措施保證流內(nèi)部解碼器上下文參數(shù)合理,當 ret = 0; 標識 avformat_find_stream_info 執(zhí)行成功,其主要流程如下:遍歷流,根據(jù)流中的一些參數(shù)初始化
AVCodecParser和AVCodecParserContext,通過函數(shù)find_probe_decoder來探測解碼器,使用函數(shù)avcodec_open2來初始化AVCodecContext并調(diào)用解碼器的init函數(shù)來初始化解碼器靜態(tài)數(shù)據(jù)。for (;;)死循環(huán)主要是使用ff_check_interrupt函數(shù)進行中斷檢測、遍歷流使用has_codec_parameters函數(shù)檢測碼流內(nèi)部的解碼器上下文參數(shù)是否合理及數(shù)據(jù)讀取的,如果合理且當前碼流有 header 信息,則標識analyzed_all_streams = 1;且flush_codecs = 0;直接break退出該死循環(huán),如果當前碼流有 header 信息,也就是ic->ctx_flags被設置為AVFMTCTX_NOHEADER的時候,此時需要調(diào)用read_frame_internal函數(shù)讀取一幀編解碼數(shù)據(jù),并將其添加到緩存中,調(diào)用try_decode_frame函數(shù)解碼一幀數(shù)據(jù)進一步填充流中的AVCodecContext。eof_reached = 1表示前面死循環(huán)使用read_frame_internal函數(shù)讀取到流的末尾了,遍歷流,再次使用has_codec_parameters函數(shù)來檢查流內(nèi)部解碼器上下文參數(shù)是否合理,不合理則再重復上面 2 中的步驟來進行解碼器上下文相關參數(shù)的初始化。解碼的過程就是一個不斷放數(shù)據(jù)和取數(shù)據(jù)的過程,分別對應的是
avcodec_send_packet和avcodec_receive_frame函數(shù),為了避免解碼器數(shù)據(jù)殘留,這里通過空的AVPacket來 flushing 解碼器,執(zhí)行的條件是flush_codecs = 1的時候,也就是需要需要上面 2 中調(diào)用了try_decode_frame執(zhí)行了解碼操作。后續(xù)就是一些碼流信息的計算,比如pix_fmt、橫縱比SAR、實際幀率、平均幀率等等。
遍歷流,調(diào)用
avcodec_parameters_from_context函數(shù)將之前填充的流的內(nèi)部AVCodecContext中的解碼器參數(shù)填充到流的解碼器參數(shù)st->codecpar中,對應結構體AVCodecParameters,到此avformat_find_stream_info函數(shù)主要流程分析完畢。
avformat_seek_file
avformat_seek_file 主要用來執(zhí)行 seek 操作的,其定義簡化如下:int?avformat_seek_file(AVFormatContext?*s,?int?stream_index,?int64_t?min_ts,可知
???????????????????????int64_t?ts,?int64_t?max_ts,?int?flags){
????//?...???????????????????
????//?優(yōu)先使用read_seek2
????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;
????}
????//?...
????//?如果不支持read_seek2則嘗試使用舊版API?seek
????if?(s->iformat->read_seek?||?1)?{
????????//?...
????????int?ret?=?av_seek_frame(s,?stream_index,?ts,?flags?|?dir);
????????return?ret;
????}
????return?-1;?//unreachable???????????????????????????
}
avformat_seek_file 函數(shù)執(zhí)行時。如果當前解復用器(AVInputFormat)支持 read_seek2,則使用對應的 read_seek2 函數(shù),否則調(diào)用舊版 API 里面的 av_seek_frame 函數(shù)進行 seek,av_seek_frame 函數(shù)如下: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 函數(shù)也就是 read_seek2 函數(shù)進行 seek,如果支持 read_seek 則優(yōu)先調(diào)用內(nèi)部 seek 函數(shù) seek_frame_internal 進行 seek,seek_frame_internal 函數(shù)主要提供尋幀的幾種方式:seek_frame_byte:按照字節(jié)方式尋幀read_seek:按照當前指定格式的方式尋幀,具體由該格式對應的解復用器提供支持。ff_seek_frame_binary:按照二分查找的方式尋幀。seek_frame_generic:按照通用方式尋幀。
read_seek2,僅支持 read_seek, ff_hls_demuxer 定義如下:AVInputFormat?ff_hls_demuxer?=?{
????//?...
????.read_seek??????=?hls_read_seek,
};
av_dump_format
av_dump_format 函數(shù)用來根據(jù)當前 AVFormatContext 來打印碼流輸入格式的詳細信息,直接看 IjkPlayer 正常播放視頻打印信息如下: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 函數(shù)主要用來選擇最合適的音視頻流,其定義簡化如下: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 函數(shù)主要從三個維度進行選擇,比較順序依次是 disposition、multiframe 和 bitrate,在 disposition 相同的時候選擇已解碼幀數(shù)多的,對應 multiframe,最后選擇比特率高的,對應 bitrate。disposition 對應的是 AVStream 的 disposition 成員,具體值是 AV_DISPOSITION_ 標識符,比如上面的 AV_DISPOSITION_HEARING_IMPAIRED 表示該流是面向聽障人群的,這個暫時了解一下。對應 read_thread 函數(shù)中 av_find_best_stream 找到了最佳的音頻、視頻、字幕流,接下來就是解碼播放了。stream_component_open
stream_component_open 函數(shù)主要是創(chuàng)建音頻渲染線程,音頻、視頻、字幕解碼線程以及初始化 VideoState,其定義簡化如下:static?int?stream_component_open(FFPlayer?*ffp,?int?stream_index){可知
????//?...
????//?1.?初始化AVCodecContext
????avctx?=?avcodec_alloc_context3(NULL);
????if?(!avctx)
????????return?AVERROR(ENOMEM);
????//?2.?使用流的解碼器參數(shù)更新當前AVCodecContext對應參數(shù)
????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);
????//?3.?根據(jù)解碼器ID查找解碼器
????codec?=?avcodec_find_decoder(avctx->codec_id);
????//?...
????//?4.?如果已經(jīng)指定了解碼器名稱則使用解碼器的名字再查找一次解碼器
????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;
????}
????//?...
????//?5.?音頻渲染線程創(chuàng)建,音視、視頻、字幕解碼器初始化,音視、視頻、字幕開始解碼
????switch?(avctx->codec_type)?{
????case?AVMEDIA_TYPE_AUDIO:
????????//?...
????????//?打開音頻輸出,創(chuàng)建音頻輸出線程ff_aout_android,對應的音頻線程函數(shù)aout_thread,
????????//?最終調(diào)用AudioTrack的write方法寫音頻數(shù)據(jù)
????????//?...
????????//?音頻解碼器初始化
????????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;
????????}
????????//?開始音頻解碼,這里創(chuàng)建音頻解碼線程?ff_audio_dec,對應的音頻解碼線程函數(shù)為audio_thread
????????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];
????????//?異步初始化解碼器,和使用MediaCodec有關
????????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;
????????}
????????????//?開始視頻解碼,這里創(chuàng)建音頻解碼線程?ff_video_dec,對應的音頻解碼線程函數(shù)為video_thread
????????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);
????????//?開始視頻解碼,這里創(chuàng)建音頻解碼線程?ff_subtitle_dec,對應的音頻解碼線程函數(shù)為subtitle_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 函數(shù)已經(jīng)來是了創(chuàng)建了對應的解碼線程了,上述代碼注釋比較詳細,這里不再贅述,在對應 read_thread 中,該函數(shù)之后填充了 IjkMediaMeta 的一些數(shù)據(jù),此時 ffp->prepared = true; 并向應用層發(fā)送播放準備完成的事件消息 FFP_MSG_PREPARED,最終回調(diào)給 OnPreparedListener 中。read_thread主循環(huán)
這里的主循環(huán)是指read_thread 中讀取數(shù)據(jù)的主循環(huán),關鍵流程如下:for?(;;)?{可知
????//?1.?流關閉或者應用層release的時候is->abort_request為1
????if?(is->abort_request)
????????break;
????//?...
????//?2.?處理seek操作
????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);
????}
????//?3.?處理attached_pic
????//?如果?個流中含有AV_DISPOSITION_ATTACHED_PIC說明這個流是*.mp3等??件中的?個Video?Stream
????//?該流只有?個AVPacket就是attached_pic
????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;
????}
????//?4.?如果隊列滿了,暫時不能讀取更多數(shù)據(jù)
????//?如果是網(wǎng)絡流ffp->infinite_buffer為1
????/*?if?the?queue?are?full,?no?need?to?read?more?*/
????if?(ffp->infinite_buffer<1?&&?!is->seek_req?&&
????????//?...
????????SDL_LockMutex(wait_mutex);
????????//?等待10ms讓解碼線程有時間消耗
????????SDL_CondWaitTimeout(is->continue_read_thread,?wait_mutex,?10);
????????SDL_UnlockMutex(wait_mutex);
????????continue;
????}
????//?5.?檢查碼流是否播放完成
????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)))?{
????????//?是否設置循環(huán)播放
????????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;
????//?6.?讀取數(shù)據(jù)包
????ret?=?av_read_frame(ic,?pkt);
????//?7.?檢測數(shù)據(jù)讀取情況
????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;
????????}
????????//?數(shù)據(jù)讀取處理...
????????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));
????????????//?break;
????????}?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;
????}
????//?...
????//?8.?填充未解碼的幀隊列
????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 中的死循環(huán)主要用來進行數(shù)據(jù)讀取的,每次讀取的一幀壓縮編碼數(shù)據(jù)都添加到未解碼的幀隊列中,具體如下:如果打開流失敗等會調(diào)用
stream_close函數(shù)或者應用層調(diào)用函數(shù)release釋放播放器直接break。處理播放過程中的 seek 操作。
處理流中的
attached_pic,如果?個流中含有AV_DISPOSITION_ATTACHED_PIC說明這個流是 *.mp3 等?件中的?個Video Stream,該流只有?個AVPacket就是attached_pic。處理隊列滿了的情況,一是當未解碼的隊列,即未解碼的音頻、視頻、字幕對應的隊列大小之和超過 15M,二是使用
stream_has_enough_packets判斷音頻、視頻、字幕流是否已經(jīng)有了足夠的待解碼的AVPacket,大于則相當于解復用器緩存滿了,延遲 10ms 供解碼器消耗數(shù)據(jù)。檢查碼流是否播放完、是否設置循環(huán)播放、是否自動退出以及播放出錯的處理等。
av_read_frame可以說是read_thread線程的關鍵函數(shù),其作用就是解復用,每次讀取的一幀壓縮編碼數(shù)據(jù)都添加到未解碼的幀隊列中供相應的解碼線程使用。檢測數(shù)據(jù)讀取情況,主要是數(shù)據(jù)讀取到流末尾的處理和數(shù)據(jù)讀取出錯的處理。
如果
av_read_frame數(shù)據(jù)讀取成功則將其添加到對應的未解碼的幀隊列中。
read_thread 線程基本梳理完畢,期待下篇。推薦閱讀:??
