问题
主流程上的区别
缓冲区的设计
内存管理的逻辑
音视频播放方式
音视频同步
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ÿ