上一篇实现了windows的音视频设备热插拔功能,这一篇集成到SDK中。我的对外接口类是HCMDesktopRecorder,该篇主要讲如何集成热插拔功能,其他代码忽略。
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); } }
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; }
删除热插拔回调并停止监测
void HCMDesktopRecorder::release() { /* 忽略 */ DEVICE::DeviceDetector::GetInstance().removeCallback(m_deviceDetectCbId); DEVICE::DeviceDetector::GetInstance().stopDetect(); }