/*
 * Copyright (C) 2010 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_NDEBUG 0
#define LOG_TAG "RTPSource"
#include <utils/Log.h>

#include "RTPSource.h"




#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <string.h>

namespace android {

const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs
static int32_t kMaxAllowedStaleAccessUnits = 20;

NuPlayer::RTPSource::RTPSource(
        const sp<AMessage> &notify,
        const String8& rtpParams)
    : Source(notify),
      mRTPParams(rtpParams),
      mFlags(0),
      mState(DISCONNECTED),
      mFinalResult(OK),
      mBuffering(false),
      mInPreparationPhase(true),
      mRTPConn(new ARTPConnection(ARTPConnection::kViLTEConnection)),
      mEOSTimeoutAudio(0),
      mEOSTimeoutVideo(0),
      mFirstAccessUnit(true),
      mAllTracksHaveTime(false),
      mNTPAnchorUs(-1),
      mMediaAnchorUs(-1),
      mLastMediaTimeUs(-1),
      mNumAccessUnitsReceived(0),
      mLastCVOUpdated(-1),
      mReceivedFirstRTCPPacket(false),
      mReceivedFirstRTPPacket(false),
      mPausing(false),
      mPauseGeneration(0) {
    ALOGD("RTPSource initialized with rtpParams=%s", rtpParams.c_str());
}

NuPlayer::RTPSource::~RTPSource() {
    if (mLooper != NULL) {
        mLooper->unregisterHandler(id());
        mLooper->unregisterHandler(mRTPConn->id());
        mLooper->stop();
    }
}

status_t NuPlayer::RTPSource::getBufferingSettings(
            BufferingSettings* buffering /* nonnull */) {
    Mutex::Autolock _l(mBufferingSettingsLock);
    *buffering = mBufferingSettings;
    return OK;
}

status_t NuPlayer::RTPSource::setBufferingSettings(const BufferingSettings& buffering) {
    Mutex::Autolock _l(mBufferingSettingsLock);
    mBufferingSettings = buffering;
    return OK;
}

void NuPlayer::RTPSource::prepareAsync() {
    if (mLooper == NULL) {
        mLooper = new ALooper;
        mLooper->setName("rtp");
        mLooper->start();

        mLooper->registerHandler(this);
        mLooper->registerHandler(mRTPConn);
    }

    CHECK_EQ(mState, (int)DISCONNECTED);
    mState = CONNECTING;

    setParameters(mRTPParams);

    TrackInfo *info = NULL;
    unsigned i;
    for (i = 0; i < mTracks.size(); i++) {
        info = &mTracks.editItemAt(i);

        if (info == NULL)
            break;

        AString sdp;
        ASessionDescription::SDPStringFactory(sdp, info->mLocalIp,
                info->mIsAudio, info->mLocalPort, info->mPayloadType, info->mAS, info->mCodecName,
                NULL, info->mWidth, info->mHeight, info->mCVOExtMap);
        ALOGD("RTPSource SDP =>\n%s", sdp.c_str());

        sp<ASessionDescription> desc = new ASessionDescription;
        bool isValidSdp = desc->setTo(sdp.c_str(), sdp.size());
        ALOGV("RTPSource isValidSdp => %d", isValidSdp);

        int sockRtp, sockRtcp;
        ARTPConnection::MakeRTPSocketPair(&sockRtp, &sockRtcp, info->mLocalIp, info->mRemoteIp,
                info->mLocalPort, info->mRemotePort, info->mSocketNetwork, info->mRtpSockOptEcn);

        sp<AMessage> notify = new AMessage('accu', this);

        ALOGV("RTPSource addStream. track-index=%d", i);
        notify->setSize("trackIndex", i);
        // index(i) should be started from 1. 0 is reserved for [root]
        mRTPConn->addStream(sockRtp, sockRtcp, desc, i + 1, notify, false);
        mRTPConn->setSelfID(info->mSelfID);
        mRTPConn->setStaticJitterTimeMs(info->mJbTimeMs);
        mRTPConn->setRtpSockOptEcn(info->mRtpSockOptEcn);
        mRTPConn->setIsIPv6(info->mLocalIp);

        unsigned long PT;
        AString formatDesc, formatParams;
        // index(i) should be started from 1. 0 is reserved for [root]
        desc->getFormatType(i + 1, &PT, &formatDesc, &formatParams);

        int32_t clockRate, numChannels;
        ASessionDescription::ParseFormatDesc(formatDesc.c_str(), &clockRate, &numChannels);
        info->mTimeScale = clockRate;

        info->mRTPSocket = sockRtp;
        info->mRTCPSocket = sockRtcp;
        info->mFirstSeqNumInSegment = 0;
        info->mNewSegment = true;
        info->mAllowedStaleAccessUnits = kMaxAllowedStaleAccessUnits;
        info->mRTPAnchor = 0;
        info->mNTPAnchorUs = -1;
        info->mNormalPlayTimeRTP = 0;
        info->mNormalPlayTimeUs = 0ll;

        // index(i) should be started from 1. 0 is reserved for [root]
        info->mPacketSource = new APacketSource(desc, i + 1);

        int32_t timeScale;
        sp<MetaData> format = getTrackFormat(i, &timeScale);
        sp<AnotherPacketSource> source = new AnotherPacketSource(format);

        if (info->mIsAudio) {
            mAudioTrack = source;
        } else {
            mVideoTrack = source;
        }

        info->mSource = source;
        info->mRTPTime = 0;
        info->mNormalPlaytimeUs = 0;
        info->mNPTMappingValid = false;
    }

    if (mInPreparationPhase) {
        mInPreparationPhase = false;
        notifyPrepared();
    }
}

void NuPlayer::RTPSource::start() {
}

void NuPlayer::RTPSource::pause() {
    mState = PAUSED;
}

void NuPlayer::RTPSource::resume() {
    mState = CONNECTING;
}

void NuPlayer::RTPSource::stop() {
    if (mLooper == NULL) {
        return;
    }
    sp<AMessage> msg = new AMessage(kWhatDisconnect, this);

    sp<AMessage> dummy;
    msg->postAndAwaitResponse(&dummy);
}

status_t NuPlayer::RTPSource::feedMoreTSData() {
    Mutex::Autolock _l(mBufferingLock);
    return mFinalResult;
}

sp<MetaData> NuPlayer::RTPSource::getFormatMeta(bool audio) {
    sp<AnotherPacketSource> source = getSource(audio);

    if (source == NULL) {
        return NULL;
    }

    return source->getFormat();
}

bool NuPlayer::RTPSource::haveSufficientDataOnAllTracks() {
    // We're going to buffer at least 2 secs worth data on all tracks before
    // starting playback (both at startup and after a seek).

    static const int64_t kMinDurationUs = 2000000ll;

    int64_t mediaDurationUs = 0;
    getDuration(&mediaDurationUs);
    if ((mAudioTrack != NULL && mAudioTrack->isFinished(mediaDurationUs))
            || (mVideoTrack != NULL && mVideoTrack->isFinished(mediaDurationUs))) {
        return true;
    }

    status_t err;
    int64_t durationUs;
    if (mAudioTrack != NULL
            && (durationUs = mAudioTrack->getBufferedDurationUs(&err))
                    < kMinDurationUs
            && err == OK) {
        ALOGV("audio track doesn't have enough data yet. (%.2f secs buffered)",
              durationUs / 1E6);
        return false;
    }

    if (mVideoTrack != NULL
            && (durationUs = mVideoTrack->getBufferedDurationUs(&err))
                    < kMinDurationUs
            && err == OK) {
        ALOGV("video track doesn't have enough data yet. (%.2f secs buffered)",
              durationUs / 1E6);
        return false;
    }

    return true;
}

status_t NuPlayer::RTPSource::dequeueAccessUnit(
        bool audio, sp<ABuffer> *accessUnit) {

    sp<AnotherPacketSource> source = getSource(audio);

    if (mState == PAUSED) {
        ALOGV("-EWOULDBLOCK");
        return -EWOULDBLOCK;
    }

    status_t finalResult;
    if (!source->hasBufferAvailable(&finalResult)) {
        if (finalResult == OK) {
            int64_t mediaDurationUs = 0;
            getDuration(&mediaDurationUs);
            sp<AnotherPacketSource> otherSource = getSource(!audio);
            status_t otherFinalResult;

            // If other source already signaled EOS, this source should also signal EOS
            if (otherSource != NULL &&
                    !otherSource->hasBufferAvailable(&otherFinalResult) &&
                    otherFinalResult == ERROR_END_OF_STREAM) {
                source->signalEOS(ERROR_END_OF_STREAM);
                return ERROR_END_OF_STREAM;
            }

            // If this source has detected near end, give it some time to retrieve more
            // data before signaling EOS
            if (source->isFinished(mediaDurationUs)) {
                int64_t eosTimeout = audio ? mEOSTimeoutAudio : mEOSTimeoutVideo;
                if (eosTimeout == 0) {
                    setEOSTimeout(audio, ALooper::GetNowUs());
                } else if ((ALooper::GetNowUs() - eosTimeout) > kNearEOSTimeoutUs) {
                    setEOSTimeout(audio, 0);
                    source->signalEOS(ERROR_END_OF_STREAM);
                    return ERROR_END_OF_STREAM;
                }
                return -EWOULDBLOCK;
            }

            if (!(otherSource != NULL && otherSource->isFinished(mediaDurationUs))) {
                // We should not enter buffering mode
                // if any of the sources already have detected EOS.
                // TODO: needs to be checked whether below line is needed or not.
                // startBufferingIfNecessary();
            }

            return -EWOULDBLOCK;
        }
        return finalResult;
    }

    setEOSTimeout(audio, 0);

    finalResult = source->dequeueAccessUnit(accessUnit);
    if (finalResult != OK) {
        return finalResult;
    }

    int32_t cvo;
    if ((*accessUnit) != NULL && (*accessUnit)->meta()->findInt32("cvo", &cvo) &&
            cvo != mLastCVOUpdated) {
        sp<AMessage> msg = new AMessage();
        msg->setInt32("payload-type", ARTPSource::RTP_CVO);
        msg->setInt32("cvo", cvo);

        sp<AMessage> notify = dupNotify();
        notify->setInt32("what", kWhatIMSRxNotice);
        notify->setMessage("message", msg);
        notify->post();

        ALOGV("notify cvo updated (%d)->(%d) to upper layer", mLastCVOUpdated, cvo);
        mLastCVOUpdated = cvo;
    }

    return finalResult;
}

sp<AnotherPacketSource> NuPlayer::RTPSource::getSource(bool audio) {
    return audio ? mAudioTrack : mVideoTrack;
}

void NuPlayer::RTPSource::setEOSTimeout(bool audio, int64_t timeout) {
    if (audio) {
        mEOSTimeoutAudio = timeout;
    } else {
        mEOSTimeoutVideo = timeout;
    }
}

status_t NuPlayer::RTPSource::getDuration(int64_t *durationUs) {
    *durationUs = 0ll;

    int64_t audioDurationUs;
    if (mAudioTrack != NULL && mAudioTrack->getFormat() != NULL
            && mAudioTrack->getFormat()->findInt64(
                kKeyDuration, &audioDurationUs)
            && audioDurationUs > *durationUs) {
        *durationUs = audioDurationUs;
    }

    int64_t videoDurationUs;
    if (mVideoTrack != NULL && mVideoTrack->getFormat() != NULL
            && mVideoTrack->getFormat()->findInt64(
                kKeyDuration, &videoDurationUs)
            && videoDurationUs > *durationUs) {
        *durationUs = videoDurationUs;
    }

    return OK;
}

status_t NuPlayer::RTPSource::seekTo(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
    ALOGV("RTPSource::seekTo=%d, mode=%d", (int)seekTimeUs, mode);
    return OK;
}

void NuPlayer::RTPSource::schedulePollBuffering() {
    sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
    msg->post(kBufferingPollIntervalUs); // 1 second intervals
}

void NuPlayer::RTPSource::onPollBuffering() {
    schedulePollBuffering();
}

bool NuPlayer::RTPSource::isRealTime() const {
    ALOGD("RTPSource::isRealTime=%d", true);
    return true;
}

void NuPlayer::RTPSource::onMessageReceived(const sp<AMessage> &msg) {
    ALOGV("onMessageReceived =%d", msg->what());

    switch (msg->what()) {
        case kWhatAccessUnitComplete:
        {
            if (mState == CONNECTING) {
                mState = CONNECTED;
            }

            int32_t timeUpdate;
            //"time-update" raised from ARTPConnection::parseSR()
            if (msg->findInt32("time-update", &timeUpdate) && timeUpdate) {
                size_t trackIndex;
                CHECK(msg->findSize("trackIndex", &trackIndex));

                uint32_t rtpTime;
                uint64_t ntpTime;
                CHECK(msg->findInt32("rtp-time", (int32_t *)&rtpTime));
                CHECK(msg->findInt64("ntp-time", (int64_t *)&ntpTime));

                onTimeUpdate(trackIndex, rtpTime, ntpTime);
            }

            int32_t IMSRxNotice;
            if (msg->findInt32("rtcp-event", &IMSRxNotice)) {
                int32_t payloadType = 0, feedbackType = 0;
                CHECK(msg->findInt32("payload-type", &payloadType));
                msg->findInt32("feedback-type", &feedbackType);

                sp<AMessage> notify = dupNotify();
                notify->setInt32("what", kWhatIMSRxNotice);
                notify->setMessage("message", msg);
                notify->post();

                ALOGV("IMSRxNotice \t\t payload : %d feedback : %d",
                      payloadType, feedbackType);
                break;
            }

            size_t trackIndex;
            CHECK(msg->findSize("trackIndex", &trackIndex));

            sp<ABuffer> accessUnit;
            if (msg->findBuffer("access-unit", &accessUnit) == false) {
                break;
            }

            int32_t damaged;
            if (accessUnit->meta()->findInt32("damaged", &damaged)
                    && damaged) {
                ALOGD("dropping damaged access unit.");
                break;
            }

            // Implicitly assert on valid trackIndex here, which we ensure by
            // never removing tracks.
            TrackInfo *info = &mTracks.editItemAt(trackIndex);

            sp<AnotherPacketSource> source = info->mSource;
            if (source != NULL) {
                uint32_t rtpTime;
                CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));

                /* AnotherPacketSource make an assertion if there is no ntp provided
                   RTPSource should provide ntpUs all the times.
                if (!info->mNPTMappingValid) {
                    // This is a live stream, we didn't receive any normal
                    // playtime mapping. We won't map to npt time.
                    source->queueAccessUnit(accessUnit);
                    break;
                }

                int64_t nptUs =
                    ((double)rtpTime - (double)info->mRTPTime)
                        / info->mTimeScale
                        * 1000000ll
                        + info->mNormalPlaytimeUs;

                */
                accessUnit->meta()->setInt64("timeUs", ALooper::GetNowUs());

                source->queueAccessUnit(accessUnit);
            }

            break;
        }
        case kWhatDisconnect:
        {
            sp<AReplyToken> replyID;
            CHECK(msg->senderAwaitsResponse(&replyID));

            for (size_t i = 0; i < mTracks.size(); ++i) {
                TrackInfo *info = &mTracks.editItemAt(i);

                if (info->mIsAudio) {
                    mAudioTrack->signalEOS(ERROR_END_OF_STREAM);
                    mAudioTrack = NULL;
                    ALOGV("mAudioTrack disconnected");
                } else {
                    mVideoTrack->signalEOS(ERROR_END_OF_STREAM);
                    mVideoTrack = NULL;
                    ALOGV("mVideoTrack disconnected");
                }

                mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
                close(info->mRTPSocket);
                close(info->mRTCPSocket);
            }

            mTracks.clear();
            mFirstAccessUnit = true;
            mAllTracksHaveTime = false;
            mNTPAnchorUs = -1;
            mMediaAnchorUs = -1;
            mLastMediaTimeUs = -1;
            mNumAccessUnitsReceived = 0;
            mReceivedFirstRTCPPacket = false;
            mReceivedFirstRTPPacket = false;
            mPausing = false;
            mPauseGeneration = 0;

            (new AMessage)->postReply(replyID);

            break;
        }
        case kWhatPollBuffering:
            break;
        default:
            TRESPASS();
    }
}

void NuPlayer::RTPSource::setTargetBitrate(int32_t bitrate) {
    mRTPConn->setTargetBitrate(bitrate);
}

void NuPlayer::RTPSource::onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
    ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = %#016llx",
         trackIndex, rtpTime, (long long)ntpTime);

    // convert ntpTime in Q32 seconds to microseconds. Note: this will not lose precision
    // because ntpTimeUs is at most 52 bits (double holds 53 bits)
    int64_t ntpTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));

    TrackInfo *track = &mTracks.editItemAt(trackIndex);

    track->mRTPAnchor = rtpTime;
    track->mNTPAnchorUs = ntpTimeUs;

    if (mNTPAnchorUs < 0) {
        mNTPAnchorUs = ntpTimeUs;
        mMediaAnchorUs = mLastMediaTimeUs;
    }

    if (!mAllTracksHaveTime) {
        bool allTracksHaveTime = (mTracks.size() > 0);
        for (size_t i = 0; i < mTracks.size(); ++i) {
            TrackInfo *track = &mTracks.editItemAt(i);
            if (track->mNTPAnchorUs < 0) {
                allTracksHaveTime = false;
                break;
            }
        }
        if (allTracksHaveTime) {
            mAllTracksHaveTime = true;
            ALOGI("Time now established for all tracks.");
        }
    }
    if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
        // Time is now established, lets start timestamping immediately
        for (size_t i = 0; i < mTracks.size(); ++i) {
            TrackInfo *trackInfo = &mTracks.editItemAt(i);
            while (!trackInfo->mPackets.empty()) {
                sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
                trackInfo->mPackets.erase(trackInfo->mPackets.begin());

                if (addMediaTimestamp(i, trackInfo, accessUnit)) {
                    postQueueAccessUnit(i, accessUnit);
                }
            }
        }
    }
}

bool NuPlayer::RTPSource::addMediaTimestamp(
        int32_t trackIndex, const TrackInfo *track,
        const sp<ABuffer> &accessUnit) {

    uint32_t rtpTime;
    CHECK(accessUnit->meta()->findInt32(
                "rtp-time", (int32_t *)&rtpTime));

    int64_t relRtpTimeUs =
        (((int64_t)rtpTime - (int64_t)track->mRTPAnchor) * 1000000ll)
        / track->mTimeScale;

    int64_t ntpTimeUs = track->mNTPAnchorUs + relRtpTimeUs;

    int64_t mediaTimeUs = mMediaAnchorUs + ntpTimeUs - mNTPAnchorUs;

    if (mediaTimeUs > mLastMediaTimeUs) {
        mLastMediaTimeUs = mediaTimeUs;
    }

    if (mediaTimeUs < 0) {
        ALOGV("dropping early accessUnit.");
        return false;
    }

    ALOGV("track %d rtpTime=%u mediaTimeUs = %lld us (%.2f secs)",
            trackIndex, rtpTime, (long long)mediaTimeUs, mediaTimeUs / 1E6);

    accessUnit->meta()->setInt64("timeUs", mediaTimeUs);

    return true;
}

bool NuPlayer::RTPSource::dataReceivedOnAllChannels() {
    TrackInfo *track;
    for (size_t i = 0; i < mTracks.size(); ++i) {
        track = &mTracks.editItemAt(i);
        if (track->mPackets.empty()) {
            return false;
        }
    }
    return true;
}

void NuPlayer::RTPSource::postQueueAccessUnit(
        size_t trackIndex, const sp<ABuffer> &accessUnit) {
    sp<AMessage> msg = new AMessage(kWhatAccessUnit, this);
    msg->setInt32("what", kWhatAccessUnit);
    msg->setSize("trackIndex", trackIndex);
    msg->setBuffer("accessUnit", accessUnit);
    msg->post();
}

void NuPlayer::RTPSource::postQueueEOS(size_t trackIndex, status_t finalResult) {
    sp<AMessage> msg = new AMessage(kWhatEOS, this);
    msg->setInt32("what", kWhatEOS);
    msg->setSize("trackIndex", trackIndex);
    msg->setInt32("finalResult", finalResult);
    msg->post();
}

sp<MetaData> NuPlayer::RTPSource::getTrackFormat(size_t index, int32_t *timeScale) {
    CHECK_GE(index, 0u);
    CHECK_LT(index, mTracks.size());

    const TrackInfo &info = mTracks.itemAt(index);

    *timeScale = info.mTimeScale;

    return info.mPacketSource->getFormat();
}

void NuPlayer::RTPSource::onConnected() {
    ALOGV("onConnected");
    mState = CONNECTED;
}

void NuPlayer::RTPSource::onDisconnected(const sp<AMessage> &msg) {
    if (mState == DISCONNECTED) {
        return;
    }

    status_t err;
    CHECK(msg->findInt32("result", &err));
    CHECK_NE(err, (status_t)OK);

//    mLooper->unregisterHandler(mHandler->id());
//    mHandler.clear();

    if (mState == CONNECTING) {
        // We're still in the preparation phase, signal that it
        // failed.
        notifyPrepared(err);
    }

    mState = DISCONNECTED;
//    setError(err);

}

status_t NuPlayer::RTPSource::setParameter(const String8 &key, const String8 &value) {
    ALOGV("setParameter: key (%s) => value (%s)", key.c_str(), value.c_str());

    bool isAudioKey = key.contains("audio");
    TrackInfo *info = NULL;
    for (unsigned i = 0; i < mTracks.size(); ++i) {
        info = &mTracks.editItemAt(i);
        if (info != NULL && info->mIsAudio == isAudioKey) {
            ALOGV("setParameter: %s track (%d) found", isAudioKey ? "audio" : "video" , i);
            break;
        }
    }

    if (info == NULL) {
        TrackInfo newTrackInfo;
        newTrackInfo.mIsAudio = isAudioKey;
        mTracks.push(newTrackInfo);
        info = &mTracks.editTop();
        info->mJbTimeMs = kStaticJitterTimeMs;
    }

    if (key == "rtp-param-mime-type") {
        info->mMimeType = value;

        const char *mime = value.c_str();
        const char *delimiter = strchr(mime, '/');
        info->mCodecName = delimiter ? (delimiter + 1) : "<none>";

        ALOGV("rtp-param-mime-type: mMimeType (%s) => mCodecName (%s)",
                info->mMimeType.c_str(), info->mCodecName.c_str());
    } else if (key == "video-param-decoder-profile") {
        info->mCodecProfile = atoi(value);
    } else if (key == "video-param-decoder-level") {
        info->mCodecLevel = atoi(value);
    } else if (key == "video-param-width") {
        info->mWidth = atoi(value);
    } else if (key == "video-param-height") {
        info->mHeight = atoi(value);
    } else if (key == "rtp-param-local-ip") {
        info->mLocalIp = value;
    } else if (key == "rtp-param-local-port") {
        info->mLocalPort = atoi(value);
    } else if (key == "rtp-param-remote-ip") {
        info->mRemoteIp = value;
    } else if (key == "rtp-param-remote-port") {
        info->mRemotePort = atoi(value);
    } else if (key == "rtp-param-payload-type") {
        info->mPayloadType = atoi(value);
    } else if (key == "rtp-param-as") {
        //AS means guaranteed bit rate that negotiated from sdp.
        info->mAS = atoi(value);
    } else if (key == "rtp-param-rtp-timeout") {
    } else if (key == "rtp-param-rtcp-timeout") {
    } else if (key == "rtp-param-time-scale") {
    } else if (key == "rtp-param-self-id") {
        info->mSelfID = atoi(value);
    } else if (key == "rtp-param-ext-cvo-extmap") {
        info->mCVOExtMap = atoi(value);
    } else if (key == "rtp-param-set-socket-network") {
        int64_t networkHandle = atoll(value);
        setSocketNetwork(networkHandle);
    } else if (key == "rtp-param-set-socket-ecn") {
        info->mRtpSockOptEcn = atoi(value);
    } else if (key == "rtp-param-jitter-buffer-time") {
        // clamping min at 40, max at 3000
        info->mJbTimeMs = std::min(std::max(40, atoi(value)), 3000);
    }

    return OK;
}

status_t NuPlayer::RTPSource::setParameters(const String8 &params) {
    ALOGV("setParameters: %s", params.c_str());
    const char *cparams = params.c_str();
    const char *key_start = cparams;
    for (;;) {
        const char *equal_pos = strchr(key_start, '=');
        if (equal_pos == NULL) {
            ALOGE("Parameters %s miss a value", cparams);
            return BAD_VALUE;
        }
        String8 key(key_start, equal_pos - key_start);
        TrimString(&key);
        if (key.length() == 0) {
            ALOGE("Parameters %s contains an empty key", cparams);
            return BAD_VALUE;
        }
        const char *value_start = equal_pos + 1;
        const char *semicolon_pos = strchr(value_start, ';');
        String8 value;
        if (semicolon_pos == NULL) {
            value = value_start;
        } else {
            value = String8(value_start, semicolon_pos - value_start);
        }
        if (setParameter(key, value) != OK) {
            return BAD_VALUE;
        }
        if (semicolon_pos == NULL) {
            break;  // Reaches the end
        }
        key_start = semicolon_pos + 1;
    }
    return OK;
}

void NuPlayer::RTPSource::setSocketNetwork(int64_t networkHandle) {
    ALOGV("setSocketNetwork: %llu", (unsigned long long)networkHandle);

    TrackInfo *info = NULL;
    for (size_t i = 0; i < mTracks.size(); ++i) {
        info = &mTracks.editItemAt(i);

        if (info == NULL)
            break;

        info->mSocketNetwork = networkHandle;
    }
}

// Trim both leading and trailing whitespace from the given string.
//static
void NuPlayer::RTPSource::TrimString(String8 *s) {
    size_t num_bytes = s->bytes();
    const char *data = s->c_str();

    size_t leading_space = 0;
    while (leading_space < num_bytes && isspace(data[leading_space])) {
        ++leading_space;
    }

    size_t i = num_bytes;
    while (i > leading_space && isspace(data[i - 1])) {
        --i;
    }

    *s = String8(&data[leading_space], i - leading_space);
}

}  // namespace android
