PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/apps/rtsp/src/rtsp_socket.erl

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