win音视频--Dshow视频采集显示

本文提供了一个简单的音视频基础程序,演示了如何使用DShow创建窗口并显示视频。从窗口创建、COM初始化到视频显示,逐步指导入门者了解DShow音视频开发的基本流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

         一个粗糙的显示小程序,完成了窗口的创建,com的初始化及dshow视频的显示。dshow的文章虽说不少,但不适合入门,从小到大,有能运行的程序然后慢慢的完善,就是入门。一个入门的小程序,希望像我一样的同学门,找到希望,找到dshow音视频开发的大门。先贴出来文章,然后整理。

#include "stdafx.h"
#include <DShow.h>
#include <Guiddef.h>
#include <strmif.h>

#define CHECK_HR(s)	if (FAILED(s)) {return 1;}
#define SAFE_RELEASE(p)     do { if ((p)) { (p)->Release(); (p) = NULL; } } while(0)

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

#pragma comment(lib, "Dxguid.lib")
#pragma comment(lib, "Strmiids.lib")

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style          = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc    = WndProc;
	wcex.cbClsExtra     = 0;
	wcex.cbWndExtra     = 0;
	wcex.hInstance      = hInstance;
	wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
	wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName   = NULL;
	wcex.lpszClassName  = _T("aaaaa");
	wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

	if (!RegisterClassEx(&wcex))
	{
		MessageBox(NULL, _T("Call to RegisterClassEx failed!"),_T("Win32 Guided Tour"), NULL);

		return 1;
	}

	IGraphBuilder		  *m_pGraph;
	ICaptureGraphBuilder2 *m_pBuild;
	IBaseFilter			  *m_pSrc;
	IMediaControl         *m_pMediaControl;
	IVideoWindow          *m_pVidWin;

	CoInitialize(NULL);
	int nRes = 0;
	HRESULT hr =CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void**)&m_pGraph);
	CHECK_HR(1);

	hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&m_pBuild);
	CHECK_HR(2);

	hr = m_pBuild->SetFiltergraph(m_pGraph);
	CHECK_HR(3);

	hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&m_pVidWin);
	CHECK_HR(4);

	ICreateDevEnum *pDevEnum=NULL;
	IEnumMoniker *pClsEnum=NULL;
	IMoniker *pMoniker = NULL;
	//创建设备枚举COM对象
	hr = CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC,IID_ICreateDevEnum,(void **)&pDevEnum);
	CHECK_HR(5);

	//创建视频采集设备枚举COM对象
	hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pClsEnum,0);
	CHECK_HR(6);

	int i = 0;
	while(i <= 0)
	{
		hr = pClsEnum->Next(1,&pMoniker,NULL);
		++i;
	}
	CHECK_HR(7);

	hr = pMoniker->BindToObject(0,0,IID_IBaseFilter,(void **)&m_pSrc);
	CHECK_HR(8);

	SAFE_RELEASE(pMoniker);
	SAFE_RELEASE(pClsEnum);
	SAFE_RELEASE(pDevEnum);


	//将设备添加到管理器graph
	hr = m_pGraph->AddFilter(m_pSrc, L"Video Capture");
	CHECK_HR(9);

	hr=m_pBuild->RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,m_pSrc,NULL,NULL);
	CHECK_HR(10);

	HWND m_hWnd = ::CreateWindowA("STATIC","ds_video_preview",WS_POPUP,100,100,500,500,NULL,NULL,NULL,NULL);
	ShowWindow(m_hWnd,	nCmdShow);
	UpdateWindow(m_hWnd);


	if (m_hWnd == NULL)
	{
		nRes = 11;
	}

	m_pVidWin->put_Owner((OAHWND)m_hWnd);
	m_pVidWin->SetWindowPosition(100, 100, 500, 500);
	m_pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);

	hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pMediaControl);
	CHECK_HR(12);

	hr = m_pMediaControl->Run();
	

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int) msg.wParam;

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	PAINTSTRUCT ps;
	HDC hdc;
	TCHAR greeting[] = _T("Hello, World!");

	switch (message)
	{
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc,5, 5,greeting, _tcslen(greeting));
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
		break;
	}

	return 0;
}

环境:vs2008 + win7 + directx90

<think>根据用户需求,我们将创建一个音视频采集类,使用Qt5实现。这个类将包括视频采集(摄像头)和音频采集(麦克风)功能,并能够将采集到的音视频帧通过信号发送出去,以便后续处理(如编码、传输或保存)。 ### 设计思路: 1. **视频采集**:使用`QCamera`和`QVideoProbe`捕获视频帧。 2. **音频采集**:使用`QAudioInput`捕获音频数据。 3. **封装成类**:创建一个名为`AVCapture`的类,提供开始、停止采集的接口,并发送音视频数据信号。 ### 实现步骤: #### 1. 创建音视频采集类`AVCapture` 头文件`AVCapture.h`: ```cpp #ifndef AVCAPTURE_H #define AVCAPTURE_H #include <QObject> #include <QCamera> #include <QVideoProbe> #include <QAudioInput> #include <QAudioDeviceInfo> #include <QAudioFormat> #include <QVideoFrame> class AVCapture : public QObject { Q_OBJECT public: explicit AVCapture(QObject *parent = nullptr); ~AVCapture(); bool startCapture(); // 开始采集 void stopCapture(); // 停止采集 signals: void videoFrameCaptured(const QVideoFrame &frame); // 视频帧信号 void audioDataReady(const QByteArray &data); // 音频数据信号 private slots: void onVideoFrameProbed(const QVideoFrame &frame); // 处理视频帧 private: QCamera *camera = nullptr; // 摄像头 QVideoProbe *videoProbe = nullptr; // 视频探测器 QAudioInput *audioInput = nullptr; // 音频输入 QIODevice *audioDevice = nullptr; // 音频设备(用于读取数据) }; #endif // AVCAPTURE_H ``` #### 2. 实现`AVCapture`类 源文件`AVCapture.cpp`: ```cpp #include "AVCapture.h" AVCapture::AVCapture(QObject *parent) : QObject(parent) { } AVCapture::~AVCapture() { stopCapture(); } bool AVCapture::startCapture() { // 初始化摄像头 QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); if (cameras.isEmpty()) { qWarning() << "No camera found!"; return false; } camera = new QCamera(cameras.first(), this); videoProbe = new QVideoProbe(this); // 连接视频帧探测信号 if (!videoProbe->setSource(camera)) { qWarning() << "Failed to set video probe source!"; return false; } connect(videoProbe, &QVideoProbe::videoFrameProbed, this, &AVCapture::onVideoFrameProbed); // 初始化音频采集 QAudioFormat format; format.setSampleRate(44100); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); QAudioDeviceInfo audioDeviceInfo = QAudioDeviceInfo::defaultInputDevice(); if (!audioDeviceInfo.isFormatSupported(format)) { format = audioDeviceInfo.nearestFormat(format); } audioInput = new QAudioInput(audioDeviceInfo, format, this); audioDevice = audioInput->start(); // 开始音频采集,并获取音频设备 // 连接音频数据可读信号 connect(audioDevice, &QIODevice::readyRead, [this]() { QByteArray data = audioDevice->readAll(); if (!data.isEmpty()) { emit audioDataReady(data); // 发送音频数据信号 } }); // 启动摄像头 camera->start(); return true; } void AVCapture::stopCapture() { if (camera) { camera->stop(); delete camera; camera = nullptr; } if (videoProbe) { videoProbe->deleteLater(); videoProbe = nullptr; } if (audioInput) { audioInput->stop(); delete audioInput; audioInput = nullptr; audioDevice = nullptr; } } void AVCapture::onVideoFrameProbed(const QVideoFrame &frame) { // 直接转发视频帧信号 emit videoFrameCaptured(frame); } ``` ### 使用示例: 在需要采集音视频的地方,创建`AVCapture`对象并连接信号: ```cpp AVCapture *capture = new AVCapture(this); if (capture->startCapture()) { qDebug() << "Capture started!"; connect(capture, &AVCapture::videoFrameCaptured, this, [](const QVideoFrame &frame) { // 处理视频帧 QVideoFrame cloneFrame(frame); if (cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) { // 获取帧数据(注意:格式可能是多种,如YUV、RGB等) const uchar *data = cloneFrame.bits(); // ... 处理或保存视频帧 cloneFrame.unmap(); } }); connect(capture, &AVCapture::audioDataReady, this, [](const QByteArray &data) { // 处理音频数据(PCM格式) // ... 处理或保存音频数据 }); } else { qDebug() << "Capture failed!"; } ``` ### 注意事项: 1. **权限**:在移动端(Android/iOS)需要申请摄像头和麦克风权限。 2. **视频帧格式**:`QVideoFrame`可能返回不同格式的视频帧(如YUV、RGB),需要根据实际情况转换。常见的转换方法是使用`QVideoFrame::pixelFormat()`判断格式,然后使用FFmpeg或Qt方法转换到所需格式(如YUV420P)[^1]。 3. **音频格式**:音频数据是PCM格式,参数由`QAudioFormat`指定。如果后续需要编码,需注意格式匹配。 4. **资源释放**:停止采集时,确保释放所有资源。 5. **跨平台**:在Linux平台,Qt多媒体功能可能有限,建议使用FFmpeg作为补充[^3]。 ### 扩展功能: - **音视频同步**:可以在视频帧和音频数据中加入时间戳,然后以视频为基准进行同步。 - **录制功能**:结合FFmpeg将采集到的音视频数据编码并保存到文件[^2]。 §§相关问题§§ 1. 如何将采集到的QVideoFrame转换为YUV420P格式? 2. 在Qt中如何实现音视频同步? 3. 如何将采集音视频数据通过FFmpeg编码并保存为MP4文件? 4. 在Android平台上使用Qt进行音视频采集需要注意什么? 5. 如何实现屏幕捕获(而不仅仅是摄像头)?</think>### 使用Qt5创建音视频采集类的实现方案 下面是一个完整的音视频采集类实现方案,结合Qt5多媒体模块和FFmpeg实现高效采集: #### 1. 音视频采集类头文件设计 ```cpp // AVRecorder.h #include <QObject> #include <QCamera> #include <QVideoProbe> #include <QAudioInput> #include <QAudioDeviceInfo> #include <QAudioBuffer> #include <QThread> #include <QAtomicInteger> extern "C" { #include <libavformat/avformat.h> } class AVRecorder : public QObject { Q_OBJECT public: explicit AVRecorder(QObject *parent = nullptr); ~AVRecorder(); bool startCapture(const QString &outputFile); void stopCapture(); // 设置视频参数 void setVideoSettings(int width, int height, int fps); // 设置音频参数 void setAudioSettings(int sampleRate, int channels); signals: void errorOccurred(const QString &message); void captureStatusChanged(bool isCapturing); private slots: void handleVideoFrame(const QVideoFrame &frame); void handleAudioData(); private: // 初始化FFmpeg上下文 bool initFFmpegContext(const QString &filename); // 视频处理线程 void processVideoFrame(const QVideoFrame &frame); // 音频处理线程 void processAudioData(const QByteArray &data); QCamera *m_camera = nullptr; QVideoProbe *m_videoProbe = nullptr; QAudioInput *m_audioInput = nullptr; QIODevice *m_audioDevice = nullptr; // FFmpeg相关成员 AVFormatContext *m_formatCtx = nullptr; AVStream *m_videoStream = nullptr; AVStream *m_audioStream = nullptr; // 同步控制 QAtomicInteger<bool> m_isCapturing{false}; int64_t m_videoPts = 0; int64_t m_audioPts = 0; // 配置参数 struct { int width = 1280; int height = 720; int fps = 30; int sampleRate = 44100; int channels = 2; } m_settings; }; ``` #### 2. 音视频采集类实现 ```cpp // AVRecorder.cpp #include "AVRecorder.h" AVRecorder::AVRecorder(QObject *parent) : QObject(parent) { // 初始化摄像头 QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); if (!cameras.isEmpty()) { m_camera = new QCamera(cameras.first()); m_videoProbe = new QVideoProbe(this); // 设置视频参数 QCameraViewfinderSettings settings; settings.setResolution(m_settings.width, m_settings.height); settings.setMaximumFrameRate(m_settings.fps); m_camera->setViewfinderSettings(settings); // 连接视频帧信号 connect(m_videoProbe, &QVideoProbe::videoFrameProbed, this, &AVRecorder::handleVideoFrame); } // 初始化音频输入 QAudioFormat format; format.setSampleRate(m_settings.sampleRate); format.setChannelCount(m_settings.channels); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); QAudioDeviceInfo audioDevice = QAudioDeviceInfo::defaultInputDevice(); if (!audioDevice.isFormatSupported(format)) { format = audioDevice.nearestFormat(format); } m_audioInput = new QAudioInput(audioDevice, format, this); } AVRecorder::~AVRecorder() { stopCapture(); delete m_camera; delete m_audioInput; } bool AVRecorder::startCapture(const QString &outputFile) { if (m_isCapturing) return false; // 初始化FFmpeg上下文 if (!initFFmpegContext(outputFile)) { emit errorOccurred("Failed to initialize FFmpeg context"); return false; } // 开始视频采集 if (m_camera) { m_videoProbe->setSource(m_camera); m_camera->start(); } // 开始音频采集 if (m_audioInput) { m_audioDevice = m_audioInput->start(); connect(m_audioDevice, &QIODevice::readyRead, this, &AVRecorder::handleAudioData); } m_isCapturing = true; emit captureStatusChanged(true); return true; } void AVRecorder::stopCapture() { if (!m_isCapturing) return; // 停止视频采集 if (m_camera && m_camera->state() == QCamera::ActiveState) { m_camera->stop(); } // 停止音频采集 if (m_audioInput) { m_audioInput->stop(); disconnect(m_audioDevice, nullptr, this, nullptr); } // 写入FFmpeg尾部并关闭 if (m_formatCtx) { av_write_trailer(m_formatCtx); avio_closep(&m_formatCtx->pb); avformat_free_context(m_formatCtx); m_formatCtx = nullptr; } m_isCapturing = false; emit captureStatusChanged(false); } void AVRecorder::handleVideoFrame(const QVideoFrame &frame) { if (!m_isCapturing) return; // 在独立线程处理视频帧避免阻塞 QtConcurrent::run([this, frame]() { processVideoFrame(frame); }); } void AVRecorder::handleAudioData() { if (!m_isCapturing || !m_audioDevice) return; // 读取音频数据 QByteArray data = m_audioDevice->readAll(); if (data.isEmpty()) return; // 在独立线程处理音频 QtConcurrent::run([this, data]() { processAudioData(data); }); } bool AVRecorder::initFFmpegContext(const QString &filename) { avformat_alloc_output_context2(&m_formatCtx, nullptr, nullptr, filename.toUtf8().constData()); if (!m_formatCtx) return false; // 添加视频流 m_videoStream = avformat_new_stream(m_formatCtx, nullptr); // 配置视频编码器参数... // 添加音频流 m_audioStream = avformat_new_stream(m_formatCtx, nullptr); // 配置音频编码器参数... // 打开输出文件 if (avio_open(&m_formatCtx->pb, filename.toUtf8().constData(), AVIO_FLAG_WRITE) < 0) { return false; } // 写入文件头 if (avformat_write_header(m_formatCtx, nullptr) < 0) { return false; } return true; } void AVRecorder::processVideoFrame(const QVideoFrame &frame) { if (!m_isCapturing || !m_formatCtx) return; QVideoFrame cloneFrame(frame); if (!cloneFrame.map(QAbstractVideoBuffer::ReadOnly)) return; // 将QVideoFrame转换为AVFrame AVFrame *avFrame = av_frame_alloc(); // 配置帧参数... // 设置时间戳 (毫秒转微秒) avFrame->pts = m_videoPts; m_videoPts += 1000000 / m_settings.fps; // 编码并写入帧 // ... (引用[2]中的回调机制) cloneFrame.unmap(); av_frame_free(&avFrame); } void AVRecorder::processAudioData(const QByteArray &data) { if (!m_isCapturing || !m_formatCtx) return; // 创建AVFrame并填充PCM数据 AVFrame *frame = av_frame_alloc(); // 配置音频帧参数... // 设置时间戳 frame->pts = m_audioPts; m_audioPts += data.size() / (m_settings.channels * sizeof(int16_t)) * 1000000 / m_settings.sampleRate; // 编码并写入帧 // ... (引用[2]中的回调机制) av_frame_free(&frame); } ``` #### 3. 使用示例 ```cpp // main.cpp #include "AVRecorder.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); AVRecorder recorder; // 设置采集参数 recorder.setVideoSettings(1920, 1080, 30); recorder.setAudioSettings(48000, 2); // 开始采集 if (!recorder.startCapture("output.mp4")) { qDebug() << "Failed to start capture"; return -1; } // 创建简单UI QPushButton stopButton("Stop Capture"); QObject::connect(&stopButton, &QPushButton::clicked, [&]() { recorder.stopCapture(); QApplication::quit(); }); stopButton.show(); return app.exec(); } ``` ### 关键技术要点 1. **多线程处理架构** - 使用`QtConcurrent`实现音视频数据的并行处理 - 视频处理线程:`processVideoFrame()` - 音频处理线程:`processAudioData()` - 避免阻塞主线程和采集线程[^1] 2. **时间戳同步机制** ```cpp // 视频时间戳计算 avFrame->pts = m_videoPts; m_videoPts += 1000000 / m_settings.fps; // 按帧率递增 // 音频时间戳计算 frame->pts = m_audioPts; m_audioPts += data.size() / (channels * sizeof(int16_t)) * 1000000 / sampleRate; ``` 3. **FFmpeg集成** - 使用`avformat_alloc_output_context2`创建输出容器 - 通过`avformat_new_stream`添加音视频- 实现音视频帧编码回调机制(参考引用[2]) 4. **资源管理** ```cpp ~AVRecorder() { stopCapture(); // 确保资源释放 // ... } void stopCapture() { // 清理FFmpeg资源 av_write_trailer(m_formatCtx); avio_closep(&m_formatCtx->pb); avformat_free_context(m_formatCtx); // ... } ``` ### 注意事项 1. **平台兼容性处理** ```cpp // Windows系统音频采集特殊处理 #if defined(Q_OS_WIN) avformat_alloc_output_context2(&m_formatCtx, NULL, "dshow", "audio=virtual-audio-capturer"); #endif ``` 2. **权限管理** - Android/iOS需在`AndroidManifest.xml`/`Info.plist`中添加权限声明 - 运行时动态请求权限: ```cpp #if defined(Q_OS_ANDROID) QtAndroid::PermissionResult result = QtAndroid::checkPermission("android.permission.CAMERA"); // ... #endif ``` 3. **性能优化** - 使用`QVideoFrame::map()`的`NoLock`模式减少内存拷贝 - 设置合适的视频缓冲区大小: ```cpp m_camera->setViewfinderSettings(settings); m_camera->setCaptureMode(QCamera::CaptureVideo); ``` 4. **错误处理** ```cpp bool initFFmpegContext(...) { // ... if (avio_open(...) < 0) { emit errorOccurred("Failed to open output file"); return false; } } ``` 此实现结合了Qt5多媒体框架的易用性和FFmpeg的强大编解码能力,适合需要高质量音视频采集的应用场景。实际部署时需根据目标平台进行针对性优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值