Exemple #1
0
/*
 *  status = binToTextKey(bin_val, text_val, text_len, &index);
 *
 *    Given the buffer 'bin_val' which was filled by calling
 *    recToBinKey(), write a textual representation of that value into
 *    'text_val', a buffer of 'text_len' characters.
 */
static skplugin_err_t
binToTextKey(
    const uint8_t      *bin_value,
    char               *text_value,
    size_t              text_size,
    void               *idx)
{
    uint64_t val_u64;

    switch (*((unsigned int*)(idx))) {
      case PAYLOAD_BYTES_KEY:
        memcpy(&val_u64, bin_value, RATE_BINARY_SIZE_KEY);
        snprintf(text_value, text_size, ("%" PRIu64),
                 ntoh64(val_u64));
        return SKPLUGIN_OK;

      case PAYLOAD_RATE_KEY:
      case PCKTS_PER_SEC_KEY:
      case BYTES_PER_SEC_KEY:
      case BYTES_PER_PACKET_KEY:
        memcpy(&val_u64, bin_value, RATE_BINARY_SIZE_KEY);
        snprintf(text_value, text_size, FORMAT_PRECISION,
                 UINT64_TO_DOUBLE(ntoh64(val_u64)));
        return SKPLUGIN_OK;
    }

    return SKPLUGIN_ERR_FATAL;
}
Exemple #2
0
static char* PrintCntlInfo(
	MP4FileHandle mp4File,
	MP4TrackId trackId)
{
  const char *media_data_name = MP4GetTrackMediaDataName(mp4File, trackId);
  const char *typeName = "Unknown";

  if (media_data_name == NULL) {
    typeName = "Unknown - no media data name";
  } else if (strcasecmp(media_data_name, "href") == 0) {
    typeName = "ISMA Href";
  } else {
    typeName = media_data_name;
  }

  MP4Duration trackDuration = 
    MP4GetTrackDuration(mp4File, trackId);
 
  double msDuration = 
    UINT64_TO_DOUBLE(MP4ConvertFromTrackDuration(mp4File, trackId, 
						 trackDuration, MP4_MSECS_TIME_SCALE));
  char *sInfo = (char *)MP4Malloc(256);

  snprintf(sInfo, 256,
	   "%u\tcontrol\t%s, %.3f secs\n",
	   trackId, 
	   typeName,
	   msDuration / 1000.0);
  return sInfo;
}
void CAviVideoByteStream::play (uint64_t start)
{
  m_play_start_time = start;

  double time = UINT64_TO_DOUBLE(start);
  time *= m_frame_rate;
  time /= 1000;
#if 0
  player_debug_message("avi video frame " D64 , start);
#endif
  // we've got the position;
  video_set_timebase((uint32_t)time);
}
/**************************************************************************
 * Quicktime stream base class functions
 **************************************************************************/
CMp4ByteStream::CMp4ByteStream (CMp4File *parent,
				MP4TrackId track,
				const char *type,
				bool has_video)
  : COurInByteStream(type)
{
#ifdef ISMACRYP_DEBUG
  my_enc_file = fopen("encbuffer.raw", "w");
  my_unenc_file = fopen("unencbuffer.raw", "w");
  my_unenc_file2 = fopen("unencbuffer2.raw", "w");
#endif
#ifdef OUTPUT_TO_FILE
  char buffer[80];
  strcpy(buffer, type);
  strcat(buffer, ".raw");
  m_output_file = fopen(buffer, "w");
#endif
  m_track = track;
  m_frame_on = 1;
  m_parent = parent;
  m_eof = false;
  MP4FileHandle fh = parent->get_file();
  m_frames_max = MP4GetTrackNumberOfSamples(fh, m_track);
  mp4f_message(LOG_DEBUG, "%s - %u samples", type, m_frames_max);
  m_max_frame_size = MP4GetTrackMaxSampleSize(fh, m_track) + 4; 
  m_sample_freq = MP4GetTrackTimeScale(fh, m_track);
  m_buffer = (u_int8_t *) malloc(m_max_frame_size * sizeof(u_int8_t));
  m_has_video = has_video;
  m_frame_in_buffer = 0xffffffff;
  MP4Duration trackDuration;
  trackDuration = MP4GetTrackDuration(fh, m_track);
  uint64_t max_ts;
  max_ts = MP4ConvertFromTrackDuration(fh, 
				       m_track, 
				       trackDuration,
				       MP4_MSECS_TIME_SCALE);
  m_max_time = UINT64_TO_DOUBLE(max_ts);
  m_max_time /= 1000.0;
  mp4f_message(LOG_DEBUG, 
	       "MP4 %s max time is "U64" %g", type, max_ts, m_max_time);
  read_frame(1, NULL);
}
Exemple #5
0
static char* PrintAudioInfo(
	MP4FileHandle mp4File,
	MP4TrackId trackId)
{
	static const char* mpeg4AudioNames[] = {
		"MPEG-4 AAC main",
		"MPEG-4 AAC LC",
		"MPEG-4 AAC SSR",
		"MPEG-4 AAC LTP",
		"MPEG-4 AAC HE",
		"MPEG-4 AAC Scalable",
		"MPEG-4 TwinVQ",
		"MPEG-4 CELP",
		"MPEG-4 HVXC",
		NULL, NULL,
		"MPEG-4 TTSI",
		"MPEG-4 Main Synthetic",
		"MPEG-4 Wavetable Syn",
		"MPEG-4 General MIDI",
		"MPEG-4 Algo Syn and Audio FX",
		"MPEG-4 ER AAC LC",
		NULL,
		"MPEG-4 ER AAC LTP",
		"MPEG-4 ER AAC Scalable",
		"MPEG-4 ER TwinVQ",
		"MPEG-4 ER BSAC",
		"MPEG-4 ER ACC LD",
		"MPEG-4 ER CELP",
		"MPEG-4 ER HVXC",
		"MPEG-4 ER HILN",
		"MPEG-4 ER Parametric",
		"MPEG-4 SSC",
		"MPEG-4 PS",
		"MPEG-4 MPEG Surround",
		NULL,
		"MPEG-4 Layer-1",
		"MPEG-4 Layer-2",
		"MPEG-4 Layer-3",
		"MPEG-4 DST",
		"MPEG-4 Audio Lossless",
		"MPEG-4 SLS",
		"MPEG-4 SLS non-core", 
	};

	static const u_int8_t mpegAudioTypes[] = {
		MP4_MPEG2_AAC_MAIN_AUDIO_TYPE,	// 0x66
		MP4_MPEG2_AAC_LC_AUDIO_TYPE,	// 0x67
		MP4_MPEG2_AAC_SSR_AUDIO_TYPE,	// 0x68
		MP4_MPEG2_AUDIO_TYPE,			// 0x69
		MP4_MPEG1_AUDIO_TYPE,			// 0x6B
		// private types
		MP4_PCM16_LITTLE_ENDIAN_AUDIO_TYPE,
		MP4_VORBIS_AUDIO_TYPE,
		MP4_ALAW_AUDIO_TYPE,
		MP4_ULAW_AUDIO_TYPE,
		MP4_G723_AUDIO_TYPE,
		MP4_PCM16_BIG_ENDIAN_AUDIO_TYPE,
	};
	static const char* mpegAudioNames[] = {
		"MPEG-2 AAC Main",
		"MPEG-2 AAC LC",
		"MPEG-2 AAC SSR",
		"MPEG-2 Audio (13818-3)",
		"MPEG-1 Audio (11172-3)",
		// private types
		"PCM16 (little endian)",
		"Vorbis",
		"G.711 aLaw",
		"G.711 uLaw",
		"G.723.1",
		"PCM16 (big endian)",
	};
	u_int8_t numMpegAudioTypes =
		sizeof(mpegAudioTypes) / sizeof(u_int8_t);

	const char* typeName = "Unknown";
	bool foundType = false;
	u_int8_t type = 0;
	const char *media_data_name;

	media_data_name = MP4GetTrackMediaDataName(mp4File, trackId);

	if (media_data_name == NULL) {
	  typeName = "Unknown - no media data name";
	} else if (strcasecmp(media_data_name, "samr") == 0) {
	    typeName = "AMR";
	    foundType = true;
	} else if (strcasecmp(media_data_name, "sawb") == 0) {
	    typeName = "AMR-WB";
	    foundType = true;
	} else if (strcasecmp(media_data_name, "mp4a") == 0) {
	    
	  type = MP4GetTrackEsdsObjectTypeId(mp4File, trackId);
	  switch (type) {
	  case MP4_INVALID_AUDIO_TYPE:
	    typeName = "AAC from .mov";
	    foundType = true;
	    break;
	  case MP4_MPEG4_AUDIO_TYPE:  {
	
	    type = MP4GetTrackAudioMpeg4Type(mp4File, trackId);
	    if (type == MP4_MPEG4_INVALID_AUDIO_TYPE ||
		type > NUM_ELEMENTS_IN_ARRAY(mpeg4AudioNames) || 
		mpeg4AudioNames[type - 1] == NULL) {
	      typeName = "MPEG-4 Unknown Profile";
	    } else {
	      typeName = mpeg4AudioNames[type - 1];
	      foundType = true;
	    }
	    break;
	  }
	    // fall through
	  default:
	    for (u_int8_t i = 0; i < numMpegAudioTypes; i++) {
	      if (type == mpegAudioTypes[i]) {
		typeName = mpegAudioNames[i];
		foundType = true;
		break;
	      }
	    }
	  }
	} else {
	  typeName = media_data_name;
	  foundType = true;
	}

	u_int32_t timeScale =
		MP4GetTrackTimeScale(mp4File, trackId);

	MP4Duration trackDuration =
		MP4GetTrackDuration(mp4File, trackId);

	double msDuration =
		UINT64_TO_DOUBLE(MP4ConvertFromTrackDuration(mp4File, trackId,
			trackDuration, MP4_MSECS_TIME_SCALE));

	u_int32_t avgBitRate =
		MP4GetTrackBitRate(mp4File, trackId);

	char *sInfo = (char*)MP4Malloc(256);

	// type duration avgBitrate samplingFrequency
	if (foundType)
	  snprintf(sInfo, 256, 
		  "%u\taudio\t%s%s, %.3f secs, %u kbps, %u Hz\n",
		  trackId,
		  MP4IsIsmaCrypMediaTrack(mp4File, trackId) ? "enca - " : "",
		  typeName,
		  msDuration / 1000.0,
		  (avgBitRate + 500) / 1000,
		  timeScale);
	else
	  snprintf(sInfo, 256,
		  "%u\taudio\t%s%s(%u), %.3f secs, %u kbps, %u Hz\n",
		  trackId,
		  MP4IsIsmaCrypMediaTrack(mp4File, trackId) ? "enca - " : "",
		  typeName,
		  type,
		  msDuration / 1000.0,
		  (avgBitRate + 500) / 1000,
		  timeScale);

	return sInfo;
}
Exemple #6
0
static char* PrintVideoInfo(
	MP4FileHandle mp4File,
	MP4TrackId trackId)
{

	static const u_int8_t mpegVideoTypes[] = {
		MP4_MPEG2_SIMPLE_VIDEO_TYPE,	// 0x60
		MP4_MPEG2_MAIN_VIDEO_TYPE,		// 0x61
		MP4_MPEG2_SNR_VIDEO_TYPE,		// 0x62
		MP4_MPEG2_SPATIAL_VIDEO_TYPE,	// 0x63
		MP4_MPEG2_HIGH_VIDEO_TYPE,		// 0x64
		MP4_MPEG2_442_VIDEO_TYPE,		// 0x65
		MP4_MPEG1_VIDEO_TYPE,			// 0x6A
		MP4_JPEG_VIDEO_TYPE,			// 0x6C
		MP4_YUV12_VIDEO_TYPE,
		MP4_H263_VIDEO_TYPE,
		MP4_H261_VIDEO_TYPE,
	};
	static const char* mpegVideoNames[] = {
		"MPEG-2 Simple",
		"MPEG-2 Main",
		"MPEG-2 SNR",
		"MPEG-2 Spatial",
		"MPEG-2 High",
		"MPEG-2 4:2:2",
		"MPEG-1",
		"JPEG",
		"YUV12",
		"H.263",
		"H.261",
	};
	u_int8_t numMpegVideoTypes =
		sizeof(mpegVideoTypes) / sizeof(u_int8_t);
	bool foundTypeName = false;
	const char* typeName = "Unknown";

	const char *media_data_name;
	char originalFormat[8];
	char  oformatbuffer[32];
	originalFormat[0] = 0;
	*oformatbuffer = 0;
	uint8_t type = 0;
	
	media_data_name = MP4GetTrackMediaDataName(mp4File, trackId);
	// encv 264b
	if (strcasecmp(media_data_name, "encv") == 0) {
	  if (MP4GetTrackMediaDataOriginalFormat(mp4File, 
						 trackId, 
						 originalFormat, 
						 sizeof(originalFormat)) == false)
	      media_data_name = NULL;
	      
	}
  
	char  typebuffer[80];
	if (media_data_name == NULL) {
	  typeName = "Unknown - no media data name";
	  foundTypeName = true;
	} else if ((strcasecmp(media_data_name, "avc1") == 0) ||
	  	(strcasecmp(originalFormat, "264b") == 0)) {
	  // avc
	  uint8_t profile, level;
	  char profileb[20], levelb[20];
	  if (MP4GetTrackH264ProfileLevel(mp4File, trackId, 
					  &profile, &level)) {
	    if (profile == 66) {
	      strcpy(profileb, "Baseline");
	    } else if (profile == 77) {
	      strcpy(profileb, "Main");
	    } else if (profile == 88) {
	      strcpy(profileb, "Extended");
	    } else if (profile == 100) {
	      strcpy(profileb, "High");
	    } else if (profile == 110) {
	      strcpy(profileb, "High 10");
	    } else if (profile == 122) {
	      strcpy(profileb, "High 4:2:2");
	    } else if (profile == 144) {
	      strcpy(profileb, "High 4:4:4");
	    } else {
	      snprintf(profileb, 20, "Unknown Profile %x", profile);
	    } 
	    switch (level) {
	    case 10: case 20: case 30: case 40: case 50:
	      snprintf(levelb, 20, "%u", level / 10);
	      break;
	    case 11: case 12: case 13:
	    case 21: case 22:
	    case 31: case 32:
	    case 41: case 42:
	    case 51:
	      snprintf(levelb, 20, "%u.%u", level / 10, level % 10);
	      break;
	    default:
	      snprintf(levelb, 20, "unknown level %x", level);
	      break;
	    }
	    if (originalFormat != NULL && originalFormat[0] != '\0') 
	      snprintf(oformatbuffer, 32, "(%s) ", originalFormat);
	    snprintf(typebuffer, sizeof(typebuffer), "H264 %s%s@%s", 
		    oformatbuffer, profileb, levelb);
	    typeName = typebuffer;
	  } else {
	    typeName = "H.264 - profile/level error";
	  }
	  foundTypeName = true;
	} else if (strcasecmp(media_data_name, "s263") == 0) {
	  // 3gp h.263
	  typeName = "H.263";
	  foundTypeName = true;
	} else if ((strcasecmp(media_data_name, "mp4v") == 0) ||
		   (strcasecmp(media_data_name, "encv") == 0)) {
	  // note encv might needs it's own field eventually.
	  type = MP4GetTrackEsdsObjectTypeId(mp4File, trackId);
	  if (type == MP4_MPEG4_VIDEO_TYPE) {
	    type = MP4GetVideoProfileLevel(mp4File, trackId);
	    typeName = Mpeg4VisualProfileName(type);
	    if (typeName == NULL) {
	      typeName = "MPEG-4 Unknown Profile";
	    } else {
	      foundTypeName = true;
	    }
	  } else {
	    for (u_int8_t i = 0; i < numMpegVideoTypes; i++) {
	      if (type == mpegVideoTypes[i]) {
		typeName = mpegVideoNames[i];
		foundTypeName = true;
		break;
	      }
	    }
	  }
	} else {
	  typeName = media_data_name;
	  foundTypeName = true; // we don't have a type value to display
	}

	MP4Duration trackDuration =
		MP4GetTrackDuration(mp4File, trackId);

	double msDuration =
		UINT64_TO_DOUBLE(MP4ConvertFromTrackDuration(mp4File, trackId,
			trackDuration, MP4_MSECS_TIME_SCALE));

	u_int32_t avgBitRate =
		MP4GetTrackBitRate(mp4File, trackId);

	// Note not all mp4 implementations set width and height correctly
	// The real answer can be buried inside the ES configuration info
	u_int16_t width = MP4GetTrackVideoWidth(mp4File, trackId);

	u_int16_t height = MP4GetTrackVideoHeight(mp4File, trackId);

	double fps = MP4GetTrackVideoFrameRate(mp4File, trackId);

	char *sInfo = (char*)MP4Malloc(256);

	// type duration avgBitrate frameSize frameRate
	if (foundTypeName) {
	  sprintf(sInfo,
		  "%u\tvideo\t%s%s, %.3f secs, %u kbps, %ux%u @ %f fps\n",
		  trackId,
		  MP4IsIsmaCrypMediaTrack(mp4File, trackId) ? "encv - " : "",
		  typeName,
		  msDuration / 1000.0,
		  (avgBitRate + 500) / 1000,
		  width,
		  height,
		  fps
		  );
	} else {
	  sprintf(sInfo,
		  "%u\tvideo\t%s(%u), %.3f secs, %u kbps, %ux%u @ %f fps\n",
		  trackId,
		  typeName,
		  type, 
		  msDuration / 1000.0,
		  (avgBitRate + 500) / 1000,
		  width,
		  height,
		  fps
		  );
	}

	return sInfo;
}
/*
 * play_all_media - get all media to play
 */
int CPlayerSession::play_all_media (int start_from_begin, 
				    double start_time,
				    char *errmsg, 
				    uint32_t errlen)
{
  int ret;
  CPlayerMedia *p;

  if (m_set_end_time == 0) {
    range_desc_t *range;
    if (m_sdp_info && m_sdp_info->session_range.have_range != FALSE) {
      range = &m_sdp_info->session_range;
    } else {
      range = NULL;
      p = m_my_media;
      while (range == NULL && p != NULL) {
	media_desc_t *media;
	media = p->get_sdp_media_desc();
	if (media && media->media_range.have_range) {
	  range = &media->media_range;
	}
	p = p->get_next();
      }
    }
    if (range != NULL) {
      m_end_time = (uint64_t)(range->range_end * 1000.0);
      m_set_end_time = 1;
    }
  }
  p = m_my_media;
  m_session_state = SESSION_BUFFERING;
  if (m_paused == 1 && start_time == 0.0 && start_from_begin == FALSE) {
    /*
     * we were paused.  Continue.
     */
    m_play_start_time = m_current_time;
    start_time = UINT64_TO_DOUBLE(m_current_time);
    start_time /= 1000.0;
    player_debug_message("Restarting at " U64 ", %g", m_current_time, start_time);
  } else {
    /*
     * We might have been paused, but we're told to seek
     */
    // Indicate what time we're starting at for sync task.
    m_play_start_time = (uint64_t)(start_time * 1000.0);
  }
  m_paused = 0;

  send_sync_thread_a_message(MSG_START_SESSION);
  // If we're doing aggregate rtsp, send the play command...

  if (session_control_is_aggregate() &&
      m_dont_send_first_rtsp_play == 0) {
    char buffer[80];
    rtsp_command_t cmd;
    rtsp_decode_t *decode;

    memset(&cmd, 0, sizeof(rtsp_command_t));
    if (m_set_end_time != 0) {
      uint64_t stime = (uint64_t)(start_time * 1000.0);
      sprintf(buffer, "npt="U64"."U64"-"U64"."U64, 
	      stime / 1000, stime % 1000, m_end_time / 1000, m_end_time % 1000);
      cmd.range = buffer;
    }
    if (rtsp_send_aggregate_play(m_rtsp_client,
				 m_session_control_url,
				 &cmd,
				 &decode) != 0) {
      if (errmsg != NULL) {
	snprintf(errmsg, errlen, "RTSP Aggregate Play Error %s-%s", 
		 decode->retcode,
		 decode->retresp != NULL ? decode->retresp : "");
      }
      player_debug_message("RTSP aggregate play command failed");
      free_decode_response(decode);
      return (-1);
    }
    if (decode->rtp_info == NULL) {
      player_error_message("No rtp info field");
    } else {
      player_debug_message("rtp info is \'%s\'", decode->rtp_info);
    }
    int ret = process_rtsp_rtpinfo(decode->rtp_info, this, NULL);
    free_decode_response(decode);
    if (ret < 0) {
      if (errmsg != NULL) {
	snprintf(errmsg, errlen, "RTSP aggregate RtpInfo response failure");
      }
      player_debug_message("rtsp aggregate rtpinfo failed");
      return (-1);
    }
  }
  m_dont_send_first_rtsp_play = 0;

  while (p != NULL) {
    ret = p->do_play(start_time, errmsg, errlen);
    if (ret != 0) return (ret);
    p = p->get_next();
  }
  return (0);
}
/*
 * Main decode thread.
 */
int CPlayerMedia::decode_thread (void) 
{
  //  uint32_t msec_per_frame = 0;
  int ret = 0;
  int thread_stop = 0, decoding = 0;
  uint32_t decode_skipped_frames = 0;
  frame_timestamp_t ourtime, lasttime;
      // Tell bytestream we're starting the next frame - they'll give us
      // the time.
  uint8_t *frame_buffer;
  uint32_t frame_len;
  void *ud = NULL;
  
  uint32_t frames_decoded;
  uint64_t start_decode_time = 0;
  uint64_t last_decode_time = 0;
  bool have_start_time = false;
  bool have_frame_ts = false;
  bool found_out_of_range_ts = false;
  uint64_t bytes_decoded;

  lasttime.msec_timestamp = 0;
  frames_decoded = 0;
  bytes_decoded = 0;


  while (thread_stop == 0) {
    // waiting here for decoding or thread stop
    ret = SDL_SemWait(m_decode_thread_sem);
#ifdef DEBUG_DECODE
    media_message(LOG_DEBUG, "%s Decode thread awake",
		  get_name());
#endif
    parse_decode_message(thread_stop, decoding);

    if (decoding == 1) {
      // We've been told to start decoding - if we don't have a codec, 
      // create one
      m_sync->set_wait_sem(m_decode_thread_sem);
      if (m_plugin == NULL) {
	switch (m_sync_type) {
	case VIDEO_SYNC:
	  create_video_plugin(NULL, 
			      STREAM_TYPE_RTP,
			      NULL,
			      -1,
			      -1,
			      m_media_fmt,
			      NULL,
			      m_user_data,
			      m_user_data_size);
	  break;
	case AUDIO_SYNC:
	  create_audio_plugin(NULL,
			      STREAM_TYPE_RTP,
			      NULL,
			      -1, 
			      -1, 
			      m_media_fmt,
			      NULL,
			      m_user_data,
			      m_user_data_size);
	  break;
	case TIMED_TEXT_SYNC:
	  create_text_plugin(NULL,
			     STREAM_TYPE_RTP, 
			     NULL, 
			     m_media_fmt, 
			     m_user_data, 
			     m_user_data_size);
	  break;
	}
	if (m_plugin_data == NULL) {
	  m_plugin = NULL;
	} else {
	  media_message(LOG_DEBUG, "Starting %s codec from decode thread",
			m_plugin->c_name);
	}
      }
      if (m_plugin != NULL) {
	m_plugin->c_do_pause(m_plugin_data);
      } else {
	while (thread_stop == 0 && decoding) {
	  SDL_Delay(100);
	  if (m_rtp_byte_stream) {
	    m_rtp_byte_stream->flush_rtp_packets();
	  }
	  parse_decode_message(thread_stop, decoding);
	}
      }
    }
    /*
     * this is our main decode loop
     */
#ifdef DEBUG_DECODE
    media_message(LOG_DEBUG, "%s Into decode loop", get_name());
#endif
    while ((thread_stop == 0) && decoding) {
      parse_decode_message(thread_stop, decoding);
      if (thread_stop != 0)
	continue;
      if (decoding == 0) {
	m_plugin->c_do_pause(m_plugin_data);
	have_frame_ts = false;
	continue;
      }
      if (m_byte_stream->eof()) {
	media_message(LOG_INFO, "%s hit eof", get_name());
	if (m_sync) m_sync->set_eof();
	decoding = 0;
	continue;
      }
      if (m_byte_stream->have_frame() == false) {
	// Indicate that we're waiting, and wait for a message from RTP
	// task.
	wait_on_bytestream();
	continue;
      }

      frame_buffer = NULL;
      bool have_frame;
      memset(&ourtime, 0, sizeof(ourtime));
      have_frame = m_byte_stream->start_next_frame(&frame_buffer, 
						   &frame_len,
						   &ourtime,
						   &ud);
      if (have_frame == false) continue;
      /*
       * If we're decoding video, see if we're playing - if so, check
       * if we've fallen significantly behind the audio
       */
      if (get_sync_type() == VIDEO_SYNC &&
	  (m_parent->get_session_state() == SESSION_PLAYING) &&
	  have_frame_ts) {
	int64_t ts_diff = ourtime.msec_timestamp - lasttime.msec_timestamp;

	if (ts_diff > TO_D64(1000) ||
	    ts_diff < TO_D64(-1000)) {
	  // out of range timestamp - we'll want to not skip here
	  found_out_of_range_ts = true;
	  media_message(LOG_INFO, "found out of range ts "U64" last "U64" "D64,
			ourtime.msec_timestamp, 
			lasttime.msec_timestamp,
			ts_diff);
	} else {
	  uint64_t current_time = m_parent->get_playing_time();
	  if (found_out_of_range_ts) {
	    ts_diff = current_time - ourtime.msec_timestamp;
	    if (ts_diff > TO_D64(0) && ts_diff < TO_D64(5000)) {
	      found_out_of_range_ts = false;
	      media_message(LOG_INFO, 
			    "ts back in playing range "U64" "D64,
			    ourtime.msec_timestamp, ts_diff);
	    }
	  } else {
	    // regular time
	    if (current_time >= ourtime.msec_timestamp) {
	      media_message(LOG_INFO, 
			    "Candidate for skip decode "U64" our "U64, 
			    current_time, ourtime.msec_timestamp);
	      // If the bytestream can skip ahead, let's do so
	      if (m_byte_stream->can_skip_frame() != 0) {
		int ret;
		int hassync;
		int count;
		current_time += 200; 
		count = 0;
		// Skip up to the current time + 200 msec
		ud = NULL;
		do {
		  if (ud != NULL) free(ud);
		  frame_buffer = NULL;
		  ret = m_byte_stream->skip_next_frame(&ourtime, 
						       &hassync,
						       &frame_buffer, 
						       &frame_len,
						       &ud);
		  decode_skipped_frames++;
		} while (ret != 0 &&
			 !m_byte_stream->eof() && 
			 current_time > ourtime.msec_timestamp);
		if (m_byte_stream->eof() || ret == 0) continue;
		media_message(LOG_INFO, "Skipped ahead "U64 " to "U64, 
			      current_time - 200, ourtime.msec_timestamp);
		/*
		 * Ooh - fun - try to match to the next sync value - if not, 
		 * 15 frames
		 */
		do {
		  if (ud != NULL) free(ud);
		  ret = m_byte_stream->skip_next_frame(&ourtime, 
						       &hassync,
						       &frame_buffer, 
						       &frame_len,
						       &ud);
		  if (hassync < 0) {
		    uint64_t diff = ourtime.msec_timestamp - current_time;
		    if (diff > TO_U64(200)) {
		      hassync = 1;
		    }
		  }
		  decode_skipped_frames++;
		  count++;
		} while (ret != 0 &&
			 hassync <= 0 &&
			 count < 30 &&
			 !m_byte_stream->eof());
		if (m_byte_stream->eof() || ret == 0) continue;
#ifdef DEBUG_DECODE
		media_message(LOG_INFO, 
			      "Matched ahead - count %d, sync %d time "U64,
			      count, hassync, ourtime.msec_timestamp);
#endif
	      }
	    }
	  } // end regular time
	}
      }
      lasttime = ourtime;
      have_frame_ts = true;
#ifdef DEBUG_DECODE
      media_message(LOG_DEBUG, "Decoding %s frame " U64, get_name(),
		    ourtime.msec_timestamp);
#endif
      if (frame_buffer != NULL && frame_len != 0) {
	int sync_frame;
	ret = m_plugin->c_decode_frame(m_plugin_data,
				       &ourtime,
				       m_streaming,
				       &sync_frame,
				       frame_buffer, 
				       frame_len,
				       ud);
#ifdef DEBUG_DECODE
	media_message(LOG_DEBUG, "Decoding %s frame return %d", 
		      get_name(), ret);
#endif
	if (ret > 0) {
	  frames_decoded++;
	  if (have_start_time == false) {
	    have_start_time = true;
	    start_decode_time = ourtime.msec_timestamp;
	  }
	  last_decode_time = ourtime.msec_timestamp;
	  m_byte_stream->used_bytes_for_frame(ret);
	  bytes_decoded += ret;
	} else {
	  m_byte_stream->used_bytes_for_frame(frame_len);
	}

      }
    }
    // calculate frame rate for session
  }
  if (is_audio() == false)
    media_message(LOG_NOTICE, "Video decoder skipped %u frames", 
		  decode_skipped_frames);
  if (last_decode_time > start_decode_time) {
    double fps, bps;
    double secs;
    uint64_t total_time = last_decode_time - start_decode_time;
    secs = UINT64_TO_DOUBLE(total_time);
    secs /= 1000.0;
#if 0
    media_message(LOG_DEBUG, "last time "U64" first "U64, 
		  last_decode_time, start_decode_time);
#endif
    fps = frames_decoded;
    fps /= secs;
    bps = UINT64_TO_DOUBLE(bytes_decoded);
    bps *= 8.0 / secs;
    media_message(LOG_NOTICE, "%s - bytes "U64", seconds %g, fps %g bps %g",
		  get_name(),
		  bytes_decoded, secs, 
		  fps, bps);
  }
  if (m_plugin) {
    m_plugin->c_close(m_plugin_data);
    m_plugin_data = NULL;
  }
  return (0);
}