位置: IT常识 - 正文

ijkplayer解码流程源码解读(ijk解码是什么意思)

编辑:rootadmin
ijkplayer解码流程源码解读

推荐整理分享ijkplayer解码流程源码解读(ijk解码是什么意思),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:ijkplayer支持的格式,ijk解码是什么意思,ijkplayer详解使用教程,ijkplayer详解使用教程,ijkplayer详解使用教程,ijkplayer详解使用教程,ijkplayer mediacodec,ijkplayer详解使用教程,内容如对您有帮助,希望把文章链接给更多的朋友!

ijkplayer是一款基于ffmpeg的在移动端比较流行的开源播放器。FFmpeg是一款用于多媒体处理、音视频编解码的自由软件工程,采用LGPL或GPL许可证。

要想理解ijkplayer源码,首先得知道视频播放器的基本原理。

视频播放器播放一个互联网上的视频文件,需要经过以下几个步骤:解协议,解封装,音视频解码,音视频同步。如果播放的是本地文件则不需要解协议。

ijkplayer核心源码都在C文件中。解码流程主要涉及到的文件是ijkplayer_jni.c、ijkplayer.c、ff_ffplay.c。第一个文件是java与c之间的jni层文件,第二个文件主要是加了锁,然后调用的ff_ffplay.c文件中的代码。具体核心功能实现还是在ff_ffplay.c文件中。

1 解封装

入口函数为ffp_prepare_async_l,其中调用了stream_open方法。

stream_open()是比较重要的一个方法,里边创建了解封装线程。

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat){ ... 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; } ...}

 VideoState和FFPlayer是2个非常重要的结构体,VideoState保存在FFPlayer中,而在FFPlayer在ff_ffplay.c文件中的大部分函数中都会传入其指针,VideoState中保存了播放器的操作状态以及其他一些重要信息。如果需要对ijkplayer源码进行修改,一些信息可以保存到FFPlayer或VideoState中。

read_thread()//ret = av_read_frame(ic, pkt); 读出一个packet数据,放入队列queue中

static int read_thread(void *arg){...//打开输入源err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);...//获取视频流信息err = avformat_find_stream_info(ic, opts);...// 根据音频/视频/字幕调用3次 /* 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]); }... for (;;) { //开启循环,如果用户进行了停止操作,则返回 if (is->abort_request) break;...//执行解封装 ret = av_read_frame(ic, pkt);...//解封装后将packet保存到VideoState的音频、视频、字幕packet队列中 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); } }...}typedef struct VideoState {...PacketQueue audioq;PacketQueue subtitleq;PacketQueue videoq;...}typedef struct PacketQueue { MyAVPacketList *first_pkt, *last_pkt; int nb_packets; int size; int64_t duration; int abort_request; int serial; SDL_mutex *mutex; SDL_cond *cond; MyAVPacketList *recycle_pkt; int recycle_count; int alloc_count; int is_buffer_indicator;} PacketQueue;

C语言中没有像C++那样有容器,链表、队列都需要自己实现。

stream_component_open函数

static int stream_component_open(FFPlayer *ffp, int stream_index){ avctx = avcodec_alloc_context3(NULL); ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar); codec = avcodec_find_decoder(avctx->codec_id); switch (avctx->codec_type) { case AVMEDIA_TYPE_AUDIO: if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)goto fail; 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; } // audio_thread 是音频解码线程 if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0) goto out; break; case AVMEDIA_TYPE_VIDEO: decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);// 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); if ((ret = decoder_start(&is->subdec, subtitle_thread, ffp, "ff_subtitle_dec")) < 0) goto out; break; }}ijkplayer解码流程源码解读(ijk解码是什么意思)

省略大部分代码,只保留一些关键代码。主要作用就是创建解码器上下文,获取解码器,打开解码器等。然后就是根据音频、视频、字幕分别调用decoder_init、decoder_start函数。

static void decoder_init(Decoder *d, AVCodecContext *avctx, PacketQueue *queue, SDL_cond *empty_queue_cond) { memset(d, 0, sizeof(Decoder)); d->avctx = avctx; d->queue = queue; ...}

在decoder_init函数中Decoder中的queue指针指向实际的解封装后的队列,后面音视频解码时,会从此队列中拿出packet进行解码。

2 开始视频解码

decoder_start()中没太多代码,主要是调用SDL_CreateThreadEx创建音频/视频/字幕解码线程

我们主要关注视频的处理,看video_thread函数,这个函数调用func_run_sync,然后后面一通没太多逻辑的调用,最终会执行到ffplay_video_thread函数。

static int ffplay_video_thread(void *arg){AVFrame *frame = av_frame_alloc();... for (;;) { ret = get_video_frame(ffp, frame);... duration = (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, frame->pkt_pos, is->viddec.pkt_serial); av_frame_unref(frame); }}

ffplay_video_thread 会调用get_video_frame获得解码后的数据帧。然后通过queue_picture函数将解码后数据帧塞到队列中保存下来,以便渲染时去拿数据渲染。

get_video_frame会调用decoder_decode_frame函数,真正执行音视频的解码。

decoder_decode_frame 函数

static int decoder_decode_frame(FFPlayer *ffp, Decoder *d, AVFrame *frame, AVSubtitle *sub) {... if (d->queue->serial == d->pkt_serial) { do { if (d->queue->abort_request) return -1; switch (d->avctx->codec_type) { case AVMEDIA_TYPE_VIDEO: // 从解码器中获得一阵解码后的视频帧 frame里面有长/宽数据 ret = avcodec_receive_frame(d->avctx, frame); if (ret >= 0) { ffp->stat.vdps = SDL_SpeedSamplerAdd(&ffp->vdps_sampler, FFP_SHOW_VDPS_AVCODEC, "vdps[avcodec]"); if (ffp->decoder_reorder_pts == -1) { frame->pts = frame->best_effort_timestamp; } else if (!ffp->decoder_reorder_pts) { frame->pts = frame->pkt_dts; } } break; case AVMEDIA_TYPE_AUDIO: // 从解码器中获得一阵解码后的音频帧 ret = avcodec_receive_frame(d->avctx, frame); if (ret >= 0) { 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; default: break; } if (ret == AVERROR_EOF) { d->finished = d->pkt_serial; avcodec_flush_buffers(d->avctx); return 0; } if (ret >= 0) return 1; } while (ret != AVERROR(EAGAIN)); } do { if (d->queue->nb_packets == 0) SDL_CondSignal(d->empty_queue_cond); if (d->packet_pending) { av_packet_move_ref(&pkt, &d->pkt); d->packet_pending = 0; } else { //从Decoder中保存的解封装队列(queue)里拿出一个packet,保存到pkt中 if (packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0) return -1; } } while (d->queue->serial != d->pkt_serial);... } else {// 将pkt发送给解码器进行解码 if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) { av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); d->packet_pending = 1; av_packet_move_ref(&d->pkt, &pkt); } }}

decoder_decode_frame函数会调用ffmpeg的avcodec_send_packet函数将解封装后的数据塞给解码器,并调用 avcodec_receive_frame函数从解码器总获得解码后的音视频数据帧。调试时发现刚开始播放时视频解码得到的frame里面的数据可能为空,包括width、height、linesize都为空。所以如果要改用解码后的视频帧数据,要先判断下里面是否有数据。

3 解码后视频帧保存

视频解码完成了,需要保存解码后的数据,以便渲染线程来拿数据渲染。视频帧解码后数据保存主要看queue_picture函数

static int queue_picture(FFPlayer *ffp, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial){... if (!(vp = frame_queue_peek_writable(&is->pictq))) return -1;... alloc_picture(ffp, src_frame->format);... //将解码后视频帧保存到队列中 frame_queue_push(&is->pictq);...}

 queue_picture及alloc_picture中,以及还有几个跟解码后数据帧拷贝相关的函数,这块还没完全理清。除了解码后YUV数据拷贝,还涉及到一些色彩空间转换。

再看frame_queue_push函数

static void frame_queue_push(FrameQueue *f){ if (++f->windex == f->max_size) f->windex = 0; SDL_LockMutex(f->mutex); f->size++; SDL_CondSignal(f->cond); SDL_UnlockMutex(f->mutex);}typedef struct FrameQueue { Frame queue[FRAME_QUEUE_SIZE]; int rindex; int windex; int size; int max_size; int keep_last; int rindex_shown; SDL_mutex *mutex; SDL_cond *cond; PacketQueue *pktq;} FrameQueue;

这个函数很简单,就是更新一些索引及队列大小。队列是循环重用的,队列中的rindex表示数据开头的index,也是读取数据的index,即read index,windex表示空数据开头的index,是写入数据的index,即write index。

4 音频解码及数据保存

从前面可知stream_component_open中会调用decode_start函数创建音频解码线程audio_thread。

static int audio_thread(void *arg){ AVFrame *frame = av_frame_alloc(); Frame *af;... // 音频解码 if ((got_frame = decoder_decode_frame(ffp, &is->auddec, frame, NULL)) < 0) goto the_end;... // 获取队列中可用于写入写入数据的队列索引(windex),根据(windex)返回Frame if (!(af = frame_queue_peek_writable(&is->sampq))) goto the_end; af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); af->pos = frame->pkt_pos; af->serial = is->auddec.pkt_serial; af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate}); av_frame_move_ref(af->frame, frame); frame_queue_push(&is->sampq);...}

可以看出audio_thread中音频解码流程比视频流程更少一点,直接调用decoder_decode_frame获得解码后数据帧frame,通过frame_queue_peek_writable函数获取到队列中下一个可用于音频帧数据保存的位置(windex),返回Frame用于解码后音频数据及相关信息保存。通过ffmpeg的av_frame_move_ref函数完成数据的拷贝,然后调用frame_queue_push更新windex。

static Frame *frame_queue_peek_writable(FrameQueue *f){ /* wait until we have space to put a new frame */ SDL_LockMutex(f->mutex); while (f->size >= f->max_size && !f->pktq->abort_request) { SDL_CondWait(f->cond, f->mutex); } SDL_UnlockMutex(f->mutex); if (f->pktq->abort_request) return NULL; return &f->queue[f->windex];}

 

图中“...”的流程代表省略掉的一些函数调用,可以看出,音频、视频、字幕的解码都是调用的同一个函数。 

本文链接地址:https://www.jiuchutong.com/zhishi/299891.html 转载请保留说明!

上一篇:【Maven】maven安装、IDEA创建maven的web项目、添加依赖、集成Tomcat(maven安装成功命令)

下一篇:多模态模型学习1——CLIP对比学习 语言-图像预训练模型(什么是多模态研究)

  • 腾讯课堂下载的视频如何在腾讯课堂app端内播放(腾讯课堂下载的文件在哪个文件下)

    腾讯课堂下载的视频如何在腾讯课堂app端内播放(腾讯课堂下载的文件在哪个文件下)

  • 怎么修改win7电脑ip地址位置(怎么修改win7电脑清晰度)

    怎么修改win7电脑ip地址位置(怎么修改win7电脑清晰度)

  • 抖音刷点赞会被限流吗

    抖音刷点赞会被限流吗

  • 台式机可以连蓝牙吗(台式机连蓝牙耳机不能说话)

    台式机可以连蓝牙吗(台式机连蓝牙耳机不能说话)

  • 喜马拉雅的功能和特点(喜马拉雅功能定位)

    喜马拉雅的功能和特点(喜马拉雅功能定位)

  • 剪映怎么加黑屏(剪映怎么加黑屏字幕和配音)

    剪映怎么加黑屏(剪映怎么加黑屏字幕和配音)

  • 数字通信系统的组成(数字通信系统的有效性用什么衡量)

    数字通信系统的组成(数字通信系统的有效性用什么衡量)

  • 把文件从远程计算机传送到本地计算机的过程称为什么(把文件从远程计算机拷贝到本地)

    把文件从远程计算机传送到本地计算机的过程称为什么(把文件从远程计算机拷贝到本地)

  • x86是32位系统吗(x86处理器是32位吗)

    x86是32位系统吗(x86处理器是32位吗)

  • 为什么键盘有些键突然不能用(为什么键盘有些亮有些不亮)

    为什么键盘有些键突然不能用(为什么键盘有些亮有些不亮)

  • 华为荣耀10有红外遥控功能吗(华为荣耀10有红包提醒功能吗)

    华为荣耀10有红外遥控功能吗(华为荣耀10有红包提醒功能吗)

  • 电话转入来电提醒是怎么回事(电话转入来电提醒是什么问题)

    电话转入来电提醒是怎么回事(电话转入来电提醒是什么问题)

  • office软件是什么软件(office软件是什么意思)

    office软件是什么软件(office软件是什么意思)

  • apple pencil支持机型(pencil支持机型)

    apple pencil支持机型(pencil支持机型)

  • 交换式局域网的核心设备是(交换式局域网的核心是什么)

    交换式局域网的核心设备是(交换式局域网的核心是什么)

  • 苹果11能否无线充电(iphone 11能无线)

    苹果11能否无线充电(iphone 11能无线)

  • 苹果11啥处理器(苹果11的处理器是?)

    苹果11啥处理器(苹果11的处理器是?)

  • ps怎么画不规则图形(ps怎么画不规则曲线)

    ps怎么画不规则图形(ps怎么画不规则曲线)

  • realme Q怎么使用SOS紧急联络(realmeq怎样)

    realme Q怎么使用SOS紧急联络(realmeq怎样)

  • 电脑怎么拷贝软件(电脑怎么拷贝软件到手机)

    电脑怎么拷贝软件(电脑怎么拷贝软件到手机)

  • 抖音手机直播怎么放音乐(抖音手机直播怎么关闭麦克风声音)

    抖音手机直播怎么放音乐(抖音手机直播怎么关闭麦克风声音)

  • 华为生活服务可以卸载吗(华为 生活服务)

    华为生活服务可以卸载吗(华为 生活服务)

  • win7怎么恢复打开方式(win7如何恢复电脑系统)

    win7怎么恢复打开方式(win7如何恢复电脑系统)

  • 怎么用minitab画箱线图(怎么用minitab画箱型图)

    怎么用minitab画箱线图(怎么用minitab画箱型图)

  • 字高怎么设置(cad出图字高怎么设置)

    字高怎么设置(cad出图字高怎么设置)

  • 抖音视频怎么删(抖音视频怎么删掉)

    抖音视频怎么删(抖音视频怎么删掉)

  • 飞利浦9系比6好在哪(飞利浦九系)

    飞利浦9系比6好在哪(飞利浦九系)

  • 小米9电池多大(小米电池多大容量好一点)

    小米9电池多大(小米电池多大容量好一点)

  • EvtEng.exe是什么进程 有何作用 EvtEng进程信息查询(ev4是什么文件)

    EvtEng.exe是什么进程 有何作用 EvtEng进程信息查询(ev4是什么文件)

  • unalias命令  取消命令别名(unistack怎么关掉)

    unalias命令 取消命令别名(unistack怎么关掉)

  • 保险税优识别码是保单号吗
  • 一般纳税人劳务票一般开几个点
  • 不征税收入计入应纳税所得额吗
  • 残疾人保障金的工资按实发还是应发
  • 今年补交上年的对外捐赠(视同销售收入)的税怎么处理?
  • 股权变更后税务需要变更吗
  • 销售费用福利费编码
  • 自产自销农产品企业所得税
  • 核定征收可以无发票做账吗
  • 企业搬迁到外省之前的债务怎么处理
  • 外购原材料自用要进项转出吗
  • 机械租赁有哪些岗位
  • 行政事业单位取暖费交个人所得税吗
  • 预收账款发货
  • 社保的计提和缴纳
  • 服务业发票税率是多少
  • 退税差额怎么做账
  • 个体户进项发票多开出发票少怎么办
  • 福利费进项税额转出会计分录
  • 企业所得税期间费用利息收支
  • 税金及附加是否设二级科目
  • 进项发票认证抵扣时间是每月的15号吗
  • 金融服务利息税率是多少
  • 签发空头支票的赔偿金
  • 资产计税基础是怎么填
  • 工程款的材料商可以直接起诉业主吗
  • 未立项进行建设
  • 公司自有房屋出租 营业范围
  • 现在还有短期投资这个科目吗
  • 小规模餐饮企业增值税申报表填写
  • 临时股东大会的召开情形
  • 个人所得税手续费返还
  • PHP:Memcached::getServerList()的用法_Memcached类
  • 财政补助收入核算内容
  • php require include
  • phpif函数的使用方法
  • 工业企业采购部门职责
  • 建筑企业总包方都有哪些印花税
  • php 随机数
  • 短期资金都是债务类资金
  • 企业所得税税前扣除凭证(发票)风险提示反馈
  • 个人所得税计提基数公式
  • 中小企业所得税优惠政策2022
  • ieee下载论文
  • php数组实现原理
  • 所得税申报表应怎么填
  • Vite4+Pinia2+vue-router4+ElmentPlus搭建Vue3项目(组件、图标等按需引入)[保姆级]
  • packet命令
  • 结转完工产品成本的会计分录
  • 预付劳务款项会计分录
  • 员工工资属于什么会计科目
  • 增值税申报表销项税额怎么算?
  • 融资租赁确认的具体条件
  • 收入总额是什么意思
  • 进口产品销售需要交税吗
  • 税务局的罚款记录怎么查
  • 补交以前年度的城建税会计分录
  • 电子承兑过期超过10天就无法转让吗
  • 递延所得税资产和负债账务处理
  • 公益性捐赠会计利润总额计算方法
  • 开票未收到款会计分录
  • 报废周转材料应负担的成本差异
  • 固定资产清理是资产类的备抵科目吗
  • 注册工贸公司业务范围
  • 企业持有的能够对被投资单位实施控制的长期股权投资
  • sql server 数据库日志不可用
  • win10蓝屏怎么修复系统
  • mac所有窗口最小化
  • 教你如何解决蓝光机/KODI无法打开局域网smb共享的问题
  • 在mac中运行exe
  • 2020win7免费升级win10教程
  • linux系统入侵检测软件有哪些
  • android游戏开发用什么语言
  • javascript常用函数大全
  • python对excel操作真的有提高吗
  • js怎么获取复选框选中的值
  • jquery获取当前地址
  • 税务上征信
  • 重庆税务登记证在哪里办理
  • 怎么批量打开excel文件
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设