From 71be2c937e3d2e049b87d53bf58c39a28eeec1ee Mon Sep 17 00:00:00 2001 From: jim Date: Wed, 26 Sep 2012 19:54:42 -0700 Subject: [PATCH] file saving, advanced settings, bug fixes, cats and dogs living together... mass hysteria! --- OBS.rc | 81 +++- OBS.vcproj | 12 +- OBSApi/APIInterface.h | 4 + OBSApi/Utility/ConfigFile.cpp | 2 +- OBSApi/Utility/Serializer.h | 4 +- OBSApi/Utility/Template.h | 22 +- OBSApi/Utility/XConfig.cpp | 2 +- OBSApi/Utility/XFile.h | 50 +- OBSApi/Utility/XFile_Windows.cpp | 33 +- OBSApi/Utility/XMath.cpp | 28 +- OBSApi/Utility/XMath.h | 17 +- Source/API.cpp | 2 +- Source/D3D10System.cpp | 8 +- Source/Encoder_x264.cpp | 39 +- Source/FLVFileStream.cpp | 119 +++++ Source/MP4FileStream.cpp | 765 +++++++++++++++++++++++++++++++ Source/Main.h | 10 + Source/OBS.cpp | 87 +++- Source/OBS.h | 29 +- Source/RTMPPublisher.cpp | 44 +- Source/RTMPServer.cpp | 549 ---------------------- Source/RTMPStuff.cpp | 16 +- Source/Settings.cpp | 396 +++++++++++++++- Source/VolumeControl.cpp | 13 +- Source/WindowStuff.cpp | 100 +++- resource.h | 30 +- 26 files changed, 1767 insertions(+), 695 deletions(-) create mode 100644 Source/FLVFileStream.cpp create mode 100644 Source/MP4FileStream.cpp delete mode 100644 Source/RTMPServer.cpp diff --git a/OBS.rc b/OBS.rc index 6e599aef..6437b244 100644 --- a/OBS.rc +++ b/OBS.rc @@ -57,12 +57,11 @@ STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTI CAPTION "Settings" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN + LISTBOX IDC_SETTINGSLIST,3,4,118,287,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + CONTROL "スタティック",IDC_SUBDIALOG,"Static",SS_LEFTNOWORDWRAP | NOT WS_VISIBLE | WS_GROUP,124,5,427,287,WS_EX_STATICEDGE DEFPUSHBUTTON "OK",IDOK,339,294,67,14 PUSHBUTTON "Cancel",IDCANCEL,412,294,67,14 PUSHBUTTON "Apply",IDC_APPLY,484,294,67,14 - LISTBOX IDC_SETTINGSLIST,3,4,118,287,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP - CONTROL "スタティック",IDC_SUBDIALOG,"Static",SS_LEFTNOWORDWRAP | NOT WS_VISIBLE | WS_GROUP,124,5,427,287,WS_EX_STATICEDGE - PUSHBUTTON "Settings.Defaults",IDC_DEFAULTS,238,294,67,14,NOT WS_VISIBLE END IDD_SETTINGS_PUBLISH DIALOGEX 0, 0, 427, 287 @@ -70,18 +69,27 @@ STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - RTEXT "Settings.Publish.Mode",IDC_STATIC,4,12,138,8,WS_DISABLED - COMBOBOX IDC_MODE,144,10,126,59,CBS_DROPDOWNLIST | WS_DISABLED | WS_VSCROLL | WS_TABSTOP + RTEXT "Settings.Publish.Mode",IDC_STATIC,4,12,138,8 + COMBOBOX IDC_MODE,144,10,126,59,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP RTEXT "Settings.Publish.Service",IDC_SERVICE_STATIC,4,29,138,8 COMBOBOX IDC_SERVICE,144,27,126,59,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - RTEXT "Settings.Publish.Playpath",IDC_PLAYPATH_STATIC,4,64,138,8 - EDITTEXT IDC_PLAYPATH,144,61,226,14,ES_PASSWORD | ES_AUTOHSCROLL RTEXT "Settings.Publish.ChannelName",IDC_CHANNELNAME_STATIC,4,47,138,8 EDITTEXT IDC_CHANNELNAME,144,44,226,14,ES_AUTOHSCROLL + RTEXT "Settings.Publish.Playpath",IDC_PLAYPATH_STATIC,4,64,138,8 + EDITTEXT IDC_PLAYPATH,144,61,226,14,ES_PASSWORD | ES_AUTOHSCROLL RTEXT "Settings.Publish.Server",IDC_SERVER_STATIC,4,81,138,8 - COMBOBOX IDC_SERVERLIST,144,79,226,59,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Settings.Info",IDC_INFO,4,119,408,37,NOT WS_VISIBLE EDITTEXT IDC_SERVEREDIT,144,78,226,14,ES_AUTOHSCROLL + COMBOBOX IDC_SERVERLIST,143,78,226,59,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Settings.Publish.AutoReconnect",IDC_AUTORECONNECT, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,97,145,10,WS_EX_RIGHT + RTEXT "Settings.Publish.AutoReconnectTimeout",IDC_AUTORECONNECT_TIMEOUT_STATIC,7,115,133,8 + EDITTEXT IDC_AUTORECONNECT_TIMEOUT_EDIT,143,112,40,14,ES_AUTOHSCROLL | ES_READONLY + CONTROL "",IDC_AUTORECONNECT_TIMEOUT,"msctls_updown32",UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,185,112,11,14 + LTEXT "Settings.Info",IDC_INFO,7,171,408,37,NOT WS_VISIBLE + CONTROL "Settings.Publish.SaveToFile",IDC_SAVETOFILE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,132,145,10,WS_EX_RIGHT + RTEXT "Settings.Publish.SavePath",IDC_SAVEPATH_STATIC,7,150,133,8 + EDITTEXT IDC_SAVEPATH,143,147,148,14,ES_AUTOHSCROLL + PUSHBUTTON "Browse",IDC_BROWSE,295,147,63,14 END IDD_SETTINGS_ENCODING DIALOGEX 0, 0, 427, 287 @@ -149,9 +157,18 @@ STYLE DS_SETFONT | DS_FIXEDSYS | DS_CONTROL | WS_CHILD | WS_SYSMENU EXSTYLE WS_EX_CONTROLPARENT FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - LTEXT "Settings.Info",IDC_INFO,4,44,408,37,NOT WS_VISIBLE + LTEXT "Settings.Info",IDC_INFO,4,85,408,37,NOT WS_VISIBLE RTEXT "Settings.Audio.Device",IDC_STATIC,4,12,138,8 COMBOBOX IDC_MICDEVICES,144,10,226,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Settings.Audio.PushToTalkHotkey",IDC_PUSHTOTALK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,31,143,10,WS_EX_RIGHT + CONTROL "",IDC_PUSHTOTALKHOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,159,29,80,14 + CONTROL "",IDC_MUTEMICHOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,160,46,80,14 + CONTROL "",IDC_MUTEDESKTOPHOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,160,64,80,14 + RTEXT "Settings.Audio.MuteMicHotkey",IDC_STATIC,4,48,138,8 + RTEXT "Settings.Audio.MuteDesktopHotkey",IDC_STATIC,4,66,138,8 + PUSHBUTTON "ClearHotkey",IDC_CLEARPUSHTOTALK,240,29,73,14 + PUSHBUTTON "ClearHotkey",IDC_CLEARMUTEMIC,240,46,73,14 + PUSHBUTTON "ClearHotkey",IDC_CLEARMUTEDESKTOP,240,64,73,14 END IDD_ENTERNAME DIALOGEX 0, 0, 265, 49 @@ -241,7 +258,33 @@ BEGIN CONTROL "",IDC_HOTKEY,"msctls_hotkey32",WS_BORDER | WS_TABSTOP,94,7,80,14 DEFPUSHBUTTON "OK",IDOK,130,28,50,14 PUSHBUTTON "Cancel",IDCANCEL,183,28,50,14 - PUSHBUTTON "Scene.Hotkey.Clear",IDC_CLEAR,177,7,56,14 + PUSHBUTTON "ClearHotkey",IDC_CLEAR,177,7,56,14 +END + +IDD_RECONNECTING DIALOGEX 0, 0, 179, 50 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Reconnecting" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Cancel",IDCANCEL,65,29,50,14 + CTEXT "",IDC_RECONNECTING,7,12,165,8 +END + +IDD_SETTINGS_ADVANCED DIALOGEX 0, 0, 427, 287 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_SYSMENU +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Settings.Video",IDC_STATIC,4,2,418,66 + CONTROL "Settings.Advanced.VideoEncoderSettings",IDC_USEVIDEOENCODERSETTINGS, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,14,203,10 + EDITTEXT IDC_VIDEOENCODERSETTINGS,33,29,373,14,ES_AUTOHSCROLL + CONTROL "Settings.Advanced.UseSyncFix",IDC_USESYNCFIX,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,49,203,10 + LTEXT "Settings.Info",IDC_INFO,4,136,418,37,NOT WS_VISIBLE + GROUPBOX "Settings.Advanced.Network",IDC_STATIC,4,73,418,52 + CONTROL "Settings.Advanced.UseSendBuffer",IDC_USESENDBUFFER, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,22,87,203,10 + COMBOBOX IDC_SENDBUFFERSIZE,144,101,158,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + RTEXT "Settings.Advanced.SendBufferSize",IDC_STATIC,4,103,138,8 END @@ -348,6 +391,22 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 41 END + + IDD_RECONNECTING, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 172 + TOPMARGIN, 7 + BOTTOMMARGIN, 43 + END + + IDD_SETTINGS_ADVANCED, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 420 + TOPMARGIN, 7 + BOTTOMMARGIN, 280 + END END #endif // APSTUDIO_INVOKED diff --git a/OBS.vcproj b/OBS.vcproj index 80c37a2f..c3760bea 100644 --- a/OBS.vcproj +++ b/OBS.vcproj @@ -410,6 +410,10 @@ RelativePath=".\Source\Encoder_x264.cpp" > + + @@ -434,6 +438,10 @@ RelativePath=".\Source\MMDeviceAudioSource.cpp" > + + @@ -446,10 +454,6 @@ RelativePath=".\Source\RTMPPublisher.cpp" > - - diff --git a/OBSApi/APIInterface.h b/OBSApi/APIInterface.h index 8b76d1c2..01f12946 100644 --- a/OBSApi/APIInterface.h +++ b/OBSApi/APIInterface.h @@ -65,6 +65,10 @@ public: virtual Vect2 GetRenderFrameSize() const=0; //get the render frame size virtual Vect2 GetOutputSize() const=0; //get the stream output size + virtual void GetBaseSize(UINT &width, UINT &height) const=0; + virtual void GetRenderFrameSize(UINT &width, UINT &height) const=0; + virtual void GetOutputSize(UINT &width, UINT &height) const=0; + virtual CTSTR GetLanguage() const=0; virtual HWND GetMainWindow() const=0; diff --git a/OBSApi/Utility/ConfigFile.cpp b/OBSApi/Utility/ConfigFile.cpp index fec1a2bc..bbd80f83 100644 --- a/OBSApi/Utility/ConfigFile.cpp +++ b/OBSApi/Utility/ConfigFile.cpp @@ -68,7 +68,7 @@ BOOL ConfigFile::LoadFile(DWORD dwOpenMode) if(bOpen) Close(); - dwLength = file.GetFileSize(); + dwLength = (DWORD)file.GetFileSize(); LPSTR lpTempFileData = (LPSTR)Allocate(dwLength+5); file.Read(&lpTempFileData[2], dwLength); diff --git a/OBSApi/Utility/Serializer.h b/OBSApi/Utility/Serializer.h index 7f390f7a..d97fc7a6 100644 --- a/OBSApi/Utility/Serializer.h +++ b/OBSApi/Utility/Serializer.h @@ -34,9 +34,9 @@ public: virtual void Serialize(LPVOID lpData, DWORD length)=0; - virtual DWORD Seek(long offset, DWORD seekType=SERIALIZE_SEEK_START)=0; + virtual UINT64 Seek(INT64 offset, DWORD seekType=SERIALIZE_SEEK_START)=0; - virtual DWORD GetPos() const=0; + virtual UINT64 GetPos() const=0; virtual BOOL DataPending() {return FALSE;} diff --git a/OBSApi/Utility/Template.h b/OBSApi/Utility/Template.h index ab6dfe77..69a4badb 100644 --- a/OBSApi/Utility/Template.h +++ b/OBSApi/Utility/Template.h @@ -776,14 +776,14 @@ public: return position < bufferSize; } - DWORD Seek(long offset, DWORD seekType=SERIALIZE_SEEK_START) + UINT64 Seek(INT64 offset, DWORD seekType=SERIALIZE_SEEK_START) { long newPos = 0; if(seekType == SERIALIZE_SEEK_START) newPos = (long)offset; else - newPos = ((seekType == SERIALIZE_SEEK_CURRENT) ? (long)position : (long)bufferSize) + offset; + newPos = ((seekType == SERIALIZE_SEEK_CURRENT) ? (long)position : (long)bufferSize) + (long)offset; if(newPos > (long)bufferSize) { @@ -798,12 +798,12 @@ public: position = (DWORD)newPos; - return abs(offset); + return abs((long)offset); } - DWORD GetPos() const + UINT64 GetPos() const { - return position; + return UINT64(position); } private: @@ -846,13 +846,13 @@ public: position += length; } - DWORD Seek(long offset, DWORD seekType=SERIALIZE_SEEK_START) + UINT64 Seek(INT64 offset, DWORD seekType=SERIALIZE_SEEK_START) { long newPos = 0; if(seekType == SERIALIZE_SEEK_START) - newPos = offset; + newPos = (long)offset; else - newPos = ((seekType == SERIALIZE_SEEK_CURRENT) ? (long)position : (long)Buffer.Num()) + offset; + newPos = ((seekType == SERIALIZE_SEEK_CURRENT) ? (long)position : (long)Buffer.Num()) + (long)offset; if(newPos < 0) { @@ -862,12 +862,12 @@ public: position = (DWORD)newPos; - return abs(offset); + return abs((long)offset); } - DWORD GetPos() const + UINT64 GetPos() const { - return position; + return UINT64(position); } private: diff --git a/OBSApi/Utility/XConfig.cpp b/OBSApi/Utility/XConfig.cpp index 4d7477b2..5b3e5b0c 100644 --- a/OBSApi/Utility/XConfig.cpp +++ b/OBSApi/Utility/XConfig.cpp @@ -906,7 +906,7 @@ bool XConfig::Open(CTSTR lpFile) RootElement = new XElement(this, NULL, TEXT("Root")); strFileName = lpFile; - DWORD dwFileSize = file.GetFileSize(); + DWORD dwFileSize = (DWORD)file.GetFileSize(); LPSTR lpFileDataUTF8 = (LPSTR)Allocate(dwFileSize+1); zero(lpFileDataUTF8, dwFileSize+1); diff --git a/OBSApi/Utility/XFile.h b/OBSApi/Utility/XFile.h index 07f5a7c1..9cdb509b 100644 --- a/OBSApi/Utility/XFile.h +++ b/OBSApi/Utility/XFile.h @@ -38,7 +38,7 @@ class BASE_EXPORT XFile { HANDLE hFile; - DWORD dwPos; + QWORD qwPos; BOOL bHasWritten; public: @@ -63,7 +63,7 @@ public: return; SetPos(0, XFILE_BEGIN); - DWORD dwFileSize = GetFileSize(); + DWORD dwFileSize = (DWORD)GetFileSize(); LPSTR lpFileDataUTF8 = (LPSTR)Allocate(dwFileSize+1); lpFileDataUTF8[dwFileSize] = 0; Read(lpFileDataUTF8, dwFileSize); @@ -73,10 +73,9 @@ public: } BOOL SetFileSize(DWORD dwSize); - DWORD GetFileSize() const; - UINT64 GetFileSize64() const; - DWORD SetPos(int iPos, DWORD dwMoveMethod); - inline DWORD GetPos() const {return dwPos;} + QWORD GetFileSize() const; + QWORD SetPos(INT64 iPos, DWORD dwMoveMethod); + inline QWORD GetPos() const {return qwPos;} void Close(); }; @@ -132,14 +131,14 @@ public: } } - DWORD Seek(long offset, DWORD seekType=SERIALIZE_SEEK_START) + UINT64 Seek(INT64 offset, DWORD seekType=SERIALIZE_SEEK_START) { - DWORD ret; + QWORD ret; if(seekType == SERIALIZE_SEEK_START) - ret = file.SetPos(offset, XFILE_BEGIN); + ret = file.SetPos(INT64(offset), XFILE_BEGIN); else if(seekType == SERIALIZE_SEEK_CURRENT) - ret = file.SetPos(long(GetPos())+offset, XFILE_BEGIN); + ret = file.SetPos(INT64(GetPos())+offset, XFILE_BEGIN); else if(seekType == SERIALIZE_SEEK_END) ret = file.SetPos(offset, XFILE_END); @@ -148,7 +147,7 @@ public: return ret; } - DWORD GetPos() const + UINT64 GetPos() const { return (file.GetPos()-bufferSize)+bufferPos; } @@ -169,7 +168,8 @@ private: XFile file; - DWORD bufferPos, bufferSize, fileSize; + DWORD bufferPos, bufferSize; + UINT64 fileSize; BYTE Buffer[2048]; }; @@ -181,8 +181,12 @@ public: BOOL IsLoading() {return FALSE;} - BOOL Open(CTSTR lpFile, DWORD dwCreationDisposition) + BOOL Open(CTSTR lpFile, DWORD dwCreationDisposition, DWORD bufferSize=(1024*64)) { + if(!bufferSize) bufferSize = 1024*64; + + this->bufferSize = bufferSize; + Buffer = (LPBYTE)Allocate(bufferSize); totalWritten = bufferPos = 0; return file.Open(lpFile, XFILE_WRITE, dwCreationDisposition); } @@ -191,6 +195,7 @@ public: { Flush(); file.Close(); + Free(Buffer); } void Serialize(LPVOID lpData, DWORD length) @@ -203,10 +208,10 @@ public: while(length) { - if(bufferPos == 2048) + if(bufferPos == bufferSize) Flush(); - DWORD dwWriteSize = MIN(length, (2048-bufferPos)); + DWORD dwWriteSize = MIN(length, (bufferSize-bufferPos)); assert(dwWriteSize); @@ -222,7 +227,7 @@ public: } } - DWORD Seek(long offset, DWORD seekType=SERIALIZE_SEEK_START) + UINT64 Seek(INT64 offset, DWORD seekType=SERIALIZE_SEEK_START) { Flush(); if(seekType == SERIALIZE_SEEK_START) @@ -232,15 +237,15 @@ public: else if(seekType == SERIALIZE_SEEK_END) seekType = XFILE_END; - return file.SetPos(offset, seekType);; + return file.SetPos(offset, seekType); } - DWORD GetPos() const + UINT64 GetPos() const { return file.GetPos()+bufferPos; } - inline DWORD GetTotalWritten() {return totalWritten;} + inline QWORD GetTotalWritten() {return totalWritten;} private: void Flush() @@ -254,6 +259,9 @@ private: XFile file; - DWORD bufferPos, totalWritten; - BYTE Buffer[2048]; + DWORD bufferPos; + DWORD bufferSize; + + QWORD totalWritten; + LPBYTE Buffer; }; diff --git a/OBSApi/Utility/XFile_Windows.cpp b/OBSApi/Utility/XFile_Windows.cpp index 4b39f8ad..8fcfdaf5 100644 --- a/OBSApi/Utility/XFile_Windows.cpp +++ b/OBSApi/Utility/XFile_Windows.cpp @@ -30,7 +30,7 @@ XFile::XFile() { hFile = INVALID_HANDLE_VALUE; - dwPos = 0; + qwPos = 0; bHasWritten = false; } @@ -48,7 +48,7 @@ BOOL XFile::Open(CTSTR lpFile, DWORD dwAccess, DWORD dwCreationDisposition) { traceInFast(XFile::Open); - dwPos = 0; + qwPos = 0; assert(lpFile); if((hFile = CreateFile(lpFile, dwAccess, 0, NULL, dwCreationDisposition, 0, NULL)) == INVALID_HANDLE_VALUE) @@ -75,7 +75,7 @@ DWORD XFile::Read(LPVOID lpBuffer, DWORD dwBytes) if(!hFile) return XFILE_ERROR; - dwPos += dwBytes; + qwPos += dwBytes; ReadFile(hFile, lpBuffer, dwBytes, &dwRet, NULL); return dwRet; @@ -93,7 +93,7 @@ DWORD XFile::Write(const void *lpBuffer, DWORD dwBytes) if(!hFile) return XFILE_ERROR; - dwPos += dwBytes; + qwPos += dwBytes; WriteFile(hFile, lpBuffer, dwBytes, &dwRet, NULL); @@ -179,8 +179,8 @@ BOOL XFile::SetFileSize(DWORD dwSize) if(!hFile) return 0; - if(dwPos > dwSize) - dwPos = dwSize; + if(qwPos > dwSize) + qwPos = dwSize; SetPos(dwSize, XFILE_BEGIN); return SetEndOfFile(hFile); @@ -188,19 +188,10 @@ BOOL XFile::SetFileSize(DWORD dwSize) traceOutFast; } -DWORD XFile::GetFileSize() const +QWORD XFile::GetFileSize() const { traceInFast(XFile::GetFileSize); - return ::GetFileSize(hFile, NULL); - - traceOutFast; -} - -UINT64 XFile::GetFileSize64() const -{ - traceInFast(XFile::GetFileSize64); - UINT64 size = 0; size |= ::GetFileSize(hFile, (DWORD*)(((BYTE*)&size)+4)); @@ -209,7 +200,7 @@ UINT64 XFile::GetFileSize64() const traceOutFast; } -DWORD XFile::SetPos(int iPos, DWORD dwMoveMethod) //uses the SetFilePointer 4th parameter flags +UINT64 XFile::SetPos(INT64 iPos, DWORD dwMoveMethod) //uses the SetFilePointer 4th parameter flags { traceInFast(XFile::SetPos); @@ -234,7 +225,13 @@ DWORD XFile::SetPos(int iPos, DWORD dwMoveMethod) //uses the SetFilePointer 4th break; } - return (dwPos = SetFilePointer(hFile, iPos, NULL, moveValue)); + XLARGE_INT largeInt; + largeInt.largeVal = iPos; + + DWORD newPosLow = SetFilePointer(hFile, LONG(largeInt.lowVal), &largeInt.highVal, moveValue); + largeInt.lowVal = newPosLow; + + return largeInt.largeVal; traceOutFast; } diff --git a/OBSApi/Utility/XMath.cpp b/OBSApi/Utility/XMath.cpp index 97c29b05..9514a1d9 100644 --- a/OBSApi/Utility/XMath.cpp +++ b/OBSApi/Utility/XMath.cpp @@ -1464,38 +1464,38 @@ BOOL Matrix4x4Inverse(float *destMatrix, float *M1) return 1; } -void Matrix4x4Ortho(float *destMatrix, float left, float right, float bottom, float top, float near, float far) +void Matrix4x4Ortho(float *destMatrix, double left, double right, double bottom, double top, double near, double far) { float matrixOut[4][4]; zero(matrixOut, 64); - matrixOut[0][0] = 2.0f / (right-left); - matrixOut[3][0] = (left+right) / (left-right); + matrixOut[0][0] = float(2.0 / (right-left)); + matrixOut[3][0] = float((left+right) / (left-right)); - matrixOut[1][1] = 2.0f / (top-bottom); - matrixOut[3][1] = (bottom+top) / (bottom-top); + matrixOut[1][1] = float(2.0 / (top-bottom)); + matrixOut[3][1] = float((bottom+top) / (bottom-top)); - matrixOut[2][2] = 1.0f / (far-near); - matrixOut[3][2] = near / (near-far); + matrixOut[2][2] = float(1.0 / (far-near)); + matrixOut[3][2] = float(near / (near-far)); matrixOut[3][3] = 1.0f; mcpy(destMatrix, matrixOut, 64); } -void Matrix4x4Frustum(float *destMatrix, float left, float right, float bottom, float top, float near, float far) +void Matrix4x4Frustum(float *destMatrix, double left, double right, double bottom, double top, double near, double far) { float matrixOut[4][4]; zero(matrixOut, 64); - matrixOut[0][0] = (2.0f*near) / (right-left); - matrixOut[2][0] = (left+right) / (left-right); + matrixOut[0][0] = float((2.0*near) / (right-left)); + matrixOut[2][0] = float((left+right) / (left-right)); - matrixOut[1][1] = (2.0f*near) / (top-bottom); - matrixOut[2][1] = (top+bottom) / (bottom-top); + matrixOut[1][1] = float((2.0*near) / (top-bottom)); + matrixOut[2][1] = float((top+bottom) / (bottom-top)); - matrixOut[2][2] = far / (far-near); - matrixOut[3][2] = (near*far) / (near-far); + matrixOut[2][2] = float(far / (far-near)); + matrixOut[3][2] = float((near*far) / (near-far)); matrixOut[2][3] = 1.0f; diff --git a/OBSApi/Utility/XMath.h b/OBSApi/Utility/XMath.h index 32883c5f..c1d4c397 100644 --- a/OBSApi/Utility/XMath.h +++ b/OBSApi/Utility/XMath.h @@ -57,6 +57,19 @@ struct Matrix; struct Float16; +union XLARGE_INT +{ + struct {DWORD lowVal; LONG highVal;}; + INT64 largeVal; +}; + +union XLARGE_UINT +{ + struct {DWORD lowUVal; DWORD highVal;}; + UINT64 largeVal; +}; + + /*========================================================= Optimized math functions @@ -2157,6 +2170,6 @@ BASE_EXPORT float Matrix4x4Determinant(float *M1); BASE_EXPORT void Matrix4x4SubMatrix(float *destMatrix, float *M1, int i, int j); BASE_EXPORT BOOL Matrix4x4Inverse(float *destMatrix, float *M1); BASE_EXPORT void Matrix4x4Transpose(float *destMatrix, float *srcMatrix); -BASE_EXPORT void Matrix4x4Ortho(float *destMatrix, float left, float right, float bottom, float top, float near, float far); -BASE_EXPORT void Matrix4x4Frustum(float *destMatrix, float left, float right, float bottom, float top, float near, float far); +BASE_EXPORT void Matrix4x4Ortho(float *destMatrix, double left, double right, double bottom, double top, double near, double far); +BASE_EXPORT void Matrix4x4Frustum(float *destMatrix, double left, double right, double bottom, double top, double near, double far); BASE_EXPORT void Matrix4x4Perspective(float *destMatrix, float angle, float aspect, float near, float far); diff --git a/Source/API.cpp b/Source/API.cpp index a9eb2cdb..a0c539f5 100644 --- a/Source/API.cpp +++ b/Source/API.cpp @@ -241,7 +241,7 @@ bool OBS::SetScene(CTSTR lpScene) //todo: cache scenes maybe? undecided. not really as necessary with global sources OSEnterMutex(hSceneMutex); - if (scene) + if(scene) scene->EndScene(); Scene *previousScene = scene; diff --git a/Source/D3D10System.cpp b/Source/D3D10System.cpp index 6b84e89e..6dfbd7a0 100644 --- a/Source/D3D10System.cpp +++ b/Source/D3D10System.cpp @@ -631,14 +631,14 @@ void D3D10System::DrawSpriteEx(Texture *texture, float x, float y, float x2, flo VBData *data = spriteVertexBuffer->GetData(); data->VertList[0].Set(x, y, 0.0f); - data->VertList[1].Set(x2, y, 0.0f); - data->VertList[2].Set(x, y2, 0.0f); + data->VertList[1].Set(x, y2, 0.0f); + data->VertList[2].Set(x2, y, 0.0f); data->VertList[3].Set(x2, y2, 0.0f); List &coords = data->UVList[0]; coords[0].Set(u, v); - coords[1].Set(u2, v); - coords[2].Set(u, v2); + coords[1].Set(u, v2); + coords[2].Set(u2, v); coords[3].Set(u2, v2); spriteVertexBuffer->FlushBuffers(); diff --git a/Source/Encoder_x264.cpp b/Source/Encoder_x264.cpp index ce6cdade..c97495a5 100644 --- a/Source/Encoder_x264.cpp +++ b/Source/Encoder_x264.cpp @@ -102,7 +102,7 @@ public: //paramData.i_threads = 4; paramData.b_vfr_input = 1; - paramData.i_keyint_max = fps*5; //keyframe every 5 sec, should make this an option + paramData.i_keyint_max = fps*2; //keyframe every 5 sec, should make this an option paramData.i_width = width; paramData.i_height = height; paramData.rc.i_vbv_max_bitrate = maxBitRate; //vbv-maxrate @@ -122,6 +122,35 @@ public: //paramData.pf_log = get_x264_log; //paramData.i_log_level = X264_LOG_INFO; + BOOL bUseCustomParams = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCustomSettings")); + if(bUseCustomParams) + { + String strCustomParams = AppConfig->GetString(TEXT("Video Encoding"), TEXT("CustomSettings")); + + StringList paramList; + strCustomParams.GetTokenList(paramList, ' ', FALSE); + for(UINT i=0; iPacket[0] = ((nal.i_type == NAL_SLICE_IDR) ? 0x17 : 0x27); newPacket->Packet[1] = 1; mcpy(newPacket->Packet+2, timeOffsetAddr, 3); - *(DWORD*)(newPacket->Packet+5) = htonl(nal.i_payload-skipBytes); + *(DWORD*)(newPacket->Packet+5) = htonl(newPayloadSize); mcpy(newPacket->Packet+9, nal.p_payload+skipBytes, newPayloadSize); } else if(nal.i_type == NAL_SPS) diff --git a/Source/FLVFileStream.cpp b/Source/FLVFileStream.cpp new file mode 100644 index 00000000..d5abc794 --- /dev/null +++ b/Source/FLVFileStream.cpp @@ -0,0 +1,119 @@ +/******************************************************************************** + Copyright (C) 2012 Hugh Bailey + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +********************************************************************************/ + + +#include "Main.h" +#include "RTMPStuff.h" + + + +class FLVFileStream : public VideoFileStream +{ + XFileOutputSerializer fileOut; + String strFile; + + UINT64 metaDataPos; + DWORD lastTimeStamp; + + void AppendFLVPacket(LPBYTE lpData, UINT size, BYTE type, DWORD timestamp) + { + UINT networkDataSize = fastHtonl(size); + UINT networkTimestamp = fastHtonl(timestamp); + UINT streamID = 0; + fileOut.OutputByte(type); + fileOut.Serialize(((LPBYTE)(&networkDataSize))+1, 3); + fileOut.Serialize(((LPBYTE)(&networkTimestamp))+1, 3); + fileOut.Serialize(&networkTimestamp, 1); + fileOut.Serialize(&streamID, 3); + fileOut.Serialize(lpData, size); + fileOut.OutputDword(fastHtonl(size+14)); + + lastTimeStamp = timestamp; + } + +public: + bool Init(CTSTR lpFile) + { + strFile = lpFile; + + if(!fileOut.Open(lpFile, XFILE_CREATEALWAYS, 1024*1024)) + return false; + + fileOut.OutputByte('F'); + fileOut.OutputByte('L'); + fileOut.OutputByte('V'); + fileOut.OutputByte(1); + fileOut.OutputByte(5); //bit 0 = (hasAudio), bit 2 = (hasAudio) + fileOut.OutputDword(DWORD_BE(9)); + fileOut.OutputDword(0); + + metaDataPos = fileOut.GetPos(); + + char metaDataBuffer[2048]; + char *enc = metaDataBuffer; + char *pend = metaDataBuffer+sizeof(metaDataBuffer); + + enc = AMF_EncodeString(enc, pend, &av_onMetaData); + char *endMetaData = App->EncMetaData(enc, pend); + UINT metaDataSize = endMetaData-metaDataBuffer; + + AppendFLVPacket((LPBYTE)metaDataBuffer, metaDataSize, 18, 0); + return true; + } + + ~FLVFileStream() + { + UINT64 fileSize = fileOut.GetPos(); + fileOut.Close(); + + XFile file; + if(file.Open(strFile, XFILE_WRITE, XFILE_OPENEXISTING)) + { + double doubleFileSize = double(fileSize); + double doubleDuration = double(lastTimeStamp/1000); + + file.SetPos(metaDataPos+0x24, XFILE_BEGIN); + QWORD outputVal = *reinterpret_cast(&doubleDuration); + outputVal = fastHtonll(outputVal); + file.Write(&outputVal, 8); + + file.SetPos(metaDataPos+0x37, XFILE_BEGIN); + outputVal = *reinterpret_cast(&doubleFileSize); + outputVal = fastHtonll(outputVal); + file.Write(&outputVal, 8); + + file.Close(); + } + } + + virtual void AddPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type) + { + AppendFLVPacket(data, size, (type == PacketType_Audio) ? 8 : 9, timestamp); + } +}; + + +VideoFileStream* CreateFLVFileStream(CTSTR lpFile) +{ + FLVFileStream *fileStream = new FLVFileStream; + if(fileStream->Init(lpFile)) + return fileStream; + + delete fileStream; + return NULL; +} diff --git a/Source/MP4FileStream.cpp b/Source/MP4FileStream.cpp new file mode 100644 index 00000000..16398ed5 --- /dev/null +++ b/Source/MP4FileStream.cpp @@ -0,0 +1,765 @@ +/******************************************************************************** + Copyright (C) 2012 Hugh Bailey + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. +********************************************************************************/ + + +#include "Main.h" +#include + + +time_t GetMacTime() +{ + return time(0)+2082844800; +} + + +struct SampleToChunk +{ + UINT firstChunkID; + UINT samplesPerChunk; +}; + +struct OffsetVal +{ + UINT count; + UINT val; +}; + +struct MP4VideoFrameInfo +{ + UINT64 fileOffset; + UINT size; + UINT timestamp; + INT compositionOffset; +}; + +struct MP4AudioFrameInfo +{ + UINT64 fileOffset; + UINT size; +}; + +#define USE_64BIT_MP4 1 + + +//code annoyance rating: fairly nightmarish + +class MP4FileStream : public VideoFileStream +{ + XFileOutputSerializer fileOut; + String strFile; + + List videoFrames; + List audioFrames; + + List IFrameIDs; + + DWORD lastVideoTimestamp; + + bool bStreamOpened; + bool bMP3; + + List endBuffer; + List boxOffsets; + + UINT64 mdatStart, mdatStop; + + void PushBox(BufferOutputSerializer &output, DWORD boxName) + { + boxOffsets.Insert(0, endBuffer.Num()); + + output.OutputDword(0); + output.OutputDword(boxName); + } + + void PopBox() + { + DWORD boxSize = endBuffer.Num()-boxOffsets[0]; + *(DWORD*)(endBuffer.Array()+boxOffsets[0]) = fastHtonl(boxSize); + + boxOffsets.Remove(0); + } + +public: + bool Init(CTSTR lpFile) + { + strFile = lpFile; + + if(!fileOut.Open(lpFile, XFILE_CREATEALWAYS, 1024*1024)) + return false; + + fileOut.OutputDword(DWORD_BE(0x20)); + fileOut.OutputDword(DWORD_BE('ftyp')); + fileOut.OutputDword(DWORD_BE('isom')); + fileOut.OutputDword(DWORD_BE(0x200)); + fileOut.OutputDword(DWORD_BE('isom')); + fileOut.OutputDword(DWORD_BE('iso2')); + fileOut.OutputDword(DWORD_BE('avc1')); + fileOut.OutputDword(DWORD_BE('mp41')); + + fileOut.OutputDword(DWORD_BE(0x8)); + fileOut.OutputDword(DWORD_BE('free')); + + mdatStart = fileOut.GetPos(); + fileOut.OutputDword(DWORD_BE(0x1)); + fileOut.OutputDword(DWORD_BE('mdat')); +#ifdef USE_64BIT_MP4 + fileOut.OutputQword(0); +#endif + + bMP3 = scmp(App->GetAudioEncoder()->GetCodec(), TEXT("MP3")) == 0; + + bStreamOpened = true; + + return true; + } + + template inline void GetChunkInfo(List &data, List &chunks, List &sampleToChunks) + { + UINT64 curChunkOffset; + UINT64 connectedSampleOffset; + UINT numSamples = 0; + + for(UINT i=0; iGetFrameTime()); + UINT audioDuration = fastHtonl(lastVideoTimestamp + DWORD(double(App->GetAudioEncoder()->GetFrameSize())/44.1)); + UINT width, height; + App->GetOutputSize(width, height); + + LPCSTR lpVideoTrack = "videoTrack"; + LPCSTR lpAudioTrack = "audioTrack"; + + //------------------------------------------- + // get video headers + DataPacket videoHeaders; + App->GetVideoHeaders(videoHeaders); + List SPS, PPS; + + LPBYTE lpHeaderData = videoHeaders.lpPacket+11; + SPS.CopyArray(lpHeaderData+2, fastHtons(*(WORD*)lpHeaderData)); + + lpHeaderData += SPS.Num()+3; + PPS.CopyArray(lpHeaderData+2, fastHtons(*(WORD*)lpHeaderData)); + + //------------------------------------------- + // get AAC headers if using AAC + List AACHeader; + if(!bMP3) + { + DataPacket data; + App->GetAudioHeaders(data); + AACHeader.CopyArray(data.lpPacket+2, data.size-2); + } + + //------------------------------------------- + // get chunk info + List videoChunks, audioChunks; + List videoSampleToChunk, audioSampleToChunk; + + GetChunkInfo(videoFrames, videoChunks, videoSampleToChunk); + GetChunkInfo(audioFrames, audioChunks, audioSampleToChunk); + + //------------------------------------------- + // build decode time list and composition offset list + List decodeTimes; + List compositionOffsets; + + for(UINT i=0; iGetAudioEncoder()->GetBitRate()*1024); + + List esDecoderDescriptor; + BufferOutputSerializer esDecoderOut(esDecoderDescriptor); + esDecoderOut.OutputByte(bMP3 ? 107 : 64); + esDecoderOut.OutputByte(0x15); //stream/type flags. always 0x15 for my purposes. + esDecoderOut.OutputWord(0); //buffer size (seems ignorable from my testing, so 0) + esDecoderOut.OutputByte(0); + esDecoderOut.OutputDword(maxBitRate); //max bit rate (cue bill 'o reily meme for these two) + esDecoderOut.OutputDword(maxBitRate); //avg bit rate + + if(!bMP3) //if AAC, put in headers + { + esDecoderOut.OutputByte(0x5); //decoder specific descriptor type + esDecoderOut.OutputByte(0x80); //some stuff that no one should probably care about + esDecoderOut.OutputByte(0x80); + esDecoderOut.OutputByte(0x80); + esDecoderOut.OutputByte(AACHeader.Num()); + esDecoderOut.Serialize((LPVOID)AACHeader.Array(), AACHeader.Num()); + } + + esDecoderOut.OutputByte(0x6); //config descriptor type + esDecoderOut.OutputByte(0x80); //some stuff that no one should probably care about + esDecoderOut.OutputByte(0x80); + esDecoderOut.OutputByte(0x80); + esDecoderOut.OutputByte(1); //len + esDecoderOut.OutputByte(2); //SL value(? always 2) + + List esDescriptor; + BufferOutputSerializer esOut(esDescriptor); + esOut.OutputWord(0); //es id + esOut.OutputByte(0); //stream priority + esOut.OutputByte(4); //descriptor type + esOut.OutputByte(0x80); //some stuff that no one should probably care about + esOut.OutputByte(0x80); + esOut.OutputByte(0x80); + esOut.OutputByte(esDecoderDescriptor.Num()); + esOut.Serialize((LPVOID)esDecoderDescriptor.Array(), esDecoderDescriptor.Num()); + + //------------------------------------------- + + PushBox(output, DWORD_BE('moov')); + + //------------------------------------------------------ + // header + PushBox(output, DWORD_BE('mvhd')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(macTime); //creation time + output.OutputDword(macTime); //modified time + output.OutputDword(DWORD_BE(1000)); //time base (milliseconds, so 1000) + output.OutputDword(videoDuration); //duration (in time base units) + output.OutputDword(DWORD_BE(0x00010000)); //fixed point playback speed 1.0 + output.OutputWord(WORD_BE(0x0100)); //fixed point vol 1.0 + output.OutputQword(0); //reserved (10 bytes) + output.OutputWord(0); + output.OutputDword(DWORD_BE(0x00010000)); output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00000000)); //window matrix row 1 (1.0, 0.0, 0.0) + output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00010000)); output.OutputDword(DWORD_BE(0x00000000)); //window matrix row 2 (0.0, 1.0, 0.0) + output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x40000000)); //window matrix row 3 (0.0, 0.0, 16384.0) + output.OutputDword(0); //prevew start time (time base units) + output.OutputDword(0); //prevew duration (time base units) + output.OutputDword(0); //still poster frame (timestamp of frame) + output.OutputDword(0); //selection(?) start time (time base units) + output.OutputDword(0); //selection(?) duration (time base units) + output.OutputDword(0); //current time (0, time base units) + output.OutputDword(DWORD_BE(3)); //next free track id (1-based rather than 0-based) + PopBox(); //mvhd + + //------------------------------------------------------ + // video track + PushBox(output, DWORD_BE('trak')); + PushBox(output, DWORD_BE('tkhd')); //track header + output.OutputDword(DWORD_BE(0x0000000F)); //version (0) and flags (0xF) + output.OutputDword(macTime); //creation time + output.OutputDword(macTime); //modified time + output.OutputDword(DWORD_BE(1)); //track ID + output.OutputDword(0); //reserved + output.OutputDword(videoDuration); //duration (in time base units) + output.OutputQword(0); //reserved + output.OutputWord(0); //video layer (0) + output.OutputWord(0); //quicktime alternate track id (0) + output.OutputWord(0); //track audio volume (this is video, so 0) + output.OutputWord(0); //reserved + output.OutputDword(DWORD_BE(0x00010000)); output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00000000)); //window matrix row 1 (1.0, 0.0, 0.0) + output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00010000)); output.OutputDword(DWORD_BE(0x00000000)); //window matrix row 2 (0.0, 1.0, 0.0) + output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x00000000)); output.OutputDword(DWORD_BE(0x40000000)); //window matrix row 3 (0.0, 0.0, 16384.0) + output.OutputDword(fastHtonl(width<<16)); //width (fixed point) + output.OutputDword(fastHtonl(height<<16)); //height (fixed point) + PopBox(); //tkhd + PushBox(output, DWORD_BE('edts')); + PushBox(output, DWORD_BE('elst')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); //count + output.OutputDword(videoDuration); //duration + output.OutputDword(0); //start time + output.OutputDword(DWORD_BE(0x00010000)); //playback speed (1.0) + PopBox(); //elst + PopBox(); //tdst + PushBox(output, DWORD_BE('mdia')); + PushBox(output, DWORD_BE('mdhd')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(macTime); //creation time + output.OutputDword(macTime); //modified time + output.OutputDword(DWORD_BE(1000)); //time scale + output.OutputDword(videoDuration); + output.OutputDword(DWORD_BE(0x55c40000)); + PopBox(); //mdhd + PushBox(output, DWORD_BE('hdlr')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(0); //quicktime type (none) + output.OutputDword(DWORD_BE('vide')); //media type + output.OutputDword(0); //manufacturer reserved + output.OutputDword(0); //quicktime component reserved flags + output.OutputDword(0); //quicktime component reserved mask + output.Serialize((LPVOID)lpVideoTrack, (DWORD)strlen(lpVideoTrack)+1); //track name + PopBox(); //hdlr + PushBox(output, DWORD_BE('minf')); + PushBox(output, DWORD_BE('vmhd')); + output.OutputDword(DWORD_BE(0x00000001)); //version (0) and flags (1) + output.OutputWord(0); //quickdraw graphic mode (copy = 0) + output.OutputWord(0); //quickdraw red value + output.OutputWord(0); //quickdraw green value + output.OutputWord(0); //quickdraw blue value + PopBox(); //vdhd + PushBox(output, DWORD_BE('dinf')); + PushBox(output, DWORD_BE('dref')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); //count + PushBox(output, DWORD_BE('url ')); + output.OutputDword(DWORD_BE(0x00000001)); //version (0) and flags (1) + PopBox(); //url + PopBox(); //dref + PopBox(); //dinf + PushBox(output, DWORD_BE('stbl')); + PushBox(output, DWORD_BE('stsd')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); //count + PushBox(output, DWORD_BE('avc1')); + output.OutputDword(0); //reserved 6 bytes + output.OutputWord(0); + output.OutputWord(WORD_BE(1)); //index + output.OutputWord(0); //encoding version + output.OutputWord(0); //encoding revision level + output.OutputDword(0); //encoding vendor + output.OutputDword(0); //temporal quality + output.OutputDword(0); //spatial quality + output.OutputWord(fastHtons(width)); //width + output.OutputWord(fastHtons(height)); //height + output.OutputDword(DWORD_BE(0x00480000)); //fixed point width pixel resolution (72.0) + output.OutputDword(DWORD_BE(0x00480000)); //fixed point height pixel resolution (72.0) + output.OutputDword(0); //quicktime video data size + output.OutputWord(WORD_BE(1)); //frame count(?) + for(UINT i=0; i<4; i++) //encoding name (byte 1 = string length, 31 bytes = string (whats the point of having a size here?) + output.OutputQword(0); + output.OutputWord(WORD_BE(24)); //bit depth + output.OutputWord(0xFFFF); //quicktime video color table id (none = -1) + PushBox(output, DWORD_BE('avcC')); + output.OutputByte(1); //version + output.OutputByte(100); //h264 profile ID + output.OutputByte(0); //h264 compatible profiles + output.OutputByte(0x1f); //h264 level + output.OutputByte(0xff); //reserved + output.OutputByte(0xe1); //first half-byte = no clue. second half = sps count + output.OutputWord(fastHtons(SPS.Num())); //sps size + output.Serialize(SPS.Array(), SPS.Num()); //sps data + output.OutputByte(1); //pps count + output.OutputWord(fastHtons(PPS.Num())); //pps size + output.Serialize(PPS.Array(), PPS.Num()); //pps data + PopBox(); //avcC + PopBox(); //avc1 + PopBox(); //stsd + PushBox(output, DWORD_BE('stts')); //frame times + output.OutputDword(0); //version and flags (none) + output.OutputDword(fastHtonl(decodeTimes.Num())); + for(UINT i=0; i 0xFFFFFFFFLL) + { + PushBox(output, DWORD_BE('co64')); //chunk offsets + output.OutputDword(0); //version and flags (none) + output.OutputDword(fastHtonl(videoChunks.Num())); + for(UINT i=0; iGetAudioEncoder()->GetFrameSize())); + output.OutputDword(DWORD_BE(0x55c40000)); + PopBox(); //mdhd + PushBox(output, DWORD_BE('hdlr')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(0); //quicktime type (none) + output.OutputDword(DWORD_BE('soun')); //media type + output.OutputDword(0); //manufacturer reserved + output.OutputDword(0); //quicktime component reserved flags + output.OutputDword(0); //quicktime component reserved mask + output.Serialize((LPVOID)lpAudioTrack, (DWORD)strlen(lpAudioTrack)+1); //track name + PopBox(); //hdlr + PushBox(output, DWORD_BE('minf')); + PushBox(output, DWORD_BE('smhd')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(0); //balance (fixed point) + PopBox(); //vdhd + PushBox(output, DWORD_BE('dinf')); + PushBox(output, DWORD_BE('dref')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); //count + PushBox(output, DWORD_BE('url ')); + output.OutputDword(DWORD_BE(0x00000001)); //version (0) and flags (1) + PopBox(); //url + PopBox(); //dref + PopBox(); //dinf + PushBox(output, DWORD_BE('stbl')); + PushBox(output, DWORD_BE('stsd')); + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); //count + PushBox(output, DWORD_BE('mp4a')); + output.OutputDword(0); //reserved (6 bytes) + output.OutputWord(0); + output.OutputWord(WORD_BE(1)); //dref index + output.OutputWord(0); //quicktime encoding version + output.OutputWord(0); //quicktime encoding revision + output.OutputDword(0); //quicktime audio encoding vendor + output.OutputWord(WORD_BE(2)); //channels + output.OutputWord(WORD_BE(16)); //sample size + output.OutputWord(0); //quicktime audio compression id + output.OutputWord(0); //quicktime audio packet size + output.OutputDword(DWORD_BE(44100<<16)); //sample rate (fixed point) + PushBox(output, DWORD_BE('esds')); + output.OutputDword(0); //version and flags (none) + output.OutputByte(3); //ES descriptor type + output.OutputByte(0x80); + output.OutputByte(0x80); + output.OutputByte(0x80); + output.OutputByte(esDescriptor.Num()); + output.Serialize((LPVOID)esDescriptor.Array(), esDescriptor.Num()); + PopBox(); + PopBox(); + PopBox(); //stsd + PushBox(output, DWORD_BE('stts')); //list of keyframe (i-frame) IDs + output.OutputDword(0); //version and flags (none) + output.OutputDword(DWORD_BE(1)); + output.OutputDword(fastHtonl(audioFrames.Num())); + output.OutputDword(fastHtonl(App->GetAudioEncoder()->GetFrameSize())); + PopBox(); //stss + PushBox(output, DWORD_BE('stsc')); //sample to chunk list + output.OutputDword(0); //version and flags (none) + output.OutputDword(fastHtonl(audioSampleToChunk.Num())); + for(UINT i=0; i 0xFFFFFFFFLL) + { + PushBox(output, DWORD_BE('co64')); //chunk offsets + output.OutputDword(0); //version and flags (none) + output.OutputDword(fastHtonl(audioChunks.Num())); + for(UINT i=0; i= 0x80) + timeOffset |= 0xFF; + timeOffset = (INT)fastHtonl(DWORD(timeOffset)); + + if(data[0] == 0x17) //i-frame + IFrameIDs << fastHtonl(videoFrames.Num()+1); + + MP4VideoFrameInfo frameInfo; + frameInfo.fileOffset = offset; + frameInfo.size = totalCopied; + frameInfo.timestamp = timestamp; + frameInfo.compositionOffset = timeOffset; + videoFrames << frameInfo; + } + else + videoFrames.Last().size += totalCopied; + + lastVideoTimestamp = timestamp; + } + } +}; + + +VideoFileStream* CreateMP4FileStream(CTSTR lpFile) +{ + MP4FileStream *fileStream = new MP4FileStream; + if(fileStream->Init(lpFile)) + return fileStream; + + delete fileStream; + return NULL; +} diff --git a/Source/Main.h b/Source/Main.h index a64bbf70..4f5c97e7 100644 --- a/Source/Main.h +++ b/Source/Main.h @@ -72,6 +72,16 @@ inline UINT ConvertMSTo100NanoSec(UINT ms) return ms*1000*10; //1000 microseconds, then 10 "100nanosecond" segments } +//big endian conversion functions +#define QWORD_BE(val) (((val>>56)&0xFF) | (((val>>48)&0xFF)<<8) | (((val>>40)&0xFF)<<16) | (((val>>32)&0xFF)<<24) | \ + (((val>>24)&0xFF)<<32) | (((val>>16)&0xFF)<<40) | (((val>>8)&0xFF)<<48) | ((val&0xFF)<<56)) +#define DWORD_BE(val) (((val>>24)&0xFF) | (((val>>16)&0xFF)<<8) | (((val>>8)&0xFF)<<16) | ((val&0xFF)<<24)) +#define WORD_BE(val) (((val>>8)&0xFF) | ((val&0xFF)<<8)) + +__forceinline QWORD fastHtonll(QWORD qw) {return QWORD_BE(qw);} +__forceinline DWORD fastHtonl (DWORD dw) {return DWORD_BE(dw);} +__forceinline WORD fastHtons (WORD w) {return WORD_BE(w);} + //------------------------------------------- // application headers diff --git a/Source/OBS.cpp b/Source/OBS.cpp index 853e320a..e3320769 100644 --- a/Source/OBS.cpp +++ b/Source/OBS.cpp @@ -44,8 +44,8 @@ bool STDCALL ConfigureBitmapSource(XElement *element, bool bCreating); ImageSource* STDCALL CreateGlobalSource(XElement *data); -NetworkStream* CreateRTMPServer(); -NetworkStream* CreateRTMPPublisher(); +//NetworkStream* CreateRTMPServer(); +NetworkStream* CreateRTMPPublisher(String &failReason, bool &bCanRetry); NetworkStream* CreateBandwidthAnalyzer(); void StartBlankSoundPlayback(); @@ -55,6 +55,10 @@ VideoEncoder* CreateNullVideoEncoder(); AudioEncoder* CreateNullAudioEncoder(); NetworkStream* CreateNullNetwork(); +VideoFileStream* CreateMP4FileStream(CTSTR lpFile); +VideoFileStream* CreateFLVFileStream(CTSTR lpFile); +//VideoFileStream* CreateAVIFileStream(CTSTR lpFile); + void Convert444to420(LPBYTE input, int width, int height, LPBYTE *output, bool bSSE2Available); void STDCALL SceneHotkey(DWORD hotkey, UPARAM param, bool bDown); @@ -342,6 +346,10 @@ public: virtual Vect2 GetRenderFrameSize() const {return Vect2(float(App->renderFrameWidth), float(App->renderFrameHeight));} virtual Vect2 GetOutputSize() const {return Vect2(float(App->outputCX), float(App->outputCY));} + virtual void GetBaseSize(UINT &width, UINT &height) const {App->GetBaseSize(width, height);} + virtual void GetRenderFrameSize(UINT &width, UINT &height) const {App->GetRenderFrameSize(width, height);} + virtual void GetOutputSize(UINT &width, UINT &height) const {App->GetOutputSize(width, height);} + virtual CTSTR GetLanguage() const {return App->strLanguage;} virtual CTSTR GetAppDataPath() const {return lpAppDataPath;} @@ -717,6 +725,11 @@ OBS::OBS() //----------------------------------------------------- + bAutoReconnect = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnect")) != 0; + reconnectTimeout = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), 10); + if(reconnectTimeout < 5) + reconnectTimeout = 5; + bRenderViewEnabled = true; //bShowFPS = AppConfig->GetInt(TEXT("General"), TEXT("ShowFPS")) != 0; @@ -904,19 +917,30 @@ void OBS::Start() int networkMode = AppConfig->GetInt(TEXT("Publish"), TEXT("Mode"), 2); + bool bCanRetry = false; + String strError; + if(bTestStream) network = CreateBandwidthAnalyzer(); else { switch(networkMode) { - case 0: network = CreateRTMPPublisher(); break; - case 1: network = CreateRTMPServer(); break; + case 0: network = CreateRTMPPublisher(strError, bCanRetry); break; + case 1: network = CreateNullNetwork(); break; } } if(!network) + { + if(!bReconnecting || !bCanRetry) + MessageBox(hwndMain, strError, NULL, MB_ICONERROR); + else + DialogBox(hinstMain, MAKEINTRESOURCE(IDD_RECONNECTING), hwndMain, OBS::ReconnectDialogProc); return; + } + + bReconnecting = false; //------------------------------------------------------------- @@ -1097,6 +1121,27 @@ void OBS::Start() //------------------------------------------------------------- + bWriteToFile = AppConfig->GetInt(TEXT("Publish"), TEXT("SaveToFile")) != 0; + String strOutputFile = AppConfig->GetString(TEXT("Publish"), TEXT("SavePath")); + + if(OSFileExists(strOutputFile)) + { + strOutputFile.FindReplace(TEXT("\\"), TEXT("/")); + String strFileWithoutExtension = GetPathWithoutExtension(strOutputFile); + String strFileExtension = GetPathExtension(strOutputFile); + UINT curFile = 0; + + String strNewFilePath; + do + { + strNewFilePath.Clear() << strFileWithoutExtension << TEXT(" (") << FormattedString(TEXT("%02u"), ++curFile) << TEXT(").") << strFileExtension; + } while(OSFileExists(strNewFilePath)); + + strOutputFile = strNewFilePath; + } + + //------------------------------------------------------------- + hRequestAudioEvent = CreateSemaphore(NULL, 0, 0x7FFFFFFFL, NULL); hSoundDataMutex = OSCreateMutex(); hSoundThread = OSCreateThread((XTHREAD)OBS::MainAudioThread, NULL); @@ -1109,6 +1154,19 @@ void OBS::Start() videoEncoder = CreateX264Encoder(fps, outputCX, outputCY, quality, preset, bUsing444, maxBitRate, bufferSize); + //------------------------------------------------------------- + + if(!bTestStream && bWriteToFile && strOutputFile.IsValid()) + { + String strFileExtension = GetPathExtension(strOutputFile); + if(strFileExtension.CompareI(TEXT("flv"))) + fileStream = CreateFLVFileStream(strOutputFile); + else if(strFileExtension.CompareI(TEXT("mp4"))) + fileStream = CreateMP4FileStream(strOutputFile); + /*else if(strFileExtension.CompareI(TEXT("avi"))) + fileStream = CreateAVIFileStream(strOutputFile));*/ + } + hMainThread = OSCreateThread((XTHREAD)OBS::MainCaptureThread, NULL); if(bTestStream) @@ -1173,13 +1231,15 @@ void OBS::Stop() delete micAudio; delete desktopAudio; - delete audioEncoder; + delete fileStream; + delete audioEncoder; delete videoEncoder; network = NULL; micAudio = NULL; desktopAudio = NULL; + fileStream = NULL; audioEncoder = NULL; videoEncoder = NULL; @@ -1478,7 +1538,7 @@ void OBS::MainCaptureLoop() SetRenderTarget(mainRenderTextures[curRenderTarget]); - Ortho(0.0f, baseSize.x, baseSize.y, 0.0f, -1.0f, 1000.0f); + Ortho(0.0f, baseSize.x, baseSize.y, 0.0f, -100.0f, 100.0f); SetViewport(0, 0, baseSize.x, baseSize.y); if(scene) @@ -1531,7 +1591,7 @@ void OBS::MainCaptureLoop() LoadVertexShader(mainVertexShader); LoadPixelShader(mainPixelShader); - Ortho(0.0f, renderFrameSize.x, renderFrameSize.y, 0.0f, -1.0f, 1000.0f); + Ortho(0.0f, renderFrameSize.x, renderFrameSize.y, 0.0f, -100.0f, 100.0f); SetViewport(0.0f, 0.0f, renderFrameSize.x, renderFrameSize.y); if(bTransitioning) @@ -1543,7 +1603,7 @@ void OBS::MainCaptureLoop() DrawSprite(mainRenderTextures[curRenderTarget], 0.0f, 0.0f, renderFrameSize.x, renderFrameSize.y); - Ortho(0.0f, renderFrameSize.x, renderFrameSize.y, 0.0f, -1.0f, 1000.0f); + Ortho(0.0f, renderFrameSize.x, renderFrameSize.y, 0.0f, -100.0f, 100.0f); LoadVertexShader(solidVertexShader); LoadPixelShader(solidPixelShader); @@ -1552,7 +1612,7 @@ void OBS::MainCaptureLoop() //draw selections if in edit mode if(bEditMode && !bSizeChanging) { - Ortho(0.0f, baseSize.x, baseSize.y, 0.0f, -1.0f, 1000.0f); + Ortho(0.0f, baseSize.x, baseSize.y, 0.0f, -100.0f, 100.0f); if(scene) scene->RenderSelections(); @@ -1602,7 +1662,7 @@ void OBS::MainCaptureLoop() yuvScalePixelShader->SetVector2(hScaleVal, 1.0f/baseSize); - Ortho(0.0f, outputSize.x, outputSize.y, 0.0f, -1.0f, 1000.0f); + Ortho(0.0f, outputSize.x, outputSize.y, 0.0f, -100.0f, 100.0f); SetViewport(0.0f, 0.0f, outputSize.x, outputSize.y); //why am I using scaleSize instead of outputSize for the texture? @@ -1615,7 +1675,7 @@ void OBS::MainCaptureLoop() BlendFunction(GS_BLEND_FACTOR, GS_BLEND_INVFACTOR, transitionAlpha); } - DrawSpriteEx(mainRenderTextures[curRenderTarget], 0.0f, 0.0f, scaleSize.x, scaleSize.y, 0.0f, 0.0f, scaleSize.x, scaleSize.y); + DrawSpriteEx(mainRenderTextures[curRenderTarget], 0.0f, 0.0f, outputSize.x, outputSize.y, 0.0f, 0.0f, outputSize.x, outputSize.y); //------------------------------------ @@ -1741,6 +1801,9 @@ void OBS::MainCaptureLoop() if(audioData.Num()) { network->SendPacket(audioData.Array(), audioData.Num(), pendingAudioFrames[0].timestamp, PacketType_Audio); + if(fileStream) + fileStream->AddPacket(audioData.Array(), audioData.Num(), pendingAudioFrames[0].timestamp, PacketType_Audio); + audioData.Clear(); } @@ -1759,6 +1822,8 @@ void OBS::MainCaptureLoop() PacketType type = videoPacketTypes[i]; network->SendPacket(packet.lpPacket, packet.size, curTimeStamp, type); + if(fileStream) + fileStream->AddPacket(packet.lpPacket, packet.size, curTimeStamp, type); } curPTS--; diff --git a/Source/OBS.h b/Source/OBS.h index 41c5ecc5..60aeb1e2 100644 --- a/Source/OBS.h +++ b/Source/OBS.h @@ -98,6 +98,15 @@ public: //------------------------------------------------------------------- +class VideoFileStream +{ +public: + virtual ~VideoFileStream() {} + virtual void AddPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type)=0; +}; + +//------------------------------------------------------------------- + enum { NoAudioAvailable, @@ -250,6 +259,7 @@ enum #define OBS_REQUESTSTOP WM_USER+1 #define OBS_CALLHOTKEY WM_USER+2 +#define OBS_RECONNECT WM_USER+3 //---------------------------- @@ -338,7 +348,7 @@ class OBS static INT_PTR CALLBACK PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static INT_PTR CALLBACK VideoSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static INT_PTR CALLBACK AudioSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); - static INT_PTR CALLBACK HotkeysSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK AdvancedSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); //--------------------------------------------------- @@ -351,6 +361,11 @@ class OBS bool bSizeChanging; bool bResizeRenderView; + bool bAutoReconnect; + bool bRetrying; + bool bReconnecting; + UINT reconnectTimeout; + bool bEditMode; bool bRenderViewEnabled; bool bShowFPS; @@ -384,6 +399,9 @@ class OBS float desktopVol, micVol; List pendingAudioFrames; + bool bWriteToFile; + VideoFileStream *fileStream; + String streamReport; List Icons; @@ -455,6 +473,7 @@ class OBS static INT_PTR CALLBACK EnterSourceNameDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static INT_PTR CALLBACK EnterSceneNameDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static INT_PTR CALLBACK SceneHotkeyDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + static INT_PTR CALLBACK ReconnectDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK ListboxHook(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK RenderFrameProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK OBSProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); @@ -466,8 +485,6 @@ class OBS void ToggleCapturing(); - char* EncMetaData(char *enc, char *pend); - Scene* CreateScene(CTSTR lpClassName, XElement *data); void ConfigureScene(XElement *element); void ConfigureImageSource(XElement *element); @@ -486,12 +503,18 @@ public: OBS(); ~OBS(); + char* EncMetaData(char *enc, char *pend); + inline void PostStopMessage() {if(hwndMain) PostMessage(hwndMain, OBS_REQUESTSTOP, 0, 0);} inline Vect2 GetBaseSize() const {return Vect2(float(baseCX), float(baseCY));} inline Vect2 GetOutputSize() const {return Vect2(float(outputCX), float(outputCY));} inline Vect2 GetRenderFrameSize() const {return Vect2(float(renderFrameWidth), float(renderFrameHeight));} + inline void GetBaseSize(UINT &width, UINT &height) const {width = baseCX; height = baseCY;} + inline void GetRenderFrameSize(UINT &width, UINT &height) const {width = renderFrameWidth; height = renderFrameHeight;} + inline void GetOutputSize(UINT &width, UINT &height) const {width = outputCX; height = outputCY;} + inline bool SSE2Available() const {return bSSE2Available;} inline AudioEncoder* GetAudioEncoder() const {return audioEncoder;} diff --git a/Source/RTMPPublisher.cpp b/Source/RTMPPublisher.cpp index 287c561d..a12afebd 100644 --- a/Source/RTMPPublisher.cpp +++ b/Source/RTMPPublisher.cpp @@ -358,12 +358,23 @@ public: rtmp = rtmpIn; - sendBuffer.SetSize(32768); + BOOL bUseSendBuffer = AppConfig->GetInt(TEXT("Publish"), TEXT("UseSendBuffer"), 1); + UINT sendBufferSize = AppConfig->GetInt(TEXT("Publish"), TEXT("SendBufferSize"), 32768); + + if(sendBufferSize > 32768) + sendBufferSize = 32768; + else if(sendBufferSize < 8192) + sendBufferSize = 8192; + + sendBuffer.SetSize(sendBufferSize); curSendBufferLen = 0; - rtmp->m_customSendFunc = (CUSTOMSEND)RTMPPublisher::BufferedSend; - rtmp->m_customSendParam = this; - rtmp->m_bCustomSend = TRUE; + if(bUseSendBuffer) + { + rtmp->m_customSendFunc = (CUSTOMSEND)RTMPPublisher::BufferedSend; + rtmp->m_customSendParam = this; + rtmp->m_bCustomSend = TRUE; + } hSendSempahore = CreateSemaphore(NULL, 0, 0x7FFFFFFFL, NULL); if(!hSendSempahore) @@ -569,13 +580,15 @@ public: } }; -NetworkStream* CreateRTMPPublisher() +NetworkStream* CreateRTMPPublisher(String &failReason, bool &bCanRetry) { traceIn(CreateRTMPPublisher); //------------------------------------------------------ // set up URL + bCanRetry = false; + String strURL; int serviceID = AppConfig->GetInt (TEXT("Publish"), TEXT("Service")); @@ -585,13 +598,13 @@ NetworkStream* CreateRTMPPublisher() if(!strServer.IsValid()) { - MessageBox(hwndMain, TEXT("No server specified to connect to"), NULL, MB_ICONERROR); + failReason = TEXT("No server specified to connect to"); return NULL; } if(!strChannel.IsValid()) { - MessageBox(hwndMain, TEXT("No channel specified"), NULL, MB_ICONERROR); + failReason = TEXT("No channel specified"); return NULL; } @@ -600,35 +613,35 @@ NetworkStream* CreateRTMPPublisher() XConfig serverData; if(!serverData.Open(TEXT("services.xconfig"))) { - MessageBox(hwndMain, TEXT("Could not open services.xconfig"), NULL, MB_ICONERROR); + failReason = TEXT("Could not open services.xconfig"); return NULL; } XElement *services = serverData.GetElement(TEXT("services")); if(!services) { - MessageBox(hwndMain, TEXT("Could not any services in services.xconfig"), NULL, MB_ICONERROR); + failReason = TEXT("Could not any services in services.xconfig"); return NULL; } XElement *service = services->GetElementByID(serviceID-1); if(!service) { - MessageBox(hwndMain, TEXT("Could not find the service specified in services.xconfig"), NULL, MB_ICONERROR); + failReason = TEXT("Could not find the service specified in services.xconfig"); return NULL; } XElement *servers = service->GetElement(TEXT("servers")); if(!servers) { - MessageBox(hwndMain, TEXT("Could not find any servers for the service specified in services.xconfig"), NULL, MB_ICONERROR); + failReason = TEXT("Could not find any servers for the service specified in services.xconfig"); return NULL; } XDataItem *item = servers->GetDataItem(strServer); if(!item) { - MessageBox(hwndMain, TEXT("Could not find any server specified for the service specified in services.xconfig"), NULL, MB_ICONERROR); + failReason = TEXT("Could not find any server specified for the service specified in services.xconfig"); return NULL; } @@ -650,7 +663,7 @@ NetworkStream* CreateRTMPPublisher() if(!RTMP_SetupURL(rtmp, lpAnsiURL)) { - MessageBox(hwndMain, Str("Connection.CouldNotParseURL"), NULL, MB_ICONERROR); + failReason = Str("Connection.CouldNotParseURL"); RTMP_Free(rtmp); return NULL; } @@ -661,14 +674,15 @@ NetworkStream* CreateRTMPPublisher() if(!RTMP_Connect(rtmp, NULL)) { - MessageBox(hwndMain, Str("Connection.CouldNotConnect"), NULL, MB_ICONERROR); + failReason = Str("Connection.CouldNotConnect"); RTMP_Free(rtmp); + bCanRetry = true; return NULL; } if(!RTMP_ConnectStream(rtmp, 0)) { - MessageBox(hwndMain, Str("Connection.InvalidStream"), NULL, MB_ICONERROR); + failReason = Str("Connection.InvalidStream"); RTMP_Close(rtmp); RTMP_Free(rtmp); return NULL; diff --git a/Source/RTMPServer.cpp b/Source/RTMPServer.cpp deleted file mode 100644 index a5bfaf76..00000000 --- a/Source/RTMPServer.cpp +++ /dev/null @@ -1,549 +0,0 @@ -/******************************************************************************** - Copyright (C) 2012 Hugh Bailey - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. -********************************************************************************/ - - -#include "Main.h" -#include "RTMPStuff.h" - - -class RTMPServer; - - -void rtmp_log_output(int level, const char *format, va_list vl); - - -//all connections should be handled in the same thread with select, but because the rtmp server is mostly just for -//personal or testing purposes (and because I'm lazy), I'm just putting each socket in their own threads -class Connection -{ - friend class RTMPServer; - - RTMP *rtmp; - SOCKET socket; - sockaddr_storage addr; - char host[NI_MAXHOST]; - char service[NI_MAXSERV]; - bool bReadyForOutput; - bool bDestroySocket; - int streamChannel; - - RTMPServer *server; - - HANDLE hThread; - - static DWORD STDCALL SocketThread(Connection *connection); - - void SendMetaData(); - -public: - inline Connection(SOCKET socket, RTMPServer *server, sockaddr_storage *addrIn, char *host, char *service) - { - this->server = server; - this->socket = socket; - mcpy(&addr, addrIn, sizeof(addr)); - strcpy(this->host, host); - strcpy(this->service, service); - - hThread = OSCreateThread((XTHREAD)SocketThread, (LPVOID)this); - } - - inline ~Connection() - { - if(RTMP_IsConnected(rtmp) && bReadyForOutput) - SendPlayStop(rtmp); - - if(hThread) - { - if(!bDestroySocket) - { - bDestroySocket = true; - OSWaitForThread(hThread, NULL); - } - - OSCloseThread(hThread); - } - } - - inline bool ReadyForOutput() const {return bReadyForOutput;} - inline RTMP* GetRTMP() const {return rtmp;} - - int DoInvoke(RTMPPacket *packet, unsigned int offset); -}; - - -class RTMPServer : public NetworkStream -{ - friend class Connection; - - SOCKET serverSocket; - - bool bDestroySockets; - List connections; - HANDLE hListMutex, hListenThread; - - - void ListenLoop() - { - traceIn(RTMPServer::ListenLoop); - - fd_set socketlist; - timeval duration; - sockaddr_storage addr; - char host[NI_MAXHOST], service[NI_MAXSERV]; - - duration.tv_sec = 0; - duration.tv_usec = 20000; - - while(!bDestroySockets) - { - FD_ZERO(&socketlist); - FD_SET(serverSocket, &socketlist); - - int ret = select(0, &socketlist, NULL, NULL, &duration); - if(ret == -1) - { - int chi = WSAGetLastError(); - AppWarning(TEXT("Got error %d from a select on the server socket"), chi); - } - else if(ret != 0) - { - int addrLen = sizeof(addr); - SOCKET clientSocket = accept(serverSocket, (sockaddr*)&addr, &addrLen); - if(clientSocket == INVALID_SOCKET) - continue; - - getnameinfo((struct sockaddr *)&addr, addrLen, - host, sizeof(host), - service, sizeof(service), - NI_NUMERICHOST); - - OSEnterMutex(hListMutex); - connections << new Connection(clientSocket, this, &addr, host, service); - OSLeaveMutex(hListMutex); - } - } - - traceOut; - } - - static DWORD STDCALL ListenThread(RTMPServer *server) - { - server->ListenLoop(); - return 0; - } - - -public: - RTMPServer() - { - traceIn(RTMPServer::RTMPServer); - - //RTMP_LogSetCallback(rtmp_log_output); - //RTMP_LogSetLevel(RTMP_LOGDEBUG); - - bDestroySockets = false; - - hListMutex = OSCreateMutex(); - - addrinfo *ai_save, *local_ai, hint; - zero(&hint, sizeof(hint)); - - hint.ai_flags = AI_PASSIVE; - hint.ai_family = AF_INET;//AF_UNSPEC;// - hint.ai_socktype = SOCK_STREAM; - - DWORD ret = getaddrinfo(NULL, "1935", &hint, &local_ai); - if(ret) - return; //this should actually never happen - - ai_save = local_ai; - - serverSocket = -1; - while(local_ai) - { - serverSocket = socket(local_ai->ai_family, local_ai->ai_socktype, local_ai->ai_protocol); - int err = WSAGetLastError(); - - if(serverSocket != INVALID_SOCKET) - { - if(bind(serverSocket, local_ai->ai_addr, (int)local_ai->ai_addrlen) == 0) - break; - - closesocket(serverSocket); - serverSocket = -1; - } - - local_ai = local_ai->ai_next; - } - - if(serverSocket != INVALID_SOCKET) - { - ret = listen(serverSocket, SOMAXCONN); - if(ret) - { - ret = WSAGetLastError(); - AppWarning(TEXT("Failed to set up the server")); - } - else - hListenThread = OSCreateThread((XTHREAD)ListenThread, (LPVOID)this); - } - else - AppWarning(TEXT("Failed to get a server socket")); - - freeaddrinfo(ai_save); - - traceOut; - } - - ~RTMPServer() - { - traceIn(RTMPServer::~RTMPServer); - - if(hListenThread) - { - bDestroySockets = true; - OSWaitForThread(hListenThread, NULL); - OSCloseThread(hListenThread); - } - - if(hListMutex) - OSCloseMutex(hListMutex); - - for(UINT i=0; i IAmNotHappyRightNow; - IAmNotHappyRightNow.SetSize(RTMP_MAX_HEADER_SIZE); - IAmNotHappyRightNow.AppendArray(data, size); - - packet.m_nBodySize = size; - packet.m_body = (char*)IAmNotHappyRightNow.Array()+RTMP_MAX_HEADER_SIZE; - - OSEnterMutex(hListMutex); - for(UINT i=0; iReadyForOutput()) - { - if(!RTMP_SendPacket(connection->GetRTMP(), &packet, FALSE)) - { - delete connection; - connections.Remove(i--); - } - } - } - OSLeaveMutex(hListMutex); - - traceOut; - } - - double GetPacketStrain() const {return 0.0;} - QWORD GetCurrentSentBytes() {return 0;} -}; - - -int Connection::DoInvoke(RTMPPacket *packet, unsigned int offset) -{ - const char *body; - unsigned int nBodySize; - int ret = 0, nRes; - - body = packet->m_body + offset; - nBodySize = packet->m_nBodySize - offset; - - if (body[0] != 0x02) - return 0; - - //--------------------------------------------------- - - AMFObject obj; - nRes = AMF_Decode(&obj, body, nBodySize, FALSE); - if (nRes < 0) - return 0; - - //--------------------------------------------------- - - AVal method; - AMFProp_GetString(AMF_GetProp(&obj, NULL, 0), &method); - double txn = AMFProp_GetNumber(AMF_GetProp(&obj, NULL, 1)); - - if(AVMATCH(&method, &av_connect)) - { - AMFObject connectObj; - AVal pname, pval; - - AMFProp_GetObject(AMF_GetProp(&obj, NULL, 2), &connectObj); - for(int i=0; iLink.app = pval; - if (!rtmp->Link.app.av_val) rtmp->Link.app.av_val = ""; - } - else if (AVMATCH(&pname, &av_flashVer)) - rtmp->Link.flashVer = pval; - else if (AVMATCH(&pname, &av_swfUrl)) - rtmp->Link.swfUrl = pval; - else if (AVMATCH(&pname, &av_tcUrl)) - rtmp->Link.tcUrl = pval; - else if (AVMATCH(&pname, &av_pageUrl)) - rtmp->Link.pageUrl = pval; - else if (AVMATCH(&pname, &av_audioCodecs)) - rtmp->m_fAudioCodecs = connectObj.o_props[i].p_vu.p_number; - else if (AVMATCH(&pname, &av_videoCodecs)) - rtmp->m_fVideoCodecs = connectObj.o_props[i].p_vu.p_number; - else if (AVMATCH(&pname, &av_objectEncoding)) - rtmp->m_fEncoding = connectObj.o_props[i].p_vu.p_number; - } - - SendConnectResult(rtmp, txn); - } - else if(AVMATCH(&method, &av_createStream)) - SendResultNumber(rtmp, txn, 1.0); - else if(AVMATCH(&method, &av_deleteStream)) - ret = 1; - else if(AVMATCH(&method, &av_getStreamLength)) - SendResultNumber(rtmp, txn, 0.0); - else if(AVMATCH(&method, &av_NetStream_Authenticate_UsherToken)) - { - AVal usherToken; - AMFProp_GetString(AMF_GetProp(&obj, NULL, 3), &usherToken); - AVreplace(&usherToken, &av_dquote, &av_escdquote); - rtmp->Link.usherToken = usherToken; - } - else if(AVMATCH(&method, &av_play)) - { - RTMP_SendCtrl(rtmp, 0, 1, 0); - SendPlayStart(rtmp); - - streamChannel = packet->m_nInfoField2; - - SendMetaData(); - - //----------------------------------------------------- - // send video headers - - DataPacket headers; - - App->GetVideoHeaders(headers); - - RTMPPacket headerPacket; - headerPacket.m_nChannel = 0x04; // source channel (invoke) - headerPacket.m_headerType = RTMP_PACKET_SIZE_LARGE; - headerPacket.m_packetType = RTMP_PACKET_TYPE_VIDEO; - headerPacket.m_nTimeStamp = 0; - headerPacket.m_nInfoField2 = 1; - headerPacket.m_hasAbsTimestamp = 1; - - List IAmNotHappyRightNow; - IAmNotHappyRightNow.SetSize(RTMP_MAX_HEADER_SIZE); - IAmNotHappyRightNow.AppendArray(headers.lpPacket, headers.size); - - headerPacket.m_nBodySize = headers.size; - headerPacket.m_body = (char*)IAmNotHappyRightNow.Array()+RTMP_MAX_HEADER_SIZE; - - RTMP_SendPacket(rtmp, &headerPacket, FALSE); - - //----------------------------------------------------- - // send audio headers - - headerPacket.m_nChannel = 0x05; // audio channel - headerPacket.m_packetType = RTMP_PACKET_TYPE_AUDIO; - - App->GetAudioHeaders(headers); - - IAmNotHappyRightNow.SetSize(RTMP_MAX_HEADER_SIZE); - IAmNotHappyRightNow.AppendArray(headers.lpPacket, headers.size); - - headerPacket.m_nBodySize = headers.size; - headerPacket.m_body = (char*)IAmNotHappyRightNow.Array()+RTMP_MAX_HEADER_SIZE; - - RTMP_SendPacket(rtmp, &headerPacket, FALSE); - - //----------------------------------------------------- - - bReadyForOutput = true; - } - else if (AVMATCH(&method, &av_FCSubscribe)) - { - - } - - return ret; -} - -void Connection::SendMetaData() -{ - RTMPPacket packet; - - //--------------------------------------------------- - - char pbuf[1024], *pend = pbuf+sizeof(pbuf); - - packet.m_nChannel = 0x03; // control channel (invoke) - packet.m_headerType = RTMP_PACKET_SIZE_MEDIUM; - packet.m_packetType = RTMP_PACKET_TYPE_INFO; - packet.m_nTimeStamp = 0; - packet.m_nInfoField2 = 0; - packet.m_hasAbsTimestamp = 0; - packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE; - - char *enc = packet.m_body; - enc = AMF_EncodeString(enc, pend, &av_onMetaData); - enc = App->EncMetaData(enc, pend); - - packet.m_nBodySize = enc - packet.m_body; - RTMP_SendPacket(rtmp, &packet, FALSE); - - bReadyForOutput = true; -} - - -DWORD STDCALL Connection::SocketThread(Connection *connection) -{ - fd_set socketlist; - timeval duration; - RTMP *clientRtmp; - RTMPPacket packet; - - clientRtmp = RTMP_Alloc(); - RTMP_Init(clientRtmp); - zero(&packet, sizeof(packet)); - - //----------------------------------------------- - - duration.tv_sec = 0; - duration.tv_usec = 20000; - - FD_ZERO(&socketlist); - FD_SET(connection->socket, &socketlist); - - if(select(0, &socketlist, NULL, NULL, &duration) <= 0) - { - DWORD chi = WSAGetLastError(); - return 0; - } - else - { - clientRtmp->m_sb.sb_socket = connection->socket; - - connection->rtmp = clientRtmp; - - if(!RTMP_Serve(clientRtmp)) - { - RTMP_Close(clientRtmp); - RTMP_Free(clientRtmp); - return 0; - } - } - - //----------------------------------------------- - - while(!connection->server->bDestroySockets && !connection->bDestroySocket && RTMP_IsConnected(clientRtmp) && RTMP_ReadPacket(clientRtmp, &packet)) - { - if(!RTMPPacket_IsReady(&packet)) - continue; - - switch(packet.m_packetType) - { - case RTMP_PACKET_TYPE_CHUNK_SIZE: - //ChangeChunkSize(?); - break; - - case RTMP_PACKET_TYPE_CONTROL: - //SetControl(?); - break; - - case RTMP_PACKET_TYPE_SERVER_BW: - //ServerBandwidth(?); - break; - case RTMP_PACKET_TYPE_CLIENT_BW: - //ClientBandwidth(?); - break; - - case RTMP_PACKET_TYPE_FLEX_MESSAGE: - //Invoke? - break; - - case RTMP_PACKET_TYPE_INFO: - //meta data thingy, shouldn't even get it, at least I think - break; - - case RTMP_PACKET_TYPE_INVOKE: - if(connection->DoInvoke(&packet, 0)) - connection->bDestroySocket = true; - break; - } - - RTMPPacket_Free(&packet); - } - - if(!connection->bDestroySocket) //if we received a request to stop stream, automatically delete self - { - OSEnterMutex(connection->server->hListMutex); - - connection->bDestroySocket = true; - connection->server->connections.RemoveItem(connection); - delete connection; - - OSLeaveMutex(connection->server->hListMutex); - } - - RTMP_Close(clientRtmp); - RTMP_Free(clientRtmp); - - connection->rtmp = NULL; - - return 0; -} - - -NetworkStream* CreateRTMPServer() -{ - return new RTMPServer; -} - diff --git a/Source/RTMPStuff.cpp b/Source/RTMPStuff.cpp index 4018384f..2163d173 100644 --- a/Source/RTMPStuff.cpp +++ b/Source/RTMPStuff.cpp @@ -222,23 +222,32 @@ char* OBS::EncMetaData(char *enc, char *pend) int audioBitRate = GetAudioEncoder()->GetBitRate(); CTSTR lpAudioCodec = GetAudioEncoder()->GetCodec(); + double audioCodecID; const AVal *av_codecFourCC; + if(scmpi(lpAudioCodec, TEXT("AAC")) == 0) + { av_codecFourCC = &av_mp4a; + audioCodecID = 10.0; + } else + { av_codecFourCC = &av_mp3; + audioCodecID = 2.0; + } *enc++ = AMF_OBJECT; - enc = AMF_EncodeNamedNumber(enc, pend, &av_duration, 0.0); + enc = AMF_EncodeNamedNumber(enc, pend, &av_duration, 0.0); + enc = AMF_EncodeNamedNumber(enc, pend, &av_fileSize, 0.0); enc = AMF_EncodeNamedNumber(enc, pend, &av_width, double(outputCX)); enc = AMF_EncodeNamedNumber(enc, pend, &av_height, double(outputCY)); - enc = AMF_EncodeNamedString(enc, pend, &av_videocodecid, &av_avc1);//7.0);// + enc = AMF_EncodeNamedNumber(enc, pend, &av_videocodecid, 7.0);//&av_avc1);// enc = AMF_EncodeNamedNumber(enc, pend, &av_videodatarate, double(maxBitRate)); enc = AMF_EncodeNamedNumber(enc, pend, &av_framerate, double(fps)); - enc = AMF_EncodeNamedString(enc, pend, &av_audiocodecid, av_codecFourCC);//0.0);//&justdiealready);//&av_mp4a);//2.0);// + enc = AMF_EncodeNamedNumber(enc, pend, &av_audiocodecid, audioCodecID);//av_codecFourCC);// enc = AMF_EncodeNamedNumber(enc, pend, &av_audiodatarate, double(audioBitRate)); //ex. 128kb\s enc = AMF_EncodeNamedNumber(enc, pend, &av_audiosamplerate, 44100.0); @@ -246,7 +255,6 @@ char* OBS::EncMetaData(char *enc, char *pend) enc = AMF_EncodeNamedNumber(enc, pend, &av_audiochannels, 2.0); enc = AMF_EncodeNamedBoolean(enc, pend, &av_stereo, true); enc = AMF_EncodeNamedString(enc, pend, &av_encoder, &av_OBSVersion); - enc = AMF_EncodeNamedNumber(enc, pend, &av_fileSize, 0.0); *enc++ = 0; *enc++ = 0; *enc++ = AMF_OBJECT_END; diff --git a/Source/Settings.cpp b/Source/Settings.cpp index dfe8d66e..0282acfa 100644 --- a/Source/Settings.cpp +++ b/Source/Settings.cpp @@ -26,7 +26,7 @@ enum SettingsSelection Settings_Publish, Settings_Video, Settings_Audio, - Settings_Hotkeys, + Settings_Advanced, }; BOOL CALLBACK MonitorInfoEnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, List &monitors); @@ -545,8 +545,7 @@ INT_PTR CALLBACK OBS::PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam hwndTemp = GetDlgItem(hwnd, IDC_MODE); SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.LiveStream")); - SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.Serve")); - //SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.SaveToFile")); + SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish.Mode.FileOnly")); int mode = LoadSettingComboInt(hwndTemp, TEXT("Publish"), TEXT("Mode"), 0, 2); @@ -634,6 +633,30 @@ INT_PTR CALLBACK OBS::PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam if(mode != 0) ShowWindow(hwndTemp, SW_HIDE); + //-------------------------------------------- + + hwndTemp = GetDlgItem(hwnd, IDC_AUTORECONNECT); + + BOOL bAutoReconnect = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnect")); + SendMessage(hwndTemp, BM_SETCHECK, bAutoReconnect ? BST_CHECKED : BST_UNCHECKED, 0); + + if(mode != 0) ShowWindow(hwndTemp, SW_HIDE); + + hwndTemp = GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT); + EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), bAutoReconnect); + EnableWindow(hwndTemp, bAutoReconnect); + + int retryTime = AppConfig->GetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), 10); + if(retryTime > 60) retryTime = 60; + else if(retryTime < 5) retryTime = 5; + + SendMessage(hwndTemp, UDM_SETRANGE32, 5, 60); + SendMessage(hwndTemp, UDM_SETPOS32, 0, retryTime); + + if(mode != 0) ShowWindow(hwndTemp, SW_HIDE); + + //-------------------------------------------- + if(mode != 0) { ShowWindow(GetDlgItem(hwnd, IDC_SERVICE_STATIC), SW_HIDE); @@ -641,14 +664,42 @@ INT_PTR CALLBACK OBS::PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam ShowWindow(GetDlgItem(hwnd, IDC_PLAYPATH_STATIC), SW_HIDE); ShowWindow(GetDlgItem(hwnd, IDC_CHANNELNAME_STATIC), SW_HIDE); ShowWindow(GetDlgItem(hwnd, IDC_SERVER_STATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_STATIC), SW_HIDE); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), SW_HIDE); } + //-------------------------------------------- + + BOOL bSaveToFile = AppConfig->GetInt(TEXT("Publish"), TEXT("SaveToFile")); + SendMessage(GetDlgItem(hwnd, IDC_SAVETOFILE), BM_SETCHECK, bSaveToFile ? BST_CHECKED : BST_UNCHECKED, 0); + + CTSTR lpSavePath = AppConfig->GetStringPtr(TEXT("Publish"), TEXT("SavePath")); + SetWindowText(GetDlgItem(hwnd, IDC_SAVEPATH), lpSavePath); + + EnableWindow(GetDlgItem(hwnd, IDC_SAVEPATH), bSaveToFile); + EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), bSaveToFile); + + //-------------------------------------------- + ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_HIDE); App->SetChangedSettings(false); return TRUE; } + case WM_NOTIFY: + { + NMHDR *nmHdr = (NMHDR*)lParam; + + if(nmHdr->idFrom == IDC_AUTORECONNECT_TIMEOUT) + { + if(nmHdr->code == UDN_DELTAPOS) + App->SetChangedSettings(true); + } + + break; + } + case WM_COMMAND: { bool bDataChanged = false; @@ -685,6 +736,10 @@ INT_PTR CALLBACK OBS::PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam ShowWindow(GetDlgItem(hwnd, IDC_PLAYPATH_STATIC), swShowControls); ShowWindow(GetDlgItem(hwnd, IDC_CHANNELNAME_STATIC), swShowControls); ShowWindow(GetDlgItem(hwnd, IDC_SERVER_STATIC), swShowControls); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT), swShowControls); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT), swShowControls); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_STATIC), swShowControls); + ShowWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), swShowControls); bDataChanged = true; break; @@ -754,10 +809,94 @@ INT_PTR CALLBACK OBS::PublishSettingsProc(HWND hwnd, UINT message, WPARAM wParam } break; - //case IDC_USERNAME: + case IDC_AUTORECONNECT: + if(HIWORD(wParam) == BN_CLICKED) + { + BOOL bAutoReconnect = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED; + + EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT), bAutoReconnect); + EnableWindow(GetDlgItem(hwnd, IDC_AUTORECONNECT_TIMEOUT_EDIT), bAutoReconnect); + + App->SetChangedSettings(true); + } + break; + + case IDC_AUTORECONNECT_TIMEOUT_EDIT: + if(HIWORD(wParam) == EN_CHANGE) + App->SetChangedSettings(true); + break; + + case IDC_SAVETOFILE: + if(HIWORD(wParam) == BN_CLICKED) + { + BOOL bSaveToFile = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED; + + EnableWindow(GetDlgItem(hwnd, IDC_SAVEPATH), bSaveToFile); + EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), bSaveToFile); + + bDataChanged = true; + } + break; + + case IDC_BROWSE: + { + TCHAR lpFile[512]; + OPENFILENAME ofn; + zero(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = hwnd; + ofn.lpstrFile = lpFile; + ofn.nMaxFile = 511; + ofn.lpstrFile[0] = 0; + ofn.lpstrFilter = TEXT("MP4 File (*.mp4)\0*.mp4\0Flash Video File (*.flv)\0*.flv\0"); + ofn.lpstrFileTitle = NULL; + ofn.nMaxFileTitle = 0; + ofn.nFilterIndex = 1; + ofn.lpstrInitialDir = AppConfig->GetStringPtr(TEXT("Publish"), TEXT("LastSaveDir")); + + ofn.Flags = OFN_PATHMUSTEXIST; + + TCHAR curDirectory[512]; + GetCurrentDirectory(511, curDirectory); + + BOOL bChoseFile = GetSaveFileName(&ofn); + SetCurrentDirectory(curDirectory); + + if(bChoseFile) + { + String strFile = lpFile; + strFile.FindReplace(TEXT("\\"), TEXT("/")); + + String strExtension = GetPathExtension(strFile); + if(strExtension.IsEmpty() || (!strExtension.CompareI(TEXT("flv")) && /*!strExtension.CompareI(TEXT("avi")) &&*/ !strExtension.CompareI(TEXT("mp4")))) + { + switch(ofn.nFilterIndex) + { + case 1: + strFile << TEXT(".mp4"); break; + case 2: + strFile << TEXT(".flv"); break; + /*case 3: + strFile << TEXT(".avi"); break;*/ + } + } + + String strFilePath = GetPathDirectory(strFile).FindReplace(TEXT("/"), TEXT("\\")) << TEXT("\\"); + AppConfig->SetString(TEXT("Publish"), TEXT("LastSaveDir"), strFilePath); + + strFile.FindReplace(TEXT("/"), TEXT("\\")); + SetWindowText(GetDlgItem(hwnd, IDC_SAVEPATH), strFile); + bDataChanged = true; + } + + break; + } + + //case IDC_USERNAME: case IDC_PLAYPATH: case IDC_CHANNELNAME: case IDC_SERVEREDIT: + case IDC_SAVEPATH: if(HIWORD(wParam) == EN_CHANGE) bDataChanged = true; break; @@ -945,10 +1084,10 @@ INT_PTR CALLBACK OBS::VideoSettingsProc(HWND hwnd, UINT message, WPARAM wParam, //-------------------------------------------- hwndTemp = GetDlgItem(hwnd, IDC_FPS); - SendMessage(hwndTemp, UDM_SETRANGE32, 5, 60); + SendMessage(hwndTemp, UDM_SETRANGE32, 15, 60); int fps = AppConfig->GetInt(TEXT("Video"), TEXT("FPS"), 25); - if(!AppConfig->HasKey(TEXT("Video"), TEXT("FPS")) || fps < 5 || fps > 60) + if(!AppConfig->HasKey(TEXT("Video"), TEXT("FPS")) || fps < 15 || fps > 60) { AppConfig->SetInt(TEXT("Video"), TEXT("FPS"), 25); fps = 25; @@ -1115,6 +1254,28 @@ INT_PTR CALLBACK OBS::AudioSettingsProc(HWND hwnd, UINT message, WPARAM wParam, SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)audioDevices); + //-------------------------------------------- + + BOOL bPushToTalk = AppConfig->GetInt(TEXT("Audio"), TEXT("UsePushToTalk")); + SendMessage(GetDlgItem(hwnd, IDC_PUSHTOTALK), BM_SETCHECK, bPushToTalk ? BST_CHECKED : BST_UNCHECKED, 0); + EnableWindow(GetDlgItem(hwnd, IDC_PUSHTOTALKHOTKEY), bPushToTalk); + EnableWindow(GetDlgItem(hwnd, IDC_CLEARPUSHTOTALK), bPushToTalk); + + DWORD hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("PushToTalkHotkey")); + SendMessage(GetDlgItem(hwnd, IDC_PUSHTOTALKHOTKEY), HKM_SETHOTKEY, hotkey, 0); + + //-------------------------------------------- + + hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("MuteMicHotkey")); + SendMessage(GetDlgItem(hwnd, IDC_MUTEMICHOTKEY), HKM_SETHOTKEY, hotkey, 0); + + //-------------------------------------------- + + hotkey = AppConfig->GetInt(TEXT("Audio"), TEXT("MuteDesktopHotkey")); + SendMessage(GetDlgItem(hwnd, IDC_MUTEDESKTOPHOTKEY), HKM_SETHOTKEY, hotkey, 0); + + //-------------------------------------------- + App->SetChangedSettings(false); return TRUE; } @@ -1131,6 +1292,47 @@ INT_PTR CALLBACK OBS::AudioSettingsProc(HWND hwnd, UINT message, WPARAM wParam, switch(LOWORD(wParam)) { + case IDC_PUSHTOTALK: + if(HIWORD(wParam) == BN_CLICKED) + { + BOOL bUsePushToTalk = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED; + EnableWindow(GetDlgItem(hwnd, IDC_PUSHTOTALKHOTKEY), bUsePushToTalk); + EnableWindow(GetDlgItem(hwnd, IDC_CLEARPUSHTOTALK), bUsePushToTalk); + App->SetChangedSettings(true); + } + break; + + case IDC_PUSHTOTALKHOTKEY: + case IDC_MUTEMICHOTKEY: + case IDC_MUTEDESKTOPHOTKEY: + if(HIWORD(wParam) == EN_CHANGE) + App->SetChangedSettings(true); + break; + + case IDC_CLEARPUSHTOTALK: + if(HIWORD(wParam) == BN_CLICKED) + { + SendMessage(GetDlgItem(hwnd, IDC_PUSHTOTALKHOTKEY), HKM_SETHOTKEY, 0, 0); + App->SetChangedSettings(true); + } + break; + + case IDC_CLEARMUTEMIC: + if(HIWORD(wParam) == BN_CLICKED) + { + SendMessage(GetDlgItem(hwnd, IDC_MUTEMICHOTKEY), HKM_SETHOTKEY, 0, 0); + App->SetChangedSettings(true); + } + break; + + case IDC_CLEARMUTEDESKTOP: + if(HIWORD(wParam) == BN_CLICKED) + { + SendMessage(GetDlgItem(hwnd, IDC_MUTEDESKTOPHOTKEY), HKM_SETHOTKEY, 0, 0); + App->SetChangedSettings(true); + } + break; + case IDC_MICDEVICES: if(HIWORD(wParam) == CBN_SELCHANGE) bDataChanged = true; @@ -1148,14 +1350,120 @@ INT_PTR CALLBACK OBS::AudioSettingsProc(HWND hwnd, UINT message, WPARAM wParam, return FALSE; } -INT_PTR CALLBACK OBS::HotkeysSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +INT_PTR CALLBACK OBS::AdvancedSettingsProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: - LocalizeWindow(hwnd); - App->SetChangedSettings(false); - return TRUE; + { + LocalizeWindow(hwnd); + + //-------------------------------------------- + + HWND hwndToolTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP|TTS_NOPREFIX|TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + hwnd, NULL, hinstMain, NULL); + + TOOLINFO ti; + zero(&ti, sizeof(ti)); + ti.cbSize = sizeof(ti); + ti.uFlags = TTF_SUBCLASS|TTF_IDISHWND; + ti.hwnd = hwnd; + + SendMessage(hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 500); + SendMessage(hwndToolTip, TTM_SETDELAYTIME, TTDT_AUTOPOP, 14000); + + //------------------------------------ + + bool bUseCustomX264Settings = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseCustomSettings")) != 0; + String strX264Settings = AppConfig->GetString(TEXT("Video Encoding"), TEXT("CustomSettings")); + + SendMessage(GetDlgItem(hwnd, IDC_USEVIDEOENCODERSETTINGS), BM_SETCHECK, bUseCustomX264Settings ? BST_CHECKED : BST_UNCHECKED, 0); + SetWindowText(GetDlgItem(hwnd, IDC_VIDEOENCODERSETTINGS), strX264Settings); + + ti.lpszText = (LPWSTR)Str("Settings.Advanced.VideoEncoderSettingsTooltip"); + ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_VIDEOENCODERSETTINGS); + SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_USEVIDEOENCODERSETTINGS); + SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + EnableWindow(GetDlgItem(hwnd, IDC_VIDEOENCODERSETTINGS), bUseCustomX264Settings); + + //------------------------------------ + + bool bUseVideoSyncFix = AppConfig->GetInt(TEXT("Video Encoding"), TEXT("UseSyncFix")) != 0; + SendMessage(GetDlgItem(hwnd, IDC_USESYNCFIX), BM_SETCHECK, bUseVideoSyncFix ? BST_CHECKED : BST_UNCHECKED, 0); + + ti.lpszText = (LPWSTR)Str("Settings.Advanced.UseSyncFixTooltip"); + ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_USESYNCFIX); + SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + //------------------------------------ + + BOOL bUseSendBuffer = AppConfig->GetInt(TEXT("Publish"), TEXT("UseSendBuffer"), 1) != 0; + SendMessage(GetDlgItem(hwnd, IDC_USESENDBUFFER), BM_SETCHECK, bUseSendBuffer ? BST_CHECKED : BST_UNCHECKED, 0); + + HWND hwndTemp = GetDlgItem(hwnd, IDC_SENDBUFFERSIZE); + EnableWindow(hwndTemp, bUseSendBuffer); + SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)TEXT("32768")); + SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)TEXT("16384")); + SendMessage(hwndTemp, CB_ADDSTRING, 0, (LPARAM)TEXT("8192")); + + LoadSettingTextComboString(hwndTemp, TEXT("Publish"), TEXT("SendBufferSize"), TEXT("32768")); + + ti.lpszText = (LPWSTR)Str("Settings.Advanced.UseSendBufferTooltip"); + ti.uId = (UINT_PTR)GetDlgItem(hwnd, IDC_USESENDBUFFER); + SendMessage(hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); + + //------------------------------------ + + App->SetChangedSettings(false); + return TRUE; + } + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDC_USEVIDEOENCODERSETTINGS: + if(HIWORD(wParam) == BN_CLICKED) + { + BOOL bUseVideoSyncFix = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED; + EnableWindow(GetDlgItem(hwnd, IDC_VIDEOENCODERSETTINGS), bUseVideoSyncFix); + + ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_SHOW); + App->SetChangedSettings(true); + } + break; + + case IDC_USESENDBUFFER: + if(HIWORD(wParam) == BN_CLICKED) + { + BOOL bUseSendBuffer = SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED; + EnableWindow(GetDlgItem(hwnd, IDC_SENDBUFFERSIZE), bUseSendBuffer); + + ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_SHOW); + App->SetChangedSettings(true); + } + break; + + case IDC_SENDBUFFERSIZE: + if(HIWORD(wParam) == CBN_SELCHANGE) + { + ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_SHOW); + App->SetChangedSettings(true); + } + break; + + case IDC_USESYNCFIX: + if(HIWORD(wParam) == BN_CLICKED) + { + ShowWindow(GetDlgItem(hwnd, IDC_INFO), SW_SHOW); + App->SetChangedSettings(true); + } + break; + } + } return FALSE; } @@ -1226,6 +1534,30 @@ void OBS::ApplySettings() strTemp = GetCBText(GetDlgItem(hwndCurrentSettings, IDC_SERVERLIST)); AppConfig->SetString(TEXT("Publish"), TEXT("Server"), strTemp); } + + //------------------------------------------ + + App->bAutoReconnect = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_AUTORECONNECT), BM_GETCHECK, 0, 0) == BST_CHECKED; + + BOOL bError = FALSE; + App->reconnectTimeout = (UINT)SendMessage(GetDlgItem(hwndCurrentSettings, IDC_AUTORECONNECT_TIMEOUT), UDM_GETPOS32, 0, (LPARAM)&bError); + if(bError) + App->reconnectTimeout = 5; + + AppConfig->SetInt(TEXT("Publish"), TEXT("AutoReconnect"), App->bAutoReconnect); + AppConfig->SetInt(TEXT("Publish"), TEXT("AutoReconnectTimeout"), App->reconnectTimeout); + + //------------------------------------------ + + String strSavePath = GetEditText(GetDlgItem(hwndCurrentSettings, IDC_SAVEPATH)); + BOOL bSaveToFile = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_SAVETOFILE), BM_GETCHECK, 0, 0) != BST_UNCHECKED; + + if(!strSavePath.IsValid()) + bSaveToFile = FALSE; + + AppConfig->SetInt (TEXT("Publish"), TEXT("SaveToFile"), bSaveToFile); + AppConfig->SetString(TEXT("Publish"), TEXT("SavePath"), strSavePath); + break; } @@ -1277,11 +1609,48 @@ void OBS::ApplySettings() EnableWindow(GetDlgItem(hwndMain, ID_MICVOLUME), FALSE); else EnableWindow(GetDlgItem(hwndMain, ID_MICVOLUME), TRUE); + + //------------------------------------ + + BOOL bUsePushToTalk = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_PUSHTOTALK), BM_GETCHECK, 0, 0) == BST_CHECKED; + DWORD hotkey = (DWORD)SendMessage(GetDlgItem(hwndCurrentSettings, IDC_PUSHTOTALKHOTKEY), HKM_GETHOTKEY, 0, 0); + + AppConfig->SetInt(TEXT("Audio"), TEXT("UsePushToTalk"), bUsePushToTalk); + AppConfig->SetInt(TEXT("Audio"), TEXT("PushToTalkHotkey"), hotkey); + + //------------------------------------ + + hotkey = (DWORD)SendMessage(GetDlgItem(hwndCurrentSettings, IDC_MUTEMICHOTKEY), HKM_GETHOTKEY, 0, 0); + AppConfig->SetInt(TEXT("Audio"), TEXT("MuteMicHotkey"), hotkey); + + //------------------------------------ + + hotkey = (DWORD)SendMessage(GetDlgItem(hwndCurrentSettings, IDC_MUTEDESKTOPHOTKEY), HKM_GETHOTKEY, 0, 0); + AppConfig->SetInt(TEXT("Audio"), TEXT("MuteDesktopHotkey"), hotkey); + break; } - case Settings_Hotkeys: + case Settings_Advanced: { + BOOL bUseCustomX264Settings = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_USEVIDEOENCODERSETTINGS), BM_GETCHECK, 0, 0) == BST_CHECKED; + String strCustomX264Settings = GetEditText(GetDlgItem(hwndCurrentSettings, IDC_VIDEOENCODERSETTINGS)); + + AppConfig->SetInt (TEXT("Video Encoding"), TEXT("UseCustomSettings"), bUseCustomX264Settings); + AppConfig->SetString(TEXT("Video Encoding"), TEXT("CustomSettings"), strCustomX264Settings); + + //-------------------------------------------------- + + BOOL bUseVideoSyncFix = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_USESYNCFIX), BM_GETCHECK, 0, 0) == BST_CHECKED; + AppConfig->SetInt (TEXT("Video Encoding"), TEXT("UseSyncFix"), bUseVideoSyncFix); + + //-------------------------------------------------- + + BOOL bUseSendBuffer = SendMessage(GetDlgItem(hwndCurrentSettings, IDC_USESENDBUFFER), BM_GETCHECK, 0, 0) == BST_CHECKED; + String strSendBufferSize = GetCBText(GetDlgItem(hwndCurrentSettings, IDC_USESENDBUFFER)); + + AppConfig->SetInt (TEXT("Publish"), TEXT("UseSendBuffer"), bUseSendBuffer); + AppConfig->SetString(TEXT("Publish"), TEXT("SendBufferSize"), strSendBufferSize); break; } } @@ -1306,7 +1675,7 @@ INT_PTR CALLBACK OBS::SettingsDialogProc(HWND hwnd, UINT message, WPARAM wParam, SendMessage(GetDlgItem(hwnd, IDC_SETTINGSLIST), LB_ADDSTRING, 0, (LPARAM)Str("Settings.Publish")); SendMessage(GetDlgItem(hwnd, IDC_SETTINGSLIST), LB_ADDSTRING, 0, (LPARAM)Str("Settings.Video")); SendMessage(GetDlgItem(hwnd, IDC_SETTINGSLIST), LB_ADDSTRING, 0, (LPARAM)Str("Settings.Audio")); - //SendMessage(GetDlgItem(hwnd, IDC_SETTINGSLIST), LB_ADDSTRING, 0, (LPARAM)Str("Settings.Hotkeys")); + SendMessage(GetDlgItem(hwnd, IDC_SETTINGSLIST), LB_ADDSTRING, 0, (LPARAM)Str("Settings.Advanced")); RECT subDialogRect; GetWindowRect(GetDlgItem(hwnd, IDC_SUBDIALOG), &subDialogRect); @@ -1372,7 +1741,8 @@ INT_PTR CALLBACK OBS::SettingsDialogProc(HWND hwnd, UINT message, WPARAM wParam, case Settings_Audio: App->hwndCurrentSettings = CreateDialog(hinstMain, MAKEINTRESOURCE(IDD_SETTINGS_AUDIO), hwnd, (DLGPROC)OBS::AudioSettingsProc); break; - case Settings_Hotkeys: + case Settings_Advanced: + App->hwndCurrentSettings = CreateDialog(hinstMain, MAKEINTRESOURCE(IDD_SETTINGS_ADVANCED), hwnd, (DLGPROC)OBS::AdvancedSettingsProc); break; } diff --git a/Source/VolumeControl.cpp b/Source/VolumeControl.cpp index e3e81fe2..a4b6fc7d 100644 --- a/Source/VolumeControl.cpp +++ b/Source/VolumeControl.cpp @@ -130,17 +130,25 @@ LRESULT CALLBACK VolumeControlProc(HWND hwnd, UINT message, WPARAM wParam, LPARA short x = short(LOWORD(lParam)); short y = short(HIWORD(lParam)); + UINT id = (UINT)GetWindowLongPtr(hwnd, GWLP_ID); + if(message == WM_LBUTTONDOWN && !control->bDisabled) { if(control->cy == 32 && x >= (control->cx-32)) { if(control->curVolume < EPSILON) + { + if(control->lastUnmutedVol < EPSILON) + control->lastUnmutedVol = 1.0f; control->curVolume = control->lastUnmutedVol; + } else { control->lastUnmutedVol = control->curVolume; control->curVolume = 0.0f; } + + SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(id, VOLN_FINALVALUE), (LPARAM)hwnd); } else { @@ -153,14 +161,13 @@ LRESULT CALLBACK VolumeControlProc(HWND hwnd, UINT message, WPARAM wParam, LPARA int cxAdjust = control->cx; if(control->bDrawIcon) cxAdjust -= 32; control->curVolume = float(x) / cxAdjust; + + SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(id, VOLN_ADJUSTING), (LPARAM)hwnd); } HDC hDC = GetDC(hwnd); control->DrawVolumeControl(hDC); ReleaseDC(hwnd, hDC); - - UINT id = (UINT)GetWindowLongPtr(hwnd, GWLP_ID); - SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(id, VOLN_ADJUSTING), (LPARAM)hwnd); } else if(control->bHasCapture) { diff --git a/Source/WindowStuff.cpp b/Source/WindowStuff.cpp index cc182797..747c2719 100644 --- a/Source/WindowStuff.cpp +++ b/Source/WindowStuff.cpp @@ -1339,6 +1339,93 @@ INT_PTR CALLBACK OBS::GlobalSourcesProc(HWND hwnd, UINT message, WPARAM wParam, //---------------------------- +struct ReconnectInfo +{ + UINT_PTR timerID; + UINT secondsLeft; +}; + +INT_PTR CALLBACK OBS::ReconnectDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + { + LocalizeWindow(hwnd); + + ReconnectInfo *ri = new ReconnectInfo; + ri->secondsLeft = App->reconnectTimeout; + ri->timerID = 1; + + if(!SetTimer(hwnd, 1, 1000, NULL)) + { + App->bReconnecting = false; + EndDialog(hwnd, IDCANCEL); + delete ri; + } + + String strText; + if(App->bReconnecting) + strText << Str("Reconnecting.Retrying") << UIntString(ri->secondsLeft); + else + strText << Str("Reconnecting") << UIntString(ri->secondsLeft); + + SetWindowText(GetDlgItem(hwnd, IDC_RECONNECTING), strText); + + SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)ri); + return TRUE; + } + + case WM_TIMER: + { + ReconnectInfo *ri = (ReconnectInfo*)GetWindowLongPtr(hwnd, DWLP_USER); + if(wParam != 1) + break; + + if(!--ri->secondsLeft) + { + SendMessage(hwndMain, OBS_RECONNECT, 0, 0); + EndDialog(hwnd, IDOK); + } + else + { + String strText; + if(App->bReconnecting) + strText << Str("Reconnecting.Retrying") << UIntString(ri->secondsLeft); + else + strText << Str("Reconnecting") << UIntString(ri->secondsLeft); + + SetWindowText(GetDlgItem(hwnd, IDC_RECONNECTING), strText); + } + break; + } + + case WM_COMMAND: + if(LOWORD(wParam) == IDCANCEL) + { + App->bReconnecting = false; + EndDialog(hwnd, IDCANCEL); + } + break; + + case WM_CLOSE: + App->bReconnecting = false; + EndDialog(hwnd, IDCANCEL); + break; + + case WM_DESTROY: + { + ReconnectInfo *ri = (ReconnectInfo*)GetWindowLongPtr(hwnd, DWLP_USER); + KillTimer(hwnd, ri->timerID); + delete ri; + } + } + + return FALSE; +} + +//---------------------------- + LRESULT CALLBACK OBS::OBSProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { traceIn(OBS::OBSProc); @@ -1527,13 +1614,24 @@ LRESULT CALLBACK OBS::OBSProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lPa case OBS_REQUESTSTOP: App->Stop(); - MessageBox(hwnd, Str("Connection.Disconnected"), NULL, 0); + if(!App->bAutoReconnect) + MessageBox(hwnd, Str("Connection.Disconnected"), NULL, 0); + else + { + App->bReconnecting = false; + DialogBox(hinstMain, MAKEINTRESOURCE(IDD_RECONNECTING), hwnd, OBS::ReconnectDialogProc); + } break; case OBS_CALLHOTKEY: App->CallHotkey((DWORD)lParam, wParam != 0); break; + case OBS_RECONNECT: + App->bReconnecting = true; + App->Start(); + break; + case WM_CLOSE: PostQuitMessage(0); break; diff --git a/resource.h b/resource.h index 3205c7e6..2f6dcbc7 100644 --- a/resource.h +++ b/resource.h @@ -23,6 +23,9 @@ #define IDD_GLOBAL_SOURCES 126 #define IDD_PLUGINS 129 #define IDD_SCENEHOTKEY 130 +#define IDD_DIALOG1 131 +#define IDD_RECONNECTING 131 +#define IDD_SETTINGS_ADVANCED 132 #define IDC_SETTINGSLIST 1006 #define IDC_SUBDIALOG 1007 #define IDC_MODE 1008 @@ -58,10 +61,16 @@ #define IDC_RESETSIZE 1041 #define IDC_CAPTUREMOUSE 1041 #define IDC_BUFFERSENDS 1041 +#define IDC_AUTORECONNECT 1041 +#define IDC_PUSHTOTALK 1041 +#define IDC_USEVIDEOENCODERSETTINGS 1041 #define IDC_SERVEREDIT 1042 +#define IDC_USEVIDEOENCODERSETTINGS2 1042 +#define IDC_USESENDBUFFER 1042 #define IDC_CHANNELNAME_STATIC 1043 #define IDC_PLAYPATH_STATIC 1044 #define IDC_USERNAME_STATIC 1045 +#define IDC_SAVETOFILE 1045 #define IDC_SERVER_STATIC 1046 #define IDC_SERVICE_STATIC 1047 #define IDC_NAME 1048 @@ -91,8 +100,25 @@ #define IDC_SELECTREGION 1071 #define IDC_HOTKEY1 1073 #define IDC_HOTKEY 1073 +#define IDC_PUSHTOTALKHOTKEY 1073 #define IDC_CLEAR 1074 +#define IDC_MUTEMICHOTKEY 1074 +#define IDC_MUTEDESKTOPHOTKEY 1075 #define IDC_ADDNEW 1077 +#define IDC_EDIT1 1078 +#define IDC_AUTORECONNECT_TIMEOUT_EDIT 1078 +#define IDC_VIDEOENCODERSETTINGS 1078 +#define IDC_SPIN1 1079 +#define IDC_AUTORECONNECT_TIMEOUT 1079 +#define IDC_AUTORECONNECT_TIMEOUT_STATIC 1080 +#define IDC_RECONNECTING 1081 +#define IDC_SAVEPATH 1081 +#define IDC_SAVEPATH_STATIC 1082 +#define IDC_USESYNCFIX 1086 +#define IDC_CLEARPUSHTOTALK 1088 +#define IDC_CLEARMUTEMIC 1089 +#define IDC_SENDBUFFERSIZE 1089 +#define IDC_CLEARMUTEDESKTOP 1090 #define IDA_SOURCE_MOVEUP 40018 #define IDA_SOURCE_MOVEDOWN 40019 #define IDA_SOURCE_MOVETOTOP 40020 @@ -106,9 +132,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 131 +#define _APS_NEXT_RESOURCE_VALUE 133 #define _APS_NEXT_COMMAND_VALUE 40028 -#define _APS_NEXT_CONTROL_VALUE 1078 +#define _APS_NEXT_CONTROL_VALUE 1090 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif