static pj_status_t stereo_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct stereo_port *sport = (struct stereo_port*) this_port; const pjmedia_audio_format_detail *s_afd, *dn_afd; pjmedia_frame tmp_frame; pj_status_t status; /* Return silence if we don't have downstream port */ if (sport->dn_port == NULL) { pj_bzero(frame->buf, frame->size); return PJ_SUCCESS; } s_afd = pjmedia_format_get_audio_format_detail(&this_port->info.fmt, 1); dn_afd = pjmedia_format_get_audio_format_detail(&sport->dn_port->info.fmt, 1); tmp_frame.buf = sport->get_buf? sport->get_buf : frame->buf; tmp_frame.size = PJMEDIA_PIA_AVG_FSZ(&sport->dn_port->info); tmp_frame.timestamp.u64 = frame->timestamp.u64; tmp_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; status = pjmedia_port_get_frame( sport->dn_port, &tmp_frame); if (status != PJ_SUCCESS) return status; if (tmp_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { frame->type = tmp_frame.type; frame->timestamp = tmp_frame.timestamp; frame->size = tmp_frame.size; if (tmp_frame.size && tmp_frame.buf == sport->get_buf) pj_memcpy(frame->buf, tmp_frame.buf, tmp_frame.size); return PJ_SUCCESS; } if (s_afd->channel_count == 1) { pjmedia_convert_channel_nto1((pj_int16_t*)frame->buf, (const pj_int16_t*)tmp_frame.buf, dn_afd->channel_count, PJMEDIA_AFD_SPF(s_afd), (sport->options & PJMEDIA_STEREO_MIX), 0); } else { pjmedia_convert_channel_1ton((pj_int16_t*)frame->buf, (const pj_int16_t*)tmp_frame.buf, s_afd->channel_count, PJMEDIA_AFD_SPF(dn_afd), sport->options); } frame->size = PJMEDIA_AFD_AVG_FSZ(s_afd); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; }
static pj_status_t ec_put_frame( pjmedia_port *this_port, pjmedia_frame *frame) { struct ec *ec = (struct ec*)this_port; PJ_ASSERT_RETURN(this_port->info.signature == SIGNATURE, PJ_EINVAL); if (frame->type == PJMEDIA_FRAME_TYPE_NONE ) { return pjmedia_port_put_frame(ec->dn_port, frame); } PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); pjmedia_echo_capture(ec->ec, (pj_int16_t*)frame->buf, 0); return pjmedia_port_put_frame(ec->dn_port, frame); }
/* Get a mono frame from a reversed phase channel (downstream direction). * The frame is put by put_frame() call to the splitcomb. */ static pj_status_t rport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; /* Update state */ op_update(rport, DIR_DOWNSTREAM, OP_GET); /* Return no frame if media flow on this direction is being * paused. */ if (rport->buf[DIR_DOWNSTREAM].paused) { frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; } /* Get frame from delay buffer */ frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->timestamp.u64 = rport->buf[DIR_DOWNSTREAM].ts.u64; return pjmedia_delay_buf_get(rport->buf[DIR_DOWNSTREAM].dbuf, (short*)frame->buf); }
PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, pjmedia_port *dn_port, unsigned channel_count, unsigned options, pjmedia_port **p_port ) { const pj_str_t name = pj_str("stereo"); struct stereo_port *sport; unsigned samples_per_frame; /* Validate arguments. */ PJ_ASSERT_RETURN(pool && dn_port && channel_count && p_port, PJ_EINVAL); /* Only supports 16bit samples per frame */ PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, PJMEDIA_ENCBITS); /* Validate channel counts */ PJ_ASSERT_RETURN(((PJMEDIA_PIA_CCNT(&dn_port->info)>1 && channel_count==1) || (PJMEDIA_PIA_CCNT(&dn_port->info)==1 && channel_count>1)), PJ_EINVAL); /* Create and initialize port. */ sport = PJ_POOL_ZALLOC_T(pool, struct stereo_port); PJ_ASSERT_RETURN(sport != NULL, PJ_ENOMEM); samples_per_frame = PJMEDIA_PIA_SPF(&dn_port->info) * channel_count / PJMEDIA_PIA_CCNT(&dn_port->info); pjmedia_port_info_init(&sport->base.info, &name, SIGNATURE, PJMEDIA_PIA_SRATE(&dn_port->info), channel_count, PJMEDIA_PIA_BITS(&dn_port->info), samples_per_frame); sport->dn_port = dn_port; sport->options = options; /* We always need buffer for put_frame */ sport->put_buf = (pj_int16_t*) pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); /* See if we need buffer for get_frame */ if (PJMEDIA_PIA_CCNT(&dn_port->info) > channel_count) { sport->get_buf = (pj_int16_t*) pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); } /* Media port interface */ sport->base.get_frame = &stereo_get_frame; sport->base.put_frame = &stereo_put_frame; sport->base.on_destroy = &stereo_destroy; /* Done */ *p_port = &sport->base; return PJ_SUCCESS; }
/* * Create file writer port. */ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool, const char *filename, unsigned sampling_rate, unsigned channel_count, unsigned samples_per_frame, unsigned bits_per_sample, unsigned flags, pj_ssize_t buff_size, pjmedia_port **p_port ) { struct file_port *fport; pjmedia_wave_hdr wave_hdr; pj_ssize_t size; pj_str_t name; pj_status_t status; /* Check arguments. */ PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL); /* Only supports 16bits per sample for now. * See flush_buffer(). */ PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL); /* Create file port instance. */ fport = PJ_POOL_ZALLOC_T(pool, struct file_port); PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM); /* Initialize port info. */ pj_strdup2(pool, &name, filename); pjmedia_port_info_init(&fport->base.info, &name, SIGNATURE, sampling_rate, channel_count, bits_per_sample, samples_per_frame); fport->base.get_frame = &file_get_frame; fport->base.put_frame = &file_put_frame; fport->base.on_destroy = &file_on_destroy; if (flags == PJMEDIA_FILE_WRITE_ALAW) { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ALAW; fport->bytes_per_sample = 1; } else if (flags == PJMEDIA_FILE_WRITE_ULAW) { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_ULAW; fport->bytes_per_sample = 1; } else { fport->fmt_tag = PJMEDIA_WAVE_FMT_TAG_PCM; fport->bytes_per_sample = 2; } /* Open file in write and read mode. * We need the read mode because we'll modify the WAVE header once * the recording has completed. */ status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd); if (status != PJ_SUCCESS) return status; /* Initialize WAVE header */ pj_bzero(&wave_hdr, sizeof(pjmedia_wave_hdr)); wave_hdr.riff_hdr.riff = PJMEDIA_RIFF_TAG; wave_hdr.riff_hdr.file_len = 0; /* will be filled later */ wave_hdr.riff_hdr.wave = PJMEDIA_WAVE_TAG; wave_hdr.fmt_hdr.fmt = PJMEDIA_FMT_TAG; wave_hdr.fmt_hdr.len = 16; wave_hdr.fmt_hdr.fmt_tag = (pj_uint16_t)fport->fmt_tag; wave_hdr.fmt_hdr.nchan = (pj_int16_t)channel_count; wave_hdr.fmt_hdr.sample_rate = sampling_rate; wave_hdr.fmt_hdr.bytes_per_sec = sampling_rate * channel_count * fport->bytes_per_sample; wave_hdr.fmt_hdr.block_align = (pj_uint16_t) (fport->bytes_per_sample * channel_count); wave_hdr.fmt_hdr.bits_per_sample = (pj_uint16_t) (fport->bytes_per_sample * 8); wave_hdr.data_hdr.data = PJMEDIA_DATA_TAG; wave_hdr.data_hdr.len = 0; /* will be filled later */ /* Convert WAVE header from host byte order to little endian * before writing the header. */ pjmedia_wave_hdr_host_to_file(&wave_hdr); /* Write WAVE header */ if (fport->fmt_tag != PJMEDIA_WAVE_FMT_TAG_PCM) { pjmedia_wave_subchunk fact_chunk; pj_uint32_t tmp = 0; fact_chunk.id = PJMEDIA_FACT_TAG; fact_chunk.len = 4; PJMEDIA_WAVE_NORMALIZE_SUBCHUNK(&fact_chunk); /* Write WAVE header without DATA chunk header */ size = sizeof(pjmedia_wave_hdr) - sizeof(wave_hdr.data_hdr); status = pj_file_write(fport->fd, &wave_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } /* Write FACT chunk if it stores compressed data */ size = sizeof(fact_chunk); status = pj_file_write(fport->fd, &fact_chunk, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } size = 4; status = pj_file_write(fport->fd, &tmp, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } /* Write DATA chunk header */ size = sizeof(wave_hdr.data_hdr); status = pj_file_write(fport->fd, &wave_hdr.data_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } } else { size = sizeof(pjmedia_wave_hdr); status = pj_file_write(fport->fd, &wave_hdr, &size); if (status != PJ_SUCCESS) { pj_file_close(fport->fd); return status; } } /* Set buffer size. */ if (buff_size < 1) buff_size = PJMEDIA_FILE_PORT_BUFSIZE; fport->bufsize = buff_size; /* Check that buffer size is greater than bytes per frame */ pj_assert(fport->bufsize >= PJMEDIA_PIA_AVG_FSZ(&fport->base.info)); /* Allocate buffer and set initial write position */ fport->buf = (char*) pj_pool_alloc(pool, fport->bufsize); if (fport->buf == NULL) { pj_file_close(fport->fd); return PJ_ENOMEM; } fport->writepos = fport->buf; /* Done. */ *p_port = &fport->base; PJ_LOG(4,(THIS_FILE, "File writer '%.*s' created: samp.rate=%d, bufsize=%uKB", (int)fport->base.info.name.slen, fport->base.info.name.ptr, PJMEDIA_PIA_SRATE(&fport->base.info), fport->bufsize / 1000)); return PJ_SUCCESS; }
/* * Put a frame in the reverse port (upstream direction). This frame * will be picked up by get_frame() above. */ static pj_status_t rport_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; pj_assert(frame->size <= PJMEDIA_PIA_AVG_FSZ(&rport->base.info)); /* Handle NULL frame */ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. */ if (++rport->buf[DIR_UPSTREAM].null_cnt > rport->max_null_frames) { /* Prevent the counter from overflowing and resetting back * to zero */ rport->buf[DIR_UPSTREAM].null_cnt = rport->max_null_frames + 1; return PJ_SUCCESS; } /* Write zero port to delaybuf so that it doesn't underflow. * If we don't do this, get_frame() on this direction will * cause delaybuf to generate missing frame and the last * frame transmitted to delaybuf will be replayed multiple * times, which doesn't sound good. */ /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_UPSTREAM].paused) return PJ_SUCCESS; /* Generate zero frame. */ pjmedia_zero_samples(rport->tmp_up_buf, PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, rport->tmp_up_buf); } /* Not sure how to handle partial frame, so better reject for now */ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* Reset NULL frame counter */ rport->buf[DIR_UPSTREAM].null_cnt = 0; /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_UPSTREAM].paused) return PJ_SUCCESS; /* Unfortunately must copy to temporary buffer since delay buf * modifies the frame content. */ pjmedia_copy_samples(rport->tmp_up_buf, (const pj_int16_t*)frame->buf, PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, rport->tmp_up_buf); }
/* * Get a multichannel frame upstream. * This will get mono channel frame from each port and put the * mono frame into the multichannel frame. */ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; pj_bool_t has_frame = PJ_FALSE; /* Read frame from each port */ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; pjmedia_frame mono_frame; pj_status_t status; if (!port) { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); } else if (sc->port_desc[ch].reversed == PJ_FALSE) { /* Read from normal port */ mono_frame.buf = sc->get_buf; mono_frame.size = PJMEDIA_PIA_AVG_FSZ(&port->info); mono_frame.timestamp.u64 = frame->timestamp.u64; status = pjmedia_port_get_frame(port, &mono_frame); if (status != PJ_SUCCESS || mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = mono_frame.timestamp.u64; } else { /* Read from temporary buffer for reverse port */ struct reverse_port *rport = (struct reverse_port*)port; /* Update rport state. */ op_update(rport, DIR_UPSTREAM, OP_GET); if (!rport->buf[DIR_UPSTREAM].paused) { pjmedia_delay_buf_get(rport->buf[DIR_UPSTREAM].dbuf, sc->get_buf); } else { pjmedia_zero_samples(sc->get_buf, PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64; } /* Combine the mono frame into multichannel frame */ store_mono_frame(sc->get_buf, (pj_int16_t*)frame->buf, ch, PJMEDIA_PIA_CCNT(&this_port->info), PJMEDIA_PIA_SPF(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); has_frame = PJ_TRUE; } /* Return NO_FRAME is we don't get any frames from downstream ports */ if (has_frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); } else frame->type = PJMEDIA_FRAME_TYPE_NONE; return PJ_SUCCESS; }
/* * "Write" a multichannel frame downstream. This would split * the multichannel frame into individual mono channel, and write * it to the appropriate port. */ static pj_status_t put_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; /* Handle null frame */ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) { for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) continue; if (!sc->port_desc[ch].reversed) { pjmedia_port_put_frame(port, frame); } else { struct reverse_port *rport = (struct reverse_port*)port; /* Update the number of NULL frames received. Once we have too * many of this, we'll stop calling op_update() to let the * media be suspended. */ if (++rport->buf[DIR_DOWNSTREAM].null_cnt > rport->max_null_frames) { /* Prevent the counter from overflowing and resetting * back to zero */ rport->buf[DIR_DOWNSTREAM].null_cnt = rport->max_null_frames + 1; continue; } /* Write zero port to delaybuf so that it doesn't underflow. * If we don't do this, get_frame() on this direction will * cause delaybuf to generate missing frame and the last * frame transmitted to delaybuf will be replayed multiple * times, which doesn't sound good. */ /* Update rport state. */ op_update(rport, DIR_DOWNSTREAM, OP_PUT); /* Discard frame if rport is paused on this direction */ if (rport->buf[DIR_DOWNSTREAM].paused) continue; /* Generate zero frame. */ pjmedia_zero_samples(sc->put_buf, PJMEDIA_PIA_SPF(&port->info)); /* Put frame to delay buffer */ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, sc->put_buf); } } return PJ_SUCCESS; } /* Not sure how we would handle partial frame, so better reject * it for now. */ PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* * Write mono frame into each channels */ for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) continue; /* Extract the mono frame to temporary buffer */ extract_mono_frame((const pj_int16_t*)frame->buf, sc->put_buf, ch, PJMEDIA_PIA_CCNT(&this_port->info), (unsigned)frame->size * 8 / PJMEDIA_PIA_BITS(&this_port->info) / PJMEDIA_PIA_CCNT(&this_port->info)); if (!sc->port_desc[ch].reversed) { /* Write to normal port */ pjmedia_frame mono_frame; mono_frame.buf = sc->put_buf; mono_frame.size = frame->size / PJMEDIA_PIA_CCNT(&this_port->info); mono_frame.type = frame->type; mono_frame.timestamp.u64 = frame->timestamp.u64; /* Write */ pjmedia_port_put_frame(port, &mono_frame); } else { /* Write to reversed phase port */ struct reverse_port *rport = (struct reverse_port*)port; /* Reset NULL frame counter */ rport->buf[DIR_DOWNSTREAM].null_cnt = 0; /* Update rport state. */ op_update(rport, DIR_DOWNSTREAM, OP_PUT); if (!rport->buf[DIR_DOWNSTREAM].paused) { pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, sc->put_buf); } } } return PJ_SUCCESS; }
/* * Create reverse phase port for the specified channel. */ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, pjmedia_port *splitcomb, unsigned ch_num, unsigned options, pjmedia_port **p_chport) { const pj_str_t name = pj_str("scomb-rev"); struct splitcomb *sc = (struct splitcomb*) splitcomb; struct reverse_port *rport; unsigned buf_cnt; const pjmedia_audio_format_detail *sc_afd, *p_afd; pjmedia_port *port; pj_status_t status; /* Sanity check */ PJ_ASSERT_RETURN(pool && splitcomb, PJ_EINVAL); /* Make sure this is really a splitcomb port */ PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL); /* Check the channel number */ PJ_ASSERT_RETURN(ch_num < PJMEDIA_PIA_CCNT(&sc->base.info), PJ_EINVAL); /* options is unused for now */ PJ_UNUSED_ARG(options); sc_afd = pjmedia_format_get_audio_format_detail(&splitcomb->info.fmt, 1); /* Create the port */ rport = PJ_POOL_ZALLOC_T(pool, struct reverse_port); rport->parent = sc; rport->ch_num = ch_num; /* Initialize port info... */ port = &rport->base; pjmedia_port_info_init(&port->info, &name, SIGNATURE_PORT, sc_afd->clock_rate, 1, sc_afd->bits_per_sample, PJMEDIA_PIA_SPF(&splitcomb->info) / sc_afd->channel_count); p_afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1); /* ... and the callbacks */ port->put_frame = &rport_put_frame; port->get_frame = &rport_get_frame; port->on_destroy = &rport_on_destroy; /* Buffer settings */ buf_cnt = options & 0xFF; if (buf_cnt == 0) buf_cnt = MAX_BUF_CNT; rport->max_burst = MAX_BURST; rport->max_null_frames = MAX_NULL_FRAMES; /* Create downstream/put buffers */ status = pjmedia_delay_buf_create(pool, "scombdb-dn", p_afd->clock_rate, PJMEDIA_PIA_SPF(&port->info), p_afd->channel_count, buf_cnt * p_afd->frame_time_usec / 1000, 0, &rport->buf[DIR_DOWNSTREAM].dbuf); if (status != PJ_SUCCESS) { return status; } /* Create upstream/get buffers */ status = pjmedia_delay_buf_create(pool, "scombdb-up", p_afd->clock_rate, PJMEDIA_PIA_SPF(&port->info), p_afd->channel_count, buf_cnt * p_afd->frame_time_usec / 1000, 0, &rport->buf[DIR_UPSTREAM].dbuf); if (status != PJ_SUCCESS) { pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf); return status; } /* And temporary upstream/get buffer */ rport->tmp_up_buf = (pj_int16_t*) pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&port->info)); /* Save port in the splitcomb */ sc->port_desc[ch_num].port = &rport->base; sc->port_desc[ch_num].reversed = PJ_TRUE; /* Done */ *p_chport = port; return status; }