From eaa4d06187e300b0a6187c6253edf573a3389ad8 Mon Sep 17 00:00:00 2001 From: Benny Prijono Date: Sun, 30 Mar 2008 08:58:58 +0000 Subject: More ticket #504: added missing new files! git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@1901 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/include/pjmedia/stereo.h | 206 ++++++++++++++++++++++ pjmedia/src/pjmedia/stereo_port.c | 207 ++++++++++++++++++++++ pjsip-apps/src/samples/stereotest.c | 334 ++++++++++++++++++++++++++++++++++++ 3 files changed, 747 insertions(+) create mode 100644 pjmedia/include/pjmedia/stereo.h create mode 100644 pjmedia/src/pjmedia/stereo_port.c create mode 100644 pjsip-apps/src/samples/stereotest.c diff --git a/pjmedia/include/pjmedia/stereo.h b/pjmedia/include/pjmedia/stereo.h new file mode 100644 index 00000000..96eb3699 --- /dev/null +++ b/pjmedia/include/pjmedia/stereo.h @@ -0,0 +1,206 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_STEREO_H__ +#define __PJMEDIA_STEREO_H__ + +/** + * @file stereo.h + * @brief Monochannel and multichannel converter. + */ + +#include +#include +#include +#include + + +/** + * @defgroup PJMEDIA_STEREO Monochannel and multichannel audio frame converter + * @ingroup PJMEDIA_MISC + * @brief Interface for converting monochannel audio frame to multichannel + * audio frame and vice versa. + * @{ + * + */ + +PJ_BEGIN_DECL + + +/** + * Multichannel to monochannel conversion mixes samples from all channels + * into the monochannel. + */ +#define PJMEDIA_STEREO_MIX PJ_TRUE + + + +/** + * Multichannel to monochannel conversion, it has two operation mode specified + * by param options, @see pjmedia_stereo_options. This function can work safely + * using the same buffer (in place conversion). + * + * @param mono Output buffer to store the mono frame extracted + * from the multichannels frame. + * @param multi Input frame containing multichannels audio. + * @param channel_count Number of channels in the input frame. + * @param samples_per_frame Number of samples in the input frame. + * @param mix If the value is PJ_TRUE then the input channels + * will be mixed to produce output frame, otherwise + * only frame from channel_src will be copied to the + * output frame. + * @param channel_src When mixing is disabled, the mono output frame + * will be copied from this channel number. + * + * @return PJ_SUCCESS on success; + */ +PJ_INLINE(pj_status_t) pjmedia_convert_channel_nto1(pj_int16_t mono[], + const pj_int16_t multi[], + unsigned channel_count, + unsigned samples_per_frame, + pj_bool_t mix, + unsigned channel_src) +{ + unsigned i; + + PJ_ASSERT_RETURN(mono && multi && channel_count && samples_per_frame && + channel_src < channel_count, PJ_EINVAL); + + if (mix==PJ_FALSE) { + for (i = channel_src; i < samples_per_frame; i += channel_count) { + *mono = multi[i]; + ++mono; + } + } else { + unsigned j; + for (i = 0; i < samples_per_frame; i += channel_count) { + int tmp = 0; + for(j = 0; j < channel_count; ++j) + tmp += multi[i+j]; + + if (tmp > 32767) tmp = 32767; + else if (tmp < -32768) tmp = -32768; + *mono = (pj_int16_t) tmp; + ++mono; + } + } + + return PJ_SUCCESS; +} + + +/** + * Monochannel to multichannel conversion, it will just duplicate the samples + * from monochannel frame to all channels in the multichannel frame. + * This function can work safely using the same buffer (in place conversion) + * as long as the buffer is big enough for the multichannel samples. + * + * @param multi Output buffer to store the multichannels frame + * mixed from the mono frame. + * @param mono The input monochannel audio frame. + * @param channel_count Desired number of channels in the output frame. + * @param samples_per_frame Number of samples in the input frame. + * @param options Options for conversion, currently must be zero. + * + * @return PJ_SUCCESS on success; + */ +PJ_INLINE(pj_status_t) pjmedia_convert_channel_1ton(pj_int16_t multi[], + const pj_int16_t mono[], + unsigned channel_count, + unsigned samples_per_frame, + unsigned options) +{ + const pj_int16_t *src; + + PJ_ASSERT_RETURN(mono && multi && channel_count && samples_per_frame, + PJ_EINVAL); + PJ_ASSERT_RETURN(options == 0, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + src = mono + samples_per_frame - 1; + samples_per_frame *= channel_count; + while (samples_per_frame) { + unsigned i; + for (i=1; i<=channel_count; ++i) + multi[samples_per_frame-i] = *src; + samples_per_frame -= channel_count; + --src; + } + + return PJ_SUCCESS; +} + + +/** + * Options for channel converter port. The @pjmedia_stereo_options is also + * valid for this port options. + */ +enum pjmedia_stereo_port_options +{ + /** + * Specifies whether this port should not destroy downstream port when + * this port is destroyed. + */ + PJMEDIA_STEREO_DONT_DESTROY_DN = 4 +}; + + +/** + * Create a mono-multi channel converter port. This creates a converter session, + * which will adjust the samples of audio frame to a different channel count + * when the port's get_frame() and put_frame() is called. + * + * When the port's get_frame() is called, this port will get a frame from + * the downstream port and convert the frame to the target channel count before + * returning it to the caller. + * + * When the port's put_frame() is called, this port will convert the frame + * to the downstream port's channel count before giving the frame to the + * downstream port. + * + * @param pool Pool to allocate the structure and buffers. + * @param dn_port The downstream port, which channel count is to + * be converted to the target channel count. + * @param channel_count This port channel count. + * @param options Bitmask flags from #pjmedia_stereo_port_options + * and also application may add PJMEDIA_STEREO_MIX + * to mix channels. + * When this flag is zero, the default behavior + * is to use simple N-to-1 channel converter and + * to destroy downstream port when this port is + * destroyed. + * @param p_port Pointer to receive the stereo port instance. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, + pjmedia_port *dn_port, + unsigned channel_count, + unsigned options, + pjmedia_port **p_port ); + +PJ_END_DECL + +/** + * @} + */ + + +#endif /* __PJMEDIA_STEREO_H__ */ + diff --git a/pjmedia/src/pjmedia/stereo_port.c b/pjmedia/src/pjmedia/stereo_port.c new file mode 100644 index 00000000..0e636420 --- /dev/null +++ b/pjmedia/src/pjmedia/stereo_port.c @@ -0,0 +1,207 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2007 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include + + +#define SIGNATURE PJMEDIA_PORT_SIGNATURE('S','T','R','O') + + +struct stereo_port +{ + pjmedia_port base; + pjmedia_port *dn_port; + unsigned options; + pj_int16_t *put_buf; + pj_int16_t *get_buf; +}; + + + +static pj_status_t stereo_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame); +static pj_status_t stereo_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t stereo_destroy(pjmedia_port *this_port); + + + +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(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS); + + /* Validate channel counts */ + PJ_ASSERT_RETURN(((dn_port->info.channel_count>1 && channel_count==1) || + (dn_port->info.channel_count==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 = dn_port->info.samples_per_frame * channel_count / + dn_port->info.channel_count; + + pjmedia_port_info_init(&sport->base.info, &name, SIGNATURE, + dn_port->info.clock_rate, + channel_count, + dn_port->info.bits_per_sample, + 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, dn_port->info.bytes_per_frame); + + /* See if we need buffer for get_frame */ + if (dn_port->info.channel_count > channel_count) { + sport->get_buf = (pj_int16_t*) + pj_pool_alloc(pool, dn_port->info.bytes_per_frame); + } + + /* 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; +} + +static pj_status_t stereo_put_frame(pjmedia_port *this_port, + const pjmedia_frame *frame) +{ + struct stereo_port *sport = (struct stereo_port*) this_port; + pjmedia_frame tmp_frame; + + /* Return if we don't have downstream port. */ + if (sport->dn_port == NULL) { + return PJ_SUCCESS; + } + + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { + tmp_frame.buf = sport->put_buf; + if (sport->dn_port->info.channel_count == 1) { + pjmedia_convert_channel_nto1(tmp_frame.buf, frame->buf, + sport->base.info.channel_count, + sport->base.info.samples_per_frame, + (sport->options & PJMEDIA_STEREO_MIX), + 0); + } else { + pjmedia_convert_channel_1ton(tmp_frame.buf, frame->buf, + sport->dn_port->info.channel_count, + sport->base.info.samples_per_frame, + sport->options); + } + tmp_frame.size = sport->dn_port->info.bytes_per_frame; + } else { + tmp_frame.buf = frame->buf; + tmp_frame.size = frame->size; + } + + tmp_frame.type = frame->type; + tmp_frame.timestamp.u64 = frame->timestamp.u64; + + return pjmedia_port_put_frame( sport->dn_port, &tmp_frame ); +} + + + +static pj_status_t stereo_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct stereo_port *sport = (struct stereo_port*) this_port; + 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; + } + + tmp_frame.buf = sport->get_buf? sport->get_buf : frame->buf; + tmp_frame.size = sport->dn_port->info.bytes_per_frame; + 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 (sport->base.info.channel_count == 1) { + pjmedia_convert_channel_nto1(frame->buf, tmp_frame.buf, + sport->dn_port->info.channel_count, + sport->dn_port->info.samples_per_frame, + (sport->options & PJMEDIA_STEREO_MIX), 0); + } else { + pjmedia_convert_channel_1ton(frame->buf, tmp_frame.buf, + sport->base.info.channel_count, + sport->dn_port->info.samples_per_frame, + sport->options); + } + + frame->size = sport->base.info.bytes_per_frame; + frame->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} + + +static pj_status_t stereo_destroy(pjmedia_port *this_port) +{ + struct stereo_port *sport = (struct stereo_port*) this_port; + + if ((sport->options & PJMEDIA_STEREO_DONT_DESTROY_DN)==0) { + pjmedia_port_destroy(sport->dn_port); + sport->dn_port = NULL; + } + + return PJ_SUCCESS; +} + diff --git a/pjsip-apps/src/samples/stereotest.c b/pjsip-apps/src/samples/stereotest.c new file mode 100644 index 00000000..583ff9c7 --- /dev/null +++ b/pjsip-apps/src/samples/stereotest.c @@ -0,0 +1,334 @@ +/* $Id$ */ +/* + * Copyright (C) 2003-2008 Benny Prijono + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * \page page_pjmedia_samples_stereo_c Samples: Using Stereo Port + * + * This example demonstrates how to use @ref PJMEDIA_STEREO_PORT to + * change the channel count of the media streams. + * + * This file is pjsip-apps/src/samples/stereotest.c + * + * \includelineno stereotest.c + */ + +#include +#include +#include + +#include +#include + +#include "util.h" + +#define REC_CLOCK_RATE 16000 +#define PTIME 10 + +#define MODE_PLAY 1 +#define MODE_RECORD 2 + + +/* For logging purpose. */ +#define THIS_FILE "stereotest.c" + + +static const char *desc = +" FILE \n" +" \n" +" stereotest.c \n" +" \n" +" PURPOSE \n" +" \n" +" Demonstrate how use stereo port to play a WAV file to sound \n" +" device or record to a WAV file from sound device with different \n" +" channel count. \n" +" \n" +" USAGE \n" +" \n" +" stereotest [options] WAV \n" +" \n" +" Options: \n" +" -m, --mode=N Operation mode: 1 = playing, 2 = recording.\n" +" -C, --rec-ch-cnt=N Number of channel for recording file. \n" +" -c, --snd-ch-cnt=N Number of channel for opening sound device.\n" +" \n"; + +int main(int argc, char *argv[]) +{ + pj_caching_pool cp; + pjmedia_endpt *med_endpt; + pj_pool_t *pool; + + pjmedia_port *file_port = NULL; + pjmedia_port *stereo_port = NULL; + pjmedia_snd_port *snd_port = NULL; + + int dev_id = -1; + char tmp[10]; + pj_status_t status; + + char *wav_file = NULL; + unsigned mode = 0; + unsigned rec_ch_cnt = 1; + unsigned snd_ch_cnt = 2; + + enum { + OPT_MODE = 'm', + OPT_REC_CHANNEL = 'C', + OPT_SND_CHANNEL = 'c', + }; + + struct pj_getopt_option long_options[] = { + { "mode", 1, 0, OPT_MODE }, + { "rec-ch-cnt", 1, 0, OPT_REC_CHANNEL }, + { "snd-ch-cnt", 1, 0, OPT_SND_CHANNEL }, + { NULL, 0, 0, 0 }, + }; + + int c; + int option_index; + + /* Must init PJLIB first: */ + status = pj_init(); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* Parse arguments */ + pj_optind = 0; + while((c=pj_getopt_long(argc,argv, "m:C:c:", long_options, &option_index))!=-1) { + + switch (c) { + case OPT_MODE: + if (mode) { + app_perror(THIS_FILE, "Cannot record and play at once!", + PJ_EINVAL); + return 1; + } + mode = atoi(pj_optarg); + break; + + case OPT_REC_CHANNEL: + rec_ch_cnt = atoi(pj_optarg); + break; + + case OPT_SND_CHANNEL: + snd_ch_cnt = atoi(pj_optarg); + break; + + default: + printf("Invalid options %s\n", argv[pj_optind]); + puts(desc); + return 1; + } + + } + + wav_file = argv[pj_optind]; + + /* Verify arguments. */ + if (!wav_file) { + app_perror(THIS_FILE, "WAV file not specified!", PJ_EINVAL); + puts(desc); + return 1; + } + if (!snd_ch_cnt || !rec_ch_cnt || rec_ch_cnt > 6) { + app_perror(THIS_FILE, "Invalid or too many channel count!", PJ_EINVAL); + puts(desc); + return 1; + } + if (mode != MODE_RECORD && mode != MODE_PLAY) { + app_perror(THIS_FILE, "Invalid operation mode!", PJ_EINVAL); + puts(desc); + return 1; + } + + /* Must create a pool factory before we can allocate any memory. */ + pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); + + /* + * Initialize media endpoint. + * This will implicitly initialize PJMEDIA too. + */ + status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + /* Create memory pool for our file player */ + pool = pj_pool_create( &cp.factory, /* pool factory */ + "app", /* pool name. */ + 4000, /* init size */ + 4000, /* increment size */ + NULL /* callback on error */ + ); + + if (mode == MODE_PLAY) { + /* Create WAVE file player port. */ + status = pjmedia_wav_player_port_create( pool, wav_file, PTIME, 0, + 0, &file_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open file", status); + return 1; + } + + /* Create sound player port. */ + status = pjmedia_snd_port_create_player( + pool, /* pool */ + dev_id, /* device id. */ + file_port->info.clock_rate, /* clock rate. */ + snd_ch_cnt, /* # of channels. */ + snd_ch_cnt * PTIME * /* samples per frame. */ + file_port->info.clock_rate / 1000, + file_port->info.bits_per_sample, /* bits per sample. */ + 0, /* options */ + &snd_port /* returned port */ + ); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open sound device", status); + return 1; + } + + if (snd_ch_cnt != file_port->info.channel_count) { + status = pjmedia_stereo_port_create( pool, + file_port, + snd_ch_cnt, + 0, + &stereo_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create stereo port", status); + return 1; + } + + status = pjmedia_snd_port_connect(snd_port, stereo_port); + } else { + status = pjmedia_snd_port_connect(snd_port, file_port); + } + + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to connect sound port", status); + return 1; + } + + } else { + /* Create WAVE file writer port. */ + status = pjmedia_wav_writer_port_create(pool, wav_file, + REC_CLOCK_RATE, + rec_ch_cnt, + rec_ch_cnt * PTIME * + REC_CLOCK_RATE / 1000, + NBITS, + 0, 0, + &file_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open file", status); + return 1; + } + + /* Create sound player port. */ + status = pjmedia_snd_port_create_rec( + pool, /* pool */ + dev_id, /* device id. */ + REC_CLOCK_RATE, /* clock rate. */ + snd_ch_cnt, /* # of channels. */ + snd_ch_cnt * PTIME * + REC_CLOCK_RATE / 1000, /* samples per frame. */ + NBITS, /* bits per sample. */ + 0, /* options */ + &snd_port /* returned port */ + ); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to open sound device", status); + return 1; + } + + if (rec_ch_cnt != snd_ch_cnt) { + status = pjmedia_stereo_port_create( pool, + file_port, + snd_ch_cnt, + 0, + &stereo_port); + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to create stereo port", status); + return 1; + } + + status = pjmedia_snd_port_connect(snd_port, stereo_port); + } else { + status = pjmedia_snd_port_connect(snd_port, file_port); + } + + if (status != PJ_SUCCESS) { + app_perror(THIS_FILE, "Unable to connect sound port", status); + return 1; + } + } + + /* Dump memory usage */ + dump_pool_usage(THIS_FILE, &cp); + + /* + * File should be playing and looping now, using sound device's thread. + */ + + + /* Sleep to allow log messages to flush */ + pj_thread_sleep(100); + + printf("Mode = %s\n", (mode == MODE_PLAY? "playing" : "recording") ); + printf("File port channel count = %d\n", file_port->info.channel_count); + printf("Sound port channel count = %d\n", + pjmedia_snd_port_get_port(snd_port)->info.channel_count); + puts(""); + puts("Press to stop and quit"); + + fgets(tmp, sizeof(tmp), stdin); + + + /* Start deinitialization: */ + + + /* Destroy sound device */ + status = pjmedia_snd_port_destroy( snd_port ); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + + /* Destroy stereo port and file_port. + * Stereo port will destroy all downstream ports (e.g. the file port) + */ + status = pjmedia_port_destroy( stereo_port? stereo_port : file_port); + PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); + + + /* Release application pool */ + pj_pool_release( pool ); + + /* Destroy media endpoint. */ + pjmedia_endpt_destroy( med_endpt ); + + /* Destroy pool factory */ + pj_caching_pool_destroy( &cp ); + + /* Shutdown PJLIB */ + pj_shutdown(); + + + /* Done. */ + return 0; + +} + + + -- cgit v1.2.3