본문 바로가기

개발하자

FastAPI의 카메라에서 비디오를 스트리밍하면 첫 번째 프레임 후에 이미지가 동결됩니다

반응형

FastAPI의 카메라에서 비디오를 스트리밍하면 첫 번째 프레임 후에 이미지가 동결됩니다

플라스크에서 찾은 예와 유사한 FastAPI를 사용하여 카메라에서 비디오를 스트리밍하려고 합니다. 플라스크에서 예제는 올바르게 작동하며 비디오는 문제없이 스트리밍됩니다. 그러나 FastAPI에서 동일한 기능을 복제하려고 할 때 첫 번째 프레임 이후 비디오 스트림이 동결되는 문제가 발생한다.

나는 이 플라스크 코드에 제공된 예를 따랐지만 FastAPI에 적응하면 비디오는 첫 번째 프레임만 표시하고 동결 상태로 유지된다. 나는 Fast가 얼마나 빠른지에 차이가 있을지 의심한다API는 Flask와 비교하여 스트리밍 응답을 처리합니다.

플라스크의 예(정상 작동):

def generate():
    # grab global references to the output frame and lock variables
    global outputFrame, lock
    # loop over frames from the output stream
    while True:
        # wait until the lock is acquired
        with lock:
            # check if the output frame is available, otherwise skip
            # the iteration of the loop
            if outputFrame is None:
                continue
            # encode the frame in JPEG format
            (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
            # ensure the frame was successfully encoded
            if not flag:
                continue
        # yield the output frame in the byte format
        yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
               bytearray(encodedImage) + b'\r\n')

@app.route("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    return Response(generate(),
                    mimetype="multipart/x-mixed-replace; boundary=frame")

여기 마이 패스트API 코드:

def generate():
    # grab global references to the output frame and lock variables
    global outputFrame, lock
    # loop over frames from the output stream
    while True:
        # wait until the lock is acquired
        with lock:
            # check if the output frame is available, otherwise skip
            # the iteration of the loop
            if outputFrame is None:
                continue
            # encode the frame in JPEG format
            (flag, encodedImage) = cv2.imencode(".jpg", outputFrame)
            # ensure the frame was successfully encoded
            if not flag:
                continue
        # yield the output frame in the byte format
        yield b''+bytearray(encodedImage)


@app.get("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    # return StreamingResponse(generate())
    return StreamingResponse(generate(), media_type="image/jpeg")

나도 그 질문을 검토했지만, 나의 구체적인 문제를 해결할 수 있는 해결책을 찾을 수 없었다.

누군가 내 패스트에서 내가 어떤 수정을 해야 하는지 이해하도록 도와줄 수 있니첫 번째 프레임 후 비디오 스트림이 지속적으로 업데이트되고 동결되지 않도록 하기 위한 API 코드? 나는 어떤 안내나 제안이라도 감사할 것이다. 감사해요!




여기에 글을 올린 후, 나는 그것을 고치는 방법을 알아냈다.

함수에서, 매개변수에서, 플라스크에서와 같은 방식으로 그것을 넣었을 뿐이다:

@app.get("/")
def video_feed():
    # return the response generated along with the specific media
    # type (mime type)
    # return StreamingResponse(generate())
    return StreamingResponse(generate(), media_type="multipart/x-mixed-replace;boundary=frame")

함수에서 다음을 생성합니다:

yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' +
               bytearray(encodedImage) + b'\r\n')

내 전체 코드:

http://github.com/mpimentel04/rtsp_fastapi




범위 요청(비디오/PDF/등에 유효)

import os
from typing import BinaryIO

from fastapi import HTTPException, Request, status
from fastapi.responses import StreamingResponse


def send_bytes_range_requests(
    file_obj: BinaryIO, start: int, end: int, chunk_size: int = 10_000
):
    """Send a file in chunks using Range Requests specification RFC7233

    `start` and `end` parameters are inclusive due to specification
    """
    with file_obj as f:
        f.seek(start)
        while (pos := f.tell()) <= end:
            read_size = min(chunk_size, end + 1 - pos)
            yield f.read(read_size)


def _get_range_header(range_header: str, file_size: int) -> tuple[int, int]:
    def _invalid_range():
        return HTTPException(
            status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE,
            detail=f"Invalid request range (Range:{range_header!r})",
        )

    try:
        h = range_header.replace("bytes=", "").split("-")
        start = int(h[0]) if h[0] != "" else 0
        end = int(h[1]) if h[1] != "" else file_size - 1
    except ValueError:
        raise _invalid_range()

    if start > end or start < 0 or end > file_size - 1:
        raise _invalid_range()
    return start, end


def range_requests_response(
    request: Request, file_path: str, content_type: str
):
    """Returns StreamingResponse using Range Requests of a given file"""

    file_size = os.stat(file_path).st_size
    range_header = request.headers.get("range")

    headers = {
        "content-type": content_type,
        "accept-ranges": "bytes",
        "content-encoding": "identity",
        "content-length": str(file_size),
        "access-control-expose-headers": (
            "content-type, accept-ranges, content-length, "
            "content-range, content-encoding"
        ),
    }
    start = 0
    end = file_size - 1
    status_code = status.HTTP_200_OK

    if range_header is not None:
        start, end = _get_range_header(range_header, file_size)
        size = end - start + 1
        headers["content-length"] = str(size)
        headers["content-range"] = f"bytes {start}-{end}/{file_size}"
        status_code = status.HTTP_206_PARTIAL_CONTENT

    return StreamingResponse(
        send_bytes_range_requests(open(file_path, mode="rb"), start, end),
        headers=headers,
        status_code=status_code,
    )

사용.

from fastapi import FastAPI


app = FastAPI()


@app.get("/video")
def get_video(request: Request):
    return range_requests_response(
        request, file_path="path_to_my_video.mp4", content_type="video/mp4"
    )



간단한 답변:

def get_video_range_response(request: Request, file_path: str, content_type: str = "video/mp4")
    file_size = os.stat(file_path).st_size
    h = request.headers.get("range").replace("bytes=", "").split("-")
    start = int(h[0]) if h[0] != "" else 0

    maxSize = 200000
    end = start + maxSize  # this is the expected end
    if end >= file_size #if end > file_size then obviously end = file_size - 1
        end = file_size - 1
    size = end - start
    headers = {"content-type": content_type,
               "accept-ranges": "bytes",
               "content-encoding": "identity",
               "content-length": str(size),
               "content-range": f" bytes {start}-{end}/{file_size}",
               }

    file_obj = open(file_path, mode="rb")
    file_obj.seek(start)
    data = file_obj.read(size)
    file_obj.close()
    status_code = status.HTTP_206_PARTIAL_CONTENT

    return Response(content=data,
                    status_code=status_code,
                    headers=headers,
                    media_type=content_type
                    )

사용.

@app.get('/Video')
def video_endpoint(req: Request):
    video_path = r"C:\WOI\pict\videos\Modi.mp4"
    return get_video_range_response(req, file_path = video_path, content_type = "video/mp4")

HTML

<video controls class="w-100">
    <source src="/Video" type="video/mp4">        
</video>

반응형