IJK核心代码剖析2019-06-08

一、总体说明

1.打开ijkplayer,可看到其主要目录结构如下:

tool - 初始化项目工程脚本

config - 编译ffmpeg使用的配置文件

extra - 存放编译ijkplayer所需的依赖源文件, 如ffmpeg、openssl等

ijkmedia - 核心代码

ijkplayer - 播放器数据下载及解码相关

ijksdl - 音视频数据渲染相关

ios - iOS平台上的上层接口封装以及平台相关方法

android - android平台上的上层接口封装以及平台相关方法

在功能的具体实现上,iOS和Android平台的差异主要表现在视频硬件解码以及音视频渲染方面,两者实现的载体区别如下表所示:

Platform HardwareCodec VideoRender AudioOutput

iOS VideoToolBox OpenGL ES AudioQueue

Android MediaCodec OpenGL ES/MediaCodec OpenSL ES/AudioTrack

2.IJK模块

初始化模块:初始化完成的主要工作就是创建IJKMediaPlayer播放器对象,创建图像渲染对象SDL_Vout,创建平台相关的IJKFF_Pipeline对象

核心模块:音视频数据读取、音视频解码、音视频渲染及同步

事件处理:在播放过程中,某些行为的完成或者变化,如prepare完成,开始渲染等,需要以事件形式通知到外部,以便上层作出具体的业务处理

二、初始化流程

在该方法中主要完成了三个动作:

1.创建IJKMediaPlayer对象;

2.创建图像渲染对象SDL_Vout;

3.创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))//ijkmp_android_create(message_loop)

//(21)通过ffp_create方法创建了FFPlayer对象,并设置消息处理函数

ijkmp_create(msg_loop)

IjkMediaPlayer *mp = (IjkMediaPlayer *) mallocz(sizeof(IjkMediaPlayer));

mp->ffplayer = ffp_create();

mp->msg_loop = msg_loop;

//(22)创建图像渲染对象SDL_Vout

mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

return SDL_VoutAndroid_CreateForANativeWindow();

SDL_Vout *vout = SDL_Vout_CreateInternal(sizeof(SDL_Vout_Opaque));

//函数指针初始化

vout->opaque_class = &g_nativewindow_class;

vout->create_overlay= func_create_overlay;

vout->free_l   = func_free_l;

vout->display_overlay = func_display_overlay;

//(23)创建平台相关的IJKFF_Pipeline对象,包括视频解码以及音频输出部分

mp->ffplayer->pipeline = ffpipeline_create_from_android(mp->ffplayer);

IJKFF_Pipeline *pipeline = ffpipeline_alloc(&g_pipeline_class, sizeof(IJKFF_Pipeline_Opaque));

//函数指针初始化

pipeline->func_open_video_decoder = func_open_video_decoder;

至此已经完成了ijkplayer播放器初始化的相关流程,简单来说,就是创建播放器对象,完成音视频解码、渲染的准备工作。

static JNINativeMethod g_methods[] = {

  {

   "_setDataSource",

  "(LV",

   !!!(void *) IjkMediaPlayer_setDataSourceAndHeaders

  },

  { "_setDataSourceFd",   "(I)V",  (void *) IjkMediaPlayer_setDataSourceFd },

  { "_setDataSource",  "(Ltv/danmaku/ijk/media/player/misc/IMediaDataSource;)V", (void *)IjkMediaPlayer_setDataSourceCallback },

  { "_setAndroidIOCallback","(Ltv/danmaku/ijk/media/player/misc/IAndroidIO;)V", (void *)IjkMediaPlayer_setAndroidIOCallback },

  { "_setVideoSurface",  "(Landroid/view/Surface;)V", (void *) IjkMediaPlayer_setVideoSurface },

  { "_prepareAsync",   "()V", (void *) IjkMediaPlayer_prepareAsync },//00

  { "_start",      "()V", (void *) IjkMediaPlayer_start },

  { "_stop",      "()V",  (void *) IjkMediaPlayer_stop },

  { "seekTo",      "(J)V", (void *) IjkMediaPlayer_seekTo },

  { "_pause",      "()V", (void *) IjkMediaPlayer_pause },

  { "isPlaying",    "()Z", (void *) IjkMediaPlayer_isPlaying },

  { "getCurrentPosition",  "()J", (void *) IjkMediaPlayer_getCurrentPosition },

  { "getDuration",    "()J",  (void *) IjkMediaPlayer_getDuration },

  { "_release",     "()V", (void *) IjkMediaPlayer_release },

  { "_reset",      "()V", (void *) IjkMediaPlayer_reset },

  { "setVolume",    "(FF)V",(void *) IjkMediaPlayer_setVolume },

  { "getAudioSessionId",  "()I",  (void *) IjkMediaPlayer_getAudioSessionId },

  { "native_init",    "()V",  (void *) IjkMediaPlayer_native_init },

  { "native_setup",    "(LV", (void *) IjkMediaPlayer_native_setup },

  { "native_finalize",  "()V", (void *) IjkMediaPlayer_native_finalize },

  { "_setOption",    "(IL IjkMediaPlayer_setOption },

  { "_setOption",    "(ILV",     (void *) IjkMediaPlayer_setOptionLong },

  { "_getColorFormatName", "(I)Ljava/lang/String;", (void *) IjkMediaPlayer_getColorFormatName },

  { "_getVideoCodecInfo",  "()Ljava/lang/String;",  (void *) IjkMediaPlayer_getVideoCodecInfo },

  { "_getAudioCodecInfo",  "()Ljava/lang/String;",  (void *) IjkMediaPlayer_getAudioCodecInfo },

  { "_getMediaMeta",   "()Landroid/os/Bundle;", (void *) IjkMediaPlayer_getMediaMeta },

  { "_setLoopCount",   "(I)V",       (void *) IjkMediaPlayer_setLoopCount },

  { "_getLoopCount",   "()I",      (void *) IjkMediaPlayer_getLoopCount },

  { "_getPropertyFloat",  "(IF)F",      (void *) ijkMediaPlayer_getPropertyFloat },

  { "_setPropertyFloat",  "(IF)V",      (void *) ijkMediaPlayer_setPropertyFloat },

  { "_getPropertyLong",   "(IJ)J",      (void *) ijkMediaPlayer_getPropertyLong },

  { "_setPropertyLong",   "(IJ)V",      (void *) ijkMediaPlayer_setPropertyLong },

  { "_setStreamSelected",  "(IZ)V",      (void *) ijkMediaPlayer_setStreamSelected },

  { "native_profileBegin", "(LV", (void *) IjkMediaPlayer_native_profileBegin },

  { "native_profileEnd",  "()V",       (void *) IjkMediaPlayer_native_profileEnd },

  { "native_setLogLevel",  "(I)V",       (void *) IjkMediaPlayer_native_setLogLevel },

  { "_setFrameAtTime",  "(L IjkMediaPlayer_setFrameAtTime },

};

三、核心代码剖析

//ijkplayer实际上是基于ffplay.c实现的,

//本章节将以该文件为主线,从数据接收(数据读取)、音视频解码、音视频渲染及同步这三大方面进行讲解,要求读者有基本的ffmpeg知识。

IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

int ijkmp_prepare_async(IjkMediaPlayer *mp)

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)

//开始播放时,启动消息线程

mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

int ret = mp->msg_loop(arg);

//该方法是启动播放器的入口函数,在此会设置player选项,打开audio output,最重要的是调用stream_open方法。

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)

//音频输出:ijkplayer中Android平台使用OpenSL ES或AudioTrack输出音频,iOS平台使用AudioQueue输出音频。

ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);

return pipeline->func_open_audio_output(pipeline, ffp);

if (ffp->opensles) {

aout = SDL_AoutAndroid_CreateForOpenSLES();

} else {

//默认硬解,主要完成的是创建SDL_Aout对象

//回到ffplay.c中,如果发现待播放的文件中含有音频,那么在调用stream_component_open打开解码器时,该方法里面也调用audio_open打开了audio output设备。

aout = SDL_AoutAndroid_CreateForAudioTrack();

}

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)

从代码中可以看出,stream_open主要做了以下几件事情:

(1)创建存放video/audio解码前数据的videoq/audioq

(2)创建存放video/audio解码后数据的pictq/sampq

(3)创建视频渲染线程video_refresh_thread

(4)创建读数据线程read_thread

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;

//视频的处理流程:

//1.在decoder_decode_frame 方法中从解码前的video queue中取出一帧数据,

//2.送入decoder进行解码,解码后的数据在ffplay_video_thread中送入pictq,

//3.解码后的数据被送到pictq后,video_image_display2函数会取出最新的解码后的视频数据

//4.然后交给SDL通过openGL来进行渲染

秒开,首先,我们知道在ijkplayer默认视频同步到音频,在video_refresh_thread对视频做了同步,

我们把视频前两帧数据不做同步,即时刷新,这样能大大加快首屏时间,其次我们设置probesize大小,

如果probesize不设置的话,avformat_find_stream_info会消耗很长时间,这里建议如果只是音频,设置1k,

如果是音视频,设置为64k,更进一步的修改是自己设置相关解码属性,不用avformat_find_stream_info获取,

最后我们还可以对前两帧的刷新时机进行进一步优化,现在通过sleep来控制,可以换成信号量,解码之后,

立即通知开始执行渲染,改完这些之后基本上首屏能在500ms内。

is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");

//音视频同步

参考时钟的选择也有多种方式:

选取视频时间戳作为参考时钟源

选取音频时间戳作为参考时钟源

选取外部时间作为参考时钟源

考虑人对视频、和音频的敏感度,在存在音频的情况下,优先选择音频作为主时钟源。

video_refresh(ffp, &remaining_time);

(312)

//lastvp是上一帧,vp是当前帧,//循环队列,看不懂???

//1.frame_queue_peek_last表示从循环队列帧里面取出当前需要显示的上一帧视频

//2.frame_queue_peek表示从循环队列帧里面取出当前需要显示的一帧视频

//3.frame_queue_peek_next表示从循环队列帧里面取出当前需要显示的下一帧视频

//4.frame_queue_next从帧队列中取出帧之后的参数操作

//5.Frame *frame_queue_peek_writable(FrameQueue *f)//返回要填充的frame_queue中的Frame。

//6.frame_queue_push放帧到队列中,frame_queue_peek_writable之后的参数操作,windex++

lastvp = frame_queue_peek_last(&is->pictq);

vp = frame_queue_peek(&is->pictq);

//last_duration则是根据当前帧和上一帧的pts,计算出来上一帧的显示时间,

//经过compute_target_delay方法,计算出显示当前帧需要等待的时间

在compute_target_delay方法中,如果发现当前主时钟源不是video,则计算当前视频时钟与主时钟的差值:

如果当前视频帧落后于主时钟源,则需要减小下一帧画面的等待时间;

如果视频帧超前,并且该帧的显示时间大于显示更新门槛,则显示下一帧的时间为超前的时间差加上上一帧的显示时间

如果视频帧超前,并且上一帧的显示时间小于显示更新门槛,则采取加倍延时的策略。

last_duration = vp_duration(is, lastvp, vp);

delay = compute_target_delay(ffp, last_duration, is);

//frame_timer实际上就是上一帧的播放时间,

//而frame_timer + delay实际上就是当前这一帧的播放时间,

//(312)如果系统时间还没有到当前这一帧的播放时间,直接跳转至display,

//而此时is->force_refresh变量为0,不显示当前帧,

//进入video_refresh_thread中下一次循环,并睡眠等待。

time= av_gettime_relative()/1000000.0;

if (isnan(is->frame_timer) || time < is->frame_timer)

is->frame_timer = time;

if (time < is->frame_timer + delay) {

*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);

goto display;

}

//(313)如果当前这一帧的播放时间已经过了,

//并且其和当前系统时间的差值超过了AV_SYNC_THRESHOLD_MAX,

//则将当前这一帧的播放时间改为系统时间,并在后续判断是否需要丢帧,

//其目的是为后面帧的播放时间重新调整frame_timer,

//如果缓冲区中有更多的数据,并且当前的时间已经大于当前帧的持续显示时间,

//则丢弃当前帧,尝试显示下一帧。

is->frame_timer += delay;

if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)

is->frame_timer = time;

SDL_LockMutex(is->pictq.mutex);

if (!isnan(vp->pts))

update_video_pts(is, vp->pts, vp->pos, vp->serial);

SDL_UnlockMutex(is->pictq.mutex);

if (frame_queue_nb_remaining(&is->pictq) > 1) {

//表示从循环队列帧里面取出当前需要显示的下一帧视频

Frame *nextvp = frame_queue_peek_next(&is->pictq);

duration = vp_duration(is, vp, nextvp);

if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) {

frame_queue_next(&is->pictq);

goto retry;

}

}

//(314)最后渲染图像的方法

video_display2(ffp);

video_image_display2(ffp);

//数据传递:is->pictq 到 vp->bmp 到 overlay

//(3131)从pictq中读取当前需要显示视频帧//f->rindex

vp = frame_queue_peek_last(&is->pictq);

//(3132)进行绘制////显示每帧图片

SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);

return vout->display_overlay(vout, overlay);

//在ijksdl_vout_android_nativewindow.c/SDL_VoutAndroid_CreateForANativeWindow()函数中

vout->display_overlay = func_display_overlay;

//调用OpengGL绘制图像

int retval = func_display_overlay_l(vout, overlay);

return IJK_EGL_display(opaque->egl, native_window, overlay);

//(or)return SDL_Android_NativeWindow_display_l(native_window, overlay);

//read_thread()调用了如下函数:

1.avformat_open_input():打开媒体。

2.avformat_find_stream_info():获得媒体信息。

3.av_dump_format():输出媒体信息到控制台。

4.stream_component_open():分别打开视频/音频/字幕解码线程。

5.refresh_thread():视频刷新线程。

6.av_read_frame():获取一帧压缩编码数据(即一个AVPacket)。

7.packet_queue_put():根据压缩编码数据类型的不同(视频/音频/字幕),放到不同的PacketQueue中。

is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");//创建数据读取线程

ic = avformat_alloc_context();//(41)创建上下文结构体,这个结构体是最上层的结构体,表示输入上下文

ic->interrupt_callback.callback = decode_interrupt_cb;//(42)设置中断函数,如果出错或者退出,就可以立刻退出

ic->interrupt_callback.opaque = is;

//(43)打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等

err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

//(44)探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息

err = avformat_find_stream_info(ic, opts);

//(45)打开ffmpeg视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程。

int stream_component_open(FFPlayer *ffp, int stream_index)

//stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);

//ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);

//stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);

codec = avcodec_find_decoder(avctx->codec_id);//获取音视频编码格式

if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {//用一个编码格式打开一个编码文件

goto fail;

}

switch (avctx->codec_type) {

case AVMEDIA_TYPE_AUDIO:

//(451)打开音频解码器,创建audio解码线程

ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)

//(4511)配置了音频输出的相关参数SDL_AudioSpec

SDL_AudioSpec wanted_spec;

wanted_spec.format = AUDIO_S16SYS;

wanted_spec.silence = 0;

wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AoutGetAudioPerSecondCallBacks(ffp->aout)));

//(4513)AudioTrack(Android)/AudioQueue(IOS)模块在工作过程中,通过不断的callback来获取pcm数据进行播放

wanted_spec.callback = sdl_audio_callback;

//(4512)用此函数来打开音响设备

while (SDL_AoutOpenAudio(ffp->aout, &wanted_spec, &spec) < 0) {}

return aout->open_audio(aout, desired, obtained);

ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")//audio的解码线程

case AVMEDIA_TYPE_VIDEO:

//(452)创建IJKFF_Pipenode

ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);

return pipeline->func_open_video_decoder(pipeline, ffp);//ffpipeline_anroid.c

//iOS平台上硬解使用VideoToolbox,Android平台上使用MediaCodec。ijkplayer中的音频解码只支持软解,暂不支持硬解。

// 根据是否设置mediacodec参数决定是否启用硬解码 //默认软解

if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2){

ALOGD("laixh1 ffpipenode_create_video_decoder_from_android_mediacodec()\n");

node = ffpipenode_create_video_decoder_from_android_mediacodec(ffp, pipeline, opaque->weak_vout);

}

if (!node) {//如果ffpipenode_create_video_decoder_from_android_mediacodec返回的node为NULL 那么就会走软解

ALOGD("laixh2 ffpipenode_create_video_decoder_from_ffplay()\n");

node = ffpipenode_create_video_decoder_from_ffplay(ffp);

}

//(453)video的解码线程

//不管视频解码还是音频解码,其基本流程都是从解码前的数据缓冲区中取出一帧数据进行解码,完成后放入相应的解码后的数据缓冲区

ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")

if (ffp->node_vdec)

{

ret = ffpipenode_run_sync(ffp->node_vdec);

//func_run_sync取决于播放前配置的软硬解。

//软解:调用ffpipenode_ffplay_vdec调用ffpipenode_ffplay_vdec.c中的函数定义;//默认软解

//硬解:调用ffpipenode_ffplay_vdec调用ffpipenode_ffplay_mediacodec.c中的函数定义;

return node->func_run_sync(node);

return ffp_video_thread(opaque->ffp);

return ffplay_video_thread(ffp);//ff_ffplay.c

for (;;) {

//(4531)该方法中从解码前的video queue中取出一帧数据,送入decoder进行解码,

ret = get_video_frame(ffp, frame);

got_picture = decoder_decode_frame(ffp, &is->viddec, frame, NULL)

AVPacket pkt;

for (;;) {

//(45311)从解码前的video queue中取出一帧数据

do {

packet_queue_get_or_buffering(ffp, d->queue, &pkt, &d->pkt_serial, &d->finished) < 0)

while(1){

//获取压缩编码数据(一个AVPacket)

int new_packet = packet_queue_get(q, pkt, 0, serial);

}

}while (d->queue->serial != d->pkt_serial);

//(45312)将流数据输出给解码器进行解码

ffmpeg中解码的API之前的是avcodec_decode_video2()和avcodec_decode_audio4(),

现在使用avcodec_send_packet()/ avcodec_receive_frame()来代替原有的接口

// 1)对于解码,请调用avcodec_send_packet()以在AVPacket中给出解码器原始的压缩数据。

// 2)对于编码,请调用avcodec_send_frame()为编码器提供包含未压缩音频或视频的AVFrame

avcodec_send_packet和avcodec_receive_frame调用关系并不一定是一对一的,

比如一些音频数据一个AVPacket中包含了1秒钟的音频,调用一次avcodec_send_packet之后,

可能需要调用25次 avcodec_receive_frame才能获取全部的解码音频数据

do {

ret = avcodec_receive_frame(d->avctx, frame);

}while (ret != AVERROR(EAGAIN));

avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)

decode_receive_frame_internal(avctx, avci->buffer_frame);

av_packet_unref(&pkt);

}

//(4532)默认MAX_RETRY_CONVERT_IMAGE=3//laixh秒开优化改成1,验证效果???没有效果!!

while (retry_convert_image <= MAX_RETRY_CONVERT_IMAGE) {

ret = convert_image(ffp, frame, (int64_t)pts, frame->width, frame->height);

//FFmpeg里面的sws_scale库可以在一个函数里面同时实现:

//1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。

//其核心函数主要有三个:sws_getContext():初始化一个SwsContext;sws_scale():处理图像数据;sws_freeContext():释放一个SwsContext。

//初始化一个SwsContext //src_frame->format转AV_PIX_FMT_RGB24

img_info->frame_img_convert_ctx = sws_getContext(width,height,src_frame->format,dst_width,dst_height,AV_PIX_FMT_RGB24,SWS_BICUBIC,NULL, NULL,NULL)

//处理图像数据。

//参数struct SwsContext *c,为上面sws_getContext函数返回值;

//参数const uint8_t *const srcSlice[], const int srcStride[]定义输入图像信息(当前处理区域的每个通道数据指针,每个通道行字节数)

//参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)

ret = sws_scale(img_info->frame_img_convert_ctx,(const uint8_t * const *) src_frame->data,src_frame->linesize,0,src_frame->height,dst_frame->data, dst_frame->linesize);

//avctx:编码器的AVCodecContext;avpkt:编码输出的AVPacket;frame:编码输入的AVFrame;got_packet_ptr:成功编码一个AVPacket的时候设置为1。

//函数返回0代表编码成功。//不是解码吗?为什么还要进行编码???

ret = avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,const AVFrame *frame, int *got_packet_ptr);

if (ret >= 0 && got_packet > 0) {

fd = open(file_path, O_RDWR | O_TRUNC | O_CREAT, 0600);

write(fd, avpkt.data, avpkt.size);

}

//释放一个SwsContext。

sws_freeContext()

}

//(4533)解码后的数据在ffplay_video_thread中送入pictq

ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);

//返回要填充的frame_queue中的Frame

//Frame *frame_queue_peek_writable(FrameQueue *f)

vp = frame_queue_peek_writable(&is->pictq)

//放帧到队列中//frame_queue_peek_writable之后的参数操作,windex++

frame_queue_push(&is->pictq);

}

}

//重复(46)、(47)步,即可不断获取待播放的数据。

//(46)读取媒体数据,得到的是音视频分离的解码前数据

ret = av_read_frame(ic, pkt);

//(47)将音视频数据分别送入相应的queue中

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);

}

四、事件处理

//在播放过程中,某些行为的完成或者变化,如prepare完成,开始渲染等,需要以事件形式通知到外部,以便上层作出具体的业务处理。

//ijkplayer支持的事件比较多,具体定义在ijkplayer/ijkmedia/ijkplayer/ff_ffmsg.h中

#define FFP_MSG_FLUSH        0

#define FFP_MSG_ERROR        100 

#define FFP_MSG_PREPARED      200

#define FFP_MSG_COMPLETED      300

#define FFP_MSG_VIDEO_SIZE_CHANGED  400 

#define FFP_MSG_SAR_CHANGED     401 

#define FFP_MSG_VIDEO_RENDERING_START  402

#define FFP_MSG_AUDIO_RENDERING_START  403

#define FFP_MSG_VIDEO_ROTATION_CHANGED 404

#define FFP_MSG_BUFFERING_START    500

#define FFP_MSG_BUFFERING_END     501

#define FFP_MSG_BUFFERING_UPDATE   502

#define FFP_MSG_BUFFERING_BYTES_UPDATE 503

#define FFP_MSG_BUFFERING_TIME_UPDATE  504

#define FFP_MSG_SEEK_COMPLETE     600 

#define FFP_MSG_PLAYBACK_STATE_CHANGED 700

#define FFP_MSG_TIMED_TEXT     800

#define FFP_MSG_VIDEO_DECODER_OPEN  10001

#define MEDIA_INFO_UNKNOWN = 1,

#define MEDIA_INFO_VIDEO_RENDERING_START = 3, // 第一帧视频数据渲染时间

#define MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001,

#define MEDIA_INFO_AUDIO_RENDERING_START= 10002,

#define MEDIA_INFO_AUDIO_DECODED_START= 10003,

#define MEDIA_INFO_VIDEO_DECODED_START= 10004, //第一帧视频解码完成时间

#define MEDIA_INFO_OPEN_INPUT    = 10005, //avformat_open_input执行完成时间

#define MEDIA_INFO_FIND_STREAM_INFO  = 10006, //avformat_find_stream_info执行完成时间

#define MEDIA_INFO_COMPONENT_OPEN   = 10007, //IO设备操作完成时间

#define MEDIA_INFO_VIDEO_FIRSTPKT_GOT //首帧获取时间

//ijkmp_android_create(message_loop)

IjkMediaPlayer *ijkmp_android_create(int(*msg_loop)(void*))

ijkmp_create(msg_loop)

//(41)消息上报初始化

mp->msg_loop = msg_loop;

mp->ffplayer->vout = SDL_VoutAndroid_CreateForAndroidSurface();

return SDL_VoutAndroid_CreateForANativeWindow();

//函数指针初始化

vout->opaque_class = &g_nativewindow_class;

vout->create_overlay= func_create_overlay;

vout->free_l   = func_free_l;

vout->display_overlay = func_display_overlay;

IjkMediaPlayer_prepareAsync(JNIEnv *env, jobject thiz)

int ijkmp_prepare_async(IjkMediaPlayer *mp)

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)

//(42)开始播放时,启动消息线程

mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

int ret = mp->msg_loop(arg);

message_loop

message_loop_n

while (1) {

//(44)最后是在该方法中读取消息,并采用notification通知到APP上层

int retval = ijkmp_get_msg(mp, &msg, 1);

switch (msg.what) {

case FFP_MSG_VIDEO_RENDERING_START:

MPTRACE("FFP_MSG_VIDEO_RENDERING_START:\n");

post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_VIDEO_RENDERING_START, 0);

break;

case FFP_MSG_OPEN_INPUT:

MPTRACE("FFP_MSG_OPEN_INPUT:\n");

post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_OPEN_INPUT, 0);

break;

case FFP_MSG_FIND_STREAM_INFO:

MPTRACE("FFP_MSG_FIND_STREAM_INFO:\n");

post_event(env, weak_thiz, MEDIA_INFO, MEDIA_INFO_FIND_STREAM_INFO, 0);

break;

}

}

//(43)ffplay.c中上报PREPARED完成

ffp_notify_msg1(ffp, FFP_MSG_PREPARED);

ffp_notify_msg1(ffp, FFP_MSG_VIDEO_RENDERING_START);

//将事件及其参数封装成了AVMessge对象

msg_queue_put_simple3(&ffp->msg_queue, what, 0, 0);

msg_init_msg(&msg);

msg_queue_put(q, &msg);

//消息对象放入消息队列

ret = msg_queue_put_private(q, msg);

预加载接口

  public void doPreload(String url, int tsindex){

   //stop

   _doPreload(url, tsindex);

  }

//预缓存清除接口

  public void deleteCache() {

   _deleteCache();

  }

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • 教程一:视频截图(Tutorial 01: Making Screencaps) 首先我们需要了解视频文件的一些基...
    90后的思维阅读 4,652评论 0 3
  • 随着互联网技术的飞速发展,移动端播放视频的需求如日中天,由此也催生了一批开源/闭源的播放器,但是无论这个播放器功能...
    金山视频云阅读 46,131评论 28 170
  • 在上一篇笔记中我们已经完成了使用SDL播放声音和视频,声音播放没有什么问题,而视频播放太快,很明显视频没有同步。在...
    762683ff5d3d阅读 1,299评论 0 1
  • 笔记可能微乱,但大致清晰,可能会对他人有所帮助,故分享出来。 ×××××××××××××××××××目录×××××...
    王英豪阅读 5,972评论 0 8
  • 最近学习播放器的一些东西,所以接触了ffmpeg,看源码的过程中,就想了解一下ffplay是怎么处理音视频同步的,...
    smm987阅读 4,400评论 0 5