| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_TAG "LibAAH_RTP" |
| //#define LOG_NDEBUG 0 |
| #include <utils/Log.h> |
| |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <sys/socket.h> |
| #include <time.h> |
| #include <utils/misc.h> |
| |
| #include <media/stagefright/Utils.h> |
| |
| #include "aah_rx_player.h" |
| #include "aah_tx_packet.h" |
| |
| namespace android { |
| |
| const uint32_t AAH_RXPlayer::kRetransRequestMagic = |
| FOURCC('T','r','e','q'); |
| const uint32_t AAH_RXPlayer::kRetransNAKMagic = |
| FOURCC('T','n','a','k'); |
| const uint32_t AAH_RXPlayer::kFastStartRequestMagic = |
| FOURCC('T','f','s','t'); |
| const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000; |
| const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000; |
| const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000; |
| |
| static inline int16_t fetchInt16(uint8_t* data) { |
| return static_cast<int16_t>(U16_AT(data)); |
| } |
| |
| static inline int32_t fetchInt32(uint8_t* data) { |
| return static_cast<int32_t>(U32_AT(data)); |
| } |
| |
| static inline int64_t fetchInt64(uint8_t* data) { |
| return static_cast<int64_t>(U64_AT(data)); |
| } |
| |
| uint64_t AAH_RXPlayer::monotonicUSecNow() { |
| struct timespec now; |
| int res = clock_gettime(CLOCK_MONOTONIC, &now); |
| CHECK(res >= 0); |
| |
| uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000; |
| ret += now.tv_nsec / 1000; |
| |
| return ret; |
| } |
| |
| status_t AAH_RXPlayer::startWorkThread() { |
| status_t res; |
| stopWorkThread(); |
| res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO); |
| |
| if (res != OK) { |
| ALOGE("Failed to start work thread (res = %d)", res); |
| } |
| |
| return res; |
| } |
| |
| void AAH_RXPlayer::stopWorkThread() { |
| thread_wrapper_->requestExit(); // set the exit pending flag |
| wakeup_work_thread_evt_.setEvent(); |
| |
| status_t res; |
| res = thread_wrapper_->requestExitAndWait(); // block until thread exit. |
| if (res != OK) { |
| ALOGE("Failed to stop work thread (res = %d)", res); |
| } |
| |
| wakeup_work_thread_evt_.clearPendingEvents(); |
| } |
| |
| void AAH_RXPlayer::cleanupSocket() { |
| if (sock_fd_ >= 0) { |
| if (multicast_joined_) { |
| int res; |
| struct ip_mreq mreq; |
| mreq.imr_multiaddr = listen_addr_.sin_addr; |
| mreq.imr_interface.s_addr = htonl(INADDR_ANY); |
| res = setsockopt(sock_fd_, |
| IPPROTO_IP, |
| IP_DROP_MEMBERSHIP, |
| &mreq, sizeof(mreq)); |
| if (res < 0) { |
| ALOGW("Failed to leave multicast group. (%d, %d)", res, errno); |
| } |
| multicast_joined_ = false; |
| } |
| |
| close(sock_fd_); |
| sock_fd_ = -1; |
| } |
| |
| resetPipeline(); |
| } |
| |
| void AAH_RXPlayer::resetPipeline() { |
| ring_buffer_.reset(); |
| |
| // Explicitly shudown all of the active substreams, then call clear out the |
| // collection. Failure to clear out a substream can result in its decoder |
| // holding a reference to itself and therefor not going away when the |
| // collection is cleared. |
| for (size_t i = 0; i < substreams_.size(); ++i) |
| substreams_.valueAt(i)->shutdown(); |
| |
| substreams_.clear(); |
| |
| current_gap_status_ = kGS_NoGap; |
| } |
| |
| bool AAH_RXPlayer::setupSocket() { |
| long flags; |
| int res, buf_size; |
| socklen_t opt_size; |
| |
| cleanupSocket(); |
| CHECK(sock_fd_ < 0); |
| |
| // Make the socket |
| sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
| if (sock_fd_ < 0) { |
| ALOGE("Failed to create listen socket (errno %d)", errno); |
| goto bailout; |
| } |
| |
| // Set non-blocking operation |
| flags = fcntl(sock_fd_, F_GETFL); |
| res = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK); |
| if (res < 0) { |
| ALOGE("Failed to set socket (%d) to non-blocking mode (errno %d)", |
| sock_fd_, errno); |
| goto bailout; |
| } |
| |
| // Bind to our port |
| struct sockaddr_in bind_addr; |
| memset(&bind_addr, 0, sizeof(bind_addr)); |
| bind_addr.sin_family = AF_INET; |
| bind_addr.sin_addr.s_addr = INADDR_ANY; |
| bind_addr.sin_port = listen_addr_.sin_port; |
| res = bind(sock_fd_, |
| reinterpret_cast<const sockaddr*>(&bind_addr), |
| sizeof(bind_addr)); |
| if (res < 0) { |
| uint32_t a = ntohl(bind_addr.sin_addr.s_addr); |
| uint16_t p = ntohs(bind_addr.sin_port); |
| ALOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)", |
| sock_fd_, |
| (a >> 24) & 0xFF, |
| (a >> 16) & 0xFF, |
| (a >> 8) & 0xFF, |
| (a ) & 0xFF, |
| p, |
| errno); |
| |
| goto bailout; |
| } |
| |
| buf_size = 1 << 16; // 64k |
| res = setsockopt(sock_fd_, |
| SOL_SOCKET, SO_RCVBUF, |
| &buf_size, sizeof(buf_size)); |
| if (res < 0) { |
| ALOGW("Failed to increase socket buffer size to %d. (errno %d)", |
| buf_size, errno); |
| } |
| |
| buf_size = 0; |
| opt_size = sizeof(buf_size); |
| res = getsockopt(sock_fd_, |
| SOL_SOCKET, SO_RCVBUF, |
| &buf_size, &opt_size); |
| if (res < 0) { |
| ALOGW("Failed to fetch socket buffer size. (errno %d)", errno); |
| } else { |
| ALOGI("RX socket buffer size is now %d bytes", buf_size); |
| } |
| |
| if (listen_addr_.sin_addr.s_addr) { |
| // Join the multicast group and we should be good to go. |
| struct ip_mreq mreq; |
| mreq.imr_multiaddr = listen_addr_.sin_addr; |
| mreq.imr_interface.s_addr = htonl(INADDR_ANY); |
| res = setsockopt(sock_fd_, |
| IPPROTO_IP, |
| IP_ADD_MEMBERSHIP, |
| &mreq, sizeof(mreq)); |
| if (res < 0) { |
| ALOGE("Failed to join multicast group. (errno %d)", errno); |
| goto bailout; |
| } |
| multicast_joined_ = true; |
| } |
| |
| return true; |
| |
| bailout: |
| cleanupSocket(); |
| return false; |
| } |
| |
| bool AAH_RXPlayer::threadLoop() { |
| struct pollfd poll_fds[2]; |
| bool process_more_right_now = false; |
| |
| if (!setupSocket()) { |
| sendEvent(MEDIA_ERROR); |
| goto bailout; |
| } |
| |
| while (!thread_wrapper_->exitPending()) { |
| // Step 1: Wait until there is something to do. |
| int gap_timeout = computeNextGapRetransmitTimeout(); |
| int ring_timeout = ring_buffer_.computeInactivityTimeout(); |
| int timeout = -1; |
| |
| if (!ring_timeout) { |
| ALOGW("RTP inactivity timeout reached, resetting pipeline."); |
| resetPipeline(); |
| timeout = gap_timeout; |
| } else { |
| if (gap_timeout < 0) { |
| timeout = ring_timeout; |
| } else if (ring_timeout < 0) { |
| timeout = gap_timeout; |
| } else { |
| timeout = (gap_timeout < ring_timeout) ? gap_timeout |
| : ring_timeout; |
| } |
| } |
| |
| if ((0 != timeout) && (!process_more_right_now)) { |
| // Set up the events to wait on. Start with the wakeup pipe. |
| memset(&poll_fds, 0, sizeof(poll_fds)); |
| poll_fds[0].fd = wakeup_work_thread_evt_.getWakeupHandle(); |
| poll_fds[0].events = POLLIN; |
| |
| // Add the RX socket. |
| poll_fds[1].fd = sock_fd_; |
| poll_fds[1].events = POLLIN; |
| |
| // Wait for something interesing to happen. |
| int poll_res = poll(poll_fds, NELEM(poll_fds), timeout); |
| if (poll_res < 0) { |
| ALOGE("Fatal error (%d,%d) while waiting on events", |
| poll_res, errno); |
| sendEvent(MEDIA_ERROR); |
| goto bailout; |
| } |
| } |
| |
| if (thread_wrapper_->exitPending()) { |
| break; |
| } |
| |
| wakeup_work_thread_evt_.clearPendingEvents(); |
| process_more_right_now = false; |
| |
| // Step 2: Do we have data waiting in the socket? If so, drain the |
| // socket moving valid RTP information into the ring buffer to be |
| // processed. |
| if (poll_fds[1].revents) { |
| struct sockaddr_in from; |
| socklen_t from_len; |
| |
| ssize_t res = 0; |
| while (!thread_wrapper_->exitPending()) { |
| // Check the size of any pending packet. |
| res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC); |
| |
| // Error? |
| if (res < 0) { |
| // If the error is anything other than would block, |
| // something has gone very wrong. |
| if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) { |
| ALOGE("Fatal socket error during recvfrom (%d, %d)", |
| (int)res, errno); |
| goto bailout; |
| } |
| |
| // Socket is out of data, just break out of processing and |
| // wait for more. |
| break; |
| } |
| |
| // Allocate a payload. |
| PacketBuffer* pb = PacketBuffer::allocate(res); |
| if (NULL == pb) { |
| ALOGE("Fatal error, failed to allocate packet buffer of" |
| " length %u", static_cast<uint32_t>(res)); |
| goto bailout; |
| } |
| |
| // Fetch the data. |
| from_len = sizeof(from); |
| res = recvfrom(sock_fd_, pb->data_, pb->length_, 0, |
| reinterpret_cast<struct sockaddr*>(&from), |
| &from_len); |
| if (res != pb->length_) { |
| ALOGE("Fatal error, fetched packet length (%d) does not" |
| " match peeked packet length (%u). This should never" |
| " happen. (errno = %d)", |
| static_cast<int>(res), |
| static_cast<uint32_t>(pb->length_), |
| errno); |
| } |
| |
| bool drop_packet = false; |
| if (transmitter_known_) { |
| if (from.sin_addr.s_addr != |
| transmitter_addr_.sin_addr.s_addr) { |
| uint32_t a = ntohl(from.sin_addr.s_addr); |
| uint16_t p = ntohs(from.sin_port); |
| ALOGV("Dropping packet from unknown transmitter" |
| " %u.%u.%u.%u:%hu", |
| ((a >> 24) & 0xFF), |
| ((a >> 16) & 0xFF), |
| ((a >> 8) & 0xFF), |
| ( a & 0xFF), |
| p); |
| |
| drop_packet = true; |
| } else { |
| transmitter_addr_.sin_port = from.sin_port; |
| } |
| } else { |
| memcpy(&transmitter_addr_, &from, sizeof(from)); |
| transmitter_known_ = true; |
| } |
| |
| if (!drop_packet) { |
| bool serious_error = !processRX(pb); |
| |
| if (serious_error) { |
| // Something went "seriously wrong". Currently, the |
| // only trigger for this should be a ring buffer |
| // overflow. The current failsafe behavior for when |
| // something goes seriously wrong is to just reset the |
| // pipeline. The system should behave as if this |
| // AAH_RXPlayer was just set up for the first time. |
| ALOGE("Something just went seriously wrong with the" |
| " pipeline. Resetting."); |
| resetPipeline(); |
| } |
| } else { |
| PacketBuffer::destroy(pb); |
| } |
| } |
| } |
| |
| // Step 3: Process any data we mave have accumulated in the ring buffer |
| // so far. |
| if (!thread_wrapper_->exitPending()) { |
| processRingBuffer(); |
| } |
| |
| // Step 4: At this point in time, the ring buffer should either be |
| // empty, or stalled in front of a gap caused by some dropped packets. |
| // Check on the current gap situation and deal with it in an appropriate |
| // fashion. If processGaps returns true, it means that it has given up |
| // on a gap and that we should try to process some more data |
| // immediately. |
| if (!thread_wrapper_->exitPending()) { |
| process_more_right_now = processGaps(); |
| } |
| |
| // Step 5: Check for fatal errors. If any of our substreams has |
| // encountered a fatal, unrecoverable, error, then propagate the error |
| // up to user level and shut down. |
| for (size_t i = 0; i < substreams_.size(); ++i) { |
| status_t status; |
| CHECK(substreams_.valueAt(i) != NULL); |
| |
| status = substreams_.valueAt(i)->getStatus(); |
| if (OK != status) { |
| ALOGE("Substream index %d has encountered an unrecoverable" |
| " error (%d). Signalling application level and shutting" |
| " down.", i, status); |
| sendEvent(MEDIA_ERROR); |
| goto bailout; |
| } |
| } |
| } |
| |
| bailout: |
| cleanupSocket(); |
| return false; |
| } |
| |
| bool AAH_RXPlayer::processRX(PacketBuffer* pb) { |
| CHECK(NULL != pb); |
| |
| uint8_t* data = pb->data_; |
| ssize_t amt = pb->length_; |
| uint32_t nak_magic; |
| uint16_t seq_no; |
| uint32_t epoch; |
| |
| // Every packet either starts with an RTP header which is at least 12 bytes |
| // long or is a retry NAK which is 14 bytes long. If there are fewer than |
| // 12 bytes here, this cannot be a proper RTP packet. |
| if (amt < 12) { |
| ALOGV("Dropping packet, too short to contain RTP header (%u bytes)", |
| static_cast<uint32_t>(amt)); |
| goto drop_packet; |
| } |
| |
| // Check to see if this is the special case of a NAK packet. |
| nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data))); |
| if (nak_magic == kRetransNAKMagic) { |
| // Looks like a NAK packet; make sure its long enough. |
| |
| if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) { |
| ALOGV("Dropping packet, too short to contain NAK payload (%u bytes)", |
| static_cast<uint32_t>(amt)); |
| goto drop_packet; |
| } |
| |
| SeqNoGap gap; |
| RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data); |
| gap.start_seq_ = ntohs(rtr->start_seq_); |
| gap.end_seq_ = ntohs(rtr->end_seq_); |
| |
| ALOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_); |
| ring_buffer_.processNAK(&gap); |
| |
| return true; |
| } |
| |
| // According to the TRTP spec, version should be 2, padding should be 0, |
| // extension should be 0 and CSRCCnt should be 0. If any of these tests |
| // fail, we chuck the packet. |
| if (data[0] != 0x80) { |
| ALOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)", |
| data[0]); |
| goto drop_packet; |
| } |
| |
| // Check the payload type. For TRTP, it should always be 100. |
| if ((data[1] & 0x7F) != 100) { |
| ALOGV("Dropping packet, bad payload type. (%u)", |
| data[1] & 0x7F); |
| goto drop_packet; |
| } |
| |
| // Check whether the transmitter has begun a new epoch. |
| epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF; |
| if (current_epoch_known_) { |
| if (epoch != current_epoch_) { |
| ALOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch); |
| current_epoch_ = epoch; |
| resetPipeline(); |
| } |
| } else { |
| current_epoch_ = epoch; |
| current_epoch_known_ = true; |
| } |
| |
| // Extract the sequence number and hand the packet off to the ring buffer |
| // for dropped packet detection and later processing. |
| seq_no = U16_AT(data + 2); |
| return ring_buffer_.pushBuffer(pb, seq_no); |
| |
| drop_packet: |
| PacketBuffer::destroy(pb); |
| return true; |
| } |
| |
| void AAH_RXPlayer::processRingBuffer() { |
| PacketBuffer* pb; |
| bool is_discon; |
| sp<Substream> substream; |
| LinearTransform trans; |
| bool foundTrans = false; |
| |
| while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) { |
| if (is_discon) { |
| // Abort all partially assembled payloads. |
| for (size_t i = 0; i < substreams_.size(); ++i) { |
| CHECK(substreams_.valueAt(i) != NULL); |
| substreams_.valueAt(i)->cleanupBufferInProgress(); |
| } |
| } |
| |
| uint8_t* data = pb->data_; |
| ssize_t amt = pb->length_; |
| |
| // Should not have any non-RTP packets in the ring buffer. RTP packets |
| // must be at least 12 bytes long. |
| CHECK(amt >= 12); |
| |
| // Extract the marker bit and the SSRC field. |
| bool marker = (data[1] & 0x80) != 0; |
| uint32_t ssrc = U32_AT(data + 8); |
| |
| // Is this the start of a new TRTP payload? If so, the marker bit |
| // should be set and there are some things we should be checking for. |
| if (marker) { |
| // TRTP headers need to have at least a byte for version, a byte for |
| // payload type and flags, and 4 bytes for length. |
| if (amt < 18) { |
| ALOGV("Dropping packet, too short to contain TRTP header" |
| " (%u bytes)", static_cast<uint32_t>(amt)); |
| goto process_next_packet; |
| } |
| |
| // Check the TRTP version and extract the payload type/flags. |
| uint8_t trtp_version = data[12]; |
| uint8_t payload_type = (data[13] >> 4) & 0xF; |
| uint8_t trtp_flags = data[13] & 0xF; |
| |
| if (1 != trtp_version) { |
| ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); |
| goto process_next_packet; |
| } |
| |
| // Is there a timestamp transformation present on this packet? If |
| // so, extract it and pass it to the appropriate substreams. |
| if (trtp_flags & 0x02) { |
| ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0); |
| if (amt < (offset + 24)) { |
| ALOGV("Dropping packet, too short to contain TRTP Timestamp" |
| " Transformation (%u bytes)", |
| static_cast<uint32_t>(amt)); |
| goto process_next_packet; |
| } |
| |
| trans.a_zero = fetchInt64(data + offset); |
| trans.b_zero = fetchInt64(data + offset + 16); |
| trans.a_to_b_numer = static_cast<int32_t>( |
| fetchInt32 (data + offset + 8)); |
| trans.a_to_b_denom = U32_AT(data + offset + 12); |
| foundTrans = true; |
| |
| uint32_t program_id = (ssrc >> 5) & 0x1F; |
| for (size_t i = 0; i < substreams_.size(); ++i) { |
| sp<Substream> iter = substreams_.valueAt(i); |
| CHECK(iter != NULL); |
| |
| if (iter->getProgramID() == program_id) { |
| iter->processTSTransform(trans); |
| } |
| } |
| } |
| |
| // Is this a command packet? If so, its not necessarily associate |
| // with one particular substream. Just give it to the command |
| // packet handler and then move on. |
| if (4 == payload_type) { |
| processCommandPacket(pb); |
| goto process_next_packet; |
| } |
| } |
| |
| // If we got to here, then we are a normal packet. Find (or allocate) |
| // the substream we belong to and send the packet off to be processed. |
| substream = substreams_.valueFor(ssrc); |
| if (substream == NULL) { |
| substream = new Substream(ssrc, omx_); |
| if (substream == NULL) { |
| ALOGE("Failed to allocate substream for SSRC 0x%08x", ssrc); |
| goto process_next_packet; |
| } |
| substreams_.add(ssrc, substream); |
| |
| if (foundTrans) { |
| substream->processTSTransform(trans); |
| } |
| } |
| |
| CHECK(substream != NULL); |
| |
| if (marker) { |
| // Start of a new TRTP payload for this substream. Extract the |
| // lower 32 bits of the timestamp and hand the buffer to the |
| // substream for processing. |
| uint32_t ts_lower = U32_AT(data + 4); |
| substream->processPayloadStart(data + 12, amt - 12, ts_lower); |
| } else { |
| // Continuation of an existing TRTP payload. Just hand it off to |
| // the substream for processing. |
| substream->processPayloadCont(data + 12, amt - 12); |
| } |
| |
| process_next_packet: |
| PacketBuffer::destroy(pb); |
| } // end of main processing while loop. |
| } |
| |
| void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) { |
| CHECK(NULL != pb); |
| |
| uint8_t* data = pb->data_; |
| ssize_t amt = pb->length_; |
| |
| // verify that this packet meets the minimum length of a command packet |
| if (amt < 20) { |
| return; |
| } |
| |
| uint8_t trtp_version = data[12]; |
| uint8_t trtp_flags = data[13] & 0xF; |
| |
| if (1 != trtp_version) { |
| ALOGV("Dropping packet, bad trtp version %hhu", trtp_version); |
| return; |
| } |
| |
| // calculate the start of the command payload |
| ssize_t offset = 18; |
| if (trtp_flags & 0x01) { |
| // timestamp is present (4 bytes) |
| offset += 4; |
| } |
| if (trtp_flags & 0x02) { |
| // transform is present (24 bytes) |
| offset += 24; |
| } |
| |
| // the packet must contain 2 bytes of command payload beyond the TRTP header |
| if (amt < offset + 2) { |
| return; |
| } |
| |
| uint16_t command_id = U16_AT(data + offset); |
| |
| switch (command_id) { |
| case TRTPControlPacket::kCommandNop: |
| break; |
| |
| case TRTPControlPacket::kCommandEOS: |
| case TRTPControlPacket::kCommandFlush: { |
| uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F; |
| ALOGI("*** %s flushing program_id=%d", |
| __PRETTY_FUNCTION__, program_id); |
| |
| Vector<uint32_t> substreams_to_remove; |
| for (size_t i = 0; i < substreams_.size(); ++i) { |
| sp<Substream> iter = substreams_.valueAt(i); |
| if (iter->getProgramID() == program_id) { |
| iter->shutdown(); |
| substreams_to_remove.add(iter->getSSRC()); |
| } |
| } |
| |
| for (size_t i = 0; i < substreams_to_remove.size(); ++i) { |
| substreams_.removeItem(substreams_to_remove[i]); |
| } |
| } break; |
| } |
| } |
| |
| bool AAH_RXPlayer::processGaps() { |
| // Deal with the current gap situation. Specifically... |
| // |
| // 1) If a new gap has shown up, send a retransmit request to the |
| // transmitter. |
| // 2) If a gap we were working on has had a packet in the middle or at |
| // the end filled in, send another retransmit request for the begining |
| // portion of the gap. TRTP was designed for LANs where packet |
| // re-ordering is very unlikely; so see the middle or end of a gap |
| // filled in before the begining is an almost certain indication that |
| // a retransmission packet was also dropped. |
| // 3) If we have been working on a gap for a while and it still has not |
| // been filled in, send another retransmit request. |
| // 4) If the are no more gaps in the ring, clear the current_gap_status_ |
| // flag to indicate that all is well again. |
| |
| // Start by fetching the active gap status. |
| SeqNoGap gap; |
| bool send_retransmit_request = false; |
| bool ret_val = false; |
| GapStatus gap_status; |
| if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) { |
| // Note: checking for a change in the end sequence number should cover |
| // moving on to an entirely new gap for case #1 as well as resending the |
| // begining of a gap range for case #2. |
| send_retransmit_request = (kGS_NoGap == current_gap_status_) || |
| (current_gap_.end_seq_ != gap.end_seq_); |
| |
| // If this is the same gap we have been working on, and it has timed |
| // out, then check to see if our substreams are about to underflow. If |
| // so, instead of sending another retransmit request, just give up on |
| // this gap and move on. |
| if (!send_retransmit_request && |
| (kGS_NoGap != current_gap_status_) && |
| (0 == computeNextGapRetransmitTimeout())) { |
| |
| // If out current gap is the fast-start gap, don't bother to skip it |
| // because substreams look like the are about to underflow. |
| if ((kGS_FastStartGap != gap_status) || |
| (current_gap_.end_seq_ != gap.end_seq_)) { |
| for (size_t i = 0; i < substreams_.size(); ++i) { |
| if (substreams_.valueAt(i)->isAboutToUnderflow()) { |
| ALOGV("About to underflow, giving up on gap [%hu, %hu]", |
| gap.start_seq_, gap.end_seq_); |
| ring_buffer_.processNAK(); |
| current_gap_status_ = kGS_NoGap; |
| return true; |
| } |
| } |
| } |
| |
| // Looks like no one is about to underflow. Just go ahead and send |
| // the request. |
| send_retransmit_request = true; |
| } |
| } else { |
| current_gap_status_ = kGS_NoGap; |
| } |
| |
| if (send_retransmit_request) { |
| // If we have been working on a fast start, and it is still not filled |
| // in, even after the extended retransmit time out, give up and skip it. |
| // The system should fall back into its normal slow-start behavior. |
| if ((kGS_FastStartGap == current_gap_status_) && |
| (current_gap_.end_seq_ == gap.end_seq_)) { |
| ALOGV("Fast start is taking forever; giving up."); |
| ring_buffer_.processNAK(); |
| current_gap_status_ = kGS_NoGap; |
| return true; |
| } |
| |
| // Send the request. |
| RetransRequest req; |
| uint32_t magic = (kGS_FastStartGap == gap_status) |
| ? kFastStartRequestMagic |
| : kRetransRequestMagic; |
| req.magic_ = htonl(magic); |
| req.mcast_ip_ = listen_addr_.sin_addr.s_addr; |
| req.mcast_port_ = listen_addr_.sin_port; |
| req.start_seq_ = htons(gap.start_seq_); |
| req.end_seq_ = htons(gap.end_seq_); |
| |
| { |
| uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr); |
| uint16_t p = ntohs(transmitter_addr_.sin_port); |
| ALOGV("Sending to transmitter %u.%u.%u.%u:%hu", |
| ((a >> 24) & 0xFF), |
| ((a >> 16) & 0xFF), |
| ((a >> 8) & 0xFF), |
| ( a & 0xFF), |
| p); |
| } |
| |
| int res = sendto(sock_fd_, &req, sizeof(req), 0, |
| reinterpret_cast<struct sockaddr*>(&transmitter_addr_), |
| sizeof(transmitter_addr_)); |
| if (res < 0) { |
| ALOGE("Error when sending retransmit request (%d)", errno); |
| } else { |
| ALOGV("%s request for range [%hu, %hu] sent", |
| (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit", |
| gap.start_seq_, gap.end_seq_); |
| } |
| |
| // Update the current gap info. |
| current_gap_ = gap; |
| current_gap_status_ = gap_status; |
| next_retrans_req_time_ = monotonicUSecNow() + |
| ((kGS_FastStartGap == current_gap_status_) |
| ? kFastStartTimeoutUSec |
| : kGapRerequestTimeoutUSec); |
| } |
| |
| return false; |
| } |
| |
| // Compute when its time to send the next gap retransmission in milliseconds. |
| // Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit |
| // right now. |
| int AAH_RXPlayer::computeNextGapRetransmitTimeout() { |
| if (kGS_NoGap == current_gap_status_) { |
| return -1; |
| } |
| |
| int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow(); |
| |
| timeout_delta /= 1000; |
| if (timeout_delta <= 0) { |
| return 0; |
| } |
| |
| return static_cast<uint32_t>(timeout_delta); |
| } |
| |
| } // namespace android |