前言 前面我们掌上游戏机之一完成了USB手柄输入设备按键的采集,现在我们来实现显示部分。由于开发板默认没有带LCD屏,所以曲线救国,我们通过网口使用RTP实时推流H264到PC上显示,使用电脑作为显示器。 这一篇我们来实现通过上述方式模拟的LCD的任意写点。
过程 整体过程如下,画点实际就是写RGB显存, 需要刷新显示时将RGB显存转为YUV,然后通过x264编码为H264裸流,通过RTP推流到PC端,PC端通过VLC播放器显示。 所以需要三个模块,分别是 Dis显示模块,负责实现写点接口,RGB转YUV接口 H264编码模块,负责将YUV缓存转为H264裸流 Rtp模块,负责将H264裸流实时推流到PC端 分别对应以下源码 dis.c
#include
#include
#include
#include
#include
#include
#include "x264.h"
#include "rtp.h"
#include "dis.h"
uint8_t s_rgb_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3]; /**< 显示RGB缓存 */
uint8_t s_yuv_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3/2]; /**< 显示I420缓存 */
uint8_t s_h264_buffer_au8[DIS_WIDTH*DIS_HEIGHT*3*10]; /**< 显示I420缓存 */
uint8_t s_flush_u8 = 0;
static void rgb_to_yuv420p(uint8_t* yuv420p, uint8_t* rgb, int width, int height)
{
if (yuv420p == NULL || rgb == NULL)
return;
int frameSize = width*height;
int chromaSize = frameSize / 4;
int yIndex = 0;
int uIndex = frameSize;
int vIndex = frameSize + chromaSize;
int R, G, B, Y, U, V;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
B = rgb[(i * width + j) * 3 + 0];
G = rgb[(i * width + j) * 3 + 1];
R = rgb[(i * width + j) * 3 + 2];
//RGB to YUV
Y = ((66 * R + 129 * G + 25 * B + 128) >> 8) + 16;
U = ((-38 * R - 74 * G + 112 * B + 128) >> 8) + 128;
V = ((112 * R - 94 * G - 18 * B + 128) >> 8) + 128;
yuv420p[yIndex++] = (unsigned char)((Y < 0) ? 0 : ((Y > 255) ? 255 : Y));
if (i % 2 == 0 && j % 2 == 0)
{
yuv420p[uIndex++] = (unsigned char)((U < 0) ? 0 : ((U > 255) ? 255 : U));
yuv420p[vIndex++] = (unsigned char)((V < 0) ? 0 : ((V > 255) ? 255 : V));
}
}
}
}
static void* dis_llopback(void* arg)
{
static int s_h264_len_i = -1;
while(1)
{
if(s_flush_u8 != 0)
{
s_flush_u8 = 0;
rgb_to_yuv420p(s_yuv_buffer_au8, s_rgb_buffer_au8, DIS_WIDTH, DIS_HEIGHT);
memset(s_h264_buffer_au8,0,sizeof(s_h264_buffer_au8));
s_h264_len_i = x264_yuv420p2h264(s_yuv_buffer_au8,s_h264_buffer_au8,sizeof(s_h264_buffer_au8),DIS_WIDTH,DIS_HEIGHT);
printf("flush frame len=%dn",s_h264_len_i);
}
if(s_h264_len_i > 0)
{
uint8_t* tmp = malloc(s_h264_len_i+1);
memcpy(tmp+1,s_h264_buffer_au8,s_h264_len_i); /**< H264 FU-A分包时会往前写一个字节,改写原始值,所以这里需要临时开辟空间用来发送 */
rtp_send_h264(tmp+1, s_h264_len_i);
free(tmp);
/* 帧率控制 */
usleep(100);
}
}
}
void dis_set_pixel(int x,int y, uint8_t r,uint8_t g,uint8_t b)
{
s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 0] = b; /**< r和b反 */
s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 1] = g;
s_rgb_buffer_au8[(y * DIS_WIDTH + x) * 3 + 2] = r;
}
void dis_flush(void)
{
s_flush_u8 = 1;
}
int dis_init(char* ip,int port)
{
rtp_h264_init(ip,port);
pthread_t id;
/* 创建函数线程,并且指定函数线程要执行的函数 */
pthread_create(&id,NULL,dis_llopback,0);
}
dis.h
#ifndef DIS_H
#define DIS_H
#define DIS_WIDTH 1024
#define DIS_HEIGHT 1024
void dis_set_pixel(int x,int y, uint8_t r,uint8_t g,uint8_t b);
int dis_init(char* ip,int port);
void dis_flush(void);
#endif
rtp.c
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_RTP_PKT_LENGTH (1400)
#define TS_INC (90000/25)
static RtpSession *session;
static uint32_t user_ts=0;
static void forward_h264frame(RtpSession * session, uint8_t * buffer, int len,uint32_t userts)
{
unsigned char NALU = buffer[0];
uint32_t valid_len = len;
if (len <= MAX_RTP_PKT_LENGTH) {
/*
RTP Header + NALU Header + NALU Data; (不包括startcode)
传入的buffer从NALU Header开始
RTP Header由rtp_session_send_with_ts添加
*/
rtp_session_send_with_ts(session, buffer,len,userts);
//printf("send slice len=%drn",len);
} else {
/* FU-A *
* FU indicator FU header DATA
* ^
* NALU Header NALU Data
*/
int packetnum = valid_len / (MAX_RTP_PKT_LENGTH-1); /* FU-A分片要多加一个字节 */
if (valid_len % (MAX_RTP_PKT_LENGTH-1) != 0) {
packetnum += 1;
}
int i = 0;
int pos = -1;
while (i < packetnum) {
if (i < packetnum - 1) {
buffer[pos] = (NALU & 0x60) | 28;
buffer[pos+1] = (NALU & 0x1f);
if (0 == i) {
buffer[pos+1] |= 0x80;
}
rtp_session_send_with_ts(session, &buffer[pos],
MAX_RTP_PKT_LENGTH, userts);
//printf("send slice offset=%d,len=%drn",pos,MAX_RTP_PKT_LENGTH);
} else {
int iSendLen = len - pos - 1;
buffer[pos] = (NALU & 0x60) | 28;
buffer[pos+1] = (NALU & 0x1f);
buffer[pos+1] |= 0x40;
rtp_session_send_with_ts(session, &buffer[pos],
iSendLen, userts);
//printf("send slice offset=%d,len=%drn",pos,iSendLen);
}
pos += MAX_RTP_PKT_LENGTH-2; /*有效长度MAX_RTP_PKT_LENGTH-1 在往前预留一个字节存储FU indicator */
++i;
}
}
}
static uint32_t h264isslice(uint8_t* buffer)
{
if((buffer[0]==0)&&(buffer[1]==0))
{
if(buffer[2]==1)
{
return 1;
}
else if(buffer[2]==0)
{
if(buffer[3]==1)
{
return 2;
}
else
{
return 0;
}
}
else
{
return 0;
}
}
else
{
return 0;
}
}
static uint32_t h264getslicelen(uint8_t* buffer,uint32_t len,uint8_t* headlen)
{
uint8_t type;
uint32_t offset = 0;
uint32_t isslice = 0;
isslice = h264isslice(buffer);
if(isslice==1)
{
offset=3;
*headlen = 3;
}
else if(isslice==2)
{
offset=4;
*headlen = 4;
}
else
{
return 0;
}
uint32_t i;
for(i=offset;i
{
if((buffer[i+0]==0)&&(buffer[i+1]==0))
{
if(buffer[i+2]==1)
{
return i;
}
else if(buffer[i+2]==0)
{
if(buffer[i+3]==1)
{
return i;
}
}
}
}
return i;
}
int rtp_h264_init(char* ip,int port)
{
int i;
char *ssrc;
int clockslide=0;
int jitter=0;
ortp_init();
ortp_scheduler_init();
ortp_set_log_level_mask(ORTP_ERROR);
session=rtp_session_new(RTP_SESSION_SENDONLY);
rtp_session_set_scheduling_mode(session,1);
rtp_session_set_blocking_mode(session,1);
rtp_session_set_connected_mode(session,TRUE);
rtp_session_set_remote_addr(session,ip,port);
rtp_session_set_payload_type(session,96);
ssrc=getenv("SSRC");
if (ssrc!=NULL)
{
printf("using SSRC=%i.n",atoi(ssrc));
rtp_session_set_ssrc(session,atoi(ssrc));
printf("rtp_session_set_ssrc %sn",ssrc);
}
}
int rtp_h264_deinit(void)
{
rtp_session_destroy(session);
ortp_exit();
ortp_global_stats_display();
}
int rtp_send_h264(uint8_t* src, uint32_t len)
{
uint8_t* src_buff = src;
uint32_t framelen = 0;
uint32_t s_sendlen_u32 = 0;
uint8_t headlen;
while(1)
{
if((framelen = h264getslicelen(src_buff,len-s_sendlen_u32,&headlen)) == 0)
{
/* 无有效数据 */
break;
}
forward_h264frame(session,src_buff+headlen,framelen-headlen,user_ts);
user_ts+=framelen-headlen;
src_buff += framelen;
s_sendlen_u32+=framelen;
if(s_sendlen_u32 >= len)
{
break;
}
}
return s_sendlen_u32;
}
rtp.h
#ifndef RTP_H
#define RTP_H
int rtp_h264_init(char* ip,int port);
int rtp_h264_deinit(void);
int rtp_send_h264(uint8_t* src, uint32_t len);
#endif
x264.c
#include
#include
#include
#include
#include
#include
int x264_yuv420p2h264(uint8_t* src,uint8_t* dst, uint32_t dstlen,uint32_t width,uint32_t height)
{
x264_param_t param;
x264_picture_t pic;
x264_picture_t pic_out;
x264_t *h;
x264_nal_t *nal;
int i_frame = 0;
int i_frame_size;
int i_nal;
int outlen = 0;
/* Get default params for preset/tuning */
if( x264_param_default_preset( ¶m, "medium", NULL ) < 0 )
{
printf("x264_param_default_preset errn");
return -1;
}
/* Configure non-default params */
param.i_bitdepth = 8;
param.i_csp = X264_CSP_I420;
param.i_width = width;
param.i_height = height;
param.b_vfr_input = 0;
param.b_repeat_headers = 1;
param.b_annexb = 1;
param.i_log_level = X264_LOG_ERROR;
/* Apply profile restrictions. */
if( x264_param_apply_profile( ¶m, "high" ) < 0 )
{
printf("x264_param_apply_profile errn");
return -1;
}
if( x264_picture_alloc( &pic, param.i_csp, param.i_width, param.i_height ) < 0 )
{
printf("x264_picture_alloc errn");
return -1;
}
h = x264_encoder_open( ¶m );
if( !h )
{
x264_picture_clean( &pic );
printf("x264_encoder_open errn");
return -1;
}
int luma_size = width * height;
int chroma_size = luma_size / 4;
i_frame++;
memcpy(pic.img.plane[0],src,luma_size);
memcpy(pic.img.plane[1],src+luma_size,chroma_size);
memcpy(pic.img.plane[2],src+luma_size+chroma_size,chroma_size);
pic.i_pts = i_frame;
i_frame_size = x264_encoder_encode( h, &nal, &i_nal, &pic, &pic_out );
if( i_frame_size < 0 )
{
printf("x264_encoder_encode err %dn",i_frame_size);
x264_encoder_close( h );
x264_picture_clean( &pic );
return -1;
}
else if( i_frame_size )
{
printf("x264_encoder_encode ok %dn",i_frame_size);
if((outlen + i_frame_size) <= dstlen)
{
memcpy(dst+outlen,nal->p_payload,i_frame_size);
outlen += i_frame_size;
}
else
{
printf("outlen=%d,i_frame_size=%d,>%dn",outlen,i_frame_size,dstlen);
}
}
/* Flush delayed frames */
while( x264_encoder_delayed_frames( h ) )
{
i_frame_size = x264_encoder_encode( h, &nal, &i_nal, NULL, &pic_out );
if( i_frame_size < 0 )
{
printf("x264_encoder_encode err %dn",i_frame_size);
x264_encoder_close( h );
x264_picture_clean( &pic );
return -1;
}
else if( i_frame_size )
{
if((outlen + i_frame_size) <= dstlen)
{
memcpy(dst+outlen,nal->p_payload,i_frame_size);
outlen += i_frame_size;
}
else
{
printf("outlen=%d,i_frame_size=%d,>%dn",outlen,i_frame_size,dstlen);
}
}
}
x264_encoder_close( h );
x264_picture_clean( &pic );
return outlen;
}
x264.h
#ifndef X264_H
#define X264_H
int x264_yuv420p2h264(uint8_t* src,uint8_t* dst, uint32_t dstlen,uint32_t width,uint32_t height);
#endif
测试main.c中代码,实现RGB刷频,和通过函数运算绘各种艺术图片
#include
#include
#include
#include
#include
#include
#include "dis.h"
#define DIM 1024
#define DM1 (DIM-1)
#define _sq(x) ((x)*(x)) // square
#define _cb(x) abs((x)*(x)*(x)) // absolute value of cube
#define _cr(x) (unsigned char)(pow((x),1.0/3.0)) // cube root
unsigned char RD1(int i,int j)
{
return (char)(_sq(cos(atan2(j-512,i-512)/2))*255);
}
unsigned char GR1(int i,int j)
{
return (char)(_sq(cos(atan2(j-512,i-512)/2-2*acos(-1)/3))*255);
}
unsigned char BL1(int i,int j)
{
return (char)(_sq(cos(atan2(j-512,i-512)/2+2*acos(-1)/3))*255);
}
int draw1(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD1(i,j)&255,GR1(i,j)&255,BL1(i,j)&255);
}
}
}
unsigned char RD2(int i,int j)
{
#define r(n)(rand()%n)
static char c[1024][1024];
return!c[j]?c[j]=!r(999)?r(256):RD2((i+r(2))%1024,(j+r(2))%1024):c[j];
}
unsigned char GR2(int i,int j)
{
static char c[1024][1024];
return!c[j]?c[j]=!r(999)?r(256):GR2((i+r(2))%1024,(j+r(2))%1024):c[j];
}
unsigned char BL2(int i,int j)
{
static char c[1024][1024];
return!c[j]?c[j]=!r(999)?r(256):BL2((i+r(2))%1024,(j+r(2))%1024):c[j];
}
int draw2(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD2(i,j)&255,GR2(i,j)&255,BL2(i,j)&255);
}
}
}
unsigned char RD3(int i,int j)
{
float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
return log(k)*47;
}
unsigned char GR3(int i,int j)
{
float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
return log(k)*47;
}
unsigned char BL3(int i,int j)
{
float x=0,y=0;int k;for(k=0;k++<256;){float a=x*x-y*y+(i-768.0)/512;y=2*x*y+(j-512.0)/512;x=a;if(x*x+y*y>4)break;}
return 128-log(k)*23;
}
int draw3(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD3(i,j)&255,GR3(i,j)&255,BL3(i,j)&255);
}
}
}
unsigned char RD4(int i,int j)
{
double a=0,b=0,c,d,n=0;
while((c=a*a)+(d=b*b)<4&&n++<880)
{b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
return 255*pow((n-80)/800,3.);
}
unsigned char GR4(int i,int j)
{
double a=0,b=0,c,d,n=0;
while((c=a*a)+(d=b*b)<4&&n++<880)
{b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
return 255*pow((n-80)/800,.7);
}
unsigned char BL4(int i,int j)
{
double a=0,b=0,c,d,n=0;
while((c=a*a)+(d=b*b)<4&&n++<880)
{b=2*a*b+j*8e-9-.645411;a=c-d+i*8e-9+.356888;}
return 255*pow((n-80)/800,.5);
}
int draw4(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD4(i,j)&255,GR4(i,j)&255,BL4(i,j)&255);
}
}
}
unsigned char RD5(int i,int j)
{
static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}
unsigned char GR5(int i,int j)
{
static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}
unsigned char BL5(int i,int j)
{
static double k;k+=rand()/1./RAND_MAX;int l=k;l%=512;return l>255?511-l:l;
}
int draw5(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD5(i,j)&255,GR5(i,j)&255,BL5(i,j)&255);
}
}
}
unsigned char RD6(int i,int j)
{
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)((i+DIM)*s+y)%2+(int)((DIM*2-i)*s+y)%2)*127;
}
unsigned char GR6(int i,int j)
{
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)(5*((i+DIM)*s+y))%2+(int)(5*((DIM*2-i)*s+y))%2)*127;
}
unsigned char BL6(int i,int j)
{
float s=3./(j+99);
float y=(j+sin((i*i+_sq(j-700)*5)/100./DIM)*35)*s;
return ((int)(29*((i+DIM)*s+y))%2+(int)(29*((DIM*2-i)*s+y))%2)*127;
}
int draw6(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD6(i,j)&255,GR6(i,j)&255,BL6(i,j)&255);
}
}
}
unsigned char BL7(int i,int j);
unsigned char RD7(int i,int j)
{
return(i+j)?256-(BL7(i,j))/2:0;
}
unsigned char GR7(int i,int j)
{
return RD7(i,j);
}
unsigned char BL7(int i,int j)
{
static int m[DIM][DIM],e,x,y,d,c[4],f,n;
if(i+j<1)
{
for(d=DIM*DIM;d;d--)
{
m[d%DIM][d/DIM]=d%6?0:rand()%2000?1:255;
}
for(n=1;n;n++)
{
x=rand()%DIM;
y=rand()%DIM;
if(m[x][y]==1)
{
f=1;
for(d=0;d<4;d++)
{
c[d]=m[(x+DIM+(d==0)-(d==2))%DIM][(y+DIM+(d==1)-(d==3))%DIM];
f=f
}
if(f>2)
{
m[x][y]=f-1;
}
else
{
++e;
e%=4;
d=e;
if(!c[e])
{
m[x][y]=0;
m[(x+DIM+(d==0)-(d==2))%DIM][(y+DIM+(d==1)-(d==3))%DIM]=1;
}
}
}
}
}
return m[j];
}
int draw7(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD7(i,j)&255,GR7(i,j)&255,BL7(i,j)&255);
}
}
}
unsigned char BL8(int i,int j);
unsigned char RD8(int i,int j)
{
return BL8(i,j)*(DIM-i)/DIM;
}
unsigned char GR8(int i,int j)
{
return BL8(i,j)*(DIM-j/2)/DIM;
}
unsigned char BL8(int i,int j)
{
static float c[DIM][DIM];
if(i+j<1)
{
float a=0,b,k,r,x;
int e,o;
for(;a
{
for(b=0;b
{
r=a*1.6/DIM+2.4;x=1.0001*b/DIM;
for(k=0;k
{
x=r*x*(1-x);
if(k>DIM/2)
{
e=a;
o=(DM1*x);
c[e][o]+=0.01;
}
}
}
}
}
return c[j]>255?255:c[j]*i/DIM;
}
int draw8(void)
{
for(int j=0;j
{
for(int i=0;i
{
dis_set_pixel(j,i,RD8(i,j)&255,GR8(i,j)&255,BL8(i,j)&255);
}
}
}
int main(int argc, char* argv[])
{
uint8_t r= 0x00;
uint8_t g= 0x00;
uint8_t b= 0xFF;
if(argc != 3)
{
printf("usage:./lcdtest ip port ie:./lcdtest 192.168.1.101 5004 n");
}
dis_init(argv[1],atoi(argv[2]));
while(1)
{
#if 0
/* RGB */
for(int i=0;i
{
for(int j=0;j
{
dis_set_pixel(i,j, 0xFF, 0, 0);
}
}
dis_flush();
sleep(2);
for(int i=0;i
{
for(int j=0;j
{
dis_set_pixel(i,j, 0, 0xFF, 0);
}
}
dis_flush();
sleep(2);
for(int i=0;i
{
for(int j=0;j
{
dis_set_pixel(i,j, 0, 0, 0xFF);
}
}
dis_flush();
sleep(2);
/* PIC */
draw1();
dis_flush();
sleep(5);
#endif
#if 1
draw2();
dis_flush();
sleep(5);
draw3();
dis_flush();
sleep(5);
draw4();
dis_flush();
sleep(5);
draw5();
dis_flush();
sleep(5);
draw6();
dis_flush();
sleep(5);
draw7();
dis_flush();
sleep(5);
draw8();
dis_flush();
sleep(20);
#endif
}
}
编译 aarch64-linux-gnu-gcc main.c dis/*.c -lx264 -lm -lpthread -ldl -lortp -L/home/lhj/opt/x264/board/lib -I/home/lhj/opt/x264/board/include -L/home/lhj/opt/board/lib -I/home/lhj/opt/board/include -I./dis -o lcdtest 将lcdtest导入到开发板 chmod + x lcdtest 运行 ./lcdtest 192.168.1.101:5004 VLC打开内容为如下的sdp文件
m=video 5004 RTP/AVP 96
a=rtpmap:96 H264/90000
a=framerate:25
c=IN IP4 192.168.1.101
效果如下 RGB刷频
艺术图片
总结 以上我们实现了任意的画点,完成了掌上游戏机的两个重点部分,输入和显示,下一篇就开始正式的NES模拟器移植了。
|