【音视频】windows视音频设备热插拔集成(5-2)

上一篇实现了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();
}
上一篇:TortoiseSVN-查看日志时查看不到最近的日志问题解决记录


下一篇:Maven的安装与配置教程