pipeline.addLast("challengeResponse", new RaopRtspChallengeResponseHandler(NetworkUtils.getInstance().getHardwareAddress())); pipeline.addLast("header", new RaopRtspHeaderHandler()); pipeline.addLast("options", new RaopRtspOptionsHandler());
根据样例格式我们可以解析出 AES 解密的秘钥和初始化矩阵IV以及流的数据格式,从而初始化 ALAC Decoder。其中,参数 m 的最后一个值和 rtpmap 的第一个值需要保持一致,rtpmap 的第一个值和 fmtp 的第一个值需要保持一致,他们都是 payload type 的值,因此在解析完包的数据之后要进行校验。fmtp 第一个参数之后的所有参数表示的都是媒体格式的指定参数。我们用这些参数来初始化 ALAC Decoder。关于 SDP 的详细参数描述你可以在谷歌上找到更多。
a=fmtp: This attribute allows parameters that are specific to a particular format to be conveyed in a way that SDP doesn’t have to understand them. The format must be one of the formats specified for the media. Format-specific parameters may be any set of parameters required to be conveyed by SDP and given unchanged to the media tool that will use this format.
It is a media attribute, and is not dependent on charset.
接下来是 AES 解密的秘钥和初始化矩阵 IV:
if ("rsaaeskey".equals(key)) { /* Sets the AES key required to decrypt the audio data. The key is * encrypted wih the AirTunes private key */ byte[] aesKeyRaw;
在 ANNOUNCE 中我们主要是得到了数据格式,数据解密的方法参数这些基本信息,那么 SETUP 的时候客户端就是在和我们交换一些连接信息:主要也就是三个 port 的信息,对应三个 channel,分别是 control port -> control channel,timing port -> timing channel 和 server port -> audio channel,这是三个 UDP 连接的端口。这也是整个 Airtunes 服务结构中最重要的部分了:
control port 是用来发送 resendTransmitRequest 的 channel,也就是当 Android 这边发现我收到的音乐流数据包中有丢失帧的时候,可以通过 control port 发送 resendTransmit 的 request 给 iOS 设备,设备收到后会将帧在 response 中补发回来
timing port 用来传输 Airplay 的时间同步包,同时也可以主动向 iOS 设备请求当前的时间戳来校准流的时间戳
server port 则是用来传输最主要的音乐流数据包
在这里我们将 control 和 timing 的包统一 reroute 到 audio 的 channel 上来处理。接收到的 UpStream 将包从 control 和 timing 集中到 audio 来处理,而发送出去的 DownStream 则是将指定类型的包从 audio 分发到 control 和 timing 去发送和接收 response。下面会详细展开。
/* Split Transport header into individual options and prepare response options list */ final Deque<String> requestOptions = new java.util.LinkedList<String>(Arrays.asList(req.getHeader(HEADER_TRANSPORT).split(";"))); final List<String> responseOptions = new java.util.LinkedList<String>();
/* Transport header. Protocol must be RTP/AVP/UDP */ finalString requestProtocol = requestOptions.removeFirst(); if ( ! "RTP/AVP/UDP".equals(requestProtocol)){ thrownew ProtocolException("Transport protocol must be RTP/AVP/UDP, but was " + requestProtocol); }
responseOptions.add(requestProtocol);
HEADER 中 key 为 Transport 的字段值必须为 RTP/AVP/UDP 。
首先对 SETUP 的参数列表进行解析,解出来的 requestOptions 仍然是用正则匹配的形式获取到 key - value 对:
/* Parse incoming transport options and build response options */ for(finalString requestOption: requestOptions) { /* Split option into key and value */ final Matcher transportOption = PATTERN_TRANSPORT_OPTION.matcher(requestOption); if ( ! transportOption.matches() ){ thrownew ProtocolException("Cannot parse Transport option " + requestOption); } finalStringkey = transportOption.group(1); finalString value = transportOption.group(3);
/* Probably means that two channels are interleaved in the stream. Included in the response options */ if ( ! "0-1".equals(value)){ thrownew ProtocolException("Unsupported Transport option, interleaved must be 0-1 but was " + value); } responseOptions.add("interleaved=0-1");
/* Means the we''re supposed to receive audio data, not send it. Included in the response options */ if ( ! "record".equals(value)){ thrownew ProtocolException("Unsupported Transport option, mode must be record but was " + value); } responseOptions.add("mode=record");
control_port 是 control channel 对应的客户端的端口号,而我们返回的 response 中需要改成服务端的端口号。可以随便分配一个比较大的端口号就行。
/* Port number of the client''s control socket. Response includes port number of *our* control port */ final int clientControlPort = Integer.valueOf(value);
/* Port number of the client''s timing socket. Response includes port number of *our* timing port */ final int clientTimingPort = Integer.valueOf(value);
/* We pretend that all communication takes place on the audio channel, * and simply re-route packets from and to the control and timing channels */ if ( ! channelType.equals(RaopRtpChannelType.Audio)) { pipeline.addLast("inputToAudioRouter", inputToAudioRouterDownstreamHandler);
/* Must come *after* the router, otherwise incoming packets are logged twice */ pipeline.addLast("packetLogger", packetLoggingHandler); } else { /* Must come *before* the router, otherwise outgoing packets are logged twice */ pipeline.addLast("packetLogger", packetLoggingHandler); pipeline.addLast("audioToOutputRouter", audioToOutputRouterUpstreamHandler); pipeline.addLast("timing", timingHandler); pipeline.addLast("resendRequester", resendRequestHandler);
if (decryptionHandler != null){ pipeline.addLast("decrypt", decryptionHandler); }
if (audioDecodeHandler != null){ pipeline.addLast("audioDecode", audioDecodeHandler); }
最新评论