/apps/rtsp/src/rtsp_socket.erl
Erlang | 590 lines | 350 code | 134 blank | 106 comment | 16 complexity | 32c58f0ab25053465d3ab0c32e3fd720 MD5 | raw file
1 2%%% @author Max Lapshin <max@maxidoors.ru> [http://erlyvideo.org] 3%%% @copyright 2010 Max Lapshin 4%%% @doc RTSP socket module 5%%% 6%%% 7%%% 1. connect 8%%% 2. describe 9%%% 3. each setup 10%%% 4. play, possible Rtp-Sync 11%%% 5. get each packet 12%%% 6. decode 13%%% 14%%% 15%%% @end 16%%% @reference See <a href="http://erlyvideo.org/rtsp" target="_top">http://erlyvideo.org</a> for common information. 17%%% @end 18%%% 19%%% This file is part of erlang-rtsp. 20%%% 21%%% erlang-rtsp is free software: you can redistribute it and/or modify 22%%% it under the terms of the GNU General Public License as published by 23%%% the Free Software Foundation, either version 3 of the License, or 24%%% (at your option) any later version. 25%%% 26%%% erlang-rtsp is distributed in the hope that it will be useful, 27%%% but WITHOUT ANY WARRANTY; without even the implied warranty of 28%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 29%%% GNU General Public License for more details. 30%%% 31%%% You should have received a copy of the GNU General Public License 32%%% along with erlang-rtsp. If not, see <http://www.gnu.org/licenses/>. 33%%% 34%%%--------------------------------------------------------------------------------------- 35-module(rtsp_socket). 36-author('Max Lapshin <max@maxidoors.ru>'). 37-behaviour(gen_server). 38 39-include("log.hrl"). 40-include_lib("erlmedia/include/video_frame.hrl"). 41-include_lib("erlmedia/include/media_info.hrl"). 42-include_lib("erlmedia/include/sdp.hrl"). 43-include("rtsp.hrl"). 44 45-export([start_link/1, set_socket/2]). 46%% gen_server callbacks 47-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). 48 49-export([read/2, connect/3, options/2, describe/2, setup/3, play/2, teardown/1]). 50 51-export([handle_sdp/3, reply/3, reply/4, save_media_info/2, generate_session/0]). 52 53read(URL, Options) when is_binary(URL) -> 54 read(binary_to_list(URL), Options); 55 56read(URL, Options) when is_list(URL) -> 57 % try read_raw(URL, Options) of 58 % {ok, RTSP, MediaInfo} -> {ok, RTSP, MediaInfo} 59 % catch 60 % _Class:{error,Reason} -> {error, Reason}; 61 % exit:Reason -> {error, Reason}; 62 % Class:Reason -> {Class, Reason} 63 % end. 64 read_raw(URL, Options). 65 66read_raw(URL, Options) -> 67 {ok, RTSP} = rtsp_sup:start_rtsp_socket([{consumer, proplists:get_value(consumer, Options, self())}]), 68 ConnectResult = rtsp_socket:connect(RTSP, URL, Options), 69 ok == ConnectResult orelse erlang:throw(ConnectResult), 70 {ok, _Methods} = rtsp_socket:options(RTSP, Options), 71 {ok, MediaInfo, AvailableTracks} = rtsp_socket:describe(RTSP, Options), 72 Tracks = 73 case proplists:get_value(tracks, Options) of 74 undefined -> AvailableTracks; 75 RequestedTracks -> [T || T <- AvailableTracks, lists:member(T,RequestedTracks)] 76 end, 77 [ok = rtsp_socket:setup(RTSP, Track, Options) || Track <- Tracks], 78 ok = rtsp_socket:play(RTSP, Options), 79 {ok,RTSP,MediaInfo}. 80 81options(RTSP, Options) -> 82 Timeout = proplists:get_value(timeout, Options, 5000)*2, 83 gen_server:call(RTSP, {request, options}, Timeout). 84 85describe(RTSP, Options) -> 86 Timeout = proplists:get_value(timeout, Options, 5000)*2, 87 gen_server:call(RTSP, {request, describe}, Timeout). 88 89setup(RTSP, Stream, Options) -> 90 Timeout = proplists:get_value(timeout, Options, 5000)*2, 91 gen_server:call(RTSP, {request, setup, Stream}, Timeout). 92 93play(RTSP, Options) -> 94 Timeout = proplists:get_value(timeout, Options, 5000)*2, 95 gen_server:call(RTSP, {request, play}, Timeout). 96 97connect(RTSP, URL, Options) -> 98 Timeout = proplists:get_value(timeout, Options, 10000)*2, 99 gen_server:call(RTSP, {connect, URL, Options}, Timeout). 100 101teardown(RTSP) -> 102 gen_server:call(RTSP, teardown). 103 104start_link(Options) -> 105 gen_server:start_link(?MODULE, [Options], []). 106 107 108set_socket(Pid, Socket) when is_pid(Pid), is_port(Socket) -> 109 gen_tcp:controlling_process(Socket, Pid), 110 gen_server:cast(Pid, {socket_ready, Socket}). 111 112 113init([Options]) -> 114 Callback = proplists:get_value(callback, Options), 115 Consumer = proplists:get_value(consumer, Options), 116 case Consumer of 117 undefined -> ok; 118 _ -> erlang:monitor(process, Consumer) 119 end, 120 {ok, #rtsp_socket{callback = Callback, options = Options, media = Consumer, auth = fun empty_auth/2, timeout = ?DEFAULT_TIMEOUT}}. 121 122 123%%------------------------------------------------------------------------- 124%% @spec (Request, From, State) -> {reply, Reply, State} | 125%% {reply, Reply, State, Timeout} | 126%% {noreply, State} | 127%% {noreply, State, Timeout} | 128%% {stop, Reason, Reply, State} | 129%% {stop, Reason, State} 130%% @doc Callback for synchronous server calls. If `{stop, ...}' tuple 131%% is returned, the server is stopped and `terminate/2' is called. 132%% @end 133%% @private 134%%------------------------------------------------------------------------- 135 136 137handle_call({connect, _, Options} = Call, From, #rtsp_socket{} = RTSP) -> 138 rtsp_inbound:handle_call(Call, From, RTSP#rtsp_socket{dump_traffic = proplists:get_value(dump_traffic, Options, true)}); 139 140handle_call({consume, _Consumer} = Call, From, RTSP) -> 141 rtsp_inbound:handle_call(Call, From, RTSP); 142 143handle_call({request, _Request} = Call, From, RTSP) -> 144 rtsp_inbound:handle_call(Call, From, RTSP); 145 146handle_call({request, setup, _Num} = Call, From, RTSP) -> 147 rtsp_inbound:handle_call(Call, From, RTSP); 148 149handle_call(teardown, _From, RTSP) -> 150 send_teardown(RTSP), 151 {stop, normal, ok, RTSP}; 152 153handle_call(Request, _From, #rtsp_socket{} = RTSP) -> 154 {stop, {unknown_call, Request}, RTSP}. 155 156 157 158%%------------------------------------------------------------------------- 159%% @spec (Msg, State) ->{noreply, State} | 160%% {noreply, State, Timeout} | 161%% {stop, Reason, State} 162%% @doc Callback for asyncrous server calls. If `{stop, ...}' tuple 163%% is returned, the server is stopped and `terminate/2' is called. 164%% @end 165%% @private 166%%------------------------------------------------------------------------- 167handle_cast({socket_ready, Socket}, #rtsp_socket{timeout = Timeout} = State) -> 168 {ok, {IP, Port}} = inet:peername(Socket), 169 inet:setopts(Socket, [{active, once}]), 170 {noreply, State#rtsp_socket{socket = Socket, addr = IP, port = Port}, Timeout}; 171 172handle_cast(Request, #rtsp_socket{} = Socket) -> 173 {stop, {unknown_cast, Request}, Socket}. 174 175 176%%------------------------------------------------------------------------- 177%% @spec (Msg, State) ->{noreply, State} | 178%% {noreply, State, Timeout} | 179%% {stop, Reason, State} 180%% @doc Callback for messages sent directly to server's mailbox. 181%% If `{stop, ...}' tuple is returned, the server is stopped and 182%% `terminate/2' is called. 183%% @end 184%% @private 185%%------------------------------------------------------------------------- 186 187 188handle_info({tcp_closed, _Socket}, State) -> 189 ?D({"RTSP socket closed"}), 190 {stop, normal, State}; 191 192handle_info({udp, _Socket, Addr, Port, Bin}, #rtsp_socket{media = Consumer, timeout = Timeout, rtp = RTP} = RTSP) -> 193 {ok, RTP1, NewFrames} = rtp:handle_data(RTP, {Addr, Port}, Bin), 194 [Consumer ! Frame || Frame <- NewFrames], 195 {noreply, RTSP#rtsp_socket{rtp = RTP1}, Timeout}; 196 197handle_info({tcp, Socket, Bin}, #rtsp_socket{buffer = Buf, timeout = Timeout} = RTSPSocket) -> 198 inet:setopts(Socket, [{active, once}]), 199 {noreply, handle_packet(RTSPSocket#rtsp_socket{buffer = <<Buf/binary, Bin/binary>>}), Timeout}; 200 201% handle_info({'DOWN', _, process, Consumer, _Reason}, #rtsp_socket{rtp = Consumer} = Socket) -> 202% ?D({"RTSP RTP process died", Consumer}), 203% {stop, normal, Socket}; 204 205handle_info({'DOWN', _, process, Consumer, _Reason}, #rtsp_socket{media = Consumer} = Socket) -> 206 ?D({"RTSP consumer died", Consumer}), 207 {stop, normal, Socket}; 208 209handle_info(#video_frame{} = Frame, #rtsp_socket{timeout = Timeout} = Socket) -> 210 {noreply, rtsp_outbound:encode_frame(Frame, Socket), Timeout}; 211 212handle_info({ems_stream, _, play_complete, _}, Socket) -> 213 {stop, normal, Socket}; 214 215handle_info(timeout, #rtsp_socket{} = Socket) -> 216 {stop, timeout, Socket}; 217 218handle_info(send_sr, #rtsp_socket{rtp = RTP} = Socket) -> 219 rtp:send_rtcp(RTP, sender_report, []), 220 {noreply, Socket}; 221 222handle_info({ems_stream,_Num,_}, #rtsp_socket{} = Socket) -> 223 {noreply, Socket}; 224 225handle_info({ems_stream,_Num,_Key,_}, #rtsp_socket{} = Socket) -> 226 {noreply, Socket}; 227 228handle_info({'EXIT', _, _}, RTSP) -> 229 {noreply, RTSP}; 230 231handle_info(Message, #rtsp_socket{} = Socket) -> 232 {stop, {unknown_message, Message}, Socket}. 233 234dump_io(false, _) -> ok; 235dump_io(true, IO) -> dump_io(IO). 236 237dump_io({request, Method, URL, Headers, undefined}) -> 238 HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]), 239 io:format("<<<<<< RTSP IN (~p:~p) <<<<<~n~s ~s RTSP/1.0~n~s~n", [?MODULE, ?LINE, Method, URL, HeaderS]); 240 241dump_io({request, Method, URL, Headers, Body}) -> 242 HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]), 243 io:format("<<<<<< RTSP IN (~p:~p) <<<<<~n~s ~s RTSP/1.0~n~s~n~s~n", [?MODULE, ?LINE, Method, URL, HeaderS, Body]); 244 245dump_io({response, Code, Message, Headers, undefined}) -> 246 HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]), 247 io:format("<<<<<< RTSP IN (~p:~p) <<<<<~nRTSP/1.0 ~p ~s~n~s~n", [?MODULE, ?LINE, Code, Message, HeaderS]); 248 249dump_io({response, Code, Message, Headers, Body}) -> 250 HeaderS = lists:flatten([io_lib:format("~p: ~p~n", [K, V]) || {K,V} <- Headers]), 251 io:format("<<<<<< RTSP IN (~p:~p) <<<<<~nRTSP/1.0 ~p ~s~n~s~n~s~n", [?MODULE, ?LINE, Code, Message, HeaderS, Body]). 252 253-define(DUMP_REQUEST(Flag, X), dump_io(Flag, X)). 254-define(DUMP_RESPONSE(Flag, X), dump_io(Flag, X)). 255 256handle_packet(#rtsp_socket{buffer = Data, rtp = RTP, media = Consumer, dump_traffic = Dump} = Socket) -> 257 case packet_codec:decode(Data) of 258 {more, Data} -> 259 Socket; 260 {ok, {rtp, Channel, Packet}, Rest} -> 261 {ok, RTP1, NewFrames} = rtp:handle_data(RTP, Channel, Packet), 262 Socket1 = case NewFrames of 263 [#video_frame{dts = DTS}|_] when Socket#rtsp_socket.sent_sdp_config == false -> 264 [Consumer ! Frame#video_frame{dts = DTS, pts = DTS} || Frame <- video_frame:config_frames(Socket#rtsp_socket.media_info)], 265 Socket#rtsp_socket{sent_sdp_config = true}; 266 _ -> 267 Socket 268 end, 269 [Consumer ! Frame || Frame <- NewFrames], 270 handle_packet(Socket1#rtsp_socket{buffer = Rest, rtp = RTP1}); 271 {ok, {response, _Code, _Message, Headers, _Body} = Response, Rest} -> 272 ?DUMP_RESPONSE(Dump, Response), 273 Socket1 = handle_response(extract_session(Socket#rtsp_socket{buffer = Rest}, Headers), Response), 274 handle_packet(Socket1); 275 {ok, {request, _Method, _URL, _Headers, _Body} = Request, Rest} -> 276 ?DUMP_REQUEST(Dump, Request), 277 Socket1 = handle_request(Request, Socket#rtsp_socket{buffer = Rest}), 278 handle_packet(Socket1) 279 end. 280 281% Special case for server, rejecting Basic auth 282handle_response(#rtsp_socket{state = Request, auth_type = basic, auth_info = AuthInfo, pending = From} = Socket, {response, 401, _Message, Headers, _Body}) -> 283 case proplists:get_value('Www-Authenticate', Headers) of 284 [digest|Digest] -> 285 [Username, Password] = string:tokens(AuthInfo, ":"), 286 287 DigestAuth = fun(ReqName, URL) -> 288 digest_auth(Digest, Username, Password, URL, ReqName) 289 end, 290 {noreply, Socket1, _T} = rtsp_inbound:handle_call({request,Request}, From, Socket#rtsp_socket{auth_type = digest, auth = DigestAuth}), 291 Socket1; 292 _ -> 293 reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, unauthorized}}) 294 end; 295 296 297handle_response(#rtsp_socket{state = options} = Socket, {response, _Code, _Message, Headers, _Body}) -> 298 Available = string:tokens(binary_to_list(proplists:get_value('Public', Headers, <<"">>)), ", "), 299 reply_pending(Socket#rtsp_socket{pending_reply = {ok, Available}}); 300 301handle_response(#rtsp_socket{state = describe} = Socket, {response, 200, _Message, Headers, Body}) -> 302 Socket1 = handle_sdp(Socket, Headers, Body), 303 reply_pending(Socket1#rtsp_socket{state = undefined}); 304 305handle_response(#rtsp_socket{state = play} = Socket, {response, 200, _Message, Headers, _Body}) -> 306 Socket1 = rtsp_inbound:sync_rtp(Socket, Headers), 307 reply_pending(Socket1#rtsp_socket{state = undefined}); 308 309handle_response(#rtsp_socket{state = {setup, StreamId}, rtp = RTP, transport = Transport} = Socket, {response, 200, _Message, Headers, _Body}) -> 310 TransportHeader = proplists:get_value('Transport', Headers, []), 311 PortOpts = case proplists:get_value(server_port, TransportHeader) of 312 {SPort1,SPort2} -> [{remote_rtp_port,SPort1},{remote_rtcp_port,SPort2}]; 313 undefined -> [] 314 end, 315 {ok, RTP1, _} = rtp:setup_channel(RTP, StreamId, [{proto,Transport}]++PortOpts), 316 reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = ok, rtp = RTP1}); 317 318handle_response(Socket, {response, 401, _Message, _Headers, _Body}) -> 319 reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, unauthorized}}); 320 321handle_response(Socket, {response, 404, _Message, _Headers, _Body}) -> 322 reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, not_found}}); 323 324handle_response(Socket, {response, _Code, _Message, _Headers, _Body}) -> 325 reply_pending(Socket#rtsp_socket{state = undefined, pending_reply = {error, _Code}}). 326 327 328reply_pending(#rtsp_socket{pending = undefined} = Socket) -> 329 Socket; 330 331reply_pending(#rtsp_socket{state = {Method, Count}} = Socket) when Count > 1 -> 332 Socket#rtsp_socket{state = {Method, Count - 1}}; 333 334reply_pending(#rtsp_socket{pending = From, pending_reply = Reply} = Socket) -> 335 gen_server:reply(From, Reply), 336 Socket#rtsp_socket{pending = undefined, pending_reply = ok}. 337 338handle_sdp(#rtsp_socket{media = Consumer, content_base = OldContentBase, url = URL} = Socket, Headers, Body) -> 339 <<"application/sdp">> = proplists:get_value('Content-Type', Headers), 340 MI1 = #media_info{streams = Streams} = sdp:decode(Body), 341 MI2 = MI1#media_info{streams = [S || #stream_info{content = Content, codec = Codec} = S <- Streams, 342 (Content == audio orelse Content == video) andalso Codec =/= undefined]}, 343 MediaInfo = MI2, 344 RTP = rtp:init(local, MediaInfo), 345 ContentBase = case proplists:get_value('Content-Base', Headers) of 346 undefined -> OldContentBase; 347 NewContentBase -> % Here we must handle important case when Content-Base is given with local network 348 {match, [_Host, BasePath]} = re:run(NewContentBase, "rtsp://([^/]+)(/.*)$", [{capture,all_but_first,list}]), 349 {match, [Host, _Path]} = re:run(URL, "rtsp://([^/]+)(/.*)$", [{capture,all_but_first,list}]), 350 "rtsp://" ++ Host ++ "/" ++ BasePath 351 end, 352 Socket1 = save_media_info(Socket#rtsp_socket{rtp = RTP, content_base = ContentBase}, MediaInfo), 353 case Consumer of 354 undefined -> ok; 355 _ -> Consumer ! Socket1#rtsp_socket.media_info 356 end, 357 Socket1. 358 359 360save_media_info(#rtsp_socket{options = Options} = Socket, #media_info{streams = Streams1} = MediaInfo) -> 361 StreamNums = lists:seq(1, length(Streams1)), 362 363 Streams2 = lists:sort(fun(#stream_info{track_id = Id1}, #stream_info{track_id = Id2}) -> 364 Id1 =< Id2 365 end, Streams1), 366 367 Streams3 = [lists:nth(Track, Streams2) || Track <- proplists:get_value(tracks, Options, lists:seq(1,length(Streams2)))], 368 369 Streams = Streams3, 370 371 StreamInfos = list_to_tuple(Streams3), 372 ControlMap = [{proplists:get_value(control, Opt),S} || #stream_info{options = Opt, track_id = S} <- Streams], 373 MediaInfo1 = MediaInfo#media_info{streams = Streams}, 374 375 % ?D({"Streams", StreamInfos, StreamNums, ControlMap}), 376 Socket#rtsp_socket{rtp_streams = StreamInfos, media_info = MediaInfo1, control_map = ControlMap, pending_reply = {ok, MediaInfo1, StreamNums}}. 377 378 379generate_session() -> 380 {_A1, A2, A3} = now(), 381 lists:flatten(io_lib:format("~p~p", [A2*1000,A3 div 1000])). 382 383seq(Headers) -> 384 proplists:get_value('Cseq', Headers, 1). 385 386% 387% Wirecast goes: 388% 389% ANNOUNCE with SDP 390% OPTIONS 391% SETUP 392 393user_agents() -> 394 [ 395 {"RealMedia", mplayer}, 396 {"LibVLC", vlc} 397 ]. 398 399detect_user_agent(Headers) -> 400 case proplists:get_value('User-Agent', Headers) of 401 undefined -> undefined; 402 UA -> find_user_agent(UA, user_agents()) 403 end. 404 405find_user_agent(_UA, []) -> undefined; 406find_user_agent(UA, [{Match, Name}|Matches]) -> 407 case re:run(UA, Match, [{capture,all_but_first,list}]) of 408 {match, _} -> Name; 409 _ -> find_user_agent(UA, Matches) 410 end. 411 412 413setup_user_agent_preferences(#rtsp_socket{} = Socket, Headers) -> 414 UserAgent = detect_user_agent(Headers), 415 Socket#rtsp_socket{user_agent = UserAgent}. 416 417 418handle_request({request, 'DESCRIBE', URL, Headers, Body}, Socket) -> 419 rtsp_outbound:handle_describe_request(Socket, URL, Headers, Body); 420 421 422handle_request({request, 'RECORD', URL, Headers, Body}, #rtsp_socket{callback = Callback} = State) -> 423 case Callback:record(URL, Headers, Body) of 424 ok -> 425 reply(State, "200 OK", [{'Cseq', seq(Headers)}]); 426 {error, authentication} -> 427 reply(State, "401 Unauthorized", [{"WWW-Authenticate", "Basic realm=\"Erlyvideo Streaming Server\""}, {'Cseq', seq(Headers)}]) 428 end; 429 430 431handle_request({request, 'PLAY', URL, Headers, Body}, #rtsp_socket{direction = in} = State) -> 432 handle_request({request, 'RECORD', URL, Headers, Body}, State); 433 434handle_request({request, 'PLAY', URL, Headers, Body}, #rtsp_socket{} = Socket) -> 435 rtsp_outbound:handle_play_request(Socket, URL, Headers, Body); 436 437handle_request({request, 'OPTIONS', _URL, Headers, _Body}, State) -> 438 reply(setup_user_agent_preferences(State, Headers), "200 OK", 439 [{'Server', ?SERVER_NAME}, {'Cseq', seq(Headers)}, {"Supported", "play.basic, con.persistent"}, 440 {'Public', "SETUP, TEARDOWN, PLAY, PAUSE, OPTIONS, ANNOUNCE, DESCRIBE, RECORD, GET_PARAMETER"}]); 441 442handle_request({request, 'ANNOUNCE', URL, Headers, Body}, Socket) -> 443 rtsp_inbound:handle_announce_request(Socket, URL, Headers, Body); 444 445handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{direction = in} = State) -> 446 rtsp_inbound:handle_pause(State, _URL, Headers, _Body); 447 448handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{direction = out} = State) -> 449 rtsp_outbound:handle_pause_request(State, _URL, Headers, _Body); 450% 451% handle_request({request, 'PAUSE', _URL, Headers, _Body}, #rtsp_socket{rtp = Consumer} = State) -> 452% gen_server:call(Consumer, {pause, self()}), 453% reply(State, "200 OK", [{'Cseq', seq(Headers)}]); 454 455handle_request({request, 'SETUP', URL, Headers, Body}, #rtsp_socket{} = Socket) -> 456 Transport = proplists:get_value('Transport', Headers), 457 case proplists:get_value(mode, Transport) of 458 'receive' -> rtsp_inbound:handle_receive_setup(Socket, URL, Headers, Body); 459 _ -> rtsp_outbound:handle_play_setup(Socket, URL, Headers, Body) 460 end; 461 462handle_request({request, 'GET_PARAMETER', URL, Headers, Body}, #rtsp_socket{} = Socket) -> 463 handle_request({request, 'OPTIONS', URL, Headers, Body}, Socket); 464 465handle_request({request, 'TEARDOWN', _URL, Headers, _Body}, #rtsp_socket{} = State) -> 466 reply(State, "200 OK", [{'Cseq', seq(Headers)}]). 467 468 469 470reply(State, Code, Headers) -> 471 reply(State, Code, Headers, undefined). 472 473reply(#rtsp_socket{socket = Socket} = State, Code, Headers, Body) -> 474 {State1, Headers1} = append_session(State, Headers), 475 Headers2 = case Body of 476 undefined -> Headers1; 477 _ -> [{'Content-Length', iolist_size(Body)}, {'Content-Type', <<"application/sdp">>}|Headers1] 478 end, 479 Reply = iolist_to_binary(["RTSP/1.0 ", Code, <<"\r\n">>, packet_codec:encode_headers(Headers2), <<"\r\n">>, 480 case Body of 481 undefined -> <<>>; 482 _ -> Body 483 end]), 484 io:format("[RTSP Response to Client]~n~s", [Reply]), 485 gen_tcp:send(Socket, Reply), 486 State1. 487 488 489 490extract_session(Socket, Headers) -> 491 case proplists:get_value('Session', Headers) of 492 undefined -> 493 Socket; 494 FullSession -> 495 Socket#rtsp_socket{session = "Session: "++hd(string:tokens(binary_to_list(FullSession), ";"))++"\r\n"} 496 end. 497 498parse_session(Session) when is_binary(Session) -> parse_session(binary_to_list(Session)); 499parse_session(Session) -> hd(string:tokens(Session, ";")). 500 501append_session(#rtsp_socket{session = undefined} = Socket, Headers) -> 502 case proplists:get_value('Session', Headers) of 503 undefined -> {Socket, Headers}; 504 Session -> {Socket#rtsp_socket{session = parse_session(Session)}, Headers} 505 end; 506 507append_session(#rtsp_socket{session = Session, timeout = Timeout} = Socket, Headers) -> 508 Sess = lists:flatten(io_lib:format("~s;timeout=~p", [Session, Timeout div 1000])), 509 {Socket#rtsp_socket{session = Session}, [{'Session', Sess}|Headers]}. 510 511 512 513send_teardown(#rtsp_socket{socket = undefined}) -> 514 % ?D({warning, teardown,"on closed socket"}), 515 ok; 516 517send_teardown(#rtsp_socket{socket = Socket, url = URL, auth = Auth, seq = Seq} = RTSP) -> 518 Call = io_lib:format("TEARDOWN ~s RTSP/1.0\r\nCSeq: ~p\r\nAccept: application/sdp\r\n"++Auth("TEARDOWN", URL)++"\r\n", [URL, Seq+1]), 519 gen_tcp:send(Socket, Call), 520 rtsp_inbound:dump_io(RTSP, Call), 521 gen_tcp:close(Socket). 522 523 524%%------------------------------------------------------------------------- 525%% @spec (Reason, State) -> any 526%% @doc Callback executed on server shutdown. It is only invoked if 527%% `process_flag(trap_exit, true)' is set by the server process. 528%% The return value is ignored. 529%% @end 530%% @private 531%%------------------------------------------------------------------------- 532terminate(_Reason, RTSP) -> 533 send_teardown(RTSP), 534 ok. 535 536%%------------------------------------------------------------------------- 537%% @spec (OldVsn, State, Extra) -> {ok, NewState} 538%% @doc Convert process state when code is changed. 539%% @end 540%% @private 541%%------------------------------------------------------------------------- 542code_change(_OldVsn, State, _Extra) -> 543 {ok, State}. 544 545 546 547to_hex(L) when is_binary(L) -> 548 to_hex(binary_to_list(L)); 549 550to_hex(L) when is_list(L) -> 551 list_to_binary(lists:flatten(lists:map(fun(X) -> int_to_hex(X) end, L))). 552 553int_to_hex(N) when N < 256 -> 554 [hex(N div 16), hex(N rem 16)]. 555 556hex(N) when N < 10 -> 557 $0+N; 558hex(N) when N >= 10, N < 16 -> 559 $a + (N-10). 560 561empty_auth(_Method, _URL) -> 562 "". 563 564 565digest_auth(Digest, Username, Password, URL, Request) -> 566 Realm = proplists:get_value(realm, Digest), 567 Nonce = proplists:get_value(nonce, Digest), 568 % CNonce = to_hex(crypto:md5(iolist_to_binary([Username, ":erlyvideo:", Password]))), 569 % CNonce = <<>>, 570 % NonceCount = <<"00000000">>, 571 _Qop = proplists:get_value(qop, Digest), 572 573 % <<"auth">> == Qop orelse erlang:throw({unsupported_digest_auth, Qop}), 574 HA1 = to_hex(crypto:md5(iolist_to_binary([Username, ":", Realm, ":", Password]))), 575 HA2 = to_hex(crypto:md5(iolist_to_binary([Request, ":", URL]))), 576 Response = to_hex(crypto:md5(iolist_to_binary([HA1, ":", Nonce, ":", HA2]))), 577 578 579 DigestAuth = io_lib:format("Authorization: Digest username=\"~s\", realm=\"~s\", nonce=\"~s\", uri=\"~s\", response=\"~s\"\r\n", 580 [Username, Realm, Nonce, URL, Response]), 581 lists:flatten(DigestAuth). 582 583 584 585-include_lib("eunit/include/eunit.hrl"). 586 587digest_auth_test() -> 588 ?assertEqual("Authorization: Digest username=\"admin\", realm=\"Avigilon-12045784\", nonce=\"dh9U5wffmjzbGZguCeXukieLz277ckKgelszUk86230000\", uri=\"rtsp://admin:admin@94.80.16.122:554/defaultPrimary0?streamType=u\", response=\"99a9e6b080a96e25547b9425ff5d68bf\"\r\n", 589 digest_auth([{realm, <<"Avigilon-12045784">>}, {nonce, <<"dh9U5wffmjzbGZguCeXukieLz277ckKgelszUk86230000">>}, {qop, <<"auth">>}], "admin", "admin", "rtsp://admin:admin@94.80.16.122:554/defaultPrimary0?streamType=u", "OPTIONS")). 590