c编写播放器
来源:互联网 发布:php安装apache 编辑:程序博客网 时间:2024/04/25 17:48
这篇文章主要介绍使用SDL和ffmpeg,c语音编写一个简单播放器的大概实现原理,其中最难理解的就是如何同步音视频。
实现主要参考老外的ffmpeg tutorial(http://dranger.com/ffmpeg/)。这系列的教程很赞,整体难度给人感觉也很大,主要是教程ffmpeg的sdk似乎是0.6的,而ffmpeg的API接口几乎是每次版本都更新,所以需要更改的地方很多。网上翻译这些列文章也很多,但能找到最新的代码也已经out of date了,所以几乎都没法在ffmepg 2.1上编译。
最近在研究ffmpeg,顺便基于ffmpeg最新的sdk实现了这个播放器,并且将代码进行了模块化,
SDL
SDL是一个跨平台的多媒体开发的函数库,包括win,unix,ios,android。在这个播放器里主要用来显示视频和播放声音。
ffmpeg
ffmepg是一个音视频解码、编码库,在这个播放器里主要用来对文件进行解码,再将编码的数据编码成SDL要求的数据格式。
原理
先贴一张工作的流程图。
播放器有4个线程,主线程创建其他线程。 线程之间有几乎都存在生产者消费者的情况,包括:解码线程生产未编码数据,音视频编码线程消费数据;视频编码线程生产picture图片数据,主线程消费并显示图片;
主线程
主线程负责初始化ffmpeg和SDL,创建解码线程,并且循环读取事件,分配内存,显示图片。
解码线程
解码线程用ffmpeg打开文件,创建context,分析音频流和视频流,并创建音频和视频编码线程。并通过av_read_frame循环读取文件,将AVPacket添加到缓存队列中。为了防止内存过大,缓存队列设置最大BUFFER_SIZE,如果大于BUFFER_SIZE,则sleep等待消费者消费。
音频编码线程
音频编码线程是SDL创建,我们只需提供声音数据的回调函数,将音频数据填入回调函数即可。SDL会将填入的数据进行播放。
视频编码线程
视频编码线解码数据,并将解码的AVFrame转化为SDL可以显示的SDL_YUVOverlay,加入图片缓存队列,等待主线程显示。
音视频同步
最蛋疼的问题就是如何实现音视频同步了。
首先我们知道ffmpeg readPacket的时候每个packet都有pts和dts,既然如此我们按照pts显示就行了,为什么会不同步?首先可能因为视频解码编码时间过长,编码完成后可能已经过了应该显示的pts时间,这时候应该尽快显示此帧。有时候解码过快,还未到显示的pts,我们需要延迟显示此帧。
但是音频的解码效率很高,而且音频是恒定频率,所以pts稳定增加,所以我们选择视频同步到音频,及以音频为参考时间,调整视频的播放时间进行音视频同步。
下面是同步的核心代码
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
void video_refresh_timer(void *userdata) { VideoState *is = (VideoState *)userdata; VideoPicture *vp; double actual_delay, delay, sync_threshold, ref_clock, diff; if(is->video_st) { if(is->pictq_size == 0) { schedule_refresh(is, 1); } else { /* printf("vidoe clock %f audio clock %f \n",is->video_clock,is->audio_clock); */ /* printf("audio clock %f",is->audio_clock); */ vp = &is->pictq[is->pictq_rindex]; delay = vp->pts - is->frame_last_pts; /* the pts from last time */ /* printf("delay %f ",delay); */ if(delay <= 0 || delay >= 1.0) { /* if incorrect delay, use previous one */ delay = is->frame_last_delay; } /* save for next time */ is->frame_last_delay = delay; is->frame_last_pts = vp->pts; /* update delay to sync to audio */ ref_clock = get_audio_clock(is); diff = vp->pts - ref_clock; /* printf("diff %f \n",diff); */ /* Skip or repeat the frame. Take delay into account FFPlay still doesn't "know if this is the best guess." */ sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD; if(fabs(diff) < AV_NOSYNC_THRESHOLD) { if(diff <= -sync_threshold) { delay = 0; } else if(diff >= sync_threshold) { delay = 2 * delay; } } is->frame_timer += delay; /* computer the REAL delay */ actual_delay = is->frame_timer - (av_gettime() / 1000000.0); /* printf("diff %f actual delay %f \n",diff,actual_delay); */ if(actual_delay < 0.010) { /* Really it should skip the picture instead */ actual_delay = 0.010; } schedule_refresh(is, (int)(actual_delay * 1000 + 0.5)); /* show the picture! */ video_display(is); /* update queue for next picture! */ if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) { is->pictq_rindex = 0; } SDL_LockMutex(is->pictq_mutex); is->pictq_size--; SDL_CondSignal(is->pictq_cond); SDL_UnlockMutex(is->pictq_mutex); } } else { schedule_refresh(is, 100); }}
同步代码在else里面,首先我们注意到delay这个值,delay是这一帧图像与上一帧的时间差,我们计算它,如果合理就使用,不合理则使用记录的上次的delay。然后我们计算pts与audio_clock的时间差,如果diff过大,说明现在的pts过大,我们需要延后这个picture显示,否则pts会越来越大。如果diff过小,我们需要抓紧显示这张图片,负责video_clock会与audio_clock更小。frame_timer初始值为播放的开始时间,每次增加delay,从而计算出实际的delay。为什么不简单的使用delay这个值呢?我觉得长期累加的值才能产生更稳定的播放效果,这是一个数学论证,作为程序员的我们了解这种思想就好@V@。当然,这只是我的推测。对了,我们还要注意sync_threshold变量的赋值,我们选取max(delay,AV_SYNC_THRESHOLD)赋值,其中AV_SYNC_THRESHOLD是一个宏,大小定义为0.01。因为AV_SYNC_THRESHOLD作为阈值只是一个猜测,我们将delay加入作为猜测才能更好适应更多的输入文件,这样设计音频与视频显示最多也就差一个视频的时间戳。
结束
自己的理解大概就是这些,如果有错误,希望能指出,有什么问题想讨论可以发邮件到jered@gmail.com。
- c编写播放器
- Objective C编写音乐播放器
- API编写音乐播放器
- MFC播放器的编写
- C#Media播放器
- C#-播放器相关
- 用API编写MP3播放器
- java编写的简单播放器
- 使用FFmpeg编写音乐播放器
- C#编写简单音乐播放器
- VLC 开发包 编写简单播放器
- java 编写的 mp3 播放器 1
- FLex 编写网页MP3播放器
- Android Broadcast编写的音乐播放器
- Java编写类似Flash的播放器
- Qt+VLC编写的流媒体播放器
- Qt+VLC编写的流媒体播放器
- java编写的音乐播放器
- composer安装laravel具体版本命令
- 包含min函数的栈
- 第十二周作业
- 两个串最长公共子序列的长度:
- 活着
- c编写播放器
- Codeforces Good Bye 2016 C. New Year and Rating(模拟)
- First time to write articles on CSDN Blog
- Hive对有null值得一列做avg,count等操作时会过滤掉有NULL值的这一行
- 集成iOS的支付宝遇到的问题及解决方案
- C指针(转)
- debugview的使用方法
- 我的学习记录42
- 重载(Overload)和重写(Override)解析