一、总体说明
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();
}