static int flv_read_metabody(flv* p, format_reader* Reader, int64_t next_pos) { AMFDataType type; char buffer[11]; //only needs to hold the string "onMetaData". Anything longer is something we don't want. //first object needs to be "onMetaData" string type = Reader->Read8(Reader); if(type != AMF_DATA_TYPE_STRING || amf_get_string(p,Reader, buffer, sizeof(buffer)) < 0 || tcscmp(buffer, "onMetaData")) return -1; //parse the second object (we want a mixed array) if(amf_parse_object(p,Reader, buffer, next_pos, 0) < 0) return -1; return 0; }
static int flv_read_metabody(AVFormatContext *s, int64_t next_pos) { AMFDataType type; AVStream *stream, *astream, *vstream, *dstream; AVIOContext *ioc; int i; char buffer[11]; //only needs to hold the string "onMetaData". Anything longer is something we don't want. vstream = astream = dstream = NULL; ioc = s->pb; //first object needs to be "onMetaData" string type = avio_r8(ioc); if (type != AMF_DATA_TYPE_STRING || amf_get_string(ioc, buffer, sizeof(buffer)) < 0) return -1; if (!strcmp(buffer, "onTextData")) return 1; if (strcmp(buffer, "onMetaData")) return -1; //find the streams now so that amf_parse_object doesn't need to do the lookup every time it is called. for(i = 0; i < s->nb_streams; i++) { stream = s->streams[i]; if(stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) vstream = stream; else if(stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) astream = stream; else if(stream->codec->codec_type == AVMEDIA_TYPE_DATA) dstream = stream; } //parse the second object (we want a mixed array) if(amf_parse_object(s, astream, vstream, buffer, next_pos, 0) < 0) return -1; return 0; }
static int amf_parse_object(AVFormatContext *s, AVStream *astream, AVStream *vstream, const char *key, int64_t max_pos, int depth) { AVCodecContext *acodec, *vcodec; AVIOContext *ioc; AMFDataType amf_type; char str_val[256]; double num_val; num_val = 0; ioc = s->pb; amf_type = avio_r8(ioc); switch(amf_type) { case AMF_DATA_TYPE_NUMBER: num_val = av_int2double(avio_rb64(ioc)); break; case AMF_DATA_TYPE_BOOL: num_val = avio_r8(ioc); break; case AMF_DATA_TYPE_STRING: if(amf_get_string(ioc, str_val, sizeof(str_val)) < 0) return -1; break; case AMF_DATA_TYPE_OBJECT: if ((vstream || astream) && ioc->seekable && key && !strcmp(KEYFRAMES_TAG, key) && depth == 1) if (parse_keyframes_index(s, ioc, vstream ? vstream : astream, max_pos) < 0) av_log(s, AV_LOG_ERROR, "Keyframe index parsing failed\n"); while (avio_tell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) { if (amf_parse_object(s, astream, vstream, str_val, max_pos, depth + 1) < 0) return -1; //if we couldn't skip, bomb out. } if(avio_r8(ioc) != AMF_END_OF_OBJECT) return -1; break; case AMF_DATA_TYPE_NULL: case AMF_DATA_TYPE_UNDEFINED: case AMF_DATA_TYPE_UNSUPPORTED: break; //these take up no additional space case AMF_DATA_TYPE_MIXEDARRAY: avio_skip(ioc, 4); //skip 32-bit max array index while(avio_tell(ioc) < max_pos - 2 && amf_get_string(ioc, str_val, sizeof(str_val)) > 0) { //this is the only case in which we would want a nested parse to not skip over the object if(amf_parse_object(s, astream, vstream, str_val, max_pos, depth + 1) < 0) return -1; } if(avio_r8(ioc) != AMF_END_OF_OBJECT) return -1; break; case AMF_DATA_TYPE_ARRAY: { unsigned int arraylen, i; arraylen = avio_rb32(ioc); for(i = 0; i < arraylen && avio_tell(ioc) < max_pos - 1; i++) { if(amf_parse_object(s, NULL, NULL, NULL, max_pos, depth + 1) < 0) return -1; //if we couldn't skip, bomb out. } } break; case AMF_DATA_TYPE_DATE: avio_skip(ioc, 8 + 2); //timestamp (double) and UTC offset (int16) break; default: //unsupported type, we couldn't skip return -1; } if(depth == 1 && key) { //only look for metadata values when we are not nested and key != NULL acodec = astream ? astream->codec : NULL; vcodec = vstream ? vstream->codec : NULL; if (amf_type == AMF_DATA_TYPE_NUMBER) { if (!strcmp(key, "duration")) s->duration = num_val * AV_TIME_BASE; else if (!strcmp(key, "videodatarate") && vcodec && 0 <= (int)(num_val * 1024.0)) vcodec->bit_rate = num_val * 1024.0; else if (!strcmp(key, "audiodatarate") && acodec && 0 <= (int)(num_val * 1024.0)) acodec->bit_rate = num_val * 1024.0; else if (!strcmp(key, "datastream")) { AVStream *st = create_stream(s, 2, AVMEDIA_TYPE_DATA); if (!st) return AVERROR(ENOMEM); st->codec->codec_id = CODEC_ID_TEXT; } } if (amf_type == AMF_DATA_TYPE_OBJECT && s->nb_streams == 1 && ((!acodec && !strcmp(key, "audiocodecid")) || (!vcodec && !strcmp(key, "videocodecid")))) s->ctx_flags &= ~AVFMTCTX_NOHEADER; //If there is either audio/video missing, codecid will be an empty object if (!strcmp(key, "duration") || !strcmp(key, "filesize") || !strcmp(key, "width") || !strcmp(key, "height") || !strcmp(key, "videodatarate") || !strcmp(key, "framerate") || !strcmp(key, "videocodecid") || !strcmp(key, "audiodatarate") || !strcmp(key, "audiosamplerate") || !strcmp(key, "audiosamplesize") || !strcmp(key, "stereo") || !strcmp(key, "audiocodecid")) return 0; if(amf_type == AMF_DATA_TYPE_BOOL) { av_strlcpy(str_val, num_val > 0 ? "true" : "false", sizeof(str_val)); av_dict_set(&s->metadata, key, str_val, 0); } else if(amf_type == AMF_DATA_TYPE_NUMBER) { snprintf(str_val, sizeof(str_val), "%.f", num_val); av_dict_set(&s->metadata, key, str_val, 0); } else if (amf_type == AMF_DATA_TYPE_STRING) av_dict_set(&s->metadata, key, str_val, 0); } return 0; }
static int amf_parse_object(flv* p, format_reader* Reader, const char *key, int64_t max_pos, int depth) { AMFDataType amf_type; char str_val[256]; conv c; double num_val; num_val = 0; amf_type = Reader->Read8(Reader); switch(amf_type) { case AMF_DATA_TYPE_NUMBER: { c.src = Reader->ReadBE64(Reader); num_val =c.dst; } break; case AMF_DATA_TYPE_BOOL: num_val = Reader->Read8(Reader); break; case AMF_DATA_TYPE_STRING: if(amf_get_string(p,Reader, str_val, sizeof(str_val)) < 0) return -1; break; case AMF_DATA_TYPE_OBJECT: { while(Reader->FilePos < max_pos - 2 && amf_get_string(p,Reader, str_val, sizeof(str_val)) > 0) { if(amf_parse_object(p,Reader, str_val, max_pos, depth + 1) < 0) { return -1; //if we couldn't skip, bomb out. } } if(Reader->Read8(Reader) != AMF_END_OF_OBJECT) return -1; } break; case AMF_DATA_TYPE_NULL: case AMF_DATA_TYPE_UNDEFINED: case AMF_DATA_TYPE_UNSUPPORTED: break; //these take up no additional space case AMF_DATA_TYPE_MIXEDARRAY: Reader->Skip(Reader,4); //skip 32-bit max array index while(Reader->FilePos < max_pos - 2 && amf_get_string(p,Reader, str_val, sizeof(str_val)) > 0) { //this is the only case in which we would want a nested parse to not skip over the object if(amf_parse_object(p,Reader, str_val, max_pos, depth + 1) < 0) return -1; } if(Reader->Read8(Reader) != AMF_END_OF_OBJECT) return -1; break; case AMF_DATA_TYPE_ARRAY: { unsigned int arraylen, i; arraylen = Reader->ReadBE32(Reader); if(strcmp(key,"times")==0||strcmp(key,"filepositions")==0) { if(p->IndexNum == 0) { int32_t size; p->IndexNum = arraylen; size = sizeof(flvindex)*p->IndexNum; size = ((size+SAFETAIL-1)/SAFETAIL)*SAFETAIL; if (!AllocBlock(size,&p->IndexBuffer,0,HEAP_ANY)) return ERR_OUT_OF_MEMORY; } } p->IndexCur = 0; for(i = 0; i < arraylen && Reader->FilePos < max_pos - 1; i++) { if(amf_parse_object(p,Reader, key, max_pos, depth + 1) < 0) return -1; //if we couldn't skip, bomb out. } } break; case AMF_DATA_TYPE_DATE: Reader->Skip(Reader, 8 + 2); //timestamp (double) and UTC offset (int16) break; default: //unsupported type, we couldn't skip return -1; } if(depth == 1 &&key) { //only look for metadata values when we are not nested and key != NULL if(amf_type == AMF_DATA_TYPE_BOOL) { } else if(amf_type == AMF_DATA_TYPE_NUMBER) { if(!tcscmp(key, "duration")) p->Format.Duration = Scale(num_val, TICKSPERSEC, 1); } else if (amf_type == AMF_DATA_TYPE_STRING) { } else if(amf_type == AMF_DATA_TYPE_DATE) { } } else if(key) { if(tcscmp(key,"times")==0) { ((flvindex*)IndexBuffer(p,p->IndexCur))->times = num_val; p->IndexCur++; } else if(tcscmp(key,"filepositions")==0) { ((flvindex*)IndexBuffer(p,p->IndexCur))->pos = num_val; p->IndexCur++; } } return 0; }