Tiny6410学习成果—视频采集小车(端程序)

昨天说好的,今天补上PC端程序制作方法。

摄像头照射mini6410 小板上效果图:
这里写图片描述

首先明确一下PC端程序需要做哪些任务。
1.向ARM端小车发出控制命令
2.接受小车实时传输的图像数据
3.解压H264视频数据
4.快速显示YUV420数据
第 3和4部分要复杂一些。
上位使用的是 Visual C++/MFC
显示视频部分,为了提高视频质量和速度,我使用了DirectX.7

(1)创建MFC
打开VS2005软件,创建一个MFC对话框应用程序(Dialog-based Application),在名称栏输入创建项目的名称,点击“确定”。如下图所示:
这里写图片描述
在出现的“MFC应用程序向导”对话框内,选择“基于对话框”,并取消“使用Unicode库(N)”其他选项不做修改,单击“下一步”,如下图所示:
这里写图片描述
单击“下一步”,选择使用windows套接字单击“完成”如下图所示:
这里写图片描述

即可创建一个MFC对话框。
在工具箱中点击 这里写图片描述,添加此控制键。
同时修改属性中 ID为:IDC_M_PICTURE。
同样方式添加五个按钮如图,按钮的ID让它为默认值吧不去修改了(Button1的ID为IDC_BUTTON1)。
修改按钮的名字,从1到5分别为,“TCP开启服务”,“左转”,“右转”,“前进”,“后退”。
这里写图片描述

为按键1 (TCP开启服务)按键添加点击事件函数,简单方法双击即可。
为picture控件添加变量,变量名为“m_picture”,操作如图:

点击“完成”。在TCP_H264控制服务端Dlg.h中自己添加:
public:
CStatic m_picture;

添加DircetDraw函数库:
在菜单中选择项目—>属性—>配置属性—>链接器—>输入—>附加依赖项添加“ddraw.lib dinput.lib dxguid.lib” 。如图所示:
这里写图片描述
提示:(添加附加依赖之前,要在VS2005 菜单中 工具—>选项—>项目和解决方案—>C++目录—>包含文件和库文件分别添加DirectX的 include 和lib所依赖的路径。

在“TCP_H264控制服务端Dlg.h”中添加:#include <ddraw.h>

添加H264 软解码库函数:
该库函数是网上的大神提取 著名的ffmpeg项目的源码。该部分代码功能为将H264压缩数据流,解码成YUV420的图片数据流。
右击“头文件”—>“添加”—>“现有”选中8个头文件(扩展名为.h)。
右击“源文件”—>“添加”—>“现有”选中7个源文件(扩展名为.c),如图:
这里写图片描述
在“TCP_H264控制服务端Dlg.cpp”中添加:

extern "C"
{
    #include "h264lib\avcodec.h"
}

同时取消预编译头文件。

到此对话框的界面设计与编程环境的构建终于完成啦!尤其是编程环境的构建,真烦,记得第一次学习DircetDraw时,为了搭建环境我卡主我好久。其实步骤很简单,但是没人指点,没人引路,就让它变的很难了。
接下来让我们开始进入程序的核心,编写代码!

(1)在“TCP_H264控制服务端Dlg.h”中为“CTCP_H264控制服务端Dlg”类添加 如下代码,
该部分代码添加为CTCP_H264控制服务端Dlg类中的public变量,这些变量为相应的句柄,指针,还有调用DircetDraw TCP H264软解码 等所要用到的结构。


//***************************************************
    //  离屏表面
    //***************************************************
    HWND  Phwnd;    //picture窗口句柄
    HRESULT ddrval; //函数返回值接收变量,以判断韩函数的调用状况
    LPDIRECTDRAW lpDD;  //DirectDraw对象

    LPDIRECTDRAWSURFACE lpDDSPrimary;   //DirectDraw主页面
    LPDIRECTDRAWSURFACE lpDDSOffScr;    //DirectDraw缓冲区,下一帧画面的存储
    LPDIRECTDRAWCLIPPER lpClipper;  //窗口句柄裁剪表
    DDSURFACEDESC ddsd; //页面描述

    LPBYTE lpSurface;   //数据指针

    void InitDricetDraw();
    void DrawH264Msg( unsigned char* H264Msg, int fileSize);///绘制H264 函数
    //***************************************************
    //  H264 
    //***************************************************
    int  got_picture, consumed_bytes; 

    struct AVCodecContext *c;  // Codec Context
    struct AVFrame *picture;   // Frame 
    //***************************************************
    //  TCP 
    //***************************************************
    SOCKET sockServer;  ///服务端句柄
    SOCKET sockConn;    ///客户端句柄
    unsigned char h264msg[50000];



    ///**************线程****************
    HANDLE hThread1;///线程句柄
    bool   hThread1_ToEnd;///标示

    ///**************************
    //鼠标按下
    ///**************************
    CButton *button1;
    CButton *button2;
    CButton *button3;
    CButton *button4;

( 2 )在在“TCP_H264控制服务端Dlg.h”中为
void InitDricetDraw();
void DrawH264Msg( unsigned char* H264Msg, int fileSize);
写出实体。

void InitDricetDraw()函数的实体:
函数的功能为:离屏表面初始化,通过句柄lpDD(DirectDraw对象)对其进行创建和设置,并且获得lpDDSPrimary 的DirectDraw主页面。
lpDD对象,表示显示硬件,它包括了显示器和显卡还有显存,用它来代表整个显示系统。
lpDDSPrimary 对象代表了一个页面。页面可以有很多种表现形式,它既可以是可见的(屏幕的一部分或全部),称之为主页面(Primary Surface);也可以是作换页用的不可见页面,称之显卡后台缓存(Back Buffer),在换页后,它成为可见;还有一种始终不可见的,称之为离屏页面(Off-screen Surface),用它来存储图象。其中,最重要的页面是主页面,每个DirectDraw应用程序都必须创建至少一个主页面,用它来代表屏幕上可见的区域,说白了,就是你的显示屏幕。

/*************************************************************/
//          窗口模式初始化DirectDraw的函数
/*************************************************************/
void CTCP_H264控制服务端Dlg::InitDricetDraw()
{
    //创建DirectDraw主对象
    ddrval = DirectDrawCreate( NULL, &lpDD, NULL );
    if( ddrval != DD_OK ){
        MessageBox("DirectDrawCreate不支持");
    }

    //用DDSCL_NORMAL表示我们要与GDI共存
    ddrval = lpDD->SetCooperativeLevel( Phwnd, DDSCL_NORMAL );
    if( ddrval != DD_OK ){
        lpDD->Release();
        MessageBox("SetCooperativeLevel不支持");
    }
    //描述主表面,对其进行设置
    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS;
    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

    // 不是可翻
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );
    if( ddrval != DD_OK ){
        lpDD->Release();
        MessageBox("CreateSurface不支持");
    }

    // 创建一个裁切板保证不画到窗口外面
    ddrval = lpDD->CreateClipper( 0, &lpClipper, NULL );
    if( ddrval != DD_OK ){
        lpDDSPrimary->Release();
        lpDD->Release();
        MessageBox("CreateClipper不支持");
    }

    // 把窗口句柄设置给裁切板就给了它一个与窗口相适的裁切框
    ddrval = lpClipper->SetHWnd( 0, Phwnd );
    if( ddrval != DD_OK ){
        lpClipper->Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        MessageBox("// 把窗口句柄给裁切板不支持");
    }

    //把裁切板附到主页面
    ddrval = lpDDSPrimary->SetClipper( lpClipper );
    if( ddrval != DD_OK ){
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        MessageBox("lpDDSPrimary->SetClipper( lpClipper );不支持");
    }
    //描述缓冲表面,对其进行设置
    memset( &ddsd, 0, sizeof(ddsd) );
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH|DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
    ddsd.dwWidth = 176;///图片宽度
    ddsd.dwHeight = 144;///图片高度
    ddsd.ddpfPixelFormat.dwSize=sizeof(DDPIXELFORMAT);
    ddsd.ddpfPixelFormat.dwFlags=DDPF_FOURCC|DDPF_YUV;
    ddsd.ddpfPixelFormat.dwFourCC=MAKEFOURCC('I','Y','U','V');//图片像素存储格式YUV420('I','Y','U','V')
    ddsd.ddpfPixelFormat.dwYUVBitCount=8;

    // 单独创建后缓冲页
    ddrval = lpDD->CreateSurface( &ddsd, &lpDDSOffScr, NULL );
    if( ddrval != DD_OK ){
        lpClipper-> Release();
        lpDDSPrimary->Release();
        lpDD->Release();
        MessageBox("单独创建后缓冲页不支持");
    }

    //H264软解码初始化
    c = avcodec_alloc_context(); 
    if(!c)
        MessageBox("// avcodec_alloc_context不支持");

    if (avcodec_open(c) < 0)
        MessageBox("// avcodec_open不支持");

    picture   = avcodec_alloc_frame();
    if(!picture)
        MessageBox("// avcodec_alloc_frame不支持");
}

void DrawH264Msg( unsigned char* H254Msg, int fileSize);函数实体:
函数功能:通过decode_frame()函数对H264数据进行软解码,相应的图片YUV420数据指针存在picture变量中,然后 向显卡缓冲区(lpDDSOffScr)中填充YUV420数据,然后通过lpDDSPrimary->Blt(&rctDest,lpDDSOffScr,&rctSour,DDBLT_WAIT,NULL)把lpDDSOffScr缓冲区中是数据(下一帧的画面)翻转到主表面lpDDSPrimary上,即在屏幕上显示画面。

/**************************************************/
//          离屏表面绘制代码
/**************************************************/
void CTCP_H264控制服务端Dlg::DrawH264Msg( unsigned char* H264Msg, int fileSize)
{
    //调用软解码库API函数decode_frame()对存储在H264Msg的h264数据进行解码,解码出的YUV420数据存在picture中
    consumed_bytes= decode_frame(c, picture, &got_picture, H264Msg, fileSize);

    //锁定缓冲表面内存数据,表示要对其进行修改填充,并获得缓冲内存指针存在ddsd结构中
    ddrval=lpDDSOffScr->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_WRITEONLY,NULL);
    while(DDERR_WASSTILLDRAWING==ddrval);
    if(DD_OK!=ddrval){
        MessageBox("离屏表面锁定不成功!");

    }
    //指向缓冲表面的指针
    LPBYTE lpSurf=(LPBYTE)ddsd.lpSurface;

    //通过指针想缓冲中填充YUV420图片是数据
    int i=0;
    for(i=0;i<ddsd.dwHeight;i++){
        memcpy(lpSurf,picture->data[0],ddsd.dwWidth);
        picture->data[0]+=208;
        lpSurf+=ddsd.lPitch;
    }
    for(i=0;i<ddsd.dwHeight/2;i++){
        memcpy(lpSurf,picture->data[1],ddsd.dwWidth/2);
        picture->data[1]+=104;
        lpSurf+=ddsd.lPitch/2;
    }
    for(i=0;i<ddsd.dwHeight/2;i++){
        memcpy(lpSurf,picture->data[2],ddsd.dwWidth/2);
        picture->data[2]+=104;
        lpSurf+=ddsd.lPitch/2;
    }
    //释放锁定,修改完成,允许被其他函数调用
    lpDDSOffScr->Unlock(NULL);

    RECT                  rctDest;        //目标区域
    RECT                  rctSour;        //源区域
    // 首先需要指出主页面的位置
    rctSour.left=0;
    rctSour.top=0;
    rctSour.right=ddsd.dwWidth;
    rctSour.bottom=ddsd.dwHeight;
    ::GetClientRect(Phwnd,&rctDest);
    ::ClientToScreen(Phwnd,(LPPOINT)&rctDest.left);
    ::ClientToScreen(Phwnd,(LPPOINT)&rctDest.right);
    // 窗口模式blit,将缓冲表面的图片数据赋主表面,即YUV420图片数据就显示在屏幕上了
    ddrval=lpDDSPrimary->Blt(&rctDest,lpDDSOffScr,&rctSour,DDBLT_WAIT,NULL);
    while(DDERR_WASSTILLDRAWING==ddrval);
    if(DD_OK!=ddrval){
        MessageBox("Blt到主表面不成功");
    }
}  

( 3 )在TCP_H264控制服务端Dlg.cpp中 添加全局函数:
DWORD WINAPI Fun1Proc(LPVOID lpParameter); 线程函数。
函数作用:该函数是该程序工作时的逻辑主体,进入后TCP进行监听,链接,接收数据,数据处理绘制,发送数据等。
实体如下:

/****************************************************************************/
//          线程函数,当TCP开启时,程序运行在本函数中,也是该程序的应用程序主体
/****************************************************************************/
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{

    CTCP_H264控制服务端Dlg *pdlg= (CTCP_H264控制服务端Dlg*)lpParameter;
    //创建套接字
    pdlg->sockServer = socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM参数设置为TCP连接
    SOCKADDR_IN addrServer; //设置服务器端套接字的相关属性
    SOCKADDR_IN addrClient; //用来接收客户端的设置,包括IP和端口

    addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //设置IP
    addrServer.sin_family=AF_INET;
    addrServer.sin_port=htons(8080); //设置端口号

    //将套接字绑定到本地地址和指定端口上
    bind( pdlg->sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));

    //将套接字设置为监听模式,并将最大请求连接数设置成,超过此数的请求全部作废
    listen( pdlg->sockServer, 5);


    int len=sizeof(SOCKADDR);
    int filesie=0;
    int dd,cc;

    while(1) //不断监听
    {
        //得到创建连接后的一个新的套接字,用来和客户端进行沟通,原套接字继续监听客户的连接请求
        pdlg->sockConn = accept( pdlg->sockServer, (SOCKADDR*)&addrClient, &len);
        if( (pdlg->sockConn) != (INVALID_SOCKET)) //创建成功
        {
            if( pdlg->hThread1_ToEnd){
                return 1;///结束线程
            }
            while(1){
                //获取H264数据的长度信息
                recv(pdlg->sockConn,(char *)&filesie,4,0);
                if(filesie<100){
                    pdlg->MessageBox("数据大小有问题!");
                }
                dd=0;
                cc=0;
                //接收长度为filesie的H264数据
                while( dd< filesie){
                    cc = recv( pdlg->sockConn, (char *)pdlg->h264msg, filesie- dd,0);
                    dd  =   dd  +   cc;
                }
                //绘制H264视频
                pdlg->DrawH264Msg(pdlg->h264msg,filesie);
            }
        }
        else{
            pdlg->MessageBox("创建连接失败");
            return 0;
        }          
    }
    return 0;
}

(4)窗口初始化函数BOOL CTCP_H264控制服务端Dlg::OnInitDialog()中添加如下代码:

// TODO: 在此添加额外的初始化代码
/*****************************************************/
//          离屏表面初始化
/*****************************************************/
    Phwnd = m_picture.GetSafeHwnd();
    InitDricetDraw();

(5)为按钮1(TCP开启)处理函数添加代码

void CTCP_H264控制服务端Dlg::OnBnClickedButton1()
{
    // TODO: 在此添加控件通知处理程序代码
    hThread1_ToEnd = 0;
    hThread1 = CreateThread(NULL,0,Fun1Proc,(LPVOID)this,0,NULL);
    CloseHandle(hThread1);
}

(6)添加void CTCP服务端解析H264Dlg::OnDestroy()函数,当窗口关闭时处理:


void CTCP_H264控制服务端Dlg::OnDestroy()
{
    CDialog::OnDestroy();

    // TODO: 在此处添加消息处理程序代码
    hThread1_ToEnd = TRUE;//通知线程结束标示
    //释放DirectDraw对象
    lpClipper->Release();
    lpDDSPrimary->Release();
    lpDD->Release();

    closesocket(sockConn); //将本次建立连接中得到套接字关闭
    closesocket(sockServer); //对一直处于监听状态的套接字进行关闭
    delete button1;
    delete button2;
    delete button3;
    delete button4;
}

(7)对了,刚想起了,我们在对话框装设置时,摆放了5个按钮,而我只为其中的第一个(TCP开启)按钮添加了点击事件处理函数,而后四个按钮没有添加任何事件处理函数,现在我们回头说一下:

后四个按钮是控制前后左右的,我本来的打算是为后四个按钮添加按钮“按下”,“抬起”消息。在按下按钮时,想ARM板发送“前进”信息,按钮抬起发送“前进结束”消息。可是MFC按钮控件没有提供这样的这样的事件处理函数,没办法只要自己写了。
这里写图片描述
右击 “工程” 选择添加—>类—>MFC类,单击添加。
输入类名CMyButton(当然个可以随便设),基类选择CButton(必须是这个)。如图:
这里写图片描述

单击完成。

然后在MyButton.h与MyButton.cpp中重写CMyButton类为:
(1)在MyButton.h中声明CTCP_H264控制服务端Dlg,重写构造函数,同时添加鼠标左键按下消息,和鼠标左键抬起消息,添加代码如下:
class CTCP_H264控制服务端Dlg;

class CMyButton : public CButton
{
    DECLARE_DYNAMIC(CMyButton)

public:
    CMyButton(CTCP_H264控制服务端Dlg *pd);
    virtual ~CMyButton();

protected:
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
public:
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
};

(2)在MyButton.cpp中为重写的构造函数写实体,为鼠标左键按下消息(OnLButtonDown)鼠标左键抬起消息(OnLButtonUp)写实体,代码如下:

构造函数实体:
函数功能:在创建CMyButton类时,需要按参数形式,把主窗体句柄传递过来,从而就可以调用到主窗体的资源了。

CMyButton::CMyButton(CTCP_H264控制服务端Dlg *pd)
{
    pdlg=pd;
}

OnLButtonDown函数的实体:
函数功能:获取按钮控件本身ID,判断是哪个按钮按下,并且TCP函数send()相应的数据信息。

void CMyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CButton::OnLButtonDown(nFlags, point);

    myID = GetDlgCtrlID();

    switch( myID )              
    {           
    case IDC_BUTTON2: 
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=1;
        ser_order[3]=0;
        ser_order[4]=1;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON3: 
        ser_order[0]=1;
        ser_order[1]=1;
        ser_order[2]=0;
        ser_order[3]=1;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON4: 
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=1;
        ser_order[3]=1;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON5:
        ser_order[0]=1;
        ser_order[1]=1;
        ser_order[2]=0;
        ser_order[3]=0;
        ser_order[4]=1;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    default :                     
        break;           
    }  
}

OnLButtonUp函数的实体:
函数功能:获取按钮控件本身ID,判断是哪个按钮按下,并且TCP函数send()相应的数据信息。

void CMyButton::OnLButtonUp(UINT nFlags, CPoint point)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值

    CButton::OnLButtonUp(nFlags, point);

    myID = GetDlgCtrlID();

    switch( myID )              
    {           
    case IDC_BUTTON2: 
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=0;
        ser_order[3]=0;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON3: 
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=0;
        ser_order[3]=0;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON4: 
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=0;
        ser_order[3]=0;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    case IDC_BUTTON5:
        ser_order[0]=1;
        ser_order[1]=0;
        ser_order[2]=0;
        ser_order[3]=0;
        ser_order[4]=0;
        send( pdlg->sockConn , ser_order , 6,0); 
        break;

    default :                     
        break;           
    } 
}

(3)最后在TCP_H264控制服务端Dlg.cpp中添加MyButton.h头文件,同时在主窗体构造函数添加如下代码:

button1 = new CMyButton(this);
    button2 = new CMyButton(this);
    button3 = new CMyButton(this);
    button4 = new CMyButton(this);

    button1->SubclassDlgItem(IDC_BUTTON2,this);///前进
    button2->SubclassDlgItem(IDC_BUTTON3,this);///后退
    button3->SubclassDlgItem(IDC_BUTTON4,this);///左转
    button4->SubclassDlgItem(IDC_BUTTON5,this);///右转

到此我们的程序已经全部写完啦!
让我们检验一下刚刚写好的程序!
环境:在ARM11开发板(由于车模还没有完成先在实验板上看看视频的效果)上运行已经也好的TCP_视频采集程序,通过网线向我们的电脑上发送视频数据,我们在PC电脑上用我们刚刚写好的程序接收视频数据并通过DirectX显示,效果如图:
这里写图片描述

这里写图片描述

这里写图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值