SDP.cpp

00001 /*********************************************************************** 00002 * * 00003 * ViTooKi * 00004 * * 00005 * title: SDP.cpp * 00006 * * 00007 * * 00008 * * 00009 * ITEC institute of the University of Klagenfurt (Austria) * 00010 * http://www.itec.uni-klu.ac.at * 00011 * * 00012 * * 00013 * For more information visit the ViTooKi homepage: * 00014 * http://ViTooKi.sourceforge.net * 00015 * vitooki-user@lists.sourceforge.net * 00016 * vitooki-devel@lists.sourceforge.net * 00017 * * 00018 * This file is part of ViTooKi, a free video toolkit. * 00019 * ViTooKi is free software; you can redistribute it and/or * 00020 * modify it under the terms of the GNU General Public License * 00021 * as published by the Free Software Foundation; either version 2 * 00022 * of the License, or (at your option) any later version. * 00023 * * 00024 * This program is distributed in the hope that it will be useful, * 00025 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00026 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00027 * GNU General Public License for more details. * 00028 * * 00029 * You should have received a copy of the GNU General Public License * 00030 * along with this program; if not, write to the Free Software * 00031 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, * 00032 * MA 02111-1307, USA. * 00033 * * 00034 ***********************************************************************/ 00035 00036 /*********************************************************************** 00037 * * 00038 * REVISION HISTORY: * 00039 * * 00040 * * 00041 * * 00042 ***********************************************************************/ 00043 00044 #include <time.h> 00045 #ifdef WIN32 00046 #ifndef WINCE 00047 #include <sys/timeb.h> 00048 #endif 00049 #else 00050 #include <sys/time.h> 00051 #endif 00052 00053 /* included from common-UCL RTP for IOD parsing */ 00054 #ifdef WINCE 00055 #include "3rdparty/commonucl/base64.h" 00056 #else 00057 #include "base64.h" 00058 #endif 00059 00060 #include "SDP.hpp" 00061 #include "ContainerInfo.hpp" 00062 #include "VideoESInfo.hpp" 00063 #include "adaptors/MP4Encoder.hpp" 00064 #include "../metadata/TerminalCapabilities.hpp" 00065 00066 00067 SDP::~SDP() 00068 { 00069 clear(); 00070 } 00071 00072 void SDP::clear() 00073 { 00074 list<struct session *>::iterator it = sessionList.begin(); 00075 while (it != sessionList.end()) { 00076 delete *it; 00077 it = sessionList.erase(it); 00078 } 00079 } 00080 00081 #define SDP_BUF_SIZE 4096 00082 00083 bool SDP::parse(const char *sdp) 00084 { 00085 char *buf = new char[SDP_BUF_SIZE]; 00086 int bufsize = strlen(sdp)+1; 00087 if (bufsize > SDP_BUF_SIZE) { 00088 delete [] buf; 00089 return false; 00090 } 00091 strcpy(buf, sdp); 00092 struct session *sp = NULL; 00093 bool err = false; 00094 bool valid_version = false; 00095 char *bufend = buf + strlen(buf); // points to terminating '\0' 00096 char *p = buf; 00097 char *field, *value; 00098 do { 00099 /* ignore empty lines */ 00100 while (p < bufend && (*p == '\n' || *p == '\r')) 00101 p++; 00102 if (p >= bufend) 00103 break; 00104 field = p; 00105 if (++p >= bufend || *p != '=') { 00106 err = true; 00107 break; 00108 } 00109 /* value is anything up to the end of line or buffer */ 00110 value = ++p; 00111 while (p < bufend && *p != '\n' && *p != '\r') 00112 p++; 00113 *p++ = '\0'; 00114 switch (*field) { 00115 case 'v': 00116 if (strcmp(value, "0") == 0) 00117 valid_version = true; 00118 else 00119 err = true; 00120 break; 00121 case 's': 00122 sp = new struct session; 00123 sp->name.assign(value); 00124 sessionList.push_back(sp); 00125 break; 00126 case 'a': 00127 if (sp) { 00128 if (strncmp(value, "control:", 8) == 0) { 00129 sp->url.assign(value + 8); 00130 } else if (strncmp(value, "range:", 6) == 0) { 00131 char *p1 = strstr(value + 6, "npt="); 00132 if (p1) { 00133 p1 += 4; 00134 char *p2 = strchr(p1, '-'); 00135 if (p2) { 00136 *p2++ = '\0'; 00137 if (strlen(p2) > 0 && sscanf(p2, "%lf", &sp->nptEnd) != 1) 00138 err = true; 00139 } 00140 if (strlen(p1) > 0 && sscanf(p1, "%lf", &sp->nptStart) != 1) 00141 err = true; 00142 } 00143 } 00144 } 00145 break; 00146 } // switch 00147 } while (p < bufend); 00148 if (err || !valid_version) { 00149 clear(); 00150 delete [] buf; 00151 return false; 00152 } 00153 #if VITOOKI_DEBUG_LEVEL >= VITOOKI_DEBUG_FULL 00154 dprintf_full("SDP::parse() parsed %d session description(s):\n", sessionList.size()); 00155 for (list<struct session *>::iterator it = sessionList.begin(); 00156 it != sessionList.end(); it++) { 00157 dprintf_full(" name = %s, url = %s, nptStart = %lf, nptEnd = %lf\n", 00158 (*it)->name.c_str(), (*it)->url.c_str(), 00159 (*it)->nptStart, (*it)->nptEnd); 00160 } 00161 #endif 00162 delete [] buf; 00163 return true; 00164 } 00165 00166 const struct SDP::session *SDP::firstSession() const 00167 { 00168 return sessionList.empty() ? NULL : sessionList.front(); 00169 } 00170 00171 char *SDP::generate(ContainerInfo* stream, bool perfectHit, 00172 const char *filename, const char *localAddr, 00173 int SL_PAYLOAD_TYPE, int sizeLength, 00174 int dtsDeltaLength, const TerminalCapabilities* tc, 00175 bool includeRtxInfo,list<rtx_group*>* groups) 00176 { 00177 00178 // mslc.SizeLength + mslc.DTSDeltaLength replaced by 00179 // sizeLength + dtsDeltaLength 00180 assert(stream!=NULL); 00181 char *buffer = new char[MSG_BUFFER_SIZE]; 00182 buffer[0] = 0; 00183 char *next = buffer; 00184 int cnt = 0; 00185 //disable rtx when rtxconfig is missing 00186 if(groups==NULL) 00187 includeRtxInfo = false; 00188 00189 #if defined WIN32 && !(defined WINCE) 00190 struct timeb tv; 00191 ftime(&tv); 00192 cnt = 00193 sprintf(next, "v=0\r\no=%s %i %i IN IP4 %s\r\ns=%s\r\nu=http:///\r\ne=admin@\r\n",SERVER_ID, 00194 (int) tv.time, (int) tv.millitm, localAddr, filename); 00195 #else 00196 timeval tv; 00197 gettimeofday(&tv, 0); 00198 cnt = 00199 sprintf(next, "v=0\r\no=%s %i %i IN IP4 %s\r\ns=%s\r\nu=http:///\r\ne=admin@\r\n",SERVER_ID, 00200 (int) tv.tv_sec, (int) tv.tv_usec, localAddr, filename); 00201 #endif 00202 00203 next += cnt; 00204 list <ESInfo*> * es = stream->getESList(); 00205 list < ESInfo * >::const_iterator li = es->begin(); 00206 float duration = 0; 00207 u32 bitRateInKilobits = 0; 00208 00209 while (li != es->end()) { 00210 if (duration < ((float) (*li)->getDurationInMs() / 1000.0)) { 00211 duration = ((float) (*li)->getDurationInMs() / 1000.0); 00212 } 00213 if ((*li)->isAudioStream() || (*li)->isVisualStream()) { 00214 00215 bitRateInKilobits += ((*li)->getAvgBandwidth() / 1024); 00216 } 00217 ++li; 00218 } 00219 if(tc) { 00220 u32 bw = tc->getNetworkCapacityInByte()*8; 00221 if(bw==0) 00222 bw = tc->getMaxDecoderBitRateInBit(); 00223 else if(tc->getMaxDecoderBitRateInBit() && 00224 tc->getMaxDecoderBitRateInBit() < bw) { 00225 bw = tc->getMaxDecoderBitRateInBit(); 00226 } 00227 if((bw/1024)<bitRateInKilobits) 00228 bitRateInKilobits=bw; 00229 } 00230 /* 00231 Connection Data 00232 00233 c=<network type> <address type> <connection address> 00234 00235 The "c=" field contains connection data. 00236 00237 A session announcement must contain one "c=" field in each media 00238 description (see below) or a "c=" field at the session-level. 00239 */ 00240 cnt = sprintf(next, "c=IN IP4 %s\r\nb=AS:%i\r\nt=0 0\r\n", localAddr, 00241 bitRateInKilobits); 00242 // cnt = sprintf(next, "c=IN IP4 0.0.0.0\r\nb=AS:%i\r\nt=0 0\r\n",bitRateInKilobits); 00243 next += cnt; 00244 00245 char *iodstr = new char[MAX_IOD_SIZE]; 00246 iodstr[0] = 0; 00247 u32 iodHandleSize=0; 00248 00249 #ifndef WINCE 00250 int iodenclen = base64encode(stream->getIODHandle(iodHandleSize), iodHandleSize, 00251 (unsigned char*)iodstr, MAX_IOD_SIZE); 00252 #else 00253 int iodenclen = 0; 00254 #endif 00255 00256 if(iodenclen<0 || iodenclen>=MAX_IOD_SIZE) 00257 iodenclen=0; 00258 iodstr[iodenclen] = 0; 00259 int streamcnt = es->size(); // #streams is without iod 00260 00261 li = es->begin(); //a=control:*\r\n 00262 cnt = sprintf(next, "a=control:*\r\na=mpeg4-iod:\"data:application/mpeg4-iod;base64,%s\"\r\n", 00263 iodstr); 00264 next += cnt; 00265 00266 // 00267 cnt = sprintf(next, "a=isma-compliance:1,1.0,1\r\na=range:npt=0-%5.5f\r\n", 00268 duration); 00269 next += cnt; 00270 00271 if(includeRtxInfo && !es->empty()) { 00272 // write the grouping infos 00273 list < ESInfo*>::const_iterator ei=es->begin(); 00274 u32 lastMid=1; 00275 while(ei!=es->end()) { 00276 if((*ei)->isAudioStream() || 00277 (*ei)->isVisualStream() ) { 00278 rtx_group* group=stream->getRtxGroup( (*ei)->getStreamId(),groups); 00279 if(!group) { 00280 // no entry found, generate one 00281 group=new rtx_group(NACK,3000); 00282 group->midESInfo=lastMid++; 00283 group->rtx->mId=lastMid++; 00284 group->es=(*ei); 00285 group->rtx->ticks=(*ei)->getMediaTimeScale(); 00286 groups->push_back(group); 00287 } 00288 if(!group->rtx) { 00289 group->rtx=new rtx_info(NACK,3000); 00290 group->midESInfo=lastMid++; 00291 group->rtx->mId=lastMid++; 00292 group->es=(*ei); 00293 group->rtx->ticks=(*ei)->getMediaTimeScale(); 00294 } 00295 cnt=sprintf(next,"a=group:FID %u %u\r\n", 00296 group->midESInfo, 00297 group->rtx->mId); 00298 next+=cnt; 00299 } 00300 ++ei; 00301 } 00302 00303 } 00304 int payloadType = SL_PAYLOAD_TYPE; 00305 u32 bw2=0; 00306 00307 for (int i = 1; i <= streamcnt; i++) { 00308 00309 u32 tid = (*li)->getStreamId(); 00310 u32 handlerType = (*li)->getHandlerType(); 00311 //const u8 *decoderConfig = (*li)->getEncodedDecoderConfig(); 00312 // u32 objectType=(*li)->getObjectType(); 00313 // u32 streamType = (*li)->getStreamType(); 00314 // u32 bufferSize=(*li)->getBufferSize(); 00315 u32 sze = 0; 00316 u8 volHeader[4096]; 00317 u8* newHeader=NULL; 00318 u8* src=NULL; 00319 uint width = 0, height = 0;uint bitrate=0; 00320 00321 if ('/' == *filename) 00322 filename++; 00323 rtx_group* currentRtx=NULL; 00324 if(includeRtxInfo) 00325 currentRtx=stream->getRtxGroup(tid,groups); 00326 00327 switch (handlerType) { 00328 case MP4VisualHandlerType: 00329 width = ((VideoESInfo*)(*li))->getWidth(); 00330 height = ((VideoESInfo*)(*li))->getHeight(); 00331 bitrate=((VideoESInfo*)(*li))->getAvgBandwidth(); 00332 if(tc && !perfectHit) { 00333 // check if tc say sth different 00334 if(tc->getDisplayHeight()!=0 && 00335 tc->getDisplayHeight() < height) 00336 height=tc->getDisplayHeight(); 00337 if(tc->getDisplayWidth()!=0 && 00338 tc->getDisplayWidth() < width) 00339 width=tc->getDisplayWidth(); 00340 bw2=tc->getNetworkCapacityInByte()*8; 00341 if(bw2==0) 00342 bw2=tc->getMaxDecoderBitRateInBit(); 00343 else if(tc->getMaxDecoderBitRateInBit() && 00344 tc->getMaxDecoderBitRateInBit() < bw2) { 00345 bw2=tc->getMaxDecoderBitRateInBit(); 00346 } 00347 u32 new_bitrate = width * height * 6; 00348 if (new_bitrate > bw2) 00349 new_bitrate = bw2; //reduce to given termCaps 00350 if (new_bitrate < bitrate) 00351 bitrate = new_bitrate; 00352 newHeader = MP4Encoder::createNewHeader((VideoESInfo*)(*li), new_bitrate, 00353 width, height, 300, (int)((VideoESInfo*)(*li))->getFPS(), &sze); 00354 } 00355 if(!newHeader) { 00356 sze = (*li)->getHeaders(&src); 00357 SDP::encodeDecoderConfig(volHeader, src, sze); 00358 } 00359 else { 00360 SDP::encodeDecoderConfig(volHeader,newHeader,sze); 00361 delete newHeader;newHeader=NULL; 00362 } 00363 cnt = sprintf(next, "m=video 0 RTP/AVP %i\r\nb=AS:%i\r\n" 00364 "a=rtpmap:%i MP4V-ES/%i\r\n" 00365 "a=control:trackID=%i\r\n" 00366 "a=cliprect:0,0,%i,%i\r\n" 00367 "a=fmtp:%i profile-level-id=1;config=%s\r\n" // StreamType=%i; "SizeLength=%i; " 00368 // "DTSDeltaLength=%i; config=%s\r\n" 00369 "a=mpeg4-esid:%i\r\n" 00370 "a=aspect-ratio=%6.6f\r\n" 00371 "a=frame-pattern=%s\r\n" 00372 "a=bframesamount=%f\r\n", 00373 payloadType, (bitrate / 1024), 00374 payloadType, (*li)->getMediaTimeScale(), 00375 tid, 00376 width, height, 00377 SL_PAYLOAD_TYPE, volHeader, // streamType, //sizeLength, 00378 // dtsDeltaLength, volHeader, 00379 tid, // "mpeg4-generic" 00380 ((VideoESInfo*)(*li))->getAspectRatio(), 00381 (((VideoESInfo*)(*li))->hasStaticFramePattern()?"static":"dynamic"), 00382 ((VideoESInfo*)(*li))->getAvgBFrameSize()); 00383 next += cnt; 00384 // add bframes and gop size for static frame pattern 00385 if(((VideoESInfo*)(*li))->hasStaticFramePattern()) { 00386 cnt = sprintf(next, "a=num-bframes %i\r\n" 00387 "a=gop-size %i\r\n", 00388 ((VideoESInfo*)(*li))->getNum_B_frames(), 00389 ((VideoESInfo*)(*li))->getGOP_size()); 00390 next+=cnt; 00391 } 00392 payloadType++; 00393 00394 if( includeRtxInfo && currentRtx && 00395 currentRtx->rtx && currentRtx->rtx->state!=NONE) { 00396 u32 maxRtxDelay=3000; 00397 if( currentRtx->rtx && 00398 currentRtx->rtx->rtxTimeInMs>0) 00399 maxRtxDelay=currentRtx->rtx->rtxTimeInMs; 00400 else { 00401 currentRtx->rtx=new rtx_info(NACK,maxRtxDelay); // impossible code 00402 } 00403 00404 00405 cnt = sprintf(next, 00406 "a=rtcp-fb:%i %s\r\n" 00407 "a=mid:%u\r\n" // write the mid for the media 00408 "m=video 0 RTP/AVPF %i\r\n" 00409 "a=rtpmap:%i rtx/%u\r\n" 00410 "a=fmtp:%i apt=%i;rtx-time=%u\r\n" 00411 "a=mid:%u\r\n", 00412 payloadType-1, sRTXState[currentRtx->rtx->state], 00413 currentRtx->midESInfo, 00414 payloadType, 00415 payloadType, currentRtx->rtx->ticks, 00416 payloadType,(payloadType-1),maxRtxDelay, 00417 currentRtx->rtx->mId); 00418 next += cnt; 00419 currentRtx->rtx->payloadType=payloadType; 00420 payloadType++; 00421 } 00422 break; 00423 case MP4AudioHandlerType: 00424 // only send audio when TermCaps indicate that the client can handle audio 00425 if(!tc || (tc && tc->getNumAudioChannels()>0)) { 00426 // audio config still not correct 00427 u8 decoderConfig[4096]; 00428 src = NULL; 00429 payloadType=14; 00430 sze = (*li)->getHeaders(&src); 00431 SDP::encodeDecoderConfig(decoderConfig, src, sze); 00432 cnt = sprintf(next, "m=audio 0 RTP/AVP %i\r\nb=AS:%i\r\n" // payload 00433 "a=control:trackID=%i\r\n" // trackid 00434 "a=mpeg4-esid:%i\r\n" // esid 00435 "a=fmtp:%i DTSDeltaLength=%i; config=%s\r\n" 00436 "a=rtpmap:%i MPA/%i\r\n", 00437 payloadType,((*li)->getAvgBandwidth() / 1024), tid, tid, 00438 payloadType, dtsDeltaLength, decoderConfig, 00439 payloadType, (*li)->getMediaTimeScale()); 00440 next += cnt; 00441 payloadType++; 00442 00443 if(includeRtxInfo) { 00444 u32 maxRtxDelay=3000; 00445 if( currentRtx->rtx && 00446 currentRtx->rtx->rtxTimeInMs>0) 00447 maxRtxDelay=currentRtx->rtx->rtxTimeInMs; 00448 else { 00449 currentRtx->rtx=new rtx_info(NACK,maxRtxDelay); 00450 } 00451 cnt = sprintf(next, 00452 "a=mid:%ui\r\n" // write the mid for the media 00453 "m=audio 0 RTP/AVPF %i\r\n" 00454 "a=rtpmap:%i rtx/%i\r\n" 00455 "a=fmtp:%i apt=%i;rtx-time=%ui\r\n" 00456 "a=mid:%i\r\n", 00457 currentRtx->midESInfo, 00458 payloadType, 00459 payloadType, currentRtx->rtx->ticks, 00460 payloadType,(payloadType-1),maxRtxDelay, 00461 currentRtx->rtx->mId); 00462 next += cnt; 00463 currentRtx->rtx->payloadType=payloadType; 00464 payloadType++; 00465 } 00466 } // if !tc... 00467 break; 00468 default: //application is not recognized by many players! 00469 /* cnt = sprintf(next, "m=application 0 RTP/AVP %i\r\n" "a=control:trackID=%i\r\n" "a=mpeg4-esid:%i\r\n" "a=fmtp:%i " // StreamType=%i; "// SizeLength=%i; " 00470 // "config=%s\r\n" // "DTSDeltaLength=%i; config=%s\r\n" 00471 "\r\na=rtpmap:%i %s\r\n", payloadType, tid, tid, payloadType, // streamType, //sizeLength, 00472 //dtsDeltaLength, decoderConfig, 00473 // payloadType, "X-MP4-ES/1000"); //mpeg4-generic 00474 next += cnt; 00475 payloadType++; */ 00476 ; 00477 } 00478 ++li; // increase list iterator 00479 if(src) 00480 delete src; 00481 } 00482 00483 *next = 0; 00484 char *result = new char[strlen(buffer) + 1]; 00485 strcpy(result, buffer); 00486 00487 delete [] iodstr; 00488 delete [] buffer; 00489 return result; 00490 }; 00491 00492 00493 00494 void SDP::encodeDecoderConfig(u8 * dest, const u8 * source, u32 size) 00495 { 00496 //encode 00497 size=size*2; 00498 for (u32 j = 0; j < size;j += 2) { 00499 u8 help = (*source) >> 4; 00500 dest[j] = help > 9 ? (char) help - 10 + 'A' : (char) help + '0'; 00501 help = (*source) & 0xf; 00502 dest[j + 1] = 00503 help > 9 ? (char) help - 10 + 'A' : (char) help + '0'; 00504 source++; 00505 } 00506 dest[size] = 0; 00507 }; 00508 00509 void SDP::decodeDecoderConfig(u8 * dest, const u8 * source, u32 srcSize) 00510 { 00511 //decode: during encode 1 Byte -> 2 Bytes (higher 4 bits were first, second byte is lower 4 bits 00512 for (u32 j = 0; j < srcSize; j += 2) { 00513 u8 help = (*source); 00514 dest[j / 2] = 00515 (help >= 00516 'A') ? ((char) help - 'A' + 10) << 4 : ((char) help - '0') << 4; 00517 source++; 00518 help = (*source); 00519 dest[j / 2] += 00520 (help >= 'A') ? ((char) help - 'A' + 10) : ((char) help - '0'); 00521 source++; 00522 } 00523 }; 00524