static pj_status_t file_get_frame(pjmedia_port *this_port, 
				  pjmedia_frame *frame)
{
    struct file_reader_port *fport = (struct file_reader_port*)this_port;
    pj_size_t frame_size;
    pj_status_t status = PJ_SUCCESS;

    pj_assert(fport->base.info.signature == SIGNATURE);
    pj_assert(frame->size <= fport->bufsize);

    /* EOF is set and readpos already passed the eofpos */
    if (fport->eof && fport->readpos >= fport->eofpos) {
	PJ_LOG(5,(THIS_FILE, "File port %.*s EOF",
		  (int)fport->base.info.name.slen,
		  fport->base.info.name.ptr));

	/* Call callback, if any */
	if (fport->cb2) {
	    pj_bool_t no_loop = (fport->options & PJMEDIA_FILE_NO_LOOP);

	    if (!fport->subscribed) {
	    	status = pjmedia_event_subscribe(NULL, &file_on_event,
	    				         fport, fport);
	    	fport->subscribed = (status == PJ_SUCCESS)? PJ_TRUE:
	    			    PJ_FALSE;
	    }

	    if (fport->subscribed && fport->eof != 2) {
	    	pjmedia_event event;

	    	if (no_loop) {
	    	    /* To prevent the callback from being called repeatedly */
	    	    fport->eof = 2;
	    	} else {
	    	    fport->eof = PJ_FALSE;
	    	}

	    	pjmedia_event_init(&event, PJMEDIA_EVENT_CALLBACK,
	                      	   NULL, fport);
	    	pjmedia_event_publish(NULL, fport, &event,
	                              PJMEDIA_EVENT_PUBLISH_POST_EVENT);
	    }
	    
	    /* Should not access player port after this since
	     * it might have been destroyed by the callback.
	     */
	    frame->type = PJMEDIA_FRAME_TYPE_NONE;
	    frame->size = 0;
	    
	    return (no_loop? PJ_EEOF: PJ_SUCCESS);

	} else if (fport->cb) {
	    status = (*fport->cb)(this_port, fport->base.port_data.pdata);
	}

	/* If callback returns non PJ_SUCCESS or 'no loop' is specified,
	 * return immediately (and don't try to access player port since
	 * it might have been destroyed by the callback).
	 */
	if ((status != PJ_SUCCESS) || (fport->options & PJMEDIA_FILE_NO_LOOP))
	{
	    frame->type = PJMEDIA_FRAME_TYPE_NONE;
	    frame->size = 0;
	    return PJ_EEOF;
	}

        /* Rewind file */
	PJ_LOG(5,(THIS_FILE, "File port %.*s rewinding..",
		  (int)fport->base.info.name.slen,
		  fport->base.info.name.ptr));
	fport->eof = PJ_FALSE;
    }

    //pj_assert(frame->size == fport->base.info.bytes_per_frame);
    if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
	frame_size = frame->size;
	//frame->size = frame_size;
    } else {
	/* Must be ULAW or ALAW */
	pj_assert(fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW || 
		  fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW);

	frame_size = frame->size >> 1;
	frame->size = frame_size << 1;
    }

    /* Copy frame from buffer. */
    frame->type = PJMEDIA_FRAME_TYPE_AUDIO;
    frame->timestamp.u64 = 0;

    if ((fport->readpos + frame_size) <= (fport->buf + fport->bufsize))
    {
	/* Read contiguous buffer. */
	pj_memcpy(frame->buf, fport->readpos, frame_size);

	/* Fill up the buffer if all has been read. */
	fport->readpos += frame_size;
	if (fport->readpos == fport->buf + fport->bufsize) {
	    fport->readpos = fport->buf;

	    status = fill_buffer(fport);
	    if (status != PJ_SUCCESS) {
		frame->type = PJMEDIA_FRAME_TYPE_NONE;
		frame->size = 0;
		fport->readpos = fport->buf + fport->bufsize;
		return status;
	    }
	}
    } else {
	unsigned endread;

	/* Split read.
	 * First stage: read until end of buffer. 
	 */
	endread = (unsigned)((fport->buf+fport->bufsize) - fport->readpos);
	pj_memcpy(frame->buf, fport->readpos, endread);

	/* End Of Buffer and EOF and NO LOOP */
	if (fport->eof && (fport->options & PJMEDIA_FILE_NO_LOOP)) {
	    fport->readpos += endread;

            if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_PCM) {
                pj_bzero((char*)frame->buf + endread, frame_size - endread);
            } else if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
                int val = pjmedia_linear2ulaw(0);
                pj_memset((char*)frame->buf + endread, val,
                          frame_size - endread);
            } else if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW) {
                int val = pjmedia_linear2alaw(0);
                pj_memset((char*)frame->buf + endread, val,
                          frame_size - endread);
            }

	    return PJ_SUCCESS;
	}

	/* Second stage: fill up buffer, and read from the start of buffer. */
	status = fill_buffer(fport);
	if (status != PJ_SUCCESS) {
	    frame->type = PJMEDIA_FRAME_TYPE_NONE;
	    frame->size = 0;
	    fport->readpos = fport->buf + fport->bufsize;
	    return status;
	}

	pj_memcpy(((char*)frame->buf)+endread, fport->buf, frame_size-endread);
	fport->readpos = fport->buf + (frame_size - endread);
    }

    if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW ||
	fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ALAW)
    {
	unsigned i;
	pj_uint16_t *dst;
	pj_uint8_t *src;

	dst = (pj_uint16_t*)frame->buf + frame_size - 1;
	src = (pj_uint8_t*)frame->buf + frame_size - 1;

	if (fport->fmt_tag == PJMEDIA_WAVE_FMT_TAG_ULAW) {
	    for (i = 0; i < frame_size; ++i) {
		*dst-- = (pj_uint16_t) pjmedia_ulaw2linear(*src--);
	    }
	} else {
	    for (i = 0; i < frame_size; ++i) {
		*dst-- = (pj_uint16_t) pjmedia_alaw2linear(*src--);
	    }
	}
    }

    return PJ_SUCCESS;
}