Compare commits
66 commits
master
...
release/27
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa50f64c3c |
||
|
|
2b60f04974 |
||
|
|
ce78cd446e | ||
|
|
d9865bdffb | ||
|
|
aab14ba29a | ||
|
|
48f41c67dc | ||
|
|
c263266975 | ||
|
|
a1ba04536f | ||
|
|
ef53407c75 | ||
|
|
928597ec9d | ||
|
|
b718aa41f4 | ||
|
|
0d68aacfb6 | ||
|
|
2056a41ca5 | ||
|
|
fb96d637e2 | ||
|
|
1cfd1bb90a | ||
|
|
63f535c86e | ||
|
|
27bb608130 | ||
|
|
4845e88219 | ||
|
|
09df945fb8 | ||
|
|
23bc7c7544 | ||
|
|
4b6170f258 | ||
|
|
20332b01fd | ||
|
|
afa8527723 | ||
|
|
b65ee7e7fb | ||
|
|
399636847d | ||
|
|
9f0650658d | ||
|
|
201e9df041 | ||
|
|
fc20e7a71b | ||
|
|
5bd791dea6 | ||
|
|
b5f85bc788 | ||
|
|
34b55180ab | ||
|
|
c1ff655dfe | ||
|
|
91759c2a2e | ||
|
|
d23cb52796 | ||
|
|
1acc6c69da | ||
|
|
fea9281a1f | ||
|
|
2e1a6c9c64 | ||
|
|
2d7cda18e9 | ||
|
|
db3cee0868 | ||
|
|
c09429aa8c | ||
|
|
cedd8b9d15 | ||
|
|
46f9a5a8b9 | ||
|
|
eff0148dd7 | ||
|
|
bad7edd051 | ||
|
|
cc1395857a | ||
|
|
68a8264197 | ||
|
|
acad9dbaf7 | ||
|
|
bfcc066099 | ||
|
|
2f48733f3c | ||
|
|
c9dfad7340 | ||
|
|
0f97427ef2 | ||
|
|
3eec65d127 | ||
|
|
a0c37e7aa9 | ||
|
|
8ad7d1b08d | ||
|
|
d1f8caeb8d | ||
|
|
94016b459f | ||
|
|
5f7c4329b2 | ||
|
|
a75687abec | ||
|
|
c4455d4de0 | ||
|
|
9c42e9f752 | ||
|
|
92bf2ebe93 | ||
|
|
b6366757c0 | ||
|
|
59a675738f | ||
|
|
5643e9cbbf | ||
|
|
e43f75fc92 | ||
|
|
f2628de7f2 |
51 changed files with 568 additions and 1226 deletions
15
.github/workflows/main.yml
vendored
15
.github/workflows/main.yml
vendored
|
|
@ -14,6 +14,7 @@ on:
|
|||
- '**.md'
|
||||
branches:
|
||||
- master
|
||||
- 'release/**'
|
||||
|
||||
env:
|
||||
MACOS_CEF_BUILD_VERSION: '4638'
|
||||
|
|
@ -34,7 +35,7 @@ jobs:
|
|||
runs-on: [macos-latest]
|
||||
env:
|
||||
MIN_MACOS_VERSION: '10.13'
|
||||
MACOS_DEPS_VERSION: '2022-01-31'
|
||||
MACOS_DEPS_VERSION: '2022-02-13'
|
||||
VLC_VERSION: '3.0.8'
|
||||
SPARKLE_VERSION: '1.23.0'
|
||||
QT_VERSION: '5.15.2'
|
||||
|
|
@ -211,10 +212,12 @@ jobs:
|
|||
if [ -d ./OBS.app/Contents/Resources/data/obs-scripting ]; then
|
||||
mv ./OBS.app/Contents/Resources/data/obs-scripting/obslua.so ./OBS.app/Contents/MacOS/
|
||||
mv ./OBS.app/Contents/Resources/data/obs-scripting/_obspython.so ./OBS.app/Contents/MacOS/
|
||||
mv ./OBS.app/Contents/Resources/data/obs-scripting/obspython.py ./OBS.app/Contents/MacOS/
|
||||
mv ./OBS.app/Contents/Resources/data/obs-scripting/obspython.py ./OBS.app/Contents/Resources/
|
||||
rm -rf ./OBS.app/Contents/Resources/data/obs-scripting/
|
||||
fi
|
||||
|
||||
/bin/cp -cpR /tmp/obsdeps/lib/*.dylib ./OBS.app/Contents/Frameworks
|
||||
|
||||
BUNDLE_PLUGINS=(
|
||||
./OBS.app/Contents/PlugIns/coreaudio-encoder.so
|
||||
./OBS.app/Contents/PlugIns/decklink-ouput-ui.so
|
||||
|
|
@ -559,7 +562,7 @@ jobs:
|
|||
CACHE_NAME: 'windows-cef-64-cache'
|
||||
with:
|
||||
path: ${{ github.workspace }}/cmbuild/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x64
|
||||
key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.WINDOWS_CEF_BUILD_VERSION }}-1
|
||||
key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.WINDOWS_CEF_BUILD_VERSION }}-3
|
||||
- name: 'Install prerequisite: QT'
|
||||
if: steps.qt-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
|
@ -579,7 +582,7 @@ jobs:
|
|||
if: steps.cef-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
curl -kL https://cdn-fastly.obsproject.com/downloads/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x64.zip -f --retry 5 -o cef.zip
|
||||
7z x cef.zip -o"${{ github.workspace }}/cmbuild"
|
||||
7z x cef.zip -o"${{ github.workspace }}/cmbuild/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x64"
|
||||
- name: 'Configure'
|
||||
run: |
|
||||
mkdir ./build
|
||||
|
|
@ -668,7 +671,7 @@ jobs:
|
|||
CACHE_NAME: 'cef-32-cache'
|
||||
with:
|
||||
path: ${{ github.workspace }}/cmbuild/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x86
|
||||
key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.WINDOWS_CEF_BUILD_VERSION }}-1
|
||||
key: ${{ runner.os }}-pr-${{ env.CACHE_NAME }}-${{ env.WINDOWS_CEF_BUILD_VERSION }}-3
|
||||
- name: 'Install prerequisite: QT'
|
||||
if: steps.qt-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
|
|
@ -688,7 +691,7 @@ jobs:
|
|||
if: steps.cef-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
curl -kL https://cdn-fastly.obsproject.com/downloads/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x86.zip -f --retry 5 -o cef.zip
|
||||
7z x cef.zip -o"${{ github.workspace }}/cmbuild"
|
||||
7z x cef.zip -o"${{ github.workspace }}/cmbuild/cef_binary_${{ env.WINDOWS_CEF_BUILD_VERSION }}_windows_x86"
|
||||
- name: 'Configure'
|
||||
run: |
|
||||
mkdir ./build
|
||||
|
|
|
|||
1
.github/workflows/qt-xml.yml
vendored
1
.github/workflows/qt-xml.yml
vendored
|
|
@ -19,6 +19,7 @@ jobs:
|
|||
|
||||
- name: Install xmllint
|
||||
run: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install --no-install-recommends -y libxml2-utils
|
||||
|
||||
- name: Register Annotations
|
||||
|
|
|
|||
|
|
@ -419,9 +419,11 @@ prepare_macos_bundle() {
|
|||
if [ -d ./OBS.app/Contents/Resources/data/obs-scripting ]; then
|
||||
/bin/mv ./OBS.app/Contents/Resources/data/obs-scripting/obslua.so ./OBS.app/Contents/MacOS/
|
||||
/bin/mv ./OBS.app/Contents/Resources/data/obs-scripting/_obspython.so ./OBS.app/Contents/MacOS/
|
||||
/bin/mv ./OBS.app/Contents/Resources/data/obs-scripting/obspython.py ./OBS.app/Contents/MacOS/
|
||||
/bin/mv ./OBS.app/Contents/Resources/data/obs-scripting/obspython.py ./OBS.app/Contents/Resources/
|
||||
/bin/rm -rf ./OBS.app/Contents/Resources/data/obs-scripting/
|
||||
fi
|
||||
# dylibbundler will only copy actually linked files into bundle, but not symlinks
|
||||
/bin/cp -cpR /tmp/obsdeps/lib/*.dylib ./OBS.app/Contents/Frameworks
|
||||
|
||||
bundle_dylibs
|
||||
install_frameworks
|
||||
|
|
|
|||
|
|
@ -1464,6 +1464,10 @@ QPushButton#sourceFiltersButton {
|
|||
qproperty-icon: url(./Dark/media/media_next.svg);
|
||||
}
|
||||
|
||||
* [themeID="previousIcon"] {
|
||||
qproperty-icon: url(./Dark/media/media_previous.svg);
|
||||
}
|
||||
|
||||
/* YouTube Integration */
|
||||
OBSYoutubeActions {
|
||||
qproperty-thumbPlaceholder: url(./Dark/sources/image.svg);
|
||||
|
|
|
|||
|
|
@ -474,6 +474,9 @@
|
|||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QFrame" name="previewFrame">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
|
|
|
|||
|
|
@ -1156,6 +1156,12 @@ bool OBSApp::InitTheme()
|
|||
OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store)
|
||||
: QApplication(argc, argv), profilerNameStore(store)
|
||||
{
|
||||
/* fix float handling */
|
||||
#if defined(Q_OS_UNIX)
|
||||
if (!setlocale(LC_NUMERIC, "C"))
|
||||
blog(LOG_WARNING, "Failed to set LC_NUMERIC to C locale");
|
||||
#endif
|
||||
|
||||
sleepInhibitor = os_inhibit_sleep_create("OBS Video/audio");
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
|
@ -1455,6 +1461,12 @@ bool OBSApp::OBSInit()
|
|||
blog(LOG_INFO, "Browser Hardware Acceleration: %s",
|
||||
browserHWAccel ? "true" : "false");
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
bool hideFromCapture = config_get_bool(globalConfig, "BasicWindow",
|
||||
"HideOBSWindowsFromCapture");
|
||||
blog(LOG_INFO, "Hide OBS windows from screen capture: %s",
|
||||
hideFromCapture ? "true" : "false");
|
||||
#endif
|
||||
|
||||
blog(LOG_INFO, "Qt Version: %s (runtime), %s (compiled)", qVersion(),
|
||||
QT_VERSION_STR);
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ const char *RunOnce::thr_name = "OBS runonce";
|
|||
void PIDFileCheck(bool &already_running)
|
||||
{
|
||||
std::string tmpfile_name =
|
||||
"/tmp/obs-studio.lock." + to_string(geteuid());
|
||||
"/tmp/obs-studio.lock." + std::to_string(geteuid());
|
||||
int fd = open(tmpfile_name.c_str(), O_RDWR | O_CREAT | O_EXLOCK, 0600);
|
||||
if (fd == -1) {
|
||||
already_running = true;
|
||||
|
|
@ -213,7 +213,6 @@ string GetDefaultVideoSavePath()
|
|||
|
||||
vector<string> GetPreferredLocales()
|
||||
{
|
||||
setlocale(LC_ALL, "");
|
||||
vector<string> matched;
|
||||
string messages = setlocale(LC_MESSAGES, NULL);
|
||||
if (!messages.size() || messages == "C" || messages == "POSIX")
|
||||
|
|
|
|||
|
|
@ -180,14 +180,15 @@ void OBSPropertiesView::GetScrollPos(int &h, int &v)
|
|||
OBSPropertiesView::OBSPropertiesView(OBSData settings_, obs_object_t *obj,
|
||||
PropertiesReloadCallback reloadCallback,
|
||||
PropertiesUpdateCallback callback_,
|
||||
PropertiesVisualUpdateCb cb_, int minSize_)
|
||||
PropertiesVisualUpdateCb visUpdateCb_,
|
||||
int minSize_)
|
||||
: VScrollArea(nullptr),
|
||||
properties(nullptr, obs_properties_destroy),
|
||||
settings(settings_),
|
||||
weakObj(obs_object_get_weak_object(obj)),
|
||||
reloadCallback(reloadCallback),
|
||||
callback(callback_),
|
||||
cb(cb_),
|
||||
visUpdateCb(visUpdateCb_),
|
||||
minSize(minSize_)
|
||||
{
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
|
|
@ -198,14 +199,15 @@ OBSPropertiesView::OBSPropertiesView(OBSData settings_, obs_object_t *obj,
|
|||
OBSPropertiesView::OBSPropertiesView(OBSData settings_, void *obj,
|
||||
PropertiesReloadCallback reloadCallback,
|
||||
PropertiesUpdateCallback callback_,
|
||||
PropertiesVisualUpdateCb cb_, int minSize_)
|
||||
PropertiesVisualUpdateCb visUpdateCb_,
|
||||
int minSize_)
|
||||
: VScrollArea(nullptr),
|
||||
properties(nullptr, obs_properties_destroy),
|
||||
settings(settings_),
|
||||
rawObj(obj),
|
||||
reloadCallback(reloadCallback),
|
||||
callback(callback_),
|
||||
cb(cb_),
|
||||
visUpdateCb(visUpdateCb_),
|
||||
minSize(minSize_)
|
||||
{
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
|
|
@ -2028,11 +2030,11 @@ void WidgetInfo::ControlChanged()
|
|||
blog(LOG_DEBUG, "No update timer or no callback!");
|
||||
}
|
||||
|
||||
if (view->cb && !view->deferUpdate) {
|
||||
if (view->visUpdateCb && !view->deferUpdate) {
|
||||
OBSObject strongObj = view->GetObject();
|
||||
void *obj = strongObj ? strongObj.Get() : view->rawObj;
|
||||
if (obj)
|
||||
view->cb(obj, view->settings);
|
||||
view->visUpdateCb(obj, view->settings);
|
||||
}
|
||||
|
||||
view->SignalChanged();
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ private:
|
|||
std::string type;
|
||||
PropertiesReloadCallback reloadCallback;
|
||||
PropertiesUpdateCallback callback = nullptr;
|
||||
PropertiesVisualUpdateCb cb = nullptr;
|
||||
PropertiesVisualUpdateCb visUpdateCb = nullptr;
|
||||
int minSize;
|
||||
std::vector<std::unique_ptr<WidgetInfo>> children;
|
||||
std::string lastFocused;
|
||||
|
|
@ -188,7 +188,10 @@ public:
|
|||
|
||||
inline void UpdateSettings()
|
||||
{
|
||||
callback(OBSGetStrongRef(weakObj), nullptr, settings);
|
||||
if (callback)
|
||||
callback(OBSGetStrongRef(weakObj), nullptr, settings);
|
||||
else if (visUpdateCb)
|
||||
visUpdateCb(OBSGetStrongRef(weakObj), settings);
|
||||
}
|
||||
inline bool DeferUpdate() const { return deferUpdate; }
|
||||
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ void SourceTreeItem::VisibilityChanged(bool visible)
|
|||
void SourceTreeItem::LockedChanged(bool locked)
|
||||
{
|
||||
lock->setChecked(locked);
|
||||
OBSBasic::Get()->UpdateEditMenu();
|
||||
}
|
||||
|
||||
void SourceTreeItem::Renamed(const QString &name)
|
||||
|
|
@ -623,12 +624,14 @@ void SourceTreeItem::Select()
|
|||
{
|
||||
tree->SelectItem(sceneitem, true);
|
||||
OBSBasic::Get()->UpdateContextBarDeferred();
|
||||
OBSBasic::Get()->UpdateEditMenu();
|
||||
}
|
||||
|
||||
void SourceTreeItem::Deselect()
|
||||
{
|
||||
tree->SelectItem(sceneitem, false);
|
||||
OBSBasic::Get()->UpdateContextBarDeferred();
|
||||
OBSBasic::Get()->UpdateEditMenu();
|
||||
}
|
||||
|
||||
/* ========================================================================= */
|
||||
|
|
|
|||
|
|
@ -1842,6 +1842,7 @@ void OBSBasic::OBSInit()
|
|||
ui->contextContainer->setVisible(contextVisible);
|
||||
if (contextVisible)
|
||||
UpdateContextBar(true);
|
||||
UpdateEditMenu();
|
||||
|
||||
{
|
||||
ProfileScope("OBSBasic::Load");
|
||||
|
|
@ -3530,6 +3531,8 @@ void OBSBasic::ActivateAudioSource(OBSSource source)
|
|||
{
|
||||
if (SourceMixerHidden(source))
|
||||
return;
|
||||
if (!obs_source_active(source))
|
||||
return;
|
||||
if (!obs_source_audio_active(source))
|
||||
return;
|
||||
|
||||
|
|
@ -4411,6 +4414,8 @@ bool OBSBasic::ResetAudio()
|
|||
return obs_reset_audio(&ai);
|
||||
}
|
||||
|
||||
extern char *get_new_source_name(const char *name, const char *format);
|
||||
|
||||
void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
|
||||
const char *deviceDesc, int channel)
|
||||
{
|
||||
|
|
@ -4434,10 +4439,11 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId,
|
|||
}
|
||||
|
||||
} else if (!disable) {
|
||||
BPtr<char> name = get_new_source_name(deviceDesc, "%s (%d)");
|
||||
|
||||
settings = obs_data_create();
|
||||
obs_data_set_string(settings, "device_id", deviceId);
|
||||
source = obs_source_create(sourceId, deviceDesc, settings,
|
||||
nullptr);
|
||||
source = obs_source_create(sourceId, name, settings, nullptr);
|
||||
|
||||
obs_set_output_source(channel, source);
|
||||
}
|
||||
|
|
@ -5369,8 +5375,6 @@ ColorSelect::ColorSelect(QWidget *parent)
|
|||
|
||||
void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
|
||||
{
|
||||
UpdateEditMenu();
|
||||
|
||||
QMenu popup(this);
|
||||
delete previewProjectorSource;
|
||||
delete sourceProjector;
|
||||
|
|
@ -7825,11 +7829,6 @@ void OBSBasic::UpdateEditMenu()
|
|||
ui->actionHorizontalCenter->setEnabled(canTransform);
|
||||
}
|
||||
|
||||
void OBSBasic::on_menuBasic_MainMenu_Edit_aboutToShow()
|
||||
{
|
||||
UpdateEditMenu();
|
||||
}
|
||||
|
||||
void OBSBasic::on_actionEditTransform_triggered()
|
||||
{
|
||||
if (transformWindow)
|
||||
|
|
|
|||
|
|
@ -966,8 +966,6 @@ private slots:
|
|||
void on_actionShowCrashLogs_triggered();
|
||||
void on_actionUploadLastCrashLog_triggered();
|
||||
|
||||
void on_menuBasic_MainMenu_Edit_aboutToShow();
|
||||
|
||||
void on_actionEditTransform_triggered();
|
||||
void on_actionCopyTransform_triggered();
|
||||
void on_actionPasteTransform_triggered();
|
||||
|
|
|
|||
|
|
@ -81,20 +81,10 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_)
|
|||
OBSDataAutoRelease nd_settings = obs_source_get_settings(source);
|
||||
obs_data_apply(oldSettings, nd_settings);
|
||||
|
||||
auto handle_memory = [](void *vp, obs_data_t *old_settings,
|
||||
obs_data_t *new_settings) {
|
||||
obs_source_t *source = reinterpret_cast<obs_source_t *>(vp);
|
||||
|
||||
obs_source_update(source, new_settings);
|
||||
|
||||
UNUSED_PARAMETER(old_settings);
|
||||
UNUSED_PARAMETER(vp);
|
||||
};
|
||||
|
||||
view = new OBSPropertiesView(
|
||||
nd_settings.Get(), source,
|
||||
(PropertiesReloadCallback)obs_source_properties,
|
||||
(PropertiesUpdateCallback)handle_memory,
|
||||
(PropertiesUpdateCallback) nullptr, // No special handling required for undo/redo
|
||||
(PropertiesVisualUpdateCb)obs_source_update);
|
||||
view->setMinimumHeight(150);
|
||||
|
||||
|
|
|
|||
|
|
@ -3008,6 +3008,9 @@ void OBSBasicSettings::SaveGeneralSettings()
|
|||
main->SetDisplayAffinity(window);
|
||||
}
|
||||
}
|
||||
|
||||
blog(LOG_INFO, "Hide OBS windows from screen capture: %s",
|
||||
hide_window ? "true" : "false");
|
||||
}
|
||||
#endif
|
||||
if (WidgetChanged(ui->openStatsOnStartup))
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ static void AddSource(void *_data, obs_scene_t *scene)
|
|||
obs_sceneitem_set_visible(sceneitem, data->visible);
|
||||
}
|
||||
|
||||
static char *get_new_source_name(const char *name)
|
||||
char *get_new_source_name(const char *name, const char *format)
|
||||
{
|
||||
struct dstr new_name = {0};
|
||||
int inc = 0;
|
||||
|
|
@ -143,7 +143,7 @@ static char *get_new_source_name(const char *name)
|
|||
if (!existing_source)
|
||||
break;
|
||||
|
||||
dstr_printf(&new_name, "%s %d", name, ++inc + 1);
|
||||
dstr_printf(&new_name, format, name, ++inc + 1);
|
||||
}
|
||||
|
||||
return new_name.array;
|
||||
|
|
@ -160,8 +160,8 @@ static void AddExisting(OBSSource source, bool visible, bool duplicate,
|
|||
|
||||
if (duplicate) {
|
||||
OBSSource from = source;
|
||||
char *new_name =
|
||||
get_new_source_name(obs_source_get_name(source));
|
||||
char *new_name = get_new_source_name(
|
||||
obs_source_get_name(source), "%s %d");
|
||||
source = obs_source_duplicate(from, new_name, false);
|
||||
obs_source_release(source);
|
||||
bfree(new_name);
|
||||
|
|
|
|||
|
|
@ -626,11 +626,7 @@ void OBSProjector::OBSRender(void *data, uint32_t cx, uint32_t cy)
|
|||
void OBSProjector::OBSSourceRemoved(void *data, calldata_t *params)
|
||||
{
|
||||
OBSProjector *window = reinterpret_cast<OBSProjector *>(data);
|
||||
|
||||
OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
|
||||
main->DeleteProjector(window);
|
||||
allProjectors.removeAll(window);
|
||||
|
||||
QMetaObject::invokeMethod(window, "EscapeTriggered");
|
||||
UNUSED_PARAMETER(params);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@
|
|||
<summary>Live streaming and video recording software</summary>
|
||||
<description>
|
||||
<p>Free and open source software for video capturing, recording, and live streaming.</p>
|
||||
<p>Features:</p>
|
||||
<ul>
|
||||
<li>High performance real time video/audio capturing and mixing. Create scenes made up of multiple sources including window captures, images, text, browser windows, webcams, capture cards and more.</li>
|
||||
<li>Set up an unlimited number of scenes you can switch between seamlessly via custom transitions.</li>
|
||||
<li>Intuitive audio mixer with per-source filters such as noise gate, noise suppression, and gain. Take full control with VST plugin support.</li>
|
||||
<li>Powerful and easy to use configuration options. Add new Sources, duplicate existing ones, and adjust their properties effortlessly.</li>
|
||||
<li>Streamlined Settings panel gives you access to a wide array of configuration options to tweak every aspect of your broadcast or recording.</li>
|
||||
<li>Modular 'Dock' UI allows you to rearrange the layout exactly as you like. You can even pop out each individual Dock to its own window.</li>
|
||||
</ul>
|
||||
<p>Create Professional Productions:</p>
|
||||
<ul>
|
||||
<li>Choose from a number of different and customizable transitions for when you switch between your scenes or add your own stinger video files.</li>
|
||||
<li>Set hotkeys for nearly every sort of action, such as switching between scenes, starting/stopping streams or recordings, muting audio sources, push to talk, and more.</li>
|
||||
<li>Studio Mode lets you preview your scenes and sources before pushing them live. Adjust your scenes and sources or create new ones and ensure they're perfect before your viewers ever see them.</li>
|
||||
<li>Get a high level view of your production using the Multiview. Monitor 8 different scenes and easily cue or transition to any of them with merely a single or double click.</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url type="homepage">https://obsproject.com</url>
|
||||
<url type="bugtracker">https://github.com/obsproject/obs-studio/issues</url>
|
||||
|
|
@ -16,7 +32,7 @@
|
|||
<url type="translate">https://crowdin.com/project/obs-studio</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://obsproject.com/assets/images/OBSDemoApp.jpg</image>
|
||||
<image>https://obsproject.com/assets/images/OBSDemoApp2610.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
|
|
|
|||
9
deps/obs-scripting/obs-scripting-callback.h
vendored
9
deps/obs-scripting/obs-scripting-callback.h
vendored
|
|
@ -33,9 +33,14 @@ struct script_callback {
|
|||
obs_script_t *script;
|
||||
calldata_t extra;
|
||||
|
||||
bool removed;
|
||||
volatile bool removed;
|
||||
};
|
||||
|
||||
static inline bool script_callback_removed(struct script_callback *cb)
|
||||
{
|
||||
return os_atomic_load_bool(&cb->removed);
|
||||
}
|
||||
|
||||
static inline void *add_script_callback(struct script_callback **first,
|
||||
obs_script_t *script, size_t extra_size)
|
||||
{
|
||||
|
|
@ -54,7 +59,7 @@ static inline void *add_script_callback(struct script_callback **first,
|
|||
|
||||
static inline void remove_script_callback(struct script_callback *cb)
|
||||
{
|
||||
cb->removed = true;
|
||||
os_atomic_set_bool(&cb->removed, true);
|
||||
|
||||
struct script_callback *next = cb->next;
|
||||
if (next)
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ static void frontend_event_callback(enum obs_frontend_event event, void *priv)
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_frontend_remove_event_callback(frontend_event_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ static void frontend_save_callback(obs_data_t *save_data, bool saving,
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_frontend_remove_save_callback(frontend_save_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
42
deps/obs-scripting/obs-scripting-lua.c
vendored
42
deps/obs-scripting/obs-scripting-lua.c
vendored
|
|
@ -288,7 +288,7 @@ static void timer_call(struct script_callback *p_cb)
|
|||
{
|
||||
struct lua_obs_callback *cb = (struct lua_obs_callback *)p_cb;
|
||||
|
||||
if (p_cb->removed)
|
||||
if (script_callback_removed(p_cb))
|
||||
return;
|
||||
|
||||
lock_callback();
|
||||
|
|
@ -329,7 +329,7 @@ static void obs_lua_main_render_callback(void *priv, uint32_t cx, uint32_t cy)
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_remove_main_render_callback(obs_lua_main_render_callback,
|
||||
cb);
|
||||
return;
|
||||
|
|
@ -377,7 +377,7 @@ static void obs_lua_tick_callback(void *priv, float seconds)
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_remove_tick_callback(obs_lua_tick_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
@ -423,7 +423,7 @@ static void calldata_signal_callback(void *priv, calldata_t *cd)
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
signal_handler_remove_current();
|
||||
return;
|
||||
}
|
||||
|
|
@ -505,7 +505,7 @@ static void calldata_signal_callback_global(void *priv, const char *signal,
|
|||
struct lua_obs_callback *cb = priv;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
signal_handler_remove_current();
|
||||
return;
|
||||
}
|
||||
|
|
@ -663,7 +663,7 @@ static void hotkey_pressed(void *p_cb, bool pressed)
|
|||
struct lua_obs_callback *cb = p_cb;
|
||||
lua_State *script = cb->script;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return;
|
||||
|
||||
lock_callback();
|
||||
|
|
@ -689,7 +689,7 @@ static void hotkey_callback(void *p_cb, obs_hotkey_id id, obs_hotkey_t *hotkey,
|
|||
{
|
||||
struct lua_obs_callback *cb = p_cb;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return;
|
||||
|
||||
if (pressed)
|
||||
|
|
@ -745,7 +745,7 @@ static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p,
|
|||
lua_State *script = cb->script;
|
||||
bool ret = false;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return false;
|
||||
|
||||
lock_callback();
|
||||
|
|
@ -801,7 +801,7 @@ static bool modified_callback(void *p_cb, obs_properties_t *props,
|
|||
lua_State *script = cb->script;
|
||||
bool ret = false;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return false;
|
||||
|
||||
lock_callback();
|
||||
|
|
@ -1078,7 +1078,7 @@ static void lua_tick(void *param, float seconds)
|
|||
struct lua_obs_timer *next = timer->next;
|
||||
struct lua_obs_callback *cb = lua_obs_timer_cb(timer);
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
lua_obs_timer_remove(timer);
|
||||
} else {
|
||||
uint64_t elapsed = ts - timer->last_ts;
|
||||
|
|
@ -1156,6 +1156,25 @@ void obs_lua_script_unload(obs_script_t *s)
|
|||
|
||||
lua_State *script = data->script;
|
||||
|
||||
/* ---------------------------- */
|
||||
/* mark callbacks as removed */
|
||||
|
||||
pthread_mutex_lock(&data->mutex);
|
||||
|
||||
/* XXX: scripts can potentially make callbacks when this happens, so
|
||||
* this probably still isn't ideal as we can't predict how the
|
||||
* processor or operating system is going to schedule things. a more
|
||||
* ideal method would be to reference count the script objects and
|
||||
* atomically share ownership with callbacks when they're called. */
|
||||
struct lua_obs_callback *cb =
|
||||
(struct lua_obs_callback *)data->first_callback;
|
||||
while (cb) {
|
||||
os_atomic_set_bool(&cb->base.removed, true);
|
||||
cb = (struct lua_obs_callback *)cb->base.next;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&data->mutex);
|
||||
|
||||
/* ---------------------------- */
|
||||
/* undefine source types */
|
||||
|
||||
|
|
@ -1189,8 +1208,7 @@ void obs_lua_script_unload(obs_script_t *s)
|
|||
/* ---------------------------- */
|
||||
/* remove all callbacks */
|
||||
|
||||
struct lua_obs_callback *cb =
|
||||
(struct lua_obs_callback *)data->first_callback;
|
||||
cb = (struct lua_obs_callback *)data->first_callback;
|
||||
while (cb) {
|
||||
struct lua_obs_callback *next =
|
||||
(struct lua_obs_callback *)cb->base.next;
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ static void frontend_save_callback(obs_data_t *save_data, bool saving,
|
|||
{
|
||||
struct python_obs_callback *cb = priv;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_frontend_remove_save_callback(frontend_save_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
@ -355,7 +355,7 @@ static void frontend_event_callback(enum obs_frontend_event event, void *priv)
|
|||
{
|
||||
struct python_obs_callback *cb = priv;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_frontend_remove_event_callback(frontend_event_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
62
deps/obs-scripting/obs-scripting-python.c
vendored
62
deps/obs-scripting/obs-scripting-python.c
vendored
|
|
@ -431,7 +431,7 @@ static void timer_call(struct script_callback *p_cb)
|
|||
{
|
||||
struct python_obs_callback *cb = (struct python_obs_callback *)p_cb;
|
||||
|
||||
if (p_cb->removed)
|
||||
if (script_callback_removed(p_cb))
|
||||
return;
|
||||
|
||||
lock_callback(cb);
|
||||
|
|
@ -476,7 +476,7 @@ static void obs_python_tick_callback(void *priv, float seconds)
|
|||
{
|
||||
struct python_obs_callback *cb = priv;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
obs_remove_tick_callback(obs_python_tick_callback, cb);
|
||||
return;
|
||||
}
|
||||
|
|
@ -546,7 +546,7 @@ static void calldata_signal_callback(void *priv, calldata_t *cd)
|
|||
{
|
||||
struct python_obs_callback *cb = priv;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
signal_handler_remove_current();
|
||||
return;
|
||||
}
|
||||
|
|
@ -652,7 +652,7 @@ static void calldata_signal_callback_global(void *priv, const char *signal,
|
|||
{
|
||||
struct python_obs_callback *cb = priv;
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
signal_handler_remove_current();
|
||||
return;
|
||||
}
|
||||
|
|
@ -767,7 +767,7 @@ static void hotkey_pressed(void *p_cb, bool pressed)
|
|||
{
|
||||
struct python_obs_callback *cb = p_cb;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return;
|
||||
|
||||
lock_callback(cb);
|
||||
|
|
@ -803,7 +803,7 @@ static void hotkey_callback(void *p_cb, obs_hotkey_id id, obs_hotkey_t *hotkey,
|
|||
{
|
||||
struct python_obs_callback *cb = p_cb;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return;
|
||||
|
||||
if (pressed)
|
||||
|
|
@ -873,7 +873,7 @@ static bool button_prop_clicked(obs_properties_t *props, obs_property_t *p,
|
|||
struct python_obs_callback *cb = p_cb;
|
||||
bool ret = false;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return false;
|
||||
|
||||
lock_callback(cb);
|
||||
|
|
@ -937,7 +937,7 @@ static bool modified_callback(void *p_cb, obs_properties_t *props,
|
|||
struct python_obs_callback *cb = p_cb;
|
||||
bool ret = false;
|
||||
|
||||
if (cb->base.removed)
|
||||
if (script_callback_removed(&cb->base))
|
||||
return false;
|
||||
|
||||
lock_callback(cb);
|
||||
|
|
@ -1333,6 +1333,24 @@ void obs_python_script_unload(obs_script_t *s)
|
|||
if (!s->loaded || !python_loaded)
|
||||
return;
|
||||
|
||||
/* ---------------------------- */
|
||||
/* mark callbacks as removed */
|
||||
|
||||
lock_python();
|
||||
|
||||
/* XXX: scripts can potentially make callbacks when this happens, so
|
||||
* this probably still isn't ideal as we can't predict how the
|
||||
* processor or operating system is going to schedule things. a more
|
||||
* ideal method would be to reference count the script objects and
|
||||
* atomically share ownership with callbacks when they're called. */
|
||||
struct script_callback *cb = data->first_callback;
|
||||
while (cb) {
|
||||
os_atomic_set_bool(&cb->removed, true);
|
||||
cb = cb->next;
|
||||
}
|
||||
|
||||
unlock_python();
|
||||
|
||||
/* ---------------------------- */
|
||||
/* unhook tick function */
|
||||
|
||||
|
|
@ -1350,7 +1368,7 @@ void obs_python_script_unload(obs_script_t *s)
|
|||
data->next_tick = NULL;
|
||||
}
|
||||
|
||||
lock_python();
|
||||
relock_python();
|
||||
|
||||
Py_XDECREF(data->tick);
|
||||
Py_XDECREF(data->save);
|
||||
|
|
@ -1364,7 +1382,7 @@ void obs_python_script_unload(obs_script_t *s)
|
|||
/* ---------------------------- */
|
||||
/* remove all callbacks */
|
||||
|
||||
struct script_callback *cb = data->first_callback;
|
||||
cb = data->first_callback;
|
||||
while (cb) {
|
||||
struct script_callback *next = cb->next;
|
||||
remove_script_callback(cb);
|
||||
|
|
@ -1532,7 +1550,7 @@ static void python_tick(void *param, float seconds)
|
|||
struct python_obs_timer *next = timer->next;
|
||||
struct python_obs_callback *cb = python_obs_timer_cb(timer);
|
||||
|
||||
if (cb->base.removed) {
|
||||
if (script_callback_removed(&cb->base)) {
|
||||
python_obs_timer_remove(timer);
|
||||
} else {
|
||||
uint64_t elapsed = ts - timer->last_ts;
|
||||
|
|
@ -1658,10 +1676,24 @@ bool obs_scripting_load_python(const char *python_path)
|
|||
bfree(absolute_script_path);
|
||||
|
||||
#if __APPLE__
|
||||
char *exec_path = os_get_executable_path_ptr("");
|
||||
if (exec_path)
|
||||
add_to_python_path(exec_path);
|
||||
bfree(exec_path);
|
||||
char *absolute_exec_path = os_get_executable_path_ptr("");
|
||||
|
||||
if (absolute_exec_path != NULL) {
|
||||
add_to_python_path(absolute_exec_path);
|
||||
|
||||
struct dstr resources_path;
|
||||
dstr_init_move_array(&resources_path, absolute_exec_path);
|
||||
dstr_cat(&resources_path, "../Resources");
|
||||
|
||||
char *absolute_resources_path = os_get_abs_path_ptr(resources_path.array);
|
||||
if (absolute_resources_path != NULL) {
|
||||
add_to_python_path(absolute_resources_path);
|
||||
bfree(absolute_resources_path);
|
||||
}
|
||||
|
||||
dstr_free(&resources_path);
|
||||
bfree(absolute_exec_path);
|
||||
}
|
||||
#endif
|
||||
|
||||
py_obspython = PyImport_ImportModule("obspython");
|
||||
|
|
|
|||
1
deps/obs-scripting/obs-scripting-python.h
vendored
1
deps/obs-scripting/obs-scripting-python.h
vendored
|
|
@ -195,6 +195,7 @@ static inline bool py_error_(const char *func, int line)
|
|||
#define py_error() py_error_(__FUNCTION__, __LINE__)
|
||||
|
||||
#define lock_python() PyGILState_STATE gstate = PyGILState_Ensure()
|
||||
#define relock_python() gstate = PyGILState_Ensure()
|
||||
#define unlock_python() PyGILState_Release(gstate)
|
||||
|
||||
struct py_source;
|
||||
|
|
|
|||
|
|
@ -199,17 +199,23 @@ gl_egl_create_dmabuf_image(EGLDisplay egl_display, unsigned int width,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
texture = gs_texture_create(width, height, color_format, 1, NULL,
|
||||
GS_DYNAMIC);
|
||||
if ((texture = gs_texture_create(width, height, color_format, 1, NULL,
|
||||
GS_DYNAMIC)) == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
const GLuint gltex = *(GLuint *)gs_texture_get_obj(texture);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, gltex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
gl_bind_texture(GL_TEXTURE_2D, gltex);
|
||||
gl_tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
gl_tex_param_i(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, egl_image);
|
||||
if (!gl_success("glEGLImageTargetTexture2DOES")) {
|
||||
gs_texture_destroy(texture);
|
||||
texture = NULL;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
gl_bind_texture(GL_TEXTURE_2D, 0);
|
||||
eglDestroyImage(egl_display, egl_image);
|
||||
|
||||
return texture;
|
||||
|
|
|
|||
|
|
@ -936,7 +936,7 @@ EXPORT gs_stagesurf_t *gs_stagesurface_create_nv12(uint32_t width,
|
|||
EXPORT void gs_register_loss_callbacks(const struct gs_device_loss *callbacks);
|
||||
EXPORT void gs_unregister_loss_callbacks(void *data);
|
||||
|
||||
#elif __linux__
|
||||
#elif defined(__linux__) || defined(__FreeBSD__)
|
||||
|
||||
EXPORT gs_texture_t *gs_texture_create_from_dmabuf(
|
||||
unsigned int width, unsigned int height, uint32_t drm_format,
|
||||
|
|
|
|||
1245
libobs/obs-cocoa.m
1245
libobs/obs-cocoa.m
File diff suppressed because it is too large
Load diff
|
|
@ -41,7 +41,7 @@
|
|||
*
|
||||
* Reset to zero each major or minor version
|
||||
*/
|
||||
#define LIBOBS_API_PATCH_VER 0
|
||||
#define LIBOBS_API_PATCH_VER 4
|
||||
|
||||
#define MAKE_SEMANTIC_VERSION(major, minor, patch) \
|
||||
((major << 24) | (minor << 16) | patch)
|
||||
|
|
|
|||
|
|
@ -1086,13 +1086,15 @@ static inline void push_back_audio(struct obs_encoder *encoder,
|
|||
struct audio_data *data, size_t size,
|
||||
size_t offset_size)
|
||||
{
|
||||
if (offset_size >= size)
|
||||
return;
|
||||
|
||||
size -= offset_size;
|
||||
|
||||
/* push in to the circular buffer */
|
||||
if (size)
|
||||
for (size_t i = 0; i < encoder->planes; i++)
|
||||
circlebuf_push_back(&encoder->audio_input_buffer[i],
|
||||
data->data[i] + offset_size, size);
|
||||
for (size_t i = 0; i < encoder->planes; i++)
|
||||
circlebuf_push_back(&encoder->audio_input_buffer[i],
|
||||
data->data[i] + offset_size, size);
|
||||
}
|
||||
|
||||
static inline size_t calc_offset_size(struct obs_encoder *encoder,
|
||||
|
|
|
|||
|
|
@ -1289,8 +1289,14 @@ static inline bool inject_hotkey(void *data, size_t idx,
|
|||
event->strict_modifiers)) {
|
||||
bool pressed = binding->key.key == event->hotkey.key &&
|
||||
event->pressed;
|
||||
handle_binding(binding, event->hotkey.modifiers, false,
|
||||
event->strict_modifiers, &pressed);
|
||||
if (binding->key.key == OBS_KEY_NONE)
|
||||
pressed = true;
|
||||
|
||||
if (pressed) {
|
||||
binding->modifiers_match = true;
|
||||
if (!binding->pressed)
|
||||
press_released_binding(binding);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ int obs_open_module(obs_module_t **module, const char *path,
|
|||
/* HACK: Do not load obsolete obs-browser build on macOS; the
|
||||
* obs-browser plugin used to live in the Application Support
|
||||
* directory. */
|
||||
if (astrstri(path, "Library/Application Support") != NULL &&
|
||||
if (astrstri(path, "Library/Application Support/obs-studio") != NULL &&
|
||||
astrstri(path, "obs-browser") != NULL) {
|
||||
blog(LOG_WARNING, "Ignoring old obs-browser.so version");
|
||||
return MODULE_ERROR;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ static void platform_keyboard_keymap(void *data, struct wl_keyboard *keyboard,
|
|||
UNUSED_PARAMETER(format);
|
||||
obs_hotkeys_platform_t *plat = (obs_hotkeys_platform_t *)data;
|
||||
|
||||
char *keymap_shm = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
char *keymap_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (keymap_shm == MAP_FAILED) {
|
||||
close(fd);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -723,7 +723,12 @@ static inline void video_sleep(struct obs_core_video *video, bool raw_active,
|
|||
*p_time = t;
|
||||
count = 1;
|
||||
} else {
|
||||
count = (int)((os_gettime_ns() - cur_time) / interval_ns);
|
||||
const uint64_t udiff = os_gettime_ns() - cur_time;
|
||||
int64_t diff;
|
||||
memcpy(&diff, &udiff, sizeof(diff));
|
||||
const uint64_t clamped_diff =
|
||||
(diff > (int64_t)interval_ns) ? diff : interval_ns;
|
||||
count = (int)(clamped_diff / interval_ns);
|
||||
*p_time = cur_time + interval_ns * count;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,13 @@
|
|||
#include <shlobj.h>
|
||||
#include <intrin.h>
|
||||
#include <psapi.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "base.h"
|
||||
#include "platform.h"
|
||||
#include "darray.h"
|
||||
#include "dstr.h"
|
||||
#include "util_uint64.h"
|
||||
#include "windows/win-registry.h"
|
||||
#include "windows/win-version.h"
|
||||
|
||||
|
|
@ -331,9 +333,9 @@ void os_cpu_usage_info_destroy(os_cpu_usage_info_t *info)
|
|||
|
||||
bool os_sleepto_ns(uint64_t time_target)
|
||||
{
|
||||
const double freq = (double)get_clockfreq();
|
||||
const uint64_t freq = get_clockfreq();
|
||||
const LONGLONG count_target =
|
||||
(LONGLONG)((double)time_target * freq / 1000000000.0);
|
||||
util_mul_div64(time_target, freq, 1000000000);
|
||||
|
||||
LARGE_INTEGER count;
|
||||
QueryPerformanceCounter(&count);
|
||||
|
|
@ -370,14 +372,9 @@ void os_sleep_ms(uint32_t duration)
|
|||
uint64_t os_gettime_ns(void)
|
||||
{
|
||||
LARGE_INTEGER current_time;
|
||||
double time_val;
|
||||
|
||||
QueryPerformanceCounter(¤t_time);
|
||||
time_val = (double)current_time.QuadPart;
|
||||
time_val *= 1000000000.0;
|
||||
time_val /= (double)get_clockfreq();
|
||||
|
||||
return (uint64_t)time_val;
|
||||
return util_mul_div64(current_time.QuadPart, 1000000000,
|
||||
get_clockfreq());
|
||||
}
|
||||
|
||||
/* returns [folder]\[name] on windows */
|
||||
|
|
|
|||
|
|
@ -298,8 +298,10 @@ static void destroy_session(obs_pipewire_data *obs_pw)
|
|||
}
|
||||
|
||||
g_clear_pointer(&obs_pw->sender_name, bfree);
|
||||
obs_enter_graphics();
|
||||
g_clear_pointer(&obs_pw->cursor.texture, gs_texture_destroy);
|
||||
g_clear_pointer(&obs_pw->texture, gs_texture_destroy);
|
||||
obs_leave_graphics();
|
||||
g_cancellable_cancel(obs_pw->cancellable);
|
||||
g_clear_object(&obs_pw->cancellable);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,6 @@ static inline enum video_format v4l2_to_obs_video_format(uint_fast32_t format)
|
|||
#endif
|
||||
case V4L2_PIX_FMT_BGR24:
|
||||
return VIDEO_FORMAT_BGR3;
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
return VIDEO_FORMAT_I422;
|
||||
default:
|
||||
return VIDEO_FORMAT_NONE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,13 +148,6 @@ static void v4l2_prep_obs_frame(struct v4l2_data *data,
|
|||
plane_offsets[1] = data->linesize * data->height;
|
||||
plane_offsets[2] = data->linesize * data->height * 5 / 4;
|
||||
break;
|
||||
case V4L2_PIX_FMT_MJPEG:
|
||||
frame->linesize[0] = 0;
|
||||
frame->linesize[1] = 0;
|
||||
frame->linesize[2] = 0;
|
||||
plane_offsets[1] = 0;
|
||||
plane_offsets[2] = 0;
|
||||
break;
|
||||
default:
|
||||
frame->linesize[0] = data->linesize;
|
||||
break;
|
||||
|
|
@ -479,7 +472,8 @@ static void v4l2_format_list(int dev, obs_property_t *prop)
|
|||
dstr_cat(&buffer, " (Emulated)");
|
||||
|
||||
if (v4l2_to_obs_video_format(fmt.pixelformat) !=
|
||||
VIDEO_FORMAT_NONE) {
|
||||
VIDEO_FORMAT_NONE ||
|
||||
fmt.pixelformat == V4L2_PIX_FMT_MJPEG) {
|
||||
obs_property_list_add_int(prop, buffer.array,
|
||||
fmt.pixelformat);
|
||||
blog(LOG_INFO, "Pixelformat: %s (available)",
|
||||
|
|
@ -1003,7 +997,8 @@ static void v4l2_init(struct v4l2_data *data)
|
|||
blog(LOG_ERROR, "Unable to set format");
|
||||
goto fail;
|
||||
}
|
||||
if (v4l2_to_obs_video_format(data->pixfmt) == VIDEO_FORMAT_NONE) {
|
||||
if (v4l2_to_obs_video_format(data->pixfmt) == VIDEO_FORMAT_NONE &&
|
||||
data->pixfmt != V4L2_PIX_FMT_MJPEG) {
|
||||
blog(LOG_ERROR, "Selected video format not supported");
|
||||
goto fail;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ int v4l2_init_mjpeg(struct v4l2_mjpeg_decoder *decoder)
|
|||
}
|
||||
|
||||
decoder->context->flags2 |= AV_CODEC_FLAG2_FAST;
|
||||
decoder->context->pix_fmt = AV_PIX_FMT_YUVJ422P;
|
||||
|
||||
if (avcodec_open2(decoder->context, decoder->codec, NULL) < 0) {
|
||||
blog(LOG_ERROR, "failed to open codec");
|
||||
|
|
@ -76,7 +75,6 @@ void v4l2_destroy_mjpeg(struct v4l2_mjpeg_decoder *decoder)
|
|||
int v4l2_decode_mjpeg(struct obs_source_frame *out, uint8_t *data,
|
||||
size_t length, struct v4l2_mjpeg_decoder *decoder)
|
||||
{
|
||||
|
||||
decoder->packet->data = data;
|
||||
decoder->packet->size = length;
|
||||
if (avcodec_send_packet(decoder->context, decoder->packet) < 0) {
|
||||
|
|
@ -94,5 +92,22 @@ int v4l2_decode_mjpeg(struct obs_source_frame *out, uint8_t *data,
|
|||
out->linesize[i] = decoder->frame->linesize[i];
|
||||
}
|
||||
|
||||
switch (decoder->context->pix_fmt) {
|
||||
case AV_PIX_FMT_YUVJ422P:
|
||||
case AV_PIX_FMT_YUV422P:
|
||||
out->format = VIDEO_FORMAT_I422;
|
||||
break;
|
||||
case AV_PIX_FMT_YUVJ420P:
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
out->format = VIDEO_FORMAT_I420;
|
||||
break;
|
||||
case AV_PIX_FMT_YUVJ444P:
|
||||
case AV_PIX_FMT_YUV444P:
|
||||
out->format = VIDEO_FORMAT_I444;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ extern "C" {
|
|||
* Data structure for mjpeg decoding
|
||||
*/
|
||||
struct v4l2_mjpeg_decoder {
|
||||
AVCodec *codec;
|
||||
const AVCodec *codec;
|
||||
AVCodecContext *context;
|
||||
AVPacket *packet;
|
||||
AVFrame *frame;
|
||||
|
|
|
|||
|
|
@ -185,7 +185,14 @@ static bool virtualcam_start(void *data)
|
|||
return false;
|
||||
}
|
||||
|
||||
n = scandir("/dev", &list, scanfilter, versionsort);
|
||||
n = scandir("/dev", &list, scanfilter,
|
||||
#if defined(__linux__)
|
||||
versionsort
|
||||
#else
|
||||
alphasort
|
||||
#endif
|
||||
);
|
||||
|
||||
if (n == -1)
|
||||
return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ static inline bool device_is_input(char *device)
|
|||
astrstri(device, "ishowu") == NULL &&
|
||||
astrstri(device, "blackhole") == NULL &&
|
||||
astrstri(device, "loopback") == NULL &&
|
||||
astrstri(device, "groundcontrol") == NULL;
|
||||
astrstri(device, "groundcontrol") == NULL &&
|
||||
astrstri(device, "vbcable") == NULL;
|
||||
}
|
||||
|
||||
static inline bool enum_success(OSStatus stat, const char *msg)
|
||||
|
|
|
|||
|
|
@ -50,28 +50,28 @@ static inline void capture_frame(struct window_capture *wc)
|
|||
size_t width = CGImageGetWidth(img);
|
||||
size_t height = CGImageGetHeight(img);
|
||||
|
||||
CGRect rect = {{0, 0}, {width, height}};
|
||||
da_resize(wc->buffer, width * height * 4);
|
||||
uint8_t *data = wc->buffer.array;
|
||||
if (!width || !height || CGImageGetBitsPerPixel(img) != 32 ||
|
||||
CGImageGetBitsPerComponent(img) != 8) {
|
||||
CGImageRelease(img);
|
||||
return;
|
||||
}
|
||||
|
||||
CGContextRef cg_context = CGBitmapContextCreate(
|
||||
data, width, height, 8, width * 4, wc->color_space,
|
||||
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
|
||||
CGContextSetBlendMode(cg_context, kCGBlendModeCopy);
|
||||
CGContextDrawImage(cg_context, rect, img);
|
||||
CGContextRelease(cg_context);
|
||||
CGImageRelease(img);
|
||||
CGDataProviderRef provider = CGImageGetDataProvider(img);
|
||||
CFDataRef data = CGDataProviderCopyData(provider);
|
||||
|
||||
struct obs_source_frame frame = {
|
||||
.format = VIDEO_FORMAT_BGRA,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data[0] = data,
|
||||
.linesize[0] = width * 4,
|
||||
.data[0] = (uint8_t *)CFDataGetBytePtr(data),
|
||||
.linesize[0] = CGImageGetBytesPerRow(img),
|
||||
.timestamp = ts,
|
||||
};
|
||||
|
||||
obs_source_output_video(wc->source, &frame);
|
||||
|
||||
CGImageRelease(img);
|
||||
CFRelease(data);
|
||||
}
|
||||
|
||||
static void *capture_thread(void *data)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b0d687937af876b52b69c46e276b4ab601b07f0e
|
||||
Subproject commit 915761778ec1eae99e740ad4bf63b40db3142ee2
|
||||
|
|
@ -386,6 +386,15 @@ static void ffmpeg_source_tick(void *data, float seconds)
|
|||
}
|
||||
}
|
||||
|
||||
#define SRT_PROTO "srt"
|
||||
#define RIST_PROTO "rist"
|
||||
|
||||
static bool requires_mpegts(char *path)
|
||||
{
|
||||
return !astrcmpi_n(path, SRT_PROTO, sizeof(SRT_PROTO) - 1) ||
|
||||
!astrcmpi_n(path, RIST_PROTO, sizeof(RIST_PROTO) - 1);
|
||||
}
|
||||
|
||||
static void ffmpeg_source_update(void *data, obs_data_t *settings)
|
||||
{
|
||||
struct ffmpeg_source *s = data;
|
||||
|
|
@ -406,6 +415,10 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
|
|||
input = (char *)obs_data_get_string(settings, "input");
|
||||
input_format =
|
||||
(char *)obs_data_get_string(settings, "input_format");
|
||||
if (requires_mpegts(input)) {
|
||||
input_format = "mpegts";
|
||||
obs_data_set_string(settings, "input_format", "mpegts");
|
||||
}
|
||||
s->reconnect_delay_sec =
|
||||
(int)obs_data_get_int(settings, "reconnect_delay_sec");
|
||||
s->reconnect_delay_sec = s->reconnect_delay_sec == 0
|
||||
|
|
@ -429,7 +442,9 @@ static void ffmpeg_source_update(void *data, obs_data_t *settings)
|
|||
s->is_clear_on_media_end =
|
||||
obs_data_get_bool(settings, "clear_on_media_end");
|
||||
s->restart_on_activate =
|
||||
obs_data_get_bool(settings, "restart_on_activate");
|
||||
!astrcmpi_n(input, RIST_PROTO, sizeof(RIST_PROTO) - 1)
|
||||
? false
|
||||
: obs_data_get_bool(settings, "restart_on_activate");
|
||||
s->range = (enum video_range_type)obs_data_get_int(settings,
|
||||
"color_range");
|
||||
s->is_linear_alpha = obs_data_get_bool(settings, "linear_alpha");
|
||||
|
|
|
|||
|
|
@ -96,6 +96,8 @@ static void *libfdk_create(obs_data_t *settings, obs_encoder_t *encoder)
|
|||
int bitrate = (int)obs_data_get_int(settings, "bitrate") * 1000;
|
||||
int afterburner = obs_data_get_bool(settings, "afterburner") ? 1 : 0;
|
||||
audio_t *audio = obs_encoder_audio(encoder);
|
||||
bool set_to_ADTS = obs_data_get_bool(settings, "set_to_ADTS");
|
||||
int transmux = set_to_ADTS ? 2 : 0;
|
||||
int mode = 0;
|
||||
AACENC_ERROR err;
|
||||
|
||||
|
|
@ -159,7 +161,9 @@ static void *libfdk_create(obs_data_t *settings, obs_encoder_t *encoder)
|
|||
aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATEMODE, 0));
|
||||
CHECK_LIBFDK(
|
||||
aacEncoder_SetParam(enc->fdkhandle, AACENC_BITRATE, bitrate));
|
||||
CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_TRANSMUX, 0));
|
||||
|
||||
CHECK_LIBFDK(
|
||||
aacEncoder_SetParam(enc->fdkhandle, AACENC_TRANSMUX, transmux));
|
||||
CHECK_LIBFDK(aacEncoder_SetParam(enc->fdkhandle, AACENC_AFTERBURNER,
|
||||
afterburner));
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@
|
|||
******************************************************************************/
|
||||
|
||||
#include "rtmp-stream.h"
|
||||
#ifdef _WIN32
|
||||
#include <util/windows/win-version.h>
|
||||
#endif
|
||||
|
||||
#ifndef SEC_TO_NSEC
|
||||
#define SEC_TO_NSEC 1000000000ULL
|
||||
|
|
@ -605,6 +608,24 @@ static void *send_thread(void *data)
|
|||
os_set_thread_name("rtmp-stream: send_thread");
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Despite MSDN claiming otherwise, send buffer auto tuning on
|
||||
// Windows 7 doesn't seem to work very well.
|
||||
if (get_win_ver_int() == 0x601) {
|
||||
DWORD cur_sendbuf_size;
|
||||
DWORD desired_sendbuf_size = 524288;
|
||||
socklen_t int_size = sizeof(int);
|
||||
|
||||
if (!getsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET,
|
||||
SO_SNDBUF, (char *)&cur_sendbuf_size,
|
||||
&int_size) &&
|
||||
cur_sendbuf_size < desired_sendbuf_size) {
|
||||
|
||||
setsockopt(stream->rtmp.m_sb.sb_socket, SOL_SOCKET,
|
||||
SO_SNDBUF, (char *)&desired_sendbuf_size,
|
||||
sizeof(desired_sendbuf_size));
|
||||
}
|
||||
}
|
||||
|
||||
log_sndbuf_size(stream);
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ static void rtmp_custom_apply_settings(void *data, obs_data_t *video_settings,
|
|||
strncmp(service->server, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) !=
|
||||
0) {
|
||||
obs_data_set_bool(video_settings, "repeat_headers", true);
|
||||
obs_data_set_bool(audio_settings, "set_to_ADTS", true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ static void init_hw_decoder(struct ffmpeg_decode *d)
|
|||
}
|
||||
|
||||
if (hw_ctx) {
|
||||
d->hw_device_ctx = hw_ctx;
|
||||
d->decoder->hw_device_ctx = av_buffer_ref(hw_ctx);
|
||||
d->hw = true;
|
||||
}
|
||||
|
|
@ -117,6 +118,9 @@ void ffmpeg_decode_free(struct ffmpeg_decode *decode)
|
|||
if (decode->frame)
|
||||
av_frame_free(&decode->frame);
|
||||
|
||||
if (decode->hw_device_ctx)
|
||||
av_buffer_unref(&decode->hw_device_ctx);
|
||||
|
||||
if (decode->packet_buffer)
|
||||
bfree(decode->packet_buffer);
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
struct ffmpeg_decode {
|
||||
AVBufferRef *hw_device_ctx;
|
||||
AVCodecContext *decoder;
|
||||
AVCodec *codec;
|
||||
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ static bool load_placeholder_internal()
|
|||
|
||||
bool initialize_placeholder()
|
||||
{
|
||||
if (initialized)
|
||||
return true;
|
||||
|
||||
GdiplusStartupInput si;
|
||||
ULONG_PTR token;
|
||||
GdiplusStartup(&token, &si, nullptr);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ VCamFilter::VCamFilter()
|
|||
VCamFilter::~VCamFilter()
|
||||
{
|
||||
SetEvent(thread_stop);
|
||||
th.join();
|
||||
if (th.joinable())
|
||||
th.join();
|
||||
video_queue_close(vq);
|
||||
|
||||
if (placeholder.scaled_data)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <util/windows/ComPtr.hpp>
|
||||
#include <util/windows/WinHandle.hpp>
|
||||
#include <util/windows/CoTaskMemPtr.hpp>
|
||||
#include <util/windows/win-version.h>
|
||||
#include <util/threading.h>
|
||||
#include <util/util_uint64.h>
|
||||
|
||||
|
|
@ -324,7 +325,19 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,
|
|||
|
||||
/* OBS will already load DLL on startup if it exists */
|
||||
const HMODULE rtwq_module = GetModuleHandle(L"RTWorkQ.dll");
|
||||
rtwq_supported = rtwq_module != NULL;
|
||||
|
||||
// while RTWQ was introduced in Win 8.1, it silently fails
|
||||
// to capture Desktop Audio for some reason. Disable for now.
|
||||
struct win_version_info win1703 = {};
|
||||
win1703.major = 10;
|
||||
win1703.minor = 0;
|
||||
win1703.build = 15063;
|
||||
win1703.revis = 0;
|
||||
struct win_version_info ver;
|
||||
get_win_ver(&ver);
|
||||
if (win_version_compare(&ver, &win1703) >= 0)
|
||||
rtwq_supported = rtwq_module != NULL;
|
||||
|
||||
if (rtwq_supported) {
|
||||
rtwq_unlock_work_queue =
|
||||
(PFN_RtwqUnlockWorkQueue)GetProcAddress(
|
||||
|
|
@ -341,46 +354,54 @@ WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,
|
|||
(PFN_RtwqPutWaitingWorkItem)GetProcAddress(
|
||||
rtwq_module, "RtwqPutWaitingWorkItem");
|
||||
|
||||
hr = rtwq_create_async_result(nullptr, &startCapture, nullptr,
|
||||
&startCaptureAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
enumerator->UnregisterEndpointNotificationCallback(
|
||||
notify);
|
||||
throw HRError(
|
||||
"Could not create startCaptureAsyncResult", hr);
|
||||
}
|
||||
try {
|
||||
hr = rtwq_create_async_result(nullptr, &startCapture,
|
||||
nullptr,
|
||||
&startCaptureAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
throw HRError(
|
||||
"Could not create startCaptureAsyncResult",
|
||||
hr);
|
||||
}
|
||||
|
||||
hr = rtwq_create_async_result(nullptr, &sampleReady, nullptr,
|
||||
&sampleReadyAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
enumerator->UnregisterEndpointNotificationCallback(
|
||||
notify);
|
||||
throw HRError("Could not create sampleReadyAsyncResult",
|
||||
hr);
|
||||
}
|
||||
hr = rtwq_create_async_result(nullptr, &sampleReady,
|
||||
nullptr,
|
||||
&sampleReadyAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
throw HRError(
|
||||
"Could not create sampleReadyAsyncResult",
|
||||
hr);
|
||||
}
|
||||
|
||||
hr = rtwq_create_async_result(nullptr, &restart, nullptr,
|
||||
&restartAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
enumerator->UnregisterEndpointNotificationCallback(
|
||||
notify);
|
||||
throw HRError("Could not create restartAsyncResult",
|
||||
hr);
|
||||
}
|
||||
hr = rtwq_create_async_result(nullptr, &restart,
|
||||
nullptr,
|
||||
&restartAsyncResult);
|
||||
if (FAILED(hr)) {
|
||||
throw HRError(
|
||||
"Could not create restartAsyncResult",
|
||||
hr);
|
||||
}
|
||||
|
||||
DWORD taskId = 0;
|
||||
DWORD id = 0;
|
||||
hr = rtwq_lock_shared_work_queue(L"Capture", 0, &taskId, &id);
|
||||
if (FAILED(hr)) {
|
||||
enumerator->UnregisterEndpointNotificationCallback(
|
||||
notify);
|
||||
throw HRError("RtwqLockSharedWorkQueue failed", hr);
|
||||
}
|
||||
DWORD taskId = 0;
|
||||
DWORD id = 0;
|
||||
hr = rtwq_lock_shared_work_queue(L"Capture", 0, &taskId,
|
||||
&id);
|
||||
if (FAILED(hr)) {
|
||||
throw HRError("RtwqLockSharedWorkQueue failed",
|
||||
hr);
|
||||
}
|
||||
|
||||
startCapture.SetQueueId(id);
|
||||
sampleReady.SetQueueId(id);
|
||||
restart.SetQueueId(id);
|
||||
} else {
|
||||
startCapture.SetQueueId(id);
|
||||
sampleReady.SetQueueId(id);
|
||||
restart.SetQueueId(id);
|
||||
} catch (HRError &err) {
|
||||
blog(LOG_ERROR, "RTWQ setup failed: %s (0x%08X)",
|
||||
err.str, err.hr);
|
||||
rtwq_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rtwq_supported) {
|
||||
captureThread = CreateThread(nullptr, 0,
|
||||
WASAPISource::CaptureThread, this,
|
||||
0, nullptr);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue