课程设计报告
课程名称: 流媒体程序设计
指导教师:
专业班级:
学 号:
姓 名:
教学部门:计算机学院
一、作品题目要求
设计一个局域网通信软件,该软件除了正常的文本信息通信外,要求实现服务器端采集音频数据,通过网络传输给客户端,客户端能够播放出该音频数据。
二、作品系统规划
随着现代计算机技术的不断发展,多媒体已经成为现代计算机不可缺少的功能,而计算机的音频,视频功能是其中最为重要的部分。而随着网络的不断发展,网络已经成为人们最重要的交流方式之一。计算机硬件的更新,非凡是海量存储设备和大容量内存在PC机上的实现,对音频媒体进行数字化处理早已经成为可能。其总体结构图如下所示:
图 2-1 总体结构图
一个完整的音频通信系统程序要完成以下工作:发送端完成音频采集、压缩编码、码流发送等;接收端则要完成码流接收、解码恢复、音频回放等。其总体程序流程图如图3-3所示。
三、程序设计模块分类
要实现点对点语音通信,原理非常简单,只要针对一个点实现话音的实时采集、处理、播放,同时能进行可靠的传送和接收,这样两点一连便可通话。对于前者,采用Windows 的低层音频服务比较合适,因为低层音频服务中的回调机制为我们提供了很大的方便。当应用程序不断向设备驱动程序提供音频数据时,设备驱动程序控制音频设备在后台完成录音和放音的具体操作,通过回调机制,我们又可以检测到什么时候用完一个数据块,并及时传送下一个数据块,从而保证了声音的连续,有了这种实时采集、回放功能后,接下来的工作就是在网络上传送话音数据。在点对点网络传输方面,选择面向连接的TCP/IP协议,TCP/IP传输协议自动处理分组丢失和交付失序问题,这样我们不用为这些问题操心,只需很好地利用这个连接,在采集话音回放之前一方面将自己的话音传给网络,另一方面接收网络传来的话音,这样便实现了点对点语音通信。其模块框图如图 3-1所示。
图 3-1 模块框图
四、模块功能说明
1.音频编/解码
自然界中的声音非常复杂,波形极其复杂,通常我们采用的是脉冲代码调制编码,即PCM编码。PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。
2.音频采集及回放
本设计采用基于WaveX 低级音频API 采集音频及实时播放的技术。利用双/ 多缓冲技术和网络拥塞控制策略可很好的控制音频的实时性和连续性。双/ 多缓冲技术可以很好的实现声音的快速连续采集和实时顺畅播放。
3.网络的传送和接收
网络的传输和接收部分是本设计最重要的模块,对于主机1传送到主机2的音频数据信息这条数据通路,主机1为客户端,主机2为服务端;而对于主机2传送到主机1的音频数据信息这条数据通路,主机2为客户端,主机1为服务器端。双方通话时,主机1和主机2同时既处于服务器状态又处于客户端状态,即通话状态。主机1呼叫主机2时,主机1进入客户端状态,主机2收到呼叫信息,进入服务器端状态,若主机2同意通话,则两机都进入通话状态,双方可以通过上述两条数据通路进行网络通话,若有一机挂断,两机都返回侦听状态,等待呼叫或被呼叫。
4.界面设计
单击新建,在弹出的对话框中单击工程,点击MFC AppWizard [exe],在右边工程名称[N]:下面的文本框中写入工程名称“test”,单击确定,进入MFC 应用程序向导—步骤1,在“您要创建的应用程序类型是:”一栏里选择“基本对话框[D]”,单击下一步,在Windows Socket [W]选项前打钩,单击下一步,选择默认设置,继续单击下一步,保持默认设置,单击完成。在弹出的新建工程信息对话框中单击确定,在出现的原始test对话框中添加connect和disconnect按钮,添加一个文本框,初始界面如图4-11所示。
图 4-1 初始界面图
五、程序设计流程
程序设计流程如下图所示:
图 5-1 总体程序流程
六、关键技术说明
1.音频采集核心代码
class CWaveIn
{
public:
线程处理
public:
获取数据
启动录音
停止录音
获取实例
获取采样位数
获取采样速率
获取频道数
设置采样位数
设置采样速率
设置采样速率
获取错误信息
构造函数
析构函数
protected:
打开设备
关闭设备
停止线程
启动线程
准备缓存
释放缓存
开始录音
结束录音
protected:
用户实例数据
protected:
频道数
采样速率
采样位数
protected:
音频输入设备句柄
函数调用返回信息
回调函数指针
线程句柄
结构缓存指针
线程启动标志
设备打开标记
内存分配标记
录音开始标记
};
2.音频回放核心代码
class CWaveOut
{
public:
线程处理
public:
根据文件设置格式
播音
开始播音
停止播音
获取缓存数目
减少缓存数目
增加缓存数目
获取实例
获取采样位数
获取采样速率
获取频道数
设置采样位数
设置采样速率
设置频道数
获取错误信息
构造函数
析构函数
protected:
打开设备
关闭设备
停止线程
启动线程
protected:
用户实例数据
protected:
频道数
采样速率
采样位数
protected:
函数调用返回信息
播音设备句柄
线程句柄
回调函数指针
缓存数目
重要部分(critical section)
线程启动标志
播音设备打开标志
线程处理
};
3.网络的传送和接收核心代码
//初始化网络
BOOL CUdpSocket::Ini()
{
已经初始化
返回
创建数据报类型的套接字
设置初始化完成标记
}
int CUdpSocket::Send(const void* lpBuf, int nBufLen, int nFlags)
{
音频数据序号
复制音频格式信息
调用基类SendTo()函数发送音频格式信息
}
//关闭套接字
BOOL CUdpSocket::CloseSocket()
{
已经关闭
关闭套接字
设置初始化未完成标记
返回
}
//设置Ip地址
void CUdpSocket::SetIp(CString ip)
{
}
//发送端CSendClient member functions
//重载OnReceive()函数
void CSendClient::OnReceive(int nErrorCode)
{
接收缓存中所有的TalkFrame结构
接收数据
出错
返回
如果不是正常数据就返回
接收缓存中所有的音频数据
接收数据
出错
返回
对方地址
端口
正常通信帧
获取连接对方地址和端口
提示开始工作
允许发送数据
设置允许工作标志
//接收端
//重载OnAccept() 函数
void CListenSocket::OnAccept(int nErrorCode)
{
客户端地址
客户端地址长度
临时socket
已经连接
用临时socket接收连接请求
关闭
返回
接受连接请求
返回
设置连接标记
调用基类OnAccept()函数
}
//关闭套接字
void CListenSocket::CloseClient()
{
关闭套接字
设置未连接标记
}
// CSendClient member functions
//重载OnReceive()函数
void CSendClient::OnReceive(int nErrorCode)
{
接收缓存中所有的TalkFrame结构
接收数据
出错
返回
如果不是正常数据就返回
接收缓存中所有的音频数据
接收数据
出错
返回
对方地址
端口
正常通信帧
获取连接对方地址和端口
提示开始工作
允许发送数据
设置允许工作标志
4.界面实现代码
// testDlg.cpp : implementation file
#include "stdafx.h"
#include "test.h"
#include "testDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
//}}AFX_VIRTUAL
// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTestDlg dialog
CTestDlg::CTestDlg(CWnd* pParent /*=NULL*/)
: CDialog(CTestDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CTestDlg)
m_ip = _T("");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CTestDlg)
DDX_Text(pDX, IDC_EDIT1, m_ip);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//{{AFX_MSG_MAP(CTestDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON2, OnButton2)
ON_BN_CLICKED(IDC_BUTTON3, OnButton3)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTestDlg message handlers
BOOL CTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
// TODO: Add extra initialization here
m_ip = "192.168.10.116";
UpdateData(FALSE);
m_talk.Ini();
return TRUE; // return TRUE unless you set the focus to a control
}
void CTestDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CTestDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR CTestDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
void CTestDlg::OnButton2()
{
// TODO: Add your control notification handler code here
this->UpdateData ();
m_talk.Start(m_ip);
void CTestDlg::OnButton3()
{// TODO: Add your control notification handler code here
m_talk.End();
}
七、实验结果
1.在要实现通信的两台机器分别将test应用程序打开,初始界面为图7-1所示。
图 7-1 初始状态
2.将另一方IP地址输入文本框里,点击连接,服务端显示如图7-2所示。
图 7-2
3.点击确定客户端显示如图7-3所示。
图 7-3
4.点击是,服务端显示 图5-4,客户端显示如图7-4所示。
图 7-4
图 7-5
5.两端都点击确定,就可以进行语音通信了,若有一方点击断开连接,显示如图7-6所示,点击确定应用程序自动退出。
图 7-6
总结
流媒体编程技术课程已经结束了,从中我学到了很多东西。
本次研究的课题是局域网通信系统,主要实现在局域网里进行语音聊天,来实现彼此沟通、交流信息。本设计主要用了Visual C++编程环境,实现了在LAN中在线用户实现语音交互。在设计里涉及到了网络通信基本原理和Socket编程及语音处理API技术。语音通信系统包括四个模块,分别是通信模块、声源采集模块、音频编解码模块、音频再生模块。
回顾起此次毕业设计,至今我仍感慨颇多,从选题到定稿,从理论到实践,可以说是苦多于甜,但是可以学到很多很多的的东西,同时不仅可以巩固了以前所学过的知识,而且学到了很多在书本上所没有学到过的知识。通过这次课程设计使我懂得了理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学的理论知识与实践相结合起来,从理论中得出结论,才能真正为社会服务,从而提高自己的实际动手能力和思考的能力。在设计的过程中遇到问题,可以说得是困难重重,但是在实践中,难免会遇到过各种各样的问题,同时在设计的过程中发现了自己的不足之处,对以前所学过的知识理解得不够深刻,通过这次课程设使我对c语言有了更深入的认识和了解,还有学会如何查阅文献。很重要的一点是对进行软件设计的整体设计流程及思维方法有了深刻的认识。在期间,发现的许多问题都源于没有认真地按步骤进行设计,不重视需求分析,总体设计部分,对各个方面将会产生的问题考虑不周全。在设计过程中,对于我来说,由于很多都是新知识,这就迫使我到处查阅相关资料,学习新知识,不仅体会到Visual C++编程环境的强大功能,也对新接触到的Socket编程和语言处理API有了大概的了解。
由于我本人的技术,可能有很多不足之处,望各位老师多加批评和指正,谢谢!