摘要:我們下面先從讀取線程入手。無論這個循環前后干了什么,都是要走這一步,讀取數據幀。從開始,我理解的是計算出當前數據幀的時間戳后再計算出播放的起始時間到當前時間,然后看這個時間戳是否在此范圍內。
ijkplayer現在比較流行,因為工作關系,接觸了他,現在做個簡單的分析記錄吧。我這里直接跳過java層代碼,進入c層,因為大多數的工作都是通過jni調用到c層來完成的,java層的內容并不是主體功能。
先來看看線索。直接看ijkplayer_jni.c文件,在ijkmedia下。所有的c函數及java函數映射關系都在這里。java上層調用的_prepareAsync方法為native方法,在這里映射為IjkMediaPlayer_prepareAsync。經過一系列調用后會走到ijkplayer.c的ijkmp_prepare_async_l方法里面,這里看下:
ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING); msg_queue_start(&mp->ffplayer->msg_queue); // released in msg_loop ijkmp_inc_ref(mp); mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop"); // msg_thread is detached inside msg_loop // TODO: 9 release weak_thiz if pthread_create() failed; int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source); if (retval < 0) { ijkmp_change_state_l(mp, MP_STATE_ERROR); return retval; }
這里顯示啟動了一個隊列,然后啟動了一個loop消息線程,然后走了一個關鍵函數ffp_prepare_async_l。所謂啟動隊列,其實就是分配空間初始化,然后塞入個私有消息體。后面這個loop線程,真正的執行體在ijkplayer_jni.c的message_loop。這個我們暫時不去管他,理解為一個獨立的線程來處理消息派發。重點看下面的ffp_prepare_async_l,這個才是進入到準備播放的階段:
int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name) { assert(ffp); assert(!ffp->is); assert(file_name); if (av_stristart(file_name, "rtmp", NULL) || av_stristart(file_name, "rtsp", NULL)) { // There is total different meaning for "timeout" option in rtmp av_log(ffp, AV_LOG_WARNING, "remove "timeout" option for rtmp. "); av_dict_set(&ffp->format_opts, "timeout", NULL, 0); } /* there is a length limit in avformat */ if (strlen(file_name) + 1 > 1024) { av_log(ffp, AV_LOG_ERROR, "%s too long url ", __func__); if (avio_find_protocol_name("ijklongurl:")) { av_dict_set(&ffp->format_opts, "ijklongurl-url", file_name, 0); file_name = "ijklongurl:"; } } av_log(NULL, AV_LOG_INFO, "===== versions ===== "); ffp_show_version_str(ffp, "ijkplayer", ijk_version_info()); ffp_show_version_str(ffp, "FFmpeg", av_version_info()); ffp_show_version_int(ffp, "libavutil", avutil_version()); ffp_show_version_int(ffp, "libavcodec", avcodec_version()); ffp_show_version_int(ffp, "libavformat", avformat_version()); ffp_show_version_int(ffp, "libswscale", swscale_version()); ffp_show_version_int(ffp, "libswresample", swresample_version()); av_log(NULL, AV_LOG_INFO, "===== options ===== "); ffp_show_dict(ffp, "player-opts", ffp->player_opts); ffp_show_dict(ffp, "format-opts", ffp->format_opts); ffp_show_dict(ffp, "codec-opts ", ffp->codec_opts); ffp_show_dict(ffp, "sws-opts ", ffp->sws_dict); ffp_show_dict(ffp, "swr-opts ", ffp->swr_opts); av_log(NULL, AV_LOG_INFO, "=================== "); av_opt_set_dict(ffp, &ffp->player_opts); if (!ffp->aout) { ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp); if (!ffp->aout) return -1; } #if CONFIG_AVFILTER if (ffp->vfilter0) { GROW_ARRAY(ffp->vfilters_list, ffp->nb_vfilters); ffp->vfilters_list[ffp->nb_vfilters - 1] = ffp->vfilter0; } #endif VideoState *is = stream_open(ffp, file_name, NULL); if (!is) { av_log(NULL, AV_LOG_WARNING, "ffp_prepare_async_l: stream_open failed OOM"); return EIJK_OUT_OF_MEMORY; } ffp->is = is; ffp->input_filename = av_strdup(file_name); return 0; }
前面判斷直播視頻流協議rtmp或rtsp,再后面基本是輸出一對信息的,真正的核心函數是stream_open。這個才是根據地址打開視頻流,代碼如下:
static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat) { 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; /* 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; 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 ", SDL_GetError()); goto fail; } 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; is->audio_volume = SDL_MIX_MAXVOLUME; is->muted = 0; is->av_sync_type = ffp->av_sync_type; is->play_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->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 ", SDL_GetError()); fail: is->abort_request = true; if (is->video_refresh_tid) SDL_WaitThread(is->video_refresh_tid, NULL); stream_close(ffp); return NULL; } return is; }
這里可以看到有3個隊列初始化,分別是視頻、音頻和字幕,這些隊列通過frame_queue_init又分別分為2個隊列,原始的和解碼后的。再往下看就是2個線程的創建,分別是video_refresh_thread和read_thread,從字面上理解,應當是輸出刷新視頻和讀取線程。我們下面先從讀取線程入手。整個代碼很長,注意這里的ic變量。是個AVFormatContext類型,這里放的是流的信息和數據。
...... ic = avformat_alloc_context(); ...... err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts); ...... err = avformat_find_stream_info(ic, opts); ...... for (i = 0; i < ic->nb_streams; i++) { AVStream *st = ic->streams[i]; enum AVMediaType type = st->codecpar->codec_type; st->discard = AVDISCARD_ALL; if (type >= 0 && ffp->wanted_stream_spec[type] && st_index[type] == -1) if (avformat_match_stream_specifier(ic, st, ffp->wanted_stream_spec[type]) > 0) st_index[type] = i; // choose first h264 if (type == AVMEDIA_TYPE_VIDEO) { enum AVCodecID codec_id = st->codecpar->codec_id; video_stream_count++; if (codec_id == AV_CODEC_ID_H264) { h264_stream_count++; if (first_h264_stream < 0) first_h264_stream = i; } } } ...... /* open the streams */ if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) { stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]); } ret = -1; if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]); } if (is->show_mode == SHOW_MODE_NONE) is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT; if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) { stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]); } ...... /* offset should be seeked*/ if (ffp->seek_at_start > 0) { ffp_seek_to_l(ffp, ffp->seek_at_start); } for (;;) { ...... ret = av_read_frame(ic, pkt); ...... /* check if packet is in play range specified by user, then queue, otherwise discard */ stream_start_time = ic->streams[pkt->stream_index]->start_time; pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; pkt_in_play_range = ffp->duration == AV_NOPTS_VALUE || (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) * av_q2d(ic->streams[pkt->stream_index]->time_base) - (double)(ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0) / 1000000 <= ((double)ffp->duration / 1000000); ......@@@ 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); } } ......
avformat_alloc_context是分配空間初始化。然后在avformat_open_input里面填充ic,這個函數里要讀取網絡數據包的信息,并判定格式等等操作。avformat_find_stream_info這個函數內部看的不是很明白,但是感覺是在找解碼器,后面再分析這個吧,先看大流程。這個for循環取出每一幀,然后進行判斷類型,音視頻字幕歸類,并分別設置個數。再來進入到核心的一個函數,就是stream_component_open,這里根據不同類型的幀是否有來進行流的讀取及解碼工作。這里本來有個疑問,如果在解碼之前就處理幀數據,例如優化丟幀處理之類的,是否會加快效率呢?不行,如果不解碼就無法分辨出gop里的幀哪個是關鍵幀i幀,對于后面的處理帶來很大不便(特此注明)。真正的讀取在av_read_frame,讀取解碼后的數據,讀取到pkt里。這個過程是在一個for無限循環內部進行的,在seek的處理之后進行。這個for不小心會漏看的......那么這個av_read_frame讀取到的是什么數據呢?看結構AVPacket,這個的說明已經挺清晰了,是一個壓縮的數據幀,是否是關鍵幀已經在flags里標記了。無論這個循環前后干了什么,都是要走這一步,讀取數據幀。下面有一大段是對錯誤的處理,暫時略過。下面有一段檢查數據包是否在用戶指定的播放范圍內,并根據時間戳排隊,否則丟棄的過程。從stream_start_time=...開始,我理解的是計算出當前數據幀的時間戳后再計算出播放的起始時間到當前時間,然后看這個時間戳是否在此范圍內。范圍內的就put到隊列中,否則丟棄。這里插一句,在@@@這里可以加入直播中的丟幀處理,詳情網上有資料可查。最后循環前做了緩沖的檢查工作。后面就是錯誤處理了,整個流程就是如此。
這里每個跳過地方其實都挺復雜,后面我撿一些關鍵的做下分析。本人剛接觸此類技術,只做筆記。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76406.html
摘要:下面是,讀取頭信息頭信息。猜測網絡部分至少在一開始就應當初始化好的,因此在的過程里面找,在中找到了。就先暫時分析到此吧。 這章要簡單分析下ijkplayer是如何從文件或網絡讀取數據源的。還是read_thread函數中的關鍵點avformat_open_input函數: int avformat_open_input(AVFormatContext **ps, const char ...
摘要:分別為音頻視頻和字母進行相關處理。向下跟蹤兩層,會發現,核心函數是。至此解碼算完了。整個過程真是粗略分析啊,對自己也很抱歉,暫時先這樣吧。 上文中說到在read_thread線程中有個關鍵函數:avformat_open_input(utils.c),應當是讀取視頻文件的,這個函數屬于ffmpeg層。這回進入到其中去看下: int avformat_open_input(AVForma...
摘要:基本上就是對一個數據幀的描述。我理解的是一個未解碼的壓縮數據幀。 read_thread這個最關鍵的讀取線程中,逐步跟蹤,可以明確stream_component_open---> decoder_start---> video_thread--->ffplay_video_thread。這個調用過程,在解碼開始后的異步解碼線程中,調用的是ffplay_video_thread。具體可...
摘要:在的過程中,不僅會啟動,而且會啟動。獲取隊列最后一幀,如果幀中的圖像正常,繼續走渲染畫面。最后通過消息通知開始渲染。這個返回的偏差值就是后面進行是否拋幀或的判斷依據。 在prepare的stream_open過程中,不僅會啟動read_thread,而且會啟動video_refresh_thread。今天就來看看這個video_refresh_thread干了什么。 static in...
摘要:初始化的過程上一篇其實并未完全分析完,這回接著來。層的函數中,最后還有的調用,走的是層的。結構體如下的和,以及,其余是狀態及的內容。整個過程是個異步的過程,并不阻塞。至于的東西,都是在層創建并填充的。 初始化的過程上一篇其實并未完全分析完,這回接著來。java層的initPlayer函數中,最后還有native_setup的調用,走的是c層的IjkMediaPlayer_native_...
閱讀 1594·2019-08-30 13:18
閱讀 1582·2019-08-29 12:19
閱讀 2122·2019-08-26 13:57
閱讀 4147·2019-08-26 13:22
閱讀 1190·2019-08-26 10:35
閱讀 2995·2019-08-23 18:09
閱讀 2508·2019-08-23 17:19
閱讀 685·2019-08-23 17:18