回顾
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 内容放进去,