Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/avi_stream.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/avi_stream.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/avi_stream.h	(revision 3402)
@@ -50,6 +50,13 @@
 };
 
+/**
+ * AVI stream data type.
+ */
 typedef pjmedia_port pjmedia_avi_stream;
 
+/**
+ * Opaque data type for AVI streams. AVI streams is a collection of
+ * zero or more AVI stream.
+ */
 typedef struct pjmedia_avi_streams pjmedia_avi_streams;
 
@@ -72,16 +79,47 @@
                                   pjmedia_avi_streams **p_streams);
 
-PJ_DECL(pj_uint8_t)
+/**
+ * Get the number of AVI stream.
+ *
+ * @param streams	The AVI streams.
+ *
+ * @return		The number of AVI stream.
+ */
+PJ_DECL(unsigned)
 pjmedia_avi_streams_get_num_streams(pjmedia_avi_streams *streams);
 
+/**
+ * Return the idx-th stream of the AVI streams.
+ *
+ * @param streams	The AVI streams.
+ * @param idx	        The stream index.
+ *
+ * @return		The AVI stream or NULL if it does not exist.
+ */
 PJ_DECL(pjmedia_avi_stream *)
 pjmedia_avi_streams_get_stream(pjmedia_avi_streams *streams,
-                               pj_uint8_t idx);
+                               unsigned idx);
 
+/**
+ * Return an AVI stream with a certain media type from the AVI streams.
+ *
+ * @param streams	The AVI streams.
+ * @param start_idx     The starting index.
+ * @param media_type    The media type of the stream.
+ *
+ * @return		The AVI stream or NULL if it does not exist.
+ */
 PJ_DECL(pjmedia_avi_stream *)
 pjmedia_avi_streams_get_stream_by_media(pjmedia_avi_streams *streams,
-                                        pj_uint8_t start_idx,
+                                        unsigned start_idx,
                                         pjmedia_type media_type);
 
+/**
+ * Return the media port of an AVI stream.
+ *
+ * @param stream	The AVI stream.
+ *
+ * @return		The media port.
+ */
 PJ_INLINE(pjmedia_port *)
 pjmedia_avi_stream_get_port(pjmedia_avi_stream *stream)
@@ -93,5 +131,5 @@
  * Get the data length, in bytes.
  *
- * @param port		The AVI stream.
+ * @param stream        The AVI stream.
  *
  * @return		The length of the data, in bytes. Upon error it will
@@ -107,5 +145,5 @@
  * registered for each AVI stream.
  *
- * @param port		The AVI stream.
+ * @param stream	The AVI stream.
  * @param user_data	User data to be specified in the callback
  * @param cb		Callback to be called. If the callback returns non-
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/clock.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/clock.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/clock.h	(revision 3402)
@@ -79,4 +79,80 @@
 
 PJ_BEGIN_DECL
+
+/**
+ * Media clock source.
+ */
+typedef struct pjmedia_clock_src
+{
+    pjmedia_type    media_type;     /**< Media type.                */
+    unsigned        clock_rate;     /**< Clock rate.                */
+    unsigned        ptime_usec;     /**< Frame interval (in usec).  */
+    /**
+     * The timestamp field holds an increasing value in samples and its
+     * value is expected to be increased by clock_rate samples per second.
+     */
+    pj_timestamp    timestamp;
+    /**
+     * Timestamp's last update. The last_update field contains a value in
+     * ticks, and it is expected to be increased by pj_get_timestamp_freq()
+     * ticks per second.
+     */
+    pj_timestamp    last_update;
+} pjmedia_clock_src;
+
+/**
+ * This is an auxiliary function to initialize the media clock source.
+ *
+ * @param clocksrc          The clock source to be initialized.
+ * @param media_type        The media type.
+ * @param clock_rate	    The clock rate.
+ * @param ptime_usec        Media frame interval (in usec).
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_clock_src_init( pjmedia_clock_src *clocksrc,
+                                             pjmedia_type media_type,
+                                             unsigned clock_rate,
+                                             unsigned ptime_usec );
+
+/**
+ * This function updates the clock source's timestamp. Application should
+ * use this function instead of updating the timestamp directly since this
+ * function will also update the last_update field of the clock source.
+ *
+ * @param clocksrc          The clock source to be updated.
+ * @param timestamp         The new timestamp, can be NULL if the current
+ *                          timestamp does not change (in this case it
+ *                          will only update the last_update field).
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) pjmedia_clock_src_update( pjmedia_clock_src *clocksrc,
+                                               const pj_timestamp *timestamp );
+
+/**
+ * This function gets the clock source's current timestamp. Application
+ * should use this function instead of accessing the timestamp directly
+ * since this function will calculate the predicted timestamp for current
+ * time, based on the values of timestamp, last_update, and clock_rate.
+ *
+ * @param clocksrc          The clock source.
+ * @param timestamp         Argument to receive the current timestamp
+ *
+ * @return		    PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_clock_src_get_current_timestamp( const pjmedia_clock_src *clocksrc,
+                                         pj_timestamp *timestamp);
+
+/**
+ * This function gets the clock source's time in msec.
+ *
+ * @param clocksrc          The clock source.
+ *
+ * @return		    The clock source's time (in msec).
+ */
+PJ_DECL(pj_uint32_t)
+pjmedia_clock_src_get_time_msec( const pjmedia_clock_src *clocksrc );
 
 
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/config.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/config.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/config.h	(revision 3402)
@@ -967,4 +967,30 @@
 #endif
 
+/**
+ * Specify the maximum time difference (in ms) for synchronization between
+ * two medias. If the synchronization media source is ahead of time
+ * greater than this duration, it is considered to make a very large jump
+ * and the synchronization will be reset.
+ *
+ * Default: 20000
+ */
+#ifndef PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC
+#   define PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC         20000
+#endif
+
+/**
+ * Specify the maximum duration (in ms) for resynchronization. When a media
+ * is late to another media it is supposed to be synchronized to, it is
+ * guaranteed to be synchronized again after this duration. While if the
+ * media is ahead/early by t ms, it is guaranteed to be synchronized after
+ * t + this duration. This timing only applies if there is no additional
+ * resynchronization required during the specified duration.
+ *
+ * Default: 2000
+ */
+#ifndef PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION
+#   define PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION 2000
+#endif
+
 
 /**
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/port.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/port.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/port.h	(revision 3402)
@@ -25,4 +25,5 @@
  * @brief Port interface declaration
  */
+#include <pjmedia/clock.h>
 #include <pjmedia/format.h>
 #include <pjmedia/frame.h>
@@ -373,4 +374,11 @@
 
     /**
+     * Get clock source.
+     * This should only be called by #pjmedia_port_get_clock_src().
+     */
+    pjmedia_clock_src* (*get_clock_src)(struct pjmedia_port *this_port,
+                                        pjmedia_dir dir);
+
+    /**
      * Sink interface. 
      * This should only be called by #pjmedia_port_put_frame().
@@ -399,5 +407,4 @@
  *
  * @param info		    The port info to be initialized.
- * @param pool		    Pool to allocate memory from.
  * @param name		    Port name.
  * @param signature	    Port signature.
@@ -437,4 +444,17 @@
 
 /**
+ * Get a clock source from the port.
+ *
+ * @param port	    The media port.
+ * @param dir       Media port's direction.
+ *
+ * @return	    The clock source or NULL if clock source is not present
+ *                  in the port.
+ */
+PJ_DECL(pjmedia_clock_src *) pjmedia_port_get_clock_src( pjmedia_port *port,
+                                                         pjmedia_dir dir );
+
+
+/**
  * Get a frame from the port (and subsequent downstream ports).
  *
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/sound_port.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/sound_port.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/sound_port.h	(revision 3402)
@@ -26,4 +26,5 @@
  */
 #include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia/clock.h>
 #include <pjmedia/port.h>
 
@@ -243,4 +244,17 @@
 
 /**
+ * Get a clock source from the sound port.
+ *
+ * @param snd_port  The sound port.
+ * @param dir       Sound port's direction.
+ *
+ * @return	    The clock source.
+ */
+PJ_DECL(pjmedia_clock_src *)
+pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port,
+                                pjmedia_dir dir );
+
+
+/**
  * Connect a port to the sound device port. If the sound device port has a
  * sound recorder device, then this will start periodic function call to
Index: /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/videoport.h
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/videoport.h	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/include/pjmedia/videoport.h	(revision 3402)
@@ -126,4 +126,30 @@
 
 /**
+ * Get a clock source from the video port.
+ *
+ * @param vid_port  The video port.
+ * @param dir       Video port's direction.
+ *
+ * @return	    The clock source.
+ */
+PJ_DECL(pjmedia_clock_src *)
+pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port,
+                                pjmedia_dir dir );
+
+/**
+ * Set a clock source for the video port.
+ *
+ * @param vid_port  The video port.
+ * @param dir       Video port's direction.
+ * @param clocksrc  The clock source.
+ *
+ * @return	    PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port,
+                                pjmedia_dir dir,
+                                pjmedia_clock_src *clocksrc );
+
+/**
  * Connect the video port to a downstream (slave) media port. This operation
  * is only valid for video ports created with active interface selected.
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia-videodev/dshow_dev.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia-videodev/dshow_dev.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia-videodev/dshow_dev.c	(revision 3402)
@@ -41,4 +41,5 @@
 
 #define THIS_FILE		"dshow_dev.c"
+#define DEFAULT_CLOCK_RATE	90000
 #define DEFAULT_WIDTH		640
 #define DEFAULT_HEIGHT		480
@@ -394,6 +395,6 @@
 
     /* Set the device capabilities here */
-    param->clock_rate = 9000;
-    param->frame_rate.num = 14;
+    param->clock_rate = DEFAULT_CLOCK_RATE;
+    param->frame_rate.num = DEFAULT_FPS;
     param->frame_rate.denum = 1;
     param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/avi_player.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/avi_player.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/avi_player.c	(revision 3402)
@@ -87,5 +87,5 @@
 struct pjmedia_avi_streams
 {
-    pj_uint8_t      num_streams;
+    unsigned        num_streams;
     pjmedia_port  **streams;
 };
@@ -94,5 +94,5 @@
 {
     pjmedia_port     base;
-    pj_uint8_t       stream_id;
+    unsigned         stream_id;
     unsigned	     options;
     pj_uint16_t	     bits_per_sample;
@@ -172,5 +172,5 @@
     struct avi_reader_port *fport[PJMEDIA_AVI_MAX_NUM_STREAMS];
     pj_off_t pos;
-    pj_uint8_t i, nstr = 0;
+    unsigned i, nstr = 0;
     pj_status_t status = PJ_SUCCESS;
 
@@ -468,5 +468,5 @@
 }
 
-PJ_DEF(pj_uint8_t)
+PJ_DEF(unsigned)
 pjmedia_avi_streams_get_num_streams(pjmedia_avi_streams *streams)
 {
@@ -477,5 +477,5 @@
 PJ_DEF(pjmedia_avi_stream *)
 pjmedia_avi_streams_get_stream(pjmedia_avi_streams *streams,
-                               pj_uint8_t idx)
+                               unsigned idx)
 {
     pj_assert(streams);
@@ -486,8 +486,8 @@
 PJ_DEF(pjmedia_avi_stream *)
 pjmedia_avi_streams_get_stream_by_media(pjmedia_avi_streams *streams,
-                                        pj_uint8_t start_idx,
+                                        unsigned start_idx,
                                         pjmedia_type media_type)
 {
-    pj_uint8_t i;
+    unsigned i;
 
     pj_assert(streams);
@@ -594,5 +594,5 @@
         pjmedia_avi_subchunk ch = {0, 0};
         char *cid;
-        pj_uint8_t stream_id;
+        unsigned stream_id;
 
         /* We need to read data from the file past the chunk boundary */
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/clock_thread.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/clock_thread.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/clock_thread.c	(revision 3402)
@@ -24,4 +24,82 @@
 #include <pj/os.h>
 #include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/compat/high_precision.h>
+
+/* API: Init clock source */
+PJ_DEF(pj_status_t) pjmedia_clock_src_init( pjmedia_clock_src *clocksrc,
+                                            pjmedia_type media_type,
+                                            unsigned clock_rate,
+                                            unsigned ptime_usec )
+{
+    PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL);
+
+    clocksrc->media_type = media_type;
+    clocksrc->clock_rate = clock_rate;
+    clocksrc->ptime_usec = ptime_usec;
+    pj_set_timestamp32(&clocksrc->timestamp, 0, 0);
+    pj_get_timestamp(&clocksrc->last_update);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Update clock source */
+PJ_DECL(pj_status_t) pjmedia_clock_src_update( pjmedia_clock_src *clocksrc,
+                                               const pj_timestamp *timestamp )
+{
+    PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL);
+
+    if (timestamp)
+        pj_memcpy(&clocksrc->timestamp, timestamp, sizeof(pj_timestamp));
+    pj_get_timestamp(&clocksrc->last_update);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get clock source's current timestamp */
+PJ_DEF(pj_status_t)
+pjmedia_clock_src_get_current_timestamp( const pjmedia_clock_src *clocksrc,
+                                         pj_timestamp *timestamp)
+{
+    pj_timestamp now;
+    unsigned elapsed_ms;
+    
+    PJ_ASSERT_RETURN(clocksrc && timestamp, PJ_EINVAL);
+
+    pj_get_timestamp(&now);
+    elapsed_ms = pj_elapsed_msec(&clocksrc->last_update, &now);
+    pj_memcpy(timestamp, &clocksrc->timestamp, sizeof(pj_timestamp));
+    pj_add_timestamp32(timestamp, elapsed_ms * clocksrc->clock_rate / 1000);
+
+    return PJ_SUCCESS;
+}
+
+/* API: Get clock source's time (in ms) */
+PJ_DEF(pj_uint32_t)
+pjmedia_clock_src_get_time_msec( const pjmedia_clock_src *clocksrc )
+{
+    pj_timestamp ts;
+
+    pjmedia_clock_src_get_current_timestamp(clocksrc, &ts);
+
+#if PJ_HAS_INT64
+    if (ts.u64 > 0x3FFFFFFFFFFFFFUL)
+        return (pj_uint32_t)(ts.u64 / clocksrc->clock_rate * 1000);
+    else
+        return (pj_uint32_t)(ts.u64 * 1000 / clocksrc->clock_rate);
+#elif PJ_HAS_FLOATING_POINT
+    return (pj_uint32_t)((1.0 * ts.u32.hi * 0xFFFFFFFFUL + ts.u32.lo)
+                         * 1000.0 / clocksrc->clock_rate);
+#else
+    if (ts.u32.lo > 0x3FFFFFUL)
+        return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi 
+                             * 1000UL + ts.u32.lo / clocksrc->clock_rate *
+                             1000UL);
+    else
+        return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi 
+                             * 1000UL + ts.u32.lo * 1000UL /
+                             clocksrc->clock_rate);
+#endif
+}
 
 
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/port.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/port.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/port.c	(revision 3402)
@@ -76,4 +76,16 @@
 
 /**
+ * Get a clock source from the port.
+ */
+PJ_DEF(pjmedia_clock_src *) pjmedia_port_get_clock_src( pjmedia_port *port,
+                                                        pjmedia_dir dir )
+{
+    if (port && port->get_clock_src)
+	return port->get_clock_src(port, dir);
+    else
+	return NULL;
+}
+
+/**
  * Get a frame from the port (and subsequent downstream ports).
  */
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/sound_port.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/sound_port.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/sound_port.c	(revision 3402)
@@ -45,4 +45,7 @@
     pjmedia_port	*port;
 
+    pjmedia_clock_src    cap_clocksrc,
+                         play_clocksrc;
+
     unsigned		 clock_rate;
     unsigned		 channel_count;
@@ -70,4 +73,6 @@
     pj_status_t status;
 
+    pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp);
+
     port = snd_port->port;
     if (port == NULL)
@@ -127,4 +132,6 @@
     pjmedia_port *port;
 
+    pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp);
+
     port = snd_port->port;
     if (port == NULL)
@@ -137,4 +144,5 @@
 
     pjmedia_port_put_frame(port, frame);
+
 
     return PJ_SUCCESS;
@@ -418,4 +426,5 @@
     pjmedia_snd_port *snd_port;
     pj_status_t status;
+    unsigned ptime_usec;
 
     PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL);
@@ -433,4 +442,11 @@
     snd_port->bits_per_sample = prm->bits_per_sample;
     pj_memcpy(&snd_port->aud_param, prm, sizeof(*prm));
+
+    ptime_usec = prm->samples_per_frame * 1000 / prm->channel_count /
+                 prm->clock_rate * 1000;
+    pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO,
+                           snd_port->clock_rate, ptime_usec);
+    pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO,
+                           snd_port->clock_rate, ptime_usec);
     
     /* Start sound device immediately.
@@ -633,4 +649,16 @@
 
 /*
+ * Get clock source.
+ */
+PJ_DEF(pjmedia_clock_src *)
+pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port,
+                                pjmedia_dir dir )
+{
+    return (dir == PJMEDIA_DIR_CAPTURE? &snd_port->cap_clocksrc:
+            &snd_port->play_clocksrc);
+}
+
+
+/*
  * Connect a port.
  */
Index: /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/videoport.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/videoport.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjmedia/src/pjmedia/videoport.c	(revision 3402)
@@ -50,4 +50,16 @@
 		        *dec_clock;
 
+    pjmedia_clock_src    cap_clocksrc,
+                         rend_clocksrc;
+
+    struct sync_clock_src_t
+    {
+        pjmedia_clock_src   *sync_clocksrc;
+        pj_int32_t           sync_delta;
+        unsigned             max_sync_ticks;
+        unsigned             nsync_frame;
+        unsigned             nsync_progress;
+    } cap_sync_clocksrc, rend_sync_clocksrc;
+
     pjmedia_frame	*enc_frm_buf,
 		        *dec_frm_buf;
@@ -99,4 +111,5 @@
     pj_bool_t need_frame_buf = PJ_FALSE;
     pj_status_t status;
+    unsigned ptime_usec;
 
     PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL);
@@ -140,4 +153,16 @@
     vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
 
+    ptime_usec = PJMEDIA_PTIME(&vfd->fps);
+    pjmedia_clock_src_init(&vp->cap_clocksrc, PJMEDIA_TYPE_VIDEO,
+                           prm->vidparam.clock_rate, ptime_usec);
+    pjmedia_clock_src_init(&vp->rend_clocksrc, PJMEDIA_TYPE_VIDEO,
+                           prm->vidparam.clock_rate, ptime_usec);
+    vp->cap_sync_clocksrc.max_sync_ticks = 
+        PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
+        1000 / vp->cap_clocksrc.ptime_usec;
+    vp->rend_sync_clocksrc.max_sync_ticks = 
+        PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
+        1000 / vp->rend_clocksrc.ptime_usec;
+
     /* Create the video stream */
     pj_bzero(&vid_cb, sizeof(vid_cb));
@@ -269,4 +294,35 @@
 
 
+
+PJ_DEF(pjmedia_clock_src *)
+pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port,
+                                pjmedia_dir dir )
+{
+    return (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_clocksrc:
+            &vid_port->rend_clocksrc);
+}
+
+PJ_DECL(pj_status_t)
+pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port,
+                                pjmedia_dir dir,
+                                pjmedia_clock_src *clocksrc)
+{
+    pjmedia_clock_src *vclocksrc;
+    struct sync_clock_src_t *sync_src;
+
+    PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL);
+
+    vclocksrc = (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_clocksrc:
+                 &vid_port->rend_clocksrc);
+    sync_src = (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_sync_clocksrc:
+                &vid_port->rend_sync_clocksrc);
+    sync_src->sync_clocksrc = clocksrc;
+    sync_src->sync_delta = pjmedia_clock_src_get_time_msec(vclocksrc) -
+                           pjmedia_clock_src_get_time_msec(clocksrc);
+
+    return PJ_SUCCESS;
+}
+
+
 PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp,
 					      pjmedia_port *port,
@@ -438,4 +494,6 @@
     pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
     pj_status_t status;
+    unsigned frame_ts = vp->rend_clocksrc.clock_rate / 1000 *
+                        vp->rend_clocksrc.ptime_usec / 1000;
 
     pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
@@ -446,7 +504,102 @@
 	return;
 
+    if (vp->rend_sync_clocksrc.sync_clocksrc) {
+        pjmedia_clock_src *src = vp->rend_sync_clocksrc.sync_clocksrc;
+        pj_int32_t diff;
+        unsigned nsync_frame;
+
+        /* Synchronization */
+        /* Calculate the time difference (in ms) with the sync source */
+        diff = pjmedia_clock_src_get_time_msec(&vp->rend_clocksrc) -
+               pjmedia_clock_src_get_time_msec(src) -
+               vp->rend_sync_clocksrc.sync_delta;
+
+        /* Check whether sync source made a large jump */
+        if (diff < 0 && -diff > PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC) {
+            pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
+            vp->rend_sync_clocksrc.sync_delta = 
+                pjmedia_clock_src_get_time_msec(src) -
+                pjmedia_clock_src_get_time_msec(&vp->rend_clocksrc);
+            vp->rend_sync_clocksrc.nsync_frame = 0;
+            return;
+        }
+
+        /* Calculate the difference (in frames) with the sync source */
+        nsync_frame = abs(diff) * 1000 / vp->rend_clocksrc.ptime_usec;
+        if (nsync_frame == 0) {
+            /* Nothing to sync */
+            vp->rend_sync_clocksrc.nsync_frame = 0;
+        } else {
+            pj_int32_t init_sync_frame = nsync_frame;
+
+            /* Check whether it's a new sync or whether we need to reset
+             * the sync
+             */
+            if (vp->rend_sync_clocksrc.nsync_frame == 0 ||
+                (vp->rend_sync_clocksrc.nsync_frame > 0 &&
+                 nsync_frame > vp->rend_sync_clocksrc.nsync_frame))
+            {
+                vp->rend_sync_clocksrc.nsync_frame = nsync_frame;
+                vp->rend_sync_clocksrc.nsync_progress = 0;
+            } else {
+                init_sync_frame = vp->rend_sync_clocksrc.nsync_frame;
+            }
+
+            if (diff >= 0) {
+                unsigned skip_mod;
+
+                /* We are too fast */
+                if (vp->rend_sync_clocksrc.max_sync_ticks > 0) {
+                    skip_mod = init_sync_frame / 
+                               vp->rend_sync_clocksrc.max_sync_ticks + 2;
+                } else
+                    skip_mod = init_sync_frame + 2;
+
+                PJ_LOG(5, (THIS_FILE, "synchronization: early by %d ms",
+                           diff));
+                /* We'll play a frame every skip_mod-th tick instead of
+                 * a complete pause
+                 */
+                if (++vp->rend_sync_clocksrc.nsync_progress % skip_mod > 0) {
+                    pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
+                    return;
+                }
+            } else {
+                unsigned i, ndrop = init_sync_frame;
+
+                /* We are too late, drop the frame */
+                if (vp->rend_sync_clocksrc.max_sync_ticks > 0) {
+                    ndrop /= vp->rend_sync_clocksrc.max_sync_ticks;
+                    ndrop++;
+                }
+                PJ_LOG(5, (THIS_FILE, "synchronization: late, "
+                                      "dropping %d frame(s)", ndrop));
+
+                if (ndrop >= nsync_frame) {
+                    vp->rend_sync_clocksrc.nsync_frame = 0;
+                    ndrop = nsync_frame;
+                } else
+                    vp->rend_sync_clocksrc.nsync_progress += ndrop;
+                for (i = 0; i < ndrop; i++) {
+                    status = pjmedia_port_get_frame(vp->client_port,
+                                                    vp->dec_frm_buf);
+                    if (status != PJ_SUCCESS) {
+                        pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
+                        return;
+                    }
+                    pj_add_timestamp32(&vp->rend_clocksrc.timestamp,
+                                       frame_ts);
+                }
+            }
+        }
+    }
+
     status = pjmedia_port_get_frame(vp->client_port, vp->dec_frm_buf);
-    if (status != PJ_SUCCESS)
+    if (status != PJ_SUCCESS) {
+        pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
 	return;
+    }
+    pj_add_timestamp32(&vp->rend_clocksrc.timestamp, frame_ts);
+    pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
 
     status = pjmedia_vid_stream_put_frame(vp->strm, vp->dec_frm_buf);
Index: /pjproject/branches/projects/2.0-dev/pjsip-apps/src/samples/aviplay.c
===================================================================
--- /pjproject/branches/projects/2.0-dev/pjsip-apps/src/samples/aviplay.c	(revision 3401)
+++ /pjproject/branches/projects/2.0-dev/pjsip-apps/src/samples/aviplay.c	(revision 3402)
@@ -91,5 +91,5 @@
 {
     pjmedia_vid_port   *vid_port;
-    pjmedia_aud_stream *aud_stream;
+    pjmedia_snd_port   *snd_port;
     pj_bool_t           is_running;
     pj_bool_t           is_quitting;
@@ -105,9 +105,4 @@
     pjmedia_converter   *conv;
 } codec_port_data_t;
-
-static pj_status_t avi_play_cb(void *user_data, pjmedia_frame *frame)
-{
-    return pjmedia_port_get_frame((pjmedia_port*)user_data, frame);
-}
 
 static pj_status_t avi_event_cb(pjmedia_vid_stream *stream,
@@ -126,10 +121,12 @@
         if (ap->is_running) {
             pjmedia_vid_port_stop(ap->vid_port);
-            if (ap->aud_stream)
-                pjmedia_aud_stream_stop(ap->aud_stream);
+            if (ap->snd_port)
+                pjmedia_aud_stream_stop(
+                    pjmedia_snd_port_get_snd_stream(ap->snd_port));
         } else {
             pjmedia_vid_port_start(ap->vid_port);
-            if (ap->aud_stream)
-                pjmedia_aud_stream_start(ap->aud_stream);
+            if (ap->snd_port)
+                pjmedia_aud_stream_start(
+                    pjmedia_snd_port_get_snd_stream(ap->snd_port));
         }
         ap->is_running = !ap->is_running;
@@ -187,9 +184,7 @@
     pjmedia_vid_port *renderer=NULL;
     pjmedia_vid_port_param param;
-    pjmedia_aud_param aparam;
     const pjmedia_video_format_info *vfi;
     pjmedia_video_format_detail *vfd;
-    pjmedia_audio_format_detail *afd;
-    pjmedia_aud_stream *strm = NULL;
+    pjmedia_snd_port *snd_port = NULL;
     pj_status_t status;
     int rc = 0;
@@ -376,29 +371,23 @@
     
     if (aud_port) {
-        status = pjmedia_aud_dev_default_param(
-                     PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV,
-		     &aparam);
+        /* Create sound player port. */
+        status = pjmedia_snd_port_create_player( 
+		 pool,				    /* pool		    */
+		 -1,				    /* use default dev.	    */
+		 PJMEDIA_PIA_SRATE(&aud_port->info),/* clock rate.	    */
+		 PJMEDIA_PIA_CCNT(&aud_port->info), /* # of channels.	    */
+		 PJMEDIA_PIA_SPF(&aud_port->info),  /* samples per frame.   */
+		 PJMEDIA_PIA_BITS(&aud_port->info), /* bits per sample.	    */
+		 0,				    /* options		    */
+		 &snd_port			    /* returned port	    */
+		 );
         if (status != PJ_SUCCESS) {
             rc = 310; goto on_return;
         }
 	
-        aparam.dir = PJMEDIA_DIR_PLAYBACK;
-        afd = pjmedia_format_get_audio_format_detail(&aud_port->info.fmt,
-                                                     PJ_TRUE);
-        aparam.clock_rate = afd->clock_rate;
-        aparam.channel_count = afd->channel_count;
-        aparam.bits_per_sample = afd->bits_per_sample;
-        aparam.samples_per_frame = afd->frame_time_usec * aparam.clock_rate *
-	                           aparam.channel_count / 1000000;
-	
-        status = pjmedia_aud_stream_create(&aparam, NULL, &avi_play_cb,
-                                           aud_port,
-                                           &strm);
-        if (status != PJ_SUCCESS) {
-            rc = 320; goto on_return;
-        }
-	
-        /* Start audio streaming.. */
-        status = pjmedia_aud_stream_start(strm);
+        /* Connect file port to the sound player.
+         * Stream playing will commence immediately.
+         */
+        status = pjmedia_snd_port_connect(snd_port, aud_port);
         if (status != PJ_SUCCESS) {
             rc = 330; goto on_return;
@@ -411,8 +400,17 @@
         pj_bzero(&cb, sizeof(cb));
         cb.on_event_cb = avi_event_cb;
-        avi_port.aud_stream = strm;
+        avi_port.snd_port = snd_port;
         avi_port.vid_port = renderer;
         avi_port.is_running = PJ_TRUE;
         pjmedia_vid_port_set_cb(renderer, &cb, &avi_port);
+
+        if (snd_port) {
+            /* Synchronize video rendering and audio playback */
+            pjmedia_vid_port_set_clock_src(
+                renderer, PJMEDIA_DIR_RENDER,
+                pjmedia_snd_port_get_clock_src(
+                    snd_port, PJMEDIA_DIR_PLAYBACK));
+        }
+                                              
 	
         /* Start video streaming.. */
@@ -428,10 +426,16 @@
 
 on_return:
-    if (strm) {
-	pjmedia_aud_stream_stop(strm);
-	pjmedia_aud_stream_destroy(strm);
+    if (snd_port) {
+        pjmedia_snd_port_disconnect(snd_port);
+        /* Without this sleep, Windows/DirectSound will repeteadly
+         * play the last frame during destroy.
+         */
+        pj_thread_sleep(100);
+        pjmedia_snd_port_destroy(snd_port);
     }
     if (renderer)
         pjmedia_vid_port_destroy(renderer);
+    if (aud_port)
+        pjmedia_port_destroy(aud_port);
     if (vid_port)
         pjmedia_port_destroy(vid_port);
