file saving, advanced settings, bug fixes, cats and dogs living together... mass hysteria!
This commit is contained in:
parent
2833b6a60e
commit
71be2c937e
81
OBS.rc
81
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 "ƒXƒ^ƒeƒBƒbƒN",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 "ƒXƒ^ƒeƒBƒbƒN",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
|
||||
|
||||
|
12
OBS.vcproj
12
OBS.vcproj
@ -410,6 +410,10 @@
|
||||
RelativePath=".\Source\Encoder_x264.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\FLVFileStream.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\GetAudioDevices.cpp"
|
||||
>
|
||||
@ -434,6 +438,10 @@
|
||||
RelativePath=".\Source\MMDeviceAudioSource.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\MP4FileStream.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\NullOutput.cpp"
|
||||
>
|
||||
@ -446,10 +454,6 @@
|
||||
RelativePath=".\Source\RTMPPublisher.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\RTMPServer.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\Source\RTMPStuff.cpp"
|
||||
>
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;}
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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<UVCoord> &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();
|
||||
|
@ -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; i<paramList.Num(); i++)
|
||||
{
|
||||
String &strParam = paramList[i];
|
||||
String strParamName = strParam.GetToken(0, '=');
|
||||
String strParamVal = strParam.GetToken(1, '=');
|
||||
|
||||
if( strParamName.CompareI(TEXT("fps")) ||
|
||||
strParamName.CompareI(TEXT("force-cfr")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LPSTR lpParam = strParamName.CreateUTF8String();
|
||||
LPSTR lpVal = strParamVal.CreateUTF8String();
|
||||
|
||||
x264_param_parse(¶mData, lpParam, lpVal);
|
||||
|
||||
Free(lpParam);
|
||||
Free(lpVal);
|
||||
}
|
||||
}
|
||||
|
||||
if(bUse444) paramData.i_csp = X264_CSP_I444;
|
||||
|
||||
x264 = x264_encoder_open(¶mData);
|
||||
@ -143,7 +172,6 @@ public:
|
||||
traceIn(X264Encoder::~X264Encoder);
|
||||
|
||||
ClearPackets();
|
||||
HeaderPacket.Clear ();
|
||||
x264_encoder_close(x264);
|
||||
|
||||
traceOut;
|
||||
@ -176,10 +204,13 @@ public:
|
||||
{
|
||||
x264_nal_t &nal = nalOut[i];
|
||||
|
||||
if(nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SLICE)
|
||||
if(nal.i_type == NAL_SLICE_IDR || nal.i_type == NAL_SLICE || nal.i_type == NAL_SEI)
|
||||
{
|
||||
VideoPacket *newPacket = CurrentPackets.CreateNew();
|
||||
|
||||
if(nal.i_type == NAL_SEI)
|
||||
nop();
|
||||
|
||||
BYTE *skip = nal.p_payload;
|
||||
while(*(skip++) != 0x1);
|
||||
int skipBytes = (int)(skip-nal.p_payload);
|
||||
@ -190,7 +221,7 @@ public:
|
||||
newPacket->Packet[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)
|
||||
|
119
Source/FLVFileStream.cpp
Normal file
119
Source/FLVFileStream.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
/********************************************************************************
|
||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
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<QWORD*>(&doubleDuration);
|
||||
outputVal = fastHtonll(outputVal);
|
||||
file.Write(&outputVal, 8);
|
||||
|
||||
file.SetPos(metaDataPos+0x37, XFILE_BEGIN);
|
||||
outputVal = *reinterpret_cast<QWORD*>(&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;
|
||||
}
|
765
Source/MP4FileStream.cpp
Normal file
765
Source/MP4FileStream.cpp
Normal file
@ -0,0 +1,765 @@
|
||||
/********************************************************************************
|
||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
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.h>
|
||||
|
||||
|
||||
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<MP4VideoFrameInfo> videoFrames;
|
||||
List<MP4AudioFrameInfo> audioFrames;
|
||||
|
||||
List<UINT> IFrameIDs;
|
||||
|
||||
DWORD lastVideoTimestamp;
|
||||
|
||||
bool bStreamOpened;
|
||||
bool bMP3;
|
||||
|
||||
List<BYTE> endBuffer;
|
||||
List<UINT> 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<typename T> inline void GetChunkInfo(List<T> &data, List<UINT64> &chunks, List<SampleToChunk> &sampleToChunks)
|
||||
{
|
||||
UINT64 curChunkOffset;
|
||||
UINT64 connectedSampleOffset;
|
||||
UINT numSamples = 0;
|
||||
|
||||
for(UINT i=0; i<data.Num(); i++)
|
||||
{
|
||||
UINT64 curOffset = data[i].fileOffset;
|
||||
if(i == 0)
|
||||
curChunkOffset = curOffset;
|
||||
else
|
||||
{
|
||||
if(curOffset != connectedSampleOffset)
|
||||
{
|
||||
chunks << curChunkOffset;
|
||||
if(!sampleToChunks.Num() || sampleToChunks.Last().samplesPerChunk != numSamples)
|
||||
{
|
||||
SampleToChunk stc;
|
||||
stc.firstChunkID = chunks.Num();
|
||||
stc.samplesPerChunk = numSamples;
|
||||
sampleToChunks << stc;
|
||||
}
|
||||
|
||||
curChunkOffset = curOffset;
|
||||
numSamples = 0;
|
||||
}
|
||||
}
|
||||
|
||||
numSamples++;
|
||||
connectedSampleOffset = curOffset+data[i].size;
|
||||
}
|
||||
|
||||
chunks << curChunkOffset;
|
||||
if(!sampleToChunks.Num() || sampleToChunks.Last().samplesPerChunk != numSamples)
|
||||
{
|
||||
SampleToChunk stc;
|
||||
stc.firstChunkID = chunks.Num();
|
||||
stc.samplesPerChunk = numSamples;
|
||||
sampleToChunks << stc;
|
||||
}
|
||||
}
|
||||
|
||||
~MP4FileStream()
|
||||
{
|
||||
if(!bStreamOpened)
|
||||
return;
|
||||
|
||||
mdatStop = fileOut.GetPos();
|
||||
|
||||
BufferOutputSerializer output(endBuffer);
|
||||
|
||||
DWORD macTime = fastHtonl(DWORD(GetMacTime()));
|
||||
UINT videoDuration = fastHtonl(lastVideoTimestamp + App->GetFrameTime());
|
||||
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<BYTE> 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<BYTE> AACHeader;
|
||||
if(!bMP3)
|
||||
{
|
||||
DataPacket data;
|
||||
App->GetAudioHeaders(data);
|
||||
AACHeader.CopyArray(data.lpPacket+2, data.size-2);
|
||||
}
|
||||
|
||||
//-------------------------------------------
|
||||
// get chunk info
|
||||
List<UINT64> videoChunks, audioChunks;
|
||||
List<SampleToChunk> videoSampleToChunk, audioSampleToChunk;
|
||||
|
||||
GetChunkInfo<MP4VideoFrameInfo>(videoFrames, videoChunks, videoSampleToChunk);
|
||||
GetChunkInfo<MP4AudioFrameInfo>(audioFrames, audioChunks, audioSampleToChunk);
|
||||
|
||||
//-------------------------------------------
|
||||
// build decode time list and composition offset list
|
||||
List<OffsetVal> decodeTimes;
|
||||
List<OffsetVal> compositionOffsets;
|
||||
|
||||
for(UINT i=0; i<videoFrames.Num(); i++)
|
||||
{
|
||||
UINT frameTime;
|
||||
if(i == videoFrames.Num()-1)
|
||||
frameTime = decodeTimes.Last().val;
|
||||
else
|
||||
frameTime = videoFrames[i+1].timestamp-videoFrames[i].timestamp;
|
||||
|
||||
if(!decodeTimes.Num() || decodeTimes.Last().val != (UINT)frameTime)
|
||||
{
|
||||
OffsetVal newVal;
|
||||
newVal.count = 1;
|
||||
newVal.val = (UINT)frameTime;
|
||||
decodeTimes << newVal;
|
||||
}
|
||||
else
|
||||
decodeTimes.Last().count++;
|
||||
|
||||
INT compositionOffset = videoFrames[i].compositionOffset;
|
||||
if(!compositionOffsets.Num() || compositionOffsets.Last().val != (UINT)compositionOffset)
|
||||
{
|
||||
OffsetVal newVal;
|
||||
newVal.count = 1;
|
||||
newVal.val = (UINT)compositionOffset;
|
||||
compositionOffsets << newVal;
|
||||
}
|
||||
else
|
||||
compositionOffsets.Last().count++;
|
||||
}
|
||||
|
||||
//-------------------------------------------
|
||||
// sound descriptor thingy. this part made me die a little inside admittedly.
|
||||
UINT maxBitRate = fastHtonl(App->GetAudioEncoder()->GetBitRate()*1024);
|
||||
|
||||
List<BYTE> 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<BYTE> 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<decodeTimes.Num(); i++)
|
||||
{
|
||||
output.OutputDword(fastHtonl(decodeTimes[i].count));
|
||||
output.OutputDword(fastHtonl(decodeTimes[i].val));
|
||||
}
|
||||
PopBox(); //stts
|
||||
PushBox(output, DWORD_BE('stss')); //list of keyframe (i-frame) IDs
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(fastHtonl(IFrameIDs.Num()));
|
||||
output.Serialize(IFrameIDs.Array(), IFrameIDs.Num()*sizeof(UINT));
|
||||
PopBox(); //stss
|
||||
PushBox(output, DWORD_BE('ctts')); //list of composition time offsets
|
||||
output.OutputDword(DWORD_BE(0x01000000)); //version (1) and flags (none)
|
||||
output.OutputDword(fastHtonl(compositionOffsets.Num()));
|
||||
for(UINT i=0; i<compositionOffsets.Num(); i++)
|
||||
{
|
||||
output.OutputDword(fastHtonl(compositionOffsets[i].count));
|
||||
output.OutputDword(fastHtonl(compositionOffsets[i].val));
|
||||
}
|
||||
PopBox(); //ctts
|
||||
PushBox(output, DWORD_BE('stsc')); //sample to chunk list
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(fastHtonl(videoSampleToChunk.Num()));
|
||||
for(UINT i=0; i<videoSampleToChunk.Num(); i++)
|
||||
{
|
||||
SampleToChunk &stc = videoSampleToChunk[i];
|
||||
output.OutputDword(fastHtonl(stc.firstChunkID));
|
||||
output.OutputDword(fastHtonl(stc.samplesPerChunk));
|
||||
output.OutputDword(DWORD_BE(1));
|
||||
}
|
||||
PopBox(); //stsc
|
||||
PushBox(output, DWORD_BE('stsz')); //sample sizes
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(0); //block size for all (0 if differing sizes)
|
||||
output.OutputDword(fastHtonl(videoFrames.Num()));
|
||||
for(UINT i=0; i<videoFrames.Num(); i++)
|
||||
output.OutputDword(fastHtonl(videoFrames[i].size));
|
||||
PopBox();
|
||||
|
||||
if(videoChunks.Num() && videoChunks.Last() > 0xFFFFFFFFLL)
|
||||
{
|
||||
PushBox(output, DWORD_BE('co64')); //chunk offsets
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(fastHtonl(videoChunks.Num()));
|
||||
for(UINT i=0; i<videoChunks.Num(); i++)
|
||||
output.OutputQword(fastHtonll(videoChunks[i]));
|
||||
PopBox(); //co64
|
||||
}
|
||||
else
|
||||
{
|
||||
PushBox(output, DWORD_BE('stco')); //chunk offsets
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(fastHtonl(videoChunks.Num()));
|
||||
for(UINT i=0; i<videoChunks.Num(); i++)
|
||||
output.OutputDword(fastHtonl((DWORD)videoChunks[i]));
|
||||
PopBox(); //stco
|
||||
}
|
||||
PopBox(); //stbl
|
||||
PopBox(); //minf
|
||||
PopBox(); //mdia
|
||||
PopBox(); //trak
|
||||
|
||||
//------------------------------------------------------
|
||||
// audio 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(2)); //track ID
|
||||
output.OutputDword(0); //reserved
|
||||
output.OutputDword(audioDuration); //duration (in time base units)
|
||||
output.OutputQword(0); //reserved
|
||||
output.OutputWord(0); //video layer (0)
|
||||
output.OutputWord(WORD_BE(1)); //quicktime alternate track id
|
||||
output.OutputWord(WORD_BE(0x0100)); //volume
|
||||
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(0); //width (fixed point)
|
||||
output.OutputDword(0); //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(audioDuration); //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(44100)); //time scale
|
||||
output.OutputDword(fastHtonl(audioFrames.Num()*App->GetAudioEncoder()->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<audioSampleToChunk.Num(); i++)
|
||||
{
|
||||
SampleToChunk &stc = audioSampleToChunk[i];
|
||||
output.OutputDword(fastHtonl(stc.firstChunkID));
|
||||
output.OutputDword(fastHtonl(stc.samplesPerChunk));
|
||||
output.OutputDword(DWORD_BE(1));
|
||||
}
|
||||
PopBox(); //stsc
|
||||
PushBox(output, DWORD_BE('stsz')); //sample sizes
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(0); //block size for all (0 if differing sizes)
|
||||
output.OutputDword(fastHtonl(audioFrames.Num()));
|
||||
for(UINT i=0; i<audioFrames.Num(); i++)
|
||||
output.OutputDword(fastHtonl(audioFrames[i].size));
|
||||
PopBox();
|
||||
|
||||
if(audioChunks.Num() && audioChunks.Last() > 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<audioChunks.Num(); i++)
|
||||
output.OutputQword(fastHtonll(audioChunks[i]));
|
||||
PopBox(); //co64
|
||||
}
|
||||
else
|
||||
{
|
||||
PushBox(output, DWORD_BE('stco')); //chunk offsets
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(fastHtonl(audioChunks.Num()));
|
||||
for(UINT i=0; i<audioChunks.Num(); i++)
|
||||
output.OutputDword(fastHtonl((DWORD)audioChunks[i]));
|
||||
PopBox(); //stco
|
||||
}
|
||||
PopBox(); //stbl
|
||||
PopBox(); //minf
|
||||
PopBox(); //mdia
|
||||
PopBox(); //trak
|
||||
|
||||
//------------------------------------------------------
|
||||
// info thingy
|
||||
PushBox(output, DWORD_BE('udta'));
|
||||
PushBox(output, DWORD_BE('meta'));
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
PushBox(output, DWORD_BE('hdlr'));
|
||||
output.OutputDword(0); //version and flags (none)
|
||||
output.OutputDword(0); //quicktime type
|
||||
output.OutputDword(DWORD_BE('mdir')); //metadata type
|
||||
output.OutputDword(DWORD_BE('appl')); //quicktime manufacturer reserved thingy
|
||||
output.OutputDword(0); //quicktime component reserved flag
|
||||
output.OutputDword(0); //quicktime component reserved flag mask
|
||||
output.OutputByte(0); //null string
|
||||
PopBox(); //hdlr
|
||||
PushBox(output, DWORD_BE('ilst'));
|
||||
PushBox(output, DWORD_BE('\xa9too'));
|
||||
PushBox(output, DWORD_BE('data'));
|
||||
output.OutputDword(DWORD_BE(1)); //version (1) + flags (0)
|
||||
output.OutputDword(0); //reserved
|
||||
LPSTR lpVersion = OBS_VERSION_STRING_ANSI;
|
||||
output.Serialize(lpVersion, (DWORD)strlen(lpVersion));
|
||||
PopBox(); //data
|
||||
PopBox(); //@too
|
||||
PopBox(); //ilst
|
||||
PopBox(); //meta
|
||||
PopBox(); //udta
|
||||
|
||||
PopBox(); //moov
|
||||
|
||||
fileOut.Serialize(endBuffer.Array(), endBuffer.Num());
|
||||
fileOut.Close();
|
||||
|
||||
XFile file;
|
||||
if(file.Open(strFile, XFILE_WRITE, XFILE_OPENEXISTING))
|
||||
{
|
||||
#ifdef USE_64BIT_MP4
|
||||
file.SetPos((INT64)mdatStart+8, XFILE_BEGIN);
|
||||
|
||||
UINT64 size = fastHtonll(mdatStop-mdatStart);
|
||||
file.Write(&size, 8);
|
||||
#else
|
||||
file.SetPos((INT64)mdatStart, XFILE_BEGIN);
|
||||
UINT size = fastHtonl((DWORD)(mdatStop-mdatStart));
|
||||
file.Write(&size, 4);
|
||||
#endif
|
||||
file.Close();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void AddPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type)
|
||||
{
|
||||
UINT64 offset = fileOut.GetPos();
|
||||
|
||||
if(type == PacketType_Audio)
|
||||
{
|
||||
UINT copySize;
|
||||
|
||||
if(bMP3)
|
||||
{
|
||||
copySize = size-1;
|
||||
fileOut.Serialize(data+1, copySize);
|
||||
}
|
||||
else
|
||||
{
|
||||
copySize = size-2;
|
||||
fileOut.Serialize(data+2, copySize);
|
||||
}
|
||||
|
||||
MP4AudioFrameInfo audioFrame;
|
||||
audioFrame.fileOffset = offset;
|
||||
audioFrame.size = copySize;
|
||||
audioFrames << audioFrame;
|
||||
}
|
||||
else
|
||||
{
|
||||
UINT totalCopied = 0;
|
||||
|
||||
if(data[0] == 0x17 && data[1] == 0) //if SPS/PPS
|
||||
{
|
||||
LPBYTE lpData = data+11;
|
||||
|
||||
UINT spsSize = fastHtons(*(WORD*)lpData);
|
||||
fileOut.OutputWord(0);
|
||||
fileOut.Serialize(lpData, spsSize+2);
|
||||
|
||||
lpData += spsSize+3;
|
||||
|
||||
UINT ppsSize = fastHtons(*(WORD*)lpData);
|
||||
fileOut.OutputWord(0);
|
||||
fileOut.Serialize(lpData, ppsSize+2);
|
||||
|
||||
totalCopied = spsSize+ppsSize+8;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalCopied = size-5;
|
||||
fileOut.Serialize(data+5, totalCopied);
|
||||
}
|
||||
|
||||
if(!videoFrames.Num() || timestamp != lastVideoTimestamp)
|
||||
{
|
||||
INT timeOffset = 0;
|
||||
mcpy(((BYTE*)&timeOffset)+1, data+2, 3);
|
||||
if(data[2] >= 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;
|
||||
}
|
@ -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
|
||||
|
@ -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--;
|
||||
|
29
Source/OBS.h
29
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<FrameAudio> pendingAudioFrames;
|
||||
|
||||
bool bWriteToFile;
|
||||
VideoFileStream *fileStream;
|
||||
|
||||
String streamReport;
|
||||
|
||||
List<IconInfo> 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;}
|
||||
|
@ -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;
|
||||
|
@ -1,549 +0,0 @@
|
||||
/********************************************************************************
|
||||
Copyright (C) 2012 Hugh Bailey <obs.jim@gmail.com>
|
||||
|
||||
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<Connection*> 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<connections.Num(); i++)
|
||||
delete connections[i];
|
||||
connections.Clear();
|
||||
|
||||
if(serverSocket)
|
||||
closesocket(serverSocket);
|
||||
|
||||
traceOut;
|
||||
}
|
||||
|
||||
void SendPacket(BYTE *data, UINT size, DWORD timestamp, PacketType type)
|
||||
{
|
||||
traceIn(RTMPServer::SendPacket);
|
||||
|
||||
RTMPPacket packet;
|
||||
packet.m_nChannel = (type == PacketType_Audio) ? 0x5 : 0x4;
|
||||
packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
|
||||
packet.m_packetType = (type == PacketType_Audio) ? RTMP_PACKET_TYPE_AUDIO : RTMP_PACKET_TYPE_VIDEO;
|
||||
packet.m_nTimeStamp = timestamp;
|
||||
packet.m_nInfoField2 = 1;
|
||||
packet.m_hasAbsTimestamp = TRUE;
|
||||
|
||||
List<BYTE> 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; i<connections.Num(); i++)
|
||||
{
|
||||
Connection *connection = connections[i];
|
||||
|
||||
if(connection->ReadyForOutput())
|
||||
{
|
||||
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; i<connectObj.o_num; i++)
|
||||
{
|
||||
pname = connectObj.o_props[i].p_name;
|
||||
pval.av_val = NULL;
|
||||
pval.av_len = 0;
|
||||
|
||||
if (connectObj.o_props[i].p_type == AMF_STRING)
|
||||
pval = connectObj.o_props[i].p_vu.p_aval;
|
||||
|
||||
pval.av_val = NULL;
|
||||
|
||||
if (AVMATCH(&pname, &av_app))
|
||||
{
|
||||
rtmp->Link.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<BYTE> 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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<MonitorInfo> &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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
30
resource.h
30
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user