上文中说到在read_thread线程中有个关键函数:avformat_open_input(utils.c),应当是读取视频文件的,这个函数属于ffmpeg层。这回进入到其中去看下:

int avformat_open_input(AVFormatContext **ps, const char *filename,AVInputFormat *fmt, AVDictionary **options)
{......if ((ret = init_input(s, filename, &tmp)) < 0)goto fail;s->probe_score = ret;if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);if (!s->protocol_whitelist) {ret = AVERROR(ENOMEM);goto fail;}}if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);if (!s->protocol_blacklist) {ret = AVERROR(ENOMEM);goto fail;}}if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);ret = AVERROR(EINVAL);goto fail;}......
}    

init_input这个函数的注释是:Open input file and probe the format if necessary.打开文件判断格式。进入到这个函数中观察,代码不多,关键点av_probe_input_buffer2,ffmpeg里的格式分析函数。里面会调用avio_read,读取文件。进入到avio_read函数内部,看到主要是循环读取packet,从AVIOContext的队列中读取,通过AVIOContext中的函数指针read_packet来进行。

下面退回到read_thread函数中,看看对h264的视频流如何处理的。

static int read_thread(void *arg)
{......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 h264if (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;}}}......
}

循环读取stream,然后判断是h264后记录下来。继续往下看又到了上文提到的stream_component_open函数,这回进入看看:

static int stream_component_open(FFPlayer *ffp, int stream_index)
{......codec = avcodec_find_decoder(avctx->codec_id);......switch (avctx->codec_type) {case AVMEDIA_TYPE_AUDIO:if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)goto out;......case AVMEDIA_TYPE_VIDEO:if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0)goto out;......case AVMEDIA_TYPE_SUBTITLE:......
}

这是个很长的函数,我看起来是分为2部分,前半部分是寻找解码器,后半部分的switch case是开始进行解码。分别为音频、视频和字母进行相关处理。decoder_start是个关键函数,开始进行解码处理。进入这个函数内部看:

static int decoder_start(Decoder *d, int (*fn)(void *), void *arg, const char *name)
{packet_queue_start(d->queue);d->decoder_tid = SDL_CreateThreadEx(&d->_decoder_tid, fn, arg, name);if (!d->decoder_tid) {av_log(NULL, AV_LOG_ERROR, "SDL_CreateThread(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}return 0;
}

很短,不是吗,但是启动了线程。根据参数传递得知就是上面传递进来的video_thread和audio_thread是关键线程函数。看到这里可以了解,解码过程是完全异步的,那么再去看看关键的线程函数吧。

static int video_thread(void *arg)
{FFPlayer *ffp = (FFPlayer *)arg;int       ret = 0;if (ffp->node_vdec) {ret = ffpipenode_run_sync(ffp->node_vdec);}return ret;
}

很短,先看参数,从上面的可得知,是FFPlayer类型,从read_thread函数中就传递进来的一个结构,可以说是播放器的结构,播放所需要的所有内容这里几乎都有了。继续看关键函数ffpipenode_run_sync。向下跟踪两层,会发现,核心函数是ffplay_video_thread。这个函数内部看起来挺麻烦,进入看看吧:

static int ffplay_video_thread(void *arg)
{......for (;;) {ret = get_video_frame(ffp, frame);......ret = av_buffersrc_add_frame(filt_in, frame);if (ret < 0)goto the_end;while (ret >= 0) {is->frame_last_returned_time = av_gettime_relative() / 1000000.0;ret = av_buffersink_get_frame_flags(filt_out, frame, 0);if (ret < 0) {if (ret == AVERROR_EOF)is->viddec.finished = is->viddec.pkt_serial;ret = 0;break;}is->frame_last_filter_delay = av_gettime_relative() / 1000000.0 - is->frame_last_returned_time;if (fabs(is->frame_last_filter_delay) > AV_NOSYNC_THRESHOLD / 10.0)is->frame_last_filter_delay = 0;tb = filt_out->inputs[0]->time_base;
#endifduration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);ret = queue_picture(ffp, frame, pts, duration, av_frame_get_pkt_pos(frame), is->viddec.pkt_serial);av_frame_unref(frame);
#if CONFIG_AVFILTER}
#endif}
}

关键点首先是get_video_frame,然后是av_buffersrc_add_frame和后面的while循环部分里的queue_picture。那么不得不进入到get_video_frame中看下:

static int get_video_frame(FFPlayer *ffp, AVFrame *frame)
{VideoState *is = ffp->is;int got_picture;ffp_video_statistic_l(ffp);if ((got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)) < 0)return -1;if (got_picture) {double dpts = NAN;if (frame->pts != AV_NOPTS_VALUE)dpts = av_q2d(is->video_st->time_base) * frame->pts;frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);if (ffp->framedrop>0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) {if (frame->pts != AV_NOPTS_VALUE) {double diff = dpts - get_master_clock(is);if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD &&diff - is->frame_last_filter_delay < 0 &&is->viddec.pkt_serial == is->vidclk.serial &&is->videoq.nb_packets) {is->frame_drops_early++;is->continuous_frame_drops_early++;if (is->continuous_frame_drops_early > ffp->framedrop) {is->continuous_frame_drops_early = 0;} else {av_frame_unref(frame);got_picture = 0;}}}}}return got_picture;
}

关键点只有一个就是decoder_decode_frame,好吧,继续往下看,层级有点多了哈:

static int decoder_decode_frame(FFPlayer *ffp, Decoder *d, AVFrame *frame, AVSubtitle *sub) {int got_frame = 0;do {int ret = -1;if (d->queue->abort_request)return -1;if (!d->packet_pending || d->queue->serial != d->pkt_serial) {AVPacket pkt;do {if (d->queue->nb_packets == 0)SDL_CondSignal(d->empty_queue_cond);if (packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0)return -1;if (pkt.data == flush_pkt.data) {avcodec_flush_buffers(d->avctx);d->finished = 0;d->next_pts = d->start_pts;d->next_pts_tb = d->start_pts_tb;}} while (pkt.data == flush_pkt.data || d->queue->serial != d->pkt_serial);av_packet_unref(&d->pkt);d->pkt_temp = d->pkt = pkt;d->packet_pending = 1;}switch (d->avctx->codec_type) {case AVMEDIA_TYPE_VIDEO: {ret = avcodec_decode_video2(d->avctx, frame, &got_frame, &d->pkt_temp);if (got_frame) {ffp->stat.vdps = SDL_SpeedSamplerAdd(&ffp->vdps_sampler, FFP_SHOW_VDPS_AVCODEC, "vdps[avcodec]");if (ffp->decoder_reorder_pts == -1) {frame->pts = av_frame_get_best_effort_timestamp(frame);} else if (!ffp->decoder_reorder_pts) {frame->pts = frame->pkt_dts;}}}break;case AVMEDIA_TYPE_AUDIO:ret = avcodec_decode_audio4(d->avctx, frame, &got_frame, &d->pkt_temp);if (got_frame) {AVRational tb = (AVRational){1, frame->sample_rate};if (frame->pts != AV_NOPTS_VALUE)frame->pts = av_rescale_q(frame->pts, av_codec_get_pkt_timebase(d->avctx), tb);else if (d->next_pts != AV_NOPTS_VALUE)frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);if (frame->pts != AV_NOPTS_VALUE) {d->next_pts = frame->pts + frame->nb_samples;d->next_pts_tb = tb;}}break;case AVMEDIA_TYPE_SUBTITLE:ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &d->pkt_temp);break;default:break;}if (ret < 0) {d->packet_pending = 0;} else {d->pkt_temp.dts =d->pkt_temp.pts = AV_NOPTS_VALUE;if (d->pkt_temp.data) {if (d->avctx->codec_type != AVMEDIA_TYPE_AUDIO)ret = d->pkt_temp.size;d->pkt_temp.data += ret;d->pkt_temp.size -= ret;if (d->pkt_temp.size <= 0)d->packet_pending = 0;} else {if (!got_frame) {d->packet_pending = 0;d->finished = d->pkt_serial;}}}} while (!got_frame && !d->finished);return got_frame;
}

packet_queue_get_or_buffering从解码前的队列中读取一帧的数据,然后调用avcodec_decode_video2。不能进去看了,没完了,总之是读取一帧。往回倒,回到ffplay_video_thread里,下面就是queue_picture,将一帧解码后的图像放入解码后队列。
至此解码算完了。整个过程真是粗略分析啊,对自己也很抱歉,暂时先这样吧。后面有空继续就某个点深入进行。

android ijkplayer c层分析-prepare过程与读取线程(续1-解码粗略分析)相关推荐

  1. 4.基于Android 12 分析系统启动过程

    基于Android12 分析系统启动过程 本文基于AOSP Android12的源码分析Android系统的启动流程. 由于这部分内容各版本之间差异不大,同样适用于Android12之前的版本. 1. ...

  2. Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  3. Android应用程序内部启动Activity过程(startActivity)的源代码分析

    上文介绍了Android应用程序的启动过程,即应用程序默认Activity的启动过程,一般来说,这种默认Activity是在新的进程和任务中启动的:本文将继续分析在应用程序内部启动非默认Activit ...

  4. Android --- IjkPlayer 阅读native层源码之解码成功后的音频数据如何发送回Android播放(九)

    整章目录:Android------- IjkPlayer 源码学习目录 本篇会有很多源代码,请注意阅读每行代码上面的注释. 本篇介绍的主要内容为上图红框圈起部分: 在前面介绍了如何将一个AvPack ...

  5. linux input子系统分析--子系统核心.事件处理层.事件传递过程

    linux input子系统分析--子系统核心.事件处理层.事件传递过程 一.  输入子系统核心分析. 1.输入子系统核心对应与/drivers/input/input.c文件,这个也是作为一个模块注 ...

  6. Android ---- Ijkplayer阅读native层源码之IjkMediaPlayer_prepareAsync(五)

    整章目录:Android------- IjkPlayer 源码学习目录 本篇会有很多源代码,请注意阅读每行代码上面的注释. 本篇介绍的主要内容为上图红框圈起部分: IjkMediaPlayer_pr ...

  7. Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处.尊重劳动成果] 1 背景 之所以写这一篇博客的原因是由于之前有写过一篇<Android应用setCont ...

  8. Android应用程序组件Content Provider在应用程序之间共享数据的原理分析(1)

             在Android系统中,不同的应用程序是不能直接读写对方的数据文件的,如果它们想共享数据的话,只能通过Content Provider组件来实现.那么,Content Provide ...

  9. android的wifi网卡移植详细过程已经通用驱动的问题

    这里有一篇详细的教程,看完还有一个问题 就是android的wifi驱动移植,如果有wifi网卡的驱动代码,是一定需要对android系统本身的代码修改重写编译吗?就是说,有无可能不改变android ...

最新文章

  1. oracle 关闭audit,关于Oracle审计(audit)
  2. 数据库并发一致性的问题
  3. c语言中的void指针,C程序中void指针的概念
  4. ASP.NET Core Web 支付功能接入 微信-扫码支付篇
  5. SAP Spartacus - Progressive Web Applications,渐进式 Web 应用程序
  6. 【转载】c++之类的基本操作(c++ primer 的读书笔记 ,类对象, 类用户, 类成员的含义)
  7. 80 个例子,彻底掌握Python日期时间处理
  8. Java框架搭建-Maven、Mybatis、Spring MVC整合搭建
  9. Botanical Dimensions:借助第九代智能英特尔® 酷睿™ 处理器实现独特沉浸式体验...
  10. 基于稀疏表示理论的图像去噪
  11. 网页视频旋转(B站)
  12. MATLAB中plot函数的用法
  13. 电容降压整流电源电路
  14. 网络号、主机号、子网掩码、IP、子网划分、主机号划分
  15. vue3代码检查以及格式化配置
  16. js禁止鼠标右键的菜单事件
  17. 服务器安全神器,Linux 上安装 Fail2Ban 保护 SSH
  18. 制作一个属于自己的BHO吧!(C#)
  19. Python BeautifulSoup简介
  20. 嵌入式系统实用电源管理技术应该如何选择?

热门文章

  1. 自动化测试--实现一套完全解耦的测试框架(三)
  2. springmvc如何使用视图解析器_SpringMVC相关面试题
  3. 简述直方图和柱形图的区别_如何区分直方图与柱形图
  4. RuoYiConfig中加入自定义属性值获取不到解决办法?
  5. 视图与表之间的异同点_灯芯绒面料印花与染色的异同点有哪些?做灯芯绒订单的了解一下...
  6. wamp xampp mysql端口冲突_解决xampp端口冲突
  7. Python的pyproject.toml文件中的tool.poetry.dev-dependencies选项
  8. 小米互联通信服务_时隔六年,小米NFC碰碰贴复活,碰一下自动亮灯、联网、投屏...
  9. Oracle中varchar2(20)和varchar2(20 byte)区别
  10. jsp ---- filter