帧率转换原理及读写指针实现
1.为什么要做帧率转换
因为视频信号在传输过程中,输入和输出的帧率可能不同,例如输出要求固定是60帧/s,而输入是30帧/s,50帧/s,或是75帧/s等等,为了保证图像仍然连续传输,无撕裂现象,那么就需要做帧率转换。
注:
(1)帧率是指每秒钟传输图像的帧数。
(2)对于视频输出,例如显卡输出,通常用刷新率表示,单位Hz,不一定等同于帧率。
(3)撕裂现象是指在高速运动画面中,图像上半部和下半部位置错开或内容不同现象。
2.帧率转换原理
通常使用SDRAM,DDR SDRAM等存储设备来完成帧率转换。即一边把图像写入帧缓存,一边从帧缓存中读取数据。
为保证输出图像无撕裂现象,读操作和写操作必须在不同的帧缓存中,那么至少需要2帧缓存以上,一帧缓存用来读,一帧缓存用来写。在输入输出帧率不相等的情况下,如使用2帧缓存,一定会出现撕裂现象。因此至少需使用3帧缓存才能保证无撕裂现象。
在输入输出帧率相等的情况下,使用2帧缓存就可以正常显示,甚至可以使用1帧缓存。
在输入输出帧率不相等的情况下,往往就需要使用3帧缓存来实现。
(1)如果输入帧率大于输出帧率,就采用丢帧的方式降低帧率。
(2)如果输入帧率小于输出帧率,就采用重复帧的方式提高帧率。
3.FPGA帧率转换实现
使用3帧缓存就是有1帧在写,1帧在读,还要1帧中间过渡,可以用读指针,写指针来表示,读写指针分别指向帧缓存的起始地址,关键就是控制好读写指针的顺序。用一句通俗的话描述就是“谁快谁等”。
图1 写比读快
图2 读比写快
如图,3帧缓存用3个标有数字的方框表示,读指针用rd_pt表示,写指针用wr_pt表示。
(1)读写指针均是按照0,1,2的顺序循环;
(2)当1帧数据读完后,rd_pt就指向下一帧;
(3)当1帧数据写完后,wr_pt就指向下一帧
(4)初始复位后,读写指针都从第0帧开始;
(5)当写比读快时,会通过写入新帧覆盖前一帧方式调节,即输入帧率大于输出帧率,写指针比读指针切换快,如图1,当wr_pt写完要由2切换到0时,发现rd_pt还在0,那么wr_pt就停留在2,数据仍然写入第2帧缓存,把之前写入的数据覆盖掉。
(6)当读比写快时,会通过重复读帧方式调节,即输出帧率大于输入帧率,读指针比写指针切换快,如图2,当rd_pt读完要由0切换到1时,发现wr_pt还在1,那么rd_pt就停留在0,仍然从第0帧读数据,即把第0帧的数据重复1遍。
(7)当读与写速率相同时,即输出帧率等于输入帧率,那么读指针会一直跟在写指针后面,以相同的速率切换。
参考代码:
reg [1:0] wr_pt;
reg [1:0] rd_pt;
//写指针跳转
always@(posedge dma_clk or negedge rst_n)
begin
if(!rst_n)
wr_pt <= 2'd0;
else
begin
if(vin_vs_falling == 1'b1) // vin_vs_falling输入一帧结束标志
begin
case (wr_pt)
2'd0 : wr_pt <= (rd_pt == 2'd1) ? 2'd0 : 2'd1;
2'd1 : wr_pt <= (rd_pt == 2'd2) ? 2'd1 : 2'd2;
2'd2 : wr_pt <= (rd_pt == 2'd2) ? 2'd2 : 2'd0;
2'd3 : wr_pt <= 2'd0;
default : wr_pt <= 2'd0;
endcase
end
end
end
//读指针跳转
always@(posedge dma_clk or negedge rst_n)
begin
if(!rst_n)
rd_pt <= 2'd0;
else
begin
if(vout_vs_falling == 1'b1) //vout_vs_falling输出一帧结束标志
begin
case (rd_pt)
2'd0 : rd_pt <= (wr_pt == 2'd1) ? 2'd0 : 2'd1;
2'd1 : rd_pt <= (wr_pt == 2'd2) ? 2'd1 : 2'd2;
2'd2 : rd_pt <= (wr_pt == 2'd0) ? 2'd2 : 2'd0;
2'd3 : rd_pt <= 2'd0;
default : rd_pt <= 2'd0;
endcase
end
end
end
通过以上方式实现对读写指针的控制,同时也实现了帧率的转换控制。
至于读写指针和每帧图像起始地址,即基地址对应关系,可以用如下方式实现。
//基地址映射
always@(posedge dma_clk or negedge rst_n)
begin
if(!rst_n)
wr_baseaddr <= 'd0;
else
begin
case (wr_pt)
2'd0 : wr_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
2'd1 : wr_baseaddr <= base_addr_i + IMAGE_SIZE ;//IMAGE_SIZE * 1
2'd2 : wr_baseaddr <= base_addr_i + (IMAGE_SIZE<<1);//IMAGE_SIZE * 2
2'd3 : wr_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
default : wr_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
endcase
end
end
always@(posedge dma_clk or negedge rst_n)
begin
if(!rst_n)
rd_baseaddr <= 'd0;
else
begin
case (rd_pt)
2'd0 : rd_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
2'd1 : rd_baseaddr <= base_addr_i + IMAGE_SIZE ;//IMAGE_SIZE * 1
2'd2 : rd_baseaddr <= base_addr_i + (IMAGE_SIZE<<1);//IMAGE_SIZE * 2
2'd3 : rd_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
default : rd_baseaddr <= base_addr_i ;//IMAGE_SIZE * 0
endcase
end
end