http://www.jianshu.com/p/daf0a61cc1e0

3.3 音视频渲染及同步

3.3.1 音频输出

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

audio output节点,在ffp_prepare_async_l方法中被创建:

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

ffpipeline_open_audio_output方法实际上调用的是IJKFF_Pipeline对象的函数指针func_open_audio_utput,该函数指针在初始化中的ijkmp_ios_create方法中被赋值,最后指向的是func_open_audio_output

static SDL_Aout *func_open_audio_output(IJKFF_Pipeline *pipeline, FFPlayer *ffp)
{return SDL_AoutIos_CreateForAudioUnit();
}

SDL_AoutIos_CreateForAudioUnit定义如下,主要完成的是创建SDL_Aout对象

SDL_Aout *SDL_AoutIos_CreateForAudioUnit()
{SDL_Aout *aout = SDL_Aout_CreateInternal(sizeof(SDL_Aout_Opaque));if (!aout)return NULL;// SDL_Aout_Opaque *opaque = aout->opaque;aout->free_l = aout_free_l;aout->open_audio  = aout_open_audio;aout->pause_audio = aout_pause_audio;aout->flush_audio = aout_flush_audio;aout->close_audio = aout_close_audio;aout->func_set_playback_rate = aout_set_playback_rate;aout->func_set_playback_volume = aout_set_playback_volume;aout->func_get_latency_seconds = auout_get_latency_seconds;aout->func_get_audio_persecond_callbacks = aout_get_persecond_callbacks;return aout;
}

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

static int audio_open(FFPlayer *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params)
{FFPlayer *ffp = opaque;VideoState *is = ffp->is;SDL_AudioSpec wanted_spec, spec;......wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout);wanted_spec.channels = wanted_nb_channels;wanted_spec.freq = wanted_sample_rate;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)));wanted_spec.callback = sdl_audio_callback;wanted_spec.userdata = opaque;while (SDL_AoutOpenAudio(ffp->aout, &wanted_spec, &spec) < 0) {.....}......return spec.size;
}

audio_open中配置了音频输出的相关参数SDL_AudioSpec,并通过

int SDL_AoutOpenAudio(SDL_Aout *aout, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
{if (aout && desired && aout->open_audio)return aout->open_audio(aout, desired, obtained);return -1;
}

设置给了Audio Output, iOS平台上即为AudioQueue。

AudioQueue模块在工作过程中,通过不断的callback来获取pcm数据进行播放。

有关AudioQueue的具体内容此处不再介绍。

3.3.2 视频渲染

iOS平台上采用OpenGL渲染解码后的YUV图像,渲染线程为video_refresh_thread,最后渲染图像的方法为video_image_display2,定义如下:

static void video_image_display2(FFPlayer *ffp)
{VideoState *is = ffp->is;Frame *vp;Frame *sp = NULL;vp = frame_queue_peek_last(&is->pictq);......SDL_VoutDisplayYUVOverlay(ffp->vout, vp->bmp);......
}

从代码实现上可以看出,该线程的主要工作为:

  1. 调用frame_queue_peek_last从pictq中读取当前需要显示视频帧
  2. 调用SDL_VoutDisplayYUVOverlay进行绘制

     int SDL_VoutDisplayYUVOverlay(SDL_Vout *vout, SDL_VoutOverlay     *overlay){if (vout && overlay && vout->display_overlay)return vout->display_overlay(vout, overlay);return -1;}

    display_overlay函数指针在前面初始化流程有介绍过,它在

     SDL_Vout *SDL_VoutIos_CreateForGLES2()

    方法中被赋值为vout_display_overlay,该方法就是调用OpengGL绘制图像。

3.4.3 音视频同步

对于播放器来说,音视频同步是一个关键点,同时也是一个难点,同步效果的好坏,直接决定着播放器的质量。通常音视频同步的解决方案就是选择一个参考时钟,播放时读取音视频帧上的时间戳,同时参考当前时钟参考时钟上的时间来安排播放。如下图所示:

音视频同步示意图.png

如果音视频帧的播放时间大于当前参考时钟上的时间,则不急于播放该帧,直到参考时钟达到该帧的时间戳;如果音视频帧的时间戳小于当前参考时钟上的时间,则需要“尽快”播放该帧或丢弃,以便播放进度追上参考时钟。

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

  • 选取视频时间戳作为参考时钟源
  • 选取音频时间戳作为参考时钟源
  • 选取外部时间作为参考时钟源

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

ijkplayer在默认情况下也是使用音频作为参考时钟源,处理同步的过程主要在视频渲染video_refresh_thread的线程中:

static int video_refresh_thread(void *arg)
{FFPlayer *ffp = arg;VideoState *is = ffp->is;double remaining_time = 0.0;while (!is->abort_request) {if (remaining_time > 0.0)av_usleep((int)(int64_t)(remaining_time * 1000000.0));remaining_time = REFRESH_RATE;if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))video_refresh(ffp, &remaining_time);}return 0;
}

从上述实现可以看出,该方法中主要循环做两件事情:

  1. 休眠等待,remaining_time的计算在video_refresh
  2. 调用video_refresh方法,刷新视频帧

可见同步的重点是在video_refresh中,下面着重分析该方法:

   lastvp = frame_queue_peek_last(&is->pictq);vp = frame_queue_peek(&is->pictq);....../* compute nominal last_duration */last_duration = vp_duration(is, lastvp, vp);delay = compute_target_delay(ffp, last_duration, is);

lastvp是上一帧,vp是当前帧,last_duration则是根据当前帧和上一帧的pts,计算出来上一帧的显示时间,经过compute_target_delay方法,计算出显示当前帧需要等待的时间。

static double compute_target_delay(FFPlayer *ffp, double delay, VideoState *is)
{double sync_threshold, diff = 0;/* update delay to follow master synchronisation source */if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {/* if video is slave, we try to correct big delays byduplicating or deleting a frame */diff = get_clock(&is->vidclk) - get_master_clock(is);/* skip or repeat frame. We take into account thedelay to compute the threshold. I still don't knowif it is the best guess */sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay));/* -- by bbcallen: replace is->max_frame_duration with AV_NOSYNC_THRESHOLD */if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {if (diff <= -sync_threshold)delay = FFMAX(0, delay + diff);else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD)delay = delay + diff;else if (diff >= sync_threshold)delay = 2 * delay;}}.....return delay;
}

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

  • 如果当前视频帧落后于主时钟源,则需要减小下一帧画面的等待时间;
  • 如果视频帧超前,并且该帧的显示时间大于显示更新门槛,则显示下一帧的时间为超前的时间差加上上一帧的显示时间
  • 如果视频帧超前,并且上一帧的显示时间小于显示更新门槛,则采取加倍延时的策略。

回到video_refresh

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

frame_timer实际上就是上一帧的播放时间,而frame_timer + delay实际上就是当前这一帧的播放时间,如果系统时间还没有到当前这一帧的播放时间,直接跳转至display,而此时is->force_refresh变量为0,不显示当前帧,进入video_refresh_thread中下一次循环,并睡眠等待。

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

如果当前这一帧的播放时间已经过了,并且其和当前系统时间的差值超过了AV_SYNC_THRESHOLD_MAX,则将当前这一帧的播放时间改为系统时间,并在后续判断是否需要丢帧,其目的是为后面帧的播放时间重新调整frame_timer,如果缓冲区中有更多的数据,并且当前的时间已经大于当前帧的持续显示时间,则丢弃当前帧,尝试显示下一帧。

{frame_queue_next(&is->pictq);is->force_refresh = 1;SDL_LockMutex(ffp->is->play_mutex);......display:/* display picture */if (!ffp->display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)video_display2(ffp);

否则进入正常显示当前帧的流程,调用video_display2开始渲染。

iOS ijkplayer 音视频同步相关推荐

  1. iOS:学习音视频的过程

    一,音视频学习中涉及到的概念 1.我们常见的音视频格式有.mp4,mkv.avi,正如我们常见的.word 需要word 工具打开,不同格式的音视频也需要不同格式的播放器打开,这种视频格式相当于存储视 ...

  2. 音视频同步原理解析;音频编码和解码原理

    视频流中的DTS/PTS到底是什么? DTS(解码时间戳)和PTS(显示时间戳)分别是解码器进行解码和显示帧时相对于SCR(系统参考)的时间戳.SCR可以理解为解码器应该开始从磁盘读取数据时的时间. ...

  3. 游戏陪玩平台系统中iOS 采集音视频及写入文件的实现

    在游戏陪玩平台系统中音视频采集包括两部分:视频采集和音频采集.在iOS中可以同步采集视频与音频,通过系统框架AVFoundation,可以帮助游戏陪玩平台系统采集音频与视频,对于视频还可以进行切换前后 ...

  4. 视频【解码】原理(播放器原理),音视频同步等

    1.视频编码格式:H264, VC-1, MPEG-2, MPEG4-ASP (Divx/Xvid), VP8, MJPEG 等.  2.音频编码格式:AAC, AC3, DTS(-HD), True ...

  5. Android IOS WebRTC 音视频开发总结(三八)-- tx help

    Android IOS WebRTC 音视频开发总结(三八)-- tx help 本文主要介绍帮一个程序员解决webrtc疑问的过程,文章来自博客园RTC.Blacker,支持原创,转载请说明出处(w ...

  6. Android IOS WebRTC 音视频开发总结(二三)-- hurtc使用说明

    Android IOS WebRTC 音视频开发总结(二三)-- hurtc使用说明 本文主要介绍如何测试基于浏览器和手机的视频通话程序,转载请说明出处,文章来自博客园RTC.Blacker,更多详见 ...

  7. Android IOS WebRTC 音视频开发总结(四二)-- webrtc开发者大会

    Android IOS WebRTC 音视频开发总结(四二)-- webrtc开发者大会 本文主要介绍11月要在北京举办的webrtc开发者全球大会,文章来自博客园RTC.Blacker,支持原创,转 ...

  8. vlc源码分析(五) 流媒体的音视频同步

    vlc播放流媒体时实现音视频同步,简单来说就是发送方发送的RTP包带有时间戳,接收方根据此时间戳不断校正本地时钟,播放音视频时根据本地时钟进行同步播放.首先了解两个概念:stream clock和sy ...

  9. 音视频同步(播放)原理

    每一帧音频或视频都有一个持续时间:duration: 采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数. .正常人听觉的频率范围大约在20Hz~20kHz之间,根据奈奎斯特采样理论 ...

最新文章

  1. d010:盈数、亏数和完全数
  2. vue防抖和节流是什么_防抖和节流为什么重要!!!
  3. MySQl笔记8:把good表中商品名为‘诺基亚xxxx‘的商品,改为‘HTCxxxx‘
  4. Android项目实战(三十一):异步下载apk文件并安装(非静默安装)
  5. BigDecimal 加减乘除
  6. Git和SourceTree配合使用
  7. 人工智能翻译能否替代人工翻译,人工智能翻译何去何从
  8. 计算机视觉文献综述选题,综述论文2021-计算机视觉十大领域最新综述文章分类大盘点...
  9. mysql与phpmyadmin安装_phpMyAdmin下载、安装和使用入门_MySQL
  10. matlab hurst,基于Matlab的Hurst指数
  11. 每日一佳——Trading Convexity for Scalability(Ronan Collobert et al. ,ICML,2006)
  12. 在Windows系统中安装CentOS系统和gcc
  13. 法航AF447失事,机上有228人
  14. 雷军:小米创业背后的一些故事和体会
  15. Java Web编程
  16. SSL1232雷达覆盖(normal)
  17. 机器学习--集成学习--Bagging,Boosting,Stacking
  18. 怎样剪切歌曲 剪切歌曲用什么音频转换器
  19. nonebot2聊天机器人插件3:计算器calculator
  20. 微信小程序开发入门(连载)—— Hello World

热门文章

  1. java导出excel 边框不全_POI 导出Excel合并单元格后部分边框不显示
  2. 标准正态分布函数数值表
  3. 第一单元----(4)认识编译器 源代码和可执行程序的关系
  4. ABOV现代芯片MC80F7708Q芯片烧录引脚接线顺序
  5. 习题3-5 三角形判断 (15 分)-PTA浙大版《C语言程序设计(第4版)》
  6. 利用adb设置安卓http代理
  7. OkHttp超时时间设置
  8. 我工作上常用的--测试用例文档模板
  9. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——1.9 案例研究...
  10. 提醒电脑族:眼睛酸涩会致病