在使用ffmpeg进行音视频开发的时候,很多时候我们需要和各种各样的硬件设备进行交互,包括摄像头、麦克风、各种采集卡等等。当设备数量比较多的时候,设备处理就比较麻烦,这时候我们一般需要枚举设备列表,针对不同的设备采用不同的处理方式。
FFmpeg并没有提供直接的获取硬件设备列表的API,只提供了list_devices配置可以将硬件设备信息输出到终端。为了获取音视频设备列表供后续调用,我们需要捕获终端的日志输出,并把对应的日志信息格式化成我们需要的硬件设备信息。对应的具体操作流程是,在设备信息输出之前调用av_log_set_callback(),设置自定义的日志输出回调函数,将设备信息输出到我们自定义的函数中并进行存储,捕获完毕之后再将回调函数设置回来。这样我们就得到了硬件设备列表信息,然后通过对文本内容进行处理,我们就得到了对应的设备信息。
详细的音视频设备列表的获取流程如下:
#define _CRT_SECURE_NO_WARNINGS
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/time.h>
#include <libavutil/pixfmt.h>
#include <libavutil/display.h>
#include <libavutil/avstring.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavutil/imgutils.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
#include <libavdevice/avdevice.h>
}
#include <string>
#include <stdio.h>
#include <sstream>
#include <vector>
#include <map>
#include <iostream>
#include <Windows.h>
#include <memory>
//windows输出需要转编码
std::string utf8_to_multibytes(const std::string& utf8)
{
int lenWC = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), static_cast<int>(utf8.length()), NULL, 0);
std::unique_ptr<wchar_t> wc(new wchar_t[lenWC]());
::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), static_cast<int>(utf8.length()), wc.get(), lenWC);
int lenMB = ::WideCharToMultiByte(CP_ACP, 0, wc.get(), lenWC, NULL, 0, NULL, NULL);
std::unique_ptr<char> mb(new char[lenMB]());
::WideCharToMultiByte(CP_ACP, 0, wc.get(), lenWC, mb.get(), lenMB, NULL, NULL);
return std::string(mb.get(), lenMB);
}
//设备类型的枚举
enum MediaType
{
UNKNOWN = -1, AUDIO, VIDEO
};
struct Device
{
Device() : deviceType(UNKNOWN) {
}
//设备类型
int deviceType;
//设备的缩略名称
std::string shortName;
//设备完整名称
std::string longName;
//设备的数字编号
std::string numString;
};
//定义回调函数
typedef void(*FFmpegLogCallbackFunc)(void*, int, const char*, va_list);
FFmpegLogCallbackFunc ffmpegLogCallback = av_log_default_callback;
//字符串流用来捕获日志信息
static std::stringstream device_sstrm;
//捕获输出的日志内容
static void logCallbackForDshowInfo(void* ptr, int level, const char* fmt, va_list vl)
{
char buf[1024];
vsprintf(buf, fmt, vl);
device_sstrm << buf;
}
//分割设备的日志信息
static bool getInsideQuote(const std::string& src, std::string& dst)
{
dst.clear();
std::string::size_type posBeg = src.find("\"");
if (posBeg == std::string::npos)
return false;
std::string::size_type posEnd = src.find("\"", posBeg + 1);
if (posEnd == std::string::npos)
return false;
dst = src.substr(posBeg + 1, posEnd - posBeg - 1);
return true;
}
void listDirectShowDevices(std::vector<Device> &devices)
{
//注册设备信息
avdevice_register_all();
devices.clear();
std::string line, name;
Device d;
std::map<std::string, int> mapNameCount;
int numDevices = 0;
AVInputFormat *iformat = av_find_input_format("dshow");
if (!iformat)
{
return;
}
AVFormatContext *formatCtx = avformat_alloc_context();
if (!formatCtx)
return;
//获取设备列表并输出到日志回调函数中
AVDictionary* options = NULL;
device_sstrm = std::stringstream();
av_log_set_callback(logCallbackForDshowInfo);
av_dict_set(&options, "list_devices", "true", 0);
avformat_open_input(&formatCtx, "dummy", iformat, &options);
//输出完毕之后重置回调函数
av_log_set_callback(ffmpegLogCallback);
//解析输出日志当中的视频设备
while (true)
{
if (device_sstrm.eof())
break;
std::getline(device_sstrm, line);
if (line.find("DirectShow video") != std::string::npos)
break;
}
bool beginParse = true;
//解析设备信息中的视频设备
while (true)
{
d.deviceType = VIDEO;
std::getline(device_sstrm, line);
if (!line.size())
break;
if (line.find("Could not enumerate") != std::string::npos)
continue;
if (line.find("DirectShow audio") != std::string::npos)
break;
if (beginParse)
{
if (getInsideQuote(line, name))
{
d.shortName = name;
beginParse = false;
}
if (device_sstrm.eof())
{
devices.clear();
goto END;
}
}
else
{
if (getInsideQuote(line, name))
{
d.longName = name;
beginParse = true;
devices.push_back(d);
}
}
if (device_sstrm.eof())
break;
}
//解析设备信息中的音频设备
beginParse = true;
while (true)
{
d.deviceType = AUDIO;
std::getline(device_sstrm, line);
if (!line.size())
break;
if (line.find("Could not enumerate") != std::string::npos)
continue;
if (beginParse)
{
if (getInsideQuote(line, name))
{
d.shortName = name;
beginParse = false;
}
if (device_sstrm.eof())
{
devices.clear();
goto END;
}
}
else
{
if (getInsideQuote(line, name))
{
d.longName = name;
beginParse = true;
devices.push_back(d);
}
}
if (device_sstrm.eof())
break;
}
//获取设备的缩略名称
numDevices = devices.size();
for (int i = 0; i < numDevices; i++)
{
std::map<std::string, int>::iterator itr = mapNameCount.find(devices[i].shortName);
if (itr == mapNameCount.end())
{
devices[i].numString = std::to_string(0);
mapNameCount[devices[i].shortName] = 0;
}
else
{
++(itr->second);
devices[i].numString = std::to_string(itr->second);
}
}
END:
avformat_close_input(&formatCtx);
av_dict_free(&options);
}
这里在主函数中调用对应的方法输出本机的音视频设备列表:
int main(int argc, char* argv[])
{
std::vector<Device> device_vector;
listDirectShowDevices(device_vector);
for (int index = 0; index<device_vector.size(); ++index)
{
Device index_device = device_vector.at(index);
std::cout << "==================" << std::endl;
if (index_device.deviceType == VIDEO)
{
std::cout << "video device" << std::endl;
std::cout << index_device.shortName << std::endl;
std::cout << index_device.longName << std::endl;
std::cout << index_device.numString << std::endl;
}
else if (index_device.deviceType == AUDIO)
{
std::cout << "audio device" << std::endl;
std::cout << utf8_to_multibytes(index_device.shortName) << std::endl;
std::cout << index_device.longName << std::endl;
std::cout << index_device.numString << std::endl;
}
}
getchar();
}