<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:黑体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimHei; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"/@黑体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:1 135135232 16 0 262144 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:宋体; mso-font-kerning:1.0pt;} h1 {mso-style-next:正文; margin-top:17.0pt; margin-right:0cm; margin-bottom:16.5pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:240%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:1; font-size:22.0pt; font-family:"Times New Roman"; mso-font-kerning:22.0pt;} h2 {mso-style-next:正文; margin-top:13.0pt; margin-right:0cm; margin-bottom:13.0pt; margin-left:0cm; text-align:justify; text-justify:inter-ideograph; line-height:173%; mso-pagination:lines-together; page-break-after:avoid; mso-outline-level:2; font-size:16.0pt; font-family:Arial; mso-fareast-font-family:黑体; mso-bidi-font-family:"Times New Roman"; mso-font-kerning:1.0pt;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:734666112; mso-list-type:hybrid; mso-list-template-ids:-1866804122 1008655198 248933464 67698715 67698703 67698713 67698715 67698703 67698713 67698715;} @list l0:level1 {mso-level-text:%1,; mso-level-tab-stop:18.0pt; mso-level-number-position:left; margin-left:18.0pt; text-indent:-18.0pt;} @list l0:level2 {mso-level-number-format:alpha-lower; mso-level-text:"%2/)"; mso-level-tab-stop:39.0pt; mso-level-number-position:left; margin-left:39.0pt; text-indent:-18.0pt;} @list l1 {mso-list-id:1779637185; mso-list-type:hybrid; mso-list-template-ids:-840918712 838602636 67698713 67698715 67698703 67698713 67698715 67698703 67698713 67698715;} @list l1:level1 {mso-level-text:%1,; mso-level-tab-stop:39.0pt; mso-level-number-position:left; margin-left:39.0pt; text-indent:-18.0pt;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} -->
Marvel Pxa310 Video 横屏竖屏切换
前言
最近这几天一直在折腾这个横屏切换,公司买了一个破屏,只支持 480 的刷屏,而不支持 800 的刷屏,也就是说我们的屏幕内容是 800X480 的,而屏呢只支持 480X800 ,所以有了旋转这个需求,就是在播放视频的时候,必须要从 800X480 转到 480X800 ;
思路
Marvel 的 pxa310 没有自己的 DSP 不过有一个图形加速,也就是 M2D ,这玩意的意思就是 memory to device ,实际上,多数用法都是从内存的一个地方按照一定的算法移动到 framebuffer 或者一个中间的内存。基于这个特点,我的想法就是把 mplayer 解码出来的数据利用 M2D 传输到 framebuffer ,这样可以节省一点 CPU 的资源。
步骤
想法其实很简单,但是实际行动中就会有很多的问题。
1, 理解 Mplayer 的架构;
它的视频输出架构大约是这样的,根据传入的参数 vo XXX 调用 init_best_video_out 函数,这个函数遍历支持的输出 driver ,而这些支持的 driver 是通过一个数组 video_out_drivers 来保存的,不同的输出 driver 只要实现
typedef struct vo_functions_s
{
const vo_info_t *info;
/*
* Preinitializes driver (real INITIALIZATION)
* arg - currently it's vo_subdevice
* returns: zero on successful initialization, non-zero on error.
*/
int (*preinit)(const char *arg);
/*
* Initialize (means CONFIGURE) the display driver.
* params:
* width,height: image source size
* d_width,d_height: size of the requested window size, just a hint
* fullscreen: flag, 0=windowd 1=fullscreen, just a hint
* title: window title, if available
* format: fourcc of pixel format
* returns : zero on successful initialization, non-zero on error.
*/
int (*config)(uint32_t width, uint32_t height, uint32_t d_width,
uint32_t d_height, uint32_t fullscreen, char *title,
uint32_t format);
/*
* Control interface
*/
int (*control)(uint32_t request, void *data, ...);
/*
* Display a new RGB/BGR frame of the video to the screen.
* params:
* src[0] - pointer to the image
*/
int (*draw_frame)(uint8_t *src[]);
/*
* Draw a planar YUV slice to the buffer:
* params:
* src[3] = source image planes (Y,U,V)
* stride[3] = source image planes line widths (in bytes)
* w,h = width*height of area to be copied (in Y pixels)
* x,y = position at the destination image (in Y pixels)
*/
int (*draw_slice)(uint8_t *src[], int stride[], int w,int h, int x,int y);
/*
* Draws OSD to the screen buffer
*/
void (*draw_osd)(void);
/*
* Blit/Flip buffer to the screen. Must be called after each frame!
*/
void (*flip_page)(void);
/*
* This func is called after every frames to handle keyboard and
* other events. It's called in PAUSE mode too!
*/
void (*check_events)(void);
/*
* Closes driver. Should restore the original state of the system.
*/
void (*uninit)(void);
}
这个结构体里面的函数指针就可以了。
我们用的是 overlay2 也就是 /dev/fb2 的输出,所以我们实现相应的 preinit , config , draw_slice 等等函数;而这里最关键的是 draw_slice , 因为我们的视频解码出来的数据是 YUV420 的,所以我们需要实现 draw_slice 否则你就要实现 draw_frame 函数;
2, 理解 draw_slice 相关函数;
对于 set_fb_overlay_region 函数,它会在 config 函数被调用的时候执行,它所做的事就是:
a) 设置视频输出格式为 YV12, 也就是 YUV420 ;
fb_vinfo.nonstd = (OV2_FMT_YUV420 << 20) ;
ioctl(fb_dev_fd , FBIOPUT_VSCREENINFO, &fb_vinfo)
这里 fb_dev_fd 是 /dev/fb2 的文件描述符,而 fb_vinfo 里面除了输出格式外还包括了输出的宽度和高度,这里必须要满足驱动的要求,比如我就需要修改这里,因为从硬件的角度来说,屏幕已经是 480X800 了,所以我必须要设置成 480X800 ,虽然,从上层的角度来说需要看成 800X480 (否则内存布局会出错);
b) 调用 ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo) 取得 fb2 相关的信息;
比如:内存大小,每行的像素个数,每个像素所占的字节数;这里我需要修改的是把取得的每行的像素个数强制设成 800 ,因为对上层来说看到的必须是 800X480 ,另外非常重要的是取得 Y , U , V 各自在 framebuffer 里面的位移和长度,比如我板子上结果就是这样, 0,384000,480000, 长度是 384000 , 96000 , 96000 ;这几个数据的作用在于控制了数据写入的目标地址;
c) 调用 mmap 映射 fb2 的 framebuffer 空间;
d )调用 m2d_create_buffer 来创建 M2D 所需的目标地址空间;
这一步非常非常的重要,因为这一步让它了解了目的空间的几何架构,也便于它在转化的时候控制换行等:
dst_buf[0] = m2d_create_buffer(context, oh, ow, GCU_PXLFMT_INDEXED_8, frame_buffer + y_off);
dst_buf[1] = m2d_create_buffer(context, (oh / 2), (ow / 2), GCU_PXLFMT_INDEXED_8, frame_buffer + cb_off);
dst_buf[2] = m2d_create_buffer(context, (oh / 2), (ow / 2), GCU_PXLFMT_INDEXED_8, frame_buffer + cr_off);
这里的 oh 就是 480 , ow 就是 800 ,而 frame_buffer 就是起始地址,而 y_off,cb_off,cr_off 就是分别的 yuv 的位移 0,384000,480000;
对于 draw_line 函数我只关心和视频输出相关的部分,其它部分先略去,它里面简单的说做了三件事;
a) 分配一个物理连续的内存空间( m2d 要求源和目的都必须是连续的空间,目的这里就是 framebuffer );
b) 把视频解码出来的数据 copy 到刚分配的连续空间里面;
c) 利用 M2D 对源进行放大,或者缩小的操作后把数据放到 framebuffer 里面去;
这里并没有实现旋转,所以需要添加旋转的功能进去;
本来打算在上面的基础上改,但是考虑到它只实现了放大缩小的功能,而并没有实现部分放大,部分缩小的功能,所以,我决定还是重新写一套硬件加速的输出;
3, 新的输出实现;
1 ,调用 m2d_create_buffer 来创建 m2d 所需的源 buffer ,这里还需要设置旋转的度数为 90 度,对于 m2d 的参数来说就是 1 ;
2 ,调用 gcu_parse 根据源的宽度和高度和目的地决定需要放大,缩小还是旋转等?并由此创建 m2d 所需要的操作子,所谓的操作子也就是关于源和目的地的信息,比如 src 的起始地址,宽度,高度,目的地的起始地址,宽度,高度等, m2d 有了这些信息才能正确进行要求的操作,这里比较关键的是必须要设置好 dst 的宽度和高度,分别为 480 和 800 ;
gcu->rotate_times = gcu_create_op(gcu->rot_opr, gcu->src_width, gcu->src_height, gcu->dst_height, gcu->dst_width, 0);
这里非常非常的关键 : 参数的传递一定是按照 src 宽度,高度,目的高度,宽度;比如对于 800X480 的旋转就是 800,480,800,480 ;原因是创建的旋转 buffer 是这么创建的:
gcu->rot_buf[i] = m2d_alloc_buffer(gcu->context, gcu->src_height[i], gcu->src_width[i], GCU_PXLFMT_INDEXED_8);
这里的宽度传入的是 src 的高度,所以对于 800X480 的源它的高度就是 480 ,于是传入的参数就是 (480,800); 也就是说对于中间 buffer 它的几何尺寸就是 480X800 的;
gcu->resize_times = gcu_create_op(gcu->opr, gcu->dst_height, gcu->dst_width, gcu->dst_width, gcu->dst_height, gcu->rotate_degree);
这里的 rot_opr 就是将要被设置的操作子,
3 ,一切就绪后就可以调用 gcu_process 来进行命令的构造了;
对于旋转拉伸的设置就是这样了:
/* 设置 m2d 的 src*/
m2d_set_srcbuf0(gcu->context, gcu->src_buf[i]);
/* 设置 m2d 的 dst ,这里是一个中间 buffer*/
m2d_set_dstbuf2(gcu->context, gcu->rot_buf[i]);
if(!gcu->rotate_times){
/* 如果源宽度小于 497 就只需要旋转一次 */
m2d_rotate_blt(gcu->context, &gcu->rot_opr[i][0], gcu->rotate_degree);
}else{
/* 如果源宽大大于 497 就要旋转两次了 , 里面的操作子在上一步的时候就设置好了 */
m2d_rotate_blt(gcu->context, &gcu->rot_opr[i][0], gcu->rotate_degree);
m2d_rotate_blt(gcu->context, &gcu->rot_opr[i][1], gcu->rotate_degree);
}
//stretch
/* 再次设置源和 dst ,注意这里的源是 rot_buf 也就是经过旋转的数据;
这里的目的是 framebuffer*/
m2d_set_srcbuf0(gcu->context, gcu->rot_buf[i]);
m2d_set_dstbuf2(gcu->context, gcu->dst_buf[i]);
if(!gcu->resize_times){/* 如果宽度小于 497, 因为已经是 480X800 得了,所以不需要操作两次,实际上如果源是 800X480 的,是不需要再拉伸了,这里只是考虑普通的情况源是小于 800X480 的 */
m2d_stretch_blt(gcu->context, &gcu->opr[i][0], gcu->dst_width[i], gcu->dst_height[i]);
}else{
m2d_stretch_blt(gcu->context, &gcu->opr[i][0], gcu->dst_width[i]>>1, gcu->dst_height[i]);
m2d_stretch_blt(gcu->context, &gcu->opr[i][1], gcu->dst_width[i]>>1, gcu->dst_height[i]);
}
4 ,最后把命令发送到驱动;
m2d_submit(gcu->context); 这个命令才最终 append 到驱动的缓冲,等待驱动取出命令并执行;
至此,基本新的输出架构实现就完了;
总结
主要的难点就是如何填这些参数,特别是旋转的宽度和高度在大于 497 的时候的设置;
理解 M2D 的本质,它只是一个搬运工具,并且自带搬运算法的工具,出问题都是在 src 写到 framebuffer 的时候出问题了,直接把 src 的内存 copy 到 fb2 的 framebuffer 本质上也是可以工作的,比如:
/*added for test*/
unsigned char *test=m2d_buff_addr(dst_buf[0]);
unsigned char *test_u=m2d_buff_addr(dst_buf[1]);
unsigned char *test_v=m2d_buff_addr(dst_buf[2]);
for(i = 0; i < h; i ++)
{
/*added for test*/
memcpy(test,pSrcY,w);
test+=iYWidth;
/*above is added by wf*/
}
for(i = 0; i < iCbCrHeight; i ++)
{
/*added by wf for test*/
memcpy(test_u,pSrcCb,iCbCrWidth);
memcpy(test_v,pSrcCr,iCbCrWidth);
test_u+=iCbCrWidth;
test_v+=iCbCrWidth;
/*above is added by wf*/
}
这样都是可以显示的,只不过没有旋转也没有拉伸。
问答
1, 出现黑屏了,什么都看不到,什么原因?
有可能是 fb2 没有 enable ,有可能是设置的 m2d 的参数不对,可以通过把 libm2d.so 加上打印信息看看,是不是参数设置不对,特别是宽度和高度;
2, 出现乱七八糟的东西,什么问题?
有可能是输出的格式的设置的问题,对于视频需要设置成 YUV420 ,也就意味着输入的源数据一定是要 YUV 格式的;创建 buffer 的时候,比如 m2d_create_buffer(context, oh, ow, GCU_PXLFMT_INDEXED_8 , frame_buffer + y_off); 这个参数 GCU_PXLFMT_INDEXED_8 一定要填好。 另外必须要仔细考虑 src 的内存布局和目标的内存布局;
3, 出现重叠,就是一个图像重复几次显示在 LCD 上,什么原因?
估计是宽度和高度设置有问题,也就是在创建 m2d 的目的 buffer 的时候参数有问题;
备注
作者: wylhistory;
联系方式: wylhistory@gmail.com