/*
 * FLV demuxer
 * Copyright (c) 2003 The FFmpeg Project
 *
 * This demuxer will generate a 1 byte extradata for VP6F content.
 * It is composed of:
 *  - upper 4bits: difference between encoded width and visible width
 *  - lower 4bits: difference between encoded height and visible height
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

//#define MPXPLAY_USE_DEBUGF 1
//#define MPXPLAY_USE_DEBUGMSG 1
#define MPXPLAY_DEBUG_OUTPUT stdout

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_INFILE_FFMPG

#include "ffutils.h"
#include <string.h>

/* offsets for packed values */
#define FLV_AUDIO_SAMPLESSIZE_OFFSET 1
#define FLV_AUDIO_SAMPLERATE_OFFSET  2
#define FLV_AUDIO_CODECID_OFFSET     4

#define FLV_VIDEO_FRAMETYPE_OFFSET   4

/* bitmasks to isolate specific values */
#define FLV_AUDIO_CHANNEL_MASK    0x01
#define FLV_AUDIO_SAMPLESIZE_MASK 0x02
#define FLV_AUDIO_SAMPLERATE_MASK 0x0c
#define FLV_AUDIO_CODECID_MASK    0xf0

#define FLV_VIDEO_CODECID_MASK    0x0f
#define FLV_VIDEO_FRAMETYPE_MASK  0xf0

#define AMF_END_OF_OBJECT         0x09

#define KEYFRAMES_TAG            "keyframes"
#define KEYFRAMES_TIMESTAMP_TAG  "times"
#define KEYFRAMES_BYTEOFFSET_TAG "filepositions"

enum {
    FLV_HEADER_FLAG_HASVIDEO = 1,
    FLV_HEADER_FLAG_HASAUDIO = 4,
};

enum {
    FLV_TAG_TYPE_AUDIO = 0x08,
    FLV_TAG_TYPE_VIDEO = 0x09,
    FLV_TAG_TYPE_META  = 0x12,
};

enum {
    FLV_MONO   = 0,
    FLV_STEREO = 1,
};

enum {
    FLV_SAMPLESSIZE_8BIT  = 0,
    FLV_SAMPLESSIZE_16BIT = 1 << FLV_AUDIO_SAMPLESSIZE_OFFSET,
};

enum {
    FLV_SAMPLERATE_SPECIAL = 0, /**< signifies 5512Hz and 8000Hz in the case of NELLYMOSER */
    FLV_SAMPLERATE_11025HZ = 1 << FLV_AUDIO_SAMPLERATE_OFFSET,
    FLV_SAMPLERATE_22050HZ = 2 << FLV_AUDIO_SAMPLERATE_OFFSET,
    FLV_SAMPLERATE_44100HZ = 3 << FLV_AUDIO_SAMPLERATE_OFFSET,
};

enum {
    FLV_CODECID_PCM                  = 0,
    FLV_CODECID_ADPCM                = 1 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_MP3                  = 2 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_PCM_LE               = 3 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_NELLYMOSER_16KHZ_MONO = 4 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_NELLYMOSER_8KHZ_MONO = 5 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_NELLYMOSER           = 6 << FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_AAC                  = 10<< FLV_AUDIO_CODECID_OFFSET,
    FLV_CODECID_SPEEX                = 11<< FLV_AUDIO_CODECID_OFFSET,
};

enum {
    FLV_CODECID_H263    = 2,
    FLV_CODECID_SCREEN  = 3,
    FLV_CODECID_VP6     = 4,
    FLV_CODECID_VP6A    = 5,
    FLV_CODECID_SCREEN2 = 6,
    FLV_CODECID_H264    = 7,
};

enum {
    FLV_FRAME_KEY        = 1 << FLV_VIDEO_FRAMETYPE_OFFSET,
    FLV_FRAME_INTER      = 2 << FLV_VIDEO_FRAMETYPE_OFFSET,
    FLV_FRAME_DISP_INTER = 3 << FLV_VIDEO_FRAMETYPE_OFFSET,
};

typedef enum {
    AMF_DATA_TYPE_NUMBER      = 0x00,
    AMF_DATA_TYPE_BOOL        = 0x01,
    AMF_DATA_TYPE_STRING      = 0x02,
    AMF_DATA_TYPE_OBJECT      = 0x03,
    AMF_DATA_TYPE_NULL        = 0x05,
    AMF_DATA_TYPE_UNDEFINED   = 0x06,
    AMF_DATA_TYPE_REFERENCE   = 0x07,
    AMF_DATA_TYPE_MIXEDARRAY  = 0x08,
    AMF_DATA_TYPE_OBJECT_END  = 0x09,
    AMF_DATA_TYPE_ARRAY       = 0x0a,
    AMF_DATA_TYPE_DATE        = 0x0b,
    AMF_DATA_TYPE_LONG_STRING = 0x0c,
    AMF_DATA_TYPE_UNSUPPORTED = 0x0d,
} AMFDataType;

#define FFFLV_SYNCSEARCH_BYTES 128000

#define FLVCONT_FLAG_ERROR_SEEK (1<<0)
#define FLVCONT_FLAG_PARSEDONE_AUDIO (1<<4)
#define FLVCONT_FLAG_PARSEDONE_VIDEO (1<<5)
#define FLVCONT_FLAG_PARSEDONE_META  (1<<6)
#define FLVCONT_FLAG_PARSEDONE_ALL (FLVCONT_FLAG_PARSEDONE_AUDIO|FLVCONT_FLAG_PARSEDONE_VIDEO|FLVCONT_FLAG_PARSEDONE_META)

typedef struct {
 int wrong_dts; ///< wrong dts due to negative cts
 uint32_t cntrl_flags;
 mpxp_filesize_t header_size;
 mpxp_filesize_t pktpos_curr,pktpos_next;
 uint32_t pkt_type;
 int32_t  pktsize_curr;
 int64_t  dts;
 uint32_t pkt_flags;
 AVStream *pkt_st;
} FLVContext;

static int flv_read_packet(AVFormatContext *s, AVPacket *pkt);

static void flv_set_audio_codec(AVFormatContext *s, AVStream *astream, int flv_codecid)
{
 AVCodecContext *acodec = astream->codec;
 switch(flv_codecid) {
  //no distinction between S16 and S8 PCM codec flags
  case FLV_CODECID_PCM:
   acodec->codec_id = (acodec->bits_per_coded_sample==8)? CODEC_ID_PCM_U8 :
#if HAVE_BIGENDIAN
                                CODEC_ID_PCM_S16BE;
#else
                                CODEC_ID_PCM_S16LE;
#endif
   break;
  case FLV_CODECID_PCM_LE:
   acodec->codec_id = acodec->bits_per_coded_sample == 8 ? CODEC_ID_PCM_U8 : CODEC_ID_PCM_S16LE; break;
  case FLV_CODECID_AAC  : acodec->codec_id = CODEC_ID_AAC; break;
  case FLV_CODECID_ADPCM: acodec->codec_id = CODEC_ID_ADPCM_SWF; break;
  case FLV_CODECID_SPEEX:
   acodec->codec_id = CODEC_ID_SPEEX;
   acodec->sample_rate = 16000;
   break;
  case FLV_CODECID_MP3:
   acodec->codec_id = CODEC_ID_MP3;
   astream->need_parsing = AVSTREAM_PARSE_FULL;
   break;
  case FLV_CODECID_NELLYMOSER_8KHZ_MONO:
   acodec->sample_rate = 8000; //in case metadata does not otherwise declare samplerate
   acodec->codec_id = CODEC_ID_NELLYMOSER;
   break;
  case FLV_CODECID_NELLYMOSER_16KHZ_MONO:
   acodec->sample_rate = 16000;
   acodec->codec_id = CODEC_ID_NELLYMOSER;
   break;
  case FLV_CODECID_NELLYMOSER:
   acodec->codec_id = CODEC_ID_NELLYMOSER;
   break;
  default:
   mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "Unsupported audio codec (%x)", flv_codecid >> FLV_AUDIO_CODECID_OFFSET);
   acodec->codec_tag = flv_codecid >> FLV_AUDIO_CODECID_OFFSET;
 }
}

static int flv_set_video_codec(AVFormatContext *s, AVStream *vstream, int flv_codecid)
{
 AVCodecContext *vcodec = vstream->codec;
 switch(flv_codecid) {
  case FLV_CODECID_H263  : vcodec->codec_id = CODEC_ID_FLV1   ; break;
  case FLV_CODECID_SCREEN: vcodec->codec_id = CODEC_ID_FLASHSV; break;
  case FLV_CODECID_SCREEN2:vcodec->codec_id = CODEC_ID_FLASHSV2; break;
  case FLV_CODECID_VP6   : vcodec->codec_id = CODEC_ID_VP6F   ;
  case FLV_CODECID_VP6A  :
   if(flv_codecid == FLV_CODECID_VP6A)
    vcodec->codec_id = CODEC_ID_VP6A;
   if(!vcodec->extradata_size){
    vcodec->extradata_size = 1;
    vcodec->extradata = pds_malloc(1);
   }
   vcodec->extradata[0] = s->fbfs->get_byte(s->fbds);
   return 1; // 1 byte body size adjustment for flv_read_packet()
  case FLV_CODECID_H264:
   vcodec->codec_id = CODEC_ID_H264;
   return 3; // not 4, reading packet type will consume one byte
  default:
   mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "Unsupported video codec (%x)", flv_codecid);
   vcodec->codec_tag = flv_codecid;
 }
 return 0;
}

static int amf_get_string(AVFormatContext *s, char *buffer, int buffsize)
{
 int length = s->fbfs->get_be16(s->fbds);
 if(length >= buffsize){
  s->fbfs->fseek(s->fbds,length,SEEK_CUR);
  return -1;
 }

 s->fbfs->fread(s->fbds,buffer,length);

 buffer[length] = '\0';

 return length;
}

/*static int parse_keyframes_index(AVFormatContext *s, AVStream *vstream, int64_t max_pos)
{
    unsigned int timeslen = 0, fileposlen = 0, i;
    char str_val[256];
    int64_t *times = NULL;
    int64_t *filepositions = NULL;
    int ret = AVERROR(ENOSYS);
    int64_t initial_pos = s->fbfs->ftell(s->fbds);

    while (s->fbfs->ftell(s->fbds) < max_pos - 2 && amf_get_string(s, str_val, sizeof(str_val)) > 0) {
        int64_t** current_array;
        unsigned int arraylen;

        // Expect array object in context
        if (s->fbfs->get_byte(s->fbds) != AMF_DATA_TYPE_ARRAY)
            break;

        arraylen = s->fbfs->get_be32(s->fbds);
        if(arraylen>>28)
            break;

        if       (!strcmp(KEYFRAMES_TIMESTAMP_TAG , str_val) && !times){
            current_array= &times;
            timeslen= arraylen;
        }else if (!strcmp(KEYFRAMES_BYTEOFFSET_TAG, str_val) && !filepositions){
            current_array= &filepositions;
            fileposlen= arraylen;
        }else // unexpected metatag inside keyframes, will not use such metadata for indexing
            break;

        if (!(*current_array = pds_calloc(sizeof(**current_array),arraylen))) {
            ret = AVERROR(ENOMEM);
            goto finish;
        }

        for (i = 0; (i<arraylen) && (s->fbfs->ftell(s->fbds) < (max_pos - 1)); i++) {
            if (s->fbfs->get_byte(s->fbds) != AMF_DATA_TYPE_NUMBER)
                goto finish;
            current_array[0][i] = mpxplay_av_int2dbl(s->fbfs->get_be64(s->fbds));
        }
        if (times && filepositions) {
            // All done, exiting at a position allowing amf_parse_object
            // to finish parsing the object
            ret = 0;
            break;
        }
    }

    if (timeslen == fileposlen) {
         //for(i = 0; i < timeslen; i++)
         //    mpxplay_av_add_index_entry(vstream, filepositions[i], times[i]*1000, 0, 0, AVINDEX_KEYFRAME);
    } else
        av_log(s, AV_LOG_WARNING, "Invalid keyframes object, skipping.\n");

finish:
    mpxplay_av_freep(&times);
    mpxplay_av_freep(&filepositions);
    s->fbfs->fseek(s->fbds, initial_pos, SEEK_SET);
    return ret;
}*/

static int amf_parse_object(AVFormatContext *s, AVStream *astream, AVStream *vstream, const char *key, int64_t max_pos, int depth)
{
 AVCodecContext *acodec, *vcodec;
 AMFDataType amf_type;
 char str_val[256];
 double num_val;

 num_val = 0;
 amf_type = s->fbfs->get_byte(s->fbds);
 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"amf type: %d",(int)amf_type);

 switch(amf_type) {
  case AMF_DATA_TYPE_NUMBER:
   num_val = mpxplay_av_int2dbl(s->fbfs->get_be64(s->fbds)); break;
  case AMF_DATA_TYPE_BOOL:
   num_val = s->fbfs->get_byte(s->fbds); break;
  case AMF_DATA_TYPE_STRING:
   if(amf_get_string(s, str_val, sizeof(str_val)) < 0)
    return -1;
   break;
  case AMF_DATA_TYPE_OBJECT:
   {
   unsigned int keylen;

   //if(ioc->seekable && key && !strcmp(KEYFRAMES_TAG, key) && depth == 1)
   // if(parse_keyframes_index(s, vstream, max_pos) < 0)
   //  av_log(s, AV_LOG_ERROR, "Keyframe index parsing failed\n");

   while((s->fbfs->ftell(s->fbds) < (max_pos-2)) && (keylen = s->fbfs->get_be16(s->fbds))) {
    s->fbfs->fseek(s->fbds, keylen, SEEK_CUR); //skip key string
    if(amf_parse_object(s, NULL, NULL, NULL, max_pos, depth + 1) < 0)
     return -1; //if we couldn't skip, bomb out.
   }
   if(s->fbfs->get_byte(s->fbds) != 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:
   s->fbfs->fseek(s->fbds, 4, SEEK_CUR); //skip 32-bit max array index
   while((s->fbfs->ftell(s->fbds) < (max_pos - 2)) && (amf_get_string(s, 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(s->fbfs->get_byte(s->fbds) != AMF_END_OF_OBJECT)
    return -1;
   break;
  case AMF_DATA_TYPE_ARRAY:
   {
    unsigned int arraylen, i;

    arraylen = s->fbfs->get_be32(s->fbds);
    for(i = 0; (i<arraylen) && (s->fbfs->ftell(s->fbds) < (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:
   s->fbfs->fseek(s->fbds, 8 + 2, SEEK_CUR); //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_BOOL) {
   mpxplay_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);
   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(amf_type == AMF_DATA_TYPE_STRING){
   //av_dict_set(&s->metadata, key, str_val, 0);
  }
 }

 return 0;
}

static int flv_read_metabody(AVFormatContext *s, AMFDataType type, int64_t next_pos)
{
 int i;
 AVStream *stream, *astream=NULL, *vstream=NULL;
 char buffer[11]; //only needs to hold the string "onMetaData". Anything longer is something we don't want.

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"META type:%d next:%d",(int)type,(int)next_pos);

 //first object needs to be "onMetaData" string
 if((type!=AMF_DATA_TYPE_STRING) || (amf_get_string(s,buffer,sizeof(buffer))<0) || strcmp(buffer, "onMetaData")){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"META fail 1");
  return -1; // !!! don't modify
 }

 //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_AUDIO)
   astream = stream;
  else if(stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
   vstream = stream;
 }

 //parse the second object (we want a mixed array)
 if(amf_parse_object(s, astream, vstream, buffer, next_pos, 0) < 0){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"META fail 2");
  return -2; // !!!
 }

 return 0;
}

static AVStream *create_stream(AVFormatContext *s, int is_audio)
{
 AVStream *st = mpxplay_av_new_stream(s, is_audio);
 if(!st)
  return NULL;
 st->codec->codec_type = (is_audio)? AVMEDIA_TYPE_AUDIO : AVMEDIA_TYPE_VIDEO;
 mpxplay_av_set_pts_info(st, 32, 1, 1000); // 32 bit pts in ms
 return st;
}

static int flv_read_header(AVFormatContext *s, AVPacket *pkt)
{
 FLVContext *flv;
 int offset, flags, retry;
 uint8_t head[4];

 if(!s || !pkt || !s->priv_data)
  return AVERROR(ENOMEM);

 if(s->fbfs->fread(s->fbds,head,4)!=4)
  return AVERROR(EIO);
 if((head[0]!='F') || (head[1]!='L') || (head[2]!='V') || (head[3]>=5))
  return -1;

 flags = s->fbfs->get_byte(s->fbds);
 if(!flags){ // FIXME: better fix needed
  flags = FLV_HEADER_FLAG_HASVIDEO | FLV_HEADER_FLAG_HASAUDIO;
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "Broken FLV file, which says no streams present, this might fail");
 }

 if((flags&(FLV_HEADER_FLAG_HASVIDEO|FLV_HEADER_FLAG_HASAUDIO)) != (FLV_HEADER_FLAG_HASVIDEO|FLV_HEADER_FLAG_HASAUDIO))
  s->ctx_flags |= AVFMTCTX_NOHEADER;

 if(flags & FLV_HEADER_FLAG_HASVIDEO){
  if(!create_stream(s, 0))
   return AVERROR(ENOMEM);
 }
 if(flags & FLV_HEADER_FLAG_HASAUDIO){
  if(!create_stream(s, 1))
   return AVERROR(ENOMEM);
 }

 offset = s->fbfs->get_be32(s->fbds);
 if(offset<=8)
  return -1;
 offset+=4; // packet_len 2
 if(s->fbfs->fseek(s->fbds, offset, SEEK_SET)!=offset)
  return -1;

 flv = s->priv_data;
 flv->header_size = offset;

 retry=32;
 do{
  int retcode=flv_read_packet(s,pkt);
  if(retcode==0){
   switch(flv->pkt_type){
    case FLV_TAG_TYPE_AUDIO:
     funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_PARSEDONE_AUDIO);
     break;
    case FLV_TAG_TYPE_VIDEO:
     funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_PARSEDONE_VIDEO);
     break;
    case FLV_TAG_TYPE_META:
     funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_PARSEDONE_META);
     break;
   }
   if((flv->cntrl_flags&FLVCONT_FLAG_PARSEDONE_ALL)==FLVCONT_FLAG_PARSEDONE_ALL)
    break; // maybe wrong if the file contains more a/v streams
  }else if((retcode<0) && (retcode!=AVERROR(EAGAIN))){
   mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"read_header failed retry:%d ret:%d",retry,retcode);
   return -1;
  }
 }while(--retry);

 //if(s->pb->seekable &&
 if(!s->duration || (s->duration==AV_NOPTS_VALUE)){
  const int64_t fsize = s->fbfs->filelength(s->fbds);
  int psize;
  s->fbfs->fseek(s->fbds, fsize-4, SEEK_SET);
  psize = s->fbfs->get_be32(s->fbds);
  s->fbfs->fseek(s->fbds, fsize-3-psize, SEEK_SET);
  if(psize == s->fbfs->get_be24(s->fbds) + 11){
   uint32_t ts = s->fbfs->get_be24(s->fbds);
   ts |= s->fbfs->get_byte(s->fbds) << 24;
   s->duration = ts * (int64_t)AV_TIME_BASE / 1000;
  }
 }

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"read_header retry:%d ns:%d",retry,s->nb_streams);

 return 0;
}

static int flv_get_extradata(AVFormatContext *s, AVStream *st, int size)
{
 if(s->flags&ADFMT_FLAG_PARSESTATUS){
  pds_free(st->codec->extradata);
  st->codec->extradata = pds_calloc(1,size + FF_INPUT_BUFFER_PADDING_SIZE);
  if(!st->codec->extradata)
   return AVERROR(ENOMEM);
  st->codec->extradata_size = size;
  s->fbfs->fread(s->fbds, st->codec->extradata, st->codec->extradata_size);
 }else{
  if(s->fbfs->fseek(s->fbds, size, SEEK_CUR)<=0){
   FLVContext *flv = s->priv_data;
   funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_ERROR_SEEK);
   return AVERROR(ESPIPE);
  }
 }
 return 0;
}

static int flv_sync_packet(AVFormatContext *s)
{
 FLVContext *flv = s->priv_data;
 int i,is_audio,bytecount = FFFLV_SYNCSEARCH_BYTES;
 mpxp_filesize_t fsize = s->fbfs->filelength(s->fbds);
 uint8_t readtmp[16];

 if(flv->cntrl_flags&FLVCONT_FLAG_ERROR_SEEK){
  if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)!=flv->pktpos_curr)
   goto err_out_seek;
  funcbit_disable(flv->cntrl_flags,FLVCONT_FLAG_ERROR_SEEK);
 }else
  flv->pktpos_curr = s->fbfs->ftell(s->fbds);

 while(!s->fbfs->eof(s->fbds)){
  if((fsize-flv->pktpos_curr)<15)
   break;
  if(!(--bytecount))
   return AVERROR(EIO); // ???
  if(s->fbfs->fread(s->fbds,readtmp,1)!=1)
   return AVERROR(EIO);
  //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"SYNC0 type:%d",(int)readtmp[0]);
  switch(readtmp[0]){
   case FLV_TAG_TYPE_AUDIO:break;
   case FLV_TAG_TYPE_VIDEO:break;
   case FLV_TAG_TYPE_META:break;
   default: flv->pktpos_curr++; continue;
  }
  flv->pkt_type     = readtmp[0];
  flv->pktsize_curr = s->fbfs->get_be24(s->fbds);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"SYNC1 type:%d size:%d pos:%d sn:%d rem:%d",flv->pkt_type,flv->pktsize_curr,(long)flv->pktpos_curr,s->nb_streams,(long)(fsize-flv->pktpos_curr));
  if((flv->pktsize_curr<1) || (flv->pktsize_curr>256000)) // !!! ???
   goto try_again;
  flv->dts          = s->fbfs->get_be24(s->fbds);
  flv->dts         |= s->fbfs->get_byte(s->fbds) << 24;
                      s->fbfs->fseek(s->fbds,3,SEEK_CUR); // stream id

  flv->pktpos_next = s->fbfs->ftell(s->fbds) + flv->pktsize_curr;
  if(flv->pktpos_next > fsize)
   goto try_again;

  if((flv->pktsize_curr>1) && (flv->pktpos_next<=(fsize-19))){ // check end of this packet and begin of next packet
   uint32_t type,psize;
   mpxp_filesize_t fpos_save=s->fbfs->ftell(s->fbds);
   if(s->fbfs->fseek(s->fbds,flv->pktpos_next,SEEK_SET)!=flv->pktpos_next)
    goto err_out_read;
   psize=s->fbfs->get_be32(s->fbds); // packetlen2 of curr packet
   type=s->fbfs->get_byte(s->fbds);  // type of next packet
   if(((type!=FLV_TAG_TYPE_AUDIO) && (type!=FLV_TAG_TYPE_VIDEO) && (type!=FLV_TAG_TYPE_META))
    || ( (psize!=(flv->pktsize_curr+11)) && (!(s->flags&ADFMT_FLAG_PARSESTATUS) || (flv->pkt_type!=FLV_TAG_TYPE_META)) ) // hack for some incorrect files (without packetlen2 in META packet)
   ){
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"t2:%d p2:%d p1:%d",type,psize,(int)(flv->pktsize_curr+11));
    goto try_again; // ???
   }
   if(s->fbfs->fseek(s->fbds,fpos_save,SEEK_SET)!=fpos_save)
    goto err_out_read;
  }

  if(s->fbfs->fread(s->fbds,readtmp,1)!=1)
   goto err_out_read;

  flv->pkt_flags = readtmp[0];
  is_audio = 0;

  switch(flv->pkt_type){
   case FLV_TAG_TYPE_AUDIO:
    flv->pktsize_curr--;
    is_audio=1;
    break;
   case FLV_TAG_TYPE_VIDEO:
    if((flv->pkt_flags & 0xf0) == 0x50) // video info / command frame
     goto skip_packet;
    flv->pktsize_curr--;
    break;
   case FLV_TAG_TYPE_META:
    if(flv->pktsize_curr <= (13+1+4))
     goto try_again;
    if(s->flags&ADFMT_FLAG_PARSESTATUS){
     int ret = flv_read_metabody(s, flv->pkt_flags, flv->pktpos_next);
     if(ret==-1){
      mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"read_meta failed");
      goto try_again;
     }
    }
    goto skip_packet;
   default:
    goto try_again;
  }
  if(flv->pktsize_curr>0){
   for(i=0; i<s->nb_streams; i++){
    flv->pkt_st = s->streams[i];
    if(flv->pkt_st->id == is_audio)
     break;
   }
   if(i == s->nb_streams){
    mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"new stream");
    if(s->flags&ADFMT_FLAG_PARSESTATUS){
     flv->pkt_st = create_stream(s, is_audio);
     if(!flv->pkt_st)
      return AVERROR(ENOMEM);
     funcbit_disable(s->ctx_flags,AVFMTCTX_NOHEADER);
    }else
     goto skip_packet;
   }
   if( (flv->pkt_st->discard >= AVDISCARD_NONKEY && !( ((flv->pkt_flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY) || is_audio))
       || (flv->pkt_st->discard >= AVDISCARD_BIDIR  && ( ((flv->pkt_flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_DISP_INTER) && !is_audio))
       || (flv->pkt_st->discard >= AVDISCARD_ALL)
   ){
    goto skip_packet;
   }
   return 0;
  }

try_again:
  flv->pktpos_curr++;
  if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)!=flv->pktpos_curr)
   goto err_out_seek;
  continue;
skip_packet:
  flv->pktpos_curr=flv->pktpos_next+4;
  if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)!=flv->pktpos_curr)
   goto err_out_seek;
 }

 return AVERROR_EOF;

err_out_read:
 if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)==flv->pktpos_curr) // restore packet begin pos
  return AVERROR(EIO); // not enough data to read the packet completely
err_out_seek:
 if(s->fbfs->eof(s->fbds) || (flv->pktpos_curr>=fsize))
  return AVERROR_EOF;
 funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_ERROR_SEEK);
 return AVERROR(ESPIPE);
}

static int flv_read_packet(AVFormatContext *s, AVPacket *pkt)
{
 FLVContext *flv;
 int ret, is_audio, size;
 int64_t pts=AV_NOPTS_VALUE;
 AVStream *st;

 if(!s || !pkt || !s->priv_data)
  return AVERROR(ENOMEM);

 ret = flv_sync_packet(s);
 if(ret<0){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"ret: %d",ret);
  return ret;
 }

 flv=s->priv_data;
 st=flv->pkt_st;
 is_audio=(flv->pkt_type==FLV_TAG_TYPE_AUDIO)? 1:0;
 size=flv->pktsize_curr;

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "PACKET type:%d, size:%d, dts:%"PRId64" pos:%d", flv->pkt_type, size, flv->dts, (long)flv->pktpos_curr);

 if(is_audio){
  if(!st->codec->channels || !st->codec->sample_rate || !st->codec->bits_per_coded_sample){
   st->codec->channels = ((flv->pkt_flags&FLV_AUDIO_CHANNEL_MASK)==FLV_STEREO)? 2:1;
   st->codec->sample_rate = (44100 << ((flv->pkt_flags & FLV_AUDIO_SAMPLERATE_MASK) >> FLV_AUDIO_SAMPLERATE_OFFSET) >> 3);
   st->codec->bits_per_coded_sample = (flv->pkt_flags & FLV_AUDIO_SAMPLESIZE_MASK) ? 16 : 8;
  }
  if(!st->codec->codec_id){
   flv_set_audio_codec(s, st, flv->pkt_flags & FLV_AUDIO_CODECID_MASK);
  }
 }else{
  size -= flv_set_video_codec(s, st, flv->pkt_flags & FLV_VIDEO_CODECID_MASK);
 }

 if((st->codec->codec_id==CODEC_ID_AAC) || (st->codec->codec_id==CODEC_ID_H264)){
  int type = s->fbfs->get_byte(s->fbds);
  size--;
  if(st->codec->codec_id == CODEC_ID_H264){
   int32_t cts = (s->fbfs->get_be24(s->fbds)+0xff800000)^0xff800000; // sign extension
   pts = flv->dts + cts;
   if(cts < 0){ // dts are wrong
    flv->wrong_dts = 1;
    av_log(s, AV_LOG_WARNING, "negative cts, previous timestamps might be wrong\n");
   }
   if(flv->wrong_dts)
    flv->dts = AV_NOPTS_VALUE;
  }

  if(type == 0){
   if((ret = flv_get_extradata(s, st, size)) < 0)
    return ret;
   if(st->codec->codec_id == CODEC_ID_AAC) {
    /*MPEG4AudioConfig cfg;
    ff_mpeg4audio_get_config(&cfg, st->codec->extradata, st->codec->extradata_size);
    st->codec->channels = cfg.channels;
    if(cfg.ext_sample_rate)
     st->codec->sample_rate = cfg.ext_sample_rate;
    else
     st->codec->sample_rate = cfg.sample_rate;*/
    //av_dlog(s, "mp4a config channels %d sample rate %d\n",st->codec->channels, st->codec->sample_rate);
   }
   ret = AVERROR(EAGAIN);
   goto leave;
  }
 }

 if(size<=0) {
  ret = AVERROR(EAGAIN);
  goto leave;
 }

 ret = s->fbfs->fread(s->fbds, pkt->data, size);
 if((ret<=0) || ((ret!=size) && !s->fbfs->eof(s->fbds))){
  if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)!=flv->pktpos_curr){
   funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_ERROR_SEEK);
   return AVERROR(ESPIPE);
  }
  return AVERROR(EIO);
 }

 memset(pkt->data+ret, 0, FF_INPUT_BUFFER_PADDING_SIZE);
 pkt->size = ret;
 pkt->dts  = flv->dts;
 pkt->pts  = (pts==AV_NOPTS_VALUE)? flv->dts : pts;
 pkt->stream_index = st->index;

 if(is_audio || ((flv->pkt_flags & FLV_VIDEO_FRAMETYPE_MASK) == FLV_FRAME_KEY))
  pkt->flags |= AV_PKT_FLAG_KEY;

leave:
 flv->pktpos_curr=flv->pktpos_next+4;
 if(flv->pktpos_curr>=s->fbfs->filelength(s->fbds))
  ret=AVERROR_EOF;
 else if(s->fbfs->fseek(s->fbds, flv->pktpos_curr, SEEK_SET)<0){
  if(s->fbfs->eof(s->fbds))
   ret=AVERROR_EOF;
  else{
   funcbit_enable(flv->cntrl_flags,FLVCONT_FLAG_ERROR_SEEK);
   ret=AVERROR(ESPIPE);
  }
 }
 return ret;
}

//#define FLV_SEEKSKIP_BACKWARD_TIME (1*AV_TIME_BASE)
//#define FLV_SEEKSRCH_BACKWARD_TIME (10*AV_TIME_BASE)
//#define FLV_SEEKSRCH_BACKWARD_RETRY 10

static int64_t flv_read_seek(AVFormatContext *s, int stream_index,
                              int64_t timestamp_av, int flags)
{
 FLVContext *flv;
 mpxp_filesize_t fsize,fpos;

 if(!s || !s->priv_data)
  return -1;

 flv = s->priv_data;

 fsize=s->fbfs->filelength(s->fbds);
 if(fsize<=flv->header_size)
  return -1;
 fsize-=(mpxp_filesize_t)flv->header_size;
 fpos=(mpxp_filesize_t)((float)timestamp_av/(float)s->duration*(float)fsize);
 fpos+=(mpxp_filesize_t)flv->header_size;

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"avt:%"PRIi64" fpos:%"PRIi64"",timestamp_av,fpos);

 if(s->fbfs->fseek(s->fbds,fpos,SEEK_SET)!=fpos)
  return -1;

/* if(flags&AVSEEK_FLAG_BACKWARD){
  if(timestamp_av>FLV_SEEKSKIP_BACKWARD_TIME)
   timestamp_av-=FLV_SEEKSKIP_BACKWARD_TIME;
  else
   timestamp_av=0;
 }

 funcbit_disable(flv->cntrl_flags,(FLVCONT_FLAG_ERROR_READ|FLVCONT_FLAG_ERROR_SEEK));

 if(flags&AVSEEK_FLAG_BACKWARD){
  unsigned int retry=FLV_SEEKSRCH_BACKWARD_RETRY;
  do{
   if(flv_sync_packet(s)==0){
    if(flv->pkt_type==FLV_TAG_TYPE_AUDIO) // !!!
     break;
    continue;
   }
   if(!timestamp_av)
    return -1;
   if(!(--retry))
    return -1;
   timestamp_av=(timestamp_av>FLV_SEEKSRCH_BACKWARD_TIME)? (timestamp_av-FLV_SEEKSRCH_BACKWARD_TIME):0;
   fpos=(int64_t)((float)timestamp_av/(float)s->duration*(float)fsize);
   fpos+=offset;
   if(s->fbfs->fseek(s->fbds,fpos,SEEK_SET)!=fpos)
    return -1;
  }while(1);
 }else{
  if(flv_sync_packet(s)<0)
   return -1;
 }
 if(s->fbfs->fseek(s->fbds,flv->pktpos_curr,SEEK_SET)!=flv->pktpos_curr)
  return -1;*/

 return timestamp_av;
}

AVInputFormat mpxplay_ff_flv_demuxer = {
 sizeof(FLVContext),
 flv_read_header,
 flv_read_packet,
 NULL,
 flv_read_seek
};

#endif // MPXPLAY_LINK_INFILE_FFMPG
