int OpenResumeFile(const char *flvFile, // file name [in] FILE ** file, // opened file [out] off_t * size, // size of the file [out] char **metaHeader, // meta data read from the file [out] uint32_t * nMetaHeaderSize, // length of metaHeader [out] double *duration) // duration of the stream in ms [out] { size_t bufferSize = 0; char hbuf[16], *buffer = NULL; *nMetaHeaderSize = 0; *size = 0; *file = fopen(flvFile, "r+b"); if (!*file) return RD_SUCCESS; // RD_SUCCESS, because we go to fresh file mode instead of quiting fseek(*file, 0, SEEK_END); *size = ftello(*file); fseek(*file, 0, SEEK_SET); if (*size > 0) { // verify FLV format and read header uint32_t prevTagSize = 0; // check we've got a valid FLV file to continue! if (fread(hbuf, 1, 13, *file) != 13) { RTMP_Log(RTMP_LOGERROR, "Couldn't read FLV file header!"); return RD_FAILED; } if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V' || hbuf[3] != 0x01) { RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!"); return RD_FAILED; } if ((hbuf[4] & 0x05) == 0) { RTMP_Log(RTMP_LOGERROR, "FLV file contains neither video nor audio, aborting!"); return RD_FAILED; } uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5); fseek(*file, dataOffset, SEEK_SET); if (fread(hbuf, 1, 4, *file) != 4) { RTMP_Log(RTMP_LOGERROR, "Invalid FLV file: missing first prevTagSize!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(hbuf); if (prevTagSize != 0) { RTMP_Log(RTMP_LOGWARNING, "First prevTagSize is not zero: prevTagSize = 0x%08X", prevTagSize); } // go through the file to find the meta data! off_t pos = dataOffset + 4; int bFoundMetaHeader = FALSE; while (pos < *size - 4 && !bFoundMetaHeader) { fseeko(*file, pos, SEEK_SET); if (fread(hbuf, 1, 4, *file) != 4) break; uint32_t dataSize = AMF_DecodeInt24(hbuf + 1); if (hbuf[0] == 0x12) { if (dataSize > bufferSize) { /* round up to next page boundary */ bufferSize = dataSize + 4095; bufferSize ^= (bufferSize & 4095); free(buffer); buffer = (char *)malloc(bufferSize); if (!buffer) return RD_FAILED; } fseeko(*file, pos + 11, SEEK_SET); if (fread(buffer, 1, dataSize, *file) != dataSize) break; AMFObject metaObj; int nRes = AMF_Decode(&metaObj, buffer, dataSize, FALSE); if (nRes < 0) { RTMP_Log(RTMP_LOGERROR, "%s, error decoding meta data packet", __FUNCTION__); break; } AVal metastring; AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring); if (AVMATCH(&metastring, &av_onMetaData)) { AMF_Dump(&metaObj); *nMetaHeaderSize = dataSize; if (*metaHeader) free(*metaHeader); *metaHeader = (char *) malloc(*nMetaHeaderSize); memcpy(*metaHeader, buffer, *nMetaHeaderSize); // get duration AMFObjectProperty prop; if (RTMP_FindFirstMatchingProperty (&metaObj, &av_duration, &prop)) { *duration = AMFProp_GetNumber(&prop); RTMP_Log(RTMP_LOGDEBUG, "File has duration: %f", *duration); } bFoundMetaHeader = TRUE; break; } //metaObj.Reset(); //delete obj; } pos += (dataSize + 11 + 4); } free(buffer); if (!bFoundMetaHeader) RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!"); } return RD_SUCCESS; }
int GetLastKeyframe(FILE * file, // output file [in] int nSkipKeyFrames, // max number of frames to skip when searching for key frame [in] uint32_t * dSeek, // offset of the last key frame [out] char **initialFrame, // content of the last keyframe [out] int *initialFrameType, // initial frame type (audio/video) [out] uint32_t * nInitialFrameSize) // length of initialFrame [out] { const size_t bufferSize = 16; char buffer[bufferSize]; uint8_t dataType; int bAudioOnly; off_t size; fseek(file, 0, SEEK_END); size = ftello(file); fseek(file, 4, SEEK_SET); if (fread(&dataType, sizeof(uint8_t), 1, file) != 1) return RD_FAILED; bAudioOnly = (dataType & 0x4) && !(dataType & 0x1); RTMP_Log(RTMP_LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly, (unsigned long long) size); // ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams) //if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames //{ // find the last seekable frame off_t tsize = 0; uint32_t prevTagSize = 0; // go through the file and find the last video keyframe do { int xread; skipkeyframe: if (size - tsize < 13) { RTMP_Log(RTMP_LOGERROR, "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0"); return RD_FAILED; } fseeko(file, size - tsize - 4, SEEK_SET); xread = fread(buffer, 1, 4, file); if (xread != 4) { RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!"); return RD_FAILED; } prevTagSize = AMF_DecodeInt32(buffer); //RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize); if (prevTagSize == 0) { RTMP_Log(RTMP_LOGERROR, "Couldn't find keyframe to resume from!"); return RD_FAILED; } if (prevTagSize < 0 || prevTagSize > size - 4 - 13) { RTMP_Log(RTMP_LOGERROR, "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!", prevTagSize); return RD_FAILED; } tsize += prevTagSize + 4; // read header fseeko(file, size - tsize, SEEK_SET); if (fread(buffer, 1, 12, file) != 12) { RTMP_Log(RTMP_LOGERROR, "Couldn't read header!"); return RD_FAILED; } //* #ifdef _DEBUG uint32_t ts = AMF_DecodeInt24(buffer + 4); ts |= (buffer[7] << 24); RTMP_Log(RTMP_LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts); #endif //*/ // this just continues the loop whenever the number of skipped frames is > 0, // so we look for the next keyframe to continue with // // this helps if resuming from the last keyframe fails and one doesn't want to start // the download from the beginning // if (nSkipKeyFrames > 0 && !(!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10))) { #ifdef _DEBUG RTMP_Log(RTMP_LOGDEBUG, "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!"); #endif nSkipKeyFrames--; goto skipkeyframe; } } while ((bAudioOnly && buffer[0] != 0x08) || (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10))); // as long as we don't have a keyframe / last audio frame // save keyframe to compare/find position in stream *initialFrameType = buffer[0]; *nInitialFrameSize = prevTagSize - 11; *initialFrame = (char *) malloc(*nInitialFrameSize); fseeko(file, size - tsize + 11, SEEK_SET); if (fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize) { RTMP_Log(RTMP_LOGERROR, "Couldn't read last keyframe, aborting!"); return RD_FAILED; } *dSeek = AMF_DecodeInt24(buffer + 4); // set seek position to keyframe tmestamp *dSeek |= (buffer[7] << 24); //} //else // handle audio only, we can seek anywhere we'd like //{ //} if (*dSeek < 0) { RTMP_Log(RTMP_LOGERROR, "Last keyframe timestamp is negative, aborting, your file is corrupt!"); return RD_FAILED; } RTMP_Log(RTMP_LOGDEBUG, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek, *nInitialFrameSize, *initialFrameType); /* // now read the timestamp of the frame before the seekable keyframe: fseeko(file, size-tsize-4, SEEK_SET); if(fread(buffer, 1, 4, file) != 4) { RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!"); goto start; } uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer); fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET); if(fread(buffer, 1, 4, file) != 4) { RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!"); goto start; } uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer); timestamp |= (buffer[3]<<24); RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp); */ if (*dSeek != 0) { // seek to position after keyframe in our file (we will ignore the keyframes resent by the server // since they are sent a couple of times and handling this would be a mess) fseeko(file, size - tsize + prevTagSize + 4, SEEK_SET); // make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets // (including several meta data headers and the keyframe we seeked to) //bNoHeader = TRUE; if bResume==true this is true anyway } //} return RD_SUCCESS; }
static event_t * rtmp_loop(rtmp_t *r, media_pipe_t *mp, char *url, char *errbuf, size_t errlen) { RTMPPacket p = {0}; int pos = -1, ret; uint32_t dts; event_t *e = NULL; while(1) { if(pos == -1) { mp->mp_eof = 0; ret = RTMP_GetNextMediaPacket(r->r, &p); if(ret == 2) { /* Wait for queues to drain */ mp->mp_eof = 1; again: e = mp_wait_for_empty_queues(mp); if(e != NULL) { e = rtmp_process_event(r, e, NULL); if(e == NULL) goto again; } if(e == NULL) e = event_create_type(EVENT_EOF); break; } if(ret == 0) { int64_t restartpos = r->seekpos_video; if(cancellable_is_cancelled(mp->mp_cancellable)) { snprintf(errbuf, errlen, "Cancelled"); return NULL; } TRACE(TRACE_ERROR, "RTMP", "Disconnected"); sleep(1); if(restartpos == AV_NOPTS_VALUE) { snprintf(errbuf, errlen, "Giving up restart since nothing was decoded"); return NULL; } RTMP_Close(r->r); RTMP_Init(r->r, mp->mp_cancellable); memset(&p, 0, sizeof(p)); TRACE(TRACE_DEBUG, "RTMP", "Reconnecting stream at pos %ld", restartpos); if(!RTMP_SetupURL(r->r, url)) { snprintf(errbuf, errlen, "Unable to setup RTMP session"); return NULL; } if(!RTMP_Connect(r->r, NULL, errbuf, errlen, 5000)) { return NULL; } if(!RTMP_ConnectStream(r->r, 0)) { snprintf(errbuf, errlen, "Unable to stream RTMP session"); return NULL; } if(mp->mp_flags & MP_CAN_SEEK) RTMP_SendSeek(r->r, restartpos / 1000); continue; } dts = p.m_nTimeStamp; switch(p.m_packetType) { case RTMP_PACKET_TYPE_INFO: if(handle_metadata(r, p.m_body, p.m_nBodySize, mp, errbuf, errlen)) { RTMPPacket_Free(&p); return NULL; } break; case RTMP_PACKET_TYPE_VIDEO: e = get_packet_v(r, (void *)p.m_body, p.m_nBodySize, dts, mp); break; case RTMP_PACKET_TYPE_AUDIO: e = get_packet_a(r, (void *)p.m_body, p.m_nBodySize, dts, mp); break; case 0x16: pos = 0; break; default: TRACE(TRACE_DEBUG, "RTMP", "Got unknown packet type %d\n", p.m_packetType); break; } if(pos == -1) RTMPPacket_Free(&p); } if(pos != -1) { if(pos + 11 < p.m_nBodySize) { uint32_t ds = AMF_DecodeInt24(p.m_body + pos + 1); if(pos + 11 + ds + 4 > p.m_nBodySize) { snprintf(errbuf, errlen, "Corrupt stream"); RTMPPacket_Free(&p); return NULL; } dts = AMF_DecodeInt24(p.m_body + pos + 4); dts |= (p.m_body[pos + 7] << 24); if(p.m_body[pos] == RTMP_PACKET_TYPE_INFO) { if(handle_metadata(r, p.m_body, p.m_nBodySize, mp, errbuf, errlen)) { RTMPPacket_Free(&p); return NULL; } } else if(p.m_body[pos] == RTMP_PACKET_TYPE_VIDEO) { e = get_packet_v(r, (void *)p.m_body + pos + 11, ds, dts, mp); } else if(p.m_body[pos] == RTMP_PACKET_TYPE_AUDIO) { e = get_packet_a(r, (void *)p.m_body + pos + 11, ds, dts, mp); } else { TRACE(TRACE_DEBUG, "RTMP", "Got unknown packet type %d\n", p.m_body[pos]); } pos += 11 + ds + 4; } else { pos = -1; RTMPPacket_Free(&p); } } if(e != NULL) break; } return e; }
static event_t * get_packet_v(rtmp_t *r, uint8_t *data, int size, int64_t dts, media_pipe_t *mp) { uint8_t flags; uint8_t type = 0; enum AVCodecID id; int d = 0; event_t *e; if(r->r->m_read.flags & RTMP_READ_SEEKING) return NULL; if(size < 2) return NULL; flags = *data++; size--; switch(flags & 0xf) { case 7: type = *data++; size--; id = AV_CODEC_ID_H264; if(size < 3) return NULL; d = (AMF_DecodeInt24((char *)data) + 0xff800000) ^ 0xff800000; data += 3; size -= 3; break; case 4: type = *data++; size--; id = AV_CODEC_ID_VP6F; break; default: return NULL; } if(r->vcodec == NULL) { media_codec_params_t mcp = {0}; switch(id) { case AV_CODEC_ID_H264: if(type != 0 || size < 0) return NULL; mcp.extradata = data; mcp.extradata_size = size; break; case AV_CODEC_ID_VP6F: if(size < 1) return NULL; mcp.extradata = data; mcp.extradata_size = size; break; default: abort(); } mcp.width = r->width; mcp.height = r->height; r->vcodec = media_codec_create(id, 0, NULL, NULL, &mcp, mp); return NULL; } int skip = 0; // r->last_video_dts = dts; int64_t pts = 1000LL * (dts + d); dts = 1000LL * dts; if(d < 0 || dts <= r->seekpos_video) { skip = 1; r->in_seek_skip = 1; } else if(r->in_seek_skip) { skip = 2; r->in_seek_skip = 0; } else { r->seekpos_video = dts; } e = sendpkt(r, &r->mp->mp_video, r->vcodec, dts, pts, data, size, skip, MB_VIDEO, r->vframeduration, 1); return e; }
static event_t * rtmp_loop(rtmp_t *r, media_pipe_t *mp, char *url, char *errbuf, size_t errlen) { RTMPPacket p = {0}; int pos = -1, ret; uint32_t dts; event_t *e = NULL; mp_set_playstatus_by_hold(mp, 0, NULL); while(1) { if(pos == -1) { mp->mp_eof = 0; ret = RTMP_GetNextMediaPacket(r->r, &p); if(ret == 2) { /* Wait for queues to drain */ mp->mp_eof = 1; again: e = mp_wait_for_empty_queues(mp); if(e != NULL) { e = rtmp_process_event(r, e, NULL); if(e == NULL) goto again; } mp_set_playstatus_stop(mp); if(e == NULL) e = event_create_type(EVENT_EOF); break; } if(ret == 0) { RTMP_Close(r->r); RTMP_Init(r->r); memset(&p, 0, sizeof(p)); TRACE(TRACE_DEBUG, "RTMP", "Reconnecting stream at pos %d", r->seekbase); if(!RTMP_SetupURL(r->r, url)) { snprintf(errbuf, errlen, "Unable to setup RTMP session"); e = NULL; break; } if(!RTMP_Connect(r->r, NULL)) { snprintf(errbuf, errlen, "Unable to connect RTMP session"); e = NULL; break; } if(!RTMP_ConnectStream(r->r, r->can_seek ? r->seekbase / 1000 : 0)) { snprintf(errbuf, errlen, "Unable to stream RTMP session"); return NULL; } r->epoch++; r->lastdts = 0; r->seekbase = AV_NOPTS_VALUE; mp_flush(mp, 0); continue; } dts = p.m_nTimeStamp; switch(p.m_packetType) { case RTMP_PACKET_TYPE_INFO: if(handle_metadata(r, p.m_body, p.m_nBodySize, mp, errbuf, errlen)) { RTMPPacket_Free(&p); return NULL; } break; case RTMP_PACKET_TYPE_VIDEO: e = get_packet_v(r, (void *)p.m_body, p.m_nBodySize, dts, mp); break; case RTMP_PACKET_TYPE_AUDIO: e = get_packet_a(r, (void *)p.m_body, p.m_nBodySize, dts, mp); break; case 0x16: pos = 0; break; default: TRACE(TRACE_DEBUG, "RTMP", "Got unknown packet type %d\n", p.m_packetType); break; } if(pos == -1) RTMPPacket_Free(&p); } if(pos != -1) { if(pos + 11 < p.m_nBodySize) { uint32_t ds = AMF_DecodeInt24(p.m_body + pos + 1); if(pos + 11 + ds + 4 > p.m_nBodySize) { snprintf(errbuf, errlen, "Corrupt stream"); RTMPPacket_Free(&p); return NULL; } dts = AMF_DecodeInt24(p.m_body + pos + 4); dts |= (p.m_body[pos + 7] << 24); if(p.m_body[pos] == RTMP_PACKET_TYPE_INFO) { if(handle_metadata(r, p.m_body, p.m_nBodySize, mp, errbuf, errlen)) { RTMPPacket_Free(&p); return NULL; } } else if(p.m_body[pos] == RTMP_PACKET_TYPE_VIDEO) { e = get_packet_v(r, (void *)p.m_body + pos + 11, ds, dts, mp); } else if(p.m_body[pos] == RTMP_PACKET_TYPE_AUDIO) { e = get_packet_a(r, (void *)p.m_body + pos + 11, ds, dts, mp); } else { TRACE(TRACE_DEBUG, "RTMP", "Got unknown packet type %d\n", p.m_body[pos]); } pos += 11 + ds + 4; } else { pos = -1; RTMPPacket_Free(&p); } } if(e != NULL) break; } return e; }
static event_t * get_packet_v(rtmp_t *r, uint8_t *data, size_t size, int64_t dts, media_pipe_t *mp) { uint8_t flags; uint8_t type = 0; enum CodecID id; int d = 0; event_t *e; if(r->r->m_read.flags & RTMP_READ_SEEKING) return NULL; if(size < 2) return NULL; flags = *data++; size--; switch(flags & 0xf) { case 7: type = *data++; size--; id = CODEC_ID_H264; if(size < 3) return NULL; d = (AMF_DecodeInt24((char *)data) + 0xff800000) ^ 0xff800000; data += 3; size -= 3; break; case 4: type = *data++; size--; id = CODEC_ID_VP6F; break; default: return NULL; } if(r->vcodec == NULL) { AVCodecContext *ctx; media_codec_params_t mcp = {0}; switch(id) { case CODEC_ID_H264: if(type != 0 || size < 0) return NULL; ctx = avcodec_alloc_context3(NULL); ctx->extradata = av_mallocz(size + FF_INPUT_BUFFER_PADDING_SIZE); memcpy(ctx->extradata, data, size); ctx->extradata_size = size; break; case CODEC_ID_VP6F: if(size < 1) return NULL; ctx = avcodec_alloc_context3(NULL); ctx->extradata = av_mallocz(1 + FF_INPUT_BUFFER_PADDING_SIZE); memcpy(ctx->extradata, &type, 1); ctx->extradata_size = 1; break; default: abort(); } mcp.width = r->width; mcp.height = r->height; r->vcodec = media_codec_create(id, 0, NULL, ctx, &mcp, mp); return NULL; } int skip = 0; int64_t pts = 1000LL * (dts + d); dts = 1000LL * dts; if(d < 0 || dts < r->seekpos) { skip = 1; r->in_seek_skip = 1; } else if(r->in_seek_skip) { skip = 2; r->in_seek_skip = 0; } r->lastdts = dts; e = sendpkt(r, &r->mp->mp_video, r->vcodec, dts, pts, AV_NOPTS_VALUE, data, size, skip, MB_VIDEO, r->vframeduration); return e; }
uint32_t inline amf_read_i24(const uint8_t *b) { return (uint32_t)AMF_DecodeInt24((const char *)b); }