blob: 9bb4cb77adaafab1c52b8a730cb047712ac93db7 [file] [log] [blame]
Byeongjo Parkd157b792019-01-24 20:56:37 +09001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//#define LOG_NDEBUG 0
18#define LOG_TAG "RTPSource"
19#include <utils/Log.h>
20
21#include "RTPSource.h"
22
23
24
25
26#include <media/stagefright/MediaDefs.h>
27#include <media/stagefright/MetaData.h>
28#include <string.h>
29
30namespace android {
31
32const int64_t kNearEOSTimeoutUs = 2000000ll; // 2 secs
33static int32_t kMaxAllowedStaleAccessUnits = 20;
34
35NuPlayer::RTPSource::RTPSource(
36 const sp<AMessage> &notify,
37 const String8& rtpParams)
38 : Source(notify),
39 mRTPParams(rtpParams),
40 mFlags(0),
41 mState(DISCONNECTED),
42 mFinalResult(OK),
43 mBuffering(false),
44 mInPreparationPhase(true),
45 mRTPConn(new ARTPConnection),
46 mEOSTimeoutAudio(0),
47 mEOSTimeoutVideo(0) {
48 ALOGD("RTPSource initialized with rtpParams=%s", rtpParams.string());
49}
50
51NuPlayer::RTPSource::~RTPSource() {
52 if (mLooper != NULL) {
53 mLooper->unregisterHandler(id());
54 mLooper->unregisterHandler(mRTPConn->id());
55 mLooper->stop();
56 }
57}
58
59status_t NuPlayer::RTPSource::getBufferingSettings(
60 BufferingSettings* buffering /* nonnull */) {
61 Mutex::Autolock _l(mBufferingSettingsLock);
62 *buffering = mBufferingSettings;
63 return OK;
64}
65
66status_t NuPlayer::RTPSource::setBufferingSettings(const BufferingSettings& buffering) {
67 Mutex::Autolock _l(mBufferingSettingsLock);
68 mBufferingSettings = buffering;
69 return OK;
70}
71
72void NuPlayer::RTPSource::prepareAsync() {
73 if (mLooper == NULL) {
74 mLooper = new ALooper;
75 mLooper->setName("rtp");
76 mLooper->start();
77
78 mLooper->registerHandler(this);
79 mLooper->registerHandler(mRTPConn);
80 }
81
82 setParameters(mRTPParams);
83
84 TrackInfo *info = NULL;
85 unsigned i;
86 for (i = 0; i < mTracks.size(); i++) {
87 info = &mTracks.editItemAt(i);
88
89 if (info == NULL)
90 break;
91
92 AString sdp;
93 ASessionDescription::SDPStringFactory(sdp, info->mLocalIp,
94 info->mIsAudio, info->mLocalPort, info->mPayloadType, info->mAS, info->mCodecName,
95 NULL, info->mWidth, info->mHeight);
96 ALOGD("RTPSource SDP =>\n%s", sdp.c_str());
97
98 sp<ASessionDescription> desc = new ASessionDescription;
99 bool isValidSdp = desc->setTo(sdp.c_str(), sdp.size());
100 ALOGV("RTPSource isValidSdp => %d", isValidSdp);
101
102 int sockRtp, sockRtcp;
103 ARTPConnection::MakeRTPSocketPair(&sockRtp, &sockRtcp, info->mLocalIp, info->mRemoteIp,
104 info->mLocalPort, info->mRemotePort);
105
106 sp<AMessage> notify = new AMessage('accu', this);
107
108 ALOGV("RTPSource addStream. track-index=%d", i);
109 notify->setSize("trackIndex", i);
110 // index(i) should be started from 1. 0 is reserved for [root]
111 mRTPConn->addStream(sockRtp, sockRtcp, desc, i + 1, notify, false);
112
113 info->mRTPSocket = sockRtp;
114 info->mRTCPSocket = sockRtcp;
115 info->mFirstSeqNumInSegment = 0;
116 info->mNewSegment = true;
117 info->mAllowedStaleAccessUnits = kMaxAllowedStaleAccessUnits;
118 info->mRTPAnchor = 0;
119 info->mNTPAnchorUs = -1;
120 info->mNormalPlayTimeRTP = 0;
121 info->mNormalPlayTimeUs = 0ll;
122
123 // index(i) should be started from 1. 0 is reserved for [root]
124 info->mPacketSource = new APacketSource(desc, i + 1);
125
126 int32_t timeScale;
127 sp<MetaData> format = getTrackFormat(i, &timeScale);
128 sp<AnotherPacketSource> source = new AnotherPacketSource(format);
129
130 if (info->mIsAudio) {
131 mAudioTrack = source;
132 } else {
133 mVideoTrack = source;
134 }
135
136 info->mSource = source;
137 }
138
139 CHECK_EQ(mState, (int)DISCONNECTED);
140 mState = CONNECTING;
141
142 if (mInPreparationPhase) {
143 mInPreparationPhase = false;
144 notifyPrepared();
145 }
146}
147
148void NuPlayer::RTPSource::start() {
149}
150
151void NuPlayer::RTPSource::pause() {
152 mState = PAUSED;
153}
154
155void NuPlayer::RTPSource::resume() {
156 mState = CONNECTING;
157}
158
159void NuPlayer::RTPSource::stop() {
160 if (mLooper == NULL) {
161 return;
162 }
163 sp<AMessage> msg = new AMessage(kWhatDisconnect, this);
164
165 sp<AMessage> dummy;
166 msg->postAndAwaitResponse(&dummy);
167}
168
169status_t NuPlayer::RTPSource::feedMoreTSData() {
170 Mutex::Autolock _l(mBufferingLock);
171 return mFinalResult;
172}
173
174sp<MetaData> NuPlayer::RTPSource::getFormatMeta(bool audio) {
175 sp<AnotherPacketSource> source = getSource(audio);
176
177 if (source == NULL) {
178 return NULL;
179 }
180
181 return source->getFormat();
182}
183
184bool NuPlayer::RTPSource::haveSufficientDataOnAllTracks() {
185 // We're going to buffer at least 2 secs worth data on all tracks before
186 // starting playback (both at startup and after a seek).
187
188 static const int64_t kMinDurationUs = 2000000ll;
189
190 int64_t mediaDurationUs = 0;
191 getDuration(&mediaDurationUs);
192 if ((mAudioTrack != NULL && mAudioTrack->isFinished(mediaDurationUs))
193 || (mVideoTrack != NULL && mVideoTrack->isFinished(mediaDurationUs))) {
194 return true;
195 }
196
197 status_t err;
198 int64_t durationUs;
199 if (mAudioTrack != NULL
200 && (durationUs = mAudioTrack->getBufferedDurationUs(&err))
201 < kMinDurationUs
202 && err == OK) {
203 ALOGV("audio track doesn't have enough data yet. (%.2f secs buffered)",
204 durationUs / 1E6);
205 return false;
206 }
207
208 if (mVideoTrack != NULL
209 && (durationUs = mVideoTrack->getBufferedDurationUs(&err))
210 < kMinDurationUs
211 && err == OK) {
212 ALOGV("video track doesn't have enough data yet. (%.2f secs buffered)",
213 durationUs / 1E6);
214 return false;
215 }
216
217 return true;
218}
219
220status_t NuPlayer::RTPSource::dequeueAccessUnit(
221 bool audio, sp<ABuffer> *accessUnit) {
222
223 sp<AnotherPacketSource> source = getSource(audio);
224
225 if (mState == PAUSED) {
226 ALOGV("-EWOULDBLOCK");
227 return -EWOULDBLOCK;
228 }
229
230 status_t finalResult;
231 if (!source->hasBufferAvailable(&finalResult)) {
232 if (finalResult == OK) {
233 int64_t mediaDurationUs = 0;
234 getDuration(&mediaDurationUs);
235 sp<AnotherPacketSource> otherSource = getSource(!audio);
236 status_t otherFinalResult;
237
238 // If other source already signaled EOS, this source should also signal EOS
239 if (otherSource != NULL &&
240 !otherSource->hasBufferAvailable(&otherFinalResult) &&
241 otherFinalResult == ERROR_END_OF_STREAM) {
242 source->signalEOS(ERROR_END_OF_STREAM);
243 return ERROR_END_OF_STREAM;
244 }
245
246 // If this source has detected near end, give it some time to retrieve more
247 // data before signaling EOS
248 if (source->isFinished(mediaDurationUs)) {
249 int64_t eosTimeout = audio ? mEOSTimeoutAudio : mEOSTimeoutVideo;
250 if (eosTimeout == 0) {
251 setEOSTimeout(audio, ALooper::GetNowUs());
252 } else if ((ALooper::GetNowUs() - eosTimeout) > kNearEOSTimeoutUs) {
253 setEOSTimeout(audio, 0);
254 source->signalEOS(ERROR_END_OF_STREAM);
255 return ERROR_END_OF_STREAM;
256 }
257 return -EWOULDBLOCK;
258 }
259
260 if (!(otherSource != NULL && otherSource->isFinished(mediaDurationUs))) {
261 // We should not enter buffering mode
262 // if any of the sources already have detected EOS.
263 // TODO: needs to be checked whether below line is needed or not.
264 // startBufferingIfNecessary();
265 }
266
267 return -EWOULDBLOCK;
268 }
269 return finalResult;
270 }
271
272 setEOSTimeout(audio, 0);
273
274 return source->dequeueAccessUnit(accessUnit);
275}
276
277sp<AnotherPacketSource> NuPlayer::RTPSource::getSource(bool audio) {
278 return audio ? mAudioTrack : mVideoTrack;
279}
280
281void NuPlayer::RTPSource::setEOSTimeout(bool audio, int64_t timeout) {
282 if (audio) {
283 mEOSTimeoutAudio = timeout;
284 } else {
285 mEOSTimeoutVideo = timeout;
286 }
287}
288
289status_t NuPlayer::RTPSource::getDuration(int64_t *durationUs) {
290 *durationUs = 0ll;
291
292 int64_t audioDurationUs;
293 if (mAudioTrack != NULL
294 && mAudioTrack->getFormat()->findInt64(
295 kKeyDuration, &audioDurationUs)
296 && audioDurationUs > *durationUs) {
297 *durationUs = audioDurationUs;
298 }
299
300 int64_t videoDurationUs;
301 if (mVideoTrack != NULL
302 && mVideoTrack->getFormat()->findInt64(
303 kKeyDuration, &videoDurationUs)
304 && videoDurationUs > *durationUs) {
305 *durationUs = videoDurationUs;
306 }
307
308 return OK;
309}
310
311status_t NuPlayer::RTPSource::seekTo(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
312 ALOGV("RTPSource::seekTo=%d, mode=%d", (int)seekTimeUs, mode);
313 return OK;
314}
315
316void NuPlayer::RTPSource::schedulePollBuffering() {
317 sp<AMessage> msg = new AMessage(kWhatPollBuffering, this);
318 msg->post(1000000ll); // 1 second intervals
319}
320
321void NuPlayer::RTPSource::onPollBuffering() {
322 schedulePollBuffering();
323}
324
325void NuPlayer::RTPSource::onMessageReceived(const sp<AMessage> &msg) {
326 ALOGV("onMessageReceived =%d", msg->what());
327
328 switch (msg->what()) {
329 case kWhatAccessUnitComplete:
330 {
331 if (mState == CONNECTING) {
332 mState = CONNECTED;
333 }
334
335 int32_t timeUpdate;
336 //"time-update" raised from ARTPConnection::parseSR()
337 if (msg->findInt32("time-update", &timeUpdate) && timeUpdate) {
338 size_t trackIndex;
339 CHECK(msg->findSize("trackIndex", &trackIndex));
340
341 uint32_t rtpTime;
342 uint64_t ntpTime;
343 CHECK(msg->findInt32("rtp-time", (int32_t *)&rtpTime));
344 CHECK(msg->findInt64("ntp-time", (int64_t *)&ntpTime));
345
346 onTimeUpdate(trackIndex, rtpTime, ntpTime);
347 break;
348 }
349
350 int32_t firstRTCP;
351 if (msg->findInt32("first-rtcp", &firstRTCP)) {
352 // There won't be an access unit here, it's just a notification
353 // that the data communication worked since we got the first
354 // rtcp packet.
355 ALOGV("first-rtcp");
356 break;
357 }
358
Kim Sungyeond3c6b322018-03-02 14:41:19 +0900359 int32_t IMSRxNotice;
360 if (msg->findInt32("IMS-Rx-notice", &IMSRxNotice)) {
361 int32_t payloadType, feedbackType;
362 CHECK(msg->findInt32("payload-type", &payloadType));
363 CHECK(msg->findInt32("feedback-type", &feedbackType));
364
365 sp<AMessage> notify = dupNotify();
366 notify->setInt32("what", kWhatIMSRxNotice);
367 notify->setMessage("message", msg);
368 notify->post();
369
370 ALOGV("IMSRxNotice \t\t payload : %d feedback : %d",
371 payloadType, feedbackType);
372 break;
373 }
374
Byeongjo Parkd157b792019-01-24 20:56:37 +0900375 size_t trackIndex;
376 CHECK(msg->findSize("trackIndex", &trackIndex));
377
378 sp<ABuffer> accessUnit;
379 if (msg->findBuffer("access-unit", &accessUnit) == false) {
380 break;
381 }
382
383 int32_t damaged;
384 if (accessUnit->meta()->findInt32("damaged", &damaged)
385 && damaged) {
386 ALOGD("dropping damaged access unit.");
387 break;
388 }
389
390 TrackInfo *info = &mTracks.editItemAt(trackIndex);
391
392 sp<AnotherPacketSource> source = info->mSource;
393 if (source != NULL) {
394 uint32_t rtpTime;
395 CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
396
397 /* AnotherPacketSource make an assertion if there is no ntp provided
398 RTPSource should provide ntpUs all the times.
399 if (!info->mNPTMappingValid) {
400 // This is a live stream, we didn't receive any normal
401 // playtime mapping. We won't map to npt time.
402 source->queueAccessUnit(accessUnit);
403 break;
404 }
405 */
406
407 int64_t nptUs =
408 ((double)rtpTime - (double)info->mRTPTime)
409 / info->mTimeScale
410 * 1000000ll
411 + info->mNormalPlaytimeUs;
412
413 accessUnit->meta()->setInt64("timeUs", nptUs);
414
415 source->queueAccessUnit(accessUnit);
416 }
417
418 break;
419 }
420 case kWhatDisconnect:
421 {
422 sp<AReplyToken> replyID;
423 CHECK(msg->senderAwaitsResponse(&replyID));
424
425 for (size_t i = 0; i < mTracks.size(); ++i) {
426 TrackInfo *info = &mTracks.editItemAt(i);
427
428 if (info->mIsAudio) {
429 mAudioTrack->signalEOS(ERROR_END_OF_STREAM);
430 mAudioTrack = NULL;
431 ALOGV("mAudioTrack disconnected");
432 } else {
433 mVideoTrack->signalEOS(ERROR_END_OF_STREAM);
434 mVideoTrack = NULL;
435 ALOGV("mVideoTrack disconnected");
436 }
437
438 mRTPConn->removeStream(info->mRTPSocket, info->mRTCPSocket);
439 close(info->mRTPSocket);
440 close(info->mRTCPSocket);
441 }
442
443 mTracks.clear();
444 mFirstAccessUnit = true;
445 mAllTracksHaveTime = false;
446 mNTPAnchorUs = -1;
447 mMediaAnchorUs = -1;
448 mLastMediaTimeUs = -1;
449 mNumAccessUnitsReceived = 0;
450 mReceivedFirstRTCPPacket = false;
451 mReceivedFirstRTPPacket = false;
452 mPausing = false;
453 mPauseGeneration = 0;
454
455 (new AMessage)->postReply(replyID);
456
457 break;
458 }
459 case kWhatPollBuffering:
460 break;
461 default:
462 TRESPASS();
463 }
464}
465
466void NuPlayer::RTPSource::onTimeUpdate(int32_t trackIndex, uint32_t rtpTime, uint64_t ntpTime) {
467 ALOGV("onTimeUpdate track %d, rtpTime = 0x%08x, ntpTime = %#016llx",
468 trackIndex, rtpTime, (long long)ntpTime);
469
470 int64_t ntpTimeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
471
472 TrackInfo *track = &mTracks.editItemAt(trackIndex);
473
474 track->mRTPAnchor = rtpTime;
475 track->mNTPAnchorUs = ntpTimeUs;
476
477 if (mNTPAnchorUs < 0) {
478 mNTPAnchorUs = ntpTimeUs;
479 mMediaAnchorUs = mLastMediaTimeUs;
480 }
481
482 if (!mAllTracksHaveTime) {
483 bool allTracksHaveTime = (mTracks.size() > 0);
484 for (size_t i = 0; i < mTracks.size(); ++i) {
485 TrackInfo *track = &mTracks.editItemAt(i);
486 if (track->mNTPAnchorUs < 0) {
487 allTracksHaveTime = false;
488 break;
489 }
490 }
491 if (allTracksHaveTime) {
492 mAllTracksHaveTime = true;
493 ALOGI("Time now established for all tracks.");
494 }
495 }
496 if (mAllTracksHaveTime && dataReceivedOnAllChannels()) {
497 // Time is now established, lets start timestamping immediately
498 for (size_t i = 0; i < mTracks.size(); ++i) {
499 TrackInfo *trackInfo = &mTracks.editItemAt(i);
500 while (!trackInfo->mPackets.empty()) {
501 sp<ABuffer> accessUnit = *trackInfo->mPackets.begin();
502 trackInfo->mPackets.erase(trackInfo->mPackets.begin());
503
504 if (addMediaTimestamp(i, trackInfo, accessUnit)) {
505 postQueueAccessUnit(i, accessUnit);
506 }
507 }
508 }
509 }
510}
511
512bool NuPlayer::RTPSource::addMediaTimestamp(
513 int32_t trackIndex, const TrackInfo *track,
514 const sp<ABuffer> &accessUnit) {
515
516 uint32_t rtpTime;
517 CHECK(accessUnit->meta()->findInt32(
518 "rtp-time", (int32_t *)&rtpTime));
519
520 int64_t relRtpTimeUs =
521 (((int64_t)rtpTime - (int64_t)track->mRTPAnchor) * 1000000ll)
522 / track->mTimeScale;
523
524 int64_t ntpTimeUs = track->mNTPAnchorUs + relRtpTimeUs;
525
526 int64_t mediaTimeUs = mMediaAnchorUs + ntpTimeUs - mNTPAnchorUs;
527
528 if (mediaTimeUs > mLastMediaTimeUs) {
529 mLastMediaTimeUs = mediaTimeUs;
530 }
531
532 if (mediaTimeUs < 0) {
533 ALOGV("dropping early accessUnit.");
534 return false;
535 }
536
537 ALOGV("track %d rtpTime=%u mediaTimeUs = %lld us (%.2f secs)",
538 trackIndex, rtpTime, (long long)mediaTimeUs, mediaTimeUs / 1E6);
539
540 accessUnit->meta()->setInt64("timeUs", mediaTimeUs);
541
542 return true;
543}
544
545bool NuPlayer::RTPSource::dataReceivedOnAllChannels() {
546 TrackInfo *track;
547 for (size_t i = 0; i < mTracks.size(); ++i) {
548 track = &mTracks.editItemAt(i);
549 if (track->mPackets.empty()) {
550 return false;
551 }
552 }
553 return true;
554}
555
556void NuPlayer::RTPSource::postQueueAccessUnit(
557 size_t trackIndex, const sp<ABuffer> &accessUnit) {
558 sp<AMessage> msg = new AMessage(kWhatAccessUnit, this);
559 msg->setInt32("what", kWhatAccessUnit);
560 msg->setSize("trackIndex", trackIndex);
561 msg->setBuffer("accessUnit", accessUnit);
562 msg->post();
563}
564
565void NuPlayer::RTPSource::postQueueEOS(size_t trackIndex, status_t finalResult) {
566 sp<AMessage> msg = new AMessage(kWhatEOS, this);
567 msg->setInt32("what", kWhatEOS);
568 msg->setSize("trackIndex", trackIndex);
569 msg->setInt32("finalResult", finalResult);
570 msg->post();
571}
572
573sp<MetaData> NuPlayer::RTPSource::getTrackFormat(size_t index, int32_t *timeScale) {
574 CHECK_GE(index, 0u);
575 CHECK_LT(index, mTracks.size());
576
577 const TrackInfo &info = mTracks.itemAt(index);
578
579 *timeScale = info.mTimeScale;
580
581 return info.mPacketSource->getFormat();
582}
583
584void NuPlayer::RTPSource::onConnected() {
585 ALOGV("onConnected");
586 mState = CONNECTED;
587}
588
589void NuPlayer::RTPSource::onDisconnected(const sp<AMessage> &msg) {
590 if (mState == DISCONNECTED) {
591 return;
592 }
593
594 status_t err;
595 CHECK(msg->findInt32("result", &err));
596 CHECK_NE(err, (status_t)OK);
597
598// mLooper->unregisterHandler(mHandler->id());
599// mHandler.clear();
600
601 if (mState == CONNECTING) {
602 // We're still in the preparation phase, signal that it
603 // failed.
604 notifyPrepared(err);
605 }
606
607 mState = DISCONNECTED;
608// setError(err);
609
610}
611
612status_t NuPlayer::RTPSource::setParameter(const String8 &key, const String8 &value) {
613 ALOGV("setParameter: key (%s) => value (%s)", key.string(), value.string());
614
615 bool isAudioKey = key.contains("audio");
616 TrackInfo *info = NULL;
617 for (unsigned i = 0; i < mTracks.size(); ++i) {
618 info = &mTracks.editItemAt(i);
619 if (info != NULL && info->mIsAudio == isAudioKey) {
620 ALOGV("setParameter: %s track (%d) found", isAudioKey ? "audio" : "video" , i);
621 break;
622 }
623 }
624
625 if (info == NULL) {
626 TrackInfo newTrackInfo;
627 newTrackInfo.mIsAudio = isAudioKey;
628 mTracks.push(newTrackInfo);
629 info = &mTracks.editTop();
630 }
631
632 if (key == "rtp-param-mime-type") {
633 info->mMimeType = value;
634
635 const char *mime = value.string();
636 const char *delimiter = strchr(mime, '/');
637 info->mCodecName = (delimiter + 1);
638
639 ALOGV("rtp-param-mime-type: mMimeType (%s) => mCodecName (%s)",
640 info->mMimeType.string(), info->mCodecName.string());
641 } else if (key == "video-param-decoder-profile") {
642 info->mCodecProfile = atoi(value);
643 } else if (key == "video-param-decoder-level") {
644 info->mCodecLevel = atoi(value);
645 } else if (key == "video-param-width") {
646 info->mWidth = atoi(value);
647 } else if (key == "video-param-height") {
648 info->mHeight = atoi(value);
649 } else if (key == "rtp-param-local-ip") {
650 info->mLocalIp = value;
651 } else if (key == "rtp-param-local-port") {
652 info->mLocalPort = atoi(value);
653 } else if (key == "rtp-param-remote-ip") {
654 info->mRemoteIp = value;
655 } else if (key == "rtp-param-remote-port") {
656 info->mRemotePort = atoi(value);
657 } else if (key == "rtp-param-payload-type") {
658 info->mPayloadType = atoi(value);
659 } else if (key == "rtp-param-as") {
660 //AS means guaranteed bit rate that negotiated from sdp.
661 info->mAS = atoi(value);
662 } else if (key == "rtp-param-rtp-timeout") {
663 } else if (key == "rtp-param-rtcp-timeout") {
664 } else if (key == "rtp-param-time-scale") {
665 }
666
667 return OK;
668}
669
670status_t NuPlayer::RTPSource::setParameters(const String8 &params) {
671 ALOGV("setParameters: %s", params.string());
672 const char *cparams = params.string();
673 const char *key_start = cparams;
674 for (;;) {
675 const char *equal_pos = strchr(key_start, '=');
676 if (equal_pos == NULL) {
677 ALOGE("Parameters %s miss a value", cparams);
678 return BAD_VALUE;
679 }
680 String8 key(key_start, equal_pos - key_start);
681 TrimString(&key);
682 if (key.length() == 0) {
683 ALOGE("Parameters %s contains an empty key", cparams);
684 return BAD_VALUE;
685 }
686 const char *value_start = equal_pos + 1;
687 const char *semicolon_pos = strchr(value_start, ';');
688 String8 value;
689 if (semicolon_pos == NULL) {
690 value.setTo(value_start);
691 } else {
692 value.setTo(value_start, semicolon_pos - value_start);
693 }
694 if (setParameter(key, value) != OK) {
695 return BAD_VALUE;
696 }
697 if (semicolon_pos == NULL) {
698 break; // Reaches the end
699 }
700 key_start = semicolon_pos + 1;
701 }
702 return OK;
703}
704
705// Trim both leading and trailing whitespace from the given string.
706//static
707void NuPlayer::RTPSource::TrimString(String8 *s) {
708 size_t num_bytes = s->bytes();
709 const char *data = s->string();
710
711 size_t leading_space = 0;
712 while (leading_space < num_bytes && isspace(data[leading_space])) {
713 ++leading_space;
714 }
715
716 size_t i = num_bytes;
717 while (i > leading_space && isspace(data[i - 1])) {
718 --i;
719 }
720
721 s->setTo(String8(&data[leading_space], i - leading_space));
722}
723
724} // namespace android