一整套音视频聊天项目,一般包括:
视频采集-->编码-->传输-->解码-->播放
声音采集-->编码-->传输-->解码-->播放
传输部分,还分P2P和中转。
因为还要加一些指令,实时消息,相当于一个完整的IM再带上语音视频功能。
最近整理一些以前做的资料,发现整理成一个完整的项目更有效,否则一堆一堆零散代码沉积下去就成泥沙了。
这里以最简单的实现方式,完成一个音视频聊天功能,不求最优,只求最简,网络和音视频的初学者一看就懂。
功能包括:
客户端:音视频采集,编解码,播放,传输。
服务端:简单用户管理,类Stun,穿透Nat,中转
充分利用开源项目,比如编解码用ffmpeg,视频采集用CCameraDS,声音采集用PortAudio等。
不过,音视频通信,最重要的实际是网络部分。主要是UDP通信。这部分完全用socket自己写,不使用开源项目,这样可以快速
理解并熟练掌握网络socket通信。
计划一个文章讲一个内容,音视频因为是比较成熟的技术,相对篇幅少一些。socket通信虽然代码量少,但比较重要,到那里再多讲一些。
这样算起来,10几到20来篇就差不多了。不过经历时间可能会不少,初步计划到2014年底。
按照上面说的顺序,先从视频的 采集开始吧。
视频采集通常用DirectShow, 或者结合OpenCV,这里有一个不错的资料可供参考:
http://wiki.opencv.org.cn/index.php/%E4%BD%BF%E7%94%A8DirectShow%E9%87%87%E9%9B%86%E5%9B%BE%E5%83%8F
这个是不带dshow库的,需要自己下载安装。
http://download.csdn.net/download/wqvbjhc/2809976
这个下载里面带有dshow .h和.lib,应该不用再安装dshow开发包了。不过我机器已经安装了,没有试。
不过,这里面的采集:
while(1)
{
//获取一帧
pFrame = m_CamDS.QueryFrame();
//显示
cvShowImage(g_szTitle, pFrame);
if (cvWaitKey(20) == 'q')
{
break;
}
}
很明显,是阻塞的,需要启一个线程。
从源码也可以看出:m_pSampleGrabber->SetOneShot(TRUE);
个人比较喜欢使用ISampleGrabber的回调方式给出视频流,所以项目里对CCameraDS做了一点改造,这里简单写一下改造的部分,完整代码在附件里。
在CCameraDS类里面,加一个子类(也可以独立一个class,根据自己喜欢):
class CSampleGrabberCB : public ISampleGrabberCB
{
public:
CSampleGrabberCB();
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample );
STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize );
public:
BOOL m_bCaptureVideo;
int m_nID;
camera_data_cb m_cb;
void* m_pParam;
};
再声明一个变量:
CSampleGrabberCB m_cGrabberCB;
在OpenCamera里面,m_pSampleGrabber->SetOneShot(TRUE);的地方修改一下:
#ifndef ISampleGrabberCB_CALLBACK
m_pSampleGrabber->SetOneShot(TRUE);
#else
m_pSampleGrabber->SetOneShot(FALSE);
m_pSampleGrabber->SetCallback(&m_cGrabberCB, 1);
#endif
然后,不再调用 QueryFrame,另外实现三个控制运行停止的函数:
HRESULT CCameraDS::Run()
{
HRESULT hr = S_OK;
OAFilterState state;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Run();
state = State_Stopped;
while (State_Running != state && SUCCEEDED(hr))
{
hr = m_pMediaControl->GetState(100, &state);
Sleep(100);
}
return hr;
}
HRESULT CCameraDS::Pause()
{
HRESULT hr = S_OK;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Pause();
return hr;
}
HRESULT CCameraDS::Stop()
{
HRESULT hr = S_OK;
OAFilterState state;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Stop();
state = State_Running;
while( State_Stopped != state && SUCCEEDED(hr))
{
hr = m_pMediaControl->GetState(100, &state);
Sleep(100);
}
return hr;
}
这样看起来清爽多了。
使用的时候,m_CamDS.OpenCamera之后,再调用m_CamDS.Run();。
然后,STDMETHODIMP CCameraDS::CSampleGrabberCB::BufferCB( double dblSampleTime, BYTE* pBuffer, long lBufferSize )
就得到视频了。我们可以在外面定义一个回调函数,有视频就回调给外面进行处理。
工程是用vc2008创建的。vc6也没问题,不过以后要调用ffmpeg sdk, vc6会比较麻烦,所以这里使用了vc2008。
代码临时放在附件里,找个时间再传到git或sf上。
代码是想到哪里就写到哪里,可能有点乱。这个项目的目的是最简原则,先用最简单的方法实现,然后再慢慢优化。最好的方法应该是用Qt来开发。暂时先用vc2008对付着。
完整代码在 https://github.com/sxcong/rttim
附件代码只包含本章内容,适合循序渐进看。
Client.rar
视频采集-->编码-->传输-->解码-->播放
声音采集-->编码-->传输-->解码-->播放
传输部分,还分P2P和中转。
因为还要加一些指令,实时消息,相当于一个完整的IM再带上语音视频功能。
最近整理一些以前做的资料,发现整理成一个完整的项目更有效,否则一堆一堆零散代码沉积下去就成泥沙了。
这里以最简单的实现方式,完成一个音视频聊天功能,不求最优,只求最简,网络和音视频的初学者一看就懂。
功能包括:
客户端:音视频采集,编解码,播放,传输。
服务端:简单用户管理,类Stun,穿透Nat,中转
充分利用开源项目,比如编解码用ffmpeg,视频采集用CCameraDS,声音采集用PortAudio等。
不过,音视频通信,最重要的实际是网络部分。主要是UDP通信。这部分完全用socket自己写,不使用开源项目,这样可以快速
理解并熟练掌握网络socket通信。
计划一个文章讲一个内容,音视频因为是比较成熟的技术,相对篇幅少一些。socket通信虽然代码量少,但比较重要,到那里再多讲一些。
这样算起来,10几到20来篇就差不多了。不过经历时间可能会不少,初步计划到2014年底。
按照上面说的顺序,先从视频的 采集开始吧。
视频采集通常用DirectShow, 或者结合OpenCV,这里有一个不错的资料可供参考:
http://wiki.opencv.org.cn/index.php/%E4%BD%BF%E7%94%A8DirectShow%E9%87%87%E9%9B%86%E5%9B%BE%E5%83%8F
这个是不带dshow库的,需要自己下载安装。
http://download.csdn.net/download/wqvbjhc/2809976
这个下载里面带有dshow .h和.lib,应该不用再安装dshow开发包了。不过我机器已经安装了,没有试。
不过,这里面的采集:
while(1)
{
//获取一帧
pFrame = m_CamDS.QueryFrame();
//显示
cvShowImage(g_szTitle, pFrame);
if (cvWaitKey(20) == 'q')
{
break;
}
}
很明显,是阻塞的,需要启一个线程。
从源码也可以看出:m_pSampleGrabber->SetOneShot(TRUE);
个人比较喜欢使用ISampleGrabber的回调方式给出视频流,所以项目里对CCameraDS做了一点改造,这里简单写一下改造的部分,完整代码在附件里。
在CCameraDS类里面,加一个子类(也可以独立一个class,根据自己喜欢):
class CSampleGrabberCB : public ISampleGrabberCB
{
public:
CSampleGrabberCB();
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();
STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
STDMETHODIMP SampleCB( double SampleTime, IMediaSample * pSample );
STDMETHODIMP BufferCB( double dblSampleTime, BYTE * pBuffer, long lBufferSize );
public:
BOOL m_bCaptureVideo;
int m_nID;
camera_data_cb m_cb;
void* m_pParam;
};
再声明一个变量:
CSampleGrabberCB m_cGrabberCB;
在OpenCamera里面,m_pSampleGrabber->SetOneShot(TRUE);的地方修改一下:
#ifndef ISampleGrabberCB_CALLBACK
m_pSampleGrabber->SetOneShot(TRUE);
#else
m_pSampleGrabber->SetOneShot(FALSE);
m_pSampleGrabber->SetCallback(&m_cGrabberCB, 1);
#endif
然后,不再调用 QueryFrame,另外实现三个控制运行停止的函数:
HRESULT CCameraDS::Run()
{
HRESULT hr = S_OK;
OAFilterState state;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Run();
state = State_Stopped;
while (State_Running != state && SUCCEEDED(hr))
{
hr = m_pMediaControl->GetState(100, &state);
Sleep(100);
}
return hr;
}
HRESULT CCameraDS::Pause()
{
HRESULT hr = S_OK;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Pause();
return hr;
}
HRESULT CCameraDS::Stop()
{
HRESULT hr = S_OK;
OAFilterState state;
if( !m_pMediaControl )
{
return E_UNEXPECTED;
}
hr = m_pMediaControl->Stop();
state = State_Running;
while( State_Stopped != state && SUCCEEDED(hr))
{
hr = m_pMediaControl->GetState(100, &state);
Sleep(100);
}
return hr;
}
这样看起来清爽多了。
使用的时候,m_CamDS.OpenCamera之后,再调用m_CamDS.Run();。
然后,STDMETHODIMP CCameraDS::CSampleGrabberCB::BufferCB( double dblSampleTime, BYTE* pBuffer, long lBufferSize )
就得到视频了。我们可以在外面定义一个回调函数,有视频就回调给外面进行处理。
工程是用vc2008创建的。vc6也没问题,不过以后要调用ffmpeg sdk, vc6会比较麻烦,所以这里使用了vc2008。
代码临时放在附件里,找个时间再传到git或sf上。
代码是想到哪里就写到哪里,可能有点乱。这个项目的目的是最简原则,先用最简单的方法实现,然后再慢慢优化。最好的方法应该是用Qt来开发。暂时先用vc2008对付着。
完整代码在 https://github.com/sxcong/rttim
附件代码只包含本章内容,适合循序渐进看。
Client.rar