回顧
ijkplayer 開機過程:
- 使用者在 Android 程式中,調用封裝接口 IjkLibLoader 方法,裝載 ijkffmpeg、ijksdl和ijkplayer三個庫檔案到安卓系統;
- 初始化播放器,調用的JNI接口程式 native_setup() 函數,此函數建立播放器消息隊列和播放其相關參數;
- 使用者在 Android 程式中,調用 createPlayer() 和 prepareAsync() 封裝接口函數建立播放器,并讓播放器進入待播放狀态;
- 啟動播放器。
前面分析過 prepareAsync() 函數相關内容,其中比較重要函數是 VideoState *is = stream_open(ffp, file_name, NULL);
在函數中:
- 建立 3 個隊列,視訊、音頻和副标題隊列,is中存放三個流 AVStream *audio_st、*subtitle_st、*video_st。
- 建立 2 個線程,read_thread() 和 video_refresh() 線程;
-
初始化解碼器相關參數,函數退出。
播放器就具備播放的能力,這個過程涵蓋 ijkplayer 源碼絕大部分内容; 進入播放過程中,程式邏輯架構已經建立起來,在運作期間能夠
處理一些使用者交換功能。
再下來回顧 read_thread() 線程功能,總結如下:
-
調用 avformat_open_input() 函數,此函數根據資料源選擇網絡協定、解封裝器類别,通過使用者 URL位址關鍵字區分,
如: “tcpext://192.168.1.31:1717/v-out.h264”, ijkplayer播放就解析出協定是tcpext、解封裝器是h264方式。
-
調用 avformat_find_stream_info(ic, opts) 函數, 此函數通過資料流資料識别編碼格式,得出該資料流應該配置
什麼樣的解碼器。在進入此函數時,該 AVFormatContext 中流的數量和種類就确定下來了,是在什麼時候确認的呢?存疑.
-
調用 stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO])函數, 根據資料流資訊為該資料流建構解碼器,
分别針對視訊、音頻和副标題流類型,配置解碼器。
- 進入線程的循環體,av_read_frame(ic, pkt) -> packet_queue_put(&is->videoq, ©),讀取-> 入隊過程反複循環。
籠統描述程式主體邏輯,大緻就這麼個邏輯。
本篇主要分析資料流讀取過程,清晰目标,開啟代碼走讀模式。
read_thread() 線程
我們先看看 read_thread() 線程中此部分相關簡化邏輯代碼,如下:
void read_thread(void *arg)
{
FFPlayer *ffp = arg; ///> 此參數是Android使用者空間傳遞相關參數
VideoState *is = ffp->is;
AVFormatContext *ic = NULL;
int err, i, ret __unused;
int st_index[AVMEDIA_TYPE_NB];
AVPacket pkt1, *pkt = &pkt1;
///> 資料流編碼格式識别部分代碼 1 部分
if (ffp->find_stream_info) {
AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts); ///> 擷取解碼器參數字典指針
int orig_nb_streams = ic->nb_streams;
do {
if (av_stristart(is->filename, "data:", NULL) && orig_nb_streams > 0) {
for (i = 0; i < orig_nb_streams; i++) {
if (!ic->streams[i] || !ic->streams[i]->codecpar || ic->streams[i]->codecpar->profile == FF_PROFILE_UNKNOWN) {
break;
}
}
if (i == orig_nb_streams) {
break;
}
}
err = avformat_find_stream_info(ic, opts); ///> 進入比對查找過程,在解碼器 options 字典中 flag 辨別流的種類
} while(0);
ffp_notify_msg1(ffp, FFP_MSG_FIND_STREAM_INFO);
}
is->realtime = is_realtime(ic);
av_dump_format(ic, 0, is->filename, 0);
///> 資料流編碼格式識别部分代碼 2 部分
int video_stream_count = 0;
int h264_stream_count = 0;
int first_h264_stream = -1;
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;
}
}
av_log(NULL, AV_LOG_INFO, "DEBUG %s, LINE:%d ,CODEC_ID:%d\n",__FILE__, __LINE__, (uint32_t)st->codecpar->codec_id);
}
///> 如果是多流模式
if (video_stream_count > 1 && st_index[AVMEDIA_TYPE_VIDEO] < 0) {
st_index[AVMEDIA_TYPE_VIDEO] = first_h264_stream;
av_log(NULL, AV_LOG_WARNING, "multiple video stream found, prefer first h264 stream: %d\n", first_h264_stream);
}
///> 逐一比對解碼器
if (!ffp->video_disable)
st_index[AVMEDIA_TYPE_VIDEO] =
av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
if (!ffp->audio_disable)
st_index[AVMEDIA_TYPE_AUDIO] =
av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
st_index[AVMEDIA_TYPE_AUDIO],
st_index[AVMEDIA_TYPE_VIDEO],
NULL, 0);
if (!ffp->video_disable && !ffp->subtitle_disable)
st_index[AVMEDIA_TYPE_SUBTITLE] =
av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
st_index[AVMEDIA_TYPE_SUBTITLE],
(st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
st_index[AVMEDIA_TYPE_AUDIO] :
st_index[AVMEDIA_TYPE_VIDEO]),
NULL, 0);
is->show_mode = ffp->show_mode;
///* open the streams,打開流格式 */
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
} else {
ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
is->av_sync_type = ffp->av_sync_type;
}
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]);
}
ffp_notify_msg1(ffp, FFP_MSG_COMPONENT_OPEN);
///> 通知 android 空間程式
if (!ffp->ijkmeta_delay_init) {
ijkmeta_set_avformat_context_l(ffp->meta, ic);
}
///> 設定字典項的狀态
ffp->stat.bit_rate = ic->bit_rate;
if (st_index[AVMEDIA_TYPE_VIDEO] >= 0)
ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_VIDEO_STREAM, st_index[AVMEDIA_TYPE_VIDEO]);
if (st_index[AVMEDIA_TYPE_AUDIO] >= 0)
ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_AUDIO_STREAM, st_index[AVMEDIA_TYPE_AUDIO]);
if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0)
ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, st_index[AVMEDIA_TYPE_SUBTITLE]);
///> 播放器狀态調節
ffp->prepared = true;
ffp_notify_msg1(ffp, FFP_MSG_PREPARED);
if (ffp->auto_resume) {
ffp_notify_msg1(ffp, FFP_REQ_START);
ffp->auto_resume = 0;
}
/* offset should be seeked*/
if (ffp->seek_at_start > 0) {
ffp_seek_to_l(ffp, (long)(ffp->seek_at_start));
}
///> 進入循環播放狀态,線程循環體
for (;;){
///>
if (is->queue_attachments_req) { ///> 在打開流的時候配置此辨別 = 1
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;
}
///>
pkt->flags = 0;
ret = av_read_frame(ic, pkt);
///>
if (pkt->flags & AV_PKT_FLAG_DISCONTINUITY) {
if (is->audio_stream >= 0) {
packet_queue_put(&is->audioq, &flush_pkt);
}
if (is->subtitle_stream >= 0) {
packet_queue_put(&is->subtitleq, &flush_pkt);
}
if (is->video_stream >= 0) {
packet_queue_put(&is->videoq, &flush_pkt);
}
}
///>
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);
}
///>
ffp_statistic_l(ffp);
av_log(NULL, AV_LOG_INFO, " %s / %s , LINE:%d \n",__FILE__, __func__, __LINE__);
}
}
此程式是簡化版結構内容,各節點有标注資訊。
讀取資料流
在 read_thread() 線程執行 av_read_frame(ic, pkt) 函數循環讀取資料流内容函數,進行跟蹤梳理函數調用關系如下。
av_read_frame(ic, pkt); ///> 入口參數: AVFormatContext *ic
-> read_frame_internal(s, pkt);
-> ff_read_packet(s, &cur_pkt); ///> 入口參數: AVPacket cur_pkt;
-> av_init_packet(pkt);
-> s->iformat->read_packet(s, pkt); ///> 此處 read_packet 調用是 ff_raw_read_partial_packet(AVFormatContext *s, AVPacket *pkt) 函數,在 libavformat/rawdec.c 檔案中
-> av_new_packet(pkt, size)
-> avio_read_partial(s->pb, pkt->data, size); ///> 入口參數: AVIOContext s->pb, 函數在 libavformat/aviobuf.c 檔案中
-> s->read_packet(s->opaque, buf, size); ///> 此 read_packet 調用是 io_read_packet() 函數,此函數最終調用 tcp_read() 函數,見下面分析。
-> memcpy(buf, s->buf_ptr, len);
-> s->buf_ptr += len;
-> return len;
-> av_shrink_packet(pkt, ret);
-> av_parser_init(st->codecpar->codec_id)
-> avcodec_get_name(st->codecpar->codec_id)
-> compute_pkt_fields(s, st, NULL, pkt, AV_NOPTS_VALUE, AV_NOPTS_VALUE)
-> read_from_packet_buffer(&s->internal->parse_queue, &s->internal->parse_queue_end, pkt)
-> update_stream_avctx(s);
-> add_to_pktbuf(&s->internal->packet_buffer, pkt,&s->internal->packet_buffer_end, 1);
下面分析入口參數之間關系, 函數調用關系 io_read_packet() -> ffurl_read() -> tcp_read() 導圖。
先從函數入口參數梳理,如下。
函數入口第一個參數 AVFormatContext -> AVIOContext -> opaque 入口參數來源關系導圖,在 AVIOContext 結構體定義中為 void * opaque 類型。
///> 函數入口參數是 s->opaque 内容
static int io_read_packet(void *opaque, uint8_t *buf, int buf_size)
{
AVIOInternal *internal = opaque; ///> 此處直接指派轉換成 AVIOInternal 指針,入口傳遞過來的 AVIOContext s->pb,
return ffurl_read(internal->h, buf, buf_size); ///> ffurl_read 調用是 tcp_read() 增加的私有協定中的函數。
}
//> 結構體 AVIOInternal 定義如下
typedef struct AVIOInternal {
URLContext *h;
} AVIOInternal;
//> 結構體 URLContext 定義如下
typedef struct URLContext {
const AVClass *av_class; /**< information for av_log(). Set by url_open(). */
const struct URLProtocol *prot;
void *priv_data;
char *filename; /**< specified URL */
int flags;
int max_packet_size; /**< if non zero, the stream is packetized with this max packet size */
int is_streamed; /**< true if streamed (no seek possible), default = false */
int is_connected;
AVIOInterruptCB interrupt_callback;
int64_t rw_timeout; /**< maximum time to wait for (network) read/write operation completion, in mcs */
const char *protocol_whitelist;
const char *protocol_blacklist;
int min_packet_size; /**< if non zero, the stream is packetized with this min packet size */
int64_t pts; ///< 增加 pts 變量
} URLContext;
///> 此函數的入口 URLContext *h 指針内容類型如上圖。
static int tcp_read(URLContext *h, uint8_t *buf, int size)
{
uint8_t header[HEADER_SIZE];
TCPEXTContext *s = h->priv_data;
int ret;
if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);
if (ret)
return ret;
}
ret = recv(s->fd, header, HEADER_SIZE, MSG_WAITALL);
if(ret < HEADER_SIZE){
av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d ,READ_HEADER_AIL length:%d \n",__FILE__, __func__, __LINE__, ret);
return 0;
}
uint32_t msb = header[0] << 24 | header[1] << 16 | header[2] << 8 | header[3];
uint32_t lsb = header[4] << 24 | header[5] << 16 | header[6] << 8 | header[7];
uint32_t len = header[8] << 24 | header[9] << 16 | header[10] << 8 | header[11];
uint64_t pts = msb << 32 | lsb ;
av_log(NULL, AV_LOG_INFO, "READ HEADER msb:%08x, lsb:%08x, len:%08x \n", msb, lsb, len);
assert( pts == NO_PTS || (pts & 0x8000000000000000) == 0);
assert(len);
ret = recv(s->fd, buf, len, MSG_WAITALL);
if (ret > 0){
av_application_did_io_tcp_read(s->app_ctx, (void*)h, ret);
uint32_t hsb = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
msb = buf[4] << 24 | buf[5] << 16 | buf[6] << 8 | buf[7];
lsb = buf[8] << 24 | buf[9] << 16 | buf[10] << 8 | buf[11];
av_log(NULL, AV_LOG_INFO, "H264 HEADER hsb:%08x, msb:%08x, lsb:%08x \n", hsb, msb, lsb);
}
av_log(NULL, AV_LOG_INFO, "%s/%s(), LINE:%d ,recv length:%d \n",__FILE__, __func__, __LINE__, ret);
return ret < 0 ? ff_neterrno() : ret;
}
總結:
-
1>. 線程 read_thread() 定義 AVFormatContext ic,AVPacket pkt1,全局變量, 函數 av_read_frame(ic, pkt);入口參數
都是全局變量,tcp_read函數入口 h = (URLContext)ic->pb->opaque, buf = pkt->data 參數。
-
2>. 隻能在 URLContext *h 中存放 pts 資料,在增加的私有解封裝器 ff_raw_read_partial_packet() 函數中,把 pts 值轉寫
到 pkt->pts 中。
- 3>. 在 ijkplayer的sdk中,增加私有通訊協定和私有解封裝器、程式處理思路基本類似,此子產品可供參考。
此處擷取的 packet_buffer 對象中有 pts 值,也就是說在讀取資料時可以把目前的 pts 内容放進去,