RTMPPublisher::~RTMPPublisher() { //OSDebugOut (TEXT("*** ~RTMPPublisher (%d queued, %d buffered, %d data)\n"), queuedPackets.Num(), bufferedPackets.Num(), curDataBufferLen); bStopping = true; //we're in the middle of connecting! wait for that to happen to avoid all manner of race conditions if (hConnectionThread) { WaitForSingleObject(hConnectionThread, INFINITE); OSCloseThread(hConnectionThread); } //send all remaining buffered packets, this may block since it respects timestamps FlushBufferedPackets (); //OSDebugOut (TEXT("%d queued after flush\n"), queuedPackets.Num()); if(hSendThread) { //this marks the thread to exit after current work is done SetEvent(hSendLoopExit); //these wake up the thread ReleaseSemaphore(hSendSempahore, 1, NULL); SetEvent(hBufferSpaceAvailableEvent); //wait 50 sec for all data to finish sending if (WaitForSingleObject(hSendThread, 50000) == WAIT_TIMEOUT) { Log(TEXT("~RTMPPublisher: Network appears stalled with %d / %d buffered, dropping connection!"), curDataBufferLen, dataBufferSize); FatalSocketShutdown(); //this will wake up and flush the sendloop if it's still trying to send out stuff ReleaseSemaphore(hSendSempahore, 1, NULL); SetEvent(hBufferSpaceAvailableEvent); } OSTerminateThread(hSendThread, 10000); } if(hSendSempahore) CloseHandle(hSendSempahore); //OSDebugOut (TEXT("*** ~RTMPPublisher hSendThread terminated (%d queued, %d buffered, %d data)\n"), queuedPackets.Num(), bufferedPackets.Num(), curDataBufferLen); if (hSocketThread) { //mark the socket loop to shut down after the buffer is empty SetEvent(hSocketLoopExit); //wake it up in case it already is empty SetEvent(hBufferEvent); //wait 60 sec for it to exit OSTerminateThread(hSocketThread, 60000); } //OSDebugOut (TEXT("*** ~RTMPPublisher hSocketThread terminated (%d queued, %d buffered, %d data)\n"), queuedPackets.Num(), bufferedPackets.Num(), curDataBufferLen); if(rtmp) { if (RTMP_IsConnected(rtmp)) { //at this point nothing should be in the buffer, flush out what remains to the net and make it blocking FlushDataBuffer(); //disable the buffered send, so RTMP_* functions write directly to the net (and thus block) rtmp->m_bCustomSend = 0; //manually shut down the stream and issue a graceful socket shutdown RTMP_DeleteStream(rtmp); shutdown(rtmp->m_sb.sb_socket, SD_SEND); //this waits for the socket shutdown to complete gracefully for (;;) { char buff[1024]; int ret; ret = recv(rtmp->m_sb.sb_socket, buff, sizeof(buff), 0); if (!ret) break; else if (ret == -1) { Log(TEXT("~RTMPublisher: Received error %d while waiting for graceful shutdown."), WSAGetLastError()); break; } } //OSDebugOut(TEXT("Graceful shutdown complete.\n")); } //this closes the socket if not already done RTMP_Close(rtmp); } if(hDataMutex) OSCloseMutex(hDataMutex); while (bufferedPackets.Num()) { //this should not happen any more... bufferedPackets[0].data.Clear(); bufferedPackets.Remove(0); } if (dataBuffer) Free(dataBuffer); if (hDataBufferMutex) OSCloseMutex(hDataBufferMutex); if (hBufferEvent) CloseHandle(hBufferEvent); if (hSendLoopExit) CloseHandle(hSendLoopExit); if (hSocketLoopExit) CloseHandle(hSocketLoopExit); if (hSendBacklogEvent) CloseHandle(hSendBacklogEvent); if (hBufferSpaceAvailableEvent) CloseHandle(hBufferSpaceAvailableEvent); if (hWriteEvent) CloseHandle(hWriteEvent); if(rtmp) { if (rtmp->Link.pubUser.av_val) Free(rtmp->Link.pubUser.av_val); if (rtmp->Link.pubPasswd.av_val) Free(rtmp->Link.pubPasswd.av_val); RTMP_Free(rtmp); } //-------------------------- for(UINT i=0; i<queuedPackets.Num(); i++) queuedPackets[i].data.Clear(); queuedPackets.Clear(); double dBFrameDropPercentage = double(numBFramesDumped)/NumTotalVideoFrames()*100.0; double dPFrameDropPercentage = double(numPFramesDumped)/NumTotalVideoFrames()*100.0; if (totalSendCount) Log(TEXT("Average send payload: %d bytes, average send interval: %d ms"), (DWORD)(totalSendBytes / totalSendCount), totalSendPeriod / totalSendCount); Log(TEXT("Number of times waited to send: %d, Waited for a total of %d bytes"), totalTimesWaited, totalBytesWaited); Log(TEXT("Number of b-frames dropped: %u (%0.2g%%), Number of p-frames dropped: %u (%0.2g%%), Total %u (%0.2g%%)"), numBFramesDumped, dBFrameDropPercentage, numPFramesDumped, dPFrameDropPercentage, numBFramesDumped+numPFramesDumped, dBFrameDropPercentage+dPFrameDropPercentage); /*if(totalCalls) Log(TEXT("average send time: %u"), totalTime/totalCalls);*/ strRTMPErrors.Clear(); //-------------------------- }
RTMPPublisher::~RTMPPublisher() { //OSDebugOut (TEXT("*** ~RTMPPublisher (%d queued, %d buffered)\n"), queuedPackets.Num(), bufferedPackets.Num()); bStopping = true; //we're in the middle of connecting! wait for that to happen to avoid all manner of race conditions if (hConnectionThread) { WaitForSingleObject(hConnectionThread, INFINITE); OSCloseThread(hConnectionThread); } FlushBufferedPackets (); //OSDebugOut (TEXT("%d queued after flush\n"), queuedPackets.Num()); if(hSendThread) { //this marks the thread to exit after current work is done SetEvent(hSendLoopExit); //this wakes up the thread ReleaseSemaphore(hSendSempahore, 1, NULL); //wait 60 sec for it to exit OSTerminateThread(hSendThread, 60000); } if(hSendSempahore) CloseHandle(hSendSempahore); //OSDebugOut (TEXT("*** ~RTMPPublisher hSendThread terminated (%d queued, %d buffered, %d data)\n"), queuedPackets.Num(), bufferedPackets.Num(), curDataBufferLen); if (hSocketThread) { //mark the socket loop to shut down after the buffer is empty SetEvent(hSocketLoopExit); //wait 60 sec for it to exit OSTerminateThread(hSocketThread, 60000); } //OSDebugOut (TEXT("*** ~RTMPPublisher hSocketThread terminated (%d queued, %d buffered, %d data)\n"), queuedPackets.Num(), bufferedPackets.Num(), curDataBufferLen); if(rtmp) { //at this point nothing should be in the buffer, flush out what remains and make it blocking FlushDataBuffer(); //disable the buffered send, so RTMP_Close writes directly to the net rtmp->m_bCustomSend = 0; //ideally we need some kind of delay here, since we just dumped several seconds worth of timestamps to the network //at once, and Twitch at shows the offline screen as soon as the connection is severed even if there are //pending video frames. if (RTMP_IsConnected(rtmp)) Sleep (500); //for now RTMP_Close(rtmp); } if(hDataMutex) OSCloseMutex(hDataMutex); while (bufferedPackets.Num()) { //this should not happen any more... bufferedPackets[0].data.Clear(); bufferedPackets.Remove(0); } if (dataBuffer) Free(dataBuffer); if (hDataBufferMutex) OSCloseMutex(hDataBufferMutex); if (hBufferEvent) CloseHandle(hBufferEvent); if (hSendLoopExit) CloseHandle(hSendLoopExit); if (hSocketLoopExit) CloseHandle(hSocketLoopExit); if (hSendBacklogEvent) CloseHandle(hSendBacklogEvent); if (hBufferSpaceAvailableEvent) CloseHandle(hBufferSpaceAvailableEvent); if (hWriteEvent) CloseHandle(hWriteEvent); if(rtmp) RTMP_Free(rtmp); //-------------------------- for(UINT i=0; i<queuedPackets.Num(); i++) queuedPackets[i].data.Clear(); queuedPackets.Clear(); double dBFrameDropPercentage = double(numBFramesDumped)/NumTotalVideoFrames()*100.0; double dPFrameDropPercentage = double(numPFramesDumped)/NumTotalVideoFrames()*100.0; Log(TEXT("Number of times waited to send: %d, Waited for a total of %d bytes"), totalTimesWaited, totalBytesWaited); Log(TEXT("Number of b-frames dropped: %u (%0.2g%%), Number of p-frames dropped: %u (%0.2g%%), Total %u (%0.2g%%)"), numBFramesDumped, dBFrameDropPercentage, numPFramesDumped, dPFrameDropPercentage, numBFramesDumped+numPFramesDumped, dBFrameDropPercentage+dPFrameDropPercentage); /*if(totalCalls) Log(TEXT("average send time: %u"), totalTime/totalCalls);*/ strRTMPErrors.Clear(); //-------------------------- }
OBS::OBS() { App = this; __cpuid(cpuInfo, 1); bSSE2Available = (cpuInfo[3] & (1<<26)) != 0; hSceneMutex = OSCreateMutex(); hAuxAudioMutex = OSCreateMutex(); monitors.Clear(); EnumDisplayMonitors(NULL, NULL, (MONITORENUMPROC)MonitorInfoEnumProc, (LPARAM)&monitors); INITCOMMONCONTROLSEX ecce; ecce.dwSize = sizeof(ecce); ecce.dwICC = ICC_STANDARD_CLASSES; if(!InitCommonControlsEx(&ecce)) CrashError(TEXT("Could not initalize common shell controls")); InitHotkeyExControl(hinstMain); InitColorControl(hinstMain); InitVolumeControl(); InitVolumeMeter(); //----------------------------------------------------- // load locale if(!locale->LoadStringFile(TEXT("locale/en.txt"))) AppWarning(TEXT("Could not open locale string file '%s'"), TEXT("locale/en.txt")); strLanguage = GlobalConfig->GetString(TEXT("General"), TEXT("Language"), TEXT("en")); if(!strLanguage.CompareI(TEXT("en"))) { String langFile; langFile << TEXT("locale/") << strLanguage << TEXT(".txt"); if(!locale->LoadStringFile(langFile)) AppWarning(TEXT("Could not open locale string file '%s'"), langFile.Array()); } //----------------------------------------------------- // load classes RegisterSceneClass(TEXT("Scene"), Str("Scene"), (OBSCREATEPROC)CreateNormalScene, NULL); RegisterImageSourceClass(TEXT("DesktopImageSource"), Str("Sources.SoftwareCaptureSource"), (OBSCREATEPROC)CreateDesktopSource, (OBSCONFIGPROC)ConfigureDesktopSource); RegisterImageSourceClass(TEXT("BitmapImageSource"), Str("Sources.BitmapSource"), (OBSCREATEPROC)CreateBitmapSource, (OBSCONFIGPROC)ConfigureBitmapSource); RegisterImageSourceClass(TEXT("BitmapTransitionSource"), Str("Sources.TransitionSource"), (OBSCREATEPROC)CreateBitmapTransitionSource, (OBSCONFIGPROC)ConfigureBitmapTransitionSource); RegisterImageSourceClass(TEXT("GlobalSource"), Str("Sources.GlobalSource"), (OBSCREATEPROC)CreateGlobalSource, (OBSCONFIGPROC)OBS::ConfigGlobalSource); RegisterImageSourceClass(TEXT("TextSource"), Str("Sources.TextSource"), (OBSCREATEPROC)CreateTextSource, (OBSCONFIGPROC)ConfigureTextSource); //----------------------------------------------------- // render frame class WNDCLASS wc; zero(&wc, sizeof(wc)); wc.hInstance = hinstMain; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszClassName = OBS_RENDERFRAME_CLASS; wc.lpfnWndProc = (WNDPROC)OBS::RenderFrameProc; wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); if(!RegisterClass(&wc)) CrashError(TEXT("Could not register render frame class")); //----------------------------------------------------- // main window class wc.lpszClassName = OBS_WINDOW_CLASS; wc.lpfnWndProc = (WNDPROC)OBSProc; wc.hIcon = LoadIcon(hinstMain, MAKEINTRESOURCE(IDI_ICON1)); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszMenuName = MAKEINTRESOURCE(IDR_MAINMENU); if(!RegisterClass(&wc)) CrashError(TEXT("Could not register main window class")); //----------------------------------------------------- // create main window int fullscreenX = GetSystemMetrics(SM_CXFULLSCREEN); int fullscreenY = GetSystemMetrics(SM_CYFULLSCREEN); borderXSize = borderYSize = 0; borderXSize += GetSystemMetrics(SM_CXSIZEFRAME)*2; borderYSize += GetSystemMetrics(SM_CYSIZEFRAME)*2; borderYSize += GetSystemMetrics(SM_CYMENU); borderYSize += GetSystemMetrics(SM_CYCAPTION); clientWidth = GlobalConfig->GetInt(TEXT("General"), TEXT("Width"), 700); clientHeight = GlobalConfig->GetInt(TEXT("General"), TEXT("Height"), 553); if(clientWidth < minClientWidth) clientWidth = minClientWidth; if(clientHeight < minClientHeight) clientHeight = minClientHeight; int maxCX = fullscreenX-borderXSize; int maxCY = fullscreenY-borderYSize; if(clientWidth > maxCX) clientWidth = maxCX; if(clientHeight > maxCY) clientHeight = maxCY; int cx = clientWidth + borderXSize; int cy = clientHeight + borderYSize; int x = (fullscreenX/2)-(cx/2); int y = (fullscreenY/2)-(cy/2); int posX = GlobalConfig->GetInt(TEXT("General"), TEXT("PosX")); int posY = GlobalConfig->GetInt(TEXT("General"), TEXT("PosY")); bool bInsideMonitors = false; if(posX || posY) { for(UINT i=0; i<monitors.Num(); i++) { if( posX >= monitors[i].rect.left && posX < monitors[i].rect.right && posY >= monitors[i].rect.top && posY < monitors[i].rect.bottom ) { bInsideMonitors = true; break; } } } if(bInsideMonitors) { x = posX; y = posY; } hwndMain = CreateWindowEx(WS_EX_CONTROLPARENT|WS_EX_WINDOWEDGE, OBS_WINDOW_CLASS, OBS_VERSION_STRING, WS_OVERLAPPED | WS_THICKFRAME | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU | WS_CLIPCHILDREN, x, y, cx, cy, NULL, NULL, hinstMain, NULL); if(!hwndMain) CrashError(TEXT("Could not create main window")); HMENU hMenu = GetMenu(hwndMain); LocalizeMenu(hMenu); //----------------------------------------------------- // render frame hwndRenderFrame = CreateWindow(OBS_RENDERFRAME_CLASS, NULL, WS_CHILDWINDOW|WS_VISIBLE, 0, 0, 0, 0, hwndMain, NULL, hinstMain, NULL); if(!hwndRenderFrame) CrashError(TEXT("Could not create render frame")); //----------------------------------------------------- // scenes listbox HWND hwndTemp; hwndTemp = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("LISTBOX"), NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|LBS_HASSTRINGS|WS_VSCROLL|LBS_NOTIFY|LBS_NOINTEGRALHEIGHT|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_SCENES, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); listboxProc = (WNDPROC)GetWindowLongPtr(hwndTemp, GWLP_WNDPROC); SetWindowLongPtr(hwndTemp, GWLP_WNDPROC, (LONG_PTR)OBS::ListboxHook); //----------------------------------------------------- // elements listview hwndTemp = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|WS_VSCROLL|WS_CLIPSIBLINGS|LVS_REPORT|LVS_NOCOLUMNHEADER| LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOLABELWRAP, 0, 0, 0, 0, hwndMain, (HMENU)ID_SOURCES, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); ListView_SetExtendedListViewStyle(hwndTemp, LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER); //add single column needed for report style LVCOLUMN column; column.mask = LVCF_TEXT; column.fmt = LVCFMT_FIXED_WIDTH; column.cx = 0; column.pszText = TEXT(""); ListView_InsertColumn(hwndTemp, 0, &column); ListView_InsertColumn(hwndTemp, 1, &column); listviewProc = (WNDPROC)GetWindowLongPtr(hwndTemp, GWLP_WNDPROC); SetWindowLongPtr(hwndTemp, GWLP_WNDPROC, (LONG_PTR)OBS::ListboxHook); HWND hwndSources = hwndTemp; //----------------------------------------------------- // status control hwndTemp = CreateWindowEx(0, STATUSCLASSNAME, NULL, WS_CHILD|WS_VISIBLE|SBARS_SIZEGRIP|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_STATUS, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // mic volume control hwndTemp = CreateWindow(VOLUME_CONTROL_CLASS, NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_MICVOLUME, 0, 0); SetVolumeControlIcons(hwndTemp, GetIcon(hinstMain, IDI_SOUND_MIC), GetIcon(hinstMain, IDI_SOUND_MIC_MUTED)); //----------------------------------------------------- // mic volume meter hwndTemp = CreateWindow(VOLUME_METER_CLASS, NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_MICVOLUMEMETER, 0, 0); //----------------------------------------------------- // desktop volume meter hwndTemp = CreateWindow(VOLUME_METER_CLASS, NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_DESKTOPVOLUMEMETER, 0, 0); //----------------------------------------------------- // desktop volume control hwndTemp = CreateWindow(VOLUME_CONTROL_CLASS, NULL, WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_DESKTOPVOLUME, 0, 0); SetVolumeControlIcons(hwndTemp, GetIcon(hinstMain, IDI_SOUND_DESKTOP), GetIcon(hinstMain, IDI_SOUND_DESKTOP_MUTED)); //----------------------------------------------------- // settings button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("Settings"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_SETTINGS, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // start/stop stream button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.StartStream"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_STARTSTOP, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // edit scene button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.SceneEditor"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_AUTOCHECKBOX|BS_PUSHLIKE|WS_DISABLED|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_SCENEEDITOR, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // global sources button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("GlobalSources"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_GLOBALSOURCES, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // test stream button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.TestStream"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_TESTSTREAM, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // plugins button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Plugins"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_PLUGINS, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // dashboard button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Dashboard"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_DASHBOARD, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // exit button hwndTemp = CreateWindow(TEXT("BUTTON"), Str("MainWindow.Exit"), WS_CHILDWINDOW|WS_VISIBLE|WS_TABSTOP|BS_TEXT|BS_PUSHBUTTON|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_EXIT, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // scenes text hwndTemp = CreateWindow(TEXT("STATIC"), Str("MainWindow.Scenes"), WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_SCENES_TEXT, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // scenes text hwndTemp = CreateWindow(TEXT("STATIC"), Str("MainWindow.Sources"), WS_CHILDWINDOW|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, hwndMain, (HMENU)ID_SOURCES_TEXT, 0, 0); SendMessage(hwndTemp, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), TRUE); //----------------------------------------------------- // populate scenes hwndTemp = GetDlgItem(hwndMain, ID_SCENES); String strScenesConfig; strScenesConfig << lpAppDataPath << TEXT("\\scenes.xconfig"); if(!scenesConfig.Open(strScenesConfig)) CrashError(TEXT("Could not open '%s'"), strScenesConfig); XElement *scenes = scenesConfig.GetElement(TEXT("scenes")); if(!scenes) scenes = scenesConfig.CreateElement(TEXT("scenes")); UINT numScenes = scenes->NumElements(); if(!numScenes) { XElement *scene = scenes->CreateElement(Str("Scene")); scene->SetString(TEXT("class"), TEXT("Scene")); numScenes++; } for(UINT i=0; i<numScenes; i++) { XElement *scene = scenes->GetElementByID(i); scene->SetString(TEXT("class"), TEXT("Scene")); SendMessage(hwndTemp, LB_ADDSTRING, 0, (LPARAM)scene->GetName()); } //----------------------------------------------------- // populate sources if(numScenes) { String strScene = AppConfig->GetString(TEXT("General"), TEXT("CurrentScene")); int id = (int)SendMessage(hwndTemp, LB_FINDSTRINGEXACT, -1, 0); if(id == LB_ERR) id = 0; SendMessage(hwndTemp, LB_SETCURSEL, (WPARAM)id, 0); SendMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)GetDlgItem(hwndMain, ID_SCENES)); } //----------------------------------------------------- hHotkeyMutex = OSCreateMutex(); hInfoMutex = OSCreateMutex(); hStartupShutdownMutex = OSCreateMutex(); //----------------------------------------------------- API = CreateOBSApiInterface(); ResizeWindow(false); ShowWindow(hwndMain, SW_SHOW); // make sure sources listview column widths are as expected after obs window is shown ListView_SetColumnWidth(hwndSources,0,LVSCW_AUTOSIZE_USEHEADER); ListView_SetColumnWidth(hwndSources,1,LVSCW_AUTOSIZE_USEHEADER); //----------------------------------------------------- for(UINT i=0; i<numScenes; i++) { XElement *scene = scenes->GetElementByID(i); DWORD hotkey = scene->GetInt(TEXT("hotkey")); if(hotkey) { SceneHotkeyInfo hotkeyInfo; hotkeyInfo.hotkey = hotkey; hotkeyInfo.scene = scene; hotkeyInfo.hotkeyID = API->CreateHotkey(hotkey, SceneHotkey, 0); if(hotkeyInfo.hotkeyID) sceneHotkeys << hotkeyInfo; } } //----------------------------------------------------- // load plugins OSFindData ofd; HANDLE hFind = OSFindFirstFile(TEXT("plugins/*.dll"), ofd); if(hFind) { do { if(!ofd.bDirectory) //why would someone give a directory a .dll extension in the first place? pranksters. { String strLocation; strLocation << TEXT("plugins/") << ofd.fileName; HMODULE hPlugin = LoadLibrary(strLocation); if(hPlugin) { LOADPLUGINPROC loadPlugin = (LOADPLUGINPROC)GetProcAddress(hPlugin, "LoadPlugin"); if(loadPlugin && loadPlugin()) { PluginInfo *pluginInfo = plugins.CreateNew(); pluginInfo->hModule = hPlugin; pluginInfo->strFile = ofd.fileName; GETPLUGINNAMEPROC getName = (GETPLUGINNAMEPROC)GetProcAddress(hPlugin, "GetPluginName"); CTSTR lpName; if(getName) lpName = getName(); else lpName = TEXT("<unknown>"); //FIXME: log this somewhere else, it comes before the OBS version info and looks weird. //Log(TEXT("Loaded plugin '%s', %s"), lpName, strLocation); } else { Log(TEXT("Failed to initialize plugin %s"), strLocation); FreeLibrary(hPlugin); } } else { Log(TEXT("Failed to load plugin %s, %d"), strLocation, GetLastError()); } } } while (OSFindNextFile(hFind, ofd)); OSFindClose(hFind); } //----------------------------------------------------- ReloadIniSettings(); ResetProfileMenu(); //----------------------------------------------------- bAutoReconnect = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnect"), 1) != 0; reconnectTimeout = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), 10); if(reconnectTimeout < 5) reconnectTimeout = 5; hHotkeyThread = OSCreateThread((XTHREAD)HotkeyThread, NULL); #ifndef OBS_DISABLE_AUTOUPDATE ULARGE_INTEGER lastUpdateTime; ULARGE_INTEGER currentTime; FILETIME systemTime; lastUpdateTime.QuadPart = GlobalConfig->GetInt(TEXT("General"), TEXT("LastUpdateCheck"), 0); GetSystemTimeAsFileTime(&systemTime); currentTime.LowPart = systemTime.dwLowDateTime; currentTime.HighPart = systemTime.dwHighDateTime; //OBS doesn't support 64 bit ints in the config file, so we have to normalize it to a 32 bit int currentTime.QuadPart /= 10000000; currentTime.QuadPart -= 13000000000; if (currentTime.QuadPart - lastUpdateTime.QuadPart >= 3600) { GlobalConfig->SetInt(TEXT("General"), TEXT("LastUpdateCheck"), (int)currentTime.QuadPart); OSCloseThread(OSCreateThread((XTHREAD)CheckUpdateThread, NULL)); } #endif bRenderViewEnabled = true; if(GlobalConfig->GetInt(TEXT("General"), TEXT("ShowWebrootWarning"), TRUE) && IsWebrootLoaded()) MessageBox(hwndMain, TEXT("Webroot Secureanywhere appears to be active. This product will cause problems with OBS as the security features block OBS from accessing Windows GDI functions. It is highly recommended that you disable Secureanywhere and restart OBS.\r\n\r\nOf course you can always just ignore this message if you want, but it may prevent you from being able to stream certain things. Please do not report any bugs you may encounter if you leave Secureanywhere enabled."), TEXT("Just a slight issue you might want to be aware of"), MB_OK); }
RTMPPublisher::~RTMPPublisher() { bStopping = true; //we're in the middle of connecting! wait for that to happen to avoid all manner of race conditions if (hConnectionThread) { WaitForSingleObject(hConnectionThread, INFINITE); OSCloseThread(hConnectionThread); } if(hSendThread) { ReleaseSemaphore(hSendSempahore, 1, NULL); //wake it up in case it's waiting for buffer space SetEvent(hBufferSpaceAvailableEvent); OSTerminateThread(hSendThread, 20000); } if(hSendSempahore) CloseHandle(hSendSempahore); if(hDataMutex) OSCloseMutex(hDataMutex); while (bufferedPackets.Num()) { bufferedPackets[0].data.Clear(); bufferedPackets.Remove(0); } //wake up and shut down the buffered sender SetEvent(hWriteEvent); SetEvent(hBufferEvent); if (hSocketThread) { OSTerminateThread(hSocketThread, 20000); //at this point nothing new should be coming in to the buffer, flush out what remains FlushDataBuffer(); } if(rtmp) { //disable the buffered send, so RTMP_Close writes directly to the net rtmp->m_bCustomSend = 0; RTMP_Close(rtmp); } if (dataBuffer) Free(dataBuffer); if (hDataBufferMutex) OSCloseMutex(hDataBufferMutex); if (hBufferEvent) CloseHandle(hBufferEvent); if (hBufferSpaceAvailableEvent) CloseHandle(hBufferSpaceAvailableEvent); if (hWriteEvent) CloseHandle(hWriteEvent); if(rtmp) RTMP_Free(rtmp); //-------------------------- for(UINT i=0; i<queuedPackets.Num(); i++) queuedPackets[i].data.Clear(); queuedPackets.Clear(); double dBFrameDropPercentage = double(numBFramesDumped)/NumTotalVideoFrames()*100.0; double dPFrameDropPercentage = double(numPFramesDumped)/NumTotalVideoFrames()*100.0; Log(TEXT("Number of times waited to send: %d, Waited for a total of %d bytes"), totalTimesWaited, totalBytesWaited); Log(TEXT("Number of b-frames dropped: %u (%0.2g%%), Number of p-frames dropped: %u (%0.2g%%), Total %u (%0.2g%%)"), numBFramesDumped, dBFrameDropPercentage, numPFramesDumped, dPFrameDropPercentage, numBFramesDumped+numPFramesDumped, dBFrameDropPercentage+dPFrameDropPercentage); /*if(totalCalls) Log(TEXT("average send time: %u"), totalTime/totalCalls);*/ strRTMPErrors.Clear(); //-------------------------- }