如何捕获摄像头视频流?

雪夜之狼 2010-11-17 10:29:08
hi
我准备写一个C#的远程视频会议系统。目前可以捕获视频和音频到文件,但是如何才能把视频和音频以流(IOStream)的方式捕获,以便进行传输?我是用DirectX.Capture实现捕获到文件的,它似乎没有办法捕获到流...
...全文
1032 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
szzzp110 2010-11-30
  • 打赏
  • 举报
回复
C# directShow中有 callback事件的
雨师88 2010-11-30
  • 打赏
  • 举报
回复
private int intPosWrite = 0;//内存流中写指针位移
private int intPosPlay = 0;//内存流中播放指针位移
private int intNotifySize = 5000;//设置通知大小

/// <summary>
/// 从字节数组中获取音频数据,并进行播放
/// </summary>
/// <param name="intRecv">字节数组长度</param>
/// <param name="bytRecv">包含音频数据的字节数组</param>
public void GetVoiceData(int intRecv, byte[] bytRecv)
{
//intPosWrite指示最新的数据写好后的末尾。intPosPlay指示本次播放开始的位置。
if (intPosWrite + intRecv <= memstream.Capacity)
{//如果当前写指针所在的位移+将要写入到缓冲区的长度小于缓冲区总大小
if ((intPosWrite - intPosPlay >= 0 && intPosWrite - intPosPlay < intNotifySize) || (intPosWrite - intPosPlay < 0 && intPosWrite - intPosPlay + memstream.Capacity < intNotifySize))
{
memstream.Write(bytRecv, 0, intRecv);
intPosWrite += intRecv;

}
else if (intPosWrite - intPosPlay >= 0)
{//先存储一定量的数据,当达到一定数据量时就播放声音。
buffDiscript.BufferBytes = intPosWrite - intPosPlay;//缓冲区大小为播放指针到写指针之间的距离。
SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一个合适的缓冲区用于播放这段数据。
memstream.Position = intPosPlay;//先将memstream的指针定位到这一次播放开始的位置
sec.Write(0, memstream, intPosWrite - intPosPlay, LockFlag.FromWriteCursor);
sec.Play(0, BufferPlayFlags.Default);
memstream.Position = intPosWrite;//写完后重新将memstream的指针定位到将要写下去的位置。
intPosPlay = intPosWrite;
}
else if (intPosWrite - intPosPlay < 0)
{
buffDiscript.BufferBytes = intPosWrite - intPosPlay + memstream.Capacity;//缓冲区大小为播放指针到写指针之间的距离。
SecondaryBuffer sec = new SecondaryBuffer(buffDiscript, PlayDev);//建立一个合适的缓冲区用于播放这段数据。
memstream.Position = intPosPlay;
sec.Write(0, memstream, memstream.Capacity - intPosPlay, LockFlag.FromWriteCursor);
memstream.Position = 0;
sec.Write(memstream.Capacity - intPosPlay, memstream, intPosWrite, LockFlag.FromWriteCursor);
sec.Play(0, BufferPlayFlags.Default);
memstream.Position = intPosWrite;
intPosPlay = intPosWrite;
}
}
else
{//当数据将要大于memstream可容纳的大小时
int irest = memstream.Capacity - intPosWrite;//memstream中剩下的可容纳的字节数。
memstream.Write(bytRecv, 0, irest);//先写完这个内存流。
memstream.Position = 0;//然后让新的数据从memstream的0位置开始记录
memstream.Write(bytRecv, irest, intRecv - irest);//覆盖旧的数据
intPosWrite = intRecv - irest;//更新写指针位置。写指针指示下一个开始写入的位置而不是上一次结束的位置,因此不用减一
}
}
雨师88 2010-11-30
  • 打赏
  • 举报
回复
public const int cNotifyNum = 16; // 缓冲队列的数目

private int mNextCaptureOffset = 0; // 该次录音缓冲区的起始值
private int mSampleCount = 0; // 录制的样本数目

private int mNotifySize = 0; // 每次通知大小
private int mBufferSize = 0; // 缓冲队列大小

private string mFileName = string.Empty; // 文件名
private FileStream mWaveFile = null; // 文件流
private BinaryWriter mWriter = null; // 写文件
private MemoryStream _memStream = null;

private Capture mCapDev = null; // 音频捕捉设备
private CaptureBuffer mRecBuffer = null; // 缓冲区对象
private Notify mNotify = null; // 消息通知对象

private WaveFormat mWavFormat; // 录音的格式
private Thread mNotifyThread = null; // 处理缓冲区消息的线程
private AutoResetEvent mNotificationEvent = null; // 通知事件


public Form3()
{
InitializeComponent();

//初始化音频捕捉设备
InitCaptureDevice();

//设定录音格式
mWavFormat = CreateWaveFormat();
}

/// <summary>
/// 设定录音结束后保存的文件,包括路径
/// </summary>
/// <param name="filename"></param>
public void SetFileName(string filename)
{
mFileName = filename;
}

/// <summary>
/// 开始录音
/// </summary>
public void RecStart()
{
// 创建录音文件
CreateSoundFile();
byte[] bytMemory = new byte[100000];
_memStream = new MemoryStream(bytMemory, 0, 100000, true, true);
InitCaptureDevice();
// 创建一个录音缓冲区,并开始录音
CreateCaptureBuffe();

// 建立通知消息,当缓冲区满的时候处理方法
InitNotifications();

mRecBuffer.Start(true);
}

/// <summary>
/// 停止录音
/// </summary>
public void RecStop()
{
if (mNotificationEvent != null)
mNotificationEvent.Set();

//停止录音
mRecBuffer.Stop();

// 写入缓冲区最后的数据
RecordCapturedData();

// 回写长度信息
mWriter.Seek(4, SeekOrigin.Begin);
mWriter.Write((int)(mSampleCount + 36)); // 写文件长度
mWriter.Seek(40, SeekOrigin.Begin);
mWriter.Write(mSampleCount); // 写数据长度

mWriter.Close();
mWaveFile.Close();
mWriter = null;
mWaveFile = null;
}

/// <summary>
/// 初始化录音设备,此处使用主录音设备
/// </summary>
/// <returns></returns>
private bool InitCaptureDevice()
{
// 获取默认音频捕捉设备
CaptureDevicesCollection devices = new CaptureDevicesCollection(); // 枚举音频捕捉设备
Guid deviceGuid = Guid.Empty;

if (devices.Count > 0)
deviceGuid = devices[0].DriverGuid;
else
{
MessageBox.Show("系统中没有音频捕捉设备!");
return false;
}

// 用指定的捕捉设备创建Capture对象

try
{
mCapDev = new Capture(deviceGuid);
}
catch (DirectXException e)
{
MessageBox.Show(e.ToString());
return false;
}

return true;
}

/// <summary>
/// 创建录音格式,此处使用16Bit,16KHz,Mono的录音格式
/// </summary>
/// <returns></returns>
private WaveFormat CreateWaveFormat()
{
WaveFormat format = new WaveFormat();

format.FormatTag = WaveFormatTag.Pcm; // PCM
format.SamplesPerSecond = 44100; // 16KHz
format.BitsPerSample = 16; // 16Bit
format.Channels = 2; // Mono
format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));
format.AverageBytesPerSecond = ((format.BitsPerSample) / 8) * format.Channels * format.SamplesPerSecond;

return format;
}

/// <summary>
/// 创建录音使用的缓冲区
/// </summary>
private void CreateCaptureBuffe()
{
// 缓冲区的描述对象
CaptureBufferDescription bufferdescription = new CaptureBufferDescription();

if (mNotify != null)
{
mNotify.Dispose();

mNotify = null;
}

if (mRecBuffer != null)
{
mRecBuffer.Dispose();

mRecBuffer = null;
}

// 设定通知的大小,默认为1s种
mNotifySize = (1024 > mWavFormat.AverageBytesPerSecond / 8) ? 1024 : (mWavFormat.AverageBytesPerSecond / 8);
mNotifySize -= mNotifySize % mWavFormat.BlockAlign;

// 设定缓冲区大小
mBufferSize = mNotifySize * cNotifyNum;

// 创建缓冲区描述
bufferdescription.BufferBytes = mBufferSize;
bufferdescription.Format = mWavFormat; // 录音格式

// 创建缓冲区
mRecBuffer = new CaptureBuffer(bufferdescription, mCapDev);

mNextCaptureOffset = 0;
}

/// <summary>
/// 初始化通知事件,将原缓冲区分成16个缓冲队列,在每个缓冲队列的结束点设定通知点。
/// </summary>
/// <returns>是否成功</returns>
private bool InitNotifications()
{
if (mRecBuffer == null)
{
MessageBox.Show("未创建录音缓冲区!");
return false;
}

// 创建一个通知事件,当缓冲队列满了就激发该事件
mNotificationEvent = new AutoResetEvent(false);

// 创建一个线程管理缓冲区事件
if (mNotifyThread == null)
{
mNotifyThread = new Thread(new ThreadStart(WaitThread));
mNotifyThread.Start();
}

// 设定通知的位置
BufferPositionNotify[] PositionNotify = new BufferPositionNotify[cNotifyNum + 1];
for (int i = 0; i < cNotifyNum; i++)
{
PositionNotify[i].Offset = (mNotifySize * i) + mNotifySize - 1;
PositionNotify[i].EventNotifyHandle = mNotificationEvent.Handle;
}

mNotify = new Notify(mRecBuffer);
mNotify.SetNotificationPositions(PositionNotify, cNotifyNum);

return true;
}

/// <summary>
/// 将录制的数据写入WAV文件
/// </summary>
private void RecordCapturedData()
{
byte[] CaptureData = null;
int ReadPos, CapturePos, LockSize;

mRecBuffer.GetCurrentPosition(out CapturePos, out ReadPos);
LockSize = ReadPos - mNextCaptureOffset;
if (LockSize < 0)
LockSize += mBufferSize;

LockSize -= (LockSize % mNotifySize);

if (LockSize == 0)
return;

// 读取缓冲区内地数据
CaptureData = (byte[])mRecBuffer.Read(mNextCaptureOffset, typeof(byte), LockFlag.None, LockSize);


// 写入WAV文件
mWriter.Write(CaptureData, 0, CaptureData.Length);

_memStream.Write(CaptureData, 0, CaptureData.Length);

// 更新已经录制的数据长度.
mSampleCount += CaptureData.Length;

// 移动录制数据的起始点,通知消息只负责产生消息的位置,并不记录上次录制的位置.
mNextCaptureOffset += CaptureData.Length;
mNextCaptureOffset %= mBufferSize; // Circular buffer
}

/// <summary>
/// 接收缓冲区满消息的处理线程
/// </summary>
private void WaitThread()
{
while (true)
{
// 等待缓冲区的通知消息
mNotificationEvent.WaitOne(Timeout.Infinite, true);

// 录制数据
RecordCapturedData();
}
}

/// <summary>
/// 创建保存的波形文件,并写入必要的文件头
/// </summary>
private void CreateSoundFile()
{

mWaveFile = new FileStream(mFileName, FileMode.Create);
mWriter = new BinaryWriter(mWaveFile);

// Set up file with RIFF chunk info.
char[] ChunkRiff = { 'R','I','F','F' };
char[] ChunkType = { 'W', 'A', 'V', 'E' };
char[] ChunkFmt = { 'f', 'm', 't', ' ' };
char[] ChunkData = { 'd', 'a', 't', 'a' };

short shPad = 1; // File padding
int nFormatChunkLength = 0x10; // Formt chunk length
int nLength = 0; // File Length,minus first 8 bytes of RIFF description. This will be filld in later.
short shBytesPerSample = 0; // Bytes per sample.

// 一个样本点的字节数目
if (mWavFormat.BitsPerSample == 8 && mWavFormat.Channels == 1)
shBytesPerSample = 1;
else if ((mWavFormat.BitsPerSample == 8 && mWavFormat.Channels == 2) || (mWavFormat.BitsPerSample == 16 && mWavFormat.Channels == 1))
shBytesPerSample = 2;
else if (mWavFormat.BitsPerSample == 16 && mWavFormat.Channels == 2)
shBytesPerSample = 4;

// FIFF 块
mWriter.Write(ChunkRiff);
mWriter.Write(nLength);
mWriter.Write(ChunkType);

// WAVE 块
mWriter.Write(ChunkFmt);
mWriter.Write(nFormatChunkLength);
mWriter.Write(shPad);
mWriter.Write(mWavFormat.Channels);
mWriter.Write(mWavFormat.SamplesPerSecond);
mWriter.Write(mWavFormat.AverageBytesPerSecond);
mWriter.Write(shBytesPerSample);
mWriter.Write(mWavFormat.BitsPerSample);

// 数据块
mWriter.Write(ChunkData);
mWriter.Write((int)0); // The sample length will be written in later.
}
vpoint2011 2010-11-30
  • 打赏
  • 举报
回复
用directshow.net 或者openCV+emgucv之类的开源工具可以获取真正视频流。
  • 打赏
  • 举报
回复
用flex + FMS 或者flex+red5
li321 2010-11-30
  • 打赏
  • 举报
回复
你在局域网还是广域网做的?
dxh2122 2010-11-27
  • 打赏
  • 举报
回复
试试这个:http://user.qzone.qq.com/154122890/infocenter
我试过可以,C/S下做的
haoxuea 2010-11-27
  • 打赏
  • 举报
回复
高科技啊。
tjava_net 2010-11-27
  • 打赏
  • 举报
回复
关注中,我也想知道,呵呵
雪夜之狼 2010-11-27
  • 打赏
  • 举报
回复
ok我搞定了....
笨办法,一秒钟抓10张图,每10s完整传输一次,其余的比较和上一张图之间的差异,用文本格式发出去....
Qcontriver 2010-11-20
  • 打赏
  • 举报
回复
帮顶一下,我现在做的项目和你这个太相似了,等待高手指点。
我现在还没有多少思路呢。。。。
crackdung 2010-11-20
  • 打赏
  • 举报
回复
它帮到你

http://www.datastead.com/products/tvideograbber/download.html



my blog
http://ufo-crackerx.blog.163.com/
wbxiaozhong 2010-11-20
  • 打赏
  • 举报
回复
学习了
nannan4 2010-11-20
  • 打赏
  • 举报
回复
关注中
yangquanlaohou 2010-11-19
  • 打赏
  • 举报
回复
关注中关注中关注中
csdn_风中雪狼 2010-11-19
  • 打赏
  • 举报
回复
高科技哟,没有搞过呢,
关注中
fangxqian 2010-11-19
  • 打赏
  • 举报
回复
帮你顶起来!
雪夜之狼 2010-11-18
  • 打赏
  • 举报
回复
而且貌似Silverlight也不能用DirectX.Capture这个库的=。=
雪夜之狼 2010-11-18
  • 打赏
  • 举报
回复
我是准备用Silverlight做的。Silverlight的网络通讯我会,关键是如何把摄像头捕获到的画面用Socket进行传输?DirectX.Capture只能捕获到文件……
happyday1799 2010-11-17
  • 打赏
  • 举报
回复
下面是Silverlight Socket通信简单的例子,你先看看
http://blog.csdn.net/banmuhuangci/archive/2009/05/16/4192031.aspx
加载更多回复(5)
敬告:该系列的课程在抓紧录制更新中,敬请大家关注。敬告:本课程项目仅供学习参考,请不要直接商用,概不负责任何法律责任。 该系列的课程涉及:FFmpeg,WebRTC,SRS,Nginx,Darwin,Live555,等。包括:音视频、流媒体、直播、Android、视频监控28181、等。 我将带领大家一起来学习使用FFmpeg开发视频监控项目,并动手操练。具体内容包括: 一、视频监控的架构和流程二、FFmpeg4.3+SDL2+Qt5开发环境的搭建三、FFmpeg的SDK编程回顾总结并操练四、SDL2.0的编程回顾总结并操练五、颜色空间转换RGB和YUV的原理与实战六、Qt5+FFmpeg本地摄像头采集预览实战七、代码封装:摄像头h264/5编码并存储八、Qt5+FFmpeg单路网络摄像头采集预览九、Qt5+FFmpeg单路网络摄像头采集预览录制会看十、onvif与GB/T-28181的简介  音视频与流媒体是一门很复杂的技术,涉及的概念、原理、理论非常多,很多初学者不学 基础理论,而是直接做项目,往往会看到c/c++的代码时一头雾水,不知道代码到底是什么意思,这是为什么呢?   因为没有学习音视频和流媒体的基础理论,就比如学习英语,不学习基本单词,而是天天听英语新闻,总也听不懂。 所以呢,一定要认真学习基础理论,然后再学习播放器、转码器、非编、流媒体直播、视频监控、等等。   梅老师从事音视频与流媒体行业18年;曾在永新视博、中科大洋、百度、美国Harris广播事业部等公司就职,经验丰富;曾亲手主导广电直播全套项目,精通h.264/h.265/aac,曾亲自参与百度app上的网页播放器等实战产品。  目前全身心自主创业,主要聚焦音视频+流媒体行业,精通音视频加密、流媒体在线转码快编等热门产品。  

110,536

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧