%%% @doc RTMP message decoding module %%% @private %%% @end %%% %%% %%% Copyright (c) 2013, Takeru Ohta %%% %%% The MIT License %%% %%% Permission is hereby granted, free of charge, to any person obtaining a copy %%% of this software and associated documentation files (the "Software"), to deal %%% in the Software without restriction, including without limitation the rights %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell %%% copies of the Software, and to permit persons to whom the Software is %%% furnished to do so, subject to the following conditions: %%% %%% The above copyright notice and this permission notice shall be included in %%% all copies or substantial portions of the Software. %%% %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN %%% THE SOFTWARE. %%% %%%--------------------------------------------------------------------------------------- -module(rtmpmsg_message_decode). -include("../include/rtmpmsg.hrl"). -include("../include/internal/rtmpmsg_internal.hrl"). %% Exported API -export([decode_chunk/1]). %%================================================================================ %% Exported API %%================================================================================ %% @doc Decode RTMP Chunk -spec decode_chunk(rtmpmsg:chunk()) -> rtmpmsg:message(). decode_chunk(Chunk) -> #chunk{msg_stream_id=StreamId, msg_type_id=TypeId, timestamp=Timestamp, payload=Payload} = Chunk, decode(StreamId, TypeId, Timestamp, Payload). %%================================================================================ %% Internal Functions %%================================================================================ -spec decode(rtmpmsg:message_stream_id(), rtmpmsg:message_type_id(), rtmpmsg:message_timestamp(), binary()) -> rtmpmsg:message(). decode(MessageStreamId, MessageTypeId, Timestamp, Payload) -> #rtmpmsg { stream_id = MessageStreamId, type_id = MessageTypeId, timestamp = Timestamp, body = decode_body(MessageTypeId, Timestamp, Payload) }. -spec decode_body(rtmpmsg:message_type_id(), rtmpmsg:message_timestamp(), binary()) -> rtmpmsg:message_body(). decode_body(TypeId, Timestamp, Payload) -> Ret = case TypeId of ?TYPE_SET_CHUNK_SIZE -> decode_set_chunk_size(Payload); ?TYPE_ABORT -> decode_abort(Payload); ?TYPE_ACK -> decode_ack(Payload); ?TYPE_USER_CONTROL -> decode_user_control(Payload); ?TYPE_WIN_ACK_SIZE -> decode_win_ack_size(Payload); ?TYPE_SET_PEER_BANDWIDTH -> decode_set_peer_bandwidth(Payload); ?TYPE_AUDIO -> decode_audio(Payload); ?TYPE_VIDEO -> decode_video(Payload); ?TYPE_COMMAND_AMF0 -> decode_command(amf0, Payload); ?TYPE_COMMAND_AMF3 -> decode_command(amf3, Payload); ?TYPE_DATA_AMF0 -> decode_data(amf0, Payload); ?TYPE_DATA_AMF3 -> decode_data(amf3, Payload); ?TYPE_SHARED_OBJECT_AMF0 -> decode_shared_object(amf0, Payload); ?TYPE_SHARED_OBJECT_AMF3 -> decode_shared_object(amf3, Payload); ?TYPE_AGGREGATE -> decode_aggregate(Payload, Timestamp); _ -> #rtmpmsg_unknown{type_id=TypeId, payload=Payload} end, case Ret of undefined -> #rtmpmsg_unknown{type_id=TypeId, payload=Payload}; _ -> Ret end. -spec decode_set_chunk_size(binary()) -> rtmpmsg:message_body_set_chunk_size(). decode_set_chunk_size(<>) -> #rtmpmsg_set_chunk_size{size=Size}. -spec decode_abort(binary()) -> rtmpmsg:message_body_abort(). decode_abort(<>) -> #rtmpmsg_abort{chunk_stream_id=ChunkStreamId}. -spec decode_ack(binary()) -> rtmpmsg:message_body_ack(). decode_ack(<>) -> #rtmpmsg_ack{sequence_number=SequenceNumber}. -spec decode_win_ack_size(binary()) -> rtmpmsg:message_body_win_ack_size(). decode_win_ack_size(<>) -> #rtmpmsg_win_ack_size{size=Size}. -spec decode_set_peer_bandwidth(binary()) -> rtmpmsg:message_body_set_peer_bandwidth(). decode_set_peer_bandwidth(<>) -> #rtmpmsg_set_peer_bandwidth{size=Size, limit_type=hard}; decode_set_peer_bandwidth(<>) -> #rtmpmsg_set_peer_bandwidth{size=Size, limit_type=soft}; decode_set_peer_bandwidth(<>) -> #rtmpmsg_set_peer_bandwidth{size=Size, limit_type=dynamic}. -spec decode_user_control(binary()) -> rtmpmsg:message_body_user_control(). decode_user_control(<>) -> Event = case EventType of ?EVENT_STREAM_BEGIN -> decode_event_stream_begin(Payload); ?EVENT_STREAM_EOF -> decode_event_stream_eof(Payload); ?EVENT_STREAM_DRY -> decode_event_stream_dry(Payload); ?EVENT_SET_BUFFER_LENGTH -> decode_event_set_buffer_length(Payload); ?EVENT_STREAM_IS_RECORDED -> decode_event_stream_is_recorded(Payload); ?EVENT_PING_REQUEST -> decode_event_ping_request(Payload); ?EVENT_PING_RESPONSE -> decode_event_ping_response(Payload); ?EVENT_BUFFER_EMPTY -> decode_event_buffer_empty(Payload); ?EVENT_BUFFER_READY -> decode_event_buffer_ready(Payload); _ -> #rtmpmsg_event_unknown{type_id=EventType, payload=Payload} end, #rtmpmsg_user_control{event=Event}. -spec decode_audio(binary()) -> rtmpmsg:message_body_audio(). decode_audio(Payload) -> #rtmpmsg_audio{data = Payload}. -spec decode_video(binary()) -> rtmpmsg:message_body_video(). decode_video(Payload) -> #rtmpmsg_video{data = Payload}. -spec decode_command(amf:amf_version(), binary()) -> rtmpmsg:message_body_command() | undefined. decode_command(AmfVersion0, Payload0) -> {AmfVersion, Payload} = case {AmfVersion0, Payload0} of {amf3, <<0, Rest/binary>>} -> {amf0, Rest}; % NOTE: 理由は分からないがFlashPlayerはこのような変則的なデータを送ってくる _ -> {AmfVersion0, Payload0} end, try {ok, CommandName, Bin1} = amf:decode(AmfVersion, Payload), {ok, TransactionId, Bin2} = amf:decode(AmfVersion, Bin1), {ok, CommandObject, Bin3} = amf:decode(AmfVersion, Bin2), OptionalArgs = decode_amf_values(AmfVersion, Bin3, []), #rtmpmsg_command{ amf_version = AmfVersion, name = CommandName, transaction_id = TransactionId, object = CommandObject, args = OptionalArgs } catch error:{badmatch, {error, _}} -> undefined end. -spec decode_data(amf:amf_version(), binary()) -> rtmpmsg:message_body_data(). decode_data(AmfVersion0, Payload0) -> {AmfVersion, Payload} = case {AmfVersion0, Payload0} of {amf3, <<0, Rest/binary>>} -> {amf0, Rest}; % NOTE: 理由は分からないがFlashPlayerはこのような変則的なデータを送ってくる _ -> {AmfVersion0, Payload0} end, #rtmpmsg_data{amf_version = AmfVersion, values = decode_amf_values(AmfVersion, Payload, [])}. -spec decode_shared_object(amf:amf_version(), binary()) -> rtmpmsg:message_body_shared_object(). decode_shared_object(AmfVersion, Payload) -> #rtmpmsg_shared_object{amf_version = AmfVersion, payload = Payload}. -spec decode_aggregate(binary(), rtmpmsg:message_timestamp()) -> rtmpmsg:message_body_aggregate(). decode_aggregate(Payload, Timestamp) -> #rtmpmsg_aggregate{messages = decode_aggregate_messages_first(Payload, Timestamp)}. -spec decode_aggregate_messages_first(binary(), rtmpmsg:message_timestamp()) -> [rtmpmsg:message()]. decode_aggregate_messages_first(<<>>, _) -> []; decode_aggregate_messages_first(<>, TimestampOffset) -> <> = <>, TimestampOffset2 = TimestampOffset - Timestamp, Msg = decode(StreamId, Type, TimestampOffset2 + Timestamp, Payload), decode_aggregate_messages(Bin, TimestampOffset2, [Msg]). -spec decode_aggregate_messages(binary(), rtmpmsg:message_timestamp(), [rtmpmsg:message()]) -> [rtmpmsg:message()]. decode_aggregate_messages(<<>>, _, Acc) -> lists:reverse(Acc); decode_aggregate_messages(<>, TimestampOffset, Acc) -> <> = <>, Msg = decode(StreamId, Type, TimestampOffset + Timestamp, Payload), decode_aggregate_messages(Bin, TimestampOffset, [Msg|Acc]). -spec decode_event_stream_begin(binary()) -> rtmpmsg:event_stream_begin(). decode_event_stream_begin(<>) -> #rtmpmsg_event_stream_begin{stream_id=StreamId}. -spec decode_event_stream_eof(binary()) -> rtmpmsg:event_stream_eof(). decode_event_stream_eof(<>) -> #rtmpmsg_event_stream_eof{stream_id=StreamId}. -spec decode_event_stream_dry(binary()) -> rtmpmsg:event_stream_dry(). decode_event_stream_dry(<>) -> #rtmpmsg_event_stream_dry{stream_id=StreamId}. -spec decode_event_stream_is_recorded(binary()) -> rtmpmsg:event_stream_is_recorded(). decode_event_stream_is_recorded(<>) -> #rtmpmsg_event_stream_is_recorded{stream_id=StreamId}. -spec decode_event_buffer_empty(binary()) -> rtmpmsg:event_buffer_empty(). decode_event_buffer_empty(<>) -> #rtmpmsg_event_buffer_empty{stream_id=StreamId}. -spec decode_event_buffer_ready(binary()) -> rtmpmsg:event_buffer_ready(). decode_event_buffer_ready(<>) -> #rtmpmsg_event_buffer_ready{stream_id=StreamId}. -spec decode_event_set_buffer_length(binary()) -> rtmpmsg:event_set_buffer_length(). decode_event_set_buffer_length(<>) -> #rtmpmsg_event_set_buffer_length{stream_id=StreamId, length=Len}. -spec decode_event_ping_request(binary()) -> rtmpmsg:event_ping_request(). decode_event_ping_request(<>) -> #rtmpmsg_event_ping_request{timestamp=Timestamp}. -spec decode_event_ping_response(binary()) -> rtmpmsg:event_ping_response(). decode_event_ping_response(<>) -> #rtmpmsg_event_ping_response{timestamp=Timestamp}. -spec decode_amf_values(amf:amf_version(), binary(), [amf:amf_value()]) -> [amf:amf_value()]. decode_amf_values(_AmfVersion, <<"">>, Acc) -> lists:reverse(Acc); decode_amf_values(AmfVersion, Bin, Acc) -> {ok, Value, Bin1} = amf:decode(AmfVersion, Bin), decode_amf_values(AmfVersion, Bin1, [Value|Acc]).