上一篇实现了windows的音视频设备热插拔功能,这一篇集成到SDK中。我的对外接口类是HCMDesktopRecorder,该篇主要讲如何集成热插拔功能,其他代码忽略。
1、开启并注册热插拔
int HCMDesktopRecorder::init(/* 忽略 */)
{
/* 忽略 */
// Create thread for detect device callback
m_detectDeviceThread = std::thread(std::bind(&HCMDesktopRecorder::detectDeviceProcess, this));
// Start detecting device
DEVICE::DeviceDetector::GetInstance().startDetect();
m_deviceDetectCbId = DEVICE::DeviceDetector::GetInstance().registerCallback(std::bind(&HCMDesktopRecorder::deviceDetectCb,
this, std::placeholders::_1, std::placeholders::_2));
/* 忽略 */
}
向DeviceDetector注册的回调,一旦收到设备变更通知就触发自己的函数detectDeviceProcess
void HCMDesktopRecorder::deviceDetectCb(DEVICE::DeviceDetectType type, DEVICE::DeviceDetectAction action)
{
std::unique_lock<std::mutex> lock(m_detectDeviceMutex);
m_detectDeviceInfoList.push_back({ type, action });
m_detectDeviceFlag = true;
m_detectDeviceCond.notify_all();
}
detectDeviceProcess的线程函数实现如下:
void HCMDesktopRecorder::detectDeviceProcess()
{
while (m_inited) {
std::unique_lock<std::mutex> lock(m_detectDeviceMutex);
while (!m_detectDeviceFlag && m_inited) {
m_detectDeviceCond.wait_for(lock, std::chrono::microseconds(1000));
}
while (!m_detectDeviceInfoList.empty()) {
auto info = m_detectDeviceInfoList.front();
m_detectDeviceInfoList.pop_front();
handleAudioDevice(info.type, info.action);
handleVideoDevice(info.type, info.action);
}
m_detectDeviceFlag = false;
}
}
其中handleAudioDevice和handleVideoDevice实现有点长,如下。
如果发现当前使用的设备被拔掉了,那么先销毁内存,并找可用的新设备,如果找到了,则开启新设备并更新到muxer中,实现见replaceAudioCaptor和replaceVideoCaptor;如果监测到插入了新设备并且当前无相同类型设备在使用,那么同样创建新设备并更新到muxer,同样详见replaceAudioCaptor和replaceVideoCaptor
void HCMDesktopRecorder::handleAudioDevice(DEVICE::DeviceDetectType type, DEVICE::DeviceDetectAction action)
{
DEVICE::AUDIO_DEVICE defaultDevice = {};
std::list<DEVICE::AUDIO_DEVICE> devices;
if (type == DEVICE::DeviceDetectType::DEVICE_DDETECT_TYPE_MIC) {
DEVICE::AudioDevice::getMicDevices(devices);
if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_REMOVE) {
bool removed = true;
for each (auto device in devices) {
if (device.id == m_setting.mic.id) {
removed = false;
break;
}
if (device.isDefault) {
defaultDevice = device;
}
}
if (removed) {
memset(m_setting.mic.id, 0, sizeof(m_setting.mic.id));
if (m_micCaptor != nullptr) {
m_fileMuxer->replaceAudioCaptor(m_micCaptor, true, false);
CAPTOR::destroyAudioCaptor(&m_micCaptor);
m_micCaptor = nullptr;
}
if (!devices.empty()) {
memcpy_s(m_setting.mic.id, sizeof(m_setting.mic.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.mic.name, sizeof(m_setting.mic.name), defaultDevice.name.c_str(), defaultDevice.name.size());
CAPTOR::createAudioCaptor((CAPTOR::AUDIO_CAPTURE_TYPE)m_setting.amCaptorId, &m_micCaptor);
m_micCaptor->init(true, m_setting.mic.id);
m_fileMuxer->replaceAudioCaptor(m_micCaptor, true, true);
}
}
}
else if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_ADD) {
bool added = false;
if (devices.size() > 0 && strlen(m_setting.mic.id) == 0) {
for each (auto device in devices) {
if (device.isDefault) {
defaultDevice = device;
added = true;
break;
}
}
if (added) {
memcpy_s(m_setting.mic.id, sizeof(m_setting.mic.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.mic.name, sizeof(m_setting.mic.name), defaultDevice.name.c_str(), defaultDevice.name.size());
CAPTOR::createAudioCaptor((CAPTOR::AUDIO_CAPTURE_TYPE)m_setting.amCaptorId, &m_micCaptor);
m_micCaptor->init(true, m_setting.mic.id);
m_fileMuxer->replaceAudioCaptor(m_micCaptor, true, true);
}
}
}
}
else if (type == DEVICE::DeviceDetectType::DEVICE_DDETECT_TYPE_SPEAKER) {
DEVICE::AudioDevice::getSpeakerDevices(devices);
if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_REMOVE) {
bool removed = true;
for each (auto device in devices) {
if (device.id == m_setting.speaker.id) {
removed = false;
break;
}
if (device.isDefault) {
defaultDevice = device;
}
}
if (removed) {
memset(m_setting.speaker.id, 0, sizeof(m_setting.speaker.id));
if (m_speakerCaptor != nullptr) {
m_fileMuxer->replaceAudioCaptor(m_speakerCaptor, false, false);
CAPTOR::destroyAudioCaptor(&m_speakerCaptor);
m_speakerCaptor = nullptr;
}
if (!devices.empty()) {
memcpy_s(m_setting.speaker.id, sizeof(m_setting.speaker.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.speaker.name, sizeof(m_setting.speaker.name), defaultDevice.name.c_str(), defaultDevice.name.size());
CAPTOR::createAudioCaptor((CAPTOR::AUDIO_CAPTURE_TYPE)m_setting.asCaptorId, &m_speakerCaptor);
m_speakerCaptor->init(false, m_setting.speaker.id);
m_fileMuxer->replaceAudioCaptor(m_speakerCaptor, false, true);
}
}
}
else if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_ADD) {
bool added = false;
if (devices.size() > 0 && strlen(m_setting.speaker.id) == 0) {
for each (auto device in devices) {
if (device.isDefault) {
defaultDevice = device;
added = true;
break;
}
}
if (added) {
memcpy_s(m_setting.speaker.id, sizeof(m_setting.speaker.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.speaker.name, sizeof(m_setting.speaker.name), defaultDevice.name.c_str(), defaultDevice.name.size());
CAPTOR::createAudioCaptor((CAPTOR::AUDIO_CAPTURE_TYPE)m_setting.asCaptorId, &m_speakerCaptor);
m_speakerCaptor->init(false, m_setting.speaker.id);
m_fileMuxer->replaceAudioCaptor(m_speakerCaptor, false, true);
}
}
}
}
if (m_callbacks.onDeviceChanged != nullptr) {
m_callbacks.onDeviceChanged(type);
}
}
void HCMDesktopRecorder::handleVideoDevice(DEVICE::DeviceDetectType type, DEVICE::DeviceDetectAction action)
{
DEVICE::VIDEO_DEVICE defaultDevice = {};
std::list<DEVICE::VIDEO_DEVICE> devices;
if (type == DEVICE::DeviceDetectType::DEVICE_DDETECT_TYPE_CAMERA) {
DEVICE::VideoDevice::getCameraDevices(devices);
if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_REMOVE) {
bool removed = true;
for each (auto device in devices) {
if (device.id == m_setting.camera.id) {
removed = false;
break;
}
if (device.isDefault) {
defaultDevice = device;
}
}
if (removed) {
memset(m_setting.camera.id, 0, sizeof(m_setting.camera.id));
// Remove camera captor
if (m_cameraCaptor != nullptr) {
m_fileMuxer->replaceVideoCaptor(m_cameraCaptor, true, false);
CAPTOR::destroyVideoCaptor(&m_cameraCaptor);
m_cameraCaptor = nullptr;
}
if (!devices.empty()) {
memcpy_s(m_setting.camera.id, sizeof(m_setting.camera.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.camera.name, sizeof(m_setting.camera.name), defaultDevice.name.c_str(), defaultDevice.name.size());
// Create camera captor
CAPTOR::createVideoCaptor((CAPTOR::VIDEO_CAPTURE_TYPE)m_setting.vcCaptorId, &m_cameraCaptor);
m_cameraCaptor->init(m_setting.camera.id, m_setting.framerate);
m_fileMuxer->replaceVideoCaptor(m_cameraCaptor, true, true);
}
}
}
else if (action == DEVICE::DeviceDetectAction::DEVICE_DDETECT_ACTION_ADD) {
bool added = false;
if (devices.size() > 0 && strlen(m_setting.camera.id) == 0) {
for each (auto device in devices) {
if (device.isDefault) {
defaultDevice = device;
added = true;
break;
}
}
if (added) {
memcpy_s(m_setting.camera.id, sizeof(m_setting.camera.id), defaultDevice.id.c_str(), defaultDevice.id.size());
memcpy_s(m_setting.camera.name, sizeof(m_setting.camera.name), defaultDevice.name.c_str(), defaultDevice.name.size());
// Create camera captor
CAPTOR::createVideoCaptor((CAPTOR::VIDEO_CAPTURE_TYPE)m_setting.vcCaptorId, &m_cameraCaptor);
m_cameraCaptor->init(m_setting.camera.id, m_setting.framerate);
m_fileMuxer->replaceVideoCaptor(m_cameraCaptor, true, true);
}
}
}
}
else if (type == DEVICE::DeviceDetectType::DEVICE_DDETECT_TYPE_MONITOR) {}
if (m_callbacks.onDeviceChanged != nullptr) {
m_callbacks.onDeviceChanged(type);
}
}
2、Muxer类关于replaceAudioCaptor和replaceVideoCaptor的实现
FfmpegMuxer实现详见《【音视频】保存同步的音视频文件-ffmpeg(九)》,这里只做热插拔实现的介绍。
replaceAudioCaptor的实现,详见代码。
amCaptor是麦克风采集器,asCaptor是扬声器采集器,aFilter是音频转码或混流过滤器
int FfmpegMuxer::replaceAudioCaptor(CAPTOR::AudioCaptor* audioCaptor, bool isMic, bool add)
{
/* 没有启动过audio流程,就不再启动(重启流程比较麻烦,就不做了,反正主要为了测试audio设备的热插拔) */
if (m_audioStream == nullptr) {
return ERROR_CODE_UNINITIALIZED;
}
int err = ERROR_CODE_OK;
std::lock_guard<std::mutex> lock(m_mutex);
if (add) {
bool hasDevice = false;
if ((m_audioStream->amCaptor != nullptr && isMic) || (m_audioStream->asCaptor != nullptr && !isMic)) {
hasDevice = true;
}
if (!hasDevice) {
if (m_audioStream->amCaptor == nullptr && isMic) {
m_audioStream->amCaptor = audioCaptor;
}
else if (m_audioStream->asCaptor == nullptr && !isMic) {
m_audioStream->asCaptor = audioCaptor;
}
/* 销毁filter */
FILTER::destroyAudioFilter(&m_audioStream->aFilter);
/* 重新创建filter */
err = createAudioFilter(m_audioStream->amCaptor, m_audioStream->asCaptor);
if (err == ERROR_CODE_OK) {
/* 启动captor和filter */
if (m_audioStream->amCaptor != nullptr) {
m_audioStream->amCaptor->start();
}
if (m_audioStream->asCaptor != nullptr) {
m_audioStream->asCaptor->start();
}
if (m_audioStream->aFilter != nullptr) {
m_audioStream->aFilter->start();
}
}
}
}
else {
if (isMic && m_audioStream->amCaptor == audioCaptor) {
err = m_audioStream->amCaptor->stop();
m_audioStream->amCaptor = nullptr;
}
else if (!isMic && m_audioStream->asCaptor == audioCaptor) {
err = m_audioStream->asCaptor->stop();
m_audioStream->asCaptor = nullptr;
}
}
return err;
}
replaceVideoCaptor实现,详见代码。
vmCaptor是显示器采集器,vcCaptor是摄像头采集器,vTranscoder是视频转码器,vFilter是视频混流过滤器
int FfmpegMuxer::replaceVideoCaptor(CAPTOR::VideoCaptor* videoCaptor, bool isCamera, bool add)
{
/* 没有启动过video流程,就不再启动(重启流程比较麻烦,就不做了,反正主要为了测试video设备的热插拔) */
if (m_videoStream == nullptr) {
return ERROR_CODE_UNINITIALIZED;
}
int err = ERROR_CODE_OK;
std::lock_guard<std::mutex> lock(m_mutex);
if (add) {
bool hasDevice = false;
if ((m_videoStream->vmCaptor != nullptr && !isCamera) || (m_videoStream->vcCaptor != nullptr && isCamera)) {
hasDevice = true;
}
if (!hasDevice) {
if (m_videoStream->vmCaptor == nullptr && !isCamera) {
m_videoStream->vmCaptor = videoCaptor;
}
else if (m_videoStream->vcCaptor == nullptr && isCamera) {
m_videoStream->vcCaptor = videoCaptor;
}
/* 销毁filter */
TRANSCODER::destroyVideoTranscoder(&m_videoStream->vTranscoder);
FILTER::destroyVideoFilter(&m_videoStream->vFilter);
/* 重新创建filter */
err = createVideoFilter(m_videoStream->vmCaptor, m_videoStream->vcCaptor);
if (err == ERROR_CODE_OK) {
/* 启动captor和filter */
if (m_videoStream->vmCaptor != nullptr) {
m_videoStream->vmCaptor->start();
}
if (m_videoStream->vcCaptor != nullptr) {
m_videoStream->vcCaptor->start();
}
if (m_videoStream->vFilter != nullptr) {
m_videoStream->vFilter->start();
}
}
}
}
else {
if (isCamera && m_videoStream->vcCaptor == videoCaptor) {
err = m_videoStream->vcCaptor->stop();
m_videoStream->vcCaptor = nullptr;
}
else if (!isCamera && m_videoStream->vmCaptor == videoCaptor) {
err = m_videoStream->vmCaptor->stop();
m_videoStream->vmCaptor = nullptr;
}
}
return err;
}
3、停止热插拔监测
删除热插拔回调并停止监测
void HCMDesktopRecorder::release()
{
/* 忽略 */
DEVICE::DeviceDetector::GetInstance().removeCallback(m_deviceDetectCbId);
DEVICE::DeviceDetector::GetInstance().stopDetect();
}