本文的主要內容:演示如何通過編程采集攝像頭的視頻數據。
整體的流程跟《音頻錄制02_編程》類似。
依賴庫
需要依賴4個庫。
extern "C" {
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
}
宏定義
#ifdef Q_OS_WIN
// 格式名稱
#define FMT_NAME "dshow"
// 設備名稱
#define DEVICE_NAME "video=Integrated Camera"
// YUV文件名
#define FILENAME "F:/out.yuv"
#else
#define FMT_NAME "avfoundation"
#define DEVICE_NAME "0"
#define FILENAME "/Users/mj/Desktop/out.yuv"
#endif
#define ERROR_BUF(ret) \
char errbuf[1024]; \
av_strerror(ret, errbuf, sizeof (errbuf));
權限申請
在Mac平台,有2個注意點:
- 需要在Info.plist中添加攝像頭的使用說明,申請攝像頭的使用權限
- 使用Debug模式運行程序
- 不然會出現閃退的情況
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>使用攝像頭采集您的靚照</string>
</dict>
</plist>
注冊設備
在整個程序的運行過程中,只需要執行1次注冊設備的代碼。
// 初始化libavdevice並注冊所有輸入和輸出設備
avdevice_register_all();
獲取輸入格式對象
// 獲取輸入格式對象
AVInputFormat *fmt = av_find_input_format(FMT_NAME);
if (!fmt) {
qDebug() << "av_find_input_format error" << FMT_NAME;
return;
}
打開輸入設備
// 格式上下文
AVFormatContext *ctx = nullptr;
// 傳遞給輸入設備的參數
AVDictionary *options = nullptr;
av_dict_set(&options, "video_size", "640x480", 0);
av_dict_set(&options, "pixel_format", "yuyv422", 0);
av_dict_set(&options, "framerate", "30", 0);
// 打開輸入設備
int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, &options);
if (ret < 0) {
ERROR_BUF(ret);
qDebug() << "avformat_open_input error" << errbuf;
return;
}
打開輸出文件
// 打開文件
QFile file(FILENAME);
if (!file.open(QFile::WriteOnly)) {
qDebug() << "file open error" << FILENAME;
// 關閉輸入設備
avformat_close_input(&ctx);
return;
}
采集視頻數據
// 計算每一幀的大小
AVCodecParameters *params = ctx->streams[0]->codecpar;
int imageSize = av_image_get_buffer_size(
(AVPixelFormat) params->format,
params->width, params->height,
1);
// 數據包
AVPacket *pkt = av_packet_alloc();
while (!isInterruptionRequested()) {
// 不斷采集數據
ret = av_read_frame(ctx, pkt);
if (ret == 0) { // 讀取成功
// 將數據寫入文件
file.write((const char *) pkt->data, imageSize);
/*
這里要使用imageSize,而不是pkt->size。
pkt->size有可能比imageSize大(比如在Mac平台),
使用pkt->size會導致寫入一些多余數據到YUV文件中,
進而導致YUV內容無法正常播放
*/
// 釋放資源
av_packet_unref(pkt);
} else if (ret == AVERROR(EAGAIN)) { // 資源臨時不可用
continue;
} else { // 其他錯誤
ERROR_BUF(ret);
qDebug() << "av_read_frame error" << errbuf;
break;
}
}
釋放資源
// 釋放資源
av_packet_free(&pkt);
// 關閉文件
file.close();
// 關閉設備
avformat_close_input(&ctx);