WinGyu_coder

Python, 파이썬으로 실시간 스트리밍 구현하기, RTMP, FFmpeg 사용 방법 본문

Python파이썬

Python, 파이썬으로 실시간 스트리밍 구현하기, RTMP, FFmpeg 사용 방법

WinGyu 2024. 3. 19. 10:00

Python 개발자로 일을 하다가 보니, 영상 분석쪽 업무를 하게 되었는데 분석한 영상을 스트리밍으로 구현을 해야 했다.

그러기 위해 Cloudflare CDN 서버를 사용했는데, 나쁘지 않게 구현이 가능해서 글을 적어본다.

 

간단한 구성은 아래와 같다.  

 

설명해보자면 우선 우분투 리눅스로 서버를 구성하였다. 

사용자가 영상 분석을 요청하면 HTTP, HTTPS 요청을 서버가 받아서 영상 분석을 시작한다.

들어온 요청 만큼 파이썬이 FFmpeg 프로세서 명령어를 실행해 영상을 RTMP 를 통해 CDN 서버인 Cloudflare에 보낸다.

아무래도 실시간 스트리밍이기 때문에 실시간 영상 분석 속도가 관건이다. (전송속도 및 영상 재생 속도가 문제 있을시 FPS 및 해상도를 조절해보자, 영상 코덱, 전송 통신, 네트워크 등 생각해야할게 너무 많았다)

 

 

우선 Cloudflare 에 대해 알아보자.

https://www.cloudflare.com/ko-kr/

 

모든 곳을 연결하고, 보호하며, 구축합니다

복잡성과 비용을 줄이면서 직원, 애플리케이션, 네트워크를 어디에서든 더 빠르고 안전하게 만듭니다.

www.cloudflare.com

 

간단히 설명하면 클라우드 서버를 운영하는 곳이다. CDN 서버를 지원한다.

영상 스트리밍 서비스도 지원을 해주고 각종 보안, 도메인, 웹 등을 지원한다.

cloudflare 는 개발자를 위한 문서뿐 아니라 일반 방송인, 관련 업종 종사자도 할 수 있게 구성을 해두었다.

 

우리는 Cloudflare를 영상 스트리밍 서버로 사용할거다.

(비용이 생기니 잘 알아보고 사용하자, 나름 합리적이고 저렴하다)

 

 

 

Django 랑 Nginx 설명은 넘어가겠다

 

가장 중요한 FFmpeg 이다.

https://ffmpeg.org/

 

FFmpeg

Converting video and audio has never been so easy. $ ffmpeg -i input.mp4 output.avi     News January 3rd, 2024, native VVC decoder The libavcodec library now contains a native VVC (Versatile Video Coding) decoder, supporting a large subset of the codec's

ffmpeg.org

 

영상 스트리밍에 대해 다양한 종류 및 형태로 기록하고 변환해주는 SW 소프트웨어 이다.

자유 소프트웨어와 오픈 소스로 구성되어 있다.

 

윈도우, 맥, 리눅스 등 전부 지원한다.

 

 

 

위 사이트를 접속해보면 아주 대단한 예시를 볼 수 있다.

위 명령어만 치면 영상 확장자를 변환할 수 있다.

 

자 그러면 필자가 구성한 리눅스 서버에 이를 설치해보자.

sudo apt update
sudo apt install ffmpeg

ffmpeg -version

 

아주 설치가 간단하다.  위 설치 명령어 후 ffmpeg -version 으로 버전을 확인해보자.

 

설치가 완료 되었으면 이를 사용할 파이썬 소스 코드다.

subprocess 라는 라이브러리를 사용했다.

 

import cv2
import numpy as np
import torch
from time import time
import subprocess

from ultralytics import YOLO
from ultralytics.utils.plotting import Annotator, colors

model_path = "weights/yolov8n-pose.pt"
video_path = "uploads/20240108_112946.mp4"

class ObjectDetection:
    
    # 초기화
    def __init__(self):
        self.model= YOLO(model_path)
        self.classes = self.model.names
    
    def draw_frame(self, frame):
        results = self.model.track(frame, persist=True)
        

        if results[0].boxes.id is not None:
            # Get the boxes and track IDs
            boxes = results[0].boxes.xywh.cpu()
            track_ids = results[0].boxes.id.int().cpu().tolist()
            clss = results[0].boxes.cls.cpu().tolist()

            annotator = Annotator(frame, line_width=2, example=str(self.classes))

            for r in results:
                if r.keypoints is not None:
                    for k in reversed(r.keypoints.data):
                        annotator.kpts(k, r.orig_shape, radius=5, kpt_line=True)

            for box, track_id, cls in zip(boxes, track_ids, clss):
                if isinstance(box, torch.Tensor):
                    box = box.tolist()

                x, y, w, h = box[0], box[1], box[2], box[3]
                w_half = w / 2
                h_half = h / 2

                box = [x - w_half, y - h_half, x + w_half, y + h_half]

                # draw 기능, 하지만 좌표 에러남. 위 bbox 재정의로 해결
                annotator.box_label(box, str(self.classes[cls]), color=colors(cls, True))
        return frame


od = ObjectDetection()

cap = cv2.VideoCapture(video_path)

private_ip = "192.168.주소.주소"

rtmp_out_url = "rtmps://live.cloudflare.com:443/live/"
key="cloudflare 키값"

make_url = rtmp_out_url + key

fps = int(cap.get(cv2.CAP_PROP_FPS))                                        
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))                 
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))     


command = ['ffmpeg',                                           
           '-y',                                                                  
           '-f', 'rawvideo',                                                       
           '-vcodec', 'rawvideo',
           '-pix_fmt', 'bgr24',
           '-s', "{}x{}".format(1280, 720),
           '-r', str(15),
           '-i', '-',
           '-c:v', 'libx264',
           '-pix_fmt', 'yuv420p',
           '-preset', 'ultrafast',
           '-f', 'flv',
           '-b:v', '2000k',
           "-acodec", "aac",
           "-b:a", "128k",
           "-flvflags", "no_duration_filesize",
           make_url]
           
# using subprocess and pipe to fetch frame data
p = subprocess.Popen(command, stdin=subprocess.PIPE)

while cap.isOpened():
    # start_time = time()
    status, frame = cap.read()
    if not status:
        break
    
    # results=od.score_frame(frame)
    # frame=od.mosaic_frame(results,frame)

    frame = od.draw_frame(frame)
    frame = cv2.resize(frame, (1280, 720))

    p.stdin.write(frame.tobytes())

    # 'q' 입력시 웹캠 종료
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
cv2.waitKey(1)

 

위 소스 코드는 ultralytics 에서 만든 yolov8을 통해 키포인트 인식 하는 소스 코드다.

 

가장 중요한 코드는 이거다.

rtmp_out_url = "rtmps://live.cloudflare.com:443/live/"
key="cloudflare 키값"

make_url = rtmp_out_url + key

fps = int(cap.get(cv2.CAP_PROP_FPS))                                        
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))                 
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))     


command = ['ffmpeg',                                           
           '-y',                                                                  
           '-f', 'rawvideo',                                                       
           '-vcodec', 'rawvideo',
           '-pix_fmt', 'bgr24',
           '-s', "{}x{}".format(1280, 720),
           '-r', str(15),
           '-i', '-',
           '-c:v', 'libx264',
           '-pix_fmt', 'yuv420p',
           '-preset', 'ultrafast',
           '-f', 'flv',
           '-b:v', '2000k',
           "-acodec", "aac",
           "-b:a", "128k",
           "-flvflags", "no_duration_filesize",
           make_url]

 

cloudflare에서 받은 rtmp 주소와 키 값을 적어준다.

그리고 FFmpeg에서 사용할 명령어를 위 command 처럼 정해준다.

 

사용할 환경에 따라 명령어를 검색해서 수정해주면 된다.

그리고 마지막엔 rtmp 주소를 적으면 된다. (make_url) 부분

 

p = subprocess.Popen(command, stdin=subprocess.PIPE)

while cap.isOpened():
    # start_time = time()
    status, frame = cap.read()
    if not status:
        break
    
    # results=od.score_frame(frame)
    # frame=od.mosaic_frame(results,frame)

    frame = od.draw_frame(frame)
    frame = cv2.resize(frame, (1280, 720))

    p.stdin.write(frame.tobytes())

    # 'q' 입력시 웹캠 종료
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break

 

이제 subprocess 를 사용해 실행해주면 분석한 영상을 실시간으로 스트리밍 및 스트리밍이 가능하다.