|
9 | 9 |
|
10 | 10 | import logging |
11 | 11 | from contextlib import asynccontextmanager |
| 12 | +from threading import RLock |
12 | 13 | from typing import Annotated, AsyncGenerator |
13 | 14 |
|
14 | 15 | from fastapi import Depends, FastAPI, HTTPException, Security, status |
|
37 | 38 | camera_controller: CameraController | None = None |
38 | 39 | streaming_manager: StreamingManager | None = None |
39 | 40 |
|
| 41 | +# Global lock for camera reconfiguration operations |
| 42 | +# Protects sequences that require stopping/reconfiguring/restarting streaming |
| 43 | +_reconfiguration_lock = RLock() |
| 44 | + |
40 | 45 | # API Key authentication |
41 | 46 | api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) |
42 | 47 |
|
@@ -1643,32 +1648,34 @@ def set_resolution( |
1643 | 1648 | """ |
1644 | 1649 | logger.info(f"Setting resolution: {req.width}x{req.height}") |
1645 | 1650 |
|
1646 | | - try: |
1647 | | - # Check if streaming is active |
1648 | | - was_streaming = streaming.is_streaming() |
| 1651 | + # Use global lock to prevent concurrent reconfiguration operations |
| 1652 | + with _reconfiguration_lock: |
| 1653 | + try: |
| 1654 | + # Check if streaming is active |
| 1655 | + was_streaming = streaming.is_streaming() |
1649 | 1656 |
|
1650 | | - # Stop streaming if active (must stop encoder first) |
1651 | | - if was_streaming: |
1652 | | - logger.info("Stopping streaming before resolution change") |
1653 | | - streaming.stop() |
| 1657 | + # Stop streaming if active (must stop encoder first) |
| 1658 | + if was_streaming: |
| 1659 | + logger.info("Stopping streaming before resolution change") |
| 1660 | + streaming.stop() |
1654 | 1661 |
|
1655 | | - # Change resolution (this will stop, reconfigure, and restart camera) |
1656 | | - camera.set_resolution(req.width, req.height) |
| 1662 | + # Change resolution (this will stop, reconfigure, and restart camera) |
| 1663 | + camera.set_resolution(req.width, req.height) |
1657 | 1664 |
|
1658 | | - # Restart streaming if requested and was previously streaming |
1659 | | - if req.restart_streaming and was_streaming: |
1660 | | - logger.info("Restarting streaming after resolution change") |
1661 | | - streaming.start() |
| 1665 | + # Restart streaming if requested and was previously streaming |
| 1666 | + if req.restart_streaming and was_streaming: |
| 1667 | + logger.info("Restarting streaming after resolution change") |
| 1668 | + streaming.start() |
1662 | 1669 |
|
1663 | | - return StatusResponse() |
1664 | | - except (CameraNotAvailableError, InvalidParameterError): |
1665 | | - raise |
1666 | | - except Exception as e: |
1667 | | - logger.error(f"Error setting resolution: {e}") |
1668 | | - raise HTTPException( |
1669 | | - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
1670 | | - detail="Failed to set resolution", |
1671 | | - ) |
| 1670 | + return StatusResponse() |
| 1671 | + except (CameraNotAvailableError, InvalidParameterError): |
| 1672 | + raise |
| 1673 | + except Exception as e: |
| 1674 | + logger.error(f"Error setting resolution: {e}") |
| 1675 | + raise HTTPException( |
| 1676 | + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 1677 | + detail="Failed to set resolution", |
| 1678 | + ) |
1672 | 1679 |
|
1673 | 1680 |
|
1674 | 1681 | @app.post( |
@@ -1708,29 +1715,31 @@ def set_framerate( |
1708 | 1715 | """ |
1709 | 1716 | logger.info(f"Setting framerate: {req.framerate}fps") |
1710 | 1717 |
|
1711 | | - try: |
1712 | | - # Check if streaming is active |
1713 | | - was_streaming = streaming.is_streaming() |
| 1718 | + # Use global lock to prevent concurrent reconfiguration operations |
| 1719 | + with _reconfiguration_lock: |
| 1720 | + try: |
| 1721 | + # Check if streaming is active |
| 1722 | + was_streaming = streaming.is_streaming() |
1714 | 1723 |
|
1715 | | - # Stop streaming if active (must stop encoder first) |
1716 | | - if was_streaming: |
1717 | | - logger.info("Stopping streaming before framerate change") |
1718 | | - streaming.stop() |
| 1724 | + # Stop streaming if active (must stop encoder first) |
| 1725 | + if was_streaming: |
| 1726 | + logger.info("Stopping streaming before framerate change") |
| 1727 | + streaming.stop() |
1719 | 1728 |
|
1720 | | - # Change framerate (this will stop, reconfigure, and restart camera) |
1721 | | - result = camera.set_framerate(req.framerate) |
| 1729 | + # Change framerate (this will stop, reconfigure, and restart camera) |
| 1730 | + result = camera.set_framerate(req.framerate) |
1722 | 1731 |
|
1723 | | - # Restart streaming if requested and was previously streaming |
1724 | | - if req.restart_streaming and was_streaming: |
1725 | | - logger.info("Restarting streaming after framerate change") |
1726 | | - streaming.start() |
| 1732 | + # Restart streaming if requested and was previously streaming |
| 1733 | + if req.restart_streaming and was_streaming: |
| 1734 | + logger.info("Restarting streaming after framerate change") |
| 1735 | + streaming.start() |
1727 | 1736 |
|
1728 | | - return FramerateResponse(**result) |
1729 | | - except (CameraNotAvailableError, InvalidParameterError): |
1730 | | - raise |
1731 | | - except Exception as e: |
1732 | | - logger.error(f"Error setting framerate: {e}") |
1733 | | - raise HTTPException( |
1734 | | - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
1735 | | - detail="Failed to set framerate", |
1736 | | - ) |
| 1737 | + return FramerateResponse(**result) |
| 1738 | + except (CameraNotAvailableError, InvalidParameterError): |
| 1739 | + raise |
| 1740 | + except Exception as e: |
| 1741 | + logger.error(f"Error setting framerate: {e}") |
| 1742 | + raise HTTPException( |
| 1743 | + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, |
| 1744 | + detail="Failed to set framerate", |
| 1745 | + ) |
0 commit comments