[Live-devel] Kasenna MediaBase support (long)
Dermot McGahon
dermot at dspsrv.com
Thu Apr 29 16:47:27 PDT 2004
Ross,
I need to talk a few things though. I'll understand if you don't have time
for this, but if can point me in the right direction on a few things I
would really appreciate that.
It goes back to this email.
On Sat, 27 Mar 2004 20:25:04 -0800, Ross Finlayson <finlayson at live.com>
wrote:
> Derk-Jan,
>
>> Thought it was better to discuss this here.
>> I really want to try to get this working no matter how much it's their
>> fault for not keeping to the latest specs (and actually creating broken
>> implementations).
>
> OK, fair enough.
>
>> 4: VLC needs to be able to call a demux from another demux (TS streams
>> need to be demuxed)
>
> Yes, this is something that's needed for playing 'proper' MPEG-2 TS/RTP
> streams also.
This is what I'm trying to achieve, but with mplayer. I went with mplayer
because it seemed that it had the best RTSP/MPEG-2 TS support. Maybe that
was wrong though as people are saying that MPEG-2 UDP multicast is working
well with VLC. I would need unicast though as the Kasenna support fast
forward and rewind, which is the functionality that I'm looking for.
>> I know how I would go about doing the RAW/RAW/UDP request...
>> I've actually already hacked up liveMedia a bit to use RAW/RAW/UDP, and
>> i edited the SETUP to use the aggregate uri instead of the
>> non-aggregate, because there is only 1 TS Stream and otherwise the
>> server reports 460 (i know, it's a mess :)
I've made similar changes and more. I'll summarise them below.
>>
>> Anyway, these are the results:
>> (Notice the completely ridiculous SDP the server responds with... )
>
> The SDP actually seems OK, except for the fact that it apparently lies
> about the nature of the stream that it's supposed to be 'describing'.
>
> Now, just to make things clear (in my own mind): Do I understand this
> correctly? Despite advertising that the stream consists of (i) a MPEG
> video RTP stream and (ii) a MPEG audio stream, the stream actually
> consists of a single MPEG Transport Stream over UDP. Is this correct??
> If so, how incredibly brain damaged!
This is correct. It's single MPEG-2 Transport Stream over UDP, with
two PES, one for audio, one for video.
> I'd prefer that any changes to the LIVE.COM libraries necessary to
> support this Kasenna crap be kept as small as possible. One possible
> way to proceed would be for the RTSP client code to (when a Kasenna
> stream is detected) throw away the SDP description that comes from the
> Kasenna server, and instead generate a different SDP description that
> *really* describes the stream. This new, proper SDP description would
> be passed to "MediaSession::initializeWithSDP()", which then wouldn't
> need to change at all, except to add support for creating a source for
> raw UDP streams - using the "BasicUDPSource" class.
In fact for MPEG-2 from a Kasenna, there isn't any SDP at all. Only for
MPEG-1 and MPEG-4. For MPEG-2 they use application/x-rtsp-mh. I think
there are reasons for that. They have extensions for the ff/rw and for
clips and sequences and other things. Plus, they are around a few years
so maybe they went with something that worked and now won't change.
Anyway, I'm reading the x-rtsp-mh and generating an SDP. But, I'm not
100% sure that it's correct.
I go through DESCRIBE/SETUP/PLAY and the server starts streaming.
Now, it's about here that I'm stuck. With demux_rtp.cpp as the demuxer
the TS packets are all out of order so it fails MPEG-2 TS continuity
checks. I'm attempting to use the mplayer TS demuxer (demux_ts.c) in
libmpdemux, copying the new_demuxers_demuxer hack used for interleaved
RTP.
Maybe I should show some changes. Please understand that I'm trying
to understand all the issues first and get a working prototype. I
am committed to going over all this for as long as it takes to turn
the hacks into proper solutions.
Groupsock::handleRead()
-----------------------
#ifdef SUPPORT_KASENNA_RTSP
int maxBytesToRead = bufferMaxSize;
#else
int maxBytesToRead = bufferMaxSize - TunnelEncapsulationTrailerMaxSize;
#endif
RTSPClient::describeURL()
-------------------------
// (Later implement more, as specified in the RTSP spec, sec D.1 #####)
char* const cmdFmt =
"DESCRIBE %s RTSP/1.0\r\n"
"CSeq: %d\r\n"
#ifdef SUPPORT_KASENNA_RTSP
"Accept: application/x-rtsp-mh, application/sdp\r\n"
#else
"Accept: application/sdp\r\n"
#endif
and towards the end of this method:
if (from != to && fVerbosityLevel >= 1) {
envir() << "Warning: " << from-to << " invalid 'NULL' bytes were
found in (and removed from) the SDP description.\n";
}
bodyStart[to] = '\0'; // trims any extra data
#ifdef SUPPORT_KASENNA_RTSP
// Translate from x-rtsp-mh to sdp
int videoPid, audioPid;
char sdpBuf[300], currentWord[20], ipAddressBuf[20];
char * currentPos = bodyStart;
while (strcmp(currentWord, "</MediaDescription>") != 0)
{
sscanf(currentPos, "%s", currentWord);
if (strcmp(currentWord, "VideoPid") == 0) {
currentPos += strlen(currentWord) + 1;
sscanf(currentPos, "%s", currentWord);
currentPos += strlen(currentWord) + 1;
sscanf(currentPos, "%d", &videoPid);
currentPos += 3;
}
if (strcmp(currentWord, "AudioPid") == 0) {
currentPos += strlen(currentWord) + 1;
sscanf(currentPos, "%s", currentWord);
currentPos += strlen(currentWord) + 1;
sscanf(currentPos, "%d", &audioPid);
currentPos += 3;
}
currentPos += strlen(currentWord) + 1;
}
unsigned char byte1 = fServerAddress & 0x000000ff;
unsigned char byte2 = (fServerAddress & 0x0000ff00) >> 8;
unsigned char byte3 = (fServerAddress & 0x00ff0000) >> 16;
unsigned char byte4 = (fServerAddress & 0xff000000) >> 24;
sprintf(ipAddressBuf, "%u%s%u%s%u%s%u%s%c",
byte1, ".",
byte2, ".",
byte3, ".",
byte4, " ",
'\0');
sprintf(sdpBuf, "%s\n%s%s\n%s%s\n%s%s\n%s\n%s\n%s\n%s\n%s%d\n",
"v=0",
"o=NoSpacesAllowed 1 1 IN IP4 ", ipAddressBuf,
"s=", url,
"c=IN IP4 ", ipAddressBuf,
"t=0 0",
"a=control:*",
"a=range:npt=0-",
"m=video 554 RAW/RAW/UDP 33",
"a=control:trackID=", videoPid);
//"m=audio 0 RTP/AVP 14",
//"a=control:trackID=", audioPid);
return strDup(sdpBuf);
#endif
}
RTSPClient::setupMediaSubsession()
----------------------------------
// (Later implement more, as specified in the RTSP spec, sec D.1 #####)
char* const cmdFmt =
#ifdef SUPPORT_KASENNA_RTSP
"SETUP %s%s RTSP/1.0\r\n"
#else
"SETUP %s%s%s RTSP/1.0\r\n"
#endif
"CSeq: %d\r\n"
#ifdef SUPPORT_REAL_RTSP
"Transport: x-pn-tng/tcp;mode=play,rtp/avp/unicast;mode=play\r\n"
#else
#ifdef SUPPORT_KASENNA_RTSP
"Transport: RAW/RAW/UDP%s%s%s=%d-%d\r\n"
#else
"Transport: RTP/AVP%s%s%s=%d-%d\r\n"
#endif
#endif
"%s"
"%s"
"%s\r\n";
and further on in the same method:
unsigned cmdSize = strlen(cmdFmt)
+ strlen(prefix) + strlen(separator)
#ifndef SUPPORT_KASENNA_RTSP
+ strlen(suffix)
#endif
+ 20 /* max int len */
+ strlen(transportTypeString) + strlen(modeString)
+ strlen(portTypeString) + 2*5 /* max port len */
+ strlen(sessionStr)
+ strlen(authenticatorStr)
+ fUserAgentHeaderStrSize;
cmd = new char[cmdSize];
sprintf(cmd, cmdFmt,
prefix, separator,
#ifndef SUPPORT_KASENNA_RTSP
suffix,
#endif
++fCSeq,
#ifndef SUPPORT_REAL_RTSP
transportTypeString, modeString, portTypeString,
rtpNumber, rtcpNumber,
#endif
sessionStr,
MediaSession::initializeWithSDP()
---------------------------------
#ifdef SUPPORT_KASENNA_RTSP
if (sscanf(sdpLine, "m=%s %hu RAW/RAW/UDP %u",
mediumName, &subsession->fClientPortNum, &payloadFormat) !=
3
#else
if (sscanf(sdpLine, "m=%s %hu RTP/AVP %u",
mediumName, &subsession->fClientPortNum, &payloadFormat) !=
3
#endif
MediaSubsession::initiate()
---------------------------
#ifdef SUPPORT_KASENNA_RTSP
success = True;
break;
#else
// Get the client port number, to make sure that it's even (for RTP):
Port clientPort(0);
if (!getSourcePort(env(), fRTPSocket->socketNum(), clientPort)) {
break;
}
fClientPortNum = ntohs(clientPort.num());
// If the port number's not even, try again:
if ((fClientPortNum&1) == 0) {
success = True;
break;
}
// Try again:
delete oldGroupsock;
oldGroupsock = fRTPSocket;
fClientPortNum = 0;
#endif
and a little on:
#ifndef SUPPORT_KASENNA_RTSP
// Set our RTCP port to be the RTP port +1
unsigned short const rtcpPortNum = fClientPortNum|1;
if (isSSM()) {
fRTCPSocket = new Groupsock(env(), tempAddr, fSourceFilterAddr,
rtcpPortNum);
// Also, send RTCP packets back to the source via unicast:
if (fRTCPSocket != NULL) {
fRTCPSocket->changeDestinationParameters(fSourceFilterAddr,0,~0);
}
} else {
fRTCPSocket = new Groupsock(env(), tempAddr, rtcpPortNum, 255);
}
if (fRTCPSocket == NULL) {
char tmpBuf[100];
sprintf(tmpBuf, "Failed to create RTCP socket (port %d)",
rtcpPortNum);
env().setResultMsg(tmpBuf);
break;
}
#endif
and a good bit further on:
} else if (strcmp(fCodecName, "X-MCT-TEXT") == 0) {
// A UDP-packetized text stream (*not* a RTP stream)
fReadSource = BasicUDPSource::createNew(env(), fRTPSocket);
fRTPSource = NULL; // Note!
} else if ( strcmp(fCodecName, "PCMU") == 0 // PCM u-law audio
|| strcmp(fCodecName, "GSM") == 0 // GSM audio
|| strcmp(fCodecName, "PCMA") == 0 // PCM a-law audio
|| strcmp(fCodecName, "L16") == 0 // 16-bit linear audio
|| strcmp(fCodecName, "MP1S") == 0 // MPEG-1 System Stream
|| strcmp(fCodecName, "MP2T") == 0 // MPEG-2 Transport Str
|| strcmp(fCodecName, "MP2P") == 0 // MPEG-2 Program Stream
|| strcmp(fCodecName, "L8") == 0 // 8-bit linear audio
|| strcmp(fCodecName, "SPEEX") == 0 // SPEEX audio
) {
#ifdef SUPPORT_KASENNA_RTSP
createBasicUDPSource = True;
#else
createSimpleRTPSource = True;
useSpecialRTPoffset = 0;
#endif
} else if (useSpecialRTPoffset >= 0) {
// We don't know this RTP payload format, but try to receive
// it using a 'SimpleRTPSource' with the specified header offset:
createSimpleRTPSource = True;
} else {
env().setResultMsg("RTP payload format unknown or not supported");
break;
}
if (createBasicUDPSource) {
fReadSource = BasicUDPSource::createNew(env(), fRTPSocket);
}
demux_rtp.cc - demux_open_rtp()
-------------------------------
// Set the OS's socket receive buffer sufficiently large to avoid
// incoming packets getting dropped between successive reads from
this
// subsession's demuxer. Depending on the bitrate(s) that you
expect,
// you may wish to tweak the "desiredReceiveBufferSize" values
above.
#ifdef SUPPORT_KASENNA_RTSP
BasicUDPSource* udpSource =
dynamic_cast<BasicUDPSource*>(subsession->readSource());
int rtpSocketNum = udpSource->groupSock()->socketNum();
#else
int rtpSocketNum = subsession->rtpSource()->RTPgs()->socketNum();
#endif
further on:
success = True;
} while (0);
if (!success) return NULL; // an error occurred
#ifdef SUPPORT_KASENNA_RTSP
stream_t* s = new_ds_stream(demuxer->video);
demuxer->type = DEMUXER_TYPE_MPEG_TS;
demuxer_t* new_demux = demux_open(s, DEMUXER_TYPE_MPEG_TS, -1, -1, -1,
NULL);
demuxer = new_demuxers_demuxer(new_demux, new_demux, new_demux);
#else
// Hack: If audio and video are demuxed together on a single RTP stream,
// then create a new "demuxer_t" structure to allow the higher-level
// code to recognize this:
if (demux_is_multiplexed_rtp_stream(demuxer)) {
stream_t* s = new_ds_stream(demuxer->video);
demuxer_t* od = demux_open(s, DEMUXER_TYPE_UNKNOWN, -1, -1, -1, NULL);
demuxer = new_demuxers_demuxer(od, od, od);
}
#endif
return demuxer;
}
demux_rtp.cpp - afterReading()
------------------------------
#ifdef SUPPORT_KASENNA_RTSP
if (frameSize > MPEG2_TS_FRAME_SIZE) {
fprintf(stderr, "Saw an input frame too large (>=%d). Increase
MPEG2_TS_FRAME_SIZE in \"demux_rtp.cpp\".\n",
MPEG2_TS_FRAME_SIZE);
}
#else
if (frameSize >= MAX_RTP_FRAME_SIZE) {
fprintf(stderr, "Saw an input frame too large (>=%d). Increase
MAX_RTP_FRAME_SIZE in \"demux_rtp.cpp\".\n",
MAX_RTP_FRAME_SIZE);
}
#endif
and ifdef out the rtcp checks:
#ifndef SUPPORT_KASENNA_RTSP
RTPState* rtpState = (RTPState*)(demuxer->priv);
// Set the packet's presentation time stamp, depending on whether or
// not our RTP source's timestamps have been synchronized yet:
Boolean hasBeenSynchronized
= bufferQueue->rtpSource()->hasBeenSynchronizedUsingRTCP();
I would really appreciate some guidance. Is this completely the wrong
approach? Should I be doing something with MPEG1or2Demux, adding in
TS support? Or is it BasicUDPSource that I should be extending?
Dermot.
--
More information about the live-devel
mailing list