#include "ka_stream.h"

#include <stdarg.h>
#include <string.h>

#include "audio.h"
#include "config.h"
#include "msg.h"
#include "timer1.h"
#include "ka_input.h"
#include "ka_mem.h"
#include "ka_log.h"
#include "ka_codecs.h"
#include "ka_vstream.h"

#define WAIT_PLAYABLE 2 // wait in seconds before giving up to find playable packets
#define WAIT_REPLAY   3 // wait in seconds before giving up replay
#define MAX_PES_BLOCK_SIZE 0x010100 // 64k + small header

#define PS_LOAD_SIZE   0x020000 // 128k
#define PS_BUFFER_SIZE 0x500000 // 5MB, must be a multiple of LOAD_SIZE

/*
 * List of functions for determining which demuxer to use.
 * Should be listed in decreasing order of reliability:
 * First those based
 */
static const ka_demux_selector selectors[] =
{ demux_selector_avi
, demux_selector_scanForMpeg
, NULL
};

/**
 * Creates a new stream.
 *
 * @param  inputname  Input name to create the stream for.
 *
 * @returns Stream handle or NULL in case of error.
 */
ka_stream_t* ka_new_stream(ka_error_t* pErrorBlock, const char* inputname, uint32_t hardware, uint32_t program
                         , uint32_t vchannel, uint32_t achannel, uint32_t schannel)
{
  ka_stream_t* stream = ka_mem_calloc(sizeof(*stream));
  if (!stream)
  {
    ka_error_fill(pErrorBlock, ka_error_nomem);
    return NULL;
  }

  stream->inputname = inputname;

  timer_install();             // reset both time counters

  stream->pErrorBlock = pErrorBlock;
  stream->buf = ka_new_buffer(pErrorBlock, MAX_PES_BLOCK_SIZE, PS_BUFFER_SIZE, PS_LOAD_SIZE);
  stream->stack = ka_new_stack(pErrorBlock);
  stream->demux.vars = ka_new_vars();
  stream->audio.astream = audio_new(pErrorBlock);
  stream->audio.saver = audiosaver_new(pErrorBlock);
  stream->subtitle.sstream = ka_new_subtitles(pErrorBlock, stream->demux.vars);
  stream->demux.pSelector = &selectors[0];
  stream->hardware = hardware;
  stream->decodeSection = -1;
  stream->displaySection = -1;

  if (!stream->buf
  ||  !stream->stack
  ||  !stream->audio.astream
  ||  !stream->audio.saver
  ||  !stream->subtitle.sstream
  ||  !stream->demux.vars)
  {
    ka_delete_stream(&stream);
    return NULL;
  }

  stream->program = program;
  stream->video.channel = vchannel;
  stream->audio.channel = achannel;
  stream->subtitle.channel = schannel;

  return stream;
}

/**
 * Deletes a stream.
 *
 * @param  stream  Stream handle.
 */
void ka_delete_stream(ka_stream_t** pstream)
{
  ka_stream_t* stream = *pstream;
  *pstream = NULL;

  if (!stream)
    return;

  ka_delete_input(&stream->input);
  ka_delete_buffer(&stream->buf);
  ka_delete_stack(&stream->stack);
  if (stream->demux.demuxer) stream->demux.demuxer->vptr->FNdelete(&stream->demux.demuxer);
  ka_delete_vstream(&stream->video.vstream);
  audio_delete(&stream->audio.astream);
  audiosaver_delete(&stream->audio.saver);
  ka_delete_subtitles(&stream->subtitle.sstream);
  ka_delete_vars(&stream->demux.vars);
  ka_mem_free(stream);
}

/**
 * Fills a message in a string.
 *
 * @param  player  Player.
 * @param  format  printf like format.
 * @param  ...     Additional parameters, format dependant.
 */
#ifdef __CC_NORCROFT
#pragma -v1 // hint to the compiler to check f/s/printf format
#endif
static void stream_report(ka_stream_t* stream, const char *format, ...)
{

  va_list arg;
  va_start(arg, format);

  msg_lookup(format, -1, stream->format, sizeof(stream->format));
  vsnprintf(stream->msg, sizeof(stream->msg), stream->format, arg);

  va_end(arg);
}
#ifdef __CC_NORCROFT
#pragma -v0 // return to default
#endif

/**
 * Clears decoded video.
 *
 * @param  stream  Stream handle.
 * @param  close   non-zero to close current stream, 0 otherwise.
 */
static void ka_stream_clearvideo(ka_stream_t* stream, int close)
{
  ka_stack_t* stack = stream->stack;

  stream->state &= ~ka_state_waittime;
  stream->video.waitTimeStart = 0;
  stream->video.waitTime = 0;
  stream->decodeSection = -1;
  stream->displaySection = -1;

  if (close)
  {
    ka_delete_vstream(&stream->video.vstream);
    stream->video.typ = 0;
    stream->video.params = NULL;
  }
  else
  {
    if (stream->video.vstream) ka_vstream_reset(stream->video.vstream);
  }

  if ((stack->video.status & ka_fifo_status_used)
  &&  (stream->video.typ != KA_BLOCK_TYPE_INVALID))
  {
    stack->video.status &= ~( ka_fifo_status_pull
                            | ka_fifo_status_pulldone
                            | ka_fifo_status_codecready
                            | ka_fifo_status_codecdone
                            );
  }
}

/**
 * Clears decoded audio.
 *
 * @param  stream  Stream handle.
 * @param  close   non-zero to close current stream, 0 otherwise.
 */
static void ka_stream_clearaudio(ka_stream_t* stream, int close)
{
  ka_stack_t* stack = stream->stack;

  if (stream->audio.astream) audio_close(stream->audio.astream);

  if (close)
  {
    stream->audio.typ = 0;
    stream->audio.params = NULL;
  }

  if (stream->audio.typ == KA_BLOCK_TYPE_INVALID)
  {
    stack->audio.status |= ka_fifo_status_pull | ka_fifo_status_codecdone;
    stack->audio.status &= ~ka_fifo_status_codecready;
  }
  else
  {
    stack->audio.status &= ~( ka_fifo_status_pull
                            | ka_fifo_status_pulldone
                            | ka_fifo_status_codecready
                            | ka_fifo_status_codecdone
                            );
  }
}

/**
 * Clears decoded subtitles.
 *
 * @param  stream  Stream handle.
 * @param  close   non-zero to close current stream, 0 otherwise.
 */
static void ka_stream_clearsubtitles(ka_stream_t* stream, int close)
{
  ka_stack_t* stack = stream->stack;

  if ((stack->subtitle.status & ka_fifo_status_used)
  &&  close)
  {
    stack->subtitle.status &= ~( ka_fifo_status_pull
                               | ka_fifo_status_pulldone
                               | ka_fifo_status_codecready
                               | ka_fifo_status_codecdone
                               );
  }

  if (stream->subtitle.sstream) ka_subtitles_reset(stream->subtitle.sstream);

  if (close)
  {
    stream->subtitle.typ = 0;
//    stream->subtitles.params = NULL;
  }
}

/**
 * Undo "all done" state.
 */
static void ka_stream_resetAllDone(ka_stream_t* stream)
{
  stream->state &= ~(ka_state_input_done | ka_state_push_done | ka_state_decode_done | ka_state_all_done);
  stream->state |= ka_state_future_load;
}

/**
 * Clears a stream after changing its position.
 *
 * @param  stream     Stream handle.
 * @param  clearInput If set, clear demuxer
 *
 * @returns 0 if OK, -1 on error with stream->pErrorBlock filled.
 */
static int ka_stream_clear(ka_stream_t* stream, int clearInput)
{
  int rc;
  // Clear current output
  ka_stream_clearvideo(stream, 0);
  ka_stream_clearaudio(stream, 0);
  ka_stream_clearsubtitles(stream, 0);
  ka_stack_clear(stream->stack);
  if (clearInput)
  {
    if (stream->demux.demuxer)
    {
      rc = stream->demux.demuxer->vptr->FNclear(stream->demux.demuxer, stream->stack);
      if (rc) return rc;
    }
    // Seeking, so clear current input
    ka_buffer_clear(stream->buf);
  }

  stream->time.video_start = stream->time.audio_start
                           = stream->time.subtitle_start
                           = stream->time.synced_start = 0;
  stream->state &= ~ka_state_clear_mask;
  stream->state |= ka_state_future_load;
  stream->video.waitTimeStart = 0;
  stream->video.waitTime = 0;
  timer_resetrtc();
  stream->time.restart = timer_gettime();

  return 0;
}

/**
 * Resets a stream in response to ka_stream_event_restart.
 *
 * @param  stream  Stream handle.
 */
int ka_stream_restart(ka_stream_t* stream)
{
  // Clear current input, like for a seek
  ka_stream_clearaudio(stream, 1);
  if (ka_stream_clear(stream, 1))
    return -1;

  return 0;
}

/**
 * Resets a stream after a restart.
 *
 * @param  stream  Stream handle.
 */
void ka_stream_reset(ka_stream_t* stream)
{
  // Clear current output
  ka_stream_clearvideo(stream, 1);
  ka_stream_clearaudio(stream, 1);
  ka_stream_clearsubtitles(stream, 1);
  ka_stack_reset(stream->stack);
  if (stream->demux.demuxer) stream->demux.demuxer->vptr->FNreset(stream->demux.demuxer, stream->stack);

  // Seeking, so clear current input
  ka_buffer_clear(stream->buf);

  stream->time.video_start = stream->time.audio_start
                           = stream->time.subtitle_start
                           = stream->time.synced_start = 0;
  stream->state = 0;

  // Reset video counters
  stream->cputime.video = stream->cputime.audio
                        = stream->cputime.subtitles
                        = stream->cputime.demux
                        = stream->cputime.io = 0;
  timer_install();             // reset both time counters
  stream->time.end = stream->time.start = stream->time.restart = timer_gettime();
}

/**
 * Looks for a demuxer capable of handling the a stream.
 *
 * @param  stream  Stream handle.
 *
 * @returns New stream state.
 */
static ka_stream_event ka_stream_finddemuxer(ka_stream_t* stream)
{
  ka_demux_params_t params;

  stream->demux.demuxer = NULL;

  params.pErrorBlock = stream->pErrorBlock;
  params.pFilename = stream->inputname;
  params.pStack = stream->stack;
  params.pInput = stream->input;
  params.pBuffer = stream->buf;
  params.pVars = stream->demux.vars;

  if (!stream->input)
  {
    ka_demux_select res = demux_selector_dvd(&stream->demux.demuxer, &params);

    if (res == ka_demux_select_error)
      return ka_stream_event_inerror;

    if (res == ka_demux_select_found)
      goto found;

    params.pInput = stream->input = ka_new_infile(stream->pErrorBlock);
    if (!stream->input)
      return ka_stream_event_inerror;

    if (ka_infile_open(stream->input, stream->inputname) < 0)
      return ka_stream_event_inerror;
  }

  params.pBuffer = stream->buf;
  while (*stream->demux.pSelector != NULL)
  {
    stream->demux.demuxer = NULL;
    ka_demux_select res = (*stream->demux.pSelector)(&stream->demux.demuxer, &params);

    if (res == ka_demux_select_error)
      return ka_stream_event_inerror;

    // Wait for more data?
    if (res == ka_demux_select_wait)
      return ka_stream_event_buffering;

    if (res == ka_demux_select_found)
      goto found;

    // Try next selector
    stream->demux.pSelector++;
  }

  ka_error_fill(stream->pErrorBlock, "error3"); // (Unrecognised film format)

  return ka_stream_event_inerror;

found:
  return ka_stream_event_ready_for_play; // or any value above buffering
}

/**
 * Signals done with video (none or unsupported)
 */
static void ka_stream_ignorevideo(ka_stream_t* stream)
{
  ka_stack_t* stack = stream->stack;

  stack->video.status |= ka_fifo_status_pull | ka_fifo_status_codecdone;
  stack->video.status &= ~ka_fifo_status_codecready;
  // Ensure all video blocks are skipped
  stream->video.typ = KA_BLOCK_TYPE_INVALID;
}

/**
 * Signals done with audio (none or unsupported)
 */
static void ka_stream_ignoreaudio(ka_stream_t* stream)
{
  ka_stack_t* stack = stream->stack;

  stack->audio.status |= ka_fifo_status_pull | ka_fifo_status_codecdone;
  stack->audio.status &= ~ka_fifo_status_codecready;
  // Ensure all audio blocks are skipped
  stream->audio.typ = KA_BLOCK_TYPE_INVALID;
}

/**
 * Processes video part of stack, try to process till one frame is done.
 *
 * @param  stream    Stream handle.
 * @param  pts_limit Limit to how much we look ahead in the stack.
 * @param  pevent    action returned.
 *
 * @returns 1 if stack modified, 0 otherwise.
 */
static int ka_stream_pullvideo(ka_stream_t* stream, ka_vcodec_event* pevent
                             , uint32_t pts_limit, uint32_t exit_time)
{
  ka_stack_t* stack = stream->stack;
  ka_fifo_t* fifo = &stack->video;
  ka_block_t* block = fifo->fread;
  int worked = 0;

  *pevent = ka_vcodec_event_buffering;

  // Don't check if the format doesn't contain any video
  if (!(fifo->status & ka_fifo_status_used))
    return 0;

  // FIXME: Assume there are enough video packets on stack now
  // to have a list of all streams and select the correct stream
  if (!stream->video.typ)
  {
    const ka_program_t* pprogram = &stack->programs[stream->program];

    int curi = (stream->video.channel < pprogram->nr_videos) ? stream->video.channel : 0;
    int i = curi;

    do
    {
      stream->video.typ = pprogram->videos[i];
      stream->video.pcodec = ka_block_to_vcodec(stream->video.typ);
      stream->video.params = stream->demux.demuxer->vptr->FNparams(stream->demux.demuxer, stream->video.typ);
      if (stream->video.pcodec && ka_stack_getCountForKid(stack, stream->video.typ))
      {
        stream->video.channel = i;
        if (i != curi) stream_report(stream, "msg_streamvideo:%d", stream->video.channel);
        break;
      }
      stream->video.typ = 0;
      stream->video.pcodec = NULL;
      stream->video.params = NULL;

      i++;
      if (i >= pprogram->nr_videos) i = 0;
    } while (i != curi);

    if (stream->video.pcodec)
    {
      stream->video.vstream = ka_new_vstream(stream->pErrorBlock, stream->video.pcodec, stream->video.params, stream->hardware);
      if (!stream->video.vstream)
      {
        *pevent = ka_vcodec_event_inerror;
        return 0;
      }
    }
    else
    {
      ka_log(ka_log_error | ka_log_video, "Video type not supported");
      ka_stream_ignorevideo(stream);
    }
  }

  // If no video processed yet, find a timestamp to start with
  if (!(fifo->status & ka_fifo_status_pull))
  {
    // If stream doesn't use stamps start with first block
    uint32_t pts = (stack->properties & ps_stack_prop_notimestamp) ? 1 : 0;

    for (; block != fifo->fwrite; )
    {
      if (block->section > stream->decodeSection)
        break;

      // Skip packets other than current stream
      if (block->typ == stream->video.typ)
      {
        if (block->pts) pts = block->pts;

        // ignore not time stamped blocks as it causes problem
        // when starting in the middle of a stream (positionning).
        if (pts)
        {
          if (config.debug & cfg_printvideoall)
            ka_log(ka_log_video, "Start pulling");

          fifo->status |= ka_fifo_status_pull;

          if (block->dts)
            stream->time.video_start = block->dts;
          else if (stack->properties & ps_stack_prop_notimestamp)
            stream->time.video_start = 0; // cf audio start
          else
            stream->time.video_start = pts;
          stream->time.synced_start = stream->time.video_start;

          // FIX ME: Assume there won't be any audio if none at this time.
          if (!(stack->audio.status & ka_fifo_status_used))
          {
            if (config.debug & cfg_printaudioall)
              ka_log(ka_log_audio, "No audio yet, assuming none in stream");
//            ka_stream_ignoreaudio(stream);
          }

          // Exit loop
          break;
        }
        else
        {
          if (config.debug & cfg_printvideopull)
            ka_log(ka_log_video, "Skipped %8x, sec %02x, pts %10u, wait first non-0 pts"
                 , block->typ, block->section, block->pts);
        }
      }
      else
      {
        if (config.debug & cfg_printvideopull)
          ka_log(ka_log_video, "Skipped %8x, sec %02x, pts %10u, active typ %8x"
               , block->typ, block->section, block->pts, stream->video.typ);
      }

      // block was skipped
      block->typ = 0;
      worked |= 1;

      if (++block == fifo->fend) block = fifo->fstart;
    }
    fifo->fread = block;

    if (!(fifo->status & ka_fifo_status_pull))
    {
      *pevent = ka_vcodec_event_buffering;
      return worked;
    }
  }

  int count = 0;
  *pevent = ka_vcodec_event_buffering;

  // Process video packets till special action is to be taken (usually when frame is decoded)
  for (; block != fifo->fwrite; )
  {
    if (block->section > stream->decodeSection)
      break;

    // Don't go too far for better stream change transitions
    if (pts_limit)
    {
      int32_t delta = block->pts - pts_limit;
      if ((delta > 0)
      &&  (delta < timer_getrate()*30)) // Ignore corrupted pts
        break;
    }

    // Skip packets other than current stream
    if (block->typ == stream->video.typ)
    {
      const uint8_t* sod = block->sod;

      *pevent = ka_vstream_decode(stream->video.vstream, block, exit_time);
      if (sod != block->sod)
        worked |= 1;

      if (*pevent != ka_vcodec_event_buffering) break;

      if (config.debug & cfg_printvideopull)
        ka_log(ka_log_video, "<= Pull %8x, sec %02x, pts %10u", block->typ, block->section, block->pts);

      count++;

      if (block->pts) stream->time.video_last = block->pts;

      if ((int32_t)(timer_gettime() - exit_time) > 0)
        break;
    }
    else
    {
      if (config.debug & cfg_printvideopull)
        ka_log(ka_log_video, "Skipped %8x, sec %02x, pts %10u, active typ %8x"
             , block->typ, block->section, block->pts, stream->video.typ);
    }

    // block was processed or skipped
    block->typ = 0;
    worked |= 1;

    if (++block == fifo->fend) block = fifo->fstart;
  }
  fifo->fread = block;

  if (*pevent >= ka_vcodec_event_decode_frame)
    worked |= 1;

  if (config.debug & cfg_printbufferstats)
  {
    // try to limit logging
    uint32_t t = timer_gettime();

    if (count || ((int32_t)(t - stream->time.video_last_log) > timer_getcs(10)))
    {
      stream->time.video_last_log = t;
      ka_log(ka_log_buffer, "Pulled %d video packets, remaining %d, status %04x, pts limit %10u"
           , count, ka_fifo_count(fifo), fifo->status, pts_limit);
    }
  }

  return worked;
}

/**
 * Activates current audio type.
 */
static int ka_stream_activateAudio(ka_stream_t* stream)
{
  if (stream->audio.typ)
  {
    stream->audio.params = stream->demux.demuxer->vptr->FNparams(stream->demux.demuxer, stream->audio.typ);
    if (ka_stack_getCountForKid(stream->stack, stream->audio.typ)
    &&  audio_allow(stream->audio.astream, stream->audio.params))
      return 1;
  }

  stream->audio.typ = 0;
  stream->audio.params = NULL;

  return 0;
}

/**
 * Pulls audio from the stack.
 *
 * @param  stream    Stream handle.
 * @param  pts_limit Limit to how much we look ahead in the stack.
 *
 * @returns 0 if stack unchanged, 1 if stack modified, 2 if decoder buffers full.
 */
static int ka_stream_pullaudio(ka_stream_t* stream, uint32_t pts_limit)
{
  ka_stack_t* stack = stream->stack;
  ka_fifo_t* fifo = &stack->audio;
  ka_block_t* block = fifo->fread;
  int worked = 0;

  // Don't check if the format doesn't contain any audio
  if (!(fifo->status & ka_fifo_status_used))
    return 0;

  const audio_info_t* ainfo = audio_info(stream->audio.astream);

  if (block != fifo->fwrite)
  {
    const ka_program_t* pprogram = &stack->programs[stream->program];

    // FIXME: Assume there are enough audio packets on stack now
    // to have a list of all streams and select the correct stream
    if (stream->audio.astream && !stream->audio.typ)
    {
      if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetAudioInfo)
      {
        ka_demux_audio_info_t info;
        stream->demux.demuxer->vptr->FNgetAudioInfo(stream->demux.demuxer, -1, &info);
        stream->audio.channel = info.streamId;
        stream->audio.typ = info.kid;
        ka_stream_activateAudio(stream);
      }
      else if (pprogram->nr_audios > 0)
      {
        int curi = (stream->audio.channel <= pprogram->nr_audios) ? stream->audio.channel - 1 : 0;
        int i = curi;

        do
        {
          stream->audio.typ = pprogram->audios[i];
          if (ka_stream_activateAudio(stream))
          {
            stream->audio.channel = i + 1;
            if (i != curi) stream_report(stream, "msg_streamaudio:%d %s %s", stream->audio.channel, "", "");
            break;
          }

          i++;
          if (i >= pprogram->nr_audios) i = 0;
        } while (i != curi);
      }
    }

    if (!stream->audio.typ)
    {
      if (stream->audio.channel)
        ka_log(ka_log_error | ka_log_audio, "Audio type not supported");
      ka_stream_ignoreaudio(stream);
    }

    // save audio to file ?
    if ((stream->audio.saver != NULL)
    &&  (stream->audio.savertyp == 0)
    &&  (config.audio.cfg & cfg_audio_save))
    {
      int curi = (stream->audio.channel <= pprogram->nr_audios) ? stream->audio.channel-1 : 0;
      stream->audio.savertyp = pprogram->audios[curi];
      const ka_aparams_t* params = stream->demux.demuxer->vptr->FNparams(stream->demux.demuxer, stream->audio.savertyp);

      if (audiosaver_openoutfile(stream->audio.saver, stream->pErrorBlock, params))
      {
        report_error_block(stream->pErrorBlock);
      }
    }

    // Ensure first packet is not pushed early (making sound late wrt video).
    // Early sound wrt video is prevented by audio_checkstart
    if (!(fifo->status & ka_fifo_status_pull))
    {
      uint32_t pts = 0;

      for (; block != fifo->fwrite; )
      {
        if (((ainfo->status & AUDIO_STATUS_REPLAYOUTPUT) == 0)
        &&  (block->section > stream->decodeSection))
          break;

        // Skip packets other than current stream
        if (block->typ == stream->audio.typ)
        {
          if (block->pts) pts = block->pts;

          if (pts >= stream->time.synced_start)
          {
            int rc;

            if (config.debug & cfg_printaudioall)
              ka_log(ka_log_audio, "Start pulling");

            fifo->status |= ka_fifo_status_pull;
            stream->time.audio_start = pts;

            rc = audio_open(stream->audio.astream, stream->pErrorBlock, stream->audio.params);
            if (rc)
            {
              audio_close(stream->audio.astream);
              ka_stream_ignoreaudio(stream);
            }

            // FIX ME: Assume there won't be any video if none at this time.
            if (!(stack->video.status & ka_fifo_status_used))
            {
              if (config.debug & cfg_printvideoall)
                ka_log(ka_log_video, "No video yet, assuming none in stream");
              ka_stream_ignorevideo(stream);
            }

            // Exit loop
            break;
          }
          else
          {
            if (config.debug & cfg_printaudiopull)
              ka_log(ka_log_audio, "Skipped %8x, sec %02x, pts %10u, too early: start %10u"
                   , block->typ, block->section, block->pts, stream->time.synced_start);
          }
        }
        else
        {
          if (config.debug & cfg_printaudiopull)
            ka_log(ka_log_audio, "Skipped %8x, sec %02x, pts %10u, active typ %8x"
                 , block->typ, block->section, block->pts, stream->audio.typ);
        }

        // block was skipped
        block->typ = 0;
        worked |= 1;

        // Write block to file (will ignore multiple attempts to write same block)
        if ((stream->audio.saver != NULL)
        &&  (stream->audio.savertyp == block->typ)
        &&  audiosaver_issaving(stream->audio.saver))
          audiosaver_writeoutfile(stream->audio.saver, block);


        if (++block == fifo->fend) block = fifo->fstart;
      }
      fifo->fread = block;

      if (!(fifo->status & ka_fifo_status_pull))
      {
        return worked;
      }
    }

    int count = 0;

    // Push as much audio packets in audio decoder buffers as possible
    for (; block != fifo->fwrite; )
    {
        if (((ainfo->status & AUDIO_STATUS_REPLAYOUTPUT) == 0)
        &&  (block->section > stream->decodeSection))

      // Don't go too far for better stream change transitions
      if (pts_limit)
      {
        int32_t delta = block->pts - pts_limit;
        if ((delta > 0)
        &&  (delta < timer_getrate()*30)) // Ignore corrupted pts
          break;
      }

      // Write block to file (will ignore multiple attempts to write same block)
      if ((stream->audio.saver != NULL)
      &&  (stream->audio.savertyp == block->typ)
      &&  audiosaver_issaving(stream->audio.saver))
        audiosaver_writeoutfile(stream->audio.saver, block);


      // Skip packets other than current stream
      if (block->typ == stream->audio.typ)
      {
        if (!audio_decode(stream->audio.astream, block))
        {
          worked |= 2;
          break; // audio buffer is full for now
        }

        // block was processed
        if (config.debug & cfg_printaudiopull)
          ka_log(ka_log_audio, "<= Pull %8x, sec %02x, pts %10u", block->typ, block->section, block->pts);

        count++;

        if (block->pts) stream->time.audio_last = block->pts;
      }
      else
      {
        if (config.debug & cfg_printaudiopull)
          ka_log(ka_log_audio, "Skipped %8x, sec %02x, pts %10u, active typ %8x"
               , block->typ, block->section, block->pts, stream->audio.typ);
      }

      // block was processed or skipped
      block->typ = 0;
      worked |= 1;

      if (++block == fifo->fend) block = fifo->fstart;
    }
    fifo->fread = block;

    if (config.debug & cfg_printbufferstats)
    {
      // try to limit logging
      uint32_t t = timer_gettime();

      if (count || ((int32_t)(t - stream->time.audio_last_log) > timer_getcs(10)))
      {
        stream->time.audio_last_log = t;
        ka_log(ka_log_buffer, "Pulled %d audio packets, remaining %d, status %04x, pts limit %10u"
             , count, ka_fifo_count(fifo), fifo->status, pts_limit);
      }
    }
  }

  if ((fifo->status & ka_fifo_status_pull)
  &&  !(fifo->status & (ka_fifo_status_codecready | ka_fifo_status_codecdone)))
  {
    worked |= 2;

    audio_checkState(stream->audio.astream);
    ainfo = audio_info(stream->audio.astream);

    if (ainfo->status & AUDIO_STATUS_READYFOROUTPUT)
      fifo->status |= ka_fifo_status_codecready;
    if (ainfo->status & AUDIO_STATUS_DONEOUTPUT)
      fifo->status |= ka_fifo_status_outputdone;
    else
      fifo->status &= ~ka_fifo_status_outputdone;
    if (ainfo->status & AUDIO_STATUS_COMPLETED)
      fifo->status |= ka_fifo_status_codecready | ka_fifo_status_codecdone;
    if (ainfo->status & AUDIO_STATUS_DISABLED)
    {
      // Ensure all audio blocks are skipped
      ka_stream_ignoreaudio(stream);
    }
  }

  return worked;
}

/**
 * Pulls subtitles from the stack.
 *
 * @param  stream    Stream handle.
 * @param  pts_limit Limit to how much we look ahead in the stack.
 *
 * @returns 0 if stack unchanged, 1 if stack modified, 2 if decoder buffers full.
 */
static int ka_stream_pullsubtitles(ka_stream_t* stream, uint32_t pts_limit)
{
  ka_stack_t* stack = stream->stack;
  ka_fifo_t* fifo = &stack->subtitle;
  ka_block_t* block = fifo->fread;
  int worked = 0;

  // Don't check if the format doesn't contain any subtitles
  if (!(fifo->status & ka_fifo_status_used))
    return 0;

  const ka_program_t* pprogram = &stack->programs[stream->program];

  // Decode all subtitles belonging to a program in parrallel, since its not much work.
  // Make at least assumptions possible here since subtitles may start way after the start of the video,
  // packets are not numerous so things like pts rollover make it interesting.

  if (block != fifo->fwrite)
  {
    int count = 0;

    // Push as much subtitles packets in subtitles decoder buffers as possible
    for (; block != fifo->fwrite; )
    {
      if (block->section > stream->decodeSection)
        break;

      // Don't go too far for better subtitles stream change transitions
      if (pts_limit)
      {
        int32_t delta = block->pts - pts_limit;
        if ((delta > 0) // Ignore pts rollover
        &&  (delta < timer_getrate()*30) // Ignore corrupted pts
        &&  !(fifo->status & ka_fifo_status_codecdone))
          break;
      }

      int ch = 1 + ka_stack_idBelongsToProgram(pprogram, block->typ);

      if (ch)
      {
        if (ch == stream->subtitle.channel)
          stream->subtitle.typ = block->typ;

        if (!(fifo->status & ka_fifo_status_pull))
        {
          uint32_t pts = 0;

          if (block->pts) pts = block->pts;

          if (config.debug & cfg_printsubtitleall)
            ka_log(ka_log_subtitle, "Start pulling");

          fifo->status |= ka_fifo_status_pull;
          stream->time.subtitle_start = pts;
        }

        int rc = ka_subtitles_decode(stream->subtitle.sstream, block);
        if (!rc)
        {
          worked |= 2;
          break; // subtitles buffer is full for now
        }

        // block was processed
        if (config.debug & cfg_printsubtitlepull)
          ka_log(ka_log_subtitle, "<= Pull %8x, sec %02x, pts %10u", block->typ, block->section, block->pts);

        count++;
      }

      if (block->pts) stream->time.subtitle_last = block->pts;

      // block was processed or skipped
      block->typ = 0;
      worked |= 1;

      if (++block == fifo->fend) block = fifo->fstart;
    }
    fifo->fread = block;

    if (config.debug & cfg_printbufferstats)
    {
      // try to limit logging
      uint32_t t = timer_gettime();

      if (count || ((int32_t)(t - stream->time.subtitle_last_log) > timer_getcs(10)))
      {
        stream->time.subtitle_last_log = t;
        ka_log(ka_log_buffer, "Pulled %d subtitles packets, remaining %d, status %04x, pts limit %10u"
             , count, ka_fifo_count(fifo), fifo->status, pts_limit);
      }
    }
  }

  return worked;
}

/**
 * Removes proccessed packs from the stack and update input buffer availability
 *
 * @param  stream    Stream handle.
 */
static void ka_stream_pack(ka_stream_t* stream)
{
  ka_stack_t* stack = stream->stack;
  ka_fifo_t* fifo = &stack->video;
  const uint8_t* sod = NULL;
  uint32_t count = 0;

  if (fifo->fread != fifo->fwrite)
  {
    // Abort if special marker
    if (fifo->fread->sod == NULL) return;

    sod = fifo->fread->sod;
    count += ka_fifo_count(fifo);

    fifo->status &= ~ka_fifo_status_pulldone;
  }
  else
    fifo->status |= ka_fifo_status_pulldone;

  fifo = &stack->audio;

  if (fifo->fread != fifo->fwrite)
  {
    // Abort if special marker
    if (fifo->fread->sod == NULL) return;

    if (sod != NULL)
      sod = ka_buffer_firstBlock(stream->buf, fifo->fread->sod, sod);
    else
      sod = fifo->fread->sod;
    count += ka_fifo_count(fifo);

    fifo->status &= ~ka_fifo_status_pulldone;
  }
  else
    fifo->status |= ka_fifo_status_pulldone;

  fifo = &stack->subtitle;

  if (fifo->fread != fifo->fwrite)
  {
    // Abort if special marker
    if (fifo->fread->sod == NULL) return;

    if (sod != NULL)
      sod = ka_buffer_firstBlock(stream->buf, fifo->fread->sod, sod);
    else
      sod = fifo->fread->sod;
    count += ka_fifo_count(fifo);

    fifo->status &= ~ka_fifo_status_pulldone;
  }
  else
    fifo->status |= ka_fifo_status_pulldone;

  fifo = &stack->navigation;

  if (fifo->fread != fifo->fwrite)
  {
    // Abort if special marker
    if (fifo->fread->sod == NULL) return;

    if (sod != NULL)
      sod = ka_buffer_firstBlock(stream->buf, fifo->fread->sod, sod);
    else
      sod = fifo->fread->sod;
    count += ka_fifo_count(fifo);

    fifo->status &= ~ka_fifo_status_pulldone;
  }
  else
    fifo->status |= ka_fifo_status_pulldone;

  // update buffer start, if sod is NULL, if will be move to start of unstacked part
  ka_buffer_setRead(stream->buf, sod);

  // all input done and processed ?
  if (((stream->state & ka_state_input_done) == ka_state_input_done)
  &&  (count == 0))
    stream->state |= ka_state_decode_done;

  // try to limit logging
  uint32_t t = timer_gettime();

  if ((int32_t)(t - stream->time.demux_last_log) > timer_getcs(10))
  {
    stream->time.demux_last_log = t;

    if (config.debug & cfg_printbufferstats)
    {
      ka_log(ka_log_buffer, "in use %08x (%08x to push, %d pulls to do, state %08x)"
           , ka_buffer_getByteCount(stream->buf)
           , ka_buffer_getUnstackedCount(stream->buf)
           , count
           , stream->state
           );
      ka_log(ka_log_buffer, "Video packets, remaining %d, status %04x"
           , ka_fifo_count(&stream->stack->video), stream->stack->video.status);
      ka_log(ka_log_buffer, "Audio packets, remaining %d, status %04x"
           , ka_fifo_count(&stream->stack->audio), stream->stack->audio.status);
      ka_log(ka_log_buffer, "Sub.  packets, remaining %d, status %04x"
           , ka_fifo_count(&stream->stack->subtitle), stream->stack->subtitle.status);
      ka_log(ka_log_buffer, "Nav.  packets, remaining %d, status %04x"
           , ka_fifo_count(&stream->stack->navigation), stream->stack->navigation.status);

    }
  }
}

/**
 * Checks if a program has some playable data types.
 *
 * Returns 1 if so, 0 otherwise.
 */
int ka_stream_programHasValidStreams(const ka_stream_t* stream, uint32_t index)
{
  const ka_program_t* program = &stream->stack->programs[index];

  // we stack only playable video types, so check is minimal
  for (int i = 0; i < program->nr_videos; i++)
  {
    ka_block_id typ = program->videos[i];
    if (ka_block_to_vcodec(typ)
    &&  ka_stack_getCountForKid(stream->stack, typ))
      return 1;
  }

  // no so for audio (depends on playing module)
  if (!stream->audio.astream || !program->nr_audios)
    return 0;

  for (int i = 0; i < program->nr_audios; i++)
  {
    ka_block_id typ = program->audios[i];
    if (ka_stack_getCountForKid(stream->stack, typ))
    {
      // Note: params may not be valid yet (info may be in an yet undecoded packet)
      const ka_aparams_t* params = stream->demux.demuxer->vptr->FNparams(stream->demux.demuxer, typ);
      if (audio_allow(stream->audio.astream, params))
        return 1;
    }
  }

  return 0;
}

/**
 * Processes some data and return possible actions.
 *
 * @param  stream    Stream handle.
 *
 * @returns Action to perform.
 */
ka_stream_event ka_stream_process(ka_stream_t* stream, uint32_t exit_time)
{
  ka_stream_event event = ka_stream_event_buffering;
  ka_stack_t* stack = stream->stack;
  uint32_t t, cur_t;
  int worked = 0;

/*
  static int oldState = 0;
  if (oldState != stream->state)
  {
    ka_log(ka_log_rtc, "STATE %08x", stream->state);
    oldState = stream->state;
  }
*/

  //----------------------------------
  // Last stage? Notify end of replay or inerror
  //
  if ((stream->state & ka_state_inerror) == ka_state_inerror)
    return ka_stream_event_inerror;

  if ((stream->state & ka_state_all_done) == ka_state_all_done)
    return ka_stream_event_end;

  //---------------
  // Start replay?
  //
  if (!(stream->state & ka_state_replaying))
  {
    if (stream->video.typ == KA_BLOCK_TYPE_INVALID)
    {
      if ((stack->audio.status & ka_fifo_status_codecready)
      &&  (stream->audio.typ != KA_BLOCK_TYPE_INVALID))
      {
        stream->state |= ka_state_replaying | ka_state_playing_audio;
        stream->time.replay_start = timer_gettime();
        if (config.debug & cfg_printreplaystats)
          ka_log(ka_log_replay, "Ready to play audio only");
        return ka_stream_event_ready_for_play;
      }
    }
    else
    {
      // don't wait for audio or subtitles (in 1 DVD audio starts 15 secs after video)
      if (stack->video.status & ka_fifo_status_codecready)
      {
        stream->state |= ka_state_replaying | ka_state_playing_video;
        if (stream->audio.typ != KA_BLOCK_TYPE_INVALID)
          stream->state |= ka_state_playing_audio;
        stream->time.replay_start = timer_gettime();
        if (config.debug & cfg_printreplaystats)
          ka_log(ka_log_replay, "Ready to play video (+ audio)");
        return ka_stream_event_ready_for_play;
      }
    }
  }

  //--------------------------------
  // Find a demuxer if not done yet
  //
  if (!stream->demux.demuxer)
  {
    ka_stream_event devent;
    t = timer_gettime();

    devent = ka_stream_finddemuxer(stream);

    stream->cputime.io += timer_gettime() - t; // mostly file reading
    if (devent == ka_stream_event_inerror)
    {
      report_error_block(stream->pErrorBlock);
      stream->state |= ka_state_inerror;
    }

    // May ask for more data or return in error
    if (devent <= ka_stream_event_buffering)
      return devent;

    // Found a demuxer?
    if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNparse)
    {
      t = timer_gettime();
      if (stream->demux.demuxer->vptr->FNparse(stream->demux.demuxer, stream->stack))
      {
        report_error_block(stream->pErrorBlock);
        stream->state |= ka_state_inerror;
        return ka_stream_event_inerror;
      }
      stream->cputime.io += timer_gettime() - t; // mostly file reading
    }
  }

  //---------------------------------------------
  // Try to load a chunk of data into the buffer
  //
  if (!(stream->state & ka_state_load_done))
  {
    int done;
    int work = 0;
    t = timer_gettime();

    int read = stream->demux.demuxer->vptr->FNfillBuffer(stream->demux.demuxer, stream->buf, &done);

    if ((stream->state & ka_state_inputstarted) == 0)
    {
      // Note: don't count on having at least read > 0
      // the whole stream may already be in the buffer during decoder selection
      if (ka_buffer_getByteCount(stream->buf))
      {
        // Start counting for WAIT_REPLAY only now to cope with slow starting media like DVDs
        stream->state |= ka_state_inputstarted;
        stream->time.restart = timer_gettime();
      }
    }

    // Data was read, it will have to be pushed on stack
    if (read > 0)
    {
      stream->state &= ~ka_state_push_done;
      work |= 1;
    }
    else if (read < 0)
    {
      report_error_block(stream->pErrorBlock);
      stream->state |= ka_state_inerror;
      return ka_stream_event_inerror;
    }

    // No more additional data will come
    if (done)
      stream->state |= ka_state_load_done;

    cur_t = timer_gettime();
    if (work)
    {
      worked |= work;
      stream->cputime.io += cur_t - t;
    }

    if ((int32_t)(cur_t - exit_time) > 0)
      return event;
  }

  //-------------------------------------------
  // Push input data on stack with the demuxer
  //
  if (!(stream->state & ka_state_push_done))
  {
    const uint8_t *buf, *pos, *end;
    int work = 0;

    t = timer_gettime();

    while (!ka_stack_isFull(stack))
    {
      // Stack is not full, try to find an unprocessed input data block
      if (!ka_buffer_getUnstackedBlock(stream->buf, &buf, &end))
      {
        stream->state |= ka_state_push_done;
        break;
      }

      // Call demuxer to process the input push data on stack
      pos = stream->demux.demuxer->vptr->FNpush(stream->demux.demuxer, stack, buf, end);
      if (pos == ka_demux_error)
      {
        // Stop after demux error
        stream->state |= (ka_state_load_done | ka_state_push_done);
        break;
      }
      else if (pos == buf)
      {
        // Demuxer did nothing, either the stack is full or the data in the block
        // is unusable as is.
        if (!ka_stack_isFull(stack))
        {
          // If block was at end of circular buffer
          // push it before the data at the data at the start of buffer
          int rc = ka_buffer_compressUnstacked(stream->buf, stream->pErrorBlock, end);
          if (rc > 0)
          {
            // That wasn't the case which means we have finished
            // till nore data is loaded
            stream->state |= ka_state_push_done;
            break;
          }
          else if (rc < 0)
          {
            // Error
            report_error_block(stream->pErrorBlock);
            stream->state |= ka_state_inerror;
            return ka_stream_event_inerror;
          }
        }
      }
      else
      {
        // We have processed that part of the buffer and have possibly
        // put some packets from it on the stack.
        ka_buffer_setStacked(stream->buf, pos);
        stack->video.status &= ~ka_fifo_status_pulldone;
        stack->audio.status &= ~ka_fifo_status_pulldone;
        stack->subtitle.status &= ~ka_fifo_status_pulldone;
        work |= 1;
      }
    }

    cur_t = timer_gettime();
    if (work)
    {
      worked |= work;
      stream->cputime.demux += cur_t - t;
    }

    if ((int32_t)(cur_t - exit_time) > 0)
      return event;
  }

  //-----------------------------------------------------------------
  // Something on stack? Process video/audio/subtitles packets with the codecs
  //

  // First check if current program has anything to offer
  // Note: we cannot yet verify which video/audio channel to play
  //       as playing paremeters mey be in yet to be decoded packets
  if (!(stream->state & ka_state_playable))
  {
    uint32_t old_program = stream->program;

    // Nothing playable on current program
    if (!ka_stream_programHasValidStreams(stream, stream->program))
    {
      // Try selecting another playable program
      do
      {
        stream->program++;
        if (stream->program >= stack->nr_programs)
          stream->program = 0;

        if (ka_stream_programHasValidStreams(stream, stream->program))
          break;
      }
      while (stream->program != old_program);

      // None found, wait for new streams in data
      if (stream->program == old_program)
      {
        t = timer_gettime();

        // Don't give up for a few seconds
        if (((int32_t)(t - stream->time.restart) <= (int32_t)(WAIT_PLAYABLE * timer_getrate()))
        ||  (stream->state & ka_state_replaying))
        {
          // Compact the stack and update input buffer availability
          // to avoid stalling if demuxer cannot push anything useful on stack
          ka_stream_pack(stream);

          return ka_stream_event_buffering;
        }

        // If no program was defined, attach stream elements to a default program
        if (stack->nr_programs == 0)
        {
          ka_stack_createFallbackProgram(stack);
          stream->program = 0;
        }

        // If still nothing playable, give up
        if (!ka_stream_programHasValidStreams(stream, stream->program))
        {
          report_error(0, "error3"); // (Unrecognised film format)
          stream->state |= ka_state_inerror;
          return ka_stream_event_inerror;
        }
      }
    }

    stream->state |= ka_state_playable;
  }

  //--------------------------------------------------
  // Ensure we decode the one section at a time
  //
  {
    ka_fifo_t* fifo = &stack->video;

    if (fifo->fread != fifo->fwrite)
    {
      stream->decodeSection = fifo->fread->section;
    }

    fifo = &stack->audio;

    if (fifo->fread != fifo->fwrite)
    {
      if ((stream->decodeSection == -1) || (stream->decodeSection > fifo->fread->section))
        stream->decodeSection = fifo->fread->section;
    }

    fifo = &stack->subtitle;

    if (fifo->fread != fifo->fwrite)
    {
      if ((stream->decodeSection == -1) || (stream->decodeSection > fifo->fread->section))
        stream->decodeSection = fifo->fread->section;
    }
  }

  //--------------------------------------------------
  // Process some audio
  //
  if (stack->audio.status & ka_fifo_status_used)
  {
    int work = 0;
    t = timer_gettime();

    if (!(stack->audio.status & ka_fifo_status_pulldone))
    {
      // Pull some audio packets from the stack
      uint32_t pts_limit = 0;

      if ((config.debug & cfg_displayall) == 0)
      {
        if (stack->audio.status & (ka_fifo_status_codecready | ka_fifo_status_codecdone))
        {
          // Don't limit if no (more) video or not ready yet
          if ((stack->video.status & ka_fifo_status_codecdone) == 0)
          {
            if (stream->state & ka_state_replaying)
            {
              pts_limit = timer_getrtc();
            }
            else
            {
              pts_limit = stream->time.synced_start;
              if ((int32_t)(pts_limit - stream->time.video_last) < 0)
                pts_limit = stream->time.video_last;
            }
            pts_limit += timer_getrate();
          }
        }
      }

      work |= ka_stream_pullaudio(stream, pts_limit);
    }
    // No more input to process? Wait till there is no more output
    else if (((stream->state & ka_state_input_done) == ka_state_input_done)
         &&  !(stack->audio.status & ka_fifo_status_codecdone))
    {
      if (stream->audio.astream == NULL)
        stack->audio.status |= ka_fifo_status_codecdone;
      else
      {
        if (!(stream->state & ka_state_future_load))
          audio_decode(stream->audio.astream, NULL);

        const audio_info_t* ainfo = audio_info(stream->audio.astream);

        if (ainfo->status & AUDIO_STATUS_READYFOROUTPUT)
          stack->audio.status |= ka_fifo_status_codecready;
        if (ainfo->status & AUDIO_STATUS_DONEOUTPUT)
          stack->audio.status |= ka_fifo_status_outputdone;
        else
          stack->audio.status &= ~ka_fifo_status_outputdone;
        if (ainfo->status & AUDIO_STATUS_COMPLETED)
          stack->audio.status |= ka_fifo_status_codecready | ka_fifo_status_codecdone;
        if (!(ainfo->status & AUDIO_STATUS_BLOCKPROCESSMASK))
          stack->audio.status |= ka_fifo_status_codecdone;
      }
    }

    cur_t = timer_gettime();
    if (work)
    {
      worked |= work;
      stream->cputime.audio += cur_t - t;
    }

    if ((int32_t)(cur_t - exit_time) > 0)
      return event;
  }

  //--------------------------------------------------
  // Process some subtitles
  //
  if (stack->subtitle.status & ka_fifo_status_used)
  {
    int work = 0;
    t = timer_gettime();

    if (!(stack->subtitle.status & ka_fifo_status_pulldone))
    {
      // Pull some subtitles packets from the stack
      uint32_t pts_limit = 0;

      if ((config.debug & cfg_displayall) == 0)
      {
        if (stack->subtitle.status & (ka_fifo_status_codecready | ka_fifo_status_codecdone))
        {
          // Don't limit if no (more) video
          if ((stack->video.status & ka_fifo_status_codecdone) == 0)
          {
            if (stream->state & ka_state_replaying)
            {
              pts_limit = timer_getrtc();
            }
            else
            {
              pts_limit = stream->time.synced_start;
              if ((int32_t)(pts_limit - stream->time.video_last) < 0)
                pts_limit = stream->time.video_last;
            }
            pts_limit += timer_getrate();
          }
        }

        // Cope with corruption
        if (stack->audio.fwrite != stack->audio.fread)
        {
          ka_block_t* block = stack->audio.fread;
          if (block->pts && ((int32_t)(block->pts - pts_limit) > 0))
            pts_limit = block->pts;
        }
      }

      work |= ka_stream_pullsubtitles(stream, pts_limit);
    }
    // No more input to process? Wait till there is no more output
    else if (((stream->state & ka_state_input_done) == ka_state_input_done)
         &&  !(stack->subtitle.status & ka_fifo_status_codecdone))
    {
      // TODO
      stack->subtitle.status |= ka_fifo_status_codecdone;
    }

    cur_t = timer_gettime();
    if (work)
    {
      worked |= work;
      stream->cputime.subtitles += cur_t - t;
    }

    if ((int32_t)(cur_t - exit_time) > 0)
      return event;
  }

  //--------------------------------------------------
  // Process some video
  //
  if (stack->video.status & ka_fifo_status_used)
  {
    int work = 0;
    t = timer_gettime();

    if (!(stack->video.status & ka_fifo_status_pulldone))
    {
      // Pull some video packets from the stack
      ka_vcodec_event vevent;

      uint32_t pts_limit = 0;

      if ((config.debug & cfg_displayall) == 0)
      {
        if (stack->audio.status & (ka_fifo_status_codecready | ka_fifo_status_codecdone))
        {
          // Don't limit if no (more) audio
          if ((stack->audio.status & ka_fifo_status_codecdone) == 0)
          {
            if (stream->state & ka_state_replaying)
            {
              pts_limit = timer_getrtc();
            }
            else
            {
              pts_limit = stream->time.synced_start;
              if ((int32_t)(pts_limit - stream->time.audio_last) < 0)
                pts_limit = stream->time.audio_last;
            }
            pts_limit += timer_getrate();
          }
        }

        // Cope with corruption
        if (stack->video.fwrite != stack->video.fread)
        {
          ka_block_t* block = stack->video.fread;
          if (block->pts && ((int32_t)(block->pts - pts_limit) > 0))
            pts_limit = block->pts;
        }
      }

      work |= ka_stream_pullvideo(stream, &vevent, pts_limit, exit_time);

      switch (vevent)
      {
        case ka_vcodec_event_inerror:
        {
          event = ka_stream_event_inerror;
          stack->video.status |= ka_fifo_status_codecdone
                              |  ka_fifo_status_codecerror;
          stream->video.typ = KA_BLOCK_TYPE_INVALID;
          report_error_block(stream->pErrorBlock);
          stream->state |= ka_state_inerror;
        }
        break;
        case ka_vcodec_event_decode_frame:
        {
          event = ka_stream_event_decode_frame;
        }
        break;
        case ka_vcodec_event_end_frame:
        {
          if (ka_vstream_count(stream->video.vstream) > 0)
            stack->video.status |= ka_fifo_status_codecready;
        }
        break;
        case ka_vcodec_event_end:
        {
            stack->video.status |= ka_fifo_status_codecdone;
        }
        break;
      }
    }
    else if ((stack->video.status & ka_fifo_status_codecdone) == 0)
    {
      if (stream->video.vstream == NULL)
      {
        stack->video.status |= ka_fifo_status_codecdone;
      }
      else if (!ka_vstream_isCodecDone(stream->video.vstream, ((stream->state & ka_state_input_done) == ka_state_input_done)))
      {
        // extract last frames to display
        ka_vcodec_event vevent;

        vevent = ka_vstream_decode(stream->video.vstream, NULL, exit_time);

        switch (vevent)
        {
          case ka_vcodec_event_decode_frame:
          {
            event = ka_stream_event_decode_frame;
         }
          break;
          case ka_vcodec_event_end_frame:
          {
            if (ka_vstream_count(stream->video.vstream) > 0)
              stack->video.status |= ka_fifo_status_codecready;
          }
          break;
          default:
            stack->video.status |= ka_fifo_status_codecdone;
        }
      }
      else
        stack->video.status |= ka_fifo_status_codecdone;
    }

/*
    if ((stack->video.status & ka_fifo_status_codecdone)
    &&  (stream->video.typ != KA_BLOCK_TYPE_INVALID)
    &&  !(stack->video.status & ka_fifo_status_codecready))
    {
      ka_log(ka_log_error | ka_log_video, "Could not find any I-frame");
      ka_stream_ignorevideo(stream);
    }
*/

    if (work)
    {
      worked |= work;
      stream->cputime.video += timer_gettime() - t;
    }
  }

  //--------------------------------------------------------
  // Compact the stack and update input buffer availability
  {
    t = timer_gettime();

    if (((int32_t)(t - stream->time.restart) > (int32_t)(WAIT_REPLAY * timer_getrate()))
    &&  (stream->state & ka_state_inputstarted)
    &&  !(stream->state & ka_state_replaying))
    {
      report_error(0, "error40"); // (Could not find starting point)
      stream->state |= ka_state_inerror;
      return ka_stream_event_inerror;
    }

    ka_stream_pack(stream);

    stream->cputime.demux += timer_gettime() - t;
  }

  //-------------------------------------------
  // No work, report it if happens for a while
  //
  if (!worked
  &&  ((stream->state & ka_state_decode_done) == 0)
  &&  (event == ka_stream_event_buffering))
  {
    uint32_t t = timer_gettime();
    // try to limit logging
    if ((int32_t)(t - stream->time.work_last_log) > (int32_t)timer_getrate())
    {
      stream->time.work_last_log = t;
      uint32_t rtc = timer_getrtc();

      if (stack->video.fwrite != stack->video.fread)
      {
        ka_block_t* block = stack->video.fread;
        if (!block->pts || (block->pts < rtc))
        {
          ka_log(ka_log_error | ka_log_buffer
               , "stuck on block type %8x, sec %02x, pts %10u, rtc %10u"
               , block->typ, block->section, block->pts, rtc
               );
        }
      }

      if (stack->audio.fwrite != stack->audio.fread)
      {
        ka_block_t* block = stack->audio.fread;
        if (!block->pts || (block->pts < rtc))
        {
          ka_log(ka_log_error | ka_log_buffer
               , "stuck on block type %8x, sec %02x, pts %10u, rtc %10u"
               , block->typ, block->section, block->pts, rtc
               );
        }
      }

      if (stack->subtitle.fwrite != stack->subtitle.fread)
      {
        ka_block_t* block = stack->subtitle.fread;
        if (!block->pts || (block->pts < rtc))
        {
          ka_log(ka_log_error | ka_log_buffer
               , "stuck on block type %8x, sec %02x, pts %10u, rtc %10u, skip it?"
               , block->typ, block->section, block->pts, rtc
               );
        }
      }

      ka_log(ka_log_error
           , "No work with state %08x, video (st: %02x c: %d) audio (st: %02x c: %d) subs (st: %02x, c: %d)"
           , stream->state
           , stack->video.status, ka_fifo_count(&stack->video)
           , stack->audio.status, ka_fifo_count(&stack->audio)
           , stack->subtitle.status, ka_fifo_count(&stack->subtitle)
           );
      ka_log(ka_log_error
           , "rtc %10u last video %10u audio %10u subtitle %10u"
           , rtc, stream->time.video_last, stream->time.audio_last, stream->time.subtitle_last
           );
    }
  }
  else stream->time.work_last_log = timer_gettime();

  //--------------------------------------------------
  // No more input to process? Wait till there is no more output
  //
  if ((stream->state & ka_state_input_done) == ka_state_input_done)
  {
    int done = 1;

    if (stream->demux.demuxer->vptr->FNsectionDone != NULL) {
      ka_demux_section_state state
          = stream->demux.demuxer->vptr->FNsectionDone(stream->demux.demuxer, KA_DEMUX_SECTION_DONE_DECODE);

      switch(state)
      {
        case KA_DEMUX_SECTION_STATE_NEXTSECTION:
        {
          stream->state &= ~ka_state_load_done;
          done = 0;
        }
        break;
        case KA_DEMUX_SECTION_STATE_DISCONTINUITY:
        {
          stream->state &= ~ka_state_future_load;
        }
        break;
      }
    }
    else
    {
      stream->state &= ~ka_state_future_load;
    }
    // All done ?
    if ((stream->state & ka_state_replaying) == 0)
      done = 0;
    else if ((stream->state & ka_state_waittime) != 0)
      done = 0;
    else if ((stack->video.status & ka_fifo_status_used)
    &&  !(stack->video.status & ka_fifo_status_codecdone))
      done = 0;
    else if ((stack->audio.status & ka_fifo_status_used)
    &&  !(stack->audio.status & ka_fifo_status_codecdone))
      done = 0;
    else if ((stack->subtitle.status & ka_fifo_status_used)
    &&  !(stack->subtitle.status & ka_fifo_status_codecdone))
      done = 0;

    if (done)
      stream->state |= ka_state_all_done;
  }

  if (!worked)
    return ka_stream_event_wait;

  return event;
}

/**
 * Returns global video information.
 *
 * @param  stream    Stream handle.
 *
 * @returns Video information structure.
 */
const ka_vinfo_t* ka_stream_getVideoInfo(const ka_stream_t* stream)
{
  ka_vinfo_t* info = NULL;
  if (stream->video.vstream)
  {
    info = (ka_vinfo_t*) ka_vstream_getInfo(stream->video.vstream);

    if (info->picture.width
    &&  (stream->demux.demuxer->vptr->FNupdateVInfo != NULL))
      stream->demux.demuxer->vptr->FNupdateVInfo(stream->demux.demuxer, info);
  }

  return info;
}

/**
 * Returns information on frame being currently decoded.
 *
 * @param  stream    Stream handle.
 *
 * @returns Frame information structure.
 */
ka_vframe_t* ka_stream_getDecodeFrame(const ka_stream_t* stream)
{
  return ka_vstream_getDecodeFrame(stream->video.vstream);
}

/**
 * Marks frame being decoded as to be skipped to accelerate processing.
 *
 * @param  stream    Stream handle.
 */
void ka_stream_skipDecodeFrame(ka_stream_t* stream)
{
  ka_vstream_skipDecodeFrame(stream->video.vstream);
}

/**
 * Forces codec to decode in monochrome/colours.
 *
 * @param  vs     Video stream handle.
 * @param  mono   1 for mono, 0 for colours.
 */
void ka_stream_setMonochromeDecode(ka_stream_t* stream, int32_t mono)
{
  ka_vstream_setMonochromeDecode(stream->video.vstream, mono);
}

/**
 * Returns information on frame to display.
 *
 * @param  stream    Stream handle.
 *
 * @returns Frame information structure.
 */
ka_vframe_t* ka_stream_getFrame(const ka_stream_t* stream, uint32_t index)
{
  if (stream->video.vstream)
    return ka_vstream_getFrame(stream->video.vstream, index);

  return NULL;
}

/**
 * Discards current display frame so that its memory can be reused.
 *
 * @param  stream    Stream handle.
 */
void ka_stream_releaseFrame(ka_stream_t* stream, uint32_t index)
{
  if (stream->video.vstream)
    ka_vstream_releaseFrame(stream->video.vstream, index);
}

/**
 * Returns the RTC time from which to start the replay.
 * Should only be called when ready_for_play event is issued.
 *
 * @param  stream    Stream handle.
 *
 * @returns RTC time.
 */
uint32_t ka_stream_getRTCStart(const ka_stream_t* stream)
{
  if ((stream->stack->video.status & ka_fifo_status_codecready) && stream->time.synced_start)
    return stream->time.synced_start;

  return stream->time.audio_start;
}

/**
 * Returns position information.
 *
 * @param  stream    Stream handle.
 * @param  pos       Pointer to pos info to fill in.
 */
void ka_stream_getPosInfo(const ka_stream_t* stream, ka_demux_pos_t* pos)
{
  if (stream->demux.demuxer)
    stream->demux.demuxer->vptr->FNgetPos(stream->demux.demuxer, stream->buf, pos);
  else if (stream->input)
  {
    pos->titleNr = 0;
    pos->chapterNr = 0;
    pos->curPos = ka_input_tell(stream->input) - (uint64_t) ka_buffer_getByteCount(stream->buf);
    pos->endPos = ka_input_getLen(stream->input);
  }
}

/**
 * Returns audio information.
 *
 * @param  stream     Stream handle.
 * @param  streamId   StreamId or -1 for active stream.
 * @param  pInfo      Pointer to audio info to fill in.
 */
void ka_stream_getAudioInfo(const ka_stream_t* stream, int32_t streamId, ka_demux_audio_info_t* pInfo)
{
  if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetAudioInfo)
    stream->demux.demuxer->vptr->FNgetAudioInfo(stream->demux.demuxer, streamId, pInfo);
  else
  {
    const ka_program_t* program = &stream->stack->programs[stream->program];

    pInfo->nrStreams = (stream->stack->nr_programs) ? program->nr_audios : 0;

    if (streamId < 0)
      streamId = stream->audio.channel;

    if (streamId && (streamId <= pInfo->nrStreams))
    {
      pInfo->streamId = streamId;
      pInfo->kid = program->audios[streamId - 1];
    }
    else
    {
      pInfo->streamId = 0;
      pInfo->kid = 0;
    }
    pInfo->channels = 0;
    pInfo->lang[0] = 0;
  }
  pInfo->type = ka_block_to_name(pInfo->kid);
}

/**
 * Returns subtitle information.
 *
 * @param  stream     Stream handle.
 * @param  streamId   StreamId or -1 for forced stream, -2 for active stream, -3 as -2 but tweak nrStreams.
 * @param  pInfo      Pointer to subtitle info to fill in.
 */
void ka_stream_getSubtitleInfo(const ka_stream_t* stream, int32_t streamId, ka_demux_subtitle_info_t* pInfo)
{
  if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetSubsInfo)
    stream->demux.demuxer->vptr->FNgetSubsInfo(stream->demux.demuxer, streamId, pInfo);
  else
  {
    const ka_program_t* program = &stream->stack->programs[stream->program];

    pInfo->nrStreams = (stream->stack->nr_programs) ? program->nr_subtitles : 0;

    // May we have reached not reached the first subtitles yet, allow all.
    if ((streamId == -3) && !pInfo->nrStreams)
      pInfo->nrStreams = 9;

    if (streamId < 0)
      streamId = stream->subtitle.channel;

    if (streamId && (streamId <= pInfo->nrStreams))
    {
      pInfo->streamId = streamId;
      pInfo->kid = program->subtitles[streamId - 1];
    }
    else
    {
      pInfo->streamId = 0;
      pInfo->kid = 0;
    }
    pInfo->lang[0] = 0;
  }
  pInfo->type = ka_block_to_name(pInfo->kid);
}

/**
 * Returns angle information.
 *
 * @param  stream     Stream handle.
 * @param  pInfo      Pointer to angle info to fill in,
 */
void ka_stream_getAngleInfo(const ka_stream_t* stream, ka_demux_angle_info_t* pInfo)
{
  if (stream->demux.demuxer)
    stream->demux.demuxer->vptr->FNgetAngle(stream->demux.demuxer, pInfo);
  else
  {
   pInfo->angleId = 1;
   pInfo->nrAngles = 1;
  }
}

/**
 * Seeks within a stream.
 *
 * @param  stream    Stream handle.
 * @param  jump      New position.
 *
 * @returns 1 if performed, 0 if outside of input and ignored, -1 if error.
 */
int ka_stream_seek(ka_stream_t* stream, int64_t jump)
{
  if (!stream->demux.demuxer
  ||  (stream->stack->properties & ps_stack_prop_noseek)
  ||  (stream->state & ka_state_waittime))
    return 0;

  ka_demux_pos_t pos;
  ka_stream_getPosInfo(stream, &pos);

  // limit forward jumps
  if (jump >= pos.endPos)
    return 0;

  if (stream->demux.demuxer)
  {
    jump = stream->demux.demuxer->vptr->FNsetPos(stream->demux.demuxer, jump);
    if ((jump < 0) || (jump > pos.endPos))
      return 0;
  }

  // Seeking, so clear current input
  if (ka_stream_restart(stream))
    return -1;

  if (config.debug & cfg_printseekstats)
    ka_log(ka_log_seek, "posn %llx, jump %llx, end %llx", pos.curPos, jump, pos.endPos);

  return 1;
}

/**
 * Jumps to give chapter.
 *
 * @returns 1 if performed, 0 if outside of input and ignored, -1 if error.
 */
int ka_stream_setChapter(ka_stream_t* stream, uint32_t title, int32_t chapter)
{
  if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNsetChapter && stream->stack)
  {
    const ka_program_t* program = &stream->stack->programs[stream->program];

    if (title < program->nr_titles)
    {
      if (chapter < (int32_t) program->titles[title].nr_chapters)
      {
        uint32_t id;

        if (chapter < 0)
          id = program->titles[title].info.blockid;
        else
          id = program->titles[title].chapters[chapter].blockid;

        if (stream->demux.demuxer->vptr->FNsetChapter(stream->demux.demuxer, id) < 0)
          return 0;

        // Seeking, so clear current input
        if (ka_stream_restart(stream))
          return -1;

        if (config.debug & cfg_printseekstats)
        {
          ka_demux_pos_t pos;
          ka_stream_getPosInfo(stream, &pos);
          ka_log(ka_log_seek, "posn %llx, end %llx", pos.curPos, pos.endPos);
        }

        return 1;
      }
    }
  }

  return 0;
}

/**
 * Selects one of the programs.
 *
 * @param  stream    Stream handle.
 * @param  program   Program index to select.
 * @param  vchannel  Video stream index to select.
 * @param  achannel  Audio stream index to select [1-n].
 * @param  schannel  Subtitles stream index [1-n] to select, or 0 for off.
 */
uint32_t ka_stream_selectProgram(ka_stream_t* stream, uint32_t program
                               , uint32_t vchannel, uint32_t achannel, uint32_t schannel)
{
  if (stream->program == program)
    return program;

  if (program >= stream->stack->nr_programs)
    program = 0;

  stream->program = program;
  stream->video.channel = -1;
  stream->audio.channel = -1;
  stream->subtitle.channel = 0;

  ka_stream_selectVideoStream(stream, vchannel);
  ka_stream_selectAudioStream(stream, achannel);
  ka_stream_selectSubtitleStream(stream, schannel);

  if (config.debug & (cfg_printvideoall | cfg_printreplaystats))
    ka_log(ka_log_video, "<< Switched to program %d >>", stream->program);

  return program;
}

/**
 * Selects one of the input video streams.
 *
 * @param  stream    Stream handle.
 * @param  channel   Video stream index to select.
 */
uint32_t ka_stream_selectVideoStream(ka_stream_t* stream, uint32_t channel)
{
  if (stream->video.channel == channel)
    return channel;

  if (channel >= stream->stack->programs[stream->program].nr_videos)
    channel = 0;

  stream->video.channel = channel;
  stream->video.typ = 0;
  stream->video.params = NULL;

  // purge old video info
  ka_stream_clearvideo(stream, 1);
  stream->state &= ~ka_state_replaying;
  timer_resetrtc();
  stream->time.restart = timer_gettime();

  if (config.debug & (cfg_printvideoall | cfg_printreplaystats))
    ka_log(ka_log_video, "<< Switched to channel %d >>", stream->video.channel);

  return channel;
}

/**
 * Selects one of the input audio streams.
 *
 * @param  stream    Stream handle.
 * @param  channel   Audio stream index to select [1-n], or 0 for off or -1 to ask demuxer.
 *
 * @returns active channel.
 */
uint32_t ka_stream_selectAudioStream(ka_stream_t* stream, int32_t channel)
{
  if (channel == -1)
  {
    if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetAudioInfo)
    {
      ka_demux_audio_info_t info;

      stream->demux.demuxer->vptr->FNgetAudioInfo(stream->demux.demuxer, -1, &info);
      stream->audio.channel = info.streamId;
      stream->audio.typ = info.kid;
    }
    else
      return stream->audio.channel;
  }
  else
  {
    if (stream->audio.channel == channel)
      return channel;

    if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetAudioInfo)
    {
      if (!stream->demux.demuxer->vptr->FNselectAudio(stream->demux.demuxer, channel))
      {
        // Unchanged
        return stream->audio.channel;
      }

      ka_demux_audio_info_t info;

      stream->demux.demuxer->vptr->FNgetAudioInfo(stream->demux.demuxer, -1, &info);
      stream->audio.channel = info.streamId;
      stream->audio.typ = info.kid;
    }
    else
    {
      if (channel > stream->stack->programs[stream->program].nr_audios)
        channel = 1;
      else if (channel < 1) channel = 1;

      stream->audio.channel = channel;
      stream->audio.typ = 0;
      stream->audio.params = NULL;
    }
  }

  // purge old audio info
  ka_stream_clearaudio(stream, 1);

  // restart pos?
  if (stream->stack->properties & ps_stack_prop_notimestamp)
    stream->time.synced_start = 0;
  else
    stream->time.synced_start = timer_getrtc();

  if (config.debug & (cfg_printaudioall | cfg_printreplaystats))
    ka_log(ka_log_audio, "<< Switched to channel %d >>", stream->audio.channel);

  return stream->audio.channel;
}

/**
 * Selects one of the input subtitles streams.
 *
 * @param  stream    Stream handle.
 * @param  channel   Subtitles stream index [1-n] to select, or 0 for off or -1 to ask demuxer.
 *
 * @returns active channel.
 */
uint32_t ka_stream_selectSubtitleStream(ka_stream_t* stream, int32_t channel)
{
  if (channel == -1)
  {
    if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetSubsInfo)
    {
      ka_demux_subtitle_info_t info;

      stream->demux.demuxer->vptr->FNgetSubsInfo(stream->demux.demuxer, -2, &info);
      stream->subtitle.channel = info.streamId;
      stream->subtitle.typ = info.kid;
    }
    else
      return stream->subtitle.channel;
  }
  else
  {
    if (stream->demux.demuxer && stream->demux.demuxer->vptr->FNgetSubsInfo)
    {
      if (!stream->demux.demuxer->vptr->FNselectSubs(stream->demux.demuxer, channel))
      {
        // Unchanged
        return stream->subtitle.channel;
      }

      ka_demux_subtitle_info_t info;

      stream->demux.demuxer->vptr->FNgetSubsInfo(stream->demux.demuxer, -2, &info);
      stream->subtitle.channel = info.streamId;
      stream->subtitle.typ = info.kid;
    }
    else
    {
      if (stream->subtitle.channel == channel)
        return channel;

      if (!stream->stack->programs[stream->program].nr_subtitles)
      {
        stream->subtitle.channel = channel;
        stream->subtitle.typ = 0;
      }
      else
      {
        if (channel > stream->stack->programs[stream->program].nr_subtitles)
          channel = 0; // off

        stream->subtitle.channel = channel;
        stream->subtitle.typ = channel ? stream->stack->programs[stream->program].subtitles[channel - 1] : 0;
      }
    }
  }
//  stream->subtitle.params = NULL;

  if (config.debug & (cfg_printsubtitleall | cfg_printreplaystats))
    ka_log(ka_log_subtitle, "<< Switched to channel %d >>", stream->subtitle.channel);

  return stream->subtitle.channel;
}

/**
 * Set angle.
 *
 * @param  stream    Stream handle.
 * @param  angle     Angle.
 *
 * @returns 1 if performed, 0 if outside of input and ignored, -1 if error.
 */
int ka_stream_setAngle(ka_stream_t* stream, uint32_t angle)
{
  if (stream->demux.demuxer)
  {
    if (stream->demux.demuxer->vptr->FNsetAngle(stream->demux.demuxer, angle))
    {
      ka_stream_clear(stream, 1);
      return 1;
    }
  }
  return 0;
}

/**
 * Log stats of the input stream.
 *
 * @param  stream    Stream handle.
 */
void ka_stream_logStats(const ka_stream_t* stream)
{
  char str1[64];
  int i, j;

  for (i = 0; i < stream->stack->nr_programs; i++)
  {
    ka_program_t* program = &stream->stack->programs[i];
    ka_log(ka_log_stats, "%s %2d:", report_string(str1, sizeof(str1), "info_program"), i);

    for (j = 0; j < program->nr_videos; j++)
    {
      if (!j) ka_log(ka_log_stats, " %s:", report_string(str1, sizeof(str1), "info_vstreams"));
      if (ka_stack_getCountForKid(stream->stack, program->videos[j]))
        ka_log(ka_log_stats, " %2d: %08x - %s", j, program->videos[j], ka_block_to_name(program->videos[j]));
      else
      {
        ka_logs(ka_log_stats, "  -: %08x - %s", program->videos[j], ka_block_to_name(program->videos[j]));
        ka_logn("%s\n", report_string(str1, sizeof(str1), "info_nopackets"));
      }
    }

    for (j = 0; j < program->nr_audios; j++)
    {
      if (!j) ka_log(ka_log_stats, " %s:", report_string(str1, sizeof(str1), "info_astreams"));
      if (ka_stack_getCountForKid(stream->stack, program->audios[j]))
        ka_log(ka_log_stats, " %2d: %08x - %s", j, program->audios[j], ka_block_to_name(program->audios[j]));
      else
      {
        ka_logs(ka_log_stats, "  -: %08x - %s", program->audios[j], ka_block_to_name(program->audios[j]));
        ka_logn("%s\n", report_string(str1, sizeof(str1), "info_nopackets"));
      }
    }

    for (j = 0; j < program->nr_subtitles; j++)
    {
      if (!j) ka_log(ka_log_stats, " %s:", report_string(str1, sizeof(str1), "info_sstreams"));
      if (ka_stack_getCountForKid(stream->stack, program->subtitles[j]))
        ka_log(ka_log_stats, " %2d: %08x - %s", j, program->subtitles[j], ka_block_to_name(program->subtitles[j]));
      else
      {
        ka_logs(ka_log_stats, "  -: %08x - %s", program->subtitles[j], ka_block_to_name(program->subtitles[j]));
        ka_logn("%s\n", report_string(str1, sizeof(str1), "info_nopackets"));
      }
    }
  }
}

const char* ka_stream_getTitleName(ka_stream_t* stream, uint32_t prg, uint32_t title)
{
  ka_stack_t* stack = stream->stack;

  if (prg >= stack->nr_programs)
    return NULL;

  ka_program_t* program = &stack->programs[prg];

  if (title >= program->nr_titles)
    return NULL;

  return program->titles[title].info.name;
}

const char* ka_stream_getChapterName(ka_stream_t* stream, uint32_t prg, uint32_t title, uint32_t chapter)
{
  ka_stack_t* stack = stream->stack;

  if (prg >= stack->nr_programs)
    return NULL;

  ka_program_t* program = &stack->programs[prg];

  if (title >= program->nr_titles)
    return NULL;

  if (chapter >= program->titles[title].nr_chapters)
    return NULL;

  return program->titles[title].chapters[chapter].name;
}

int ka_stream_navigate(ka_stream_t* stream, int key)
{
  int ret = 0;
  if (stream->demux.demuxer
  &&  stream->demux.demuxer->vptr->FNnavigate)
  {
    ret = stream->demux.demuxer->vptr->FNnavigate(stream->demux.demuxer, timer_getrtc(), key);

    if (ret & ka_nav_discontinuity)
    {
      // Seeking, so clear current input
      ka_stream_restart(stream);
    }
    return ret;
  }

  // Could implement here some basic navigation

  return 0;
}

ka_stream_event ka_stream_checkSectionChange(ka_stream_t* stream, ka_stream_event event, ka_vframe_t* nextFrame)
{
  if (stream->demux.demuxer->vptr->FNsectionDone == NULL)
    return event;

  if (stream->state & ka_state_waittime)
  {
    // Still time started yet?
    if (!stream->video.waitTimeStart)
    {
      stream->video.waitTimeStart = timer_getrtc();

      if (stream->demux.demuxer->vptr->FNgetWaitTime != NULL)
        stream->video.waitTime = stream->demux.demuxer->vptr->FNgetWaitTime(stream->demux.demuxer, stream->video.waitTimeStart);
      else
        stream->video.waitTime = 0;

      if (config.debug & cfg_printnavstats)
        ka_log(ka_log_nav, "Wait duration %d", stream->video.waitTime);
    }

    // Still time expired ?
    if (stream->video.waitTime)
    {
      if (stream->video.waitTime == -1)
        return ka_stream_event_wait; // Infinite still time

      if (((int32_t)(timer_getrtc() - stream->video.waitTimeStart)) < stream->video.waitTime)
        return ka_stream_event_wait;
    }

    // Done with still time
    stream->state &= ~ka_state_waittime;
    stream->video.waitTimeStart = 0;
    stream->video.waitTime = 0;
  }
  else
  {
    int same = 1;

    if (nextFrame != NULL)
    {
      if (stream->displaySection == -1)
        stream->displaySection = nextFrame->section;
      else if (stream->displaySection != nextFrame->section)
        same = 0;
    }

    // no more frames ?
    if (nextFrame == NULL)
    {
      if ((stream->stack->video.status & ka_fifo_status_used)
      &&  (stream->stack->video.status & ka_fifo_status_codecdone))
      {
        if (!(stream->stack->audio.status & ka_fifo_status_used)
        ||  (stream->stack->audio.status & ka_fifo_status_outputdone))
          same = 0;
      }
    }

    if (same)
      return event;

    stream->state |= ka_state_waittime;

    // Allow buffering to resume
    ka_stream_resetAllDone(stream);

    return ka_stream_event_wait;
  }

  stream->displaySection = -1;

  ka_demux_section_state rc
      = (stream->demux.demuxer->vptr->FNsectionDone != NULL)
      ? stream->demux.demuxer->vptr->FNsectionDone(stream->demux.demuxer, KA_DEMUX_SECTION_DONE_DISPLAY)
      : KA_DEMUX_SECTION_STATE_EXIT;

  switch(rc)
  {
    case KA_DEMUX_SECTION_STATE_ERROR:
      return ka_stream_event_inerror;
    break;
    case KA_DEMUX_SECTION_STATE_WAIT:
      return event;
    break;
    case KA_DEMUX_SECTION_STATE_NEXTSECTION:
      return ka_stream_event_buffering;
    break;
    case KA_DEMUX_SECTION_STATE_DISCONTINUITY:
    {
      stream->state &= ~ka_state_future_load;
      return ka_stream_event_discontinuity;
    }
    break;
    default:
      stream->state &= ~ka_state_future_load;
      return ka_stream_event_end;
    break;
  }
}
