diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index afda7ec..e38e171 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,25 +61,30 @@ jobs: cp -r shaders release-artifacts/ cd release-artifacts tar -czf uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz uvc2gl shaders + mv uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz .. cd .. - name: Create Release - id: create_release - uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref_name }} - body: | - ## uvc2gl ${{ steps.version.outputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PRERELEASE_FLAG="" + if [[ "${{ github.ref_name }}" == *"-"* ]]; then + PRERELEASE_FLAG="--prerelease" + fi + + gh release create ${{ github.ref_name }} \ + $PRERELEASE_FLAG \ + --title "Release ${{ github.ref_name }}" \ + --notes "## uvc2gl ${{ steps.version.outputs.version }} ### Installation - ```bash + \`\`\`bash tar -xzf uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz cd uvc2gl ./uvc2gl - ``` + \`\`\` ### Requirements - Linux kernel with V4L2 support @@ -89,16 +94,5 @@ jobs: ### What's Changed See commit history for details. - **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.version.outputs.version }}...HEAD - draft: false - prerelease: ${{ contains(github.ref, '-') && !contains(github.ref, 'v0.0.0-') }} - - - name: Upload Release Asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./build/release-artifacts/uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz - asset_name: uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz - asset_content_type: application/gzip + **Full Changelog**: https://github.com/${{ github.repository }}/commits/${{ github.ref_name }}" \ + ./build/uvc2gl-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz diff --git a/VERSION b/VERSION index 0c64b5c..538ee20 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.0-dev +1.2.1-dev diff --git a/src/audio/AudioCapture.h b/src/audio/AudioCapture.h index 473f319..5e48488 100644 --- a/src/audio/AudioCapture.h +++ b/src/audio/AudioCapture.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/src/core/Application.cpp b/src/core/Application.cpp index c9c95d5..7b98897 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -29,133 +29,175 @@ Application::Application(const char* title, int width, int height) { // Initialize ImGui InitImGui(); - // Load config - m_config.LoadFromFile(m_configPath); + m_fallbackMode = false; - // Enumerate available devices - m_availableDevices = V4L2Capabilities::EnumerateDevices(); - - // Try to use saved device if available, otherwise fallback - m_currentDevice = m_config.videoDevice; - bool deviceFound = false; - - if (!m_availableDevices.empty()) { - for (const auto& device : m_availableDevices) { - if (device.path == m_config.videoDevice) { - deviceFound = true; - break; + try { + // Load config + m_config.LoadFromFile(m_configPath); + + // Enumerate available devices + m_availableDevices = V4L2Capabilities::EnumerateDevices(); + + // Try to use saved device if available, otherwise fallback + m_currentDevice = m_config.videoDevice; + bool deviceFound = false; + + if (!m_availableDevices.empty()) { + for (const auto& device : m_availableDevices) { + if (device.path == m_config.videoDevice) { + deviceFound = true; + break; + } + } + + if (!deviceFound) { + // Saved device not found, use first available + m_currentDevice = m_availableDevices[0].path; + std::cout << "Saved device not found, using " << m_currentDevice << std::endl; } + std::cout << "Found " << m_availableDevices.size() << " video device(s)" << std::endl; } - if (!deviceFound) { - // Saved device not found, use first available - m_currentDevice = m_availableDevices[0].path; - std::cout << "Saved device not found, using " << m_currentDevice << std::endl; + // Query available formats for current device + m_availableFormats = V4L2Capabilities::QueryFormats(m_currentDevice); + + // Try to use saved resolution/fps if available + m_currentWidth = m_config.width; + m_currentHeight = m_config.height; + m_currentFps = m_config.fps; + m_currentFormat = m_config.videoFormat; + + bool formatFound = false; + if (!m_availableFormats.empty()) { + for (const auto& format : m_availableFormats) { + if (format.width == m_config.width && + format.height == m_config.height && + format.fps == m_config.fps) { + formatFound = true; + break; + } + } + + if (!formatFound) { + // Saved format not available, use first available + m_currentWidth = m_availableFormats[0].width; + m_currentHeight = m_availableFormats[0].height; + m_currentFps = m_availableFormats[0].fps; + std::cout << "Saved format not available, using " << m_currentWidth << "x" + << m_currentHeight << "@" << m_currentFps << "fps" << std::endl; + } } - std::cout << "Found " << m_availableDevices.size() << " video device(s)" << std::endl; - } - - // Query available formats for current device - m_availableFormats = V4L2Capabilities::QueryFormats(m_currentDevice); - - // Try to use saved resolution/fps if available - m_currentWidth = m_config.width; - m_currentHeight = m_config.height; - m_currentFps = m_config.fps; - m_currentFormat = m_config.videoFormat; - - bool formatFound = false; - if (!m_availableFormats.empty()) { - for (const auto& format : m_availableFormats) { - if (format.width == m_config.width && - format.height == m_config.height && - format.fps == m_config.fps) { - formatFound = true; - break; + + // Try to initialize video capture (may fail if device not available) + bool videoInitialized = false; + try { + m_video = std::make_unique(m_currentDevice, m_currentWidth, m_currentHeight, m_currentFps, m_currentFormat, 10); + m_decoder = std::make_unique(); + m_video->Start(); + + // Give it a moment to start up and validate it's actually working + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + if (!m_video->IsRunning() || m_video->HasError()) { + std::cerr << "Video capture failed to start properly" << std::endl; + if (m_video->HasError()) { + std::cerr << "Error: " << m_video->GetLastError() << std::endl; + } + m_video.reset(); + m_decoder.reset(); + } else { + std::cout << "Video capture started on " << m_currentDevice << std::endl; + videoInitialized = true; } + } catch (const std::exception& e) { + std::cerr << "Warning: Failed to initialize video capture: " << e.what() << std::endl; + m_video.reset(); + m_decoder.reset(); } - if (!formatFound) { - // Saved format not available, use first available - m_currentWidth = m_availableFormats[0].width; - m_currentHeight = m_availableFormats[0].height; - m_currentFps = m_availableFormats[0].fps; - std::cout << "Saved format not available, using " << m_currentWidth << "x" - << m_currentHeight << "@" << m_currentFps << "fps" << std::endl; + // Initialize audio capture + m_availableAudioDevices = ALSACapabilities::EnumerateDevices(); + + // Try to use saved audio device if available + m_currentAudioDevice = m_config.audioDevice; + bool audioDeviceFound = false; + + for (const auto& device : m_availableAudioDevices) { + if (device.name == m_config.audioDevice) { + audioDeviceFound = true; + break; + } } - } - - // Try to initialize video capture (may fail if device not available) - try { - m_video = std::make_unique(m_currentDevice, m_currentWidth, m_currentHeight, m_currentFps, m_currentFormat, 10); - m_decoder = std::make_unique(); - m_video->Start(); - // Give it a moment to start up and validate it's actually working - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + if (!audioDeviceFound && !m_availableAudioDevices.empty()) { + // Saved audio device not found, fallback to pulse or first device + m_currentAudioDevice = "pulse"; + for (const auto& device : m_availableAudioDevices) { + if (device.description.find("USB3 Video") != std::string::npos || + device.description.find("USB 3 Video") != std::string::npos) { + m_currentAudioDevice = device.name; + std::cout << "Found capture card audio: " << device.description << std::endl; + break; + } + } + } - if (!m_video->IsRunning()) { - std::cerr << "Video capture failed to start properly, resetting" << std::endl; - m_video.reset(); - m_decoder.reset(); - } else { - std::cout << "Video capture started on " << m_currentDevice << std::endl; + bool audioInitialized = false; + try { + m_audio = std::make_unique(m_currentAudioDevice, 48000, 2, 1024); + m_audio->Start(); + std::cout << "Audio capture started on " << m_currentAudioDevice << std::endl; + audioInitialized = true; + } catch (const std::exception& e) { + std::cerr << "Warning: Failed to initialize audio capture: " << e.what() << std::endl; + std::cerr << "Attempting to use system audio only." << std::endl; } - } catch (const std::exception& e) { - std::cerr << "Warning: Failed to initialize video capture: " << e.what() << std::endl; - std::cerr << "Running without video input." << std::endl; - m_video.reset(); - m_decoder.reset(); - } - - // Initialize audio capture - m_availableAudioDevices = ALSACapabilities::EnumerateDevices(); - - // Try to use saved audio device if available - m_currentAudioDevice = m_config.audioDevice; - bool audioDeviceFound = false; - - for (const auto& device : m_availableAudioDevices) { - if (device.name == m_config.audioDevice) { - audioDeviceFound = true; - break; + + // Initialize audio playback + try { + m_audioPlayback = std::make_unique(48000, 2); + m_audioPlayback->Start(); + // Restore saved volume + m_audioPlayback->SetVolume(m_config.volume); + } catch (const std::exception& e) { + std::cerr << "Warning: Failed to initialize audio playback: " << e.what() << std::endl; + std::cerr << "Continuing without audio." << std::endl; } - } - - if (!audioDeviceFound && !m_availableAudioDevices.empty()) { - // Saved audio device not found, fallback to pulse or first device - m_currentAudioDevice = "pulse"; - for (const auto& device : m_availableAudioDevices) { - if (device.description.find("USB3 Video") != std::string::npos || - device.description.find("USB 3 Video") != std::string::npos) { - m_currentAudioDevice = device.name; - std::cout << "Found capture card audio: " << device.description << std::endl; - break; + + // Determine if we need fallback mode + if (!videoInitialized) { + std::cout << "\n========== FALLBACK MODE ACTIVATED ==========" << std::endl; + std::cout << "Video capture unavailable. Running with black screen." << std::endl; + if (!audioInitialized && m_audioPlayback) { + std::cout << "Audio: Using system audio playback only." << std::endl; + } else if (!audioInitialized) { + std::cout << "Audio: Unavailable." << std::endl; + } else { + std::cout << "Audio: Capture enabled." << std::endl; } + std::cout << "==========================================\n" << std::endl; + m_fallbackMode = true; } - } - - try { - m_audio = std::make_unique(m_currentAudioDevice, 48000, 2, 1024); - m_audio->Start(); - std::cout << "Audio capture started on " << m_currentAudioDevice << std::endl; - } catch (const std::exception& e) { - std::cerr << "Warning: Failed to initialize audio capture: " << e.what() << std::endl; - std::cerr << "Running without audio input." << std::endl; - } - - // Initialize audio playback - try { - m_audioPlayback = std::make_unique(48000, 2); - m_audioPlayback->Start(); - // Restore saved volume - m_audioPlayback->SetVolume(m_config.volume); + } catch (const std::exception& e) { - std::cerr << "Warning: Failed to initialize audio playback: " << e.what() << std::endl; - std::cerr << "Running without audio output." << std::endl; + std::cerr << "Critical error during initialization: " << e.what() << std::endl; + std::cerr << "Entering emergency fallback mode." << std::endl; + m_fallbackMode = true; + + // Try to at least initialize audio playback for fallback + try { + if (!m_audioPlayback) { + m_audioPlayback = std::make_unique(48000, 2); + m_audioPlayback->Start(); + m_audioPlayback->SetVolume(0.5f); + } + } catch (const std::exception& e2) { + std::cerr << "Failed to initialize audio playback in fallback: " << e2.what() << std::endl; + } } } + Application::~Application() { std::cout << "Shutting down application..." << std::endl; diff --git a/src/core/Application.h b/src/core/Application.h index 60cca75..ef04007 100644 --- a/src/core/Application.h +++ b/src/core/Application.h @@ -59,6 +59,7 @@ class Application { int m_currentFps = 30; std::string m_currentFormat = "YUYV"; bool m_isFullscreen = false; + bool m_fallbackMode = false; // Black screen + system audio when video/audio init fails AppConfig m_config; std::string m_configPath = "uvc2gl.conf"; diff --git a/src/core/Config.h b/src/core/Config.h index 978bedd..b734872 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -14,6 +14,7 @@ struct AppConfig { int fps = 30; std::string videoFormat = "MJPEG"; // MJPEG or YUYV float volume = 1.0f; + bool fallbackMode = false; // Use black screen + system audio if config fails to load bool LoadFromFile(const std::string& filename) { std::ifstream file(filename); diff --git a/src/video/VideoCapture.cpp b/src/video/VideoCapture.cpp index a8cb8d3..60cc375 100644 --- a/src/video/VideoCapture.cpp +++ b/src/video/VideoCapture.cpp @@ -37,6 +37,7 @@ namespace uvc2gl { m_mjpegDecoder = std::make_unique(); m_yuyvDecoder = std::make_unique(); m_Running = false; + m_LastError = ""; } VideoCapture::~VideoCapture() { @@ -209,6 +210,7 @@ namespace uvc2gl { } close(fd); } catch (const std::exception& e) { + m_LastError = e.what(); std::cerr << "Video capture error: " << e.what() << std::endl; m_Running = false; } diff --git a/src/video/VideoCapture.h b/src/video/VideoCapture.h index ce07153..3727607 100644 --- a/src/video/VideoCapture.h +++ b/src/video/VideoCapture.h @@ -23,6 +23,8 @@ namespace uvc2gl { void Start(); void Stop(); bool IsRunning() const { return m_Running.load(); } + bool HasError() const { return !m_LastError.empty(); } + std::string GetLastError() const { return m_LastError; } std::optional GetFrame(); @@ -39,6 +41,7 @@ namespace uvc2gl { std::unique_ptr m_yuyvDecoder; std::thread m_CaptureThread; std::atomic m_Running; + std::string m_LastError; }; }