Media Foundation 捕获摄像头并采样编码为h265后保存,ffmpeg 6.0版本
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <atomic>
#include <conio.h> // 用于检测用户按键
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <mferror.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>
#include<libavutil/imgutils.h>
#include<libswscale/swscale.h>
}
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
using namespace std;
// 全局变量
std::queue<AVFrame*> videoQueue;
std::mutex queueMutex;
std::condition_variable queueCV;
std::atomic<bool> stopCapture(false); // 使用原子变量确保线程安全
#define VIDEO_WIDTH 640
#define VIDEO_HEIGHT 480
#define FRAME_RATE 25
void CaptureVideoThread() {
cout << "Start Capture Video Device..." << endl;
HRESULT hr = MFStartup(MF_VERSION);
IMFMediaSource* pSource = nullptr;
IMFAttributes* pConfig = nullptr;
IMFActivate** ppDevices = nullptr;
UINT32 count = 0;
// 创建属性存储以保存搜索条件
hr = MFCreateAttributes(&pConfig, 1);
if (SUCCEEDED(hr)) {
wcout << "Create an video attribute success!" << endl;
hr = pConfig->SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
}
// 枚举设备
if (SUCCEEDED(hr)) {
wcout << "video device SetGUID success!" << endl;
hr = MFEnumDeviceSources(pConfig, &ppDevices, &count);
}
if (SUCCEEDED(hr) && count > 0) {
wcout << L"Video Device count:" << count << endl;
// 激活第一个视频设备
hr = ppDevices[0]->ActivateObject(IID_PPV_ARGS(&pSource));
if (FAILED(hr)) {
wcerr << L"激活设备失败!" << endl;
goto cleanup;
}
IMFSourceReader* pReader = nullptr;
hr = MFCreateSourceReaderFromMediaSource(pSource, nullptr, &pReader);
if (FAILED(hr)) {
wcerr << L"创建源失败!" << endl;
goto cleanup;
}
// 设置视频格式
IMFMediaType* pVideoType = nullptr;
hr = MFCreateMediaType(&pVideoType);
if (SUCCEEDED(hr)) {
hr = pVideoType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
hr = pVideoType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12);
hr = MFSetAttributeSize(pVideoType, MF_MT_FRAME_SIZE, VIDEO_WIDTH, VIDEO_HEIGHT);
hr = MFSetAttributeRatio(pVideoType, MF_MT_FRAME_RATE, FRAME_RATE, 1);
hr = MFSetAttributeRatio(pVideoType, MF_MT_PIXEL_ASPECT_RATIO, 1, 1);
hr = pReader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, pVideoType);
}
if (SUCCEEDED(hr)) {
wcout << L"视频格式设置成功!" << endl;
}
else {
wcout << L"视频格式设置失败!" << endl;
goto cleanup;
}
// 开始读取视频帧
while (!stopCapture) {
DWORD streamIndex = 0;
DWORD flags = 0;
LONGLONG timestamp = 0;
IMFSample* pSample = nullptr;
hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &streamIndex, &flags, ×tamp, &pSample);
if (SUCCEEDED(hr) && pSample) {
IMFMediaBuffer* pBuffer = nullptr;
hr = pSample->ConvertToContiguousBuffer(&pBuffer);
if (SUCCEEDED(hr)) {
BYTE* pData = nullptr;
DWORD maxLength = 0, currentLength = 0;
hr = pBuffer->Lock(&pData, &maxLength, ¤tLength);
if (SUCCEEDED(hr)) {
// 分配 AVFrame 并填充数据
AVFrame* frame = av_frame_alloc();
frame->format = AV_PIX_FMT_NV12;
frame->width = VIDEO_WIDTH;
frame->height = VIDEO_HEIGHT;
av_frame_get_buffer(frame, 32);
memcpy(frame->data[0], pData, VIDEO_WIDTH * VIDEO_HEIGHT); // Y 分量
memcpy(frame->data[1], pData + VIDEO_WIDTH * VIDEO_HEIGHT, VIDEO_WIDTH * VIDEO_HEIGHT / 2); // UV 分量
// 将帧放入队列
{
lock_guard<mutex> lock(queueMutex);
videoQueue.push(frame);
}
queueCV.notify_one();
pBuffer->Unlock();
}
pBuffer->Release();
}
pSample->Release();
}
}
cleanup:
if (pSource) pSource->Release();
if (pConfig) pConfig->Release();
if (ppDevices) CoTaskMemFree(ppDevices);
MFShutdown();
}
}
AVCodecContext* InitH265Encoder(AVFormatContext* fmt_ctx, AVStream** video_stream) {
const AVCodec* codec = avcodec_find_encoder_by_name("libx265");
if (!codec) {
wcerr << L"Cannot find H265 encoder!" << endl;
return nullptr;
}
AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
wcerr << L"Cannot allocate video codec context!" << endl;
return nullptr;
}
codec_ctx->bit_rate = 400000;
codec_ctx->width = VIDEO_WIDTH;
codec_ctx->height = VIDEO_HEIGHT;
codec_ctx->time_base = { 1, 30 };
codec_ctx->framerate = { 25, 1 };
codec_ctx->gop_size = 10;
codec_ctx->max_b_frames = 1;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
if (avcodec_open2(codec_ctx, codec, nullptr) < 0) {
wcerr << L"Cannot open H265 encoder!" << endl;
avcodec_free_context(&codec_ctx);
return nullptr;
}
*video_stream = avformat_new_stream(fmt_ctx, nullptr);
if (!*video_stream) {
wcerr << L"Cannot create video stream!" << endl;
avcodec_free_context(&codec_ctx);
return nullptr;
}
(*video_stream)->time_base = codec_ctx->time_base;
if (avcodec_parameters_from_context((*video_stream)->codecpar, codec_ctx) < 0) {
wcerr << L"Cannot copy codec parameters to stream!" << endl;
avcodec_free_context(&codec_ctx);
return nullptr;
}
return codec_ctx;
}
int main() {
avformat_network_init();
AVFormatContext* fmt_ctx = nullptr;
avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, "out.mp4");
if (!fmt_ctx) {
cerr << "Could not create output context" << endl;
return -1;
}
AVStream* video_stream = nullptr;
AVCodecContext* codec_ctx = InitH265Encoder(fmt_ctx, &video_stream);
if (!codec_ctx) {
cerr << "Failed to initialize H265 encoder" << endl;
return -1;
}
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&fmt_ctx->pb, "out.mp4", AVIO_FLAG_WRITE) < 0) {
cerr << "Could not open output file" << endl;
return -1;
}
}
if (avformat_write_header(fmt_ctx, nullptr) < 0) {
cerr << "Error occurred when writing header to output file" << endl;
return -1;
}
thread captureThread(CaptureVideoThread);
SwsContext* sws_ctx = sws_getContext(VIDEO_WIDTH, VIDEO_HEIGHT, AV_PIX_FMT_NV12,
VIDEO_WIDTH, VIDEO_HEIGHT, AV_PIX_FMT_YUV420P,
SWS_BILINEAR, nullptr, nullptr, nullptr);
AVFrame* yuv_frame = av_frame_alloc();
yuv_frame->format = AV_PIX_FMT_YUV420P;
yuv_frame->width = VIDEO_WIDTH;
yuv_frame->height = VIDEO_HEIGHT;
av_frame_get_buffer(yuv_frame, 32);
int frame_count = 0;
// 启动一个线程监听用户输入
thread userInputThread([&]() {
cout << "Press 'q' to quit..." << endl;
while (!_kbhit()) { // 等待用户按键
this_thread::sleep_for(chrono::milliseconds(100));
}
char ch = _getch(); // 获取按键
if (ch == 'q') {
stopCapture = true; // 设置停止标志
cout << "Exiting..." << endl;
}
});
while (!stopCapture || !videoQueue.empty()) {
AVFrame* frame = nullptr;
{
unique_lock<mutex> lock(queueMutex);
queueCV.wait(lock, [] { return !videoQueue.empty(); });
frame = videoQueue.front();
videoQueue.pop();
}
if (!frame) break;
// 转换 NV12 到 YUV420P
sws_scale(sws_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, VIDEO_HEIGHT,
yuv_frame->data, yuv_frame->linesize);
yuv_frame->pts = frame_count++;
// 发送帧到编码器
if (avcodec_send_frame(codec_ctx, yuv_frame) == 0) {
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
pkt.stream_index = video_stream->index;
av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base);
av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
}
}
av_frame_free(&frame);
}
// 刷新编码器
avcodec_send_frame(codec_ctx, nullptr);
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
while (avcodec_receive_packet(codec_ctx, &pkt) == 0) {
pkt.stream_index = video_stream->index;
av_packet_rescale_ts(&pkt, codec_ctx->time_base, video_stream->time_base);
av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
}
// 清理资源
av_write_trailer(fmt_ctx);
avcodec_free_context(&codec_ctx);
av_frame_free(&yuv_frame);
sws_freeContext(sws_ctx);
if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
avio_closep(&fmt_ctx->pb);
}
avformat_free_context(fmt_ctx);
stopCapture = true;
captureThread.join();
userInputThread.join();
cout << "Video encoding completed successfully." << endl;
return 0;
}
CMakeLists.txt文件:
# 设置最低 CMake 版本要求
cmake_minimum_required(VERSION 3.10)
# 定义项目名称和使用的语言
project(videocapture1 LANGUAGES CXX)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找 FFmpeg 库
# 假设 FFmpeg 安装在 /usr/local/ffmpeg
set(FFMPEG_ROOT ${PROJECT_SOURCE_DIR}/thirdparty/ffmpeg) # 如果是默认路径,可以省略此行
# 指定 FFmpeg 的头文件路径
include_directories(${FFMPEG_ROOT}/include)
# 指定 FFmpeg 的库文件路径
link_directories(${FFMPEG_ROOT}/lib)
# 定义需要链接的 FFmpeg 库
# FFmpeg 的主要库包括:avcodec, avformat, avutil, swscale, swresample 等
set(FFMPEG_LIBS
avcodec
avformat
avutil
swscale
swresample
)
set(MF_LIBS
Mfplat
Mf
mfreadwrite
mfuuid
)
# 添加可执行文件
add_executable(${PROJECT_NAME} videocapture1.cpp)
# 链接 FFmpeg 库
target_link_libraries(${PROJECT_NAME} ${FFMPEG_LIBS} ${MF_LIBS})