From: hyc Date: Fri, 12 Mar 2010 04:53:01 +0000 (+0000) Subject: Add RTMP_Read() to simplify library usage X-Git-Tag: v2.4~204 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a687f0492d306ae1da031e34be4ee8951b8ba7ce;p=rtmpdump Add RTMP_Read() to simplify library usage git-svn-id: svn://svn.mplayerhq.hu/rtmpdump/trunk@331 400ebc74-4327-4243-bc38-086b20814532 --- diff --git a/librtmp/rtmp.c b/librtmp/rtmp.c index a98da18..ca29b0b 100644 --- a/librtmp/rtmp.c +++ b/librtmp/rtmp.c @@ -2567,6 +2567,17 @@ RTMP_Close(RTMP *r) r->m_nClientBW2 = 2; r->m_nServerBW = 2500000; + r->m_read.buf = NULL; + r->m_read.dataType = 0; + r->m_read.bResume = 0; + r->m_read.status = 0; + r->m_read.bStopIgnoring = false; + r->m_read.bFoundKeyframe = false; + r->m_read.bFoundFlvKeyframe = false; + r->m_read.nResumeTS = 0; + r->m_read.nIgnoredFrameCounter = 0; + r->m_read.nIgnoredFlvFrameCounter = 0; + for (i = 0; i < RTMP_CHANNELS; i++) { if (r->m_vecChannelsIn[i]) @@ -2822,3 +2833,541 @@ HTTP_read(RTMP *r, int fill) } return 0; } + +#define MAX_IGNORED_FRAMES 50 + +/* Read from the stream until we get a media packet. + * Returns -3 if Play.Close/Stop, -2 if fatal error, -1 if no more media + * packets, 0 if ignorable error, >0 if there is a media packet + */ +static int +Read_1_Packet(RTMP *r, char *buf, int buflen) +{ + uint32_t prevTagSize = 0; + int rtnGetNextMediaPacket = 0, ret = RTMP_READ_EOF; + RTMPPacket packet = { 0 }; + bool recopy = false; + + rtnGetNextMediaPacket = RTMP_GetNextMediaPacket(r, &packet); + while (rtnGetNextMediaPacket) + { + char *packetBody = packet.m_body; + unsigned int nPacketLen = packet.m_nBodySize; + + /* Return -3 if this was completed nicely with invoke message + * Play.Stop or Play.Complete + */ + if (rtnGetNextMediaPacket == 2) + { + Log(LOGDEBUG, + "Got Play.Complete or Play.Stop from server. " + "Assuming stream is complete"); + ret = RTMP_READ_COMPLETE; + break; + } + + r->m_read.dataType |= (((packet.m_packetType == 0x08) << 2) | + (packet.m_packetType == 0x09)); + + if (packet.m_packetType == 0x09 && nPacketLen <= 5) + { + Log(LOGWARNING, "ignoring too small video packet: size: %d", + nPacketLen); + ret = RTMP_READ_IGNORE; + break; + } + if (packet.m_packetType == 0x08 && nPacketLen <= 1) + { + Log(LOGWARNING, "ignoring too small audio packet: size: %d", + nPacketLen); + ret = RTMP_READ_IGNORE; + break; + } +#if 1 /* _DEBUG */ + Log(LOGDEBUG, "type: %02X, size: %d, TS: %d ms, abs TS: %d", + packet.m_packetType, nPacketLen, packet.m_nTimeStamp, + packet.m_hasAbsTimestamp); + if (packet.m_packetType == 0x09) + Log(LOGDEBUG, "frametype: %02X", (*packetBody & 0xf0)); +#endif + + if (r->m_read.bResume) + { + /* check the header if we get one */ + if (packet.m_nTimeStamp == 0) + { + if (r->m_read.nMetaHeaderSize > 0 + && packet.m_packetType == 0x12) + { + AMFObject metaObj; + int nRes = + AMF_Decode(&metaObj, packetBody, nPacketLen, false); + if (nRes >= 0) + { + AVal metastring; + AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), + &metastring); + + if (AVMATCH(&metastring, &av_onMetaData)) + { + // compare + if ((r->m_read.nMetaHeaderSize != nPacketLen) || + (memcmp + (r->m_read.metaHeader, packetBody, + r->m_read.nMetaHeaderSize) != 0)) + { + ret = RTMP_READ_ERROR; + } + } + AMF_Reset(&metaObj); + if (ret == RTMP_READ_ERROR) + break; + } + } + + /* check first keyframe to make sure we got the right position + * in the stream! (the first non ignored frame) + */ + if (r->m_read.nInitialFrameSize > 0) + { + /* video or audio data */ + if (packet.m_packetType == r->m_read.initialFrameType + && r->m_read.nInitialFrameSize == nPacketLen) + { + /* we don't compare the sizes since the packet can + * contain several FLV packets, just make sure the + * first frame is our keyframe (which we are going + * to rewrite) + */ + if (memcmp + (r->m_read.initialFrame, packetBody, + r->m_read.nInitialFrameSize) == 0) + { + Log(LOGDEBUG, "Checked keyframe successfully!"); + r->m_read.bFoundKeyframe = true; + /* ignore it! (what about audio data after it? it is + * handled by ignoring all 0ms frames, see below) + */ + ret = RTMP_READ_IGNORE; + break; + } + } + + /* hande FLV streams, even though the server resends the + * keyframe as an extra video packet it is also included + * in the first FLV stream chunk and we have to compare + * it and filter it out !! + */ + if (packet.m_packetType == 0x16) + { + /* basically we have to find the keyframe with the + * correct TS being nResumeTS + */ + unsigned int pos = 0; + uint32_t ts = 0; + + while (pos + 11 < nPacketLen) + { + /* size without header (11) and prevTagSize (4) */ + uint32_t dataSize = + AMF_DecodeInt24(packetBody + pos + 1); + ts = AMF_DecodeInt24(packetBody + pos + 4); + ts |= (packetBody[pos + 7] << 24); + +#ifdef _DEBUG + Log(LOGDEBUG, + "keyframe search: FLV Packet: type %02X, dataSize: %d, timeStamp: %d ms", + packetBody[pos], dataSize, ts); +#endif + /* ok, is it a keyframe?: + * well doesn't work for audio! + */ + if (packetBody[pos /*6928, test 0 */ ] == + r->m_read.initialFrameType + /* && (packetBody[11]&0xf0) == 0x10 */ ) + { + if (ts == r->m_read.nResumeTS) + { + Log(LOGDEBUG, + "Found keyframe with resume-keyframe timestamp!"); + if (r->m_read.nInitialFrameSize != dataSize + || memcmp(r->m_read.initialFrame, + packetBody + pos + 11, + r->m_read. + nInitialFrameSize) != 0) + { + Log(LOGERROR, + "FLV Stream: Keyframe doesn't match!"); + ret = RTMP_READ_ERROR; + break; + } + r->m_read.bFoundFlvKeyframe = true; + + /* skip this packet? + * check whether skippable: + */ + if (pos + 11 + dataSize + 4 > nPacketLen) + { + Log(LOGWARNING, + "Non skipable packet since it doesn't end with chunk, stream corrupt!"); + ret = RTMP_READ_ERROR; + break; + } + packetBody += (pos + 11 + dataSize + 4); + nPacketLen -= (pos + 11 + dataSize + 4); + + goto stopKeyframeSearch; + + } + else if (r->m_read.nResumeTS < ts) + { + /* the timestamp ts will only increase with + * further packets, wait for seek + */ + goto stopKeyframeSearch; + } + } + pos += (11 + dataSize + 4); + } + if (ts < r->m_read.nResumeTS) + { + Log(LOGERROR, + "First packet does not contain keyframe, all " + "timestamps are smaller than the keyframe " + "timestamp; probably the resume seek failed?"); + } + stopKeyframeSearch: + ; + if (!r->m_read.bFoundFlvKeyframe) + { + Log(LOGERROR, + "Couldn't find the seeked keyframe in this chunk!"); + ret = RTMP_READ_IGNORE; + break; + } + } + } + } + + if (packet.m_nTimeStamp > 0 + && (r->m_read.bFoundFlvKeyframe || r->m_read.bFoundKeyframe)) + { + /* another problem is that the server can actually change from + * 09/08 video/audio packets to an FLV stream or vice versa and + * our keyframe check will prevent us from going along with the + * new stream if we resumed. + * + * in this case set the 'found keyframe' variables to true. + * We assume that if we found one keyframe somewhere and were + * already beyond TS > 0 we have written data to the output + * which means we can accept all forthcoming data including the + * change between 08/09 <-> FLV packets + */ + r->m_read.bFoundFlvKeyframe = true; + r->m_read.bFoundKeyframe = true; + } + + /* skip till we find our keyframe + * (seeking might put us somewhere before it) + */ + if (!r->m_read.bFoundKeyframe && packet.m_packetType != 0x16) + { + Log(LOGWARNING, + "Stream does not start with requested frame, ignoring data... "); + r->m_read.nIgnoredFrameCounter++; + if (r->m_read.nIgnoredFrameCounter > MAX_IGNORED_FRAMES) + ret = RTMP_READ_ERROR; /* fatal error, couldn't continue stream */ + else + ret = RTMP_READ_IGNORE; + break; + } + /* ok, do the same for FLV streams */ + if (!r->m_read.bFoundFlvKeyframe && packet.m_packetType == 0x16) + { + Log(LOGWARNING, + "Stream does not start with requested FLV frame, ignoring data... "); + r->m_read.nIgnoredFlvFrameCounter++; + if (r->m_read.nIgnoredFlvFrameCounter > MAX_IGNORED_FRAMES) + ret = RTMP_READ_ERROR; + else + ret = RTMP_READ_IGNORE; + break; + } + + /* we have to ignore the 0ms frames since these are the first + * keyframes; we've got these so don't mess around with multiple + * copies sent by the server to us! (if the keyframe is found at a + * later position there is only one copy and it will be ignored by + * the preceding if clause) + */ + if (!r->m_read.bStopIgnoring && packet.m_packetType != 0x16) + { /* exclude type 0x16 (FLV) since it can + * contain several FLV packets */ + if (packet.m_nTimeStamp == 0) + { + ret = RTMP_READ_IGNORE; + break; + } + else + { + r->m_read.bStopIgnoring = true; /* stop ignoring packets */ + } + } + } + + /* calculate packet size and allocate slop buffer if necessary */ + unsigned int size = nPacketLen + + ((packet.m_packetType == 0x08 || packet.m_packetType == 0x09 + || packet.m_packetType == 0x12) ? 11 : 0) + + (packet.m_packetType != 0x16 ? 4 : 0); + + char *ptr, *pend; + if (size + 4 > buflen) + { + /* the extra 4 is for the case of an FLV stream without a last + * prevTagSize (we need extra 4 bytes to append it) */ + r->m_read.buf = malloc(size + 4); + if (r->m_read.buf == 0) + { + Log(LOGERROR, "Couldn't allocate memory!"); + ret = RTMP_READ_ERROR; // fatal error + break; + } + recopy = true; + ptr = r->m_read.buf; + } + else + { + ptr = buf; + } + pend = ptr + size + 4; + + /* use to return timestamp of last processed packet */ + uint32_t nTimeStamp = 0; + + /* audio (0x08), video (0x09) or metadata (0x12) packets : + * construct 11 byte header then add rtmp packet's data */ + if (packet.m_packetType == 0x08 || packet.m_packetType == 0x09 + || packet.m_packetType == 0x12) + { + nTimeStamp = r->m_read.nResumeTS + packet.m_nTimeStamp; + prevTagSize = 11 + nPacketLen; + + *ptr = packet.m_packetType; + ptr++; + ptr = AMF_EncodeInt24(ptr, pend, nPacketLen); + + /*if(packet.m_packetType == 0x09) { // video + + // H264 fix: + if((packetBody[0] & 0x0f) == 7) { // CodecId = H264 + uint8_t packetType = *(packetBody+1); + + uint32_t ts = AMF_DecodeInt24(packetBody+2); // composition time + int32_t cts = (ts+0xff800000)^0xff800000; + Log(LOGDEBUG, "cts : %d\n", cts); + + nTimeStamp -= cts; + // get rid of the composition time + CRTMP::EncodeInt24(packetBody+2, 0); + } + Log(LOGDEBUG, "VIDEO: nTimeStamp: 0x%08X (%d)\n", nTimeStamp, nTimeStamp); + } */ + + ptr = AMF_EncodeInt24(ptr, pend, nTimeStamp); + *ptr = (char)((nTimeStamp & 0xFF000000) >> 24); + ptr++; + + /* stream id */ + ptr = AMF_EncodeInt24(ptr, pend, 0); + } + + memcpy(ptr, packetBody, nPacketLen); + unsigned int len = nPacketLen; + + /* correct tagSize and obtain timestamp if we have an FLV stream */ + if (packet.m_packetType == 0x16) + { + unsigned int pos = 0; + + while (pos + 11 < nPacketLen) + { + /* size without header (11) and without prevTagSize (4) */ + uint32_t dataSize = AMF_DecodeInt24(packetBody + pos + 1); + nTimeStamp = AMF_DecodeInt24(packetBody + pos + 4); + nTimeStamp |= (packetBody[pos + 7] << 24); + + /* + CRTMP::EncodeInt24(ptr+pos+4, nTimeStamp); + ptr[pos+7] = (nTimeStamp>>24)&0xff;// */ + + /* set data type */ + r->m_read.dataType |= (((*(packetBody + pos) == 0x08) << 2) | + (*(packetBody + pos) == 0x09)); + + if (pos + 11 + dataSize + 4 > nPacketLen) + { + if (pos + 11 + dataSize > nPacketLen) + { + Log(LOGERROR, + "Wrong data size (%lu), stream corrupted, aborting!", + dataSize); + ret = RTMP_READ_ERROR; + break; + } + Log(LOGWARNING, "No tagSize found, appending!"); + + /* we have to append a last tagSize! */ + prevTagSize = dataSize + 11; + AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, + prevTagSize); + size += 4; + len += 4; + } + else + { + prevTagSize = + AMF_DecodeInt32(packetBody + pos + 11 + dataSize); + +#ifdef _DEBUG + Log(LOGDEBUG, + "FLV Packet: type %02X, dataSize: %lu, tagSize: %lu, timeStamp: %lu ms", + (unsigned char)packetBody[pos], dataSize, prevTagSize, + nTimeStamp); +#endif + + if (prevTagSize != (dataSize + 11)) + { +#ifdef _DEBUG + Log(LOGWARNING, + "Tag and data size are not consitent, writing tag size according to dataSize+11: %d", + dataSize + 11); +#endif + + prevTagSize = dataSize + 11; + AMF_EncodeInt32(ptr + pos + 11 + dataSize, pend, + prevTagSize); + } + } + + pos += prevTagSize + 4; //(11+dataSize+4); + } + } + ptr += len; + + if (packet.m_packetType != 0x16) + { + /* FLV tag packets contain their own prevTagSize */ + AMF_EncodeInt32(ptr, pend, prevTagSize); + } + + /* In non-live this nTimeStamp can contain an absolute TS. + * Update ext timestamp with this absolute offset in non-live mode + * otherwise report the relative one + */ + // LogPrintf("\nDEBUG: type: %02X, size: %d, pktTS: %dms, TS: %dms, bLiveStream: %d", packet.m_packetType, nPacketLen, packet.m_nTimeStamp, nTimeStamp, bLiveStream); + r->m_read.tsm = r->Link.bLiveStream ? packet.m_nTimeStamp : nTimeStamp; + + ret = size; + break; + } + + if (rtnGetNextMediaPacket) + RTMPPacket_Free(&packet); + + if (recopy) + { + memcpy(buf, r->m_read.buf, buflen); + r->m_read.bufpos = r->m_read.buf + buflen; + r->m_read.buflen = ret - buflen; + } + return ret; +} + +static const char flvHeader[] = { 'F', 'L', 'V', 0x01, + 0x00, /* 0x04 == audio, 0x01 == video */ + 0x00, 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00, 0x00 +}; + +#define HEADERBUF (128*1024) +int +RTMP_Read(RTMP *r, char *buf, int size) +{ + int nRead = 0, total = 0; + + /* can't continue */ + if (r->m_read.status < 0) + return -1; + + /* first time thru */ + if (!r->m_read.bDidHeader) + { + if (!r->m_read.bResume) + { + char *mybuf = malloc(HEADERBUF); + r->m_read.buf = mybuf; + r->m_read.buflen = HEADERBUF; + + memcpy(mybuf, flvHeader, sizeof(flvHeader)); + r->m_read.buf += sizeof(flvHeader); + r->m_read.buflen -= sizeof(flvHeader); + + while (r->m_read.tsm == 0) + { + nRead = Read_1_Packet(r, r->m_read.buf, r->m_read.buflen); + if (nRead < 0) + { + free(mybuf); + r->m_read.buf = NULL; + r->m_read.buflen = 0; + r->m_read.status = nRead; + return -1; + } + r->m_read.buf += nRead; + r->m_read.buflen -= nRead; + } + mybuf[4] = r->m_read.dataType; + r->m_read.buflen = r->m_read.buf - mybuf; + r->m_read.buf = mybuf; + r->m_read.bufpos = mybuf; + } + r->m_read.bDidHeader = true; + } + + /* If there's leftover data buffered, use it up */ + if (r->m_read.buf) + { + nRead = r->m_read.buflen; + if (nRead > size) + nRead = size; + memcpy(buf, r->m_read.bufpos, nRead); + r->m_read.buflen -= nRead; + if (!r->m_read.buflen) + { + free(r->m_read.buf); + r->m_read.buf = NULL; + r->m_read.bufpos = NULL; + } + else + { + r->m_read.bufpos += nRead; + } + buf += nRead; + total += nRead; + size -= nRead; + } + + while (size > 0 && (nRead = Read_1_Packet(r, buf, size)) >= 0) + { + buf += nRead; + total += nRead; + size -= nRead; + } + if (nRead < 0) + r->m_read.status = nRead; + + if (size < 0) + total += size; + return total; +} diff --git a/librtmp/rtmp.h b/librtmp/rtmp.h index d0b1817..179f37b 100644 --- a/librtmp/rtmp.h +++ b/librtmp/rtmp.h @@ -177,6 +177,36 @@ extern "C" #endif } RTMP_LNK; + /* state for read() wrapper */ + typedef struct RTMP_READ + { + char *buf; + char *bufpos; + unsigned int buflen; + uint32_t tsm; + uint8_t dataType; + uint8_t bResume; + uint8_t bDidHeader; + int8_t status; +#define RTMP_READ_COMPLETE -3 +#define RTMP_READ_ERROR -2 +#define RTMP_READ_EOF -1 +#define RTMP_READ_IGNORE 0 + + /* if bResume == TRUE */ + uint8_t initialFrameType; + uint8_t bStopIgnoring; + uint8_t bFoundKeyframe; + uint8_t bFoundFlvKeyframe; + uint32_t nResumeTS; + char *metaHeader; + char *initialFrame; + uint32_t nMetaHeaderSize; + uint32_t nInitialFrameSize; + uint32_t nIgnoredFrameCounter; + uint32_t nIgnoredFlvFrameCounter; + } RTMP_READ; + typedef struct RTMP { int m_inChunkSize; @@ -193,9 +223,9 @@ extern "C" int m_nServerBW; int m_nClientBW; uint8_t m_nClientBW2; - bool m_bPlaying; - bool m_bSendEncoding; - bool m_bSendCounter; + uint8_t m_bPlaying; + uint8_t m_bSendEncoding; + uint8_t m_bSendCounter; AVal *m_methodCalls; /* remote method calls queue */ int m_numCalls; @@ -217,6 +247,7 @@ extern "C" int m_unackd; AVal m_clientID; + RTMP_READ m_read; RTMPSockBuf m_sb; } RTMP; @@ -280,6 +311,7 @@ extern "C" bool RTMP_SendSeek(RTMP *r, double dTime); bool RTMP_SendServerBW(RTMP *r); void RTMP_DropRequest(RTMP *r, int i, bool freeit); + int RTMP_Read(RTMP *r, char *buf, int size); #ifdef CRYPTO /* hashswf.c */