带着问题,再读ijkplayer源码

本文详细分析了ijkplayer的源码,涵盖了数据流向、内存管理、音视频同步、seek处理等方面。重点讨论了缓冲区设计,如packetQueue和frameQueue的实现,以及packet和frame的内存管理策略。此外,还探讨了音视频的播放方式,特别是音频和视频帧的处理。最后,文章介绍了ijkplayer的音视频同步机制,强调了同步钟在不同场景下的应用和修正。
摘要由CSDN通过智能技术生成

640?wx_fmt=png

问题
  • 主流程上的区别

  • 缓冲区的设计

  • 内存管理的逻辑

  • 音视频播放方式

  • 音视频同步

  • seek的问题:缓冲区flush、播放时间显示、k帧间距大时定位不准问题…

  • stop时怎么释放资源,是否切换到副线程?

  • 网络不好时的处理,如获取frame速度慢于消耗速度时,如果不暂停,会一致卡顿,是否会主动暂停?

  • VTB的解码和ffmpeg的解码怎么统一的?架构上怎么设计的?

数据流向

主流程更详细看ijkPlayer主流程分析

音频
  • av_read_frame

  • packet_queue_put

  • audio_thread+decoder_decode_frame+packet_queue_get_or_buffering

  • frame_queue_peek_writable+frame_queue_push

  • audio_decode_frame+frame_queue_peek_readable,数据到is->audio_buf

sdl_audio_callback,数据导入到参数stream里。这个函数是上层的音频播放库的buffer填充函数,如iOS里使用audioQueue,回调函数IJKSDLAudioQueueOuptutCallback调用到这里,然后把数据传入到audioQueue.

视频

读取packet部分一样

video_thread,然后ffpipenode_run_sync里硬解码定位到videotoolbox_video_thread,然后ffp_packet_queue_get_or_buffering读取。

VTDecoderCallback解码完成回调里,SortQueuePush(ctx, newFrame);把解码后的pixelBuffer装入到一个有序的队列里。

GetVTBPicture从有序队列里把frame的封装拿出来,也就是这个有序队列只是一个临时的用来排序的工具罢了,这个思想是可以吸收的;queue_picture里,把解码的frame放入frame缓冲区
显示video_refresh+video_image_display2+[IJKSDLGLView display:]

最后的纹理生成放在了render里,对vtb的pixelBuffer,在yuv420sp_vtb_uploadTexture。使用render这个角色,渲染的部分都抽象出来了。shader在IJK_GLES2_getFragmentShader_yuv420sp

结论:主流程上没有大的差别。

缓冲区的设计

packetQueue:

1、数据结构设计

packetQueue采用两条链表,一个是保存数据的链表,一个是复用节点链表,保存没有数据的那些节点。数据链表从first_pkt到last_pkt,插入数据接到last_pkt的后面,取数据从first_pkt拿。复用链表的开头是recycle_pkt,取完数据后的空节点,放到空链表recycle_pkt的头部,然后这个空节点成为新的recycle_pkt。存数据时,也从recycle_pkt复用一个节点。
链表的节点像是包装盒,装载数据的时候放到数据链表,数据取出后回归到复用链表。

2、进出的阻塞控制

取数据的时候可能没有,那么就有几种处理:直接返回、阻塞等待。它这里的处理是阻塞等待,并且会把视频播放暂停。所以这个回答了问题8,外面看到的效果就是:网络卡的时候,会停止播放然后流畅的播放一会,然后又继续卡顿,播放和卡顿是清晰分隔的。
进数据的时候并没有做阻塞控制,为什么数据不会无限扩大?
是有阻塞的,但阻塞不是在packetQueue里面,而是在readFrame函数里:

 
 

if (ffp->infinite_buffer<1 && !is->seek_req &&
             (is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
           || (   stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
               && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
               && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES)))) {
           if (!is->eof) {
               ffp_toggle_buffering(ffp, 0);
           }
           /* wait 10 ms */
           SDL_LockMutex(wait_mutex);
           SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
           SDL_UnlockMutex(wait_mutex);
           continue;
       }

简化来看就是:

  • infinite_buffer不是无限的缓冲

  • is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size,使用数据大小做限制

  • stream_has_enough_packets使用数据的个数做限制

因为个数设置到了50000,一般达不到,而是数据大小做了限制,在15M左右。
这里精髓的地方有两点:

  • 采用了数据大小做限制,因为对于不同的视频,分辨率的问题会导致同一个packet差距巨大,而我们实际关心的其实就是内存问题。

  • 暂停10ms,而不是无限暂停等待条件锁的signal。从设计上说会更简单,而且可以避免频繁的wait+signal。这个问题还需仔细思考,但直觉上觉得这样的操作非常好。

frameQueue:

数据使用一个简单的数组保存,可以把这个数据看成是环形的,然后也是其中一段有数据,另一段没有数据。rindex表示数据开头的index,也是读取数据的index,即read indexÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值