在第三篇介绍了视频的线程,音频的线程代码也是在那一块儿:
if (!ResetAudio())
throw "Failed to initialize audio";
音频线程的创建是在audio_output_open函数中
int audio_output_open(audio_t **audio, struct audio_output_info *info)
{
。。。
if (pthread_create(&out->thread, NULL, audio_thread, out) != 0)
goto fail;
。。。
}
obs在windows上采集音频用的是wasapi, 主要是在win-wasapi这个工程里面,在界面初始化时需要加载各种dll, 这里面就有个wasapi.dll,音频采集线程创建的代码如下
void WASAPISource::InitCapture()
{
HRESULT res = client->GetService(__uuidof(IAudioCaptureClient),
(void **)capture.Assign());
if (FAILED(res))
throw HRError("Failed to create capture context", res);
res = client->SetEventHandle(receiveSignal);
if (FAILED(res))
throw HRError("Failed to set event handle", res);
//创建音频采集线程
captureThread = CreateThread(nullptr, 0, WASAPISource::CaptureThread,
this, 0, nullptr);
if (!captureThread.Valid())
throw "Failed to create capture thread";
client->Start();
active = true;
blog(LOG_INFO, "WASAPI: Device '%s' [%s Hz] initialized",
device_name.c_str(), device_sample.c_str());
}
可以去看看线程函数 CaptureThread的代码
while (WaitForCaptureSignal(2, sigs, dur)) {
if (!source->ProcessCaptureData()) {
reconnect = true;
break;
}
}
在点击【开始录制】按钮后,会触发相应的windows事件,然后进入这个while循环,调用ProcessCaptureData去采集处理音频数据,主要就是调用下面这句代码:
res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
然后执行到下面这句代码
obs_source_output_audio(source, &data);
再就是
void obs_source_output_audio(obs_source_t *source,
const struct obs_source_audio *audio)
{
。。。
source_output_audio_data(source, &data);
。。。
source_output_audio_data这个函数就很重要了,他需要处理音视频同步
static void source_output_audio_data(obs_source_t *source,
const struct audio_data *data)
{
size_t sample_rate = audio_output_get_sample_rate(obs->audio.audio);
struct audio_data in = *data;
uint64_t diff;
uint64_t os_time = os_gettime_ns();
int64_t sync_offset;
bool using_direct_ts = false;
bool push_back = false;
/* detects 'directly' set timestamps as long as they're within
* a certain threshold */
if (uint64_diff(in.timestamp, os_time) timing_adjust = 0;
source->timing_set = true;
using_direct_ts = true;
}
if (!source->timing_set) {
reset_audio_timing(source, in.timestamp, os_time);
} else if (source->next_audio_ts_min != 0) {
diff = uint64_diff(source->next_audio_ts_min, in.timestamp);
/* smooth audio if within threshold */
if (diff > MAX_TS_VAR && !using_direct_ts)
handle_ts_jump(source, source->next_audio_ts_min,
in.timestamp, diff, os_time);
else if (diff async_unbuffered && source->async_decoupled)
source->timing_adjust = os_time - in.timestamp;
in.timestamp = source->next_audio_ts_min;
}
}
source->last_audio_ts = in.timestamp;
source->next_audio_ts_min =
in.timestamp + conv_frames_to_time(sample_rate, in.frames);
in.timestamp += source->timing_adjust;
pthread_mutex_lock(&source->audio_buf_mutex);
if (source->next_audio_sys_ts_min == in.timestamp) {
push_back = true;
} else if (source->next_audio_sys_ts_min) {
diff = uint64_diff(source->next_audio_sys_ts_min, in.timestamp);
if (diff MAX_TS_VAR) {
reset_audio_timing(source, data->timestamp, os_time);
in.timestamp = data->timestamp + source->timing_adjust;
}
}
sync_offset = source->sync_offset;
in.timestamp += sync_offset;
in.timestamp -= source->resample_offset;
source->next_audio_sys_ts_min =
source->next_audio_ts_min + source->timing_adjust;
if (source->last_sync_offset != sync_offset) {
if (source->last_sync_offset)
push_back = false;
source->last_sync_offset = sync_offset;
}
if (source->monitoring_type != OBS_MONITORING_TYPE_MONITOR_ONLY) {
if (push_back && source->audio_ts)
source_output_audio_push_back(source, &in);
else
source_output_audio_place(source, &in);
}
pthread_mutex_unlock(&source->audio_buf_mutex);
source_signal_audio_data(source, data, source_muted(source, os_time));
}
关于音视频同步的内容,可以去看看ffplay的源码解析,有个阈值TS_SMOOTHING_THRESHOLD,需要把音频、视频的每一帧时间戳的差值TS_SMOOTHING_THRESHOLD做比较,然后做延时还是快进。
wasapi是com接口,在使用之前需要做初始化,如下所示:
void WASAPISource::Initialize()
{
HRESULT res;
res = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
(void **)enumerator.Assign());
if (FAILED(res))
throw HRError("Failed to create enumerator", res);
if (!InitDevice())
return;
device_name = GetDeviceName(device);
if (!notify) {
notify = new WASAPINotify(this);
enumerator->RegisterEndpointNotificationCallback(notify);
}
HRESULT resSample;
IPropertyStore *store = nullptr;
PWAVEFORMATEX deviceFormatProperties;
PROPVARIANT prop;
resSample = device->OpenPropertyStore(STGM_READ, &store);
if (!FAILED(resSample)) {
resSample =
store->GetValue(PKEY_AudioEngine_DeviceFormat, &prop);
if (!FAILED(resSample)) {
if (prop.vt != VT_EMPTY && prop.blob.pBlobData) {
deviceFormatProperties =
(PWAVEFORMATEX)prop.blob.pBlobData;
device_sample = std::to_string(
deviceFormatProperties->nSamplesPerSec);
}
}
store->Release();
}
InitClient();
if (!isInputDevice)
InitRender();
InitCapture();
}
win-wasapi.cpp这个文件,大部分都是对系统音频设备的操作,初始化wasapi, 获取设备,获取音频buffer等。