You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Erlang通过RTSP获取IP摄像头视频并录制的技术咨询

Erlang通过RTSP从IP摄像头获取并录制视频的解决方案

一、先明确RTSP与IP摄像头的核心交互流程

你当前的问题核心是没掌握RTSP的标准会话流程,正确的交互步骤必须按顺序执行:

  • OPTIONS:先发送OPTIONS请求,确认摄像头支持的方法(你已完成这一步,但要注意每个请求必须带CSeq递增序列号)
  • DESCRIBE:获取SDP媒体描述,拿到视频/音频轨道的控制URL、编码格式等信息(你已拿到SDP,其中a=control:track1是视频轨道的控制路径)
  • SETUP:为每个媒体轨道单独发送SETUP请求,指定传输协议与通道,这是你当前SETUP失败的关键
  • PLAY:发送PLAY请求启动流传输
  • TEARDOWN:结束会话时发送

二、解决SETUP命令连接被关闭的问题

你的SETUP请求缺少必要的头部字段,导致摄像头拒绝连接,必须包含以下核心内容:

  • CSeq:每个RTSP请求必须带递增的序列号(比如OPTIONS用CSeq:1,DESCRIBE用CSeq:2,SETUP用CSeq:3),摄像头会通过响应中的CSeq匹配请求
  • Transport:指定传输方式,推荐用TCP interleaved模式(无需额外开UDP端口,多数摄像头支持),格式示例:Transport: RTP/AVP/TCP;interleaved=0-1(0对应视频通道,1对应音频通道)
  • Authorization:如果摄像头需要认证,DESCRIBE用了Basic认证,SETUP必须携带相同的认证头
  • Session:若之前的请求返回了Session头,后续SETUP请求必须带上

正确的SETUP请求示例(针对视频track1):

SETUP rtsp://192.168.*.*:554/track1 RTSP/1.0
CSeq: 3
Authorization: Basic *************=
Transport: RTP/AVP/TCP;interleaved=0-1

三、PLAY返回200但无视频流的原因及解决

PLAY返回200但没流,大概率是SETUP未正确完成,或是你未处理数据通道的流:

  1. 若使用TCP interleaved模式,RTP包会通过RTSP控制套接字发送,格式为$<通道号><长度高字节><长度低字节><RTP数据>,你需要在代码中区分处理RTSP文本响应和RTP二进制数据
  2. 若使用UDP模式,摄像头会把RTP包发送到你SETUP时指定的客户端端口,需单独监听UDP端口接收数据

你的现有代码只处理了RTSP文本响应,完全未处理RTP数据,所以自然看不到流。

四、现有Erlang代码的改进方向

你的代码存在几个关键问题,需要调整:

  1. 硬匹配响应:<<"RTSP/1.0 200 OK\r\n">> = Bin会导致代码崩溃——RTSP响应是多行的,{packet, line}会逐行返回,你需要收集所有行直到空行,再解析完整响应
  2. 缺少CSeq处理:所有请求必须带CSeq,否则摄像头可能拒绝
  3. 未处理RTP数据:TCP模式下套接字会收到两种数据,需区分处理

改进后的代码框架示例:

get_rtsp() ->
    {ok, Socket} = gen_tcp:connect("192.168.*.*", 554, [binary, {active, true}]),
    % 发送带CSeq的OPTIONS请求
    ok = gen_tcp:send(Socket, "OPTIONS rtsp://192.168.*.*:554 RTSP/1.0\r\nCSeq: 1\r\n\r\n"),
    rtsp_loop(Socket, 1, undefined).

rtsp_loop(Socket, NextCSeq, Session) ->
    receive
        {tcp, Socket, Bin} ->
            case binary:first(Bin) of
                $R -> % 处理RTSP文本响应(需拼接多行直到空行)
                    handle_rtsp_response(Bin, Socket, NextCSeq, Session);
                $$ -> % 处理RTP interleaved二进制数据
                    handle_rtp_data(Bin),
                    rtsp_loop(Socket, NextCSeq, Session);
                _ -> % 拼接多行响应的剩余部分
                    rtsp_loop(Socket, NextCSeq, Session)
            end;
        {tcp_closed, Socket} ->
            io:format("Socket closed~n")
    end.

handle_rtsp_response(Bin, Socket, NextCSeq, Session) ->
    % 解析响应中的状态码、CSeq、Session等字段
    case binary:match(Bin, <<"200 OK">>) of
        {_, _} ->
            case NextCSeq of
                1 -> % OPTIONS成功,发送DESCRIBE
                    Auth = "Authorization: Basic *************=\r\n",
                    Req = io_lib:format("DESCRIBE rtsp://192.168.*.*:554 RTSP/1.0\r\nCSeq: ~b\r\n~s\r\n", [NextCSeq+1, Auth]),
                    gen_tcp:send(Socket, list_to_binary(Req)),
                    rtsp_loop(Socket, NextCSeq+1, Session);
                2 -> % DESCRIBE成功,发送视频轨道SETUP
                    Auth = "Authorization: Basic *************=\r\n",
                    Transport = "Transport: RTP/AVP/TCP;interleaved=0-1\r\n",
                    SessionHeader = case Session of undefined -> ""; S -> io_lib:format("Session: ~s\r\n", [S]) end,
                    Req = io_lib:format("SETUP rtsp://192.168.*.*:554/track1 RTSP/1.0\r\nCSeq: ~b\r\n~s~s~s\r\n", [NextCSeq+1, Auth, Transport, SessionHeader]),
                    gen_tcp:send(Socket, list_to_binary(Req)),
                    rtsp_loop(Socket, NextCSeq+1, Session);
                3 -> % SETUP成功,发送PLAY请求
                    Auth = "Authorization: Basic *************=\r\n",
                    SessionHeader = io_lib:format("Session: ~s\r\n", [Session]),
                    Req = io_lib:format("PLAY rtsp://192.168.*.*:554 RTSP/1.0\r\nCSeq: ~b\r\n~s~s\r\n", [NextCSeq+1, Auth, SessionHeader]),
                    gen_tcp:send(Socket, list_to_binary(Req)),
                    rtsp_loop(Socket, NextCSeq+1, Session)
            end
    end.

handle_rtp_data(Bin) ->
    % 解析RTP interleaved数据格式:$ + 通道号 + 长度高8位 + 长度低8位 + 负载数据
    <<$$, Channel:8, LenHigh:8, LenLow:8, RTPData/binary>> = Bin,
    Len = (LenHigh bsl 8) bor LenLow,
    % 提取H264/PCMA负载,后续可写入磁盘或封装
    io:format("Received RTP data on channel ~b, length ~b~n", [Channel, Len]),
    ok.

五、录制视频到磁盘的实现步骤

  1. 解析RTP负载:从RTP数据中提取H264/PCMA原始数据,注意H264的RTP包可能是FU-A分片,需要重组为完整的NAL单元
  2. 封装媒体容器:将H264和PCMA数据封装为MP4等标准格式,可使用Erlang第三方MP4封装库,或调用外部工具(如ffmpeg)完成封装
  3. 持续写入磁盘:将封装后的数据流持续写入文件,直到会话结束或主动停止

六、额外注意事项

  • 部分摄像头的RTSP路径可能不是根路径,需查看说明书确认(比如rtsp://ip:554/stream1)
  • 若Basic认证失效,可能需要实现Digest认证逻辑
  • 需定期发送OPTIONS或GET_PARAMETER请求保持会话活跃,避免摄像头主动断开连接

内容的提问来源于stack exchange,提问作者Alexandr Kirilov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 10:56:00