linux下播放器设计和开发
http://blog.csdn.net/henryjee/article/details/6737392
DawnLightPlayer目前可以运行在Linux和Windows系统上,并使用VC和Python开发了GUI,支持大部分的音视频文件格式和网络流,另外新增对CMMB协议的支持,不支持 RMVB, SWF 等尚未公开协议的视频文件格式。
目录:
一. 播放器的流程
1. 输入
2. 解码
3. 输出
二. 播放器的实现
1. 输入实现
2. 解码线程实现
3. 输出线程实现
三. 视频输出库
1. SDL (多平台,支持硬件缩放)
2. DirectX DirectDraw (win32平台,支持硬件缩放)
3. OpenGL (多平台,支持硬件缩放)
4. X11 (Linux/Unix)
5. FrameBuffer (Linux, 无硬件缩放)
四. 音频输出
1. OSS (Open Sound System for Linux)
2. ALSA (Advanced Linux Sound Architecture)
3. DirectSound (WIN32)
五. 音视频同步
1. 以音频为基准同步视频
2. 以视频为基准同步音频
3. 同步于一个外部时钟
六. 截图
1. 使用jpeglib保存成jpeg文件
2. 使用libpng保存成png文件
七. YUV RGB 软件转换
八. 软件缩放
一. 播放器的流程
1. 输入 : 从文件或网络等读取原数据,如 x.avi, x.mov, rtsp://xxx, 对原数据进行解析,比如文件,首先要分析文件格式,从文件中取得音视频编码参数,视频时间长度等信息,然后要从其中取出音频编码数据和视频编码数据送到解 码部分,这里暂称这种编码源数据块为 packet。
2. 解码 : 初始化时,利用输入端从源数据中取得的信息调用不同的解码库初始化;然后接收输入端传送来的音视频编码数据,分别进行音频解码和视频解码,视频解码出来的 数据一般是 YUV 或 RGB 数据,这里暂称为 picture, 音频解码出来的数据是采样数据,是声卡可以播放的数据,这里暂称为 sample。 解码所得的数据接下来送到输出部分。
3. 输出 : 接收解码部分送来的 picture 和 sample 并显示。 视频显示一般使用某个图形库,如 SDL, Xlib, DirectDraw, OpengGL, FrameBuffer等, 音频输出是把 sample 写入系统的音频驱动,由音频驱动送入声卡播放, 可用的音频输出有 ALSA, OSS, SDL, DirectSound, WaveOut等。
二. 播放器的实现
推荐实现方案
一个audio_packet队列,一个video_packet队列,一个picture队列,一个sample队列
一个input线程,两个decode线程,两个output线程,一个UI控制线程
1. 输入实现
对 文件的解析,首先要了解文件的格式,文件格式一般称为文件容器。公开的文件格式,按格式协议读取分析就可以了,但像RMVB,SWF这种目前还不公开格式 的文件,就不好办,也是目前一般播放器的困难。一般的文件格式的解析libavformat库已经做了,只要使用它就行,下面给出示例代码段:
初始化:
static int avin_file_init(void)
{
AVFormatParameters params, *ap = ¶ms;
err = av_open_input_file( &fmtctx, input_filename, NULL, 0, ap );
if ( err < 0 )
{
av_log(NULL, AV_LOG_ERROR, "%d: init input from file error\n", __LINE__);
print_error( input_filename, err );
return -1;
}
fmtctx->flags |= AVFMT_FLAG_GENPTS;
err = av_find_stream_info( fmtctx );
if ( err < 0 )
{
av_log(NULL, AV_LOG_ERROR, "%d: init input from file error\n", __LINE__);
print_error( input_filename, err );
return -1;
}
if (fmtctx->pb) fmtctx->pb->eof_reached = 0;
dump_format( fmtctx, 0, input_filename, 0 );
....
}
读取packet:
while( 1 )
{
AVPacket *pkt = NULL;
pkt = av_malloc( sizeof(AVPacket) );
ret = av_read_frame(fmtctx, pkt);
送出packet到解码部分:
可以memcpy, 或用LinkList结构处理,如:
push_to_video_packet_queue(pkt);
}
如果是自己的私有输入,比如移动电视的视频输入,代码如下,部分是伪代码:
while( 1 )
{
your_parse_code();
size = your_get_video_data(buf);
pkt = av_mallocz( sizeof(AVPacket) );
x = av_new_packet( pkt, vret);
memcpy( pkt->data, buf, size );
pkt->pts = your_time;
push_to_video_packet_queue(pkt);
}
2. 解码线程实现
解码是个算法大课题,大多只能使用已有的解码库,如libavcodec,下面示例代码:
while ( 1 )
{
AVPicture *picture;
AVPacket *pkt = pop_from_video_packet_queue();
AVFrame *frame = avcodec_alloc_frame();
avcodec_decode_video(video_ctxp, frame, &got_picture, pkt->data, pkt->size);
if ( got_picture )
{
convert_frame_to_picture( picture, frame );
picture->pts = pkt->pts;
push_to_picture_queue( picture );
}
}
音频雷同
3. 输出线程实现
视 频输出要控制FPS,比如25帧每秒的视频,那么每一帧的显示时间要是1/25秒,但把一帧RGB数据写入显存用不了1/25秒的时间,那么就要控制,不 能让25帧的数据在0.1或0.2秒的时间内就显示完了,最简单的实现是在每显示一帧数据后,sleep( 1/fps - 显示用去的时间 )。
音 视频同步这个重要的工作也要在输出线程里完成。以音频为基准同步视频,以视频为基准同步音频,或与一个外部时钟同步,都是可行的方法,但以音频为基准同步 视频是最简单也最有效的方法。音频驱动只要设置好sample rate, sample size 和 channels 后, write 数据就会以此恒定的速度播放, 如果驱动的输出 buffer 满,则 write 就可以等待。
视频:
while( 1 )
{
picture = pop_from_picture_queue();
picture_shot( picture ); /* 截图 */
vo->display( picture );
video_pts = picture->pts;
sync_with_audio(); /* 同步 */
control_fps(); /* FPS */
}
音频:
while( 1 )
{
sample = pop_from_sample_queue();
ao->play( sample );
now_pts = sample->pts;
}
三. 视频输出库
1. SDL (多平台,支持硬件缩放)
SDL(Simple DirectMedia Layer) is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.
其实SDL就是一个中间件,它封装了下层的OpenGL, FrameBuffer, X11, DirectX等给上层提供一个统一的API接口,使用SDL的优点是我们不必再为X11或DirectX分别做个视频输出程序了。
SDL可以直接显示YUV数据和RGB数据,一般解码得到的picture都是YUV420P格式的,不用做YUV2RGB的转换就可以直接显示,主要代码如下:
static int vo_sdl_init(void)
{
....
screen = SDL_SetVideoMode(ww, wh, 0, flags);
overlay = SDL_CreateYUVOverlay(dw, dh, SDL_YV12_OVERLAY, screen);
....
}
static void vo_sdl_display(AVPicture *pict)
{
SDL_Rect rect;
AVPicture p;
SDL_LockYUVOverlay(overlay);
p.data[0] = overlay->pixels[0];
p.data[1] = overlay->pixels[2];
p.data[2] = overlay->pixels[1];
p.linesize[0] = overlay->pitches[0];
p.linesize[1] = overlay->pitches[2];
p.linesize[2] = overlay->pitches[1];
vo_sdl_sws( &p, pict ); /* only do memcpy */
SDL_UnlockYUVOverlay(overlay);
rect.x = dx;
rect.y = dy;
rect.w = dw;
rect.h = dh;
SDL_DisplayYUVOverlay(overlay, &rect);
}
2. DirectX DirectDraw (win32平台,支持硬件缩放)
DirectX是window上使用较多的一种输出,也支持直接YUV或RGB显示,示例代码:
static int vo_dx_init(void)
{
DxCreateWindow();
DxInitDirectDraw();
DxCreatePrimarySurface();
DxCreateOverlay();
DetectImgFormat();
}
static void vo_dx_display(AVPicture *pic)
{
vfmt2rgb(my_pic, pic);
memcpy( g_image, my_pic->data[0], my_pic->linesize[0] * height );
flip_page();
}
3. OpenGL (多平台,支持硬件缩放)
OpenGL是3D游戏库,跨平台,效率高,支持大多数的显示加速,显示2D RGB数据只要使用glDrawPixels函数就足够了,同时禁用一些OpenGL管线操作效率更高,如:
glDisable( GL_SCISSOR_TEST );
glDisable( GL_ALPHA_TEST );
glDisable( GL_DEPTH_TEST );
glDisable( GL_DITHER );
4. X11 (Linux/Unix)
X11 是Unix/Linux系统平台上的基本图形界面库,像普通的GTK,QT等主要都是建立在X11的基础之上。但X11的API接口太多,复杂,很不利于 开发,基本的GUI程序一般都会使用GTK,QT等,不会直接调用X11的API,这里只是为了效率。MPlyaer的libvo里有X11的完整使用代 码,包括全屏等功能。
static void vo_x11_display(AVPicture* pic)
{
vfmt2rgb( my_pic, pic );
Ximg->data = my_pic->data[0];
XPutImage(Xdisplay, Xvowin, Xvogc, Ximg,
0, 0, 0, 0, dw, dh);
XSync(Xdisplay, False);
XSync(Xdisplay, False);
}
5. FrameBuffer (Linux, 无硬件缩放)
FrameBuffer是Linux内核的一部分,提供一个到显存的存取地址的map,但没有任何加速使用。
static void vo_fb_display(AVPicture *pic)
{
int i;
uint8_t *src, *dst = fbctxp->mem;
vfmt2rgb( my_pic, pic );
src = my_pic->data[0];
for ( i = 0; i < fbctxp->dh; i++ )
{
memcpy( dst, src, fbctxp->dw * (fbctxp->varinfo.bits_per_pixel / 8) );
dst += fbctxp->fixinfo.line_length;
src += my_pic->linesize[0];
}
}
四. 音频输出
1. OSS (Open Sound System for Linux)
OSS是Linux下面最简单的音频输出了,直接write就可以。
static int ao_oss_init(void)
{
int i;
dsp = open(dsp_dev, O_WRONLY);
if ( dsp < 0 )
{
av_log(NULL, AV_LOG_ERROR, "open oss: %s\n", strerror(errno));
return -1;
}
i = sample_rate;
ioctl (dsp, SNDCTL_DSP_SPEED, &i);
i = format2oss(sample_fmt);
ioctl(dsp, SNDCTL_DSP_SETFMT, &i);
i = channels;
if ( i > 2 ) i = 2;
ioctl(dsp, SNDCTL_DSP_CHANNELS, &i);
return 0;
}
static void ao_oss_play(AVSample *s)
{
write(dsp, s->data, s->size);
}
2. ALSA (Advanced Linux Sound Architecture)
ALSA做的比较失败,长长的函数名。
static void ao_alsa_play(AVSample *s)
{
int num_frames = s->size / bytes_per_sample;
snd_pcm_sframes_t res = 0;
uint8_t *data = s->data;
if (!alsa_handle)
return ;
if (num_frames == 0)
return ;
rewrite:
res = snd_pcm_writei(alsa_handle, data, num_frames);
if ( res == -EINTR )
goto rewrite;
if ( res < 0 )
{
snd_pcm_prepare(alsa_handle);
goto rewrite;
}
if ( res < num_frames )
{
data += res * bytes_per_sample;
num_frames -= res;
goto rewrite;
}
}
3. DirectSound (WIN32)
MS DirectX的一部分,它的缺点是不如Linux里面的OSS或ALSA那样,在没有sample写入的时候,自动 silent,DirectSound在播放过程中,当没有sample数据送入输出线程时,它总是回放最后0.2或0.5秒的数据。由于只是最近移植 DawnLightPlayer才使用起Windows,不太了解其机制。
static void dsound_play(AVSample *s)
{
int wlen, ret, len = s->size;
uint8_t *data = s->data;
while ( len > 0 )
{
wlen = dsound_getspace();
if ( wlen > len ) wlen = len;
ret = write_buffer(data, wlen);
data += ret;
len -= ret;
usleep(10*1000);
}
}
五. 音视频同步
1. 以音频为基准同步视频
视频输出线程中如下处理:
start_time = now();
....
vo->display( picture );
last_video_pts = picture->pts;
end_time = now();
rest_time = end_time - start_time;
av_diff = last_audio_pts - last_video_pts;
if ( av_diff > 0.2 )
{
if ( av_diff < 0.5 ) rest_time -= rest_time / 4;
else rest_time -= rest_time / 2;
}
else if ( av_diff < -0.2)
{
if ( av_diff > -0.5 ) rest_time += rest_time / 4;
else rest_time += rest_time / 2;
}
if ( rest_time > 0 )
usleep(rest_time);
2. 以视频为基准同步音频
3. 同步于一个外部时钟
六. 截图
截图可以在解码线程做,也可以在输出线程做,见前面的输出线程部分。只要在display前把picture保存起来即可。一般加一些编码,如保存成 PNG 或 JPEG 格式。
1. 使用jpeglib保存成jpeg文件
static void draw_jpeg(AVPicture *pic)
{
char fname[128];
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
int row_stride;
uint8_t *buffer;
if ( !po_status )
return ;
vfmt2rgb24(my_pic, pic);
buffer = my_pic->data[0];
#ifdef __MINGW32__
sprintf(fname, "%s\\DLPShot-%d.jpg", get_save_path(), framenum++);
#else
sprintf(fname, "%s/DLPShot-%d.jpg", get_save_path(), framenum++);
#endif
fp = fopen (fname, "wb");
if (fp == NULL)
{
av_log(NULL, AV_LOG_ERROR, "fopen %s error\n", fname);
return;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
cinfo.write_JFIF_header = TRUE;
cinfo.JFIF_major_version = 1;
cinfo.JFIF_minor_version = 2;
cinfo.density_unit = 1;
cinfo.X_density = jpeg_dpi * width / width;
cinfo.Y_density = jpeg_dpi * height / height;
cinfo.write_Adobe_marker = TRUE;
jpeg_set_quality(&cinfo, jpeg_quality, jpeg_baseline);
cinfo.optimize_coding = jpeg_optimize;
cinfo.smoothing_factor = jpeg_smooth;
if ( jpeg_progressive_mode )
{
jpeg_simple_progression(&cinfo);
}
jpeg_start_compress(&cinfo, TRUE);
row_stride = width * 3;
while (cinfo.next_scanline < height)
{
row_pointer[0] = &buffer[cinfo.next_scanline * row_stride];
(void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
fclose(fp);
jpeg_destroy_compress(&cinfo);
return ;
}
2. 使用libpng保存成png文件
static void draw_png(AVPicture *pic)
{
int k;
png_byte *row_pointers[height]; /* GCC C99 */
if ( init_png() < 0 )
{
av_log(NULL, AV_LOG_ERROR, "draw_png: init png error\n");
return ;
}
vfmt2rgb24( my_pic, pic );
for ( k = 0; k < height; k++ )
row_pointers[k] = my_pic->data[0] + my_pic->linesize[0] * k;
png_write_image(png.png_ptr, row_pointers);
destroy_png();
}
七. YUV RGB 转换
YUV 与RGB的转换和缩放,一般在低端设备上,要有硬件加速来做,否则CPU吃不消。在如今的高端PC上,可以使用软件来做,libswscale库正为此而 来。libswscale针对X86 CPU已经做了优化,如使用 MMX, SSE, 3DNOW 等 CPU 相关的多媒体指令。
static int vfmt2rgb(AVPicture *dst, AVPicture *src)
{
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getCachedContext(img_convert_ctx,
width, height, src_pic_fmt,
width, height, my_pic_fmt, SWS_X, NULL, NULL, NULL);
sws_scale(img_convert_ctx, src->data, src->linesize,
0, width, dst->data, dst->linesize);
return 0;
}
比如从 YUV420P 到 RGB24 的转换,只要设置
src_pic_fmt = PIX_FMT_YUV420P ;
my_pic_fmt = PIX_FMT_RGB24 ;
八. 软件缩放
软件缩放就可以使用上述的 libswscale 库,调用代码基本一样,只是改一下目标picture的width和height,如放大两倍:
static int zoom_2(AVPicture *dst, AVPicture *src)
{
static struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getCachedContext(img_convert_ctx,
width, height, src_pic_fmt,
width*2, height*2, my_pic_fmt, SWS_X, NULL, NULL, NULL);
sws_scale(img_convert_ctx, src->data, src->linesize,
0, width*2, dst->data, dst->linesize);
return 0;
}
linux下播放器设计和开发的更多相关文章
- 搭建rtmp直播流服务之4:videojs和ckPlayer开源播放器二次开发(播放rtmp、hls直播流及普通视频)
前面几章讲解了使用 nginx-rtmp搭建直播流媒体服务器; ffmpeg推流到nginx-rtmp服务器; java通过命令行调用ffmpeg实现推流服务; 从数据源获取,到使用ffmpeg推流, ...
- Android VLC播放器二次开发3——音乐播放(歌曲列表+歌词同步滚动)
今天讲一下对VLC播放器音频播放功能进行二次开发,讲解如何改造音乐播放相关功能.最近一直在忙着优化视频解码部分代码,因为我的视频播放器需要在一台主频比较低的机器上跑(800M主频),所以视频解码能力受 ...
- Android VLC播放器二次开发2——CPU类型检查+界面初始化
上一篇讲了VLC整个程序的模块划分和界面主要使用的技术,今天分析一下VLC程序初始化过程,主要是初始化界面.加载解码库的操作.今天主要分析一下org.videolan.vlc.gui.MainActi ...
- windows/Linux下设置ASP.Net Core开发环境并部署应用
10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...
- linux下播放组播流出现setsockopt:No such device错误
在linux下播放组播流出现setsockopt:No such device错误是因为多播IP没有add路由表里面 可以采用如下命令完成: root@android:/ # busybox rout ...
- ubuntu下一款有点感觉的 linux音乐播放器 clementine(小橘子))
https://www.clementine-player.org/ 在linux听音乐的感觉确实不是很好,音乐播放器很多.但是仅仅只是数量上的优势,在确实不是很好用.自带的rhythmbox确实很占 ...
- 基于Basys2开发板的简易电子琴和音乐播放器设计
背景:华中科技大学 电测综合实验 主要功能:Basys2开发板外接一个扬声器(或无源蜂鸣器也可)实现电子琴和音乐播放器的功能.其中由于开发板上只有4个按键,所以电子琴功能只做了4个音调,分别对应于4个 ...
- Android VLC播放器二次开发1——程序结构分析
最近因为一个新项目需要一个多媒体播放器,所以需要做个视频.音频.图片方面的播放器.也查阅了不少这方面的资料,如果要从头做一个播放器工作量太大了,而且难度也很大.所以最后选择了VLC作为基础,进行二次开 ...
- [Linux][Madplay播放器移植mini2440(ARM9)]
Madplay移植到mini2440全过程详解 madplay交叉编译 交叉编译器:arm-linux-gcc 3.4.1PC环境:RedHat-6 注意:最好在root权限下执行以下移植,否则在ma ...
随机推荐
- Touching segments(数据结构)
题目链接 Problem Statement Your Maths professor is a very nice guy, but he sometimes comes up with not s ...
- vue 编写插件
1. 将插件逻辑封装成一个对象 最后在install编写业务代码暴露给Vue对象 好处: 可以添加任意参数在这个对象上 方便将install函数封装的更加精简 可拓展性也比较高 2. 将所有 ...
- 探索SpringBoot中的SpringMVC
spring boot就是一个大框架里面包含了许许多多的东西,其中spring就是最核心的内容之一,当然就包含spring mvc.spring mvc 是只是spring 处理web层请求的一个模块 ...
- flex 手册摘要
个人学习 摘抄翻译 http://www.cs.princeton.edu/~appel/modern/c/software/flex/flex.html#SEC1 程序的格式 分成显示的三部分 由% ...
- UOJ#80. 二分图最大权匹配 模板
#80. 二分图最大权匹配 描述 提交 自定义测试 从前一个和谐的班级,有 nlnl 个是男生,有 nrnr 个是女生.编号分别为 1,…,nl1,…,nl 和 1,…,nr1,…,nr. 有若干个这 ...
- 移动端页面输入法挡住input输入框的解决方法
1,宽高用了百分比或者vw/vh布局的,input输入框的最外层父容器的可用JS动态设置为当前窗口的宽高(防止输入法的弹出令页面变形) 2,最外层父容器用了fixed定位的,不要用top:0;要用bo ...
- HDU4578 Transformation (多操作线段树)
传送门 终于过了这道题.. 要注意标记之间的影响,和add操作时更新求和的顺序. same 区间每个数设置为x标记 mult 区间每个数乘x标记 add 区间每个数加x标记 ①:当打same标记时 ...
- org.hibernate.service.UnknownServiceException: Unknown service requested [org.hibernate.engine.jdbc.connections.spi.ConnectionProvider]
蜜汁问题? 完全不知道怎么解决啊
- Spring2.5依靠注入的方式有三种
Spring2.5依靠注入的方式有三种: 1.通过setter方法注入: 2.通过构造方法注入: 3.通过注解进行注入: 第一种方式:通过setter方法注入 Java代码 package com.t ...
- python 处理缺失数据