예제 #1
0
파일: jbuf.c 프로젝트: xhook/asterisk-v11
PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool, 
					const pj_str_t *name,
					unsigned frame_size, 
					unsigned ptime,
					unsigned max_count,
					pjmedia_jbuf **p_jb)
{
    pjmedia_jbuf *jb;
    pj_status_t status;

    jb = PJ_POOL_ZALLOC_T(pool, pjmedia_jbuf);

    status = jb_framelist_init(pool, &jb->jb_framelist, frame_size, max_count);
    if (status != PJ_SUCCESS)
	return status;

    pj_strdup_with_null(pool, &jb->jb_name, name);
    jb->jb_frame_size	 = frame_size;
    jb->jb_frame_ptime   = ptime;
    jb->jb_prefetch	 = PJ_MIN(PJMEDIA_JB_DEFAULT_INIT_DELAY,max_count*4/5);
    jb->jb_min_prefetch  = 0;
    jb->jb_max_prefetch  = max_count*4/5;
    jb->jb_max_count	 = max_count;
    jb->jb_min_shrink_gap= PJMEDIA_JBUF_DISC_MIN_GAP / ptime;
    jb->jb_max_burst	 = PJ_MAX(MAX_BURST_MSEC / ptime, max_count*3/4);

    pj_math_stat_init(&jb->jb_delay);
    pj_math_stat_init(&jb->jb_burst);

    pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_PROGRESSIVE);
    pjmedia_jbuf_reset(jb);

    *p_jb = jb;
    return PJ_SUCCESS;
}
예제 #2
0
static unsigned max_common_substr_len(const pj_str_t* str1, const pj_str_t* str2)
{
    unsigned max_len = 0;
    /* We compare only on first MAX_COMPARE_LEN char */
    unsigned tree[MAX_COMPARE_LEN][MAX_COMPARE_LEN];
    unsigned m1=0, m2=0;
    int i=0, j=0;

    if(str1->slen == 0 || str2->slen == 0)
    {
        return 0;
    }

    /* Init tree */
    for(i=0;i < MAX_COMPARE_LEN;i++) {
        pj_bzero(tree[i], PJ_ARRAY_SIZE( tree[i] ));
    }

    m1 = PJ_MIN(str1->slen, MAX_COMPARE_LEN);
    m2 = PJ_MIN(str2->slen, MAX_COMPARE_LEN);

    for (i = 0; i < m1; i++) {
        for (j = 0; j < m2; j++) {
            if (str1->ptr[i] != str2->ptr[j])
            {
                tree[i][j] = 0;
            }
            else
            {
                if ((i == 0) || (j == 0))
                {
                    tree[i][j] = 1;
                }
                else
                {
                    tree[i][j] = 1 + tree[i - 1][j - 1];
                }

                if (tree[i][j] > max_len)
                {
                    max_len = tree[i][j];
                }
            }
        }
    }
    return max_len;
}
예제 #3
0
static pj_status_t alt_vid_codec_enum_codecs( pjmedia_vid_codec_factory *factory,
					      unsigned *count,
					      pjmedia_vid_codec_info codecs[])
{
    unsigned i, max_cnt;

    PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);

    max_cnt = PJ_MIN(*count, PJ_ARRAY_SIZE(alt_vid_codecs));
    *count = 0;

    for (i=0; i<max_cnt; ++i) {
	pj_memcpy(&codecs[*count], &alt_vid_codecs[i].info,
		  sizeof(pjmedia_vid_codec_info));
	(*count)++;
    }

    return PJ_SUCCESS;
}
/*
 * Get cipher list supported by SSL/TLS backend.
 */
PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables (pj_ssl_cipher ciphers[],
					          unsigned *cipher_num)
{
    unsigned i;

    PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL);
    
    if (ciphers_num_ == 0) {
        RSocket sock;
        CSecureSocket *secure_sock;
        TPtrC16 proto(_L16("TLS1.0"));

        secure_sock = CSecureSocket::NewL(sock, proto);
        if (secure_sock) {
            TBuf8<128> ciphers_buf(0);
            secure_sock->AvailableCipherSuites(ciphers_buf);
            
            ciphers_num_ = ciphers_buf.Length() / 2;
            if (ciphers_num_ > PJ_ARRAY_SIZE(ciphers_))
        	ciphers_num_ = PJ_ARRAY_SIZE(ciphers_);
            for (i = 0; i < ciphers_num_; ++i) {
                ciphers_[i].id = (pj_ssl_cipher)(ciphers_buf[i*2]*10 + 
					         ciphers_buf[i*2+1]);
		ciphers_[i].name = get_cipher_name(ciphers_[i].id);
	    }
        }
        
        delete secure_sock;
    }
    
    if (ciphers_num_ == 0) {
	*cipher_num = 0;
	return PJ_ENOTFOUND;
    }
    
    *cipher_num = PJ_MIN(*cipher_num, ciphers_num_);
    for (i = 0; i < *cipher_num; ++i)
        ciphers[i] = ciphers_[i].id;
    
    return PJ_SUCCESS;
}
예제 #5
0
파일: jbuf.c 프로젝트: xhook/asterisk-v11
PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper)
{
    if(jb->jb_last_op != oper) {
	jb->jb_last_op = oper;

	if (jb->jb_status == JB_STATUS_INITIALIZING) {
	    /* Switch status 'initializing' -> 'processing' after some OP 
	     * switch cycles and current OP is GET (burst level is calculated 
	     * based on PUT burst), so burst calculation is guaranted to be
	     * performed right after the status switching.
	     */
	    if (++jb->jb_init_cycle_cnt >= INIT_CYCLE && oper == JB_OP_GET) {
		jb->jb_status = JB_STATUS_PROCESSING;
		/* To make sure the burst calculation will be done right after
		 * this, adjust burst level if it exceeds max burst level.
		 */
		jb->jb_level = PJ_MIN(jb->jb_level, jb->jb_max_burst);
	    } else {
		jb->jb_level = 0;
		return;
	    }
	}

	/* Perform jitter calculation based on PUT burst-level only, since 
	 * GET burst-level may not be accurate, e.g: when VAD is active.
	 * Note that when burst-level is too big, i.e: exceeds jb_max_burst,
	 * the GET op may be idle, in this case, we better skip the jitter 
	 * calculation.
	 */
	if (oper == JB_OP_GET && jb->jb_level <= jb->jb_max_burst)
	    jbuf_calculate_jitter(jb);

	jb->jb_level = 0;
    }

    /* Call discard algorithm */
    if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) {
	(*jb->jb_discard_algo)(jb);
    }
}
예제 #6
0
/*
 * Enum codecs supported by this factory.
 */
static pj_status_t openh264_enum_codecs( pjmedia_vid_codec_factory *factory,
				       unsigned *count, 
				       pjmedia_vid_codec_info codecs[])
{
    unsigned i, max_cnt;

    PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
    PJ_ASSERT_RETURN(factory == &openh264_factory.base, PJ_EINVAL);

    max_cnt = PJ_MIN(*count, PJ_ARRAY_SIZE(codec_desc));
    *count = 0;

    for (i=0; i<max_cnt; ++i) {
		if (codec_desc[i].enabled) {
			pj_memcpy(&codecs[*count], &codec_desc[i].info, 
				  sizeof(pjmedia_vid_codec_info));
			(*count)++;
		}
    }

    return PJ_SUCCESS;
}
예제 #7
0
/*
 * Create the echo canceller. 
 */
PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool,
					 unsigned clock_rate,
					 unsigned channel_count,
					 unsigned samples_per_frame,
					 unsigned tail_ms,
					 unsigned latency_ms,
					 unsigned options,
					 pjmedia_echo_state **p_echo )
{
    unsigned ptime, lat_cnt;
    unsigned delay_buf_opt = 0;
    pjmedia_echo_state *ec;
    pj_status_t status;

    /* Create new pool and instantiate and init the EC */
    pool = pj_pool_create(pool->factory, "ec%p", 256, 256, NULL);
    ec = PJ_POOL_ZALLOC_T(pool, struct pjmedia_echo_state);
    ec->pool = pool;
    ec->obj_name = pool->obj_name;
    ec->samples_per_frame = samples_per_frame;
    ec->frm_buf = (pj_int16_t*)pj_pool_alloc(pool, samples_per_frame<<1);
    pj_list_init(&ec->lat_buf);
    pj_list_init(&ec->lat_free);

    /* Select the backend algorithm */
    if (0) {
	/* Dummy */
	;
#if defined(PJMEDIA_HAS_SPEEX_AEC) && PJMEDIA_HAS_SPEEX_AEC!=0
    } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_SPEEX ||
	       (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) 
    {
	ec->op = &speex_aec_op;
#endif

#if defined(PJMEDIA_HAS_INTEL_IPP_AEC) && PJMEDIA_HAS_INTEL_IPP_AEC!=0
    } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_IPP ||
	       (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT)
    {
	ec->op = &ipp_aec_op;

#endif

    } else {
	ec->op = &echo_supp_op;
    }

    /* Completeness check for EC operation playback and capture, they must
     * be implemented both or none.
     */
    pj_assert(!ec->op->ec_capture == !ec->op->ec_playback);

    PJ_LOG(5,(ec->obj_name, "Creating %s", ec->op->name));

    /* Instantiate EC object */
    status = (*ec->op->ec_create)(pool, clock_rate, channel_count, 
				  samples_per_frame, tail_ms, 
				  options, &ec->state);
    if (status != PJ_SUCCESS) {
	pj_pool_release(pool);
	return status;
    }

    /* If EC algo does not have playback and capture callbakcs,
     * create latency buffer and delay buffer to handle drift.
     */
    if (ec->op->ec_playback && ec->op->ec_capture) {
	latency_ms = 0;
    } else {
	/* Create latency buffers */
	ptime = samples_per_frame * 1000 / clock_rate;
	if (latency_ms > ptime) {
	    /* Normalize latency with delaybuf/WSOLA latency */
	    latency_ms -= PJ_MIN(ptime, PJMEDIA_WSOLA_DELAY_MSEC);
	}
	if (latency_ms < ptime) {
	    /* Give at least one frame delay to simplify programming */
	    latency_ms = ptime;
	}
	lat_cnt = latency_ms / ptime;
	while (lat_cnt--)  {
	    struct frame *frm;

	    frm = (struct frame*) pj_pool_alloc(pool, (samples_per_frame<<1) +
						      sizeof(struct frame));
	    pj_list_push_back(&ec->lat_free, frm);
	}

	/* Create delay buffer to compensate drifts */
	if (options & PJMEDIA_ECHO_USE_SIMPLE_FIFO)
	    delay_buf_opt |= PJMEDIA_DELAY_BUF_SIMPLE_FIFO;
	status = pjmedia_delay_buf_create(ec->pool, ec->obj_name, clock_rate, 
					  samples_per_frame, channel_count,
					  (PJMEDIA_SOUND_BUFFER_COUNT+1) * ptime,
					  delay_buf_opt, &ec->delay_buf);
	if (status != PJ_SUCCESS) {
	    pj_pool_release(pool);
	    return status;
	}
    }

    PJ_LOG(4,(ec->obj_name, 
	      "%s created, clock_rate=%d, channel=%d, "
	      "samples per frame=%d, tail length=%d ms, "
	      "latency=%d ms", 
	      ec->op->name, clock_rate, channel_count, samples_per_frame,
	      tail_ms, latency_ms));

    /* Done */
    *p_echo = ec;

    return PJ_SUCCESS;
}
예제 #8
0
파일: jbuf.c 프로젝트: xhook/asterisk-v11
PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, 
				     const void *frame, 
				     pj_size_t frame_size, 
				     pj_uint32_t bit_info,
				     int frame_seq,
				     pj_uint32_t ts,
				     pj_bool_t *discarded)
{
    pj_size_t min_frame_size;
    int new_size, cur_size;
    pj_status_t status;

    cur_size = jb_framelist_eff_size(&jb->jb_framelist);

    /* Attempt to store the frame */
    min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size);
    status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
				 min_frame_size, bit_info, ts,
				 PJMEDIA_JB_NORMAL_FRAME);
    
    /* Jitter buffer is full, remove some older frames */
    while (status == PJ_ETOOMANY) {
	int distance;
	unsigned removed;

	/* Remove as few as possible just to make this frame in. Note that
	 * the cases of seq-jump, out-of-order, and seq restart should have
	 * been handled/normalized by previous call of jb_framelist_put_at().
	 * So we're confident about 'distance' value here.
	 */
	distance = (frame_seq - jb_framelist_origin(&jb->jb_framelist)) -
		   jb->jb_max_count + 1;
	pj_assert(distance > 0);

	removed = jb_framelist_remove_head(&jb->jb_framelist, distance);
	status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame,
				     min_frame_size, bit_info, ts,
				     PJMEDIA_JB_NORMAL_FRAME);

	jb->jb_discard += removed;
    }

    /* Get new JB size after PUT */
    new_size = jb_framelist_eff_size(&jb->jb_framelist);

    /* Return the flag if this frame is discarded */
    if (discarded)
	*discarded = (status != PJ_SUCCESS);

    if (status == PJ_SUCCESS) {
	if (jb->jb_prefetching) {
	    TRACE__((jb->jb_name.ptr, "PUT prefetch_cnt=%d/%d", 
		     new_size, jb->jb_prefetch));
	    if (new_size >= jb->jb_prefetch)
		jb->jb_prefetching = PJ_FALSE;
	}
	jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1);
	jbuf_update(jb, JB_OP_PUT);
    } else
	jb->jb_discard++;
}
예제 #9
0
파일: jbuf.c 프로젝트: xhook/asterisk-v11
static void jbuf_calculate_jitter(pjmedia_jbuf *jb)
{
    int diff, cur_size;

    cur_size = jb_framelist_eff_size(&jb->jb_framelist);
    pj_math_stat_update(&jb->jb_burst, jb->jb_level);
    jb->jb_max_hist_level = PJ_MAX(jb->jb_max_hist_level, jb->jb_level);

    /* Burst level is decreasing */
    if (jb->jb_level < jb->jb_eff_level) {

	enum { STABLE_HISTORY_LIMIT = 20 };
        
	jb->jb_stable_hist++;
        
	/* Only update the effective level (and prefetch) if 'stable' 
	 * condition is reached (not just short time impulse)
	 */
	if (jb->jb_stable_hist > STABLE_HISTORY_LIMIT) {
    	
	    diff = (jb->jb_eff_level - jb->jb_max_hist_level) / 3;

	    if (diff < 1)
		diff = 1;

	    /* Update effective burst level */
	    jb->jb_eff_level -= diff;

	    /* Update prefetch based on level */
	    if (jb->jb_init_prefetch) {
		jb->jb_prefetch = jb->jb_eff_level;
		if (jb->jb_prefetch < jb->jb_min_prefetch) 
		    jb->jb_prefetch = jb->jb_min_prefetch;
	    }

	    /* Reset history */
	    jb->jb_max_hist_level = 0;
	    jb->jb_stable_hist = 0;

	    TRACE__((jb->jb_name.ptr,"jb updated(1), lvl=%d pre=%d, size=%d",
		     jb->jb_eff_level, jb->jb_prefetch, cur_size));
	}
    }

    /* Burst level is increasing */
    else if (jb->jb_level > jb->jb_eff_level) {

	/* Instaneous set effective burst level to recent maximum level */
	jb->jb_eff_level = PJ_MIN(jb->jb_max_hist_level,
				  (int)(jb->jb_max_count*4/5));

	/* Update prefetch based on level */
	if (jb->jb_init_prefetch) {
	    jb->jb_prefetch = jb->jb_eff_level;
	    if (jb->jb_prefetch > jb->jb_max_prefetch)
		jb->jb_prefetch = jb->jb_max_prefetch;
	}

	jb->jb_stable_hist = 0;
	/* Do not reset max_hist_level. */
	//jb->jb_max_hist_level = 0;

	TRACE__((jb->jb_name.ptr,"jb updated(2), lvl=%d pre=%d, size=%d", 
		 jb->jb_eff_level, jb->jb_prefetch, cur_size));
    }

    /* Level is unchanged */
    else {
	jb->jb_stable_hist = 0;
    }
}
예제 #10
0
static pj_status_t pj_vpx_codec_encode_more(pjmedia_vid_codec *codec,
        unsigned out_size, pjmedia_frame *output, pj_bool_t *has_more) {
    vpx_private *vpx = (vpx_private*) codec->codec_data;
    const vpx_codec_cx_pkt_t *pkt;

    /* Default return */
    *has_more = PJ_FALSE;
    output->size = 0;
    output->type = PJMEDIA_FRAME_TYPE_NONE;

    if (vpx->enc_frame_len == 0) {
        /*
         * For now we assume that we have only one cx data here
         * Which is probably fine as we do not ask encoder to bufferize
         */
        //PJ_LOG(4, (THIS_FILE, "Encode one frame at %p", vpx->enc_iter));
        pkt = vpx_codec_get_cx_data(&vpx->encoder, &vpx->enc_iter);
        if (pkt == NULL ) {
            if (!vpx->encoder.err) {
                PJ_LOG(3, (THIS_FILE, "Encoder packet dropped"));
                return PJ_SUCCESS;
            } else {
                PJ_LOG(1, (THIS_FILE, "Failed to get cx datas : %s", vpx_codec_err_to_string(vpx->encoder.err)));
                return PJMEDIA_CODEC_EFAILED;
            }
        } else if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) {
            pj_memcpy(vpx->enc_buf, pkt->data.frame.buf, pkt->data.frame.sz);
            vpx->enc_frame_len = pkt->data.frame.sz;
            vpx->enc_processed = 0;
            vpx->enc_buf_is_keyframe = !!(pkt->data.frame.flags & VPX_FRAME_IS_KEY) ? PJ_TRUE : PJ_FALSE;

            //PJ_LOG(4, (THIS_FILE, "Encoded with 0 byte : %d", ((pj_uint8_t*)(vpx->enc_buf))[0]));
        } else {
            PJ_LOG(6, (THIS_FILE, "Vpx packet kind %d not taken into account", pkt->kind));
            return PJ_SUCCESS;
        }
    }
    // TODO we should support if iter not over too

    if(vpx->enc_frame_len > 0) {
        //PJ_LOG(4, (THIS_FILE, "We have an enc_frame : %d; max : %d", vpx->enc_frame_len, vpx->param.enc_mtu));
        /* Reserve 1 octet for vp8 packetization info */
        unsigned max_size = vpx->param.enc_mtu - 1;
        unsigned remaining_size = vpx->enc_frame_len - vpx->enc_processed;
        /* TODO : we could equally distributed packets sizes */
        unsigned payload_len = PJ_MIN(remaining_size, max_size);
        pj_uint8_t* p = (pj_uint8_t*) output->buf;
        pj_uint8_t* s = (pj_uint8_t*) vpx->enc_buf;
        //PJ_LOG(4, (THIS_FILE, "Payload : %d", payload_len));

        output->type = PJMEDIA_FRAME_TYPE_VIDEO;
        output->bit_info = 0;
        if (vpx->enc_buf_is_keyframe) {
            output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME;
        }
        /* Set vp8 packetization info */
        p[0] = 0;
        if(vpx->enc_processed == 0)
            p[0] |= 0x10;
        if(!vpx->enc_buf_is_keyframe)
            p[0] |= 0x20;

        pj_memcpy( (p + 1), (s + vpx->enc_processed), payload_len);
        output->size = payload_len + 1;

        vpx->enc_processed += payload_len;
        *has_more = !(vpx->enc_processed == vpx->enc_frame_len);
    }

    //PJ_LOG(4, (THIS_FILE, "Encoded size %d", output->size));

    return PJ_SUCCESS;
}
예제 #11
0
파일: dsound.c 프로젝트: deveck/Deveck.TAM
/*
 * Initialize DirectSound player device.
 */
static pj_status_t init_player_stream( struct dsound_stream *ds_strm,
				       int dev_id,
				       unsigned clock_rate,
				       unsigned channel_count,
				       unsigned samples_per_frame,
				       unsigned buffer_count)
{
    HRESULT hr;
    HWND hwnd;
    PCMWAVEFORMAT pcmwf; 
    DSBUFFERDESC dsbdesc;
    DSBPOSITIONNOTIFY dsPosNotify[MAX_PACKET_BUFFER_COUNT];
    unsigned bytes_per_frame;
    unsigned max_latency;
    unsigned i;


    PJ_ASSERT_RETURN(buffer_count <= MAX_PACKET_BUFFER_COUNT, PJ_EINVAL);

    /* Check device ID */
    if (dev_id == -1)
	dev_id = 0;

    PJ_ASSERT_RETURN(dev_id>=0 && dev_id < (int)dev_count, PJ_EINVAL);

    /*
     * Create DirectSound device.
     */
    hr = DirectSoundCreate(dev_info[dev_id].lpGuid, &ds_strm->ds.play.lpDs, 
			   NULL);
    if (FAILED(hr))
	return PJ_RETURN_OS_ERROR(hr);

    hwnd = GetForegroundWindow();
    if (hwnd == NULL) {
	hwnd = GetDesktopWindow();
    }    
    hr = IDirectSound_SetCooperativeLevel( ds_strm->ds.play.lpDs, hwnd, 
					   DSSCL_PRIORITY);
    if FAILED(hr)
	return PJ_RETURN_OS_ERROR(hr);
    
    /*
     * Set up wave format structure for initialize DirectSound play
     * buffer. 
     */
    init_waveformatex(&pcmwf, clock_rate, channel_count);
    bytes_per_frame = samples_per_frame * BYTES_PER_SAMPLE;

    /* Set up DSBUFFERDESC structure. */
    pj_bzero(&dsbdesc, sizeof(DSBUFFERDESC)); 
    dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
    dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLPOSITIONNOTIFY |
		      DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;

    dsbdesc.dwBufferBytes = buffer_count * bytes_per_frame;
    dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&pcmwf; 

    /*
     * Create DirectSound playback buffer. 
     */
    hr = IDirectSound_CreateSoundBuffer(ds_strm->ds.play.lpDs, &dsbdesc, 
					&ds_strm->ds.play.lpDsBuffer, NULL); 
    if (FAILED(hr) )
	return PJ_RETURN_OS_ERROR(hr);

    /*
     * Create event for play notification.
     */
    ds_strm->hEvent = CreateEvent( NULL, FALSE, FALSE, NULL);
    if (ds_strm->hEvent == NULL)
	return pj_get_os_error();

    /*
     * Setup notification for play.
     */
    hr = IDirectSoundBuffer_QueryInterface( ds_strm->ds.play.lpDsBuffer, 
					    &IID_IDirectSoundNotify, 
					    (LPVOID *)&ds_strm->lpDsNotify); 
    if (FAILED(hr))
	return PJ_RETURN_OS_ERROR(hr);

    
    for (i=0; i<buffer_count; ++i) {
	dsPosNotify[i].dwOffset = i * bytes_per_frame;
	dsPosNotify[i].hEventNotify = ds_strm->hEvent;
    }
    
    hr = IDirectSoundNotify_SetNotificationPositions( ds_strm->lpDsNotify, 
						      buffer_count, 
						      dsPosNotify);
    if (FAILED(hr))
	return PJ_RETURN_OS_ERROR(hr);


    hr = IDirectSoundBuffer_SetCurrentPosition(ds_strm->ds.play.lpDsBuffer, 0);
    if (FAILED(hr))
	return PJ_RETURN_OS_ERROR(hr);


    ds_strm->dwBytePos = 0;
    ds_strm->dwDsBufferSize = buffer_count * bytes_per_frame;
    ds_strm->timestamp.u64 = 0;
    /*
     * Play latency does not need to be on a frame boundry, it is just how far
     * ahead of the read pointer we set the write pointer.  So we should just
     * use the user configured latency.  However, if the latency measured in
     * bytes causes more buffers than we are allowed, we must cap the latency
     * at the time contained in 1-buffer_count.
     */
    max_latency = (1 - buffer_count) * samples_per_frame * 1000 / clock_rate /
        channel_count;
    ds_strm->latency = PJ_MIN(max_latency, snd_output_latency);

    /* Done setting up play device. */
    PJ_LOG(5,(THIS_FILE, 
	      " DirectSound player \"%s\" initialized (clock_rate=%d, "
	      "channel_count=%d, samples_per_frame=%d (%dms))",
	      dev_info[dev_id].info.name,
	      clock_rate, channel_count, samples_per_frame,
	      samples_per_frame * 1000 / clock_rate));

    return PJ_SUCCESS;
}