Skip to content

Instantly share code, notes, and snippets.

@raspiduino
Created May 19, 2025 04:14
Show Gist options
  • Save raspiduino/1315779bdae3271875d9cb5e51c4a866 to your computer and use it in GitHub Desktop.
Save raspiduino/1315779bdae3271875d9cb5e51c4a866 to your computer and use it in GitHub Desktop.
Stream H.265 RTSP to Youtube HLS

Collection of stupid methods to stream H.265 RTSP from an IP camera to Youtube without video transcoding

Why?

I have a IP cam that output RTSP stream with:

  • H.265 video codec
  • Weird resolution of 1920x2160 (which is the result of stacking two 1920x1080 streams vertically into one frame)
  • Super unstable timing (pts + dts is not even provided)
  • 15fps when stable (yes that's what the camera output)

and I want to stream that to Youtube (using ffmpeg)

Transcoding is NOT an option since my server is just a little 4x Cortex A53 SoC with no hardware encoding/decoding capability for H.265 (and maybe H.264). I have tried transcoding from H.265 to H.264. It works, but at the even at the fastest preset and at the resolution of 640x720, it barely reaches 10fps.

What I have tried?

(You can retry if you think you have a better ffmpeg command for the approach)

  • Read the RTSP stream using ffmpeg and stream directly to Youtube via RTMP (ffmpeg input RTSP output RTMP) => Not work since ffmpeg's flv container does not support H.265.
  • Read the RTSP stream using ffmpeg and stream directly to Youtube via HLS (ffmpeg input RTSP output HLS) => Not work due to missing pts and dts (I think there should be a way to fix this but currently I don't know of one since. I have tried flags like genpts and use wallclock time but none worked)
  • Read the RTSP stream using ffmpeg, convert to mpeg live stream format and pipe to another ffmpeg which take that package and stream to HLS => Not work due to missing pts and dts
  • Record the RTSP stream to mp4 file segments, and another ffmpeg read these files and stream to Youtube via HLS (worked, see segmented_stream.py)
  • Record the RTSP stream to mp4 file segments, implement dual ffmpeg concat lists to continuous read new mp4 segments without restarting ffmpeg (promising, but my implementation failed since ffmpeg has really weird timing)
# Delay time is about 0.5 to 1 min
import os
import subprocess
import time
# Length of each segment
# Must be long enough so Youtube think you are providing a stable enough stream.
# I have tested 20, but Youtube terminated my stream thinking it was too unstable
# Should be fine-tuned
segment_length = 30 # time in seconds
# Somewhere to store segments
# I write to shm (write to disk is a pain for embed systems. It takes longer and damage the storage faster)
segment_dir = "/dev/shm/temp_segments"
# Command to record mp4 segments
# The -timeout flag (in microseconds) is the timeout in case rtsp stream corrupted while recording. Otherwise ffmpeg just stay hanged.
rec_command = f"ffmpeg -hide_banner -y -loglevel error -rtsp_transport udp -timeout 5000000 -use_wallclock_as_timestamps 1 -i rtsp://username:password@ip:554/onvif1 -vcodec copy -acodec aac -f segment -reset_timestamps 1 -segment_time {segment_length} -segment_format mp4 -segment_atclocktime 1 -strftime 1 {segment_dir}/%d-%m-%Y_%H-%M-%S.mp4"
def main():
if not os.path.exists(segment_dir):
os.mkdir(segment_dir)
global rec_command
rec_proc = subprocess.Popen(rec_command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print("Recorder started")
while True:
# Filter only mp4 files
mp4_files = [i for i in os.listdir(segment_dir) if i[-3:] == "mp4"]
# If there are files to process
if len(mp4_files) > 0:
# Sort from oldest to newest
mp4_files.sort()
# Remove oldest file (which is currently being recorded)
mp4_files.pop(-1)
for mp4_file in mp4_files:
print(f"Streaming {mp4_file}")
input_file = segment_dir + "/" + mp4_file
# hls time and hls init time should be between 1 and 5 sec as recommended by Youtube, but I chosed 5 to (hopefully) indicate we have a slowly updated stream (and therefore not disconnecting early)
str_command = f"ffmpeg -hide_banner -y -loglevel error -re -fflags +genpts -i {input_file} -flags +global_header -c:v copy -tag:v hvc1 -codec:a copy -hls_init_time 5.000 -hls_time 5.000 -strftime 1 -master_pl_name master.m3u8 -http_persistent 1 -f hls -segment_wrap 1 -method POST \"https://a.upload.youtube.com/http_upload_hls?cid=youtube-stream-key&copy=0&file=master.m3u8\" 2> stream_log.txt"
#str_command = str_command.replace("{input_file}", segment_dir + "/" + mp4_file)
print(f"Stream command: {str_command}")
os.system(str_command)
print(f"Delete streamed file {mp4_file}... ", end="")
os.remove(input_file)
print("done")
time.sleep(0.05)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment